From dc59fc5706c2ea0835d4d42c86ede72ad2abc217 Mon Sep 17 00:00:00 2001 From: H0zen Date: Mon, 13 Jul 2026 20:12:32 +0300 Subject: [PATCH] ACE removal - phase 2/2 --- .github/workflows/core_codestyle.yml | 16 +- .github/workflows/core_linux_build.yml | 49 +- .github/workflows/core_windows_build.yml | 39 +- .github/workflows/docker_build.yml | 50 +- CLAUDE.md | 52 +- CMakeLists.txt | 37 +- cmake/MangosParams.cmake | 6 +- cmake/StatusInfo.cmake | 9 +- src/baker/CMakeLists.txt | 31 +- src/game/AuctionHouseBot/AuctionHouseBot.cpp | 22 +- src/game/AuctionHouseBot/AuctionHouseBot.h | 2 + src/game/BattleGround/BattleGround.cpp | 11 +- src/game/BattleGround/BattleGround.h | 3 + src/game/BattleGround/BattleGroundAA.h | 5 +- src/game/BattleGround/BattleGroundAB.cpp | 1 + src/game/BattleGround/BattleGroundBE.h | 3 +- src/game/BattleGround/BattleGroundEY.h | 7 +- src/game/BattleGround/BattleGroundMgr.cpp | 7 +- src/game/BattleGround/BattleGroundMgr.h | 14 +- src/game/BattleGround/BattleGroundMgrUtil.cpp | 2 +- src/game/BattleGround/BattleGroundNA.h | 3 +- src/game/BattleGround/BattleGroundQueue.cpp | 5 - src/game/BattleGround/BattleGroundRL.h | 3 +- src/game/BattleGround/BattleGroundReward.cpp | 2 + src/game/ChatCommands/AccountCommands.cpp | 3 + .../ChatCommands/AuctionHouseCommands.cpp | 2 + src/game/ChatCommands/BanAndKickCommands.cpp | 2 + src/game/ChatCommands/CastAndAuraCommands.cpp | 1 + .../ChatCommands/CommunicationCommands.cpp | 2 + src/game/ChatCommands/CreatureCommands.cpp | 7 +- src/game/ChatCommands/DebugCommands.cpp | 7 +- src/game/ChatCommands/EventCommands.cpp | 2 + src/game/ChatCommands/GMCommands.cpp | 3 + src/game/ChatCommands/GMTicketCommands.cpp | 11 +- src/game/ChatCommands/GameObjectCommands.cpp | 4 + src/game/ChatCommands/GridCommands.cpp | 2 +- src/game/ChatCommands/GuildCommands.cpp | 1 + src/game/ChatCommands/InstanceCommands.cpp | 4 + src/game/ChatCommands/ListCommands.cpp | 3 +- src/game/ChatCommands/LookupCommands.cpp | 3 + src/game/ChatCommands/MMapCommands.cpp | 2 + src/game/ChatCommands/MailCommands.cpp | 4 + .../ChatCommands/MiscellanousCommands.cpp | 1 + .../PlayerAndCreatureCommands.cpp | 1 + src/game/ChatCommands/PlayerCommands.cpp | 5 + src/game/ChatCommands/PlayerHonorCommands.cpp | 1 + src/game/ChatCommands/PlayerLearnCommands.cpp | 3 + src/game/ChatCommands/PlayerMiscCommands.cpp | 3 +- src/game/ChatCommands/PlayerTitleCommands.cpp | 2 + src/game/ChatCommands/PoolCommands.cpp | 1 + src/game/ChatCommands/ReloadCommands.cpp | 4 +- src/game/ChatCommands/SelectCommands.cpp | 1 + src/game/ChatCommands/ServerCommands.cpp | 5 +- .../TeleportationAndPositionCommands.cpp | 3 + src/game/ChatCommands/TriggerCommands.cpp | 1 + src/game/ChatCommands/WaypointCommands.cpp | 2 + src/game/Maps/MapUpdater.cpp | 209 +++-- src/game/Maps/MapUpdater.h | 93 +-- .../FleeingMovementGenerator.cpp | 1 + src/game/MotionGenerators/MotionFrame.cpp | 111 ++- src/game/MotionGenerators/MotionFrame.h | 26 +- src/game/MotionGenerators/MotionMaster.cpp | 4 +- src/game/MotionGenerators/MotionMaster.h | 1 + src/game/MotionGenerators/PathFinder.cpp | 4 + .../TargetedMovementGenerator.h | 6 +- .../WaypointMovementGenerator.cpp | 2 + src/game/Object/AggressorAI.cpp | 2 +- src/game/Object/AggressorAI.h | 1 - src/game/Object/ArenaTeam.cpp | 3 + src/game/Object/ArenaTeam.h | 3 + src/game/Object/AuctionHouseMgr.cpp | 8 +- src/game/Object/AuctionHouseMgr.h | 4 + src/game/Object/Bag.cpp | 1 + src/game/Object/Camera.cpp | 1 + src/game/Object/Camera.h | 2 + src/game/Object/Corpse.cpp | 2 + src/game/Object/Corpse.h | 1 + src/game/Object/Creature.cpp | 107 ++- src/game/Object/Creature.h | 16 +- src/game/Object/CreatureAI.cpp | 1 + src/game/Object/CreatureAISelector.cpp | 3 +- src/game/Object/CreatureEventAI.cpp | 1 + src/game/Object/CreatureEventAI.h | 2 + src/game/Object/CreatureEventAIMgr.cpp | 4 +- src/game/Object/CreatureSpellCooldown.cpp | 1 + src/game/Object/CreatureVendorTrainer.cpp | 1 + src/game/Object/Formulas.h | 1 + src/game/Object/GMTicketMgr.cpp | 4 +- src/game/Object/GMTicketMgr.h | 2 + src/game/Object/GameObject.cpp | 9 +- src/game/Object/GameObject.h | 3 + src/game/Object/GameObjectDestructible.cpp | 1 + src/game/Object/GameObjectUpdate.cpp | 1 + src/game/Object/GameObjectUse.cpp | 2 + src/game/Object/Guild.cpp | 3 + src/game/Object/Guild.h | 4 + src/game/Object/GuildBank.cpp | 3 + src/game/Object/GuildRank.cpp | 1 + src/game/Object/HonorMgr.cpp | 1 + src/game/Object/HonorMgr.h | 1 + src/game/Object/Item.cpp | 2 + src/game/Object/Item.h | 1 + src/game/Object/ItemEnchantmentMgr.cpp | 1 + src/game/Object/ItemPersistence.cpp | 2 + src/game/Object/ItemPrototype.h | 2 + src/game/Object/LootMgr.cpp | 2 + src/game/Object/LootMgr.h | 3 + src/game/Object/Object.cpp | 1 + src/game/Object/Object.h | 4 +- src/game/Object/ObjectAccessor.cpp | 16 +- src/game/Object/ObjectAccessor.h | 28 +- src/game/Object/ObjectGuid.cpp | 14 + src/game/Object/ObjectGuid.h | 4 + src/game/Object/ObjectMgr.cpp | 27 +- src/game/Object/ObjectMgr.h | 7 +- src/game/Object/ObjectMgrAreaTrigger.cpp | 1 + src/game/Object/ObjectMgrCreatures.cpp | 11 +- src/game/Object/ObjectMgrGameObjects.cpp | 5 - src/game/Object/ObjectMgrGameTele.cpp | 1 + src/game/Object/ObjectMgrGossipMenu.cpp | 3 + src/game/Object/ObjectMgrGraveyard.cpp | 1 + src/game/Object/ObjectMgrInstanceData.cpp | 5 +- src/game/Object/ObjectMgrItems.cpp | 2 + src/game/Object/ObjectMgrLocaleStrings.cpp | 1 + src/game/Object/ObjectMgrLocales.cpp | 1 + src/game/Object/ObjectMgrNameCheck.cpp | 1 + src/game/Object/ObjectMgrPlayerInfo.cpp | 1 + src/game/Object/ObjectMgrQuests.cpp | 2 + src/game/Object/ObjectMgrText.cpp | 2 + src/game/Object/ObjectMgrTrainerVendor.cpp | 1 + src/game/Object/ObjectPosSelector.cpp | 1 + src/game/Object/ObjectUpdate.cpp | 1 + src/game/Object/ObjectValues.cpp | 1 + src/game/Object/Pet.cpp | 1 + src/game/Object/Pet.h | 11 +- src/game/Object/PetAI.cpp | 2 + src/game/Object/PetDatabase.cpp | 9 +- src/game/Object/PetSpells.cpp | 1 + src/game/Object/Player.cpp | 8 +- src/game/Object/Player.h | 18 +- src/game/Object/PlayerChannel.cpp | 2 + src/game/Object/PlayerDeath.cpp | 1 + src/game/Object/PlayerDuel.cpp | 2 + src/game/Object/PlayerGossip.cpp | 1 + src/game/Object/PlayerGroup.cpp | 1 + src/game/Object/PlayerItemStorage.cpp | 1 + src/game/Object/PlayerItemValidation.cpp | 1 + src/game/Object/PlayerLoad.cpp | 8 +- src/game/Object/PlayerLogger.cpp | 3 +- src/game/Object/PlayerLoot.cpp | 1 + src/game/Object/PlayerMail.cpp | 1 + src/game/Object/PlayerMovement.cpp | 2 +- src/game/Object/PlayerPvP.cpp | 1 + src/game/Object/PlayerQuest.cpp | 2 + src/game/Object/PlayerRest.cpp | 1 + src/game/Object/PlayerSave.cpp | 9 +- src/game/Object/PlayerSpell.cpp | 1 + src/game/Object/PlayerStats.cpp | 8 +- src/game/Object/PlayerTaxi.cpp | 3 + src/game/Object/PlayerVisibility.cpp | 1 + src/game/Object/PlayerZone.cpp | 1 + src/game/Object/ReputationMgr.h | 1 + src/game/Object/SocialMgr.cpp | 2 +- src/game/Object/SocialMgr.h | 2 + src/game/Object/SpellCooldownMgr.cpp | 1 + src/game/Object/SpellCooldownMgr.h | 1 + src/game/Object/SpellMgr.cpp | 2 + src/game/Object/SpellMgr.h | 2 + src/game/Object/SpellMgrData.cpp | 1 + src/game/Object/SpellMgrProc.cpp | 1 + src/game/Object/StatSystem.cpp | 5 +- src/game/Object/Totem.cpp | 1 + src/game/Object/Unit.cpp | 14 +- src/game/Object/Unit.h | 5 + src/game/Object/UnitAura.cpp | 2 + src/game/Object/UnitCombat.cpp | 12 +- src/game/Object/UnitDamage.cpp | 1 + src/game/Object/UnitMeleeDamage.cpp | 5 +- src/game/Object/WorldObjectPosition.cpp | 1 + src/game/Object/WorldObjectSummon.cpp | 1 + src/game/OutdoorPvP/OutdoorPvP.h | 2 + src/game/OutdoorPvP/OutdoorPvPEP.cpp | 1 + src/game/OutdoorPvP/OutdoorPvPEP.h | 1 + src/game/OutdoorPvP/OutdoorPvPHP.cpp | 1 + src/game/OutdoorPvP/OutdoorPvPHP.h | 1 + src/game/OutdoorPvP/OutdoorPvPMgr.cpp | 2 +- src/game/OutdoorPvP/OutdoorPvPMgr.h | 1 + src/game/OutdoorPvP/OutdoorPvPNA.cpp | 1 + src/game/OutdoorPvP/OutdoorPvPNA.h | 1 + src/game/OutdoorPvP/OutdoorPvPTF.cpp | 1 + src/game/OutdoorPvP/OutdoorPvPTF.h | 1 + src/game/References/MapRefManager.h | 2 + src/game/Server/DBCStores.cpp | 12 +- src/game/Server/DBCStores.h | 1 + src/game/Server/DBCStructure_reference.h | 2 + src/game/Server/Opcodes.h | 3 +- src/game/Server/WorldSession.cpp | 34 +- src/game/Server/WorldSession.h | 13 +- src/game/Server/WorldSocket.cpp | 707 ++++------------ src/game/Server/WorldSocket.h | 267 +++--- src/game/Server/WorldSocketMgr.cpp | 197 +---- src/game/Server/WorldSocketMgr.h | 79 +- src/game/Time/GameTime.cpp | 2 + src/game/Time/GameTime.h | 1 + src/game/Time/UpdateTime.cpp | 6 +- src/game/Tools/CharacterDatabaseCleaner.cpp | 1 + src/game/Tools/CharacterDatabaseCleaner.h | 2 + src/game/Tools/PlayerDump.cpp | 7 + src/game/Tools/PlayerDump.h | 2 + src/game/Warden/Modules/WardenModuleMac.h | 2 + src/game/Warden/Modules/WardenModuleWin.h | 2 + src/game/Warden/Warden.cpp | 3 + src/game/Warden/Warden.h | 2 +- src/game/Warden/WardenCheckMgr.cpp | 16 +- src/game/Warden/WardenCheckMgr.h | 4 +- src/game/Warden/WardenMac.cpp | 2 + src/game/Warden/WardenWin.cpp | 3 + src/game/Warden/WardenWin.h | 1 + src/game/WorldHandlers/AccountMgr.cpp | 2 +- src/game/WorldHandlers/AccountMgr.h | 1 + src/game/WorldHandlers/AddonHandler.cpp | 2 +- src/game/WorldHandlers/ArenaTeamHandler.cpp | 1 + .../WorldHandlers/AuctionHouseHandler.cpp | 5 + src/game/WorldHandlers/CellImpl.h | 1 - src/game/WorldHandlers/Channel.cpp | 13 +- src/game/WorldHandlers/Channel.h | 1 + src/game/WorldHandlers/ChannelHandler.cpp | 1 + src/game/WorldHandlers/ChannelMgr.cpp | 3 +- src/game/WorldHandlers/ChannelMgr.h | 1 + src/game/WorldHandlers/CharacterHandler.cpp | 3 + .../CharacterHandlerCustomize.cpp | 1 + src/game/WorldHandlers/Chat.cpp | 5 + src/game/WorldHandlers/Chat.h | 4 + src/game/WorldHandlers/ChatArgExtract.cpp | 6 + src/game/WorldHandlers/ChatHandler.cpp | 3 + src/game/WorldHandlers/ChatHelp.cpp | 2 + src/game/WorldHandlers/ChatMessage.cpp | 8 +- .../WorldHandlers/ChatMessageValidation.cpp | 4 + src/game/WorldHandlers/CommandMgr.cpp | 2 +- src/game/WorldHandlers/CreatureLinkingMgr.cpp | 3 +- src/game/WorldHandlers/CreatureLinkingMgr.h | 3 +- src/game/WorldHandlers/DisableMgr.cpp | 6 +- src/game/WorldHandlers/DuelHandler.cpp | 1 + src/game/WorldHandlers/DynamicCollision.cpp | 2 + src/game/WorldHandlers/FusedTerrain.cpp | 4 + src/game/WorldHandlers/GMTicketHandler.cpp | 1 + src/game/WorldHandlers/GameEventMgr.cpp | 4 +- src/game/WorldHandlers/GameEventMgr.h | 7 + src/game/WorldHandlers/GameObjectModel.cpp | 4 + src/game/WorldHandlers/GossipDef.cpp | 1 + src/game/WorldHandlers/GossipDef.h | 2 + src/game/WorldHandlers/GridMap.cpp | 10 +- src/game/WorldHandlers/GridMap.h | 10 +- src/game/WorldHandlers/GridNotifiers.cpp | 1 + src/game/WorldHandlers/GridNotifiers.h | 2 + src/game/WorldHandlers/Group.cpp | 2 + src/game/WorldHandlers/Group.h | 6 + src/game/WorldHandlers/GroupHandler.cpp | 1 + src/game/WorldHandlers/GroupMemberState.cpp | 1 + src/game/WorldHandlers/GuildHandler.cpp | 3 +- src/game/WorldHandlers/GuildMgr.cpp | 2 +- src/game/WorldHandlers/GuildMgr.h | 1 + src/game/WorldHandlers/InstanceData.cpp | 1 + src/game/WorldHandlers/ItemHandler.cpp | 1 + src/game/WorldHandlers/ItemHandlerVendor.cpp | 1 + src/game/WorldHandlers/LFGHandler.cpp | 1 + src/game/WorldHandlers/LootHandler.cpp | 4 + src/game/WorldHandlers/Mail.cpp | 2 + src/game/WorldHandlers/Mail.h | 3 + src/game/WorldHandlers/MailHandler.cpp | 6 +- src/game/WorldHandlers/Map.cpp | 39 +- src/game/WorldHandlers/Map.h | 8 +- src/game/WorldHandlers/MapManager.cpp | 30 +- src/game/WorldHandlers/MapManager.h | 24 +- .../WorldHandlers/MapPersistentStateMgr.cpp | 9 +- .../WorldHandlers/MapPersistentStateMgr.h | 6 +- src/game/WorldHandlers/MassMailMgr.cpp | 4 +- src/game/WorldHandlers/MassMailMgr.h | 1 + src/game/WorldHandlers/MiscHandler.cpp | 4 +- src/game/WorldHandlers/MiscHandlerSocial.cpp | 1 + src/game/WorldHandlers/MoveMap.cpp | 5 + src/game/WorldHandlers/MovementHandler.cpp | 2 + src/game/WorldHandlers/NPCHandler.cpp | 2 + src/game/WorldHandlers/ObjectGridLoader.cpp | 3 +- src/game/WorldHandlers/PetHandler.cpp | 3 + src/game/WorldHandlers/PetitionsHandler.cpp | 17 +- src/game/WorldHandlers/PoolManager.cpp | 5 +- src/game/WorldHandlers/PoolManager.h | 6 + src/game/WorldHandlers/QueryHandler.cpp | 2 + src/game/WorldHandlers/QuestDef.h | 2 + src/game/WorldHandlers/ScriptAction.cpp | 11 +- src/game/WorldHandlers/ScriptMgr.cpp | 4 +- src/game/WorldHandlers/ScriptMgr.h | 16 +- src/game/WorldHandlers/ScriptMgrBinding.cpp | 15 +- src/game/WorldHandlers/ScriptMgrDbScripts.cpp | 6 +- src/game/WorldHandlers/SkillDiscovery.cpp | 3 + src/game/WorldHandlers/Spell.h | 4 + src/game/WorldHandlers/SpellAuraControl.cpp | 1 + src/game/WorldHandlers/SpellAuraPeriodic.cpp | 6 +- src/game/WorldHandlers/SpellAuras.cpp | 4 + src/game/WorldHandlers/SpellAuras.h | 1 + src/game/WorldHandlers/SpellCast.cpp | 1 + .../SpellEffectDamageTeleport.cpp | 2 +- src/game/WorldHandlers/SpellEffectDummy.cpp | 2 + .../WorldHandlers/SpellEffectHealPower.cpp | 3 +- .../WorldHandlers/SpellEffectObjectCombat.cpp | 1 + .../SpellEffectSkillEnchantPet.cpp | 4 + .../WorldHandlers/SpellEffectSummonLock.cpp | 22 +- src/game/WorldHandlers/SpellEffectTail.cpp | 5 +- src/game/WorldHandlers/SpellPackets.cpp | 1 + src/game/WorldHandlers/SpellTargetList.cpp | 1 + src/game/WorldHandlers/SpellTargeting.cpp | 3 + src/game/WorldHandlers/TaxiHandler.cpp | 1 + src/game/WorldHandlers/TransportSystem.cpp | 86 +- src/game/WorldHandlers/TransportSystem.h | 37 +- src/game/WorldHandlers/Transports.cpp | 527 +++++++++++- src/game/WorldHandlers/Transports.h | 108 ++- .../WorldHandlers/UnitAuraProcHandler.cpp | 18 +- src/game/WorldHandlers/WaypointManager.cpp | 3 +- src/game/WorldHandlers/WaypointManager.h | 3 + src/game/WorldHandlers/Weather.cpp | 3 +- src/game/WorldHandlers/World.cpp | 482 ++++++----- src/game/WorldHandlers/World.h | 102 ++- src/game/WorldHandlers/WorldConfig.cpp | 2 + src/game/movement/MoveSpline.cpp | 4 + src/game/movement/MoveSpline.h | 2 + src/game/movement/MoveSplineFlag.h | 1 + src/game/movement/spline.cpp | 3 + src/game/movement/spline.h | 1 + src/game/movement/util.cpp | 1 + src/game/terrain/Accelerators.cpp | 4 + src/game/terrain/Accelerators.hpp | 1 + src/game/terrain/CollisionModel.cpp | 5 + src/game/terrain/Terrain.hpp | 1 + src/game/terrain/TileSerializer.cpp | 5 + src/game/terrain/WmoModel.cpp | 3 + src/mangosd/AFThread.cpp | 84 -- src/mangosd/AFThread.h | 46 -- src/mangosd/CMakeLists.txt | 13 +- src/mangosd/CliThread.cpp | 165 ---- src/mangosd/Master.cpp | 764 ++++++++++++++++++ src/mangosd/Master.h | 96 +++ src/mangosd/RASession.cpp | 332 ++++++++ src/mangosd/RASession.h | 151 ++++ src/mangosd/RAThread.cpp | 438 ---------- src/mangosd/SOAP/SoapThread.cpp | 13 +- src/mangosd/SOAP/SoapThread.h | 1 - src/mangosd/Service.h | 77 ++ src/mangosd/WorldThread.cpp | 130 --- src/mangosd/mangosd.conf.dist.in | 11 + src/mangosd/mangosd.cpp | 738 ++++++----------- src/modules/Bots/CMakeLists.txt | 2 +- src/modules/Bots/ahbot/AhBot.cpp | 20 +- src/modules/Bots/ahbot/AhBotConfig.cpp | 1 - src/modules/Bots/ahbot/AhBotConfig.h | 5 + src/modules/Bots/playerbot/GuildTaskMgr.cpp | 1 - .../Bots/playerbot/PlayerbotAIConfig.cpp | 1 - .../Bots/playerbot/PlayerbotCommandServer.cpp | 114 +-- .../Bots/playerbot/PlayerbotDbStore.cpp | 1 - .../Bots/playerbot/RandomPlayerbotMgr.cpp | 1 - src/modules/Immersive/CMakeLists.txt | 4 - src/modules/Immersive/ImmersiveConfig.cpp | 1 - src/modules/Immersive/ImmersiveServer.cpp | 143 ++-- src/modules/Immersive/immersive.cpp | 1 - src/realmd | 2 +- src/shared/Auth/AuthCrypt.cpp | 2 + src/shared/Auth/BigNumber.cpp | 1 + src/shared/Auth/HMACSHA1.cpp | 1 + src/shared/Auth/HMACSHA1.h | 1 + src/shared/Auth/OpenSSLProvider.cpp | 1 + src/shared/Auth/OpenSSLProvider.h | 1 + src/shared/Auth/Sha1.cpp | 2 + src/shared/Auth/Sha1.h | 1 + src/shared/Auth/WardenKeyGeneration.h | 1 + src/shared/CMakeLists.txt | 74 +- src/shared/Common/Common.cpp | 1 + src/shared/Common/Common.h | 69 +- src/shared/Config/Config.cpp | 166 ++-- src/shared/Config/Config.h | 27 +- src/shared/DataStores/DBCFileLoader.cpp | 2 + src/shared/Database/Database.cpp | 27 +- src/shared/Database/Database.h | 34 +- src/shared/Database/DatabaseMysql.cpp | 7 +- src/shared/Database/DatabaseMysql.h | 5 +- src/shared/Database/Field.h | 3 + src/shared/Database/QueryResult.h | 2 + src/shared/Database/QueryResultMysql.h | 1 + src/shared/Database/SQLStorage.cpp | 2 + src/shared/Database/SQLStorage.h | 2 + src/shared/Database/SQLStorageImpl.h | 3 + src/shared/Database/SqlDelayThread.cpp | 25 +- src/shared/Database/SqlDelayThread.h | 6 +- src/shared/Database/SqlOperations.cpp | 38 +- src/shared/Database/SqlOperations.h | 38 +- src/shared/Database/SqlPreparedStatement.cpp | 5 + src/shared/Database/SqlPreparedStatement.h | 3 +- src/shared/Dynamic/FactoryHolder.h | 1 + src/shared/Dynamic/ObjectRegistry.h | 2 +- src/shared/GameSystem/NGrid.h | 2 +- src/shared/GameSystem/TypeContainer.h | 1 + src/shared/GameSystem/TypeContainerVisitor.h | 1 + src/shared/Linux/PosixDaemon.cpp | 11 + src/shared/LockedQueue/LockedQueue.h | 77 +- src/shared/Log/ConsoleLogWriter.cpp | 22 +- src/shared/Log/ConsoleLogWriter.h | 11 +- src/shared/Log/Log.cpp | 103 ++- src/shared/Log/Log.h | 57 +- src/shared/Platform/Define.h | 79 +- src/shared/Policies/CreationPolicy.h | 194 ----- src/shared/Policies/ObjectLifeTime.cpp | 39 - src/shared/Policies/ObjectLifeTime.h | 83 -- src/shared/Policies/Singleton.h | 157 +--- src/shared/Policies/ThreadingModel.h | 277 ------- src/shared/Threading/DelayExecutor.cpp | 208 ----- src/shared/Threading/DelayExecutor.h | 59 -- src/shared/Threading/ThreadLocalStore.h | 70 ++ src/shared/Threading/Threading.cpp | 354 +------- src/shared/Threading/Threading.h | 168 +--- src/shared/Utilities/ByteBuffer.cpp | 12 +- src/shared/Utilities/ByteBuffer.h | 5 + src/shared/Utilities/ByteConverter.h | 2 +- src/shared/Utilities/ConsoleStyle.cpp | 307 +++++++ src/shared/Utilities/ConsoleStyle.h | 137 ++++ src/shared/Utilities/Errors.h | 27 +- src/shared/Utilities/EventProcessor.cpp | 1 + .../Utilities/LinkedReference/Reference.h | 1 + src/shared/Utilities/ProgressBar.cpp | 64 +- src/shared/Utilities/ProgressBar.h | 35 + src/shared/Utilities/RNGen.h | 20 +- src/shared/Utilities/ScheduledExit.cpp | 2 + src/shared/Utilities/StartupUI.cpp | 556 +++++++++++++ src/shared/Utilities/StartupUI.h | 141 ++++ src/shared/Utilities/StartupUIDemo.cpp | 154 ++++ src/shared/Utilities/Timer.h | 1 + src/shared/Utilities/Util.cpp | 34 +- src/shared/Utilities/Util.h | 15 +- src/shared/Win/WheatyExceptionReport.cpp | 1 + src/shared/Win/WheatyExceptionReport.h | 3 + src/shared/net/BindAddress.cpp | 81 ++ src/shared/net/BindAddress.hpp | 51 ++ src/shared/net/FlowControl.hpp | 99 +++ src/shared/net/ISession.hpp | 147 ++++ src/shared/net/SendQueue.hpp | 165 ++++ src/shared/net/Server.hpp | 83 ++ src/shared/net/iocp/IocpServer.cpp | 412 ++++++++++ src/shared/net/iocp/IocpServer.hpp | 187 +++++ src/shared/net/reactor/Connection.hpp | 95 +++ src/shared/net/reactor/EpollPoller.cpp | 120 +++ .../net/reactor/EpollPoller.hpp} | 52 +- src/shared/net/reactor/KqueuePoller.cpp | 127 +++ .../net/reactor/KqueuePoller.hpp} | 54 +- src/shared/net/reactor/Poller.hpp | 88 ++ .../net/reactor/PollerFactory.cpp} | 49 +- src/shared/net/reactor/ReactorServer.cpp | 441 ++++++++++ src/shared/net/reactor/ReactorServer.hpp | 100 +++ src/shared/net/uring/UringServer.cpp | 433 ++++++++++ src/shared/net/uring/UringServer.hpp | 173 ++++ 457 files changed, 9771 insertions(+), 5278 deletions(-) delete mode 100644 src/mangosd/AFThread.cpp delete mode 100644 src/mangosd/AFThread.h delete mode 100644 src/mangosd/CliThread.cpp create mode 100644 src/mangosd/Master.cpp create mode 100644 src/mangosd/Master.h create mode 100644 src/mangosd/RASession.cpp create mode 100644 src/mangosd/RASession.h delete mode 100644 src/mangosd/RAThread.cpp create mode 100644 src/mangosd/Service.h delete mode 100644 src/mangosd/WorldThread.cpp delete mode 100644 src/shared/Policies/CreationPolicy.h delete mode 100644 src/shared/Policies/ObjectLifeTime.cpp delete mode 100644 src/shared/Policies/ObjectLifeTime.h delete mode 100644 src/shared/Policies/ThreadingModel.h delete mode 100644 src/shared/Threading/DelayExecutor.cpp delete mode 100644 src/shared/Threading/DelayExecutor.h create mode 100644 src/shared/Threading/ThreadLocalStore.h create mode 100644 src/shared/Utilities/ConsoleStyle.cpp create mode 100644 src/shared/Utilities/ConsoleStyle.h create mode 100644 src/shared/Utilities/StartupUI.cpp create mode 100644 src/shared/Utilities/StartupUI.h create mode 100644 src/shared/Utilities/StartupUIDemo.cpp create mode 100644 src/shared/net/BindAddress.cpp create mode 100644 src/shared/net/BindAddress.hpp create mode 100644 src/shared/net/FlowControl.hpp create mode 100644 src/shared/net/ISession.hpp create mode 100644 src/shared/net/SendQueue.hpp create mode 100644 src/shared/net/Server.hpp create mode 100644 src/shared/net/iocp/IocpServer.cpp create mode 100644 src/shared/net/iocp/IocpServer.hpp create mode 100644 src/shared/net/reactor/Connection.hpp create mode 100644 src/shared/net/reactor/EpollPoller.cpp rename src/{mangosd/RAThread.h => shared/net/reactor/EpollPoller.hpp} (56%) create mode 100644 src/shared/net/reactor/KqueuePoller.cpp rename src/{mangosd/CliThread.h => shared/net/reactor/KqueuePoller.hpp} (52%) create mode 100644 src/shared/net/reactor/Poller.hpp rename src/{mangosd/WorldThread.h => shared/net/reactor/PollerFactory.cpp} (53%) create mode 100644 src/shared/net/reactor/ReactorServer.cpp create mode 100644 src/shared/net/reactor/ReactorServer.hpp create mode 100644 src/shared/net/uring/UringServer.cpp create mode 100644 src/shared/net/uring/UringServer.hpp diff --git a/.github/workflows/core_codestyle.yml b/.github/workflows/core_codestyle.yml index cce46ecd96..ed352bf376 100644 --- a/.github/workflows/core_codestyle.yml +++ b/.github/workflows/core_codestyle.yml @@ -1,21 +1,25 @@ name: Codestyle Checks + permissions: contents: read + on: push: - branches: [ master ] + branches: [master, devel] pull_request: - branches: [ master ] + branches: [master] + +concurrency: + group: codestyle-${{ github.ref }} + cancel-in-progress: true jobs: check-codestyle: - strategy: - fail-fast: false - runs-on: ubuntu-latest name: Check Codestyling steps: - - uses: actions/checkout@v2 + # v2 runs on a long-deprecated Node runtime and is scheduled for removal. + - uses: actions/checkout@v4 - name: Check Codestyling run: source ./apps/ci/ci-codestyle.sh diff --git a/.github/workflows/core_linux_build.yml b/.github/workflows/core_linux_build.yml index 43e1fe570d..17b9aaf991 100644 --- a/.github/workflows/core_linux_build.yml +++ b/.github/workflows/core_linux_build.yml @@ -2,13 +2,19 @@ name: Linux Build (GCC + Clang) on: push: - branches: [ master, devel ] + branches: [master, devel] pull_request: - branches: [ master ] + branches: [master] permissions: contents: read +# A second push to the same branch makes the first run's result worthless. Cancel it rather +# than pay for two builds and read the stale one. +concurrency: + group: linux-build-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ubuntu-latest @@ -34,21 +40,35 @@ jobs: submodules: recursive fetch-depth: 1 - - name: Cache CMake build + # ccache, not a cached build directory. + # + # The old workflow cached _build itself, which cannot work with make: actions/checkout + # rewrites every source file, so all sources end up NEWER than the restored objects and + # make rebuilds the lot anyway -- we paid to upload and download a build tree for zero + # reuse. Worse, the key hashed only **/CMakeLists.txt, and actions/cache does not + # re-save on an exact key hit, so once populated that tree was frozen for good. + # + # ccache keys on the *content* of each translation unit, so it is immune to both + # problems. The key carries github.sha (always unique => always saved) while + # restore-keys pulls the newest previous cache to seed it. + - name: Cache ccache uses: actions/cache@v4 with: - path: _build - key: ${{ runner.os }}-${{ matrix.name }}-cmake-${{ hashFiles('**/CMakeLists.txt') }} + path: ~/.cache/ccache + key: ${{ runner.os }}-${{ matrix.name }}-ccache-${{ github.sha }} restore-keys: | - ${{ runner.os }}-${{ matrix.name }}-cmake- + ${{ runner.os }}-${{ matrix.name }}-ccache- + # libace-dev is deliberately absent: ACE was removed from the core (see "ACE removal"), + # and the single remaining "ace/..." include in the tree belongs to the Movemap + # generator, which BUILD_TOOLS=0 below no longer builds. - name: Install build dependencies run: | sudo apt-get update -qq sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ - git cmake make build-essential \ + git cmake make build-essential ccache \ libssl-dev libbz2-dev default-libmysqlclient-dev \ - libace-dev libreadline-dev \ + libreadline-dev \ ${{ matrix.pkg }} - name: Configure project @@ -60,18 +80,25 @@ jobs: -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_C_COMPILER=${{ matrix.cc }} \ -DCMAKE_CXX_COMPILER=${{ matrix.cxx }} \ - -DBUILD_TOOLS=1 \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ + -DBUILD_TOOLS=0 \ -DBUILD_MANGOSD=1 \ -DBUILD_REALMD=1 \ -DSOAP=1 \ -DSCRIPT_LIB_ELUNA=1 \ - -DSCRIPT_LIB_SD3_GATE=1 \ + -DSCRIPT_LIB_SD3=1 \ -DPLAYERBOTS=1 \ -DUSE_STORMLIB=1 \ - -DPCH=0 + -DPCH=0 \ + --warn-uninitialized - name: Build and install run: | cd _build make -j"$(nproc)" make install -j"$(nproc)" + + - name: ccache statistics + if: always() + run: ccache -s diff --git a/.github/workflows/core_windows_build.yml b/.github/workflows/core_windows_build.yml index e49d8848ba..21a4c7b874 100644 --- a/.github/workflows/core_windows_build.yml +++ b/.github/workflows/core_windows_build.yml @@ -5,25 +5,27 @@ permissions: on: push: - branches: [ master , devel ] + branches: [master, devel] pull_request: - branches: [ master ] + branches: [master] + +# Supersede an in-flight run when the branch moves on: its result is already stale. +concurrency: + group: windows-build-${{ github.ref }} + cancel-in-progress: true jobs: build: runs-on: windows-2022 - strategy: - fail-fast: false steps: - - name: Checkout repository uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 1 - + # Bump the -vN suffix to force a re-install of OpenSSL. - name: Cache OpenSSL id: cache-openssl uses: actions/cache@v4 @@ -31,25 +33,21 @@ jobs: path: | C:\Program Files\OpenSSL C:\Program Files\OpenSSL-Win64 - key: ${{ runner.os }}-openssl-full + key: ${{ runner.os }}-openssl-v1 - - - name: Install full OpenSSL (developer) + - name: Install OpenSSL (developer) if: steps.cache-openssl.outputs.cache-hit != 'true' shell: powershell run: | - Write-Host "Installing latest full OpenSSL for development..." choco upgrade openssl -y --no-progress $ver = & openssl version Write-Host "Installed OpenSSL version: $ver" - - name: Setup Windows 10 SDK uses: GuillaumeFalourd/setup-windows10-sdk-action@v2 with: sdk-version: 22621 - - name: Configure OpenSSL environment shell: bash run: | @@ -63,18 +61,11 @@ jobs: echo "OPENSSL_INCLUDE_DIR=C:/Program Files/OpenSSL-Win64/include" >> $GITHUB_ENV echo "OPENSSL_CRYPTO_LIBRARY=C:/Program Files/OpenSSL-Win64/lib/VC/libcrypto64MT.lib" >> $GITHUB_ENV echo "OPENSSL_SSL_LIBRARY=C:/Program Files/OpenSSL-Win64/lib/VC/libssl64MT.lib" >> $GITHUB_ENV + else + echo "::error::OpenSSL not found in either expected location" + exit 1 fi - - - name: Cache CMake build - uses: actions/cache@v4 - with: - path: build - key: ${{ runner.os }}-cmake-${{ hashFiles('**/CMakeLists.txt') }} - restore-keys: | - ${{ runner.os }}-cmake- - - - name: Configure CMake project shell: bash run: | @@ -82,9 +73,8 @@ jobs: cmake .. \ -G "Visual Studio 17 2022" \ -A x64 \ - -DCMAKE_BUILD_TYPE=Release \ -DOPENSSL_USE_STATIC_LIBS=TRUE \ - -DBUILD_TOOLS=1 \ + -DBUILD_TOOLS=0 \ -DBUILD_MANGOSD=1 \ -DBUILD_REALMD=1 \ -DSOAP=1 \ @@ -94,7 +84,6 @@ jobs: -DUSE_STORMLIB=1 \ -DPCH=0 - - name: Build project shell: bash run: | diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml index 19e4fe038d..b8e200dcdc 100644 --- a/.github/workflows/docker_build.yml +++ b/.github/workflows/docker_build.yml @@ -1,31 +1,53 @@ name: Docker Build + +# This was the one workflow with no permissions block at all, so it ran with the repository's +# default token scope -- which can be write. It only builds images and pushes nothing, so +# read is all it ever needed. +permissions: + contents: read + on: push: - branches: [ master , devel ] + branches: [master, devel] pull_request: - branches: [ master ] + branches: [master] + +concurrency: + group: docker-build-${{ github.ref }} + cancel-in-progress: true jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + # v2 runs on a deprecated Node runtime. + - name: Checkout repository + uses: actions/checkout@v4 with: - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Checkout Submodules - shell: bash - run: | - git submodule init && git submodule update + submodules: recursive + fetch-depth: 1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 + # Layer caching across runs. - name: Build Mangosd Docker image - run: | - docker build -t mangosd:latest -f dockercontainer/DockerFile-mangosd . + uses: docker/build-push-action@v6 + with: + context: . + file: dockercontainer/DockerFile-mangosd + tags: mangosd:latest + push: false + cache-from: type=gha,scope=mangosd + cache-to: type=gha,mode=max,scope=mangosd - name: Build Realmd Docker image - run: | - docker build -t realmd:latest -f dockercontainer/DockerFile-realmd . \ No newline at end of file + uses: docker/build-push-action@v6 + with: + context: . + file: dockercontainer/DockerFile-realmd + tags: realmd:latest + push: false + cache-from: type=gha,scope=realmd + cache-to: type=gha,mode=max,scope=realmd diff --git a/CLAUDE.md b/CLAUDE.md index 495b87dacd..aad7d89ddf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,8 +12,8 @@ this repo. Humans: also read [`doc/CodingStandard.md`](doc/CodingStandard.md). `Rel##_##_###_*.sql` migrations that chain via `db_version`. - Clone/update **recursively**: `dep`, `src/realmd`, `src/modules/{SD3,Eluna}`, `src/tools/Extractor_projects` and `win` are submodules. Never shallow-update a submodule to a non-tip pinned SHA. -- Less-obvious locations: out-of-process services in `src/ipc/` + `src/ah-service/`; scripting in - `src/modules/` (Eluna = Lua, SD3 = C++, Bots = playerbots). +- Less-obvious locations: scripting lives in `src/modules/` — Eluna (Lua) and SD3 (C++) are submodules; + Bots (playerbots) is in-tree. The AuctionHouseBot is in `src/game/AuctionHouseBot/`. ## Build & test @@ -22,20 +22,30 @@ this repo. Humans: also read [`doc/CodingStandard.md`](doc/CodingStandard.md). ```sh git clone --recursive https://github.com/mangosone/server.git && cd server -sudo apt-get install -y git cmake make build-essential \ - libssl-dev libbz2-dev default-libmysqlclient-dev libace-dev libreadline-dev # Debian/Ubuntu deps +sudo apt-get install -y git cmake make build-essential ccache \ + libssl-dev libbz2-dev default-libmysqlclient-dev libreadline-dev # Debian/Ubuntu deps mkdir -p _build _install && cd _build cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../_install \ - -DBUILD_TOOLS=1 -DBUILD_MANGOSD=1 -DBUILD_REALMD=1 -DSOAP=1 \ - -DSCRIPT_LIB_ELUNA=1 -DSCRIPT_LIB_SD3_GATE=1 -DPLAYERBOTS=1 \ - -DUSE_STORMLIB=1 -DBUILD_AH_SERVICE=1 -DPCH=0 + -DBUILD_TOOLS=0 -DBUILD_MANGOSD=1 -DBUILD_REALMD=1 -DSOAP=1 \ + -DSCRIPT_LIB_ELUNA=1 -DSCRIPT_LIB_SD3=1 -DPLAYERBOTS=1 \ + -DUSE_STORMLIB=1 -DPCH=0 make -j"$(nproc)" && make install -j"$(nproc)" ``` -Windows: use the EasyBuild helper in `win/`. **A PR MUST keep CI green:** the Linux build compiles with -**both** GCC and Clang, Windows builds on AppVeyor, and Codacy/CodeFactor gate quality. If you touch -`src/ipc/` or `src/ah-service/`, keep `ah-service --selftest` passing (it must print `intent codec selftest OK` -and `ipc selftest OK`). +Every `-D` above is a real option (see the `option(...)` block in `CMakeLists.txt`). CMake **silently ignores +an unknown `-D`** — it only mutters "Manually-specified variables were not used" at the end — so a misspelt +flag looks like it works while configuring nothing. Check the name against `CMakeLists.txt` before adding one. + +`BUILD_TOOLS=0`: the asset extractors are not used, and the Movemap generator is the last thing in the tree +that wanted ACE — which is why `libace-dev` is no longer in the dependency list either. + +Windows: use the EasyBuild helper in `win/`. **A PR MUST keep CI green.** CI is GitHub Actions +(`.github/workflows/`): `core_linux_build.yml` builds with **both GCC and Clang**, `core_windows_build.yml` +builds with MSVC, `core_codestyle.yml` checks the style rules below, and `docker_build.yml` builds the images. + +The GCC/Clang split matters more than it looks: the two standard libraries do not leak the same headers, so +a missing `#include` can pass on one and fail on the other. **Include what you use** — if a file names +`std::vector`, it includes `` itself rather than inheriting it from some header four levels up. ## Code style @@ -56,9 +66,12 @@ Source of truth: [`doc/CodingStandard.md`](doc/CodingStandard.md). Non-default r ## Architecture note -The **in-process AuctionHouseBot is the default**; the out-of-process AH service is additive and default-off -(`AH.Service.Enabled`). `mangosd` is the **sole authority** over game state — worker/service "intents" are -re-validated server-side before they are applied. +`mangosd` is the **sole authority** over game state. The world runs as a single heartbeat loop on the main +thread (`Master::WorldLoop`); everything else — the console, remote access, SOAP, the freeze watchdog, the +DB delay threads — is auxiliary and must not mutate world state directly. Anything arriving from another +thread is queued to the world thread (see `World::QueueCliCommand`) rather than applied where it landed. + +The AuctionHouseBot (`src/game/AuctionHouseBot/`) runs **in-process**. ## Logging @@ -76,8 +89,9 @@ Recommended runtime mode: `LogLevel=1` (quiet console) + `LogFileLevel=3` (buffe ## Review focus (for `@claude`) -Prioritise: **(1)** correctness/safety in `src/game/` handlers and anything touching live world/DB state or -the `mangosd`↔worker trust boundary; **(2)** coding-standard conformance above; **(3)** build/CI impact (GCC -*and* Clang, Windows, the AH self-test); **(4)** DB-migration correctness (use the `mangosone/database` -pattern). Keep feedback concrete and minimal-diff; flag correctness/standard issues, not style preferences -the standard doesn't cover. +Prioritise: **(1)** correctness/safety in `src/game/` handlers and anything touching live world or DB state, +especially cross-thread state (the shutdown signal, the CLI command queue, the DB delay threads); +**(2)** coding-standard conformance above; **(3)** build/CI impact — GCC *and* Clang *and* MSVC, including +missing `#include`s that only one standard library exposes; **(4)** DB-migration correctness (use the +`mangosone/database` pattern). Keep feedback concrete and minimal-diff; flag correctness/standard issues, not +style preferences the standard doesn't cover. diff --git a/CMakeLists.txt b/CMakeLists.txt index c45933f77b..0ab9660fe3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ cmake_policy(SET CMP0054 NEW) cmake_policy(SET CMP0063 NEW) # CMP0042 = MACOSX_RPATH (macOS only) — optional if(APPLE) - cmake_policy(SET CMP0042 NEW) + cmake_policy(SET CMP0042 NEW) endif() # ============================================================================= @@ -54,7 +54,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Define available cmake options below option(BUILD_MANGOSD "Build the main server" ON) option(BUILD_REALMD "Build the login server" ON) -option(BUILD_TOOLS "Build the map/vmap/mmap extractors" ON) +option(BUILD_TOOLS "Build the assets extractor (baker)" ON) +option(BUILD_TESTS "Build collision-related utilities" OFF) option(USE_STORMLIB "Use StormLib for reading MPQs" ON) option(SCRIPT_LIB_ELUNA "Compile with support for Eluna scripts" ON) option(SCRIPT_LIB_SD3 "Compile with support for ScriptDev3 scripts" ON) @@ -72,7 +73,8 @@ message( CMAKE_INSTALL_PREFIX Path where the server should be installed to BUILD_MANGOSD Build the main server BUILD_REALMD Build the login server - BUILD_TOOLS Build the map/vmap/mmap extractors + BUILD_TOOLS Build the assets extractor (baker) + BUILD_TESTS Build collision-related utilities USE_STORMLIB Use StormLib for reading MPQs SOAP Enable remote access via SOAP PCH Enable use of precompiled headers @@ -208,10 +210,35 @@ endif() # ============================================================================= # Subdirectories # ============================================================================= -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/dep/CMakeLists.txt") - add_subdirectory(dep) +if (NOT TARGET "ZLIB::ZLIB") + add_subdirectory(dep/zlib) endif() +if (NOT TARGET "BZip2::BZip2") + add_subdirectory(dep/bzip2) +endif() + + +add_subdirectory(dep/utf8cpp) + +if(BUILD_MANGOSD) + if(SCRIPT_LIB_ELUNA) + add_subdirectory(dep/lualib) + endif() + if(SOAP) + add_subdirectory(dep/gsoap) + endif() +endif() + +if(BUILD_MANGOSD OR BUILD_TOOLS) + add_subdirectory(dep/recastnavigation) +endif() + +if(BUILD_TOOLS) + add_subdirectory(dep/StormLib) +endif() + + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/src/CMakeLists.txt") add_subdirectory(src) else() diff --git a/cmake/MangosParams.cmake b/cmake/MangosParams.cmake index 265b57752f..8dc495fb99 100644 --- a/cmake/MangosParams.cmake +++ b/cmake/MangosParams.cmake @@ -1,5 +1,5 @@ set(MANGOS_EXP "TBC") set(MANGOS_PKG "Mangos One") -set(MANGOS_WORLD_VER 2026062800) -set(MANGOS_REALM_VER 2026060300) -set(MANGOS_AHBOT_VER 2021010100) +set(MANGOS_WORLD_VER 2026071300) +set(MANGOS_REALM_VER 2026071300) +set(MANGOS_AHBOT_VER 2026071300) diff --git a/cmake/StatusInfo.cmake b/cmake/StatusInfo.cmake index a3a127c5d4..c31b457cf8 100644 --- a/cmake/StatusInfo.cmake +++ b/cmake/StatusInfo.cmake @@ -44,5 +44,12 @@ if(BUILD_TOOLS) else() message("Build tools : No") endif() + +if(BUILD_TESTS) + message("Build tests : Yes") +else() + message("Build tests : No (default)") +endif() + message("") -message("===================================================") +message("===================================================") \ No newline at end of file diff --git a/src/baker/CMakeLists.txt b/src/baker/CMakeLists.txt index 18cb147d60..c427e07bb9 100644 --- a/src/baker/CMakeLists.txt +++ b/src/baker/CMakeLists.txt @@ -30,8 +30,8 @@ set(TOOLS_DIR "tools") # # DBCs are read with the SERVER's reader: src/shared/DataStores/DBCFileLoader.cpp compiled # in directly, driven by the format strings in src/game/Server/DBCfmt.h. It cannot be -# linked from the `shared` library instead, because `shared` exports ACE (and MySQL, and -# OpenSSL) as PUBLIC dependencies. Compiling the one .cpp is why DBCFileLoader.h and +# linked from the `shared` library instead, because `shared` exports MySQL and +# OpenSSL as PUBLIC dependencies. Compiling the one .cpp is why DBCFileLoader.h and # Utilities/ByteConverter.h were made free of Platform/Define.h -- keep them that way. # src/baker/stores is now only the two thin MPQ-backed stores over that reader. # @@ -96,6 +96,21 @@ target_link_libraries(mangos-baker Threads::Threads ) +install( + TARGETS mangos-baker + DESTINATION ${BIN_DIR}/${TOOLS_DIR} +) + +if(WIN32 AND MSVC) + install( + FILES + $ + DESTINATION ${BIN_DIR}/${TOOLS_DIR} + OPTIONAL + ) +endif() + +if(BUILD_TESTS) # Offline probes for baked .tile files. They drive the RUNTIME query path (FusedTerrain # + the shared terrain engine), NOT the baker's extraction half, so a query here is # exactly what mangosd computes. No StormLib: they read the baked tiles, they do not @@ -131,19 +146,25 @@ foreach(_probe mangos-tiletest mangos-height-check mangos-accelbench mangos-losb endforeach() install( - TARGETS mangos-baker mangos-tiletest mangos-height-check mangos-accelbench - mangos-losbench mangos-bvhdebug + TARGETS mangos-tiletest + mangos-height-check + mangos-accelbench + mangos-losbench + mangos-bvhdebug DESTINATION ${BIN_DIR}/${TOOLS_DIR} ) if(WIN32 AND MSVC) install( FILES - $ $ $ $ + $ + $ DESTINATION ${BIN_DIR}/${TOOLS_DIR} OPTIONAL ) endif() + +endif() \ No newline at end of file diff --git a/src/game/AuctionHouseBot/AuctionHouseBot.cpp b/src/game/AuctionHouseBot/AuctionHouseBot.cpp index 8e87c91789..33440bc023 100755 --- a/src/game/AuctionHouseBot/AuctionHouseBot.cpp +++ b/src/game/AuctionHouseBot/AuctionHouseBot.cpp @@ -37,6 +37,14 @@ */ #include "Policies/Singleton.h" +#include +#include +#include +#include +#include +#include +#include +#include /** * @brief Structure to track auction evaluation state for buyer @@ -82,10 +90,10 @@ typedef std::map CheckEntryMap; struct AHB_Buyer_Config { public: - AHB_Buyer_Config() : m_houseType(AUCTION_HOUSE_NEUTRAL), + AHB_Buyer_Config() : FactionChance(0), BuyerEnabled(false), BuyerPriceRatio(0), - FactionChance(0) {} + m_houseType(AUCTION_HOUSE_NEUTRAL) {} /** * @brief Initialize configuration for a specific auction house @@ -168,7 +176,7 @@ struct SellerItemInfo class AHB_Seller_Config { public: - AHB_Seller_Config() : m_houseType(AUCTION_HOUSE_NEUTRAL), LastMissedItem(0), m_minTime(0), m_maxTime(0) + AHB_Seller_Config() : LastMissedItem(0), m_houseType(AUCTION_HOUSE_NEUTRAL), m_minTime(0), m_maxTime(0) {} ~AHB_Seller_Config() {} @@ -578,8 +586,6 @@ class AuctionBotSeller : public AuctionBotAgent void LoadItemsQuantity(AHB_Seller_Config& config); }; -INSTANTIATE_SINGLETON_1(AuctionHouseBot); -INSTANTIATE_SINGLETON_1(AuctionBotConfig); //== AuctionBotConfig functions ============================ @@ -588,9 +594,9 @@ INSTANTIATE_SINGLETON_1(AuctionBotConfig); */ AuctionBotConfig::AuctionBotConfig() : m_configFileName(AUCTIONHOUSEBOT_CONFIG_LOCATION), - m_BotId(0), // Initialize m_BotId - m_ItemsPerCycleBoost(0), // Initialize m_ItemsPerCycleBoost - m_ItemsPerCycleNormal(0) // Initialize m_ItemsPerCycleNormal + m_ItemsPerCycleBoost(0), + m_ItemsPerCycleNormal(0), + m_BotId(0) { // Initialize m_configBoolValues array to false std::fill(std::begin(m_configBoolValues), std::end(m_configBoolValues), false); diff --git a/src/game/AuctionHouseBot/AuctionHouseBot.h b/src/game/AuctionHouseBot/AuctionHouseBot.h index 7be2d3d7b2..9a4a525765 100644 --- a/src/game/AuctionHouseBot/AuctionHouseBot.h +++ b/src/game/AuctionHouseBot/AuctionHouseBot.h @@ -29,6 +29,8 @@ #include "AuctionHouseMgr.h" #include "SharedDefines.h" #include "Item.h" +#include +#include /** * @file AuctionHouseBot.h diff --git a/src/game/BattleGround/BattleGround.cpp b/src/game/BattleGround/BattleGround.cpp index e3acf47374..d37d77b5d7 100644 --- a/src/game/BattleGround/BattleGround.cpp +++ b/src/game/BattleGround/BattleGround.cpp @@ -55,6 +55,9 @@ #include "Chat.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif /* ENABLE_ELUNA */ namespace MaNGOS @@ -193,7 +196,7 @@ namespace MaNGOS /// The source. /// The arg1. /// The arg2. - BattleGround2YellBuilder(uint32 language, int32 textId, Creature const* source, int32 arg1, int32 arg2) + BattleGround2YellBuilder(Language language, int32 textId, Creature const* source, int32 arg1, int32 arg2) : i_language(language), i_textId(textId), i_source(source), i_arg1(arg1), i_arg2(arg2) {} void operator()(WorldPacket& data, int32 loc_idx) { @@ -204,11 +207,11 @@ namespace MaNGOS char str[2048]; snprintf(str, 2048, text, arg1str, arg2str); - ChatHandler::BuildChatPacket(data, CHAT_MSG_MONSTER_YELL, str, LANG_UNIVERSAL, CHAT_TAG_NONE, i_source ? i_source->GetObjectGuid() : ObjectGuid(), i_source ? i_source->GetName() : ""); + ChatHandler::BuildChatPacket(data, CHAT_MSG_MONSTER_YELL, str, i_language, CHAT_TAG_NONE, i_source ? i_source->GetObjectGuid() : ObjectGuid(), i_source ? i_source->GetName() : ""); } private: - uint32 i_language; + Language i_language; int32 i_textId; Creature const* i_source; int32 i_arg1; @@ -1363,7 +1366,7 @@ void BattleGround::SendYell2ToAll(int32 entry, uint32 language, ObjectGuid guid, { return; } - MaNGOS::BattleGround2YellBuilder bg_builder(language, entry, source, arg1, arg2); + MaNGOS::BattleGround2YellBuilder bg_builder(Language(language), entry, source, arg1, arg2); MaNGOS::LocalizedPacketDo bg_do(bg_builder); BroadcastWorker(bg_do); } diff --git a/src/game/BattleGround/BattleGround.h b/src/game/BattleGround/BattleGround.h index c3cd8df2f3..6ca16b7b07 100644 --- a/src/game/BattleGround/BattleGround.h +++ b/src/game/BattleGround/BattleGround.h @@ -51,6 +51,9 @@ #include "Map.h" #include "ByteBuffer.h" #include "ObjectGuid.h" +#include +#include +#include // magic event-numbers #define BG_EVENT_NONE 255 diff --git a/src/game/BattleGround/BattleGroundAA.h b/src/game/BattleGround/BattleGroundAA.h index 14e2bf12d1..76a453709c 100644 --- a/src/game/BattleGround/BattleGroundAA.h +++ b/src/game/BattleGround/BattleGroundAA.h @@ -25,7 +25,10 @@ #ifndef MANGOS_H_BATTLEGROUNDAA #define MANGOS_H_BATTLEGROUNDAA -class BattleGround; +// Not a forward declaration: the classes below *derive* from BattleGround and +// BattleGroundScore, and you cannot inherit from an incomplete type. This header only ever +// compiled because every .cpp that used it happened to include BattleGround.h first. +#include "BattleGround.h" class BattleGroundAAScore : public BattleGroundScore { diff --git a/src/game/BattleGround/BattleGroundAB.cpp b/src/game/BattleGround/BattleGroundAB.cpp index c9c713025d..a3b9da1d84 100644 --- a/src/game/BattleGround/BattleGroundAB.cpp +++ b/src/game/BattleGround/BattleGroundAB.cpp @@ -49,6 +49,7 @@ #include "WorldPacket.h" // TODO REMOVE this when graveyard handling for pvp is updated #include "DBCStores.h" +#include /** * @brief Constructor for BattleGroundAB. diff --git a/src/game/BattleGround/BattleGroundBE.h b/src/game/BattleGround/BattleGroundBE.h index 6d4dece5d1..ea849f5740 100644 --- a/src/game/BattleGround/BattleGroundBE.h +++ b/src/game/BattleGround/BattleGroundBE.h @@ -25,7 +25,8 @@ #ifndef MANGOS_H_BATTLEGROUNDBE #define MANGOS_H_BATTLEGROUNDBE -class BattleGround; +// Derives from BattleGround / BattleGroundScore: needs the definitions, not a declaration. +#include "BattleGround.h" class BattleGroundBEScore : public BattleGroundScore { diff --git a/src/game/BattleGround/BattleGroundEY.h b/src/game/BattleGround/BattleGroundEY.h index 466fa149ce..f545d53303 100644 --- a/src/game/BattleGround/BattleGroundEY.h +++ b/src/game/BattleGround/BattleGroundEY.h @@ -27,7 +27,8 @@ #include "Language.h" -class BattleGround; +// Derives from BattleGround / BattleGroundScore: needs the definitions, not a declaration. +#include "BattleGround.h" #define EY_FLAG_RESPAWN_TIME (10 * IN_MILLISECONDS) //10 seconds #define EY_RESOURCES_UPDATE_TIME (2 * IN_MILLISECONDS) //2 seconds @@ -279,6 +280,10 @@ class BattleGroundEY : public BattleGround void EventPlayerClickedOnFlag(Player* source, GameObject* target_obj) override; void EventPlayerDroppedFlag(Player* source) override; + // EY captures are node-based, so the private overload below would + // otherwise hide this inherited one + using BattleGround::EventPlayerCapturedFlag; + Team GetPrematureWinner() override; private: diff --git a/src/game/BattleGround/BattleGroundMgr.cpp b/src/game/BattleGround/BattleGroundMgr.cpp index a1819d7eb2..af4168cba9 100644 --- a/src/game/BattleGround/BattleGroundMgr.cpp +++ b/src/game/BattleGround/BattleGroundMgr.cpp @@ -65,8 +65,11 @@ #endif /* ENABLE_ELUNA */ #include "Policies/Singleton.h" +#include +#include +#include +#include -INSTANTIATE_SINGLETON_1(BattleGroundMgr); /*********************************************************/ /*** BATTLEGROUND QUEUE SYSTEM ***/ @@ -223,7 +226,7 @@ void BattleGroundMgr::Update(uint32 diff) std::vector scheduled; { // create mutex - // ACE_Guard guard(SchedulerLock); + // std::lock_guard guard(SchedulerLock); // copy vector and clear the other scheduled = std::vector(m_QueueUpdateScheduler); m_QueueUpdateScheduler.clear(); diff --git a/src/game/BattleGround/BattleGroundMgr.h b/src/game/BattleGround/BattleGroundMgr.h index 8a09244a1b..fb5aea88e2 100644 --- a/src/game/BattleGround/BattleGroundMgr.h +++ b/src/game/BattleGround/BattleGroundMgr.h @@ -59,8 +59,13 @@ #include "SharedDefines.h" #include "DBCEnums.h" #include "BattleGround.h" -#include #include "Utilities/EventProcessor.h" +#include +#include +#include +#include +#include +#include /** * @brief Container for storing battleground instances. @@ -244,7 +249,10 @@ class BattleGroundQueue uint32 GetAverageQueueWaitTime(GroupQueueInfo* ginfo, BattleGroundBracketId bracket_id); private: - ACE_Recursive_Thread_Mutex m_Lock; /**< Mutex that should not allow changing private data, nor allowing to update Queue during private data change. */ + // No lock here: the battleground queue is driven entirely from the world thread. + // There used to be a recursive mutex, but every guard on it had already been + // commented out, so it protected nothing. Carrying it over would only have + // implied a thread-safety guarantee this class does not actually make. /** * @brief Map for storing queued players. @@ -773,7 +781,7 @@ class BattleGroundMgr */ static bool IsBGWeekend(BattleGroundTypeId bgTypeId); private: - ACE_Thread_Mutex SchedulerLock; /**< Mutex to protect the scheduler from concurrent access. */ + std::mutex SchedulerLock; /**< Mutex to protect the scheduler from concurrent access. */ BattleMastersMap mBattleMastersMap; /**< Map storing battle master entries. */ CreatureBattleEventIndexesMap m_CreatureBattleEventIndexMap; /**< Map storing creature battle event indexes. */ GameObjectBattleEventIndexesMap m_GameObjectBattleEventIndexMap; /**< Map storing game object battle event indexes. */ diff --git a/src/game/BattleGround/BattleGroundMgrUtil.cpp b/src/game/BattleGround/BattleGroundMgrUtil.cpp index bbe4cb2410..820470b817 100644 --- a/src/game/BattleGround/BattleGroundMgrUtil.cpp +++ b/src/game/BattleGround/BattleGroundMgrUtil.cpp @@ -215,7 +215,7 @@ void BattleGroundMgr::ToggleArenaTesting() */ void BattleGroundMgr::ScheduleQueueUpdate(uint32 arenaRating, ArenaType arenaType, BattleGroundQueueTypeId bgQueueTypeId, BattleGroundTypeId bgTypeId, BattleGroundBracketId bracket_id) { - // ACE_Guard guard(SchedulerLock); + // std::lock_guard guard(SchedulerLock); // we will use only 1 number created of bgTypeId and bracket_id uint64 schedule_id = ((uint64)arenaRating << 32) | (arenaType << 24) | (bgQueueTypeId << 16) | (bgTypeId << 8) | bracket_id; bool found = false; diff --git a/src/game/BattleGround/BattleGroundNA.h b/src/game/BattleGround/BattleGroundNA.h index 76cb6ec5d5..1b4586902c 100644 --- a/src/game/BattleGround/BattleGroundNA.h +++ b/src/game/BattleGround/BattleGroundNA.h @@ -25,7 +25,8 @@ #ifndef MANGOS_H_BATTLEGROUNDNA #define MANGOS_H_BATTLEGROUNDNA -class BattleGround; +// Derives from BattleGround / BattleGroundScore: needs the definitions, not a declaration. +#include "BattleGround.h" class BattleGroundNAScore : public BattleGroundScore { diff --git a/src/game/BattleGround/BattleGroundQueue.cpp b/src/game/BattleGround/BattleGroundQueue.cpp index d7cb0e56a2..946773d587 100644 --- a/src/game/BattleGround/BattleGroundQueue.cpp +++ b/src/game/BattleGround/BattleGroundQueue.cpp @@ -207,7 +207,6 @@ GroupQueueInfo* BattleGroundQueue::AddGroup(Player* leader, Group* grp, BattleGr // add players from group to ginfo { - // ACE_Guard guard(m_Lock); if (grp) { for (GroupReference* itr = grp->GetFirstMember(); itr != NULL; itr = itr->next()) @@ -377,7 +376,6 @@ uint32 BattleGroundQueue::GetAverageQueueWaitTime(GroupQueueInfo* ginfo, BattleG void BattleGroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) { // Player *plr = sObjectMgr.GetPlayer(guid); - // ACE_Guard guard(m_Lock); int32 bracket_id = -1; // signed for proper for-loop finish QueuedPlayersMap::iterator itr; @@ -518,7 +516,6 @@ void BattleGroundQueue::RemovePlayer(ObjectGuid guid, bool decreaseInvitedCount) */ bool BattleGroundQueue::IsPlayerInvited(ObjectGuid pl_guid, const uint32 bgInstanceGuid, const uint32 removeTime) { - // ACE_Guard g(m_Lock); QueuedPlayersMap::const_iterator qItr = m_QueuedPlayers.find(pl_guid); return (qItr != m_QueuedPlayers.end() && qItr->second.GroupInfo->IsInvitedToBGInstanceGUID == bgInstanceGuid && @@ -537,7 +534,6 @@ bool BattleGroundQueue::IsPlayerInvited(ObjectGuid pl_guid, const uint32 bgInsta */ bool BattleGroundQueue::GetPlayerGroupInfoData(ObjectGuid guid, GroupQueueInfo* ginfo) { - // ACE_Guard g(m_Lock); QueuedPlayersMap::const_iterator qItr = m_QueuedPlayers.find(guid); if (qItr == m_QueuedPlayers.end()) { @@ -955,7 +951,6 @@ bool BattleGroundQueue::CheckSkirmishForSameFaction(BattleGroundBracketId bracke */ void BattleGroundQueue::Update(BattleGroundTypeId bgTypeId, BattleGroundBracketId bracket_id, ArenaType arenaType, bool isRated, uint32 arenaRating) { - // ACE_Guard guard(m_Lock); // if no players in queue - do nothing if (m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_ALLIANCE].empty() && m_QueuedGroups[bracket_id][BG_QUEUE_PREMADE_HORDE].empty() && diff --git a/src/game/BattleGround/BattleGroundRL.h b/src/game/BattleGround/BattleGroundRL.h index f4e0c7703d..0145788cb0 100644 --- a/src/game/BattleGround/BattleGroundRL.h +++ b/src/game/BattleGround/BattleGroundRL.h @@ -25,7 +25,8 @@ #ifndef MANGOS_H_BATTLEGROUNDRL #define MANGOS_H_BATTLEGROUNDRL -class BattleGround; +// Derives from BattleGround / BattleGroundScore: needs the definitions, not a declaration. +#include "BattleGround.h" class BattleGroundRLScore : public BattleGroundScore { diff --git a/src/game/BattleGround/BattleGroundReward.cpp b/src/game/BattleGround/BattleGroundReward.cpp index 6bdf243ca0..d22efb547c 100644 --- a/src/game/BattleGround/BattleGroundReward.cpp +++ b/src/game/BattleGround/BattleGroundReward.cpp @@ -59,6 +59,8 @@ #include "Chat.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/ChatCommands/AccountCommands.cpp b/src/game/ChatCommands/AccountCommands.cpp index 3fec583351..bdb47b394d 100644 --- a/src/game/ChatCommands/AccountCommands.cpp +++ b/src/game/ChatCommands/AccountCommands.cpp @@ -40,6 +40,9 @@ #include "AccountMgr.h" #include "Database/DatabaseEnv.h" #include "Player.h" +#include +#include +#include /** * @brief Displays the current account information and access level. diff --git a/src/game/ChatCommands/AuctionHouseCommands.cpp b/src/game/ChatCommands/AuctionHouseCommands.cpp index 337c7ff46d..0b9a2a7865 100644 --- a/src/game/ChatCommands/AuctionHouseCommands.cpp +++ b/src/game/ChatCommands/AuctionHouseCommands.cpp @@ -35,6 +35,8 @@ #include "Chat.h" #include "Language.h" #include "ObjectMgr.h" +#include +#include /** * @brief Opens the auction house interface for the player. diff --git a/src/game/ChatCommands/BanAndKickCommands.cpp b/src/game/ChatCommands/BanAndKickCommands.cpp index a3f5040d79..4934ab0b36 100644 --- a/src/game/ChatCommands/BanAndKickCommands.cpp +++ b/src/game/ChatCommands/BanAndKickCommands.cpp @@ -39,6 +39,8 @@ #include "AccountMgr.h" #include "Util.h" #include "ObjectMgr.h" +#include +#include /** * @brief Helper function to display ban list information. diff --git a/src/game/ChatCommands/CastAndAuraCommands.cpp b/src/game/ChatCommands/CastAndAuraCommands.cpp index 578849890f..7e9efe835e 100644 --- a/src/game/ChatCommands/CastAndAuraCommands.cpp +++ b/src/game/ChatCommands/CastAndAuraCommands.cpp @@ -36,6 +36,7 @@ #include "Language.h" #include "SpellAuras.h" #include "SpellMgr.h" +#include /********************************************************************** CommandTable : castCommandTable diff --git a/src/game/ChatCommands/CommunicationCommands.cpp b/src/game/ChatCommands/CommunicationCommands.cpp index 85c465341c..208afd1668 100644 --- a/src/game/ChatCommands/CommunicationCommands.cpp +++ b/src/game/ChatCommands/CommunicationCommands.cpp @@ -36,6 +36,8 @@ #include "World.h" #include "ObjectMgr.h" +#include +#include /** * @brief Handler for HandleAnnounceCommand command. diff --git a/src/game/ChatCommands/CreatureCommands.cpp b/src/game/ChatCommands/CreatureCommands.cpp index 7172fea41e..fc78923f55 100644 --- a/src/game/ChatCommands/CreatureCommands.cpp +++ b/src/game/ChatCommands/CreatureCommands.cpp @@ -43,6 +43,10 @@ #include "WaypointManager.h" #include "PathFinder.h" // for mmap commands #include "Totem.h" +#include +#include +#include +#include /********************************************************************** CommandTable : commandTable @@ -358,7 +362,8 @@ bool ChatHandler::HandleNpcAIInfoCommand(char* /*args*/) std::string strScript = pTarget->GetScriptName(); std::string strAI = pTarget->GetAIName(); - char const* cstrAIClass = pTarget->AI() ? typeid(*pTarget->AI()).name() : " - "; + CreatureAI* pAI = pTarget->AI(); + char const* cstrAIClass = pAI ? typeid(*pAI).name() : " - "; PSendSysMessage(LANG_NPC_AI_NAMES, strAI.empty() ? " - " : strAI.c_str(), diff --git a/src/game/ChatCommands/DebugCommands.cpp b/src/game/ChatCommands/DebugCommands.cpp index d8008c09ef..7279c3f1c5 100644 --- a/src/game/ChatCommands/DebugCommands.cpp +++ b/src/game/ChatCommands/DebugCommands.cpp @@ -48,6 +48,10 @@ #include "ObjectMgr.h" #include "ObjectGuid.h" #include "SpellMgr.h" +#include +#include +#include +#include /** * @brief Handler for HandleDebugSendSpellFailCommand command. @@ -1407,7 +1411,8 @@ bool ChatHandler::HandlerDebugModValueHelper(Object* target, uint32 field, char* } uint32 value = target->GetUInt32Value(field); - const char* guidString = guid.GetString().c_str(); + std::string guidStr = guid.GetString(); + const char* guidString = guidStr.c_str(); switch (type) { diff --git a/src/game/ChatCommands/EventCommands.cpp b/src/game/ChatCommands/EventCommands.cpp index 20eb21f090..38c069a017 100644 --- a/src/game/ChatCommands/EventCommands.cpp +++ b/src/game/ChatCommands/EventCommands.cpp @@ -36,6 +36,8 @@ #include "Chat.h" #include "ObjectMgr.h" #include "GameEventMgr.h" +#include +#include /** * @brief Handler for HandleEventListCommand command. diff --git a/src/game/ChatCommands/GMCommands.cpp b/src/game/ChatCommands/GMCommands.cpp index fe128bcc97..dfaaf0a808 100644 --- a/src/game/ChatCommands/GMCommands.cpp +++ b/src/game/ChatCommands/GMCommands.cpp @@ -37,6 +37,9 @@ #include "World.h" #include "Weather.h" #include "SpellMgr.h" +#include +#include +#include /** * @brief Handler for HandlePInfoCommand command. diff --git a/src/game/ChatCommands/GMTicketCommands.cpp b/src/game/ChatCommands/GMTicketCommands.cpp index abe1549c0c..926a3d5838 100644 --- a/src/game/ChatCommands/GMTicketCommands.cpp +++ b/src/game/ChatCommands/GMTicketCommands.cpp @@ -37,6 +37,10 @@ #include "World.h" #include "GMTicketMgr.h" #include "Mail.h" +#include +#include +#include +#include // show ticket (helper) void ChatHandler::ShowTicket(GMTicket const* ticket) @@ -174,7 +178,6 @@ bool ChatHandler::HandleTicketCloseCommand(char* args) if (m_session) { const char* format = "[System Message] This ticket was closed by %s without any written response, perhaps it was resolved by direct chat."; - const char* buffer; snprintf(response, responseBufferSize, format, m_session->GetPlayer()->GetName()); } else @@ -305,7 +308,7 @@ bool ChatHandler::HandleTicketDeleteCommand(char* args) * @param args Command arguments. * @returns True if the command executed successfully, false otherwise. */ -bool ChatHandler::HandleTicketInfoCommand(char* args) +bool ChatHandler::HandleTicketInfoCommand(char* /*args*/) { size_t count = sTicketMgr.GetTicketCount(); @@ -327,7 +330,7 @@ bool ChatHandler::HandleTicketInfoCommand(char* args) * @param args Command arguments. * @returns True if the command executed successfully, false otherwise. */ -bool ChatHandler::HandleTicketListCommand(char* args) +bool ChatHandler::HandleTicketListCommand(char* /*args*/) { uint16 numToShow = std::min(uint16(sTicketMgr.GetTicketCount()), uint16(sWorld.getConfig(CONFIG_UINT32_GM_TICKET_LIST_SIZE))); for (uint16 i = 0; i < numToShow; ++i) @@ -347,7 +350,7 @@ bool ChatHandler::HandleTicketListCommand(char* args) * @param args Command arguments. * @returns True if the command executed successfully, false otherwise. */ -bool ChatHandler::HandleTicketOnlineListCommand(char* args) +bool ChatHandler::HandleTicketOnlineListCommand(char* /*args*/) { uint16 count = 0; for (uint16 i = 0; i < sTicketMgr.GetTicketCount(); ++i) diff --git a/src/game/ChatCommands/GameObjectCommands.cpp b/src/game/ChatCommands/GameObjectCommands.cpp index 4c3d52e2f4..e75a04489b 100644 --- a/src/game/ChatCommands/GameObjectCommands.cpp +++ b/src/game/ChatCommands/GameObjectCommands.cpp @@ -38,6 +38,10 @@ #include "MapManager.h" #include "GameEventMgr.h" #include "ObjectMgr.h" +#include +#include +#include +#include /** * @brief Handler for HandleGameObjectDeleteCommand command. diff --git a/src/game/ChatCommands/GridCommands.cpp b/src/game/ChatCommands/GridCommands.cpp index 05e44793bb..fee57f6b4d 100644 --- a/src/game/ChatCommands/GridCommands.cpp +++ b/src/game/ChatCommands/GridCommands.cpp @@ -229,7 +229,7 @@ bool ChatHandler::HandleGridInfoCommand(char* args) * LivingWorld anchors enabled by the current AnchorPolicyMask) -- not every * possible DB anchor. Used to measure how sparse anchor-held grids are. Read-only. */ -bool ChatHandler::HandleGridAnchorsCommand(char* args) +bool ChatHandler::HandleGridAnchorsCommand(char* /*args*/) { Player* player = m_session ? m_session->GetPlayer() : NULL; if (!player) diff --git a/src/game/ChatCommands/GuildCommands.cpp b/src/game/ChatCommands/GuildCommands.cpp index f63336d287..f103c90362 100644 --- a/src/game/ChatCommands/GuildCommands.cpp +++ b/src/game/ChatCommands/GuildCommands.cpp @@ -37,6 +37,7 @@ #include "ObjectMgr.h" #include "GuildMgr.h" #include "Guild.h" +#include /** \brief GM command level 3 - Create a guild. * diff --git a/src/game/ChatCommands/InstanceCommands.cpp b/src/game/ChatCommands/InstanceCommands.cpp index e4a55f166d..733fa7e5b5 100644 --- a/src/game/ChatCommands/InstanceCommands.cpp +++ b/src/game/ChatCommands/InstanceCommands.cpp @@ -37,6 +37,10 @@ #include "ObjectMgr.h" #include "MapManager.h" #include "InstanceData.h" +#include +#include +#include +#include /** * @brief Handler for HandleInstanceListBindsCommand command. diff --git a/src/game/ChatCommands/ListCommands.cpp b/src/game/ChatCommands/ListCommands.cpp index 5041f9facf..c5c6ef802e 100644 --- a/src/game/ChatCommands/ListCommands.cpp +++ b/src/game/ChatCommands/ListCommands.cpp @@ -36,6 +36,8 @@ #include "Chat.h" #include "ObjectMgr.h" #include "SpellAuras.h" +#include +#include /** * @brief Handler for HandleListAurasCommand command. @@ -462,7 +464,6 @@ bool ChatHandler::HandleListPlayersCommand(char* args) uint32 mapId = player->GetMapId(); uint32 zoneId = player->GetZoneId(); - MapEntry const* mapEntry = sMapStore.LookupEntry(mapId); AreaTableEntry const* zoneEntry = GetAreaEntryByAreaID(zoneId); PSendSysMessage("%-20s | Lvl %-2u | Map %u Zone %u (%s)", diff --git a/src/game/ChatCommands/LookupCommands.cpp b/src/game/ChatCommands/LookupCommands.cpp index 5bec950171..e24ff23dd5 100644 --- a/src/game/ChatCommands/LookupCommands.cpp +++ b/src/game/ChatCommands/LookupCommands.cpp @@ -39,6 +39,9 @@ #include "GameEventMgr.h" #include "World.h" #include "SQLStorages.h" +#include +#include +#include /** * @brief Handler for LookupPlayerSearchCommand command. diff --git a/src/game/ChatCommands/MMapCommands.cpp b/src/game/ChatCommands/MMapCommands.cpp index b491df4c9e..d52139e4d7 100644 --- a/src/game/ChatCommands/MMapCommands.cpp +++ b/src/game/ChatCommands/MMapCommands.cpp @@ -45,6 +45,8 @@ #include #include #include +#include +#include /** * @brief Handler for HandleMmapPathCommand command. diff --git a/src/game/ChatCommands/MailCommands.cpp b/src/game/ChatCommands/MailCommands.cpp index a59c371834..d81b43189e 100644 --- a/src/game/ChatCommands/MailCommands.cpp +++ b/src/game/ChatCommands/MailCommands.cpp @@ -36,6 +36,10 @@ #include "ObjectMgr.h" #include "Mail.h" #include "MassMailMgr.h" +#include +#include +#include +#include /** * @brief Handler for HandleSendMailCommand command. diff --git a/src/game/ChatCommands/MiscellanousCommands.cpp b/src/game/ChatCommands/MiscellanousCommands.cpp index 562e1d7d7d..3bf614fd27 100644 --- a/src/game/ChatCommands/MiscellanousCommands.cpp +++ b/src/game/ChatCommands/MiscellanousCommands.cpp @@ -37,6 +37,7 @@ #include "World.h" #include "BattleGroundMgr.h" #include "PlayerDump.h" +#include /** * @brief Handler for HandleHelpCommand command. diff --git a/src/game/ChatCommands/PlayerAndCreatureCommands.cpp b/src/game/ChatCommands/PlayerAndCreatureCommands.cpp index 84fef82c7a..ac31bf1d2d 100644 --- a/src/game/ChatCommands/PlayerAndCreatureCommands.cpp +++ b/src/game/ChatCommands/PlayerAndCreatureCommands.cpp @@ -39,6 +39,7 @@ #include "MovementGenerator.h" #include "FollowerReference.h" #include "terrain/Geometry/Vector3.h" +#include /** * @brief Handler for HandleDeMorphCommand command. diff --git a/src/game/ChatCommands/PlayerCommands.cpp b/src/game/ChatCommands/PlayerCommands.cpp index a2dc8a5e05..635dbf0f4d 100644 --- a/src/game/ChatCommands/PlayerCommands.cpp +++ b/src/game/ChatCommands/PlayerCommands.cpp @@ -38,6 +38,11 @@ #include "World.h" #include "AccountMgr.h" #include "SQLStorages.h" +#include +#include +#include +#include +#include /** * @brief Handler for HandleCharacterEraseCommand command. diff --git a/src/game/ChatCommands/PlayerHonorCommands.cpp b/src/game/ChatCommands/PlayerHonorCommands.cpp index 5e42b77bb4..620b7bb099 100644 --- a/src/game/ChatCommands/PlayerHonorCommands.cpp +++ b/src/game/ChatCommands/PlayerHonorCommands.cpp @@ -34,6 +34,7 @@ #include "Chat.h" #include "ObjectMgr.h" +#include /** * @brief Handler for HandleHonorAddCommand command. diff --git a/src/game/ChatCommands/PlayerLearnCommands.cpp b/src/game/ChatCommands/PlayerLearnCommands.cpp index cf3f2ee4b2..9fd34f0d7c 100644 --- a/src/game/ChatCommands/PlayerLearnCommands.cpp +++ b/src/game/ChatCommands/PlayerLearnCommands.cpp @@ -35,6 +35,9 @@ #include "Chat.h" #include "ObjectMgr.h" #include "SpellMgr.h" +#include +#include +#include /** * @brief Handler for HandleUnLearnCommand command. diff --git a/src/game/ChatCommands/PlayerMiscCommands.cpp b/src/game/ChatCommands/PlayerMiscCommands.cpp index e2df0bef43..b1f7adc0c5 100644 --- a/src/game/ChatCommands/PlayerMiscCommands.cpp +++ b/src/game/ChatCommands/PlayerMiscCommands.cpp @@ -35,6 +35,7 @@ #include "Chat.h" #include "ObjectMgr.h" #include "World.h" +#include /** * @brief Handler for HandleBankCommand command. @@ -64,7 +65,7 @@ bool ChatHandler::HandleStableCommand(char* /*args*/) /********************************************************************** CommandTable : resetCommandTable -/***********************************************************************/ +***********************************************************************/ static bool HandleResetStatsOrLevelHelper(Player* player) { ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(player->getClass()); diff --git a/src/game/ChatCommands/PlayerTitleCommands.cpp b/src/game/ChatCommands/PlayerTitleCommands.cpp index 70d228c7ea..21e65ccc62 100644 --- a/src/game/ChatCommands/PlayerTitleCommands.cpp +++ b/src/game/ChatCommands/PlayerTitleCommands.cpp @@ -25,6 +25,8 @@ #include "Chat.h" #include "Language.h" #include "World.h" +#include +#include bool ChatHandler::HandleLookupTitleCommand(char* args) diff --git a/src/game/ChatCommands/PoolCommands.cpp b/src/game/ChatCommands/PoolCommands.cpp index 0dc15049b9..6b5428ff2a 100644 --- a/src/game/ChatCommands/PoolCommands.cpp +++ b/src/game/ChatCommands/PoolCommands.cpp @@ -34,6 +34,7 @@ #include "Chat.h" #include "ObjectMgr.h" +#include /** * @brief Displays summary information for a pool template. diff --git a/src/game/ChatCommands/ReloadCommands.cpp b/src/game/ChatCommands/ReloadCommands.cpp index 81091fe5af..daaaf7c900 100644 --- a/src/game/ChatCommands/ReloadCommands.cpp +++ b/src/game/ChatCommands/ReloadCommands.cpp @@ -152,7 +152,7 @@ bool ChatHandler::HandleReloadAllLootCommand(char* /*args*/) * @param args Command arguments. * @returns True if the command executed successfully, false otherwise. */ -bool ChatHandler::HandleReloadAllNpcCommand(char* args) +bool ChatHandler::HandleReloadAllNpcCommand(char* /*args*/) { HandleReloadNpcTrainerCommand((char*)"a"); HandleReloadNpcVendorCommand((char*)"a"); @@ -648,7 +648,7 @@ bool ChatHandler::HandleReloadMangosStringCommand(char* /*args*/) * @param args Command arguments. * @returns True if the command executed successfully, false otherwise. */ -bool ChatHandler::HandleReloadNpcGossipCommand(char* args) +bool ChatHandler::HandleReloadNpcGossipCommand(char* /*args*/) { sLog.outString("Re-Loading 'gossip_menus' Table!"); sObjectMgr.LoadGossipMenus(); diff --git a/src/game/ChatCommands/SelectCommands.cpp b/src/game/ChatCommands/SelectCommands.cpp index 2216a7e23c..0a88e17f26 100755 --- a/src/game/ChatCommands/SelectCommands.cpp +++ b/src/game/ChatCommands/SelectCommands.cpp @@ -36,6 +36,7 @@ #include "ObjectMgr.h" #include "Player.h" #include "Language.h" +#include /** * @brief Handler for HandleSelectPlayerCommand command. diff --git a/src/game/ChatCommands/ServerCommands.cpp b/src/game/ChatCommands/ServerCommands.cpp index aa0cdb47ea..8d53114a67 100644 --- a/src/game/ChatCommands/ServerCommands.cpp +++ b/src/game/ChatCommands/ServerCommands.cpp @@ -41,6 +41,9 @@ #include "SystemConfig.h" #include "UpdateTime.h" #include "revision_data.h" +#include +#include +#include /** * @brief Handler for HandleServerInfoCommand command. @@ -432,7 +435,7 @@ bool ChatHandler::HandleServerCorpsesCommand(char* /*args*/) * @param args Command arguments. * @returns True if the command executed successfully, false otherwise. */ -bool ChatHandler::HandleServerResetAllRaidCommand(char* args) +bool ChatHandler::HandleServerResetAllRaidCommand(char* /*args*/) { PSendSysMessage("Global raid instances reset, all players in raid instances will be teleported to homebind!"); sMapPersistentStateMgr.GetScheduler().ResetAllRaid(); diff --git a/src/game/ChatCommands/TeleportationAndPositionCommands.cpp b/src/game/ChatCommands/TeleportationAndPositionCommands.cpp index c58bdc7101..644cd4752a 100644 --- a/src/game/ChatCommands/TeleportationAndPositionCommands.cpp +++ b/src/game/ChatCommands/TeleportationAndPositionCommands.cpp @@ -41,6 +41,9 @@ #include "Player.h" #include "TransportSystem.h" #include "Transports.h" +#include +#include +#include /* All commands related to Teleportation */ diff --git a/src/game/ChatCommands/TriggerCommands.cpp b/src/game/ChatCommands/TriggerCommands.cpp index abd647439f..2045157034 100644 --- a/src/game/ChatCommands/TriggerCommands.cpp +++ b/src/game/ChatCommands/TriggerCommands.cpp @@ -34,6 +34,7 @@ #include "Chat.h" #include "ObjectMgr.h" +#include /** * @brief Helper function to display trigger target information. diff --git a/src/game/ChatCommands/WaypointCommands.cpp b/src/game/ChatCommands/WaypointCommands.cpp index 4389c30a6b..825d1d7bd7 100644 --- a/src/game/ChatCommands/WaypointCommands.cpp +++ b/src/game/ChatCommands/WaypointCommands.cpp @@ -47,6 +47,8 @@ #include #include #include +#include +#include /// Helper function inline Creature* Helper_CreateWaypointFor(Creature* wpOwner, WaypointPathOrigin wpOrigin, int32 pathId, uint32 wpId, WaypointNode const* wpNode, CreatureInfo const* waypointInfo) diff --git a/src/game/Maps/MapUpdater.cpp b/src/game/Maps/MapUpdater.cpp index b1f8e21d6b..95f3960e3f 100644 --- a/src/game/Maps/MapUpdater.cpp +++ b/src/game/Maps/MapUpdater.cpp @@ -2,7 +2,7 @@ * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * - * Copyright (C) 2005-2025 MaNGOS + * Copyright (C) 2005-2026 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,159 +24,150 @@ /** * @file MapUpdater.cpp - * @brief Implementation of the MapUpdater class for managing map update requests. - * - * This file contains the implementation of the MapUpdater class which is responsible - * for managing and scheduling map updates. It includes: - * - Map update request scheduling - * - Thread management for concurrent map updates - * - Synchronization mechanisms for pending requests + * @brief Implementation of the parallel map-update worker pool. */ #include "MapUpdater.h" -#include "DelayExecutor.h" + #include "Map.h" #include "DatabaseEnv.h" +#include "Log.h" +#include +#include -#include -#include - -/** - * @brief A request to update a map. - */ -class MapUpdateRequest : public ACE_Method_Request -{ - private: - Map& m_map; ///< Reference to the map to be updated. - MapUpdater& m_updater; ///< Reference to the map updater. - ACE_UINT32 m_diff; ///< Time difference for the update. - - public: - /** - * @brief Constructor for MapUpdateRequest. - * @param m Reference to the map. - * @param u Reference to the map updater. - * @param d Time difference for the update. - */ - MapUpdateRequest(Map& m, MapUpdater& u, ACE_UINT32 d) - : m_map(m), m_updater(u), m_diff(d) - { - } - - /** - * @brief Executes the map update request. - * @return Always returns 0. - */ - virtual int call() - { - m_map.Update(m_diff); - m_updater.update_finished(); - return 0; - } -}; - -/** - * @brief Constructor for MapUpdater. - */ -MapUpdater::MapUpdater(): -m_executor(), m_mutex(), m_condition(m_mutex), pending_requests(0) +MapUpdater::MapUpdater() + : m_pending(0), m_stop(false) { } -/** - * @brief Destructor for MapUpdater. - */ MapUpdater::~MapUpdater() { deactivate(); } -/** - * @brief Activates the map updater with the specified number of threads. - * @param num_threads Number of threads to activate. - * @return Result of the activation. - */ int MapUpdater::activate(size_t num_threads) { - return m_executor._activate((int)num_threads); + if (num_threads == 0 || activated()) + { + return -1; + } + + { + std::lock_guard guard(m_mutex); + m_stop = false; + } + + m_workers.reserve(num_threads); + for (size_t i = 0; i < num_threads; ++i) + { + m_workers.emplace_back([this] { workerLoop(); }); + } + + return 0; } -/** - * @brief Deactivates the map updater. - * @return Result of the deactivation. - */ int MapUpdater::deactivate() { - sLog.outString("[shutdown] MapUpdater::deactivate: draining pending map updates (pending=%zu)", pending_requests); + if (!activated()) + { + return 0; + } + + sLog.outString("[shutdown] MapUpdater::deactivate: draining pending map updates (pending=%zu)", m_pending); + + // Drain first: a map must not be left half-updated, and Map::Update touches world + // state that is torn down right after this returns. wait(); + sLog.outString("[shutdown] MapUpdater::deactivate: pending drained; joining worker threads"); - int r = m_executor.deactivate(); - sLog.outString("[shutdown] MapUpdater::deactivate: worker threads joined"); - return r; -} -/** - * @brief Waits for all pending requests to be processed. - * @return Always returns 0. - */ -int MapUpdater::wait() -{ - ACE_GUARD_RETURN(ACE_Thread_Mutex, guard, m_mutex, -1); + { + std::lock_guard guard(m_mutex); + m_stop = true; + } + m_taskAdded.notify_all(); - while (pending_requests > 0) + for (std::thread& worker : m_workers) { - m_condition.wait(); + if (worker.joinable()) + { + worker.join(); + } } + m_workers.clear(); + sLog.outString("[shutdown] MapUpdater::deactivate: worker threads joined"); return 0; } -/** - * @brief Schedules a map update. - * @param map Reference to the map to be updated. - * @param diff Time difference for the update. - * @return Result of the scheduling. - */ -int MapUpdater::schedule_update(Map& map, ACE_UINT32 diff) +bool MapUpdater::activated() { - ACE_GUARD_RETURN(ACE_Thread_Mutex, guard, m_mutex, -1); + return !m_workers.empty(); +} - ++pending_requests; +int MapUpdater::schedule_update(Map& map, uint32 diff) +{ + std::unique_lock guard(m_mutex); - if (m_executor.execute(new MapUpdateRequest(map, *this, diff)) == -1) + if (m_stop || m_workers.empty()) { - ACE_DEBUG((LM_ERROR, ACE_TEXT("(%t) \n"), ACE_TEXT("Failed to schedule Map Update"))); - - --pending_requests; + sLog.outError("MapUpdater::schedule_update: pool is not running, map %u not updated", map.GetId()); return -1; } + m_tasks.push(Task(&map, diff)); + ++m_pending; + + guard.unlock(); + m_taskAdded.notify_one(); + return 0; } -/** - * @brief Checks if the map updater is activated. - * @return True if activated, false otherwise. - */ -bool MapUpdater::activated() +int MapUpdater::wait() { - return m_executor.activated(); + std::unique_lock guard(m_mutex); + + m_taskDone.wait(guard, [this] { return m_pending == 0; }); + + return 0; } -/** - * @brief Called when a map update is finished. - */ -void MapUpdater::update_finished() +void MapUpdater::workerLoop() { - ACE_GUARD(ACE_Thread_Mutex, guard, m_mutex); - - if (pending_requests == 0) + for (;;) { - ACE_ERROR((LM_ERROR, ACE_TEXT("(%t)\n"), ACE_TEXT("MapUpdater::update_finished BUG, report to devs"))); - return; - } + Task task(nullptr, 0); + + { + std::unique_lock guard(m_mutex); + + m_taskAdded.wait(guard, [this] { return m_stop || !m_tasks.empty(); }); + + // Only retire once the queue is genuinely empty, so a stop racing with a + // still-queued tick cannot drop that map's update on the floor. + if (m_tasks.empty()) + { + if (m_stop) + { + return; + } + continue; + } + + task = m_tasks.front(); + m_tasks.pop(); + } - --pending_requests; + task.first->Update(task.second); - m_condition.broadcast(); + { + std::lock_guard guard(m_mutex); + --m_pending; + } + + // Outside the lock: wait() only ever cares about the count reaching zero, and + // notifying while holding the mutex would just make the waiter block again. + m_taskDone.notify_all(); + } } diff --git a/src/game/Maps/MapUpdater.h b/src/game/Maps/MapUpdater.h index 803c70d861..db6861f77a 100644 --- a/src/game/Maps/MapUpdater.h +++ b/src/game/Maps/MapUpdater.h @@ -2,7 +2,7 @@ * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * - * Copyright (C) 2005-2025 MaNGOS + * Copyright (C) 2005-2026 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,87 +24,90 @@ /** * @file MapUpdater.h - * @brief Header file for the MapUpdater class. + * @brief Worker pool that ticks maps in parallel. * - * This file contains the definition of the MapUpdater class which is responsible - * for managing and scheduling map updates. It includes: - * - MapUpdateRequest class for handling individual update requests - * - Thread management for concurrent map updates - * - Synchronization mechanisms for pending requests + * The world thread hands each map's Update() to this pool via schedule_update(), then + * blocks in wait() until the whole tick has been processed. This replaces the ACE + * DelayExecutor / Method_Request / Activation_Queue machinery with a plain std::thread + * pool; the public API is unchanged, so MapManager did not have to move with it. */ #ifndef _MAP_UPDATER_H_INCLUDED #define _MAP_UPDATER_H_INCLUDED -#include -#include +#include "Platform/Define.h" -#include "DelayExecutor.h" +#include +#include +#include +#include +#include +#include +#include class Map; /** - * @brief The MapUpdater class is responsible for managing map update requests. + * @brief Schedules map updates across a pool of worker threads. */ class MapUpdater { public: - /** - * @brief Constructor for MapUpdater. - */ - MapUpdater(); - /** - * @brief Destructor for MapUpdater. - */ - virtual ~MapUpdater(); + MapUpdater(); + ~MapUpdater(); - friend class MapUpdateRequest; + MapUpdater(const MapUpdater&) = delete; + MapUpdater& operator=(const MapUpdater&) = delete; /** - * @brief Schedules a map update. - * @param map Reference to the map to be updated. - * @param diff Time difference for the update. - * @return Result of the scheduling. + * @brief Queue map.Update(diff) for a worker. + * @return 0 on success, -1 if the pool is not running. */ - int schedule_update(Map& map, ACE_UINT32 diff); + int schedule_update(Map& map, uint32 diff); /** - * @brief Waits for all pending requests to be processed. - * @return Always returns 0. + * @brief Block until every scheduled update has finished. + * + * This is the tick barrier: the world thread must not advance until every map + * queued this tick has been updated. + * + * @return Always 0. */ int wait(); /** - * @brief Activates the map updater with the specified number of threads. - * @param num_threads Number of threads to activate. - * @return Result of the activation. + * @brief Start @p num_threads workers. + * @return 0 on success, -1 on failure. */ int activate(size_t num_threads); /** - * @brief Deactivates the map updater. - * @return Result of the deactivation. + * @brief Drain outstanding updates, then stop and join the workers. + * @return Always 0. */ int deactivate(); - /** - * @brief Checks if the map updater is activated. - * @return True if activated, false otherwise. - */ + /// True while worker threads are running. bool activated(); private: - DelayExecutor m_executor; ///< Executor for handling delayed tasks. - ACE_Thread_Mutex m_mutex; ///< Mutex for synchronizing access to pending requests. - ACE_Condition_Thread_Mutex m_condition; ///< Condition variable for signaling when requests are processed. - size_t pending_requests; ///< Number of pending update requests. - /** - * @brief Called when a map update is finished. - */ - void update_finished(); + /// One queued map tick. + typedef std::pair Task; + + /// Worker body: run tasks until stopped and the queue has drained. + void workerLoop(); + + std::vector m_workers; + std::queue m_tasks; + + std::mutex m_mutex; ///< Guards m_tasks, m_pending and m_stop + std::condition_variable m_taskAdded; ///< Wakes a worker when work arrives + std::condition_variable m_taskDone; ///< Wakes wait() once m_pending hits zero + + size_t m_pending; ///< Scheduled but not yet finished updates + bool m_stop; ///< Set by deactivate() to retire the workers }; #endif //_MAP_UPDATER_H_INCLUDED - diff --git a/src/game/MotionGenerators/FleeingMovementGenerator.cpp b/src/game/MotionGenerators/FleeingMovementGenerator.cpp index 35f403b2cc..ec5e9a27a3 100644 --- a/src/game/MotionGenerators/FleeingMovementGenerator.cpp +++ b/src/game/MotionGenerators/FleeingMovementGenerator.cpp @@ -28,6 +28,7 @@ #include "MotionFrame.h" #include "ObjectAccessor.h" #include "Util.h" +#include namespace { diff --git a/src/game/MotionGenerators/MotionFrame.cpp b/src/game/MotionGenerators/MotionFrame.cpp index 1f4b3d20bc..aecdbc72f6 100644 --- a/src/game/MotionGenerators/MotionFrame.cpp +++ b/src/game/MotionGenerators/MotionFrame.cpp @@ -34,6 +34,7 @@ #include #include +#include namespace Motion { @@ -66,7 +67,7 @@ namespace Motion } // A failed route still leaves a straight-line shortcut in the - // points, which some movement kinds want and others refuse — so + // points, which some movement kinds want and others refuse -- so // report it (Failed) rather than deciding here. return m_path.getPath().size() >= 2; } @@ -204,18 +205,18 @@ namespace Motion * question is answered by the vessel's baked mesh IN THE SPACE IT WAS BAKED IN. * * No world transform is ever applied to that mesh, and this is not an - * optimisation — it is the only correct thing to do. The server does not know + * optimisation -- it is the only correct thing to do. The server does not know * where the ship is: the client interpolates it along a Catmull-Rom curve that * the server never reproduces. Posing the hull into world coordinates and firing - * rays at it — which is what every other core does, and why none of them can keep - * a crew on a deck — computes collisions against a ship that is not there. + * rays at it -- which is what every other core does, and why none of them can keep + * a crew on a deck -- computes collisions against a ship that is not there. */ /// The step, in yards, between the samples a deck leg is built from. constexpr float DECK_STEP = 2.0f; - /// Each sample is sought from this far above the last one — so a low step or a - /// ramp is found underfoot — and no further than this below it, so a hatch does + /// Each sample is sought from this far above the last one -- so a low step or a + /// ramp is found underfoot -- and no further than this below it, so a hatch does /// not silently drop the leg onto the deck two levels down. constexpr float DECK_SEARCH_UP = 2.0f; constexpr float DECK_SEARCH_DOWN = 6.0f; @@ -256,42 +257,18 @@ namespace Motion /** * @brief The deck offset of an object aboard `vessel`, if it is aboard at all. * - * There are TWO ways to be on a ship, and a pet following its master needs the - * second one: - * - * * A crew member or a pet is a TransportBase passenger, and carries a - * TransportInfo. Its offset is the truth we maintain. - * - * * A PLAYER is not. A player rides on Player::m_transport plus the deck offset - * its own client sends in every movement packet -- which is better than - * anything we could compute, because the client is authoritative for where it - * is standing. So we read the number it gave us. - * - * Missing the second case would send a pet chasing its master's WORLD position, - * converted into the deck frame via an estimated pose -- when the exact answer was - * sitting in the packet all along. + * Both ways of being on a ship have to be read -- a passenger's TransportInfo and a + * player's own client-sent offset -- and Transport::LocalPositionOf is where that + * rule lives. A pet following its master needs the player case: without it the + * chase would aim at the master's WORLD position brought into the deck frame + * through a vessel pose we are only estimating, when the exact answer was sitting + * in the master's movement packet all along. */ std::optional LocalPositionAboard(Transport const& vessel, WorldObject const& obj) { - if (TransportInfo const* info = obj.GetTransportInfo()) + if (const auto local = vessel.LocalPositionOf(obj)) { - if (info->GetTransport() == &vessel) - { - return LocalPositionOf(*info); - } - - return std::nullopt; // aboard a DIFFERENT vessel - } - - if (obj.GetTypeId() == TYPEID_PLAYER) - { - Player const& player = static_cast(obj); - - if (player.GetTransport() == &vessel) - { - Position const* t = player.m_movementInfo.GetTransportPos(); - return Vector3(t->x, t->y, t->z); - } + return Vector3(local->x, local->y, local->z); } return std::nullopt; @@ -299,24 +276,9 @@ namespace Motion std::optional LocalOrientationAboard(Transport const& vessel, WorldObject const& obj) { - if (TransportInfo const* info = obj.GetTransportInfo()) + if (const auto local = vessel.LocalPositionOf(obj)) { - if (info->GetTransport() == &vessel) - { - return info->GetLocalOrientation(); - } - - return std::nullopt; - } - - if (obj.GetTypeId() == TYPEID_PLAYER) - { - Player const& player = static_cast(obj); - - if (player.GetTransport() == &vessel) - { - return player.m_movementInfo.GetTransportPos()->o; - } + return local->o; } return std::nullopt; @@ -345,7 +307,7 @@ namespace Motion /** * @brief The deck's router. * - * There is no navmesh on a ship, so a deck leg is the straight line to the goal — + * There is no navmesh on a ship, so a deck leg is the straight line to the goal -- * but SAMPLED, with every sample dropped onto the deck, and cut short at the first * one that has no deck under it or that cannot be reached from the sample before. * @@ -374,7 +336,7 @@ namespace Motion m_points.push_back(start); // No baked mesh: there is no deck to walk on, and we must NOT quietly - // fall back to the world terrain under the hull — that is the sea + // fall back to the world terrain under the hull -- that is the sea // floor. All we can honestly offer is the straight line, reported as // Failed, exactly as a world route with no navmesh is. The movement // kinds that refuse a shortcut then refuse this too. @@ -416,9 +378,30 @@ namespace Motion if (forceDestination && !complete) { - // A pet heeling its master is allowed to cheat to the exact spot. - m_points.push_back(goal); - complete = true; + // A pet heeling its master may cheat past the ship's SCENERY -- a + // crate, a companionway, whatever its master strolled straight + // through. It may not cheat through the SHIP. The spot it lands on + // still has to be a spot on the deck, so the cheat skips the + // obstruction test and only that; the floor is not negotiable. + // + // Taking the goal raw -- which is what the world frame safely does, + // because a world goal was dropped onto the terrain by GetNearPoint + // long before it reached any router -- would walk the pet into a + // bulkhead and leave it standing inside the hull, where no client + // can draw it. On a deck the sampling above IS the ground + // resolution, and NearPoint hands back goals it could not resolve + // (see the note there) precisely because it trusts the router to + // refuse them. + // + // No deck under the goal at all -- the master is leaning over the + // rail and the heel spot is out over the water -- and the leg simply + // stays short: the pet crowds up against the rail, as close as the + // deck allows. That is what it does on retail, and it beats swimming. + if (const auto onDeck = DeckDrop(*vessel, goal)) + { + m_points.push_back(*onDeck); + complete = true; + } } if (m_points.size() < 2) @@ -501,7 +484,7 @@ namespace Motion // A fellow passenger's deck offset is exact and needs no conversion. It // is also the only reading that stays right while the vessel moves under - // both of them — which is precisely why chase and follow work on a + // both of them -- which is precisely why chase and follow work on a // rolling deck with no changes of their own, and why a pet keeps up with // its master instead of swimming after the boat. if (vessel) @@ -514,7 +497,7 @@ namespace Motion // Someone ashore, or on another vessel. Read where they are in the // world and bring that into our frame. The leg to them will be cut off - // at the deck's edge by the router — which is the right answer: a mob + // at the deck's edge by the router -- which is the right answer: a mob // may not walk off a ship to reach you. Vector3 p; obj.GetPosition(p.x, p.y, p.z); @@ -542,8 +525,8 @@ namespace Motion float /*searcherBounding*/, float distance2d, float absAngle) const override { - // absAngle is a FRAME angle — the generators derive it from - // ObjectOrientation or from frame positions — so the offset is applied + // absAngle is a FRAME angle -- the generators derive it from + // ObjectOrientation or from frame positions -- so the offset is applied // in the deck's own 2D system and no yaw correction belongs here. const Vector3 t = ObjectPosition(mover, target); const Vector3 guess(t.x + distance2d * std::cos(absAngle), diff --git a/src/game/MotionGenerators/MotionFrame.h b/src/game/MotionGenerators/MotionFrame.h index 88cb03c177..fd03b86de6 100644 --- a/src/game/MotionGenerators/MotionFrame.h +++ b/src/game/MotionGenerators/MotionFrame.h @@ -36,20 +36,20 @@ class Unit; class WorldObject; /** - * @brief The COORDINATE FRAME a movement leg lives in — the seam that makes the + * @brief The COORDINATE FRAME a movement leg lives in -- the seam that makes the * spline and the path calculus terrain-agnostic. * - * Every terrain-dependent question the movement stack asks — route me, where is my - * target, give me a reachable point nearby, drop this guess onto the ground — is + * Every terrain-dependent question the movement stack asks -- route me, where is my + * target, give me a reachable point nearby, drop this guess onto the ground -- is * answered by the frame, not by a direct call into Map or PathFinder. That is the * whole point: nothing above this interface knows, or may know, whether the unit is * walking on world terrain or on the deck of a moving ship. * - * * WorldFrame — world coordinates. Routing through the Detour navmesh, heights + * * WorldFrame -- world coordinates. Routing through the Detour navmesh, heights * from the terrain. Behaves exactly as the movement code did * before the seam existed. * - * * TransportFrame — a vessel's LOCAL system. A boarded unit's position, its target's + * * TransportFrame -- a vessel's LOCAL system. A boarded unit's position, its target's * position and the leg it walks are all LOCAL coordinates the * client composes with the live vessel pose; the floor comes from * the deck mesh rather than the world, so a passenger can never @@ -62,7 +62,7 @@ class WorldObject; * driver enforces it by dropping its router whenever the frame under it changes. * * The other half of that invariant is FromWorld/ToWorld. Every anchor a generator - * captures — a respawn coord, a DB waypoint, a script's MovePoint — is WORLD data, + * captures -- a respawn coord, a DB waypoint, a script's MovePoint -- is WORLD data, * because world data is the only kind the database and the script API speak. Read as * a deck offset it would send the unit somewhere absurd, so it must come through * FromWorld. Under WorldFrame both calls are the identity, which is why the @@ -84,7 +84,7 @@ namespace Motion * @brief The 2D bearing from one FRAME point to another, normalised to [0, 2*PI) * exactly as WorldObject::GetAngle normalises. * - * Generators need this because GetAngle itself reads world positions — and a boarded + * Generators need this because GetAngle itself reads world positions -- and a boarded * unit's world position is only a cache, derived from a vessel pose the server is * merely estimating. A chase bearing computed from it would aim at where the deck is * not. Distances survive the change of frame (a rigid transform preserves them, which @@ -112,7 +112,7 @@ namespace Motion * @param forceDestination Arrive exactly at `goal` even when it is not * cleanly routable (a pet heeling its master may cheat). * @param lengthLimit Cap the path length in yards (0 = default). - * @return True when usable geometry came out — which INCLUDES the + * @return True when usable geometry came out -- which INCLUDES the * straight-line fallback used when routing failed (see Failed). * False only when nothing at all could be produced. */ @@ -127,13 +127,13 @@ namespace Motion * whatever is in the way. * * Whether that is acceptable is the CALLER's decision, and the movement - * kinds genuinely disagree — hence MOVE_REQUIRE_PATH on the intent rather + * kinds genuinely disagree -- hence MOVE_REQUIRE_PATH on the intent rather * than the router quietly picking one behaviour for everybody. */ virtual bool Failed() const = 0; /** - * @brief The last Calculate produced a REAL route, not a straight line — + * @brief The last Calculate produced a REAL route, not a straight line -- * a different question from Failed, because a map with no routing * data answers every query with a straight line that did not "fail". * @@ -168,7 +168,7 @@ namespace Motion * @brief Bring a WORLD-space point into this frame. * * Spawn coordinates, DB waypoints and every script that says "walk to - * (x,y,z)" speak world space and always will — so an anchor a generator + * (x,y,z)" speak world space and always will -- so an anchor a generator * captures from them has to be converted before it can be used as a goal. * The identity under WorldFrame. */ @@ -178,7 +178,7 @@ namespace Motion /// to talk to the map (grid placement, a spell's destination, a summon). virtual Vector3 ToWorld(Unit const& mover, Vector3 const& local) const = 0; - /// Another object's position, in the MOVER's frame — this is what lets + /// Another object's position, in the MOVER's frame -- this is what lets /// chase and follow track a target without caring which frame either of /// them stands in, and why there is no need for deck-local twins of them. virtual Vector3 ObjectPosition(Unit const& mover, WorldObject const& obj) const = 0; @@ -216,7 +216,7 @@ namespace Motion * @brief The frame `mover` is currently moving in. * * Always the world frame today. When TransportFrame lands, a boarded unit resolves - * to its vessel's frame HERE and nothing above this call has to change — that is + * to its vessel's frame HERE and nothing above this call has to change -- that is * the payoff of routing every terrain question through IMotionFrame. */ IMotionFrame const& FrameFor(Unit const& mover); diff --git a/src/game/MotionGenerators/MotionMaster.cpp b/src/game/MotionGenerators/MotionMaster.cpp index a1d89fe054..a2be8cf860 100644 --- a/src/game/MotionGenerators/MotionMaster.cpp +++ b/src/game/MotionGenerators/MotionMaster.cpp @@ -42,6 +42,8 @@ #include "Log.h" #include +#include +#include /** * @brief Checks if the movement generator is static (idle movement). @@ -323,7 +325,7 @@ void MotionMaster::MoveIdle() * @param radius Radius of the random movement. * @param verticalZ Vertical offset for the movement. */ -void MotionMaster::MoveRandomAroundPoint(float x, float y, float z, float radius, float verticalZ) +void MotionMaster::MoveRandomAroundPoint(float x, float y, float z, float radius, float /*verticalZ*/) { if (m_owner->GetTypeId() == TYPEID_PLAYER) { diff --git a/src/game/MotionGenerators/MotionMaster.h b/src/game/MotionGenerators/MotionMaster.h index 88952a9aed..1ef9f99b65 100644 --- a/src/game/MotionGenerators/MotionMaster.h +++ b/src/game/MotionGenerators/MotionMaster.h @@ -28,6 +28,7 @@ #include "Common.h" #include #include +#include class MovementGenerator; class Unit; diff --git a/src/game/MotionGenerators/PathFinder.cpp b/src/game/MotionGenerators/PathFinder.cpp index 78b3ed4d26..53e4607d7d 100644 --- a/src/game/MotionGenerators/PathFinder.cpp +++ b/src/game/MotionGenerators/PathFinder.cpp @@ -30,6 +30,10 @@ #include "PathFinder.h" #include "Log.h" +#include +#include +#include + ////////////////// PathFinder ////////////////// /** diff --git a/src/game/MotionGenerators/TargetedMovementGenerator.h b/src/game/MotionGenerators/TargetedMovementGenerator.h index a8e1b4e5cf..270fd44363 100644 --- a/src/game/MotionGenerators/TargetedMovementGenerator.h +++ b/src/game/MotionGenerators/TargetedMovementGenerator.h @@ -84,12 +84,12 @@ class TargetedMovementGenerator : public IntentMovementGenerator, /// The target is gone in a way only this kind can detect (a creature that killed /// its own pet is no longer chasing anything). - virtual bool LostTarget(Unit& owner) const { return false; } + virtual bool LostTarget(Unit& /*owner*/) const { return false; } /// The unit is now as close as it asked to be. Fires once per approach. - virtual void ReachTarget(Unit& owner) {} + virtual void ReachTarget(Unit& /*owner*/) {} - virtual bool EnableWalking(Unit& owner) const { return false; } + virtual bool EnableWalking(Unit& /*owner*/) const { return false; } /// How often the standing spot is re-derived. Deriving it is the expensive half /// (it snaps to the ground), so it is throttled — and a follower looks twice as diff --git a/src/game/MotionGenerators/WaypointMovementGenerator.cpp b/src/game/MotionGenerators/WaypointMovementGenerator.cpp index 242770f852..76e6909e53 100644 --- a/src/game/MotionGenerators/WaypointMovementGenerator.cpp +++ b/src/game/MotionGenerators/WaypointMovementGenerator.cpp @@ -35,6 +35,8 @@ #include "movement/MoveSplineInit.h" #include +#include +#include namespace { diff --git a/src/game/Object/AggressorAI.cpp b/src/game/Object/AggressorAI.cpp index e54d0a44c7..3727a68728 100644 --- a/src/game/Object/AggressorAI.cpp +++ b/src/game/Object/AggressorAI.cpp @@ -54,7 +54,7 @@ int AggressorAI::Permissible(const Creature* creature) * * @param c The creature controlled by this AI. */ -AggressorAI::AggressorAI(Creature* c) : CreatureAI(c), i_state(STATE_NORMAL), i_tracker(TIME_INTERVAL_LOOK) +AggressorAI::AggressorAI(Creature* c) : CreatureAI(c), i_tracker(TIME_INTERVAL_LOOK) { } diff --git a/src/game/Object/AggressorAI.h b/src/game/Object/AggressorAI.h index d481308c79..e7a1964e94 100644 --- a/src/game/Object/AggressorAI.h +++ b/src/game/Object/AggressorAI.h @@ -53,7 +53,6 @@ class AggressorAI : public CreatureAI private: ObjectGuid i_victimGuid; - AggressorState i_state; TimeTracker i_tracker; }; diff --git a/src/game/Object/ArenaTeam.cpp b/src/game/Object/ArenaTeam.cpp index ff8a0d707a..f5ec0d4e3b 100644 --- a/src/game/Object/ArenaTeam.cpp +++ b/src/game/Object/ArenaTeam.cpp @@ -28,6 +28,9 @@ #include "ArenaTeam.h" #include "World.h" #include "Player.h" +#include +#include +#include void ArenaTeamMember::ModifyPersonalRating(Player* plr, int32 mod, uint32 slot) { diff --git a/src/game/Object/ArenaTeam.h b/src/game/Object/ArenaTeam.h index 361c3c12e8..913d35f383 100644 --- a/src/game/Object/ArenaTeam.h +++ b/src/game/Object/ArenaTeam.h @@ -28,6 +28,9 @@ #include "Common.h" #include "ObjectGuid.h" #include "SharedDefines.h" +#include +#include +#include class QueryResult; class WorldPacket; diff --git a/src/game/Object/AuctionHouseMgr.cpp b/src/game/Object/AuctionHouseMgr.cpp index 8f701612a6..0ca39ec000 100644 --- a/src/game/Object/AuctionHouseMgr.cpp +++ b/src/game/Object/AuctionHouseMgr.cpp @@ -41,13 +41,17 @@ #include "Mail.h" #include "Policies/Singleton.h" +#include +#include +#include +#include +#include /** \addtogroup auctionhouse * @{ * \file */ -INSTANTIATE_SINGLETON_1(AuctionHouseMgr); /** * @brief Initializes the auction house manager. @@ -1306,8 +1310,6 @@ void AuctionEntry::AuctionBidWinning(Player* newbidder) */ bool AuctionEntry::UpdateBid(uint32 newbid, Player* newbidder /*=NULL*/) { - Player* auction_owner = owner ? sObjectMgr.GetPlayer(ObjectGuid(HIGHGUID_PLAYER, owner)) : NULL; - // bid can't be greater buyout if (buyout && newbid > buyout) { diff --git a/src/game/Object/AuctionHouseMgr.h b/src/game/Object/AuctionHouseMgr.h index 3562a0e832..f4bbe83ce5 100644 --- a/src/game/Object/AuctionHouseMgr.h +++ b/src/game/Object/AuctionHouseMgr.h @@ -41,6 +41,10 @@ #include "Common.h" #include "Policies/Singleton.h" #include "DBCStructure.h" +#include +#include +#include +#include /** \addtogroup auctionhouse * @{ diff --git a/src/game/Object/Bag.cpp b/src/game/Object/Bag.cpp index 0e472b44c5..f8d4d68a36 100644 --- a/src/game/Object/Bag.cpp +++ b/src/game/Object/Bag.cpp @@ -26,6 +26,7 @@ #include "ObjectMgr.h" #include "Database/DatabaseEnv.h" #include "UpdateData.h" +#include /** * @brief Creates an empty bag item instance. diff --git a/src/game/Object/Camera.cpp b/src/game/Object/Camera.cpp index bc6b7c68dd..8d0c15649e 100644 --- a/src/game/Object/Camera.cpp +++ b/src/game/Object/Camera.cpp @@ -28,6 +28,7 @@ #include "Log.h" #include "Errors.h" #include "Player.h" +#include /** * @brief Creates a camera bound to a player. diff --git a/src/game/Object/Camera.h b/src/game/Object/Camera.h index 2d1f99946f..7465ab1248 100644 --- a/src/game/Object/Camera.h +++ b/src/game/Object/Camera.h @@ -27,6 +27,8 @@ #include "Common.h" #include "GridDefines.h" +#include +#include class ViewPoint; class WorldObject; diff --git a/src/game/Object/Corpse.cpp b/src/game/Object/Corpse.cpp index b282de8d38..e73678de34 100644 --- a/src/game/Object/Corpse.cpp +++ b/src/game/Object/Corpse.cpp @@ -29,6 +29,8 @@ #include "Database/DatabaseEnv.h" #include "World.h" #include "ObjectMgr.h" +#include +#include /** * @brief Creates a corpse object of the specified type. diff --git a/src/game/Object/Corpse.h b/src/game/Object/Corpse.h index 2fabc65910..2d9ce8ec32 100644 --- a/src/game/Object/Corpse.h +++ b/src/game/Object/Corpse.h @@ -53,6 +53,7 @@ #include "Database/DatabaseEnv.h" #include "GridDefines.h" #include "LootMgr.h" +#include /// @brief Corpse type enumeration. /// diff --git a/src/game/Object/Creature.cpp b/src/game/Object/Creature.cpp index 79c5a5ab72..bef7001ecb 100644 --- a/src/game/Object/Creature.cpp +++ b/src/game/Object/Creature.cpp @@ -41,6 +41,7 @@ #include "LootMgr.h" #include "MapManager.h" #include "TransportSystem.h" +#include "Transports.h" #include "CreatureAI.h" #include "CreatureAISelector.h" #include "InstanceData.h" @@ -61,6 +62,10 @@ // apply implementation of the singletons #include "Policies/Singleton.h" +#include +#include +#include +#include /** @@ -156,20 +161,48 @@ bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) */ void CreatureCreatePos::SelectFinalPoint(Creature* cr) { - // if object provided then selected point at specific dist/angle from object forward look - if (m_closeObject) + // no object to be near: the coordinates were given to us outright + if (!m_closeObject) { - if (m_dist == 0.0f) - { - m_pos.x = m_closeObject->GetPositionX(); - m_pos.y = m_closeObject->GetPositionY(); - m_pos.z = m_closeObject->GetPositionZ(); - } - else + return; + } + + // right on top of it -- no floor to look for, so a deck needs no special case here + if (m_dist == 0.0f) + { + m_pos.x = m_closeObject->GetPositionX(); + m_pos.y = m_closeObject->GetPositionY(); + m_pos.z = m_closeObject->GetPositionZ(); + return; + } + + // The summoner is standing on a deck, and its summon belongs on that deck -- which is + // the one floor GetClosePoint cannot find. It resolves Z against the WORLD, and the + // world beneath a hull is the sea floor: a pet called at the rail would be created a + // hundred yards straight down, in the water under the ship. Out of everyone's sight, + // too far below the deck to ever be boarded by Transport::UpdateMinions, and gone. + // + // So the spot is chosen on the vessel's deck mesh instead, and handed back in WORLD + // coordinates, because the creature being created is a grid citizen and the map has to + // be told where it stands. Boarding it -- putting it into the vessel's frame for good + // -- is UpdateMinions' job, and it does it on the next tick, now that there is a deck + // under the thing to find. + if (Transport* vessel = Transport::VesselOf(*m_closeObject)) + { + const float distance2d = m_dist + m_closeObject->GetObjectBoundingRadius() + + cr->GetObjectBoundingRadius(); + + if (const auto spot = vessel->DeckSpotNear(*m_closeObject, distance2d, m_angle)) { - m_closeObject->GetClosePoint(m_pos.x, m_pos.y, m_pos.z, cr->GetObjectBoundingRadius(), m_dist, m_angle); + float go; + vessel->CalculateGlobalPositionOf(spot->x, spot->y, spot->z, spot->o, + m_pos.x, m_pos.y, m_pos.z, go); + return; } } + + m_closeObject->GetClosePoint(m_pos.x, m_pos.y, m_pos.z, cr->GetObjectBoundingRadius(), + m_dist, m_angle); } /** @@ -204,10 +237,11 @@ Creature::Creature(CreatureSubtype subtype) : Unit(), m_lootMoney(0), m_lootGroupRecipientId(0), m_corpseRemoveTime(0), m_respawnTime(0), m_respawnDelay(25), m_corpseDelay(60), m_aggroDelay(0), m_respawnradius(5.0f), m_subtype(subtype), m_defaultMovementType(IDLE_MOTION_TYPE), m_equipmentId(0), + m_PlayerDamageReq(0), m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false), m_AI_locked(false), m_IsDeadByDefault(false), m_temporaryFactionFlags(TEMPFACTION_NONE), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), - m_PlayerDamageReq(0), m_creatureInfo(NULL) + m_creatureInfo(NULL) { /* Loot data */ hasBeenLootedOnce = false; @@ -295,13 +329,11 @@ void Creature::AddToWorld() */ void Creature::RemoveFromWorld() { - // A creature standing on a deck is a PASSENGER of that vessel, and the vessel holds a - // raw pointer to it. A pet that dies or is dismissed is deleted right after this call, - // so leaving it boarded would leave the ship dereferencing freed memory on its very - // next tick. Step off the boat before leaving the world. - // - // (Crew are unboarded explicitly by the vessel before it removes them, so by the time - // they arrive here they are already ashore and this is a no-op.) + // A creature standing on a deck is a PASSENGER of that vessel, which holds a raw pointer + // to it in its passenger list (and its container). Step off the boat before leaving the + // world, or the ship dereferences freed memory on its next tick. Idempotent, and a no-op + // for crew (the vessel unboards them explicitly first) -- but the real guarantee for a + // deleted creature is in CleanupsBeforeDelete, which is the path a dying pet takes. if (TransportInfo* transportInfo = GetTransportInfo()) { transportInfo->GetTransportBase()->UnBoardPassenger(this); @@ -326,6 +358,27 @@ void Creature::RemoveFromWorld() Unit::RemoveFromWorld(); } +/** + * @brief Final cleanup before the creature is deleted. + * + * This -- not RemoveFromWorld -- is the path a dying or dismissed PET takes: Pet::Unsummon + * queues the pet on the map's remove list, and Map::RemoveAllObjectsInRemoveList reaps it + * through Map::Remove(true), which calls CleanupsBeforeDelete and then deletes. A boarded pet + * must leave its vessel HERE, before it is freed, or the ship keeps a dangling pointer to it + * in its passenger list. (The GridReference auto-unlinks from the vessel's container when the + * creature is destroyed, but the passenger list is a plain map and does not.) Idempotent, so + * it is safe alongside the RemoveFromWorld hook and the vessel's own explicit unboards. + */ +void Creature::CleanupsBeforeDelete() +{ + if (TransportInfo* transportInfo = GetTransportInfo()) + { + transportInfo->GetTransportBase()->UnBoardPassenger(this); + } + + Unit::CleanupsBeforeDelete(); +} + /** * @brief Removes the creature corpse and schedules respawn handling. * @@ -762,8 +815,6 @@ void Creature::Update(uint32 update_diff, uint32 diff) UpdateEntry(m_originalEntry, TEAM_NONE, NULL, eventData); } - CreatureInfo const* cinfo = GetCreatureInfo(); - SelectLevel(); UpdateAllStats(); // to be sure stats is correct regarding level of the creature SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE); @@ -2990,8 +3041,20 @@ void Creature::RelocateToRespawnPoint() if (TransportInfo* transportInfo = GetTransportInfo()) { - transportInfo->SetLocalPosition(x, y, z, o); - return; + // CREW: its home IS a deck offset, so the offset is the thing to set, and the vessel + // refreshes the world cache from it. + if (!transportInfo->IsMinion()) + { + transportInfo->SetLocalPosition(x, y, z, o); + return; + } + + // A MINION -- a pet, a guardian -- only borrows the deck. Its respawn coord is a place + // on the MAP, where it was summoned, so there is nothing here for the deck frame to + // do: read as an offset it would name a point in open water beside the ship, and going + // home would be the one thing it never did. Going home means going ashore, so it + // steps off the boat first and is then relocated in the world, like any other creature. + transportInfo->GetTransportBase()->UnBoardPassenger(this); } GetMap()->CreatureRelocation(this, x, y, z, o); diff --git a/src/game/Object/Creature.h b/src/game/Object/Creature.h index f3511b4ebe..80cdbbc8fb 100644 --- a/src/game/Object/Creature.h +++ b/src/game/Object/Creature.h @@ -55,6 +55,10 @@ #include "Cell.h" #include +#include +#include +#include +#include struct SpellEntry; @@ -581,6 +585,7 @@ class Creature : public Unit void AddToWorld() override; void RemoveFromWorld() override; + void CleanupsBeforeDelete() override; bool Create(uint32 guidlow, CreatureCreatePos& cPos, CreatureInfo const* cinfo, Team team = TEAM_NONE, const CreatureData* data = NULL, GameEventCreatureData const* eventData = NULL); bool LoadCreatureAddon(bool reload); @@ -610,9 +615,9 @@ class Creature : public Unit bool IsGuard() const { return GetCreatureInfo()->ExtraFlags & CREATURE_FLAG_EXTRA_GUARD; } bool CanWalk() const { return GetCreatureInfo()->InhabitType & INHABIT_GROUND; } - bool CanSwim() const { return GetCreatureInfo()->InhabitType & INHABIT_WATER; } + bool CanSwim() const override { return GetCreatureInfo()->InhabitType & INHABIT_WATER; } bool IsSwimming() const { return (m_movementInfo.HasMovementFlag((MovementFlags)(MOVEFLAG_SWIMMING))); } - bool CanFly() const { return (GetCreatureInfo()->InhabitType & INHABIT_AIR) || (GetByteValue(UNIT_FIELD_BYTES_1, 3) & UNIT_BYTE1_FLAG_FLY_ANIM) || m_movementInfo.HasMovementFlag((MovementFlags)(MOVEFLAG_LEVITATING | MOVEFLAG_CAN_FLY)); } + bool CanFly() const override { return (GetCreatureInfo()->InhabitType & INHABIT_AIR) || (GetByteValue(UNIT_FIELD_BYTES_1, 3) & UNIT_BYTE1_FLAG_FLY_ANIM) || m_movementInfo.HasMovementFlag((MovementFlags)(MOVEFLAG_LEVITATING | MOVEFLAG_CAN_FLY)); } bool IsFlying() const { return (m_movementInfo.HasMovementFlag((MovementFlags)(MOVEFLAG_FLYING | MOVEFLAG_LEVITATING))); } bool IsTrainerOf(Player* player, bool msg) const; bool CanInteractWithBattleMaster(Player* player, bool msg) const; @@ -881,9 +886,10 @@ class Creature : public Unit void SetRespawnCoord(float x, float y, float z, float ori) { m_respawnPos.x = x; m_respawnPos.y = y; m_respawnPos.z = z; m_respawnPos.o = ori; } void GetRespawnCoord(float& x, float& y, float& z, float* ori = NULL, float* dist = NULL) const; - /// Go home -- in whichever world this creature lives in. For a crew member the - /// respawn coord is a deck offset, not a map coordinate, and must not be handed to - /// the grid. + /// Go home -- in whichever world this creature lives in. For a CREW member the respawn + /// coord is a deck offset, not a map coordinate, and must not be handed to the grid. A + /// boarded MINION is the opposite case: its home is a place on the map, so going home + /// means going ashore, and it leaves the vessel on the way. void RelocateToRespawnPoint(); void ResetRespawnCoord(); diff --git a/src/game/Object/CreatureAI.cpp b/src/game/Object/CreatureAI.cpp index f4ea5d03a6..4141b92a0a 100644 --- a/src/game/Object/CreatureAI.cpp +++ b/src/game/Object/CreatureAI.cpp @@ -29,6 +29,7 @@ #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "CellImpl.h" +#include static_assert(MAXIMAL_AI_EVENT_EVENTAI <= 32, "Maximal 32 AI_EVENTs supported with EventAI"); diff --git a/src/game/Object/CreatureAISelector.cpp b/src/game/Object/CreatureAISelector.cpp index c74e84d2a4..97f38000af 100644 --- a/src/game/Object/CreatureAISelector.cpp +++ b/src/game/Object/CreatureAISelector.cpp @@ -31,9 +31,8 @@ #include "ScriptMgr.h" #include "Pet.h" #include "Log.h" +#include -INSTANTIATE_SINGLETON_1(CreatureAIRegistry); -INSTANTIATE_SINGLETON_1(MovementGeneratorRegistry); namespace FactorySelector { diff --git a/src/game/Object/CreatureEventAI.cpp b/src/game/Object/CreatureEventAI.cpp index d597d634fc..84a1039c87 100644 --- a/src/game/Object/CreatureEventAI.cpp +++ b/src/game/Object/CreatureEventAI.cpp @@ -34,6 +34,7 @@ #include "InstanceData.h" #include "Chat.h" #include "Language.h" +#include /** * @brief Updates the repeat timer for an EventAI event. diff --git a/src/game/Object/CreatureEventAI.h b/src/game/Object/CreatureEventAI.h index a71434d12c..3b64ac9538 100644 --- a/src/game/Object/CreatureEventAI.h +++ b/src/game/Object/CreatureEventAI.h @@ -29,6 +29,8 @@ #include "Creature.h" #include "CreatureAI.h" #include "Unit.h" +#include +#include class Player; class WorldObject; diff --git a/src/game/Object/CreatureEventAIMgr.cpp b/src/game/Object/CreatureEventAIMgr.cpp index 8da3b81711..865c34cf20 100644 --- a/src/game/Object/CreatureEventAIMgr.cpp +++ b/src/game/Object/CreatureEventAIMgr.cpp @@ -33,8 +33,10 @@ #include "GridDefines.h" #include "SpellMgr.h" #include "World.h" +#include +#include +#include -INSTANTIATE_SINGLETON_1(CreatureEventAIMgr); // ------------------- /** diff --git a/src/game/Object/CreatureSpellCooldown.cpp b/src/game/Object/CreatureSpellCooldown.cpp index 358c8c80c5..8a6d0ee95c 100644 --- a/src/game/Object/CreatureSpellCooldown.cpp +++ b/src/game/Object/CreatureSpellCooldown.cpp @@ -60,6 +60,7 @@ #include "Policies/Singleton.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/CreatureVendorTrainer.cpp b/src/game/Object/CreatureVendorTrainer.cpp index f367b5a4a0..0876856f52 100644 --- a/src/game/Object/CreatureVendorTrainer.cpp +++ b/src/game/Object/CreatureVendorTrainer.cpp @@ -59,6 +59,7 @@ #include "Policies/Singleton.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/Formulas.h b/src/game/Object/Formulas.h index 34d5e9903d..1cb8479c74 100644 --- a/src/game/Object/Formulas.h +++ b/src/game/Object/Formulas.h @@ -26,6 +26,7 @@ #define MANGOS_FORMULAS_H #include "World.h" +#include namespace MaNGOS { diff --git a/src/game/Object/GMTicketMgr.cpp b/src/game/Object/GMTicketMgr.cpp index 1ac582d0e4..c5f7e9d553 100644 --- a/src/game/Object/GMTicketMgr.cpp +++ b/src/game/Object/GMTicketMgr.cpp @@ -31,8 +31,10 @@ #include "ProgressBar.h" #include "Policies/Singleton.h" #include "Player.h" +#include +#include +#include -INSTANTIATE_SINGLETON_1(GMTicketMgr); /** * @brief Stores GM survey answers from a received packet. diff --git a/src/game/Object/GMTicketMgr.h b/src/game/Object/GMTicketMgr.h index f45dae202a..c7c26d0384 100644 --- a/src/game/Object/GMTicketMgr.h +++ b/src/game/Object/GMTicketMgr.h @@ -32,6 +32,8 @@ #include "WorldSession.h" #include "SharedDefines.h" #include +#include +#include /** * \addtogroup game diff --git a/src/game/Object/GameObject.cpp b/src/game/Object/GameObject.cpp index b1b9538d6a..d2089160ee 100644 --- a/src/game/Object/GameObject.cpp +++ b/src/game/Object/GameObject.cpp @@ -53,6 +53,9 @@ #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif /* ENABLE_ELUNA */ @@ -62,8 +65,8 @@ GameObject::GameObject() : WorldObject(), loot(this), m_model(NULL), - m_displayInfo(NULL), m_goInfo(NULL), + m_displayInfo(NULL), m_AI_locked(false) { m_objectType |= TYPEMASK_GAMEOBJECT; @@ -90,8 +93,6 @@ GameObject::GameObject() : WorldObject(), m_reStockTimer = 0; m_rearmTimer = 0; m_despawnTimer = 0; - - m_AI_locked; } /** @@ -1275,8 +1276,6 @@ void GameObject::RollIfMineralVein() { uint32 entrynew = RollMineralVein(GetRealEntry()); - uint32 guid = GetObjectGuid(); - GameObjectInfo const* goinfonew = ObjectMgr::GetGameObjectInfo(entrynew); m_goInfo = goinfonew; diff --git a/src/game/Object/GameObject.h b/src/game/Object/GameObject.h index 01f135b884..75beac74e7 100644 --- a/src/game/Object/GameObject.h +++ b/src/game/Object/GameObject.h @@ -57,6 +57,9 @@ #include "LootMgr.h" #include "Utilities/EventProcessor.h" #include +#include +#include +#include // GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push,N), also any gcc version not support it at some platform #if defined( __GNUC__ ) diff --git a/src/game/Object/GameObjectDestructible.cpp b/src/game/Object/GameObjectDestructible.cpp index bc87eca327..c20696feea 100644 --- a/src/game/Object/GameObjectDestructible.cpp +++ b/src/game/Object/GameObjectDestructible.cpp @@ -54,6 +54,7 @@ #include "terrain/Geometry/Quat.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/GameObjectUpdate.cpp b/src/game/Object/GameObjectUpdate.cpp index f22b20ab12..7fe4cd939f 100644 --- a/src/game/Object/GameObjectUpdate.cpp +++ b/src/game/Object/GameObjectUpdate.cpp @@ -54,6 +54,7 @@ #include "terrain/Geometry/Quat.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ enum diff --git a/src/game/Object/GameObjectUse.cpp b/src/game/Object/GameObjectUse.cpp index b293152180..f64e30b90c 100644 --- a/src/game/Object/GameObjectUse.cpp +++ b/src/game/Object/GameObjectUse.cpp @@ -54,6 +54,8 @@ #include "terrain/Geometry/Quat.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/Guild.cpp b/src/game/Object/Guild.cpp index 82a0cfcd6b..024e0ee789 100644 --- a/src/game/Object/Guild.cpp +++ b/src/game/Object/Guild.cpp @@ -37,6 +37,9 @@ #include "World.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif /* ENABLE_ELUNA */ //// MemberSlot //////////////////////////////////////////// diff --git a/src/game/Object/Guild.h b/src/game/Object/Guild.h index 5c9b95c5b3..b3c7ae1e6e 100644 --- a/src/game/Object/Guild.h +++ b/src/game/Object/Guild.h @@ -32,6 +32,10 @@ #include "Item.h" #include "ObjectAccessor.h" #include "SharedDefines.h" +#include +#include +#include +#include class Item; diff --git a/src/game/Object/GuildBank.cpp b/src/game/Object/GuildBank.cpp index 862d4776b7..153ca84e29 100644 --- a/src/game/Object/GuildBank.cpp +++ b/src/game/Object/GuildBank.cpp @@ -42,6 +42,9 @@ #include "Log.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif /* ENABLE_ELUNA */ // ************************************************* diff --git a/src/game/Object/GuildRank.cpp b/src/game/Object/GuildRank.cpp index aa26264144..fd978b5871 100644 --- a/src/game/Object/GuildRank.cpp +++ b/src/game/Object/GuildRank.cpp @@ -40,6 +40,7 @@ #include "World.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/HonorMgr.cpp b/src/game/Object/HonorMgr.cpp index a3798ee6ba..fc3c20da74 100644 --- a/src/game/Object/HonorMgr.cpp +++ b/src/game/Object/HonorMgr.cpp @@ -32,6 +32,7 @@ #include "ObjectGuid.h" #include "UpdateFields.h" #include "Formulas.h" +#include void HonorMgr::UpdateKills() { diff --git a/src/game/Object/HonorMgr.h b/src/game/Object/HonorMgr.h index f716abd46e..808646f986 100644 --- a/src/game/Object/HonorMgr.h +++ b/src/game/Object/HonorMgr.h @@ -26,6 +26,7 @@ #define MANGOS_H_HONORMGR #include "Common.h" +#include class Player; class Unit; diff --git a/src/game/Object/Item.cpp b/src/game/Object/Item.cpp index 122430a57e..5a21321e5f 100644 --- a/src/game/Object/Item.cpp +++ b/src/game/Object/Item.cpp @@ -31,6 +31,8 @@ #include "SQLStorages.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/Item.h b/src/game/Object/Item.h index 45f503e79b..a939591874 100644 --- a/src/game/Object/Item.h +++ b/src/game/Object/Item.h @@ -57,6 +57,7 @@ #include "Object.h" #include "LootMgr.h" #include "ItemPrototype.h" +#include struct SpellEntry; class Bag; diff --git a/src/game/Object/ItemEnchantmentMgr.cpp b/src/game/Object/ItemEnchantmentMgr.cpp index 2b407b67ff..8c1e37a3ee 100644 --- a/src/game/Object/ItemEnchantmentMgr.cpp +++ b/src/game/Object/ItemEnchantmentMgr.cpp @@ -31,6 +31,7 @@ #include #include +#include struct EnchStoreItem { diff --git a/src/game/Object/ItemPersistence.cpp b/src/game/Object/ItemPersistence.cpp index e447b3dfcd..e43a566004 100644 --- a/src/game/Object/ItemPersistence.cpp +++ b/src/game/Object/ItemPersistence.cpp @@ -33,6 +33,8 @@ #include "SQLStorages.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/ItemPrototype.h b/src/game/Object/ItemPrototype.h index d03de892ba..a8525f93fa 100644 --- a/src/game/Object/ItemPrototype.h +++ b/src/game/Object/ItemPrototype.h @@ -26,6 +26,8 @@ #define MANGOS_H_ITEMPROTOTYPE #include "Common.h" +#include +#include enum ItemModType { diff --git a/src/game/Object/LootMgr.cpp b/src/game/Object/LootMgr.cpp index 8474e7985f..e7513131b1 100644 --- a/src/game/Object/LootMgr.cpp +++ b/src/game/Object/LootMgr.cpp @@ -33,6 +33,8 @@ #include "SQLStorages.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include +#include static eConfigFloatValues const qualityToRate[MAX_ITEM_QUALITY] = { diff --git a/src/game/Object/LootMgr.h b/src/game/Object/LootMgr.h index e28a4264e3..26c4c5afd0 100644 --- a/src/game/Object/LootMgr.h +++ b/src/game/Object/LootMgr.h @@ -30,6 +30,9 @@ #include "Utilities/LinkedReference/RefManager.h" #include +#include +#include +#include class Player; class LootStore; diff --git a/src/game/Object/Object.cpp b/src/game/Object/Object.cpp index 2734aa5291..3360a3c000 100644 --- a/src/game/Object/Object.cpp +++ b/src/game/Object/Object.cpp @@ -67,6 +67,7 @@ #include "LuaEngine.h" #include "ElunaConfig.h" #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/Object.h b/src/game/Object/Object.h index 75790a6a74..569aa97d80 100644 --- a/src/game/Object/Object.h +++ b/src/game/Object/Object.h @@ -36,7 +36,9 @@ #include "LuaValue.h" #endif /* ENABLE_ELUNA */ -#include +#include +#include +#include #define CONTACT_DISTANCE 0.5f #define INTERACTION_DISTANCE 5.0f diff --git a/src/game/Object/ObjectAccessor.cpp b/src/game/Object/ObjectAccessor.cpp index f5a4918a82..8d81eb0314 100644 --- a/src/game/Object/ObjectAccessor.cpp +++ b/src/game/Object/ObjectAccessor.cpp @@ -36,10 +36,10 @@ #include "World.h" #include +#include +#include +#include -#define CLASS_LOCK MaNGOS::ClassLevelLockable -INSTANTIATE_SINGLETON_2(ObjectAccessor, CLASS_LOCK); -INSTANTIATE_CLASS_MUTEX(ObjectAccessor, ACE_Recursive_Thread_Mutex); /** * @brief Initializes the global object accessor. @@ -140,7 +140,7 @@ Player* ObjectAccessor::FindPlayer(ObjectGuid guid, bool inWorld /*= true*/) */ Player* ObjectAccessor::FindPlayerByName(const char* name) { - ACE_READ_GUARD_RETURN(HashMapHolder::LockType, guard, i_playerMap.GetLock(), nullptr) + std::shared_lock::LockType> guard(i_playerMap.GetLock()); for (auto& iter : i_playerMap.GetContainer()) { if (iter.second->IsInWorld() && (::strcmp(name, iter.second->GetName()) == 0)) @@ -194,7 +194,7 @@ void ObjectAccessor::KickPlayer(ObjectGuid guid) */ Corpse* ObjectAccessor::GetCorpseForPlayerGUID(ObjectGuid guid) { - ACE_GUARD_RETURN(LockType, guard, i_corpseGuard, nullptr) + std::lock_guard guard(i_corpseGuard); Player2CorpsesMapType::iterator iter = i_player2corpse.find(guid); @@ -216,7 +216,7 @@ void ObjectAccessor::RemoveCorpse(Corpse* corpse) { MANGOS_ASSERT(corpse && corpse->GetType() != CORPSE_BONES); - ACE_GUARD(LockType, guard, i_corpseGuard) + std::lock_guard guard(i_corpseGuard); Player2CorpsesMapType::iterator iter = i_player2corpse.find(corpse->GetOwnerGuid()); if (iter == i_player2corpse.end()) @@ -243,7 +243,7 @@ void ObjectAccessor::AddCorpse(Corpse* corpse) { MANGOS_ASSERT(corpse && corpse->GetType() != CORPSE_BONES); - ACE_GUARD(LockType, guard, i_corpseGuard) + std::lock_guard guard(i_corpseGuard); MANGOS_ASSERT(i_player2corpse.find(corpse->GetOwnerGuid()) == i_player2corpse.end()); i_player2corpse[corpse->GetOwnerGuid()] = corpse; @@ -264,7 +264,7 @@ void ObjectAccessor::AddCorpse(Corpse* corpse) */ void ObjectAccessor::AddCorpsesToGrid(GridPair const& gridpair, GridType& grid, Map* map) { - ACE_GUARD(LockType, guard, i_corpseGuard) + std::lock_guard guard(i_corpseGuard); for (Player2CorpsesMapType::iterator iter = i_player2corpse.begin(); iter != i_player2corpse.end(); ++iter) { diff --git a/src/game/Object/ObjectAccessor.h b/src/game/Object/ObjectAccessor.h index 89360fc756..bbb16bed72 100644 --- a/src/game/Object/ObjectAccessor.h +++ b/src/game/Object/ObjectAccessor.h @@ -50,8 +50,6 @@ #include "Common.h" #include "Platform/Define.h" #include "Policies/Singleton.h" -#include -#include "Policies/ThreadingModel.h" #include "UpdateData.h" #include "GridDefines.h" #include "Object.h" @@ -59,6 +57,8 @@ #include "Corpse.h" #include +#include +#include class Unit; class WorldObject; @@ -82,9 +82,9 @@ class Map; /// /// @note This is a thread-safe singleton, should not be instantiated directly /// @see sObjectAccessor for the singleton accessor macro -class ObjectAccessor : public MaNGOS::Singleton > +class ObjectAccessor : public MaNGOS::Singleton { - friend class MaNGOS::OperatorNew; + friend class MaNGOS::Singleton; ObjectAccessor(); ~ObjectAccessor(); @@ -104,7 +104,7 @@ class ObjectAccessor : public MaNGOS::Singleton Object pointer) using MapType = std::unordered_map; /// @brief Lock type for synchronization - using LockType = ACE_RW_Thread_Mutex; + using LockType = std::shared_mutex; /// @brief Constructor initializes empty map - HashMapHolder() : i_lock(nullptr), m_objectMap() {} + HashMapHolder() : m_objectMap() {} /// @brief Insert an object into the map with write lock. /// /// @param o Pointer to object to insert void Insert(T* o) { - ACE_WRITE_GUARD(LockType, guard, i_lock) + std::unique_lock guard(i_lock); m_objectMap[o->GetObjectGuid()] = o; } @@ -133,7 +133,7 @@ class ObjectAccessor : public MaNGOS::Singleton guard(i_lock); m_objectMap.erase(o->GetObjectGuid()); } @@ -143,7 +143,7 @@ class ObjectAccessor : public MaNGOS::Singleton guard(i_lock); auto itr = m_objectMap.find(guid); return (itr != m_objectMap.end()) ? itr->second : nullptr; } @@ -168,7 +168,11 @@ class ObjectAccessor : public MaNGOS::Singleton; - using LockType = ACE_Recursive_Thread_Mutex; + /// Guards the player<->corpse map. Plain, not recursive: the four functions that + /// take it (Add/Remove/GetCorpseForPlayerGUID/AddCorpsesToGrid) never call one + /// another, and the callers that chain them (ConvertCorpseForPlayer, + /// RemoveOldCorpses) do so without holding it. + using LockType = std::mutex; public: @@ -321,7 +325,7 @@ class ObjectAccessor : public MaNGOS::Singleton void DoForAllPlayers(F&& f) { - ACE_READ_GUARD(HashMapHolder::LockType, g, i_playerMap.GetLock()) + std::shared_lock::LockType> g(i_playerMap.GetLock()); for (auto& iter : i_playerMap.GetContainer()) { if (iter.second != nullptr) diff --git a/src/game/Object/ObjectGuid.cpp b/src/game/Object/ObjectGuid.cpp index 99f12eccf1..dbe55d3b14 100644 --- a/src/game/Object/ObjectGuid.cpp +++ b/src/game/Object/ObjectGuid.cpp @@ -27,7 +27,9 @@ #include "World.h" #include "ObjectMgr.h" +#include // std::abort, on guid-space exhaustion #include +#include /** * @brief Gets a human-readable type name for a high GUID category. @@ -94,6 +96,18 @@ uint32 ObjectGuidGenerator::Generate() { sLog.outError("%s guid overflow!! Can't continue, shutting down server. ", ObjectGuid::GetTypeName(high)); World::StopNow(ERROR_EXIT_CODE); + + // See IdGenerator::Generate(): the stop lands at the next world-loop check, not + // here, so the counter can still be driven past the limit within this tick. Beyond + // it every value we could return is one already in use, and a duplicate ObjectGuid + // is world corruption that persists into the character database. Refuse to wrap. + if (m_nextGuid >= ObjectGuid::GetMaxCounter(high)) + { + sLog.outError("%s guid space is exhausted; refusing to issue a duplicate guid.", + ObjectGuid::GetTypeName(high)); + sLog.Flush(); + std::abort(); + } } return m_nextGuid++; } diff --git a/src/game/Object/ObjectGuid.h b/src/game/Object/ObjectGuid.h index 7c5a47a720..3ac6f88835 100644 --- a/src/game/Object/ObjectGuid.h +++ b/src/game/Object/ObjectGuid.h @@ -27,6 +27,10 @@ #include "Common.h" #include "ByteBuffer.h" +#include +#include +#include +#include enum TypeID { diff --git a/src/game/Object/ObjectMgr.cpp b/src/game/Object/ObjectMgr.cpp index 8ee742e8b3..5f9571ec6a 100644 --- a/src/game/Object/ObjectMgr.cpp +++ b/src/game/Object/ObjectMgr.cpp @@ -56,10 +56,18 @@ #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include // std::abort, on guid-space exhaustion #include #include +#include +#include +#include +#include +#include +#include +#include +#include -INSTANTIATE_SINGLETON_1(ObjectMgr); // Temporary startup accumulator for LivingWorld observability (written during LoadActiveEntities(NULL) only) static ObjectMgr::LivingWorldStartupStats s_livingWorldStats; @@ -189,6 +197,19 @@ T IdGenerator::Generate() { sLog.outError("%s guid overflow!! Can't continue, shutting down server. ", m_name); World::StopNow(ERROR_EXIT_CODE); + + // StopNow() is only honoured at the next world-loop check, which can still be many + // Generate() calls away inside the current tick — the warning above fires one id + // short of the limit, and that headroom is all we get. Once it is spent there is + // nothing safe left to return: wrapping issues a *duplicate* guid, and a duplicate + // guid is silent, persistent corruption the moment it reaches the database. Dying + // here is strictly better than saving a corrupt world, so refuse rather than wrap. + if (m_nextGuid >= std::numeric_limits::max()) + { + sLog.outError("%s guid space is exhausted; refusing to issue a duplicate guid.", m_name); + sLog.Flush(); + std::abort(); + } } return m_nextGuid++; } @@ -633,7 +654,7 @@ void ObjectMgr::LoadPetCreateSpells() continue; } - if (CreatureSpellDataEntry const* petSpellEntry = cInfo->PetSpellDataId ? sCreatureSpellDataStore.LookupEntry(cInfo->PetSpellDataId) : NULL) + if (cInfo->PetSpellDataId && sCreatureSpellDataStore.LookupEntry(cInfo->PetSpellDataId)) { sLog.outErrorDb("Creature id %u listed in `petcreateinfo_spell` have set `PetSpellDataId` field and will use its instead, skip.", creature_id); continue; @@ -3004,7 +3025,7 @@ ObjectMgr::LivingWorldStartupStats ObjectMgr::LoadActiveEntities(Map* _map) s_livingWorldStartupPass = true; uint32 continents[] = {0, 1, 369, 530}; - for (int i = 0; i < countof(continents); ++i) + for (size_t i = 0; i < countof(continents); ++i) { _map = sMapMgr.FindMap(continents[i]); if (!_map) diff --git a/src/game/Object/ObjectMgr.h b/src/game/Object/ObjectMgr.h index 0c499ba687..b07fcf8ec4 100644 --- a/src/game/Object/ObjectMgr.h +++ b/src/game/Object/ObjectMgr.h @@ -43,6 +43,11 @@ #include #include +#include +#include +#include +#include +#include class Group; class ArenaTeam; @@ -127,7 +132,7 @@ typedef UNORDERED_MAP < uint32/*(mapid,spawnMode) pair*/, CellObjectGuidsMap > M #define MAX_CREATURE_AI_TEXT_STRING_ID (-1000000) // Anything below MAX_CREATURE_AI_TEXT_STRING_ID is handled by the external script lib -static_assert(MAX_DB_SCRIPT_STRING_ID < ACE_INT32_MAX, "Must scope with int32 range"); +static_assert(MAX_DB_SCRIPT_STRING_ID < INT32_MAX, "Must scope with int32 range"); struct MangosStringLocale { diff --git a/src/game/Object/ObjectMgrAreaTrigger.cpp b/src/game/Object/ObjectMgrAreaTrigger.cpp index daa5d6f2ba..026484d84d 100644 --- a/src/game/Object/ObjectMgrAreaTrigger.cpp +++ b/src/game/Object/ObjectMgrAreaTrigger.cpp @@ -57,6 +57,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Loads area trigger teleport destinations and access requirements. diff --git a/src/game/Object/ObjectMgrCreatures.cpp b/src/game/Object/ObjectMgrCreatures.cpp index e65978af9d..72bad6963d 100644 --- a/src/game/Object/ObjectMgrCreatures.cpp +++ b/src/game/Object/ObjectMgrCreatures.cpp @@ -60,6 +60,14 @@ #include "DisableMgr.h" #include "Formulas.h" #include "ItemEnchantmentMgr.h" +#include +#include +#include +#include +#include +#include +#include +#include struct SQLCreatureLoader : public SQLStorageLoaderBase { @@ -1027,7 +1035,6 @@ void ObjectMgr::LoadCreatureModelRace() */ void ObjectMgr::LoadCreatures() { - uint32 count = 0; // 0 1 2 3 QueryResult* result = WorldDatabase.Query("SELECT `creature`.`guid`, `creature`.`id`, `map`, `modelid`," // 4 5 6 7 8 9 10 11 @@ -1229,8 +1236,6 @@ void ObjectMgr::LoadCreatures() } } } - - ++count; } while (result->NextRow()); diff --git a/src/game/Object/ObjectMgrGameObjects.cpp b/src/game/Object/ObjectMgrGameObjects.cpp index b91d68cbd1..83c1adda0a 100644 --- a/src/game/Object/ObjectMgrGameObjects.cpp +++ b/src/game/Object/ObjectMgrGameObjects.cpp @@ -64,8 +64,6 @@ */ void ObjectMgr::LoadGameObjects() { - uint32 count = 0; - // 0 1 2 3 4 5 6 QueryResult* result = WorldDatabase.Query("SELECT `gameobject`.`guid`, `gameobject`.`id`, `gameobject`.`map`, `gameobject`.`position_x`, `gameobject`.`position_y`, `gameobject`.`position_z`, `gameobject`.`orientation`, " // 7 8 9 10 11 12 13 14 @@ -199,9 +197,6 @@ void ObjectMgr::LoadGameObjects() { AddGameobjectToGrid(guid, &data); } - - - ++count; } while (result->NextRow()); diff --git a/src/game/Object/ObjectMgrGameTele.cpp b/src/game/Object/ObjectMgrGameTele.cpp index 7e524b00f7..6d02d6209c 100644 --- a/src/game/Object/ObjectMgrGameTele.cpp +++ b/src/game/Object/ObjectMgrGameTele.cpp @@ -56,6 +56,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Loads saved game teleport locations. diff --git a/src/game/Object/ObjectMgrGossipMenu.cpp b/src/game/Object/ObjectMgrGossipMenu.cpp index e834675390..b87826c4d3 100644 --- a/src/game/Object/ObjectMgrGossipMenu.cpp +++ b/src/game/Object/ObjectMgrGossipMenu.cpp @@ -56,6 +56,9 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include +#include +#include /** * @brief Loads gossip menu headers and validates linked texts, scripts, and conditions. diff --git a/src/game/Object/ObjectMgrGraveyard.cpp b/src/game/Object/ObjectMgrGraveyard.cpp index c164e4b2d7..021cb94a80 100644 --- a/src/game/Object/ObjectMgrGraveyard.cpp +++ b/src/game/Object/ObjectMgrGraveyard.cpp @@ -57,6 +57,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Loads graveyard to zone links from the database. diff --git a/src/game/Object/ObjectMgrInstanceData.cpp b/src/game/Object/ObjectMgrInstanceData.cpp index 1405bb8990..ea594ed14e 100644 --- a/src/game/Object/ObjectMgrInstanceData.cpp +++ b/src/game/Object/ObjectMgrInstanceData.cpp @@ -57,6 +57,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Gets instance template data by map id. @@ -258,7 +259,7 @@ void ObjectMgr::LoadGroups() uint32 leaderGuidLow = fields[0].GetUInt32(); uint32 mapId = fields[1].GetUInt32(); - Difficulty diff = (Difficulty)fields[4].GetUInt8(); + uint8 diff = fields[4].GetUInt8(); uint32 groupId = fields[7].GetUInt32(); if (!group || group->GetId() != groupId) @@ -285,7 +286,7 @@ void ObjectMgr::LoadGroups() diff = REGULAR_DIFFICULTY; } - DungeonPersistentState* state = (DungeonPersistentState*)sMapPersistentStateMgr.AddPersistentState(mapEntry, fields[2].GetUInt32(), diff, (time_t)fields[5].GetUInt64(), (fields[6].GetUInt32() == 0), true); + DungeonPersistentState* state = (DungeonPersistentState*)sMapPersistentStateMgr.AddPersistentState(mapEntry, fields[2].GetUInt32(), Difficulty(diff), (time_t)fields[5].GetUInt64(), (fields[6].GetUInt32() == 0), true); group->BindToInstance(state, fields[3].GetBool(), true); } while (result->NextRow()); diff --git a/src/game/Object/ObjectMgrItems.cpp b/src/game/Object/ObjectMgrItems.cpp index 0275783e96..c610a20c2d 100644 --- a/src/game/Object/ObjectMgrItems.cpp +++ b/src/game/Object/ObjectMgrItems.cpp @@ -60,6 +60,8 @@ #include "DisableMgr.h" #include "Formulas.h" #include "ItemEnchantmentMgr.h" +#include +#include /** * @brief Loads localized item names and descriptions. diff --git a/src/game/Object/ObjectMgrLocaleStrings.cpp b/src/game/Object/ObjectMgrLocaleStrings.cpp index e0cbd3162e..d9b5ac48b2 100644 --- a/src/game/Object/ObjectMgrLocaleStrings.cpp +++ b/src/game/Object/ObjectMgrLocaleStrings.cpp @@ -56,6 +56,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Gets localized creature name and subname strings for a locale index. diff --git a/src/game/Object/ObjectMgrLocales.cpp b/src/game/Object/ObjectMgrLocales.cpp index d59d597436..7e97ffcfb5 100644 --- a/src/game/Object/ObjectMgrLocales.cpp +++ b/src/game/Object/ObjectMgrLocales.cpp @@ -57,6 +57,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Loads localized creature names and subnames from the database. diff --git a/src/game/Object/ObjectMgrNameCheck.cpp b/src/game/Object/ObjectMgrNameCheck.cpp index 964aa0879a..7bae7bd3ea 100644 --- a/src/game/Object/ObjectMgrNameCheck.cpp +++ b/src/game/Object/ObjectMgrNameCheck.cpp @@ -56,6 +56,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Loads the list of reserved player names. diff --git a/src/game/Object/ObjectMgrPlayerInfo.cpp b/src/game/Object/ObjectMgrPlayerInfo.cpp index 178d391ec8..41675941d6 100644 --- a/src/game/Object/ObjectMgrPlayerInfo.cpp +++ b/src/game/Object/ObjectMgrPlayerInfo.cpp @@ -57,6 +57,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Loads pet base stats for each supported level. diff --git a/src/game/Object/ObjectMgrQuests.cpp b/src/game/Object/ObjectMgrQuests.cpp index 4ded7bfa7c..a497635f18 100644 --- a/src/game/Object/ObjectMgrQuests.cpp +++ b/src/game/Object/ObjectMgrQuests.cpp @@ -60,6 +60,8 @@ #include "DisableMgr.h" #include "Formulas.h" #include "ItemEnchantmentMgr.h" +#include +#include /** * @brief Loads quest templates and validates quest-related database references. diff --git a/src/game/Object/ObjectMgrText.cpp b/src/game/Object/ObjectMgrText.cpp index a6da22a6b7..4a312fe41d 100644 --- a/src/game/Object/ObjectMgrText.cpp +++ b/src/game/Object/ObjectMgrText.cpp @@ -57,6 +57,8 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include +#include /** * @brief Loads page text records and validates page chains. diff --git a/src/game/Object/ObjectMgrTrainerVendor.cpp b/src/game/Object/ObjectMgrTrainerVendor.cpp index 081bc01f4a..8a628023a0 100644 --- a/src/game/Object/ObjectMgrTrainerVendor.cpp +++ b/src/game/Object/ObjectMgrTrainerVendor.cpp @@ -57,6 +57,7 @@ #include "CellImpl.h" #include "DisableMgr.h" #include "ItemEnchantmentMgr.h" +#include /** * @brief Loads trainer spell data from a database table. diff --git a/src/game/Object/ObjectPosSelector.cpp b/src/game/Object/ObjectPosSelector.cpp index 6501063bbb..557df4aaf0 100644 --- a/src/game/Object/ObjectPosSelector.cpp +++ b/src/game/Object/ObjectPosSelector.cpp @@ -24,6 +24,7 @@ #include "ObjectPosSelector.h" #include "Object.h" +#include // The bigger this value, the more space npcs require around their target #define OCCUPY_POS_ANGLE_ATAN_FACTOR 1.8f diff --git a/src/game/Object/ObjectUpdate.cpp b/src/game/Object/ObjectUpdate.cpp index 426988a00c..ebcfbfcd16 100644 --- a/src/game/Object/ObjectUpdate.cpp +++ b/src/game/Object/ObjectUpdate.cpp @@ -75,6 +75,7 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_ELUNA #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/ObjectValues.cpp b/src/game/Object/ObjectValues.cpp index a46f9a5c71..40b023b62a 100644 --- a/src/game/Object/ObjectValues.cpp +++ b/src/game/Object/ObjectValues.cpp @@ -75,6 +75,7 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_ELUNA #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/Pet.cpp b/src/game/Object/Pet.cpp index 286a29a91a..bfb175eb85 100644 --- a/src/game/Object/Pet.cpp +++ b/src/game/Object/Pet.cpp @@ -31,6 +31,7 @@ #include "Formulas.h" #include "SpellAuras.h" #include "Unit.h" +#include // numbers represent minutes * 100 while happy (you get 100 loyalty points per min while happy) uint32 const LevelUpLoyalty[6] = diff --git a/src/game/Object/Pet.h b/src/game/Object/Pet.h index 42678c8641..1c7b78fba4 100644 --- a/src/game/Object/Pet.h +++ b/src/game/Object/Pet.h @@ -56,6 +56,9 @@ #include "ObjectGuid.h" #include "Creature.h" #include "Unit.h" +#include +#include +#include /// @brief Pet type enumeration. /// @@ -218,7 +221,7 @@ class Pet : public Creature void SetDeathState(DeathState s) override; // overwrite virtual Creature::SetDeathState and Unit::SetDeathState void Update(uint32 update_diff, uint32 diff) override; // overwrite virtual Creature::Update and Unit::Update - uint8 GetPetAutoSpellSize() const { return m_autospells.size(); } + uint8 GetPetAutoSpellSize() const override { return m_autospells.size(); } uint32 GetPetAutoSpellOnPos(uint8 pos) const override { if (pos >= m_autospells.size()) @@ -231,7 +234,7 @@ class Pet : public Creature } } - bool CanSwim() const + bool CanSwim() const override { Unit const* owner = GetOwner(); if (owner) @@ -244,7 +247,7 @@ class Pet : public Creature } } - bool CanFly() const { return false; } // pet are not able to fly. TODO: check if this is right + bool CanFly() const override { return false; } // pet are not able to fly. TODO: check if this is right void RegenerateAll(uint32 update_diff) override; // overwrite Creature::RegenerateAll void LooseHappiness(); @@ -326,7 +329,7 @@ class Pet : public Creature void ResetAuraUpdateMask() { m_auraUpdateMask = 0; } // overwrite Creature function for name localization back to WorldObject version without localization - const char* GetNameForLocaleIdx(int32 locale_idx) const { return WorldObject::GetNameForLocaleIdx(locale_idx); } + const char* GetNameForLocaleIdx(int32 locale_idx) const override { return WorldObject::GetNameForLocaleIdx(locale_idx); } DeclinedName const* GetDeclinedNames() const { return m_declinedname; } diff --git a/src/game/Object/PetAI.cpp b/src/game/Object/PetAI.cpp index f3786700e9..c5e84f26d7 100644 --- a/src/game/Object/PetAI.cpp +++ b/src/game/Object/PetAI.cpp @@ -32,6 +32,8 @@ #include "Creature.h" #include "World.h" #include "Util.h" +#include +#include /** * @brief Determines whether PetAI can control the given creature. diff --git a/src/game/Object/PetDatabase.cpp b/src/game/Object/PetDatabase.cpp index 4129bc2755..1cf608c23d 100644 --- a/src/game/Object/PetDatabase.cpp +++ b/src/game/Object/PetDatabase.cpp @@ -35,6 +35,9 @@ #include "CreatureAI.h" #include "Unit.h" #include "Util.h" +#include +#include +#include /** * @brief Loads a pet from the database for an owner. @@ -394,12 +397,6 @@ void Pet::SavePetToDB(PetSaveMode mode) _SaveSpellCooldowns(); _SaveAuras(); - uint32 loyalty = 1; - if (getPetType() != HUNTER_PET) - { - loyalty = GetLoyaltyLevel(); - } - uint32 ownerLow = GetOwnerGuid().GetCounter(); // remove current data static SqlStatementID delPet ; diff --git a/src/game/Object/PetSpells.cpp b/src/game/Object/PetSpells.cpp index d6c3476876..e568740118 100644 --- a/src/game/Object/PetSpells.cpp +++ b/src/game/Object/PetSpells.cpp @@ -35,6 +35,7 @@ #include "CreatureAI.h" #include "Unit.h" #include "Util.h" +#include /** * @brief Loads saved pet spell cooldowns from the database. diff --git a/src/game/Object/Player.cpp b/src/game/Object/Player.cpp index eb852b244e..b71c0efafb 100644 --- a/src/game/Object/Player.cpp +++ b/src/game/Object/Player.cpp @@ -78,6 +78,11 @@ #endif #include +#include +#include +#include +#include +#include #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -2263,8 +2268,7 @@ void Player::SetGameMaster(bool on) if (Pet* pet = GetPet()) { - if (m_ExtraFlags |= PLAYER_EXTRA_GM_ON) - pet->setFaction(35); + pet->setFaction(35); pet->GetHostileRefManager().setOnlineOfflineState(false); } } diff --git a/src/game/Object/Player.h b/src/game/Object/Player.h index b2940792c2..8d54c0da7c 100644 --- a/src/game/Object/Player.h +++ b/src/game/Object/Player.h @@ -92,6 +92,16 @@ class Item; struct AreaTrigger; #include "CinematicFlyover.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #ifdef ENABLE_PLAYERBOTS class PlayerbotAI; @@ -379,7 +389,7 @@ typedef std::list PlayerCreateInfoActions; struct PlayerInfo { // existence checked by displayId != 0 // existence checked by displayId != 0 - PlayerInfo() : displayId_m(0), displayId_f(0), levelInfo(NULL), areaId(0), mapId(0), orientation(0.0f), positionX(0.0f), positionY(0.0f), positionZ(0.0f) {} + PlayerInfo() : mapId(0), areaId(0), positionX(0.0f), positionY(0.0f), positionZ(0.0f), orientation(0.0f), displayId_m(0), displayId_f(0), levelInfo(NULL) {} uint32 mapId; // Map ID uint32 areaId; // Area ID @@ -1446,7 +1456,7 @@ class Player : public Unit void RemoveMiniPet(); // Get the player's mini pet - Pet* GetMiniPet() const; + Pet* GetMiniPet() const override; // Set the player's mini pet (used only in Pet::Unsummon/Spell::DoSummon) void _SetMiniPet(Pet* pet) @@ -3628,10 +3638,10 @@ class Player : public Unit bool isMovingOrTurning() const { return m_movementInfo.HasMovementFlag(movementOrTurningFlagsMask); } // Check if the player can swim - bool CanSwim() const { return true; } + bool CanSwim() const override { return true; } // Check if the player can fly - bool CanFly() const { return m_movementInfo.HasMovementFlag(MOVEFLAG_CAN_FLY); } + bool CanFly() const override { return m_movementInfo.HasMovementFlag(MOVEFLAG_CAN_FLY); } // Check if the player is flying bool IsFlying() const { return m_movementInfo.HasMovementFlag(MOVEFLAG_FLYING); } diff --git a/src/game/Object/PlayerChannel.cpp b/src/game/Object/PlayerChannel.cpp index 164da22f10..567af92220 100644 --- a/src/game/Object/PlayerChannel.cpp +++ b/src/game/Object/PlayerChannel.cpp @@ -74,6 +74,8 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerDeath.cpp b/src/game/Object/PlayerDeath.cpp index 8eac43e687..7a2b779156 100644 --- a/src/game/Object/PlayerDeath.cpp +++ b/src/game/Object/PlayerDeath.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ // corpse reclaim times diff --git a/src/game/Object/PlayerDuel.cpp b/src/game/Object/PlayerDuel.cpp index e5e29c493a..816934098a 100644 --- a/src/game/Object/PlayerDuel.cpp +++ b/src/game/Object/PlayerDuel.cpp @@ -74,6 +74,8 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ // If players are too far way of duel flag... then player loose the duel diff --git a/src/game/Object/PlayerGossip.cpp b/src/game/Object/PlayerGossip.cpp index 6db7ea0b03..238ed2d260 100644 --- a/src/game/Object/PlayerGossip.cpp +++ b/src/game/Object/PlayerGossip.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerGroup.cpp b/src/game/Object/PlayerGroup.cpp index eca16531e8..eee85c56fb 100644 --- a/src/game/Object/PlayerGroup.cpp +++ b/src/game/Object/PlayerGroup.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerItemStorage.cpp b/src/game/Object/PlayerItemStorage.cpp index c2a8879368..8eb5994ad5 100644 --- a/src/game/Object/PlayerItemStorage.cpp +++ b/src/game/Object/PlayerItemStorage.cpp @@ -34,6 +34,7 @@ #include "DBCStores.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ // Return stored item (if stored to stack, it can diff. from pItem). And pItem ca be deleted in this case. diff --git a/src/game/Object/PlayerItemValidation.cpp b/src/game/Object/PlayerItemValidation.cpp index be873d22b4..034ba3c42e 100644 --- a/src/game/Object/PlayerItemValidation.cpp +++ b/src/game/Object/PlayerItemValidation.cpp @@ -31,6 +31,7 @@ #include "DBCStores.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerLoad.cpp b/src/game/Object/PlayerLoad.cpp index 13bb8a4a40..ec3fb4ef65 100644 --- a/src/game/Object/PlayerLoad.cpp +++ b/src/game/Object/PlayerLoad.cpp @@ -76,6 +76,12 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ // corpse reclaim times @@ -2016,8 +2022,6 @@ void Player::SendRaidInfo() size_t p_counter = data.wpos(); data << uint32(counter); // placeholder - time_t now = time(NULL); - for (int i = 0; i < MAX_DIFFICULTY; ++i) { for (BoundInstancesMap::const_iterator itr = m_boundInstances[i].begin(); itr != m_boundInstances[i].end(); ++itr) diff --git a/src/game/Object/PlayerLogger.cpp b/src/game/Object/PlayerLogger.cpp index 2cdd8bedff..465db260d1 100644 --- a/src/game/Object/PlayerLogger.cpp +++ b/src/game/Object/PlayerLogger.cpp @@ -29,13 +29,14 @@ #include "Database/DatabaseEnv.h" #include "World.h" #include "Log.h" +#include /** * @brief Creates a player logger for a specific player. * * @param guid The GUID of the player whose events are tracked. */ -PlayerLogger::PlayerLogger(ObjectGuid const & guid) : logActiveMask(0), playerGuid(guid.GetCounter()) +PlayerLogger::PlayerLogger(ObjectGuid const & guid) : playerGuid(guid.GetCounter()), logActiveMask(0) { for (uint8 i = 0; i < MAX_PLAYER_LOG_ENTITIES; ++i) { diff --git a/src/game/Object/PlayerLoot.cpp b/src/game/Object/PlayerLoot.cpp index e1c2919060..963fc4884c 100644 --- a/src/game/Object/PlayerLoot.cpp +++ b/src/game/Object/PlayerLoot.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /* If in a battleground a player dies, and an enemy removes the insignia, the player's bones is lootable diff --git a/src/game/Object/PlayerMail.cpp b/src/game/Object/PlayerMail.cpp index ce7b1a7a49..6588c90bac 100644 --- a/src/game/Object/PlayerMail.cpp +++ b/src/game/Object/PlayerMail.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerMovement.cpp b/src/game/Object/PlayerMovement.cpp index 40a6bbc0e7..f34ce46a96 100644 --- a/src/game/Object/PlayerMovement.cpp +++ b/src/game/Object/PlayerMovement.cpp @@ -107,7 +107,7 @@ void Player::SetWaterWalk(bool enable) * * @param enable Unused levitation state flag. */ -void Player::SetLevitate(bool enable) +void Player::SetLevitate(bool /*enable*/) { // TODO: check if there is something similar for 2.4.3. // WorldPacket data; diff --git a/src/game/Object/PlayerPvP.cpp b/src/game/Object/PlayerPvP.cpp index 7e47a5da53..17e82bd22b 100644 --- a/src/game/Object/PlayerPvP.cpp +++ b/src/game/Object/PlayerPvP.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerQuest.cpp b/src/game/Object/PlayerQuest.cpp index 6c0bf35b4d..3d17072d14 100644 --- a/src/game/Object/PlayerQuest.cpp +++ b/src/game/Object/PlayerQuest.cpp @@ -75,6 +75,8 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerRest.cpp b/src/game/Object/PlayerRest.cpp index bfb3508ea1..38ba36879e 100644 --- a/src/game/Object/PlayerRest.cpp +++ b/src/game/Object/PlayerRest.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerSave.cpp b/src/game/Object/PlayerSave.cpp index 963d2006e7..e073b3ca47 100644 --- a/src/game/Object/PlayerSave.cpp +++ b/src/game/Object/PlayerSave.cpp @@ -75,6 +75,13 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ #define PLAYER_SKILL_INDEX(x) (PLAYER_SKILL_INFO_1_1 + ((x)*3)) @@ -840,7 +847,7 @@ void Player::_SaveSpells() SqlStatement stmtDel = CharacterDatabase.CreateStatement(delSpells, "DELETE FROM `character_spell` WHERE `guid` = ? and `spell` = ?"); SqlStatement stmtIns = CharacterDatabase.CreateStatement(insSpells, "INSERT INTO `character_spell` (`guid`,`spell`,`active`,`disabled`) VALUES (?, ?, ?, ?)"); - for (PlayerSpellMap::iterator itr = m_spells.begin(), next = m_spells.begin(); itr != m_spells.end();) + for (PlayerSpellMap::iterator itr = m_spells.begin(); itr != m_spells.end();) { PlayerSpell& playerSpell = itr->second; diff --git a/src/game/Object/PlayerSpell.cpp b/src/game/Object/PlayerSpell.cpp index 679fd46f99..aad751885f 100644 --- a/src/game/Object/PlayerSpell.cpp +++ b/src/game/Object/PlayerSpell.cpp @@ -75,6 +75,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerStats.cpp b/src/game/Object/PlayerStats.cpp index c1b2e9e890..4a8446f31e 100644 --- a/src/game/Object/PlayerStats.cpp +++ b/src/game/Object/PlayerStats.cpp @@ -105,9 +105,9 @@ */ void Player::HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, float amount, bool apply) { - if (modGroup >= BASEMOD_END || modType >= MOD_END) + if (modGroup >= BASEMOD_END) { - sLog.outError("ERROR in HandleBaseModValue(): nonexistent BaseModGroup of wrong BaseModType!"); + sLog.outError("ERROR in HandleBaseModValue(): nonexistent BaseModGroup!"); return; } @@ -153,9 +153,9 @@ void Player::HandleBaseModValue(BaseModGroup modGroup, BaseModType modType, floa */ float Player::GetBaseModValue(BaseModGroup modGroup, BaseModType modType) const { - if (modGroup >= BASEMOD_END || modType > MOD_END) + if (modGroup >= BASEMOD_END) { - sLog.outError("trial to access nonexistent BaseModGroup or wrong BaseModType!"); + sLog.outError("trial to access nonexistent BaseModGroup!"); return 0.0f; } diff --git a/src/game/Object/PlayerTaxi.cpp b/src/game/Object/PlayerTaxi.cpp index 0ac91e7433..855422b06f 100644 --- a/src/game/Object/PlayerTaxi.cpp +++ b/src/game/Object/PlayerTaxi.cpp @@ -37,6 +37,9 @@ #include "ObjectAccessor.h" #include "DBCStores.h" #include "MapManager.h" +#include +#include +#include /** * @brief Initializes known taxi nodes for a newly created player. diff --git a/src/game/Object/PlayerVisibility.cpp b/src/game/Object/PlayerVisibility.cpp index 0de69f46f4..1b45a1c2ed 100644 --- a/src/game/Object/PlayerVisibility.cpp +++ b/src/game/Object/PlayerVisibility.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/PlayerZone.cpp b/src/game/Object/PlayerZone.cpp index b972633f52..6b066aca63 100644 --- a/src/game/Object/PlayerZone.cpp +++ b/src/game/Object/PlayerZone.cpp @@ -74,6 +74,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) diff --git a/src/game/Object/ReputationMgr.h b/src/game/Object/ReputationMgr.h index 32f8c183e2..b96b7f6479 100644 --- a/src/game/Object/ReputationMgr.h +++ b/src/game/Object/ReputationMgr.h @@ -29,6 +29,7 @@ #include "SharedDefines.h" #include "DBCStructure.h" #include +#include enum FactionFlags { diff --git a/src/game/Object/SocialMgr.cpp b/src/game/Object/SocialMgr.cpp index 48eccec1e0..d0562b2761 100644 --- a/src/game/Object/SocialMgr.cpp +++ b/src/game/Object/SocialMgr.cpp @@ -31,8 +31,8 @@ #include "ObjectMgr.h" #include "World.h" #include "Util.h" +#include -INSTANTIATE_SINGLETON_1(SocialMgr); /** * @brief Creates an empty social list for a player. diff --git a/src/game/Object/SocialMgr.h b/src/game/Object/SocialMgr.h index c8912917a9..f6136b69b3 100644 --- a/src/game/Object/SocialMgr.h +++ b/src/game/Object/SocialMgr.h @@ -27,6 +27,8 @@ #include "Database/DatabaseEnv.h" #include "ObjectGuid.h" +#include +#include class SocialMgr; class PlayerSocial; diff --git a/src/game/Object/SpellCooldownMgr.cpp b/src/game/Object/SpellCooldownMgr.cpp index 9f53d6f89a..0fd5038bab 100644 --- a/src/game/Object/SpellCooldownMgr.cpp +++ b/src/game/Object/SpellCooldownMgr.cpp @@ -33,6 +33,7 @@ #include "Spell.h" #include "DBCStores.h" #include "Database/DatabaseEnv.h" +#include void SpellCooldownMgr::AddSpellAndCategoryCooldowns(SpellEntry const* spellInfo, uint32 itemId, Spell* spell, bool infinityCooldown) { diff --git a/src/game/Object/SpellCooldownMgr.h b/src/game/Object/SpellCooldownMgr.h index cedf14429f..5758c1774a 100644 --- a/src/game/Object/SpellCooldownMgr.h +++ b/src/game/Object/SpellCooldownMgr.h @@ -27,6 +27,7 @@ #include "Common.h" #include +#include class Player; class Spell; diff --git a/src/game/Object/SpellMgr.cpp b/src/game/Object/SpellMgr.cpp index ef2c377b1c..b6e8bcb160 100644 --- a/src/game/Object/SpellMgr.cpp +++ b/src/game/Object/SpellMgr.cpp @@ -32,6 +32,8 @@ #include "Spell.h" #include "Unit.h" #include "World.h" +#include +#include /** * @brief Checks whether a skill line is a primary profession. diff --git a/src/game/Object/SpellMgr.h b/src/game/Object/SpellMgr.h index b9f495669d..74536d4457 100644 --- a/src/game/Object/SpellMgr.h +++ b/src/game/Object/SpellMgr.h @@ -37,6 +37,8 @@ #include "Utilities/UnorderedMapSet.h" #include +#include +#include class Player; class Spell; diff --git a/src/game/Object/SpellMgrData.cpp b/src/game/Object/SpellMgrData.cpp index 4bcb21567c..b84199b359 100644 --- a/src/game/Object/SpellMgrData.cpp +++ b/src/game/Object/SpellMgrData.cpp @@ -36,6 +36,7 @@ #include "Spell.h" #include "Unit.h" #include "World.h" +#include /** * @brief Loads spell target destination coordinates from the database. diff --git a/src/game/Object/SpellMgrProc.cpp b/src/game/Object/SpellMgrProc.cpp index 6b4d9d7745..a1b1653447 100644 --- a/src/game/Object/SpellMgrProc.cpp +++ b/src/game/Object/SpellMgrProc.cpp @@ -36,6 +36,7 @@ #include "Chat.h" #include "Spell.h" #include "World.h" +#include template struct SpellRankHelper diff --git a/src/game/Object/StatSystem.cpp b/src/game/Object/StatSystem.cpp index e9a25a1acf..d424e5a86d 100644 --- a/src/game/Object/StatSystem.cpp +++ b/src/game/Object/StatSystem.cpp @@ -28,6 +28,7 @@ #include "Creature.h" #include "SharedDefines.h" #include "SpellAuras.h" +#include /*####################################### ######## ######## @@ -435,22 +436,18 @@ void Player::UpdateShieldBlockValue() void Player::CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, float& min_damage, float& max_damage) { UnitMods unitMod; - UnitMods attPower; switch (attType) { case BASE_ATTACK: default: unitMod = UNIT_MOD_DAMAGE_MAINHAND; - attPower = UNIT_MOD_ATTACK_POWER; break; case OFF_ATTACK: unitMod = UNIT_MOD_DAMAGE_OFFHAND; - attPower = UNIT_MOD_ATTACK_POWER; break; case RANGED_ATTACK: unitMod = UNIT_MOD_DAMAGE_RANGED; - attPower = UNIT_MOD_ATTACK_POWER_RANGED; break; } diff --git a/src/game/Object/Totem.cpp b/src/game/Object/Totem.cpp index bbc9732205..cc9f69bd6d 100644 --- a/src/game/Object/Totem.cpp +++ b/src/game/Object/Totem.cpp @@ -33,6 +33,7 @@ #include "InstanceData.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/Unit.cpp b/src/game/Object/Unit.cpp index f45d251cee..19f0f71640 100644 --- a/src/game/Object/Unit.cpp +++ b/src/game/Object/Unit.cpp @@ -61,6 +61,10 @@ #endif /* ENABLE_ELUNA */ #include +#include +#include +#include +#include float baseMoveSpeed[MAX_MOVE_TYPE] = { @@ -1751,7 +1755,7 @@ void Unit::CalculateSpellDamage(SpellNonMeleeDamage* damageInfo, int32 damage, S return; } - if (!this || !pVictim) + if (!pVictim) { return; } @@ -1843,7 +1847,7 @@ void Unit::DealSpellDamage(SpellNonMeleeDamage* damageInfo, bool durabilityLoss) Unit* pVictim = damageInfo->target; - if (!this || !pVictim) + if (!pVictim) { return; } @@ -2233,7 +2237,7 @@ void Unit::SetCurrentCastedSpell(Spell* pSpell) */ void Unit::InterruptSpell(CurrentSpellTypes spellType, bool withDelayed) { - MANGOS_ASSERT(spellType < CURRENT_MAX_SPELL); + MANGOS_ASSERT(uint32(spellType) < CURRENT_MAX_SPELL); if (m_currentSpells[spellType] && (withDelayed || m_currentSpells[spellType]->getState() != SPELL_STATE_DELAYED)) { @@ -3336,7 +3340,7 @@ Unit* Unit::_GetTotem(TotemSlot slot) const */ Totem* Unit::GetTotem(TotemSlot slot) const { - if (slot >= MAX_TOTEM_SLOT || !IsInWorld() || !m_TotemSlot[slot]) + if (uint32(slot) >= MAX_TOTEM_SLOT || !IsInWorld() || !m_TotemSlot[slot]) { return NULL; } @@ -4360,7 +4364,7 @@ int32 Unit::CalculateSpellDamage(Unit const* target, SpellEntry const* spellProt return value; } -int32 Unit::CalculateAuraDuration(SpellEntry const* spellProto, uint32 effectMask, int32 duration, Unit const* caster) +int32 Unit::CalculateAuraDuration(SpellEntry const* spellProto, uint32 effectMask, int32 duration, Unit const* /*caster*/) { if (duration <= 0) { diff --git a/src/game/Object/Unit.h b/src/game/Object/Unit.h index 7a7778c308..3627edd184 100644 --- a/src/game/Object/Unit.h +++ b/src/game/Object/Unit.h @@ -75,6 +75,11 @@ #include "Log.h" #include +#include +#include +#include +#include +#include /** * @brief Spell interrupt flags diff --git a/src/game/Object/UnitAura.cpp b/src/game/Object/UnitAura.cpp index 7699ba55c9..3472d6a382 100644 --- a/src/game/Object/UnitAura.cpp +++ b/src/game/Object/UnitAura.cpp @@ -554,6 +554,8 @@ bool Unit::AddSpellAuraHolder(SpellAuraHolder* holder) case TRACK_AURA_TYPE_SINGLE_TARGET: // Register spell holder single target scTargets[aurSpellInfo] = GetObjectGuid(); break; + default: + break; } } } diff --git a/src/game/Object/UnitCombat.cpp b/src/game/Object/UnitCombat.cpp index 4f4e85cacf..c175e0857a 100644 --- a/src/game/Object/UnitCombat.cpp +++ b/src/game/Object/UnitCombat.cpp @@ -66,6 +66,7 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_ELUNA #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ /** @@ -92,16 +93,7 @@ void Unit::AttackerStateUpdate(Unit* pVictim, WeaponAttackType attType, bool ext return; } - uint32 hitInfo; - if (attType == BASE_ATTACK) - { - hitInfo = HITINFO_NORMALSWING2; - } - else if (attType == OFF_ATTACK) - { - hitInfo = HITINFO_LEFTSWING; - } - else + if (attType != BASE_ATTACK && attType != OFF_ATTACK) { return; // ignore ranged case } diff --git a/src/game/Object/UnitDamage.cpp b/src/game/Object/UnitDamage.cpp index 63622e14f5..473494ed64 100644 --- a/src/game/Object/UnitDamage.cpp +++ b/src/game/Object/UnitDamage.cpp @@ -66,6 +66,7 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_ELUNA #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/UnitMeleeDamage.cpp b/src/game/Object/UnitMeleeDamage.cpp index 05485c0ddd..5cd491ae94 100644 --- a/src/game/Object/UnitMeleeDamage.cpp +++ b/src/game/Object/UnitMeleeDamage.cpp @@ -63,6 +63,7 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_ELUNA #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ // TODO for melee need create structure as in @@ -85,7 +86,7 @@ void Unit::CalculateMeleeDamage(Unit* pVictim, CalcDamageInfo* damageInfo, Weapo damageInfo->procEx = PROC_EX_NONE; damageInfo->hitOutCome = MELEE_HIT_EVADE; - if (!this || !pVictim) + if (!pVictim) { return; } @@ -364,7 +365,7 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss) } Unit* pVictim = damageInfo->target; - if (!this || !pVictim) + if (!pVictim) { return; } diff --git a/src/game/Object/WorldObjectPosition.cpp b/src/game/Object/WorldObjectPosition.cpp index 6848efa2c1..570f669b5c 100644 --- a/src/game/Object/WorldObjectPosition.cpp +++ b/src/game/Object/WorldObjectPosition.cpp @@ -77,6 +77,7 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_ELUNA #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/Object/WorldObjectSummon.cpp b/src/game/Object/WorldObjectSummon.cpp index a70ef50267..dd27c0bd57 100644 --- a/src/game/Object/WorldObjectSummon.cpp +++ b/src/game/Object/WorldObjectSummon.cpp @@ -75,6 +75,7 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_ELUNA #include "ElunaEventMgr.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/OutdoorPvP/OutdoorPvP.h b/src/game/OutdoorPvP/OutdoorPvP.h index 26871ec872..72e20e7f81 100644 --- a/src/game/OutdoorPvP/OutdoorPvP.h +++ b/src/game/OutdoorPvP/OutdoorPvP.h @@ -29,6 +29,8 @@ #include "ObjectGuid.h" #include "SharedDefines.h" #include "OutdoorPvPMgr.h" +#include +#include // forward declaration class WorldPacket; diff --git a/src/game/OutdoorPvP/OutdoorPvPEP.cpp b/src/game/OutdoorPvP/OutdoorPvPEP.cpp index 0d97270fac..6ca6637348 100644 --- a/src/game/OutdoorPvP/OutdoorPvPEP.cpp +++ b/src/game/OutdoorPvP/OutdoorPvPEP.cpp @@ -30,6 +30,7 @@ #include "Creature.h" #include "GameObject.h" #include "Player.h" +#include OutdoorPvPEP::OutdoorPvPEP() : OutdoorPvP(), m_towersAlliance(0), diff --git a/src/game/OutdoorPvP/OutdoorPvPEP.h b/src/game/OutdoorPvP/OutdoorPvPEP.h index 8a72d7398c..4fed1a8583 100644 --- a/src/game/OutdoorPvP/OutdoorPvPEP.h +++ b/src/game/OutdoorPvP/OutdoorPvPEP.h @@ -28,6 +28,7 @@ #include "Common.h" #include "OutdoorPvP.h" #include "Language.h" +#include /** * @brief Eastern Plaguelands outdoor PvP constants diff --git a/src/game/OutdoorPvP/OutdoorPvPHP.cpp b/src/game/OutdoorPvP/OutdoorPvPHP.cpp index 0e8f2c0eb3..fd751c5da8 100644 --- a/src/game/OutdoorPvP/OutdoorPvPHP.cpp +++ b/src/game/OutdoorPvP/OutdoorPvPHP.cpp @@ -29,6 +29,7 @@ #include "Creature.h" #include "GameObject.h" #include "Player.h" +#include OutdoorPvPHP::OutdoorPvPHP() : OutdoorPvP(), m_towersAlliance(0), diff --git a/src/game/OutdoorPvP/OutdoorPvPHP.h b/src/game/OutdoorPvP/OutdoorPvPHP.h index 4d14c03c22..6fc3485cd3 100644 --- a/src/game/OutdoorPvP/OutdoorPvPHP.h +++ b/src/game/OutdoorPvP/OutdoorPvPHP.h @@ -28,6 +28,7 @@ #include "Common.h" #include "OutdoorPvP.h" #include "Language.h" +#include enum { diff --git a/src/game/OutdoorPvP/OutdoorPvPMgr.cpp b/src/game/OutdoorPvP/OutdoorPvPMgr.cpp index 6367791e24..c4e38e79c3 100644 --- a/src/game/OutdoorPvP/OutdoorPvPMgr.cpp +++ b/src/game/OutdoorPvP/OutdoorPvPMgr.cpp @@ -34,8 +34,8 @@ #include "OutdoorPvPTF.h" #include "OutdoorPvPZM.h" #include "DisableMgr.h" +#include -INSTANTIATE_SINGLETON_1(OutdoorPvPMgr); OutdoorPvPMgr::OutdoorPvPMgr() { diff --git a/src/game/OutdoorPvP/OutdoorPvPMgr.h b/src/game/OutdoorPvP/OutdoorPvPMgr.h index 05f7e60ac2..a8fbee1ae8 100644 --- a/src/game/OutdoorPvP/OutdoorPvPMgr.h +++ b/src/game/OutdoorPvP/OutdoorPvPMgr.h @@ -27,6 +27,7 @@ #include "Common.h" #include "Timer.h" +#include /** * @brief Timer constants for outdoor PvP manager diff --git a/src/game/OutdoorPvP/OutdoorPvPNA.cpp b/src/game/OutdoorPvP/OutdoorPvPNA.cpp index a60f2c65ee..cb9e44a548 100644 --- a/src/game/OutdoorPvP/OutdoorPvPNA.cpp +++ b/src/game/OutdoorPvP/OutdoorPvPNA.cpp @@ -30,6 +30,7 @@ #include "Creature.h" #include "GameObject.h" #include "Player.h" +#include OutdoorPvPNA::OutdoorPvPNA() : OutdoorPvP(), m_zoneOwner(TEAM_NONE), diff --git a/src/game/OutdoorPvP/OutdoorPvPNA.h b/src/game/OutdoorPvP/OutdoorPvPNA.h index fa2bf0e05b..acc13f8f92 100644 --- a/src/game/OutdoorPvP/OutdoorPvPNA.h +++ b/src/game/OutdoorPvP/OutdoorPvPNA.h @@ -28,6 +28,7 @@ #include "Common.h" #include "OutdoorPvP.h" #include "Language.h" +#include enum { diff --git a/src/game/OutdoorPvP/OutdoorPvPTF.cpp b/src/game/OutdoorPvP/OutdoorPvPTF.cpp index 9a8dec716d..c3e319c16f 100644 --- a/src/game/OutdoorPvP/OutdoorPvPTF.cpp +++ b/src/game/OutdoorPvP/OutdoorPvPTF.cpp @@ -30,6 +30,7 @@ #include "Creature.h" #include "GameObject.h" #include "Player.h" +#include OutdoorPvPTF::OutdoorPvPTF() : OutdoorPvP(), m_zoneWorldState(WORLD_STATE_TF_TOWERS_CONTROLLED), diff --git a/src/game/OutdoorPvP/OutdoorPvPTF.h b/src/game/OutdoorPvP/OutdoorPvPTF.h index 22c48dce18..fd0f3fa09d 100644 --- a/src/game/OutdoorPvP/OutdoorPvPTF.h +++ b/src/game/OutdoorPvP/OutdoorPvPTF.h @@ -28,6 +28,7 @@ #include "Common.h" #include "OutdoorPvP.h" #include "Language.h" +#include enum { diff --git a/src/game/References/MapRefManager.h b/src/game/References/MapRefManager.h index 236e493a05..f28726ab57 100644 --- a/src/game/References/MapRefManager.h +++ b/src/game/References/MapRefManager.h @@ -27,6 +27,8 @@ #include "Utilities/LinkedReference/RefManager.h" +class Map; +class Player; class MapReference; /** diff --git a/src/game/Server/DBCStores.cpp b/src/game/Server/DBCStores.cpp index 1c68fef1e2..fdbc16f905 100644 --- a/src/game/Server/DBCStores.cpp +++ b/src/game/Server/DBCStores.cpp @@ -31,6 +31,16 @@ #include "DBCfmt.h" #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include typedef std::map AreaFlagByAreaID; typedef std::map AreaFlagByMapID; @@ -904,7 +914,7 @@ ChatChannelsEntry const* GetChannelEntryFor(uint32 channel_id) return NULL; } -static ChatChannelsEntry worldCh = { 26, 4, "world" }; +static ChatChannelsEntry worldCh = { 26, 4, { "world" } }; /** * @brief Finds a chat channel entry by display name. diff --git a/src/game/Server/DBCStores.h b/src/game/Server/DBCStores.h index 0f4cdef73d..a8a0cf7ded 100644 --- a/src/game/Server/DBCStores.h +++ b/src/game/Server/DBCStores.h @@ -30,6 +30,7 @@ #include "DBCStructure.h" #include +#include /** * Checks whether the specified client build is supported by the server. diff --git a/src/game/Server/DBCStructure_reference.h b/src/game/Server/DBCStructure_reference.h index 48d7f2c627..4e6aa407a6 100644 --- a/src/game/Server/DBCStructure_reference.h +++ b/src/game/Server/DBCStructure_reference.h @@ -1,6 +1,8 @@ #ifndef DBCSTRUCTURE_REFERENCE_H #define DBCSTRUCTURE_REFERENCE_H +#include "Common.h" // int32 / uint32 / float, the field types of every entry below + // AUTO-GENERATED by CoreAlign/tools/gen_reference.py -- DO NOT EDIT. // Inert reference map of the TBC 2.4.3.8606 client DBC tables the core does NOT model. // This header is never #included; activating a table = move its struct into diff --git a/src/game/Server/Opcodes.h b/src/game/Server/Opcodes.h index b2ea7003cf..df52a72278 100644 --- a/src/game/Server/Opcodes.h +++ b/src/game/Server/Opcodes.h @@ -1176,7 +1176,8 @@ inline const char* LookupOpcodeName(uint16 id) { return "Received unknown opcode, it's more than max!"; } - return opcodeTable[id].name; + // An entry that was never registered has a NULL name; never hand that to a "%s" + return opcodeTable[id].name ? opcodeTable[id].name : "UNKNOWN"; } #endif diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index da67515201..eff0ed6a89 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -45,7 +45,7 @@ * @see Opcodes.cpp for opcode registration */ -#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it +#include "WorldSocket.h" #include "Common.h" #include "Database/DatabaseEnv.h" #include "Log.h" @@ -72,6 +72,14 @@ // Warden #include "WardenWin.h" #include "WardenMac.h" +#include +#include +#include +#include +#include +#include +#include +#include /** * @brief Helper for Map session filtering @@ -144,18 +152,18 @@ bool WorldSessionFilter::Process(WorldPacket* packet) } /// WorldSession constructor -WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale) : +WorldSession::WorldSession(uint32 id, std::shared_ptr sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale) : LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time), - _player(NULL), m_Socket(sock), _security(sec), _accountId(id), _warden(NULL), _build(0), m_expansion(expansion), _logoutTime(0), + _player(NULL), m_Socket(std::move(sock)), _security(sec), _accountId(id), m_expansion(expansion), _warden(NULL), _build(0), _logoutTime(0), m_inQueue(false), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_playerSave(false), m_sessionDbcLocale(sWorld.GetAvailableDbcLocale(locale)), m_sessionDbLocaleIndex(sObjectMgr.GetIndexForLocale(locale)), - m_latency(0), m_clientTimeDelay(0), m_tutorialState(TUTORIALDATA_UNCHANGED), m_npcWatchLastGuid(), + m_latency(0), m_tutorialState(TUTORIALDATA_UNCHANGED), m_clientTimeDelay(0), m_npcWatchLastGuid(), m_lastPingTime(0), m_overSpeedPings(0) { if (sock) { m_Address = sock->GetRemoteAddress(); - sock->AddReference(); + } } @@ -172,8 +180,7 @@ WorldSession::~WorldSession() if (m_Socket) { m_Socket->CloseSocket(); - m_Socket->RemoveReference(); - m_Socket = NULL; + m_Socket.reset(); } // Warden @@ -438,8 +445,7 @@ bool WorldSession::Update(PacketFilter& updater) ///- Cleanup socket pointer if need if (m_Socket && m_Socket->IsClosed()) { - m_Socket->RemoveReference(); - m_Socket = NULL; + m_Socket.reset(); } // Warden @@ -1086,6 +1092,16 @@ void WorldSession::SendTransferAborted(uint32 mapid, uint8 reason, uint8 arg) */ void WorldSession::ExecuteOpcode(OpcodeHandler const& opHandle, WorldPacket* packet) { + // An opcode missing from the table is all-zero, which reads back as STATUS_AUTHED + // (0) with a NULL handler - a status Update() dispatches unconditionally. Drop the + // packet instead of calling through the NULL pointer. + if (!opHandle.handler) + { + sLog.outError("SESSION: opcode 0x%.4X has no registered handler, packet dropped", + packet->GetOpcode()); + return; + } + #ifdef ENABLE_ELUNA if (Eluna* e = sWorld.GetEluna()) { diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index 16d89373ee..128473e9e2 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -37,6 +37,11 @@ #include "Item.h" struct ItemPrototype; +#include +#include +#include +#include + struct AuctionEntry; struct AuctionHouseEntry; struct DeclinedName; @@ -249,7 +254,7 @@ class WorldSession * @param mute_time Mute time * @param locale Locale */ - WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale); + WorldSession(uint32 id, std::shared_ptr sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale); /** * @brief Destructor @@ -953,7 +958,9 @@ class WorldSession void LogUnprocessedTail(WorldPacket* packet); Player* _player; - WorldSocket* m_Socket; + /// Shared with the transport: the socket may outlive its connection, which is + /// what lets a tick in progress finish safely (its sends simply become no-ops). + std::shared_ptr m_Socket; std::string m_Address; AccountTypes _security; @@ -983,7 +990,7 @@ class WorldSession time_t m_lastPingTime; uint32 m_overSpeedPings; - ACE_Based::LockedQueue _recvQueue; + MaNGOS::LockedQueue _recvQueue; }; #endif /// @} diff --git a/src/game/Server/WorldSocket.cpp b/src/game/Server/WorldSocket.cpp index 20ace81065..fa8953624d 100644 --- a/src/game/Server/WorldSocket.cpp +++ b/src/game/Server/WorldSocket.cpp @@ -44,18 +44,6 @@ * @see WorldSession for the player session * @see WorldSocketMgr for the socket manager */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - #include "WorldSocket.h" #include "Common.h" @@ -77,6 +65,15 @@ #include "LuaEngine.h" #endif /* ENABLE_ELUNA */ +#include +#include +#include +#include +#include +#include +#include +#include + #if defined( __GNUC__ ) #pragma pack(1) #else @@ -121,16 +118,12 @@ struct ClientPktHeader * - Output buffer size: 64KB * - Random seed for encryption */ -WorldSocket::WorldSocket(void) : - WorldHandler(), - m_SessionLock(), +WorldSocket::WorldSocket() : m_Session(0), - m_RecvWPct(0), - m_RecvPct(), - m_Header(sizeof(ClientPktHeader)), - m_OutBufferLock(), - m_OutBuffer(0), - m_OutBufferSize(65536), + m_closed(false), + m_headerPending(false), + m_recvOpcode(0), + m_recvSize(0), m_Seed(static_cast(rand32())), m_AuthPending(false), m_AuthBuildNumber(0), @@ -138,531 +131,204 @@ WorldSocket::WorldSocket(void) : m_AuthDigest{}, m_AuthAddonData() { - reference_counting_policy().value(ACE_Event_Handler::Reference_Counting_Policy::ENABLED); } -/** - * @brief WorldSocket destructor - * - * Cleans up the receive packet and any allocated resources. - */ -WorldSocket::~WorldSocket(void) +WorldSocket::~WorldSocket() { - delete m_RecvWPct; - - if (m_OutBuffer) - { - m_OutBuffer->release(); - } - - closing_ = true; - - peer().close(); - - WorldPacket* pct; - while (m_PacketQueue.dequeue_head(pct) == 0) - { - delete pct; - } } /** - * @brief Checks whether the socket is already marked as closing. + * @brief Mark the connection dead and ask the transport to close it. * - * @return true if the socket is closed or closing; otherwise false. + * Idempotent, and safe from any thread: the Closer is disarmed by the transport at + * teardown, so a late call from the world thread is a no-op rather than a use-after-free. */ -bool WorldSocket::IsClosed(void) const -{ - return closing_; -} - -/** - * @brief Begins graceful shutdown of the socket writer side. - */ -void WorldSocket::CloseSocket(void) +void WorldSocket::CloseSocket() { + if (m_closed.exchange(true)) { - ACE_GUARD(LockType, Guard, m_OutBufferLock); - - if (closing_) - { - return; - } - - closing_ = true; - peer().close_writer(); + return; } - // Clear the session pointer under its own lock (not the output-buffer - // lock) so the network thread can never route a packet to a session that - // is being torn down. This is done outside the block above to keep a - // single, consistent lock order (m_OutBufferLock is never held while - // taking m_SessionLock). + // Detach the session first, so the network thread can never route a packet into a + // session that is being torn down. SetSession(NULL); -} -/** - * @brief Gets the cached remote address string for this connection. - * - * @return const std::string& The remote address. - */ -const std::string& WorldSocket::GetRemoteAddress(void) const -{ - return m_Address; + if (m_closer) + { + m_closer(); + } } /** - * @brief Sends a packet immediately or queues it for later flush. + * @brief Serialise a packet into an encrypted-header frame ready for the wire. * - * @param pkt The packet to send. - * @return int Zero on success; otherwise -1. + * Wire format is a 4-byte ServerPktHeader -- big-endian size (payload + 2), little-endian + * opcode -- with the whole header encrypted, followed by the raw payload. */ -int WorldSocket::SendPacket(const WorldPacket& pkt) +std::vector WorldSocket::EncodePacket(const WorldPacket& pct) { - ACE_GUARD_RETURN(LockType, Guard, m_OutBufferLock, -1); + ServerPktHeader header; + header.cmd = pct.GetOpcode(); + header.size = static_cast(pct.size() + 2); - if (closing_) - { - return -1; - } + EndianConvertReverse(header.size); + EndianConvert(header.cmd); - WorldPacket pct = pkt; + std::vector frame; + frame.reserve(sizeof(header) + pct.size()); - if (iSendPacket(pct) == -1) { - WorldPacket* npct; + // EncryptSend mutates the cipher state, and SendPacket is reachable from both + // the world thread and the network thread, so the encrypt-and-append must be + // atomic: two packets encrypted out of order would decrypt to garbage. + std::lock_guard guard(m_CryptSendLock); + + m_Crypt.EncryptSend(reinterpret_cast(&header), sizeof(header)); - ACE_NEW_RETURN(npct, WorldPacket(pct), -1); + const uint8_t* raw = reinterpret_cast(&header); + frame.insert(frame.end(), raw, raw + sizeof(header)); - // NOTE maybe check of the size of the queue can be good ? - // to make it bounded instead of unbounded - if (m_PacketQueue.enqueue_tail(npct) == -1) + if (!pct.empty()) { - delete npct; - sLog.outError("WorldSocket::SendPacket: m_PacketQueue.enqueue_tail failed"); - return -1; + frame.insert(frame.end(), pct.contents(), pct.contents() + pct.size()); } } - Guard.release(); - - if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - { - sLog.outError("SendPacket failed setting WRITE mask, peer = %s", GetRemoteAddress().c_str()); - return -1; - } - - return 0; -} - -/** - * @brief Increments the socket reference count. - * - * @return long The new reference count. - */ -long WorldSocket::AddReference(void) -{ - return static_cast(add_reference()); + return frame; } /** - * @brief Decrements the socket reference count. + * @brief Send a packet. Reentrant, and callable from any thread. * - * @return long The new reference count. - */ -long WorldSocket::RemoveReference(void) -{ - return static_cast(remove_reference()); -} - -/** - * @brief Opens the socket handler and sends the authentication challenge. + * The bytes are handed straight to the transport, which appends them to this + * connection's outbound buffer. That buffer coalesces everything queued between two + * writes into one send, so the per-packet output buffer and overflow queue this class + * used to carry are no longer needed. * - * @param a The ACE open hook parameter. - * @return int Zero on success; otherwise -1. + * @return -1 if the connection is gone. */ -int WorldSocket::open(void* a) +int WorldSocket::SendPacket(const WorldPacket& pct) { - ACE_UNUSED_ARG(a); - - // Prevent double call to this func. - if (m_OutBuffer) + if (m_closed.load() || !m_sender) { return -1; } - // Hook for the manager. - if (sWorldSocketMgr->OnSocketOpen(this) == -1) - { - return -1; - } - - // Allocate the buffer. - ACE_NEW_RETURN(m_OutBuffer, ACE_Message_Block(m_OutBufferSize), -1); - - // Store peer address. - ACE_INET_Addr remote_addr; - - if (peer().get_remote_addr(remote_addr) == -1) + if (sLog.IsPacketLoggingEnabled()) { - sLog.outError("WorldSocket::open: peer ().get_remote_addr errno = %s", ACE_OS::strerror(errno)); - return -1; + sLog.outWorldPacketDump(0, pct.GetOpcode(), pct.GetOpcodeName(), &pct, false); } - m_Address = remote_addr.get_host_addr(); - - // Register with ACE Reactor - if (reactor()->register_handler(this, ACE_Event_Handler::READ_MASK) == -1) - { - sLog.outError("WorldSocket::open: unable to register client handler errno = %s", ACE_OS::strerror(errno)); - return -1; - } + const std::vector frame = EncodePacket(pct); + m_sender(frame.data(), frame.size()); - // reactor takes care of the socket from now on - remove_reference(); + return 0; +} - // Send startup packet. +/// Greet the client with SMSG_AUTH_CHALLENGE, carrying our seed (network thread). +std::vector WorldSocket::onConnect() +{ WorldPacket packet(SMSG_AUTH_CHALLENGE, 4); packet << m_Seed; - return SendPacket(packet); + return EncodePacket(packet); } -/** - * @brief Closes the socket and releases its final ACE reference. - * - * @return int Always returns zero. - */ -int WorldSocket::close(u_long) +/// The transport is tearing the connection down (network thread). +void WorldSocket::onClose() { - shutdown(); + m_closed.store(true); - closing_ = true; - - remove_reference(); - - return 0; + // Drop the session link so a world tick still holding this socket stops routing + // through it. The WorldSession itself outlives us; it notices via IsClosed(). + SetSession(NULL); } /** - * @brief Processes readable socket input. + * @brief Reassemble the TCP stream into packets (network thread). * - * @return int Zero on success; otherwise -1. + * A recv may deliver half a header, several whole packets, or anything between, so bytes + * accumulate in m_recvBuf until a complete packet can be cut out. A header is decrypted + * exactly once -- m_headerPending remembers that we already did it -- because decryption + * advances the cipher and doing it twice would corrupt every packet after it. */ -int WorldSocket::handle_input(ACE_HANDLE) +std::vector WorldSocket::onData(const uint8_t* data, size_t len) { - if (closing_) + if (m_closed.load()) { - return -1; + return {}; } - switch (handle_input_missing_data()) + m_recvBuf.insert(m_recvBuf.end(), data, data + len); + + size_t pos = 0; + for (;;) { - case -1 : + if (!m_headerPending) { - if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) + if (m_recvBuf.size() - pos < sizeof(ClientPktHeader)) { - return 0; + break; // not even a full header yet } - } - case 0: - { - errno = ECONNRESET; - return -1; - } - default: - return 0; - } - ACE_NOTREACHED(return -1); -} + ClientPktHeader header; + memcpy(&header, m_recvBuf.data() + pos, sizeof(header)); + pos += sizeof(header); -/** - * @brief Flushes pending outbound socket data. - * - * @return int Zero on success; otherwise -1. - */ -int WorldSocket::handle_output(ACE_HANDLE) -{ - ACE_GUARD_RETURN(LockType, Guard, m_OutBufferLock, -1); - - if (closing_) - { - return -1; - } + m_Crypt.DecryptRecv(reinterpret_cast(&header), sizeof(header)); - const size_t send_len = m_OutBuffer->length(); + EndianConvertReverse(header.size); + EndianConvert(header.cmd); - if (send_len == 0) - { - Guard.release(); - if (reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - { - return -1; - } - return 0; - } - -#ifdef MSG_NOSIGNAL - ssize_t n = peer().send(m_OutBuffer->rd_ptr(), send_len, MSG_NOSIGNAL); -#else - ssize_t n = peer().send(m_OutBuffer->rd_ptr(), send_len); -#endif // MSG_NOSIGNAL - - if (n == 0) - { - return -1; - } - - else if (n == -1) - { - if (errno == EWOULDBLOCK || errno == EAGAIN) - { - Guard.release(); - if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) + if ((header.size < 4) || (header.size > 10240) || (header.cmd > 10240)) { - return -1; + sLog.outError("WorldSocket::onData: client sent malformed packet size = %d , cmd = %d", + header.size, header.cmd); + CloseSocket(); + return {}; } - return 0; - } - return -1; - } - else if (n < (ssize_t)send_len) // now n > 0 - { - m_OutBuffer->rd_ptr(static_cast(n)); - - // move the data to the base of the buffer - m_OutBuffer->crunch(); - Guard.release(); - if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - { - return -1; + m_recvOpcode = static_cast(header.cmd); + m_recvSize = header.size - 4u; // the opcode's own 4 bytes are counted in + m_headerPending = true; } - return 0; - } - else // now n == send_len - { - m_OutBuffer->reset(); - if (!iFlushPacketQueue()) //no more packets in queue + if (m_recvBuf.size() - pos < m_recvSize) { - Guard.release(); - if (reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - { - return -1; - } - return 0; + break; // header is in hand, payload still incomplete } - else - { - Guard.release(); - if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - { - return -1; - } - return 0; - } - } - ACE_NOTREACHED(return 0); -} -/** - * @brief Handles socket closure and unregisters the handler from the reactor. - * - * @param h The closing handle. - * @return int Always returns zero. - */ -int WorldSocket::handle_close(ACE_HANDLE h, ACE_Reactor_Mask) -{ - { - ACE_GUARD_RETURN(LockType, Guard, m_OutBufferLock, -1); - - closing_ = true; - - if (h == ACE_INVALID_HANDLE) + WorldPacket* pct = new WorldPacket(OpcodesList(m_recvOpcode), m_recvSize); + if (m_recvSize > 0) { - peer().close_writer(); + pct->resize(m_recvSize); + memcpy(const_cast(pct->contents()), m_recvBuf.data() + pos, m_recvSize); + pos += m_recvSize; } - } - - // Clear the session pointer under m_SessionLock so a concurrent packet - // route on the network thread sees a consistent value. - SetSession(NULL); - - reactor()->remove_handler(this, ACE_Event_Handler::DONT_CALL | ACE_Event_Handler::ALL_EVENTS_MASK); - return 0; -} - - -/** - * @brief Parses and validates an incoming packet header. - * - * @return int Zero on success; otherwise -1. - */ -int WorldSocket::handle_input_header(void) -{ - MANGOS_ASSERT(m_RecvWPct == NULL); + m_headerPending = false; - MANGOS_ASSERT(m_Header.length() == sizeof(ClientPktHeader)); - - m_Crypt.DecryptRecv((uint8*) m_Header.rd_ptr(), sizeof(ClientPktHeader)); - - ClientPktHeader& header = *((ClientPktHeader*) m_Header.rd_ptr()); - - EndianConvertReverse(header.size); - EndianConvert(header.cmd); - - if ((header.size < 4) || (header.size > 10240) || (header.cmd > 10240)) - { - sLog.outError("WorldSocket::handle_input_header: client sent malformed packet size = %d , cmd = %d", - header.size, header.cmd); - - errno = EINVAL; - return -1; - } - - header.size -= 4; - - ACE_NEW_RETURN(m_RecvWPct, WorldPacket(OpcodesList(header.cmd), header.size), -1); - - if (header.size > 0) - { - m_RecvWPct->resize(header.size); - m_RecvPct.base((char*) m_RecvWPct->contents(), m_RecvWPct->size()); - } - else - { - MANGOS_ASSERT(m_RecvPct.space() == 0); - } - - return 0; -} - -/** - * @brief Finalizes processing of a fully received packet payload. - * - * @return int Zero on success; otherwise -1. - */ -int WorldSocket::handle_input_payload(void) -{ - // set errno properly here on error !!! - // now have a header and payload - - MANGOS_ASSERT(m_RecvPct.space() == 0); - MANGOS_ASSERT(m_Header.space() == 0); - MANGOS_ASSERT(m_RecvWPct != NULL); - - const int ret = ProcessIncoming(m_RecvWPct); - - m_RecvPct.base(NULL, 0); - m_RecvPct.reset(); - m_RecvWPct = NULL; - - m_Header.reset(); - - if (ret == -1) - { - errno = EINVAL; - } - - return ret; -} - -/** - * @brief Receives and assembles any missing header or payload data from the socket. - * - * @return int A receive state code, or -1 on error. - */ -int WorldSocket::handle_input_missing_data(void) -{ - char buf [4096]; - - ACE_Data_Block db(sizeof(buf), - ACE_Message_Block::MB_DATA, - buf, - 0, - 0, - ACE_Message_Block::DONT_DELETE, - 0); - - ACE_Message_Block message_block(&db, - ACE_Message_Block::DONT_DELETE, - 0); - - const size_t recv_size = message_block.space(); - - const ssize_t n = peer().recv(message_block.wr_ptr(), - recv_size); - - if (n <= 0) - { - return (int)n; - } - - message_block.wr_ptr(n); - - while (message_block.length() > 0) - { - if (m_Header.space() > 0) + if (ProcessIncoming(pct) == -1) { - // need to receive the header - const size_t to_header = (message_block.length() > m_Header.space() ? m_Header.space() : message_block.length()); - m_Header.copy(message_block.rd_ptr(), to_header); - message_block.rd_ptr(to_header); - - if (m_Header.space() > 0) - { - // Couldn't receive the whole header this time. - MANGOS_ASSERT(message_block.length() == 0); - errno = EWOULDBLOCK; - return -1; - } - - // We just received nice new header - if (handle_input_header() == -1) - { - MANGOS_ASSERT((errno != EWOULDBLOCK) && (errno != EAGAIN)); - return -1; - } - } - - // Its possible on some error situations that this happens - // for example on closing when epoll receives more chunked data and stuff - // hope this is not hack ,as proper m_RecvWPct is asserted around - if (!m_RecvWPct) - { - sLog.outError("Forcing close on input m_RecvWPct = NULL"); - errno = EINVAL; - return -1; + CloseSocket(); + return {}; } - // We have full read header, now check the data payload - if (m_RecvPct.space() > 0) + if (m_closed.load()) { - // need more data in the payload - const size_t to_data = (message_block.length() > m_RecvPct.space() ? m_RecvPct.space() : message_block.length()); - m_RecvPct.copy(message_block.rd_ptr(), to_data); - message_block.rd_ptr(to_data); - - if (m_RecvPct.space() > 0) - { - // Couldn't receive the whole data this time. - MANGOS_ASSERT(message_block.length() == 0); - errno = EWOULDBLOCK; - return -1; - } + return {}; } + } - // just received fresh new payload - if (handle_input_payload() == -1) - { - MANGOS_ASSERT((errno != EWOULDBLOCK) && (errno != EAGAIN)); - return -1; - } + // Drop what we consumed; whatever is left is the start of the next packet. + if (pos > 0) + { + m_recvBuf.erase(m_recvBuf.begin(), m_recvBuf.begin() + pos); } - return size_t(n) == recv_size ? 1 : 2; + // Nothing is ever answered synchronously: packets are queued to the session and + // handled on the world thread, and replies go back out through the Sender. + return {}; } /** @@ -676,9 +342,9 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct) MANGOS_ASSERT(new_pct); // manage memory ;) - ACE_Auto_Ptr aptr(new_pct); + std::unique_ptr aptr(new_pct); - const ACE_UINT16 opcode = new_pct->GetOpcode(); + const uint16 opcode = new_pct->GetOpcode(); if (opcode >= NUM_MSG_TYPES) { @@ -686,7 +352,7 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct) return -1; } - if (closing_) + if (m_closed.load()) { return -1; } @@ -694,7 +360,7 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct) // Dump received packet (opt-in via PacketLoggingEnabled; off by default). if (sLog.IsPacketLoggingEnabled()) { - sLog.outWorldPacketDump(uint32(get_handle()), new_pct->GetOpcode(), new_pct->GetOpcodeName(), new_pct, true); + sLog.outWorldPacketDump(0, new_pct->GetOpcode(), new_pct->GetOpcodeName(), new_pct, true); } try @@ -726,11 +392,11 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct) // in-place on the network thread. default: { - // Hold the session lock across QueuePacket so the session - // cannot be cleared (and later destroyed) while we hand the - // packet to it. QueuePacket only touches its own queue lock, - // so there is no lock-order conflict with m_OutBufferLock. - ACE_GUARD_RETURN(LockType, Guard, m_SessionLock, -1); + // Hold the session lock across QueuePacket so the session cannot be + // cleared (and later destroyed) while we hand the packet to it. + // QueuePacket only touches its own queue lock, so no lock order is at + // risk here. + std::lock_guard Guard(m_SessionLock); if (m_Session != NULL) { @@ -771,7 +437,7 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct) } } - ACE_NOTREACHED(return 0); + return 0; } /** @@ -828,15 +494,18 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) memcpy(m_AuthDigest, digest, SHA_DIGEST_LENGTH); m_AuthAddonData = recvPacket; - // Keep the socket alive until HandleAuthSessionCallback() runs; released there. - AddReference(); + // Keep the socket alive until HandleAuthSessionCallback() runs. Capturing a + // shared_ptr is what the transport's shared ownership is for: the connection may be + // torn down while the query is still in flight, and the callback must still find a + // live object (it will simply observe closed() and bail). + auto self = std::static_pointer_cast(shared_from_this()); // Account lookup and ban check in a single round-trip: the account_banned // check needs the account id, which this same query produces, so it is // expressed as a correlated subquery instead of a second chained query. - LoginDatabase.AsyncPQuery([this](QueryResult* result) + LoginDatabase.AsyncPQuery([self](QueryResult* result) { - HandleAuthSessionCallback(result); + self->HandleAuthSessionCallback(result); }, "SELECT " "`a`.`id`, " // 0 @@ -874,19 +543,11 @@ int WorldSocket::HandleAuthSession(WorldPacket& recvPacket) */ void WorldSocket::HandleAuthSessionCallback(QueryResult* result) { - // Matches the AddReference() taken in HandleAuthSession() before the - // async query was issued; released on every exit path from here. - struct ReferenceGuard - { - WorldSocket* socket; - ~ReferenceGuard() { socket->RemoveReference(); } - } refGuard{this}; - - ACE_Auto_Ptr resultGuard(result); + std::unique_ptr resultGuard(result); m_AuthPending = false; - if (closing_) + if (m_closed.load()) { return; } @@ -1031,7 +692,8 @@ void WorldSocket::HandleAuthSessionCallback(QueryResult* result) SqlStatement stmt = LoginDatabase.CreateStatement(updAccount, "UPDATE `account` SET `last_ip` = ? WHERE `username` = ?"); stmt.PExecute(address.c_str(), m_AuthAccountName.c_str()); - WorldSession* session = new WorldSession(id, this, AccountTypes(security), expansion, mutetime, locale); + WorldSession* session = new WorldSession(id, std::static_pointer_cast(shared_from_this()), + AccountTypes(security), expansion, mutetime, locale); // Publish the session under the lock so the network thread routes incoming // packets to it consistently. @@ -1057,83 +719,6 @@ void WorldSocket::HandleAuthSessionCallback(QueryResult* result) } } -/** - * @brief Serializes a packet into the outbound socket buffer. - * - * @param pct The packet to serialize. - * @return int Zero on success; otherwise -1. - */ -int WorldSocket::iSendPacket(const WorldPacket& pct) -{ - if (m_OutBuffer->space() < pct.size() + sizeof(ServerPktHeader)) - { - errno = ENOBUFS; - return -1; - } - - if (sLog.IsPacketLoggingEnabled()) // server packet logging (opt-in via PacketLoggingEnabled) - { - sLog.outWorldPacketDump(uint32(get_handle()), pct.GetOpcode(), pct.GetOpcodeName(), &pct, false); - } - - ServerPktHeader header; - - header.cmd = pct.GetOpcode(); - - header.size = (uint16) pct.size() + 2; - - EndianConvertReverse(header.size); - EndianConvert(header.cmd); - - m_Crypt.EncryptSend((uint8*) & header, sizeof(header)); - - if (m_OutBuffer->copy((char*) & header, sizeof(header)) == -1) - { - ACE_ASSERT(false); - } - - if (!pct.empty()) - if (m_OutBuffer->copy((char*) pct.contents(), pct.size()) == -1) - { - ACE_ASSERT(false); - } - - return 0; -} - -/** - * @brief Flushes queued packets into the outbound buffer while space remains. - * - * @return true if at least one packet was flushed; otherwise false. - */ -bool WorldSocket::iFlushPacketQueue() -{ - WorldPacket* pct; - bool haveone = false; - - while (m_PacketQueue.dequeue_head(pct) == 0) - { - if (iSendPacket(*pct) == -1) - { - if (m_PacketQueue.enqueue_head(pct) == -1) - { - delete pct; - sLog.outError("WorldSocket::iFlushPacketQueue m_PacketQueue->enqueue_head"); - return false; - } - - break; - } - else - { - haveone = true; - delete pct; - } - } - - return haveone; -} - /** * @brief Returns the current session pointer under m_SessionLock. * @@ -1145,7 +730,7 @@ bool WorldSocket::iFlushPacketQueue() */ WorldSession* WorldSocket::GetSession() { - ACE_GUARD_RETURN(LockType, Guard, m_SessionLock, NULL); + std::lock_guard Guard(m_SessionLock); return m_Session; } @@ -1156,6 +741,6 @@ WorldSession* WorldSocket::GetSession() */ void WorldSocket::SetSession(WorldSession* session) { - ACE_GUARD(LockType, Guard, m_SessionLock); + std::lock_guard Guard(m_SessionLock); m_Session = session; } diff --git a/src/game/Server/WorldSocket.h b/src/game/Server/WorldSocket.h index a3ced75be6..0c862c291b 100644 --- a/src/game/Server/WorldSocket.h +++ b/src/game/Server/WorldSocket.h @@ -2,7 +2,7 @@ * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * - * Copyright (C) 2005-2025 MaNGOS + * Copyright (C) 2005-2026 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,228 +31,165 @@ #ifndef MANGOS_H_WORLDSOCKET #define MANGOS_H_WORLDSOCKET -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#if !defined (ACE_LACKS_PRAGMA_ONCE) -#pragma once -#endif /* ACE_LACKS_PRAGMA_ONCE */ - #include "Common.h" #include "Auth/AuthCrypt.h" #include "Auth/Sha1.h" #include "WorldPacket.h" -class ACE_Message_Block; -class WorldPacket; +#include "net/ISession.hpp" + +#include +#include +#include +#include +#include +#include +#include + class WorldSession; -class WorldSocket; class QueryResult; -typedef ACE_Svc_Handler WorldHandler; -typedef ACE_Acceptor< WorldSocket, ACE_SOCK_ACCEPTOR > WorldAcceptor; - /** - * WorldSocket. - * - * This class is responsible for the communication with - * remote clients. - * Most methods return -1 on failure. - * The class uses reference counting. - * - * For output the class uses one buffer (64K usually) and - * a queue where it stores packet if there is no space left on - * the buffer. The reason this is done, is because the server - * does really a lot of small-size writes to it, and it doesn't - * scale well to allocate memory for every. When something is - * written to the output buffer the socket is not immediately - * activated for output (again for the same reason), there - * is 10ms celling (thats why there is Update() override method). - * This concept is similar to TCP_CORK, but TCP_CORK - * uses 200ms celling. As result overhead generated by - * sending packets from "producer" threads is minimal, - * and doing a lot of writes with small size is tolerated. + * @brief The world protocol spoken over one client connection. * - * The calls to Update () method are managed by WorldSocketMgr. + * A pure protocol object: the shared networking engine (net::Server) owns the socket + * and the byte plumbing and hands the bytes here. Inbound, onData() reassembles the TCP + * stream into WorldPackets and queues them for the world thread. Outbound, SendPacket() + * encrypts a header and pushes the bytes through the Sender, which may be called from + * any thread. * - * For input, the class uses one 1024 bytes buffer on stack - * to which it does recv() calls. And then received data is - * distributed where its needed. 1024 matches pretty well the - * traffic generated by client for now. - * - * The input/output do speculative reads/writes (AKA it tryes - * to read all data available in the kernel buffer or tryes to - * write everything available in userspace buffer), - * which is ok for using with Level and Edge Triggered IO - * notification. + * @note There is no output buffer, packet overflow queue or write cork here any more. + * The transport's net::SendQueue already coalesces everything queued between two writes + * into a single contiguous send — which is exactly what the old 64 KB m_OutBuffer plus + * its 10 ms cork existed to achieve, and it does so without a heap allocation per packet. * + * @note Lifetime: the transport holds this by shared_ptr, and so does the WorldSession + * once one is attached. The socket may therefore outlive its connection — deliberately, + * and safely, because the transport disarms the Sender on teardown, so a world thread + * still ticking a dying session merely sends into a no-op. */ - -class WorldSocket : protected WorldHandler +class WorldSocket : public net::ISession { public: - /// Declare some friends - friend class ACE_Acceptor< WorldSocket, ACE_SOCK_ACCEPTOR >; - friend class WorldSocketMgr; - /// Mutex type used for various synchronizations. - typedef ACE_Thread_Mutex LockType; + WorldSocket(); + ~WorldSocket() override; - /// Queue for storing packets for which there is no space. - typedef ACE_Unbounded_Queue< WorldPacket* > PacketQueueT; + // ── net::ISession ──────────────────────────────────────────────────────── - /// Check if socket is closed. - bool IsClosed(void) const; + /// Peer address, handed over by the transport right after accept. + void setPeerAddress(const std::string& address) override { m_Address = address; } - /// Close the socket. - void CloseSocket(void); + void setSender(net::Sender sender) override { m_sender = std::move(sender); } + void setCloser(net::Closer closer) override { m_closer = std::move(closer); } - /// Get address of connected peer. - const std::string& GetRemoteAddress(void) const; + /// Greets the client with SMSG_AUTH_CHALLENGE, carrying our seed. + std::vector onConnect() override; - /// Send A packet on the socket, this function is reentrant. - /// @param pct packet to send - /// @return -1 of failure - int SendPacket(const WorldPacket& pct); + /// Feed received bytes into the reassembler (network thread). + std::vector onData(const uint8_t* data, size_t len) override; - /// Add reference to this object. - long AddReference(void); + /// The transport is tearing the connection down (network thread). + void onClose() override; - /// Remove reference to this object. - long RemoveReference(void); + bool closed() const override { return m_closed.load(); } - protected: - /// things called by ACE framework. - WorldSocket(void); - virtual ~WorldSocket(void); + // ── Used by WorldSession ───────────────────────────────────────────────── - /// Called on open ,the void* is the acceptor. - int open(void*) override; + /// Check if the socket is closed. + bool IsClosed() const { return m_closed.load(); } - /// Called on failures inside of the acceptor, don't call from your code. - int close(u_long) override; + /// Mark the connection dead and ask the transport to close it. + void CloseSocket(); - /// Called when we can read from the socket. - int handle_input(ACE_HANDLE = ACE_INVALID_HANDLE) override; + /// Address of the connected peer. + const std::string& GetRemoteAddress() const { return m_Address; } - /// Called when the socket can write. - int handle_output(ACE_HANDLE = ACE_INVALID_HANDLE) override; - - /// Called when connection is closed or error happens. - int handle_close(ACE_HANDLE = ACE_INVALID_HANDLE, - ACE_Reactor_Mask = ACE_Event_Handler::ALL_EVENTS_MASK) override; + /// Send a packet on the socket. Reentrant; callable from any thread. + /// @return -1 on failure. + int SendPacket(const WorldPacket& pct); private: - /// Helper functions for processing incoming data. - int handle_input_header(void); - int handle_input_payload(void); - int handle_input_missing_data(void); - /// process one incoming packet. - /// @param new_pct received packet ,note that you need to delete it. + /// Serialise one packet into an encrypted-header frame ready for the wire. + std::vector EncodePacket(const WorldPacket& pct); + + /// Process one fully assembled incoming packet. Takes ownership of @p new_pct. int ProcessIncoming(WorldPacket* new_pct); - /// Called by ProcessIncoming() on CMSG_AUTH_SESSION. Validates what it - /// can synchronously, then issues an async account lookup so the - /// network thread never blocks on a DB round-trip. + /// Called by ProcessIncoming() on CMSG_AUTH_SESSION. Validates what it can + /// synchronously, then issues an async account lookup so the network thread + /// never blocks on a DB round-trip. int HandleAuthSession(WorldPacket& recvPacket); - /// Async callback for the account lookup started by HandleAuthSession(). - /// Runs on the world thread (via Database::ProcessResultQueue), not the - /// network thread. Takes ownership of (and must delete) \p result. + /// Async callback for the account lookup started by HandleAuthSession(). Runs on + /// the world thread (via Database::ProcessResultQueue), not the network thread. + /// Takes ownership of (and must delete) @p result. void HandleAuthSessionCallback(QueryResult* result); - /// Try to write WorldPacket to m_OutBuffer ,return -1 if no space - /// Need to be called with m_OutBufferLock lock held - int iSendPacket(const WorldPacket& pct); - - /// Flush m_PacketQueue if there are packets in it - /// Need to be called with m_OutBufferLock lock held - /// @return true if it wrote to the buffer ( AKA you need - /// to mark the socket for output ). - bool iFlushPacketQueue(); - - /// Return the current session pointer, read under m_SessionLock. - /// The result is either a live session or NULL, never a dangling - /// pointer, because the session is always cleared under the same lock - /// before it is destroyed. + /// Current session, read under m_SessionLock. Either live or NULL, never + /// dangling: it is cleared under the same lock before the session is destroyed. WorldSession* GetSession(); - - /// Store the session pointer under m_SessionLock. void SetSession(WorldSession* session); private: + /// Address of the remote peer std::string m_Address; - /// Class used for managing encryption of the headers + /// Manages encryption of the packet headers AuthCrypt m_Crypt; - /// Mutex protecting m_Session. It is set from the network thread on - /// authentication and cleared on close, while the network thread also - /// reads it to route incoming packets, so every access goes through - /// this lock. - LockType m_SessionLock; + /// Serialises EncryptSend, which mutates the cipher state and is reached from + /// both the world thread (SendPacket) and the network thread (onConnect). + std::mutex m_CryptSendLock; - /// Session to which received packets are routed + /// Protects m_Session. Set from the network thread on authentication and cleared + /// on close, while the network thread also reads it to route incoming packets. + std::mutex m_SessionLock; WorldSession* m_Session; - /// here are stored the fragments of the received data - WorldPacket* m_RecvWPct; - - /// This block actually refers to m_RecvWPct contents, - /// which allows easy and safe writing to it. - /// It wont free memory when its deleted. m_RecvWPct takes care of freeing. - ACE_Message_Block m_RecvPct; - - /// Fragment of the received header. - ACE_Message_Block m_Header; - - /// Mutex for protecting output related data. - LockType m_OutBufferLock; - - /// Buffer used for writing output. - ACE_Message_Block* m_OutBuffer; - - /// Size of the m_OutBuffer. - size_t m_OutBufferSize; - - /// Here are stored packets for which there was no space on m_OutBuffer, - /// this allows not-to kick player if its buffer is overflowed. - PacketQueueT m_PacketQueue; + /// Set once the connection is finished with; the transport polls closed(). + std::atomic m_closed; + + /// Outbound channel and teardown request, armed by the transport before + /// onConnect(). Both are lifetime-safe: after teardown they become no-ops. + net::Sender m_sender; + net::Closer m_closer; + + // ── Inbound reassembly (network thread only) ───────────────────────────── + // + // TCP is a stream, so one recv may carry half a header, several whole packets, or + // anything in between. Bytes accumulate in m_recvBuf until a whole packet can be + // cut out of them. m_headerPending records that a header has already been + // decrypted: decryption mutates the cipher, so a header must never be decrypted + // twice while we wait for its payload to arrive. + std::vector m_recvBuf; + bool m_headerPending; + uint16 m_recvOpcode; + uint32 m_recvSize; ///< Payload bytes still expected const uint32 m_Seed; - /// Set once HandleAuthSession() issues the async account lookup, so a - /// second CMSG_AUTH_SESSION arriving before the first one's callback - /// runs is rejected instead of clobbering the fields below. + /// Set once HandleAuthSession() issues the async account lookup, so a second + /// CMSG_AUTH_SESSION arriving before the first one's callback runs is rejected + /// instead of clobbering the fields below. bool m_AuthPending; - /// Fields captured from CMSG_AUTH_SESSION, read back once the async - /// account lookup issued by HandleAuthSession() completes. Only used - /// while a login is in flight for this socket, so it needs no locking: - /// it is written once on the network thread before the query is - /// issued, then read once on the world thread when the callback runs. - uint32 m_AuthBuildNumber; + /// Captured from CMSG_AUTH_SESSION and read back once the async account lookup + /// completes. Written once on the network thread before the query is issued, then + /// read once on the world thread when the callback runs, so it needs no locking. + uint32 m_AuthBuildNumber; std::string m_AuthAccountName; - uint32 m_AuthClientSeed; - uint8 m_AuthDigest[SHA_DIGEST_LENGTH]; + uint32 m_AuthClientSeed; + uint8 m_AuthDigest[SHA_DIGEST_LENGTH]; - /// Remainder of the CMSG_AUTH_SESSION packet (the addon block), copied - /// because the original packet is freed once HandleAuthSession() - /// returns, but the addon list is only consumed once the async - /// account lookup completes. + /// Remainder of CMSG_AUTH_SESSION (the addon block), copied because the original + /// packet is freed once HandleAuthSession() returns but the addon list is only + /// consumed once the account lookup completes. WorldPacket m_AuthAddonData; }; -#endif /* _WORLDSOCKET_H */ +#endif /* MANGOS_H_WORLDSOCKET */ /// @} diff --git a/src/game/Server/WorldSocketMgr.cpp b/src/game/Server/WorldSocketMgr.cpp index c1fde83858..df31a7951a 100644 --- a/src/game/Server/WorldSocketMgr.cpp +++ b/src/game/Server/WorldSocketMgr.cpp @@ -2,7 +2,7 @@ * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * - * Copyright (C) 2005-2025 MaNGOS + * Copyright (C) 2005-2026 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,22 +24,10 @@ /** * @file WorldSocketMgr.cpp - * @brief World server network socket manager - * - * This file implements WorldSocketMgr which manages the network layer - * for the world server. It handles: - * - * - TCP socket creation and acceptance - * - Network thread pool management - * - Socket option configuration (buffer sizes, TCP_NODELAY) - * - Integration with ACE reactor pattern for async I/O - * - * The manager uses ACE (Adaptive Communication Environment) for portable - * networking and the TP_Reactor for thread-per-connection handling. + * @brief Owner of the world server's listening socket. * * @see WorldSocketMgr for the manager class - * @see WorldSocket for individual socket handling - * @see WorldAcceptor for connection acceptance + * @see WorldSocket for the protocol spoken on each connection */ #include "Common.h" @@ -49,190 +37,53 @@ #include "WorldSocketMgr.h" #include "Opcodes.h" -#include -#include -#include -#include -#include -#include - -#include +#include +#include +#include +#include -/** - * @brief Construct WorldSocketMgr - * - * Initializes with default socket options: - * - Output kernel buffer: -1 (use default) - * - Output user buffer: 64KB - * - TCP_NODELAY: enabled - * - * Also initializes the opcode table for protocol handling. - */ WorldSocketMgr::WorldSocketMgr() - : m_SockOutKBuff(-1), m_SockOutUBuff(65536), m_UseNoDelay(true), - reactor_(NULL), acceptor_(NULL) + : m_started(false) { - InitializeOpcodes(); } -/** - * @brief Destroy WorldSocketMgr - * - * Cleans up the reactor and acceptor objects. - */ WorldSocketMgr::~WorldSocketMgr() { - if (reactor_) - { - delete reactor_; - } - if (acceptor_) - { - delete acceptor_; - } + StopNetwork(); } - -/** - * @brief Service thread main function - * @return Always returns 0 - * - * Runs the ACE reactor event loop for handling network events. - * This method runs in each network thread and processes: - * - Socket read/write events - * - New connection acceptances - * - Timer events - */ -int WorldSocketMgr::svc() +int WorldSocketMgr::StartNetwork(uint16_t port, const std::string& bindIp) { - DEBUG_LOG("Starting Network Thread"); - - reactor_->run_reactor_event_loop(); - - DEBUG_LOG("Network Thread Exitting"); - return 0; -} - - - -/** - * @brief Start the network layer - * @param addr Address and port to bind to - * @return 0 on success, -1 on failure - * - * Initializes and starts the network subsystem: - * 1. Reads configuration (threads, buffer sizes, TCP_NODELAY) - * 2. Creates ACE_TP_Reactor for thread-pool handling - * 3. Opens acceptor on specified address - * 4. Spawns network threads - * - * Configuration options: - * - Network.Threads: Number of network threads (default: 1) - * - Network.OutUBuff: Output user buffer size (default: 65536) - * - Network.OutKBuff: Output kernel buffer size (default: -1 = system default) - * - Network.TcpNodelay: Enable TCP_NODELAY (default: true) - */ -int WorldSocketMgr::StartNetwork(ACE_INET_Addr& addr) -{ - int num_threads = sConfig.GetIntDefault("Network.Threads", 1); - if (num_threads <= 0) + if (m_started) { - sLog.outError("Network.Threads is wrong in your config file"); return -1; } - m_SockOutUBuff = sConfig.GetIntDefault("Network.OutUBuff", 65536); - if (m_SockOutUBuff <= 0) + // One WorldSocket per accepted connection; the engine owns it (and shares it with + // the WorldSession once the client authenticates). + net::SessionFactory factory = []() -> std::shared_ptr { - sLog.outError("Network.OutUBuff is wrong in your config file"); - return -1; - } - - // -1 means use default - m_SockOutKBuff = sConfig.GetIntDefault("Network.OutKBuff", -1); - m_UseNoDelay = sConfig.GetBoolDefault("Network.TcpNodelay", true); + return std::make_shared(); + }; - // Create thread-pool reactor for handling multiple connections - ACE_Reactor_Impl* imp = 0; - imp = new ACE_TP_Reactor(); - imp->max_notify_iterations(128); - reactor_ = new ACE_Reactor(imp, 1); - - acceptor_ = new WorldAcceptor; - - if (acceptor_->open(addr, reactor_, ACE_NONBLOCK) == -1) + if (!m_server.start(port, std::move(factory), bindIp)) { - sLog.outError("Failed to open acceptor, check if the port is free"); + sLog.outError("WorldSocketMgr::StartNetwork: failed to listen on %s:%u", + (bindIp.empty() ? "0.0.0.0" : bindIp.c_str()), unsigned(port)); return -1; } - if (activate(THR_NEW_LWP | THR_JOINABLE, num_threads) == -1) - { - return -1; - } - - sLog.outString("Max allowed socket connections: %d", ACE::max_handles()); + m_started = true; return 0; } -/** - * @brief Stop the network layer - * - * Gracefully shuts down the network: - * 1. Closes acceptor (stops accepting new connections) - * 2. Signals reactor to end event loop - * 3. Waits for all network threads to complete - */ void WorldSocketMgr::StopNetwork() { - if (acceptor_) - { - acceptor_->close(); - } - if (reactor_) + if (!m_started) { - reactor_->end_reactor_event_loop(); + return; } - wait(); -} - -/** - * @brief Configure a newly opened socket - * @param sock Socket to configure - * @return 0 on success, -1 on failure - * - * Applies socket options to a new client connection: - * - Sets send buffer size (if configured) - * - Enables TCP_NODELAY to reduce latency (if configured) - * - Sets output buffer size - * - Associates socket with the reactor - */ -int WorldSocketMgr::OnSocketOpen(WorldSocket* sock) -{ - // Set kernel send buffer size if configured - if (m_SockOutKBuff >= 0) - { - if (sock->peer().set_option(SOL_SOCKET, SO_SNDBUF, (void*)&m_SockOutKBuff, sizeof(int)) == -1 && errno != ENOTSUP) - { - sLog.outError("WorldSocketMgr::OnSocketOpen set_option SO_SNDBUF"); - return -1; - } - } - - static const int ndoption = 1; - // Set TCP_NODELAY to disable Nagle's algorithm for lower latency - if (m_UseNoDelay) - { - if (sock->peer().set_option(ACE_IPPROTO_TCP, TCP_NODELAY, (void*)&ndoption, sizeof(int)) == -1) - { - sLog.outError("WorldSocketMgr::OnSocketOpen: peer().set_option TCP_NODELAY errno = %s", ACE_OS::strerror(errno)); - return -1; - } - } - - sock->m_OutBufferSize = static_cast(m_SockOutUBuff); - sock->reactor(reactor_); - - return 0; + m_server.stop(); + m_started = false; } diff --git a/src/game/Server/WorldSocketMgr.h b/src/game/Server/WorldSocketMgr.h index 14a8efeea4..0e10bc1ceb 100644 --- a/src/game/Server/WorldSocketMgr.h +++ b/src/game/Server/WorldSocketMgr.h @@ -2,7 +2,7 @@ * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * - * Copyright (C) 2005-2025 MaNGOS + * Copyright (C) 2005-2026 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,73 +31,54 @@ #ifndef MANGOS_H_WORLDSOCKETMGR #define MANGOS_H_WORLDSOCKETMGR -#include -#include -#include -#include -#include -#include +#include "Common.h" +#include "Policies/Singleton.h" -class WorldSocket; +#include "net/Server.hpp" + +#include +#include /** - * @brief World socket manager class + * @brief Owns the world server's listening socket. + * + * A thin lid on the shared networking engine: it starts a net::Server on the world port + * and gives it a factory that mints one WorldSocket per accepted connection. The engine + * owns the threads, the sockets and the byte plumbing; everything protocol-shaped lives + * in WorldSocket. * - * This is a pool of threads designed to be used by an ACE_TP_Reactor. - * Manages all sockets connected to peers. + * The ACE reactor, its thread pool, the acceptor and the per-socket buffer tuning that + * used to live here are gone — the engine does that job for every protocol now, and + * realmd's AuthServer is the same shape. */ -class WorldSocketMgr : public ACE_Task_Base +class WorldSocketMgr : public MaNGOS::Singleton { - friend class ACE_Singleton; - friend class WorldSocket; + friend class MaNGOS::Singleton; public: - /** - * @brief Start network - * @param addr Internet address - * @return Result code - */ - int StartNetwork(ACE_INET_Addr& addr); /** - * @brief Stop network + * @brief Bind and start accepting world connections. + * + * @param port TCP port to listen on. + * @param bindIp Interface to bind to; empty (or "0.0.0.0") listens on all. + * @return 0 on success, -1 on failure. */ + int StartNetwork(uint16_t port, const std::string& bindIp); + + /// Stop accepting, and tear down every live connection. void StopNetwork(); private: - /** - * @brief Handle socket open - * @param sock World socket - * @return Result code - */ - int OnSocketOpen(WorldSocket* sock); - /** - * @brief Service method (ACE thread pool) - * @return Result code - */ - virtual int svc(); - - /** - * @brief Constructor - */ WorldSocketMgr(); + ~WorldSocketMgr(); - /** - * @brief Virtual destructor - */ - virtual ~WorldSocketMgr(); - - private: - int m_SockOutKBuff; ///< Socket output kernel buffer size - int m_SockOutUBuff; ///< Socket output user buffer size - bool m_UseNoDelay; ///< Use TCP_NODELAY - - ACE_Reactor* reactor_; ///< ACE reactor - WorldAcceptor* acceptor_; ///< World acceptor + net::Server m_server; + bool m_started; }; -#define sWorldSocketMgr ACE_Singleton::instance() +#define sWorldSocketMgr MaNGOS::Singleton::Instance() #endif /// @} diff --git a/src/game/Time/GameTime.cpp b/src/game/Time/GameTime.cpp index 36bd27aeed..2aafe458d9 100644 --- a/src/game/Time/GameTime.cpp +++ b/src/game/Time/GameTime.cpp @@ -45,6 +45,8 @@ #include "GameTime.h" #include "Timer.h" +#include +#include namespace GameTime { diff --git a/src/game/Time/GameTime.h b/src/game/Time/GameTime.h index 86fa08a660..b7c5571530 100644 --- a/src/game/Time/GameTime.h +++ b/src/game/Time/GameTime.h @@ -28,6 +28,7 @@ #include "Define.h" #include +#include /** * @brief GameTime namespace provides time-related functions for the game server diff --git a/src/game/Time/UpdateTime.cpp b/src/game/Time/UpdateTime.cpp index 28623e3fd4..4d9f0bf37e 100644 --- a/src/game/Time/UpdateTime.cpp +++ b/src/game/Time/UpdateTime.cpp @@ -48,6 +48,7 @@ #include "Timer.h" #include "Config.h" #include "Log.h" +#include /** * @var sWorldUpdateTime @@ -63,8 +64,9 @@ WorldUpdateTime sWorldUpdateTime; * Initializes all metrics to zero. The data table is sized by the template * parameter (default 100 entries for historical tracking). */ -UpdateTime::UpdateTime() : _averageUpdateTime(0), _totalUpdateTime(0), _updateTimeTableIndex(0), _maxUpdateTime(0), - _maxUpdateTimeOfLastTable(0), _maxUpdateTimeOfCurrentTable(0), _updateTimeDataTable() { } +UpdateTime::UpdateTime() : _updateTimeDataTable(), _averageUpdateTime(0), _totalUpdateTime(0), + _updateTimeTableIndex(0), _maxUpdateTime(0), _maxUpdateTimeOfLastTable(0), + _maxUpdateTimeOfCurrentTable(0) { } /** * @brief Get simple average update time diff --git a/src/game/Tools/CharacterDatabaseCleaner.cpp b/src/game/Tools/CharacterDatabaseCleaner.cpp index ccf4ef4920..6f450b582b 100644 --- a/src/game/Tools/CharacterDatabaseCleaner.cpp +++ b/src/game/Tools/CharacterDatabaseCleaner.cpp @@ -48,6 +48,7 @@ #include "Database/DatabaseEnv.h" #include "DBCStores.h" #include "ProgressBar.h" +#include /** * @brief Main database cleanup entry point diff --git a/src/game/Tools/CharacterDatabaseCleaner.h b/src/game/Tools/CharacterDatabaseCleaner.h index 9459a83dcb..694786adc9 100644 --- a/src/game/Tools/CharacterDatabaseCleaner.h +++ b/src/game/Tools/CharacterDatabaseCleaner.h @@ -25,6 +25,8 @@ #ifndef CHARACTERDATABASECLEANER_H #define CHARACTERDATABASECLEANER_H +#include "Common.h" // uint32, used by the flags and the function signatures below + namespace CharacterDatabaseCleaner { /** diff --git a/src/game/Tools/PlayerDump.cpp b/src/game/Tools/PlayerDump.cpp index 7642952f5d..165d7cf74a 100644 --- a/src/game/Tools/PlayerDump.cpp +++ b/src/game/Tools/PlayerDump.cpp @@ -55,6 +55,13 @@ #include "UpdateFields.h" #include "ObjectMgr.h" #include "AccountMgr.h" +#include +#include +#include +#include +#include +#include +#include /** * @struct DumpTable diff --git a/src/game/Tools/PlayerDump.h b/src/game/Tools/PlayerDump.h index 68a8e619ff..3e70863552 100644 --- a/src/game/Tools/PlayerDump.h +++ b/src/game/Tools/PlayerDump.h @@ -25,6 +25,8 @@ #ifndef MANGOS_H_PLAYER_DUMP #define MANGOS_H_PLAYER_DUMP +#include "Common.h" // uint32, used throughout the declarations below + #include #include diff --git a/src/game/Warden/Modules/WardenModuleMac.h b/src/game/Warden/Modules/WardenModuleMac.h index 4994995fe4..391fde652a 100644 --- a/src/game/Warden/Modules/WardenModuleMac.h +++ b/src/game/Warden/Modules/WardenModuleMac.h @@ -26,6 +26,8 @@ #ifndef _WARDEN_MODULE_MAC_H #define _WARDEN_MODULE_MAC_H +#include "Common.h" // uint8, the type of the module blob below + uint8 Module_0DBBF209A27B1E279A9FEC5C168A15F7_Data[9318] = { 0x07, 0x0C, 0x44, 0xCD, 0xC9, 0xFB, 0x99, 0xBC, 0x7C, 0x77, 0xDC, 0xE8, 0x8D, 0x07, 0xBE, 0x55, diff --git a/src/game/Warden/Modules/WardenModuleWin.h b/src/game/Warden/Modules/WardenModuleWin.h index 57e85f5927..3d982313ff 100644 --- a/src/game/Warden/Modules/WardenModuleWin.h +++ b/src/game/Warden/Modules/WardenModuleWin.h @@ -26,6 +26,8 @@ #ifndef _WARDEN_MODULE_WIN_H #define _WARDEN_MODULE_WIN_H +#include "Common.h" // uint8, the type of every field of the module blob below + /* Seed: 4D808D2C77D905C41A6380EC08586AFE (0x05 packet) Hash: 568C054C781A972A6037A2290C22B52571A06F4E (0x04 packet) diff --git a/src/game/Warden/Warden.cpp b/src/game/Warden/Warden.cpp index ec778e8c5b..b7aa41c155 100644 --- a/src/game/Warden/Warden.cpp +++ b/src/game/Warden/Warden.cpp @@ -56,6 +56,9 @@ #include "Warden.h" #include "AccountMgr.h" #include "GameTime.h" +#include +#include +#include /** * @brief Warden constructor diff --git a/src/game/Warden/Warden.h b/src/game/Warden/Warden.h index a07486f58f..a5a7a79a5f 100644 --- a/src/game/Warden/Warden.h +++ b/src/game/Warden/Warden.h @@ -26,12 +26,12 @@ #ifndef _WARDEN_BASE_H #define _WARDEN_BASE_H -#include #include "ARC4.h" #include "BigNumber.h" #include "ByteBuffer.h" #include "WardenCheckMgr.h" #include "Database/DatabaseEnv.h" +#include // the default client version with info in warden_checks; for other version checks, see warden_build_specific #define DEFAULT_CLIENT_BUILD 8606 diff --git a/src/game/Warden/WardenCheckMgr.cpp b/src/game/Warden/WardenCheckMgr.cpp index 14f639796d..8734c052f7 100644 --- a/src/game/Warden/WardenCheckMgr.cpp +++ b/src/game/Warden/WardenCheckMgr.cpp @@ -49,13 +49,19 @@ #include "Database/DatabaseEnv.h" #include "WardenCheckMgr.h" #include "Warden.h" +#include +#include +#include +#include +#include +#include /** * @brief WardenCheckMgr constructor * * Initializes the check manager with empty stores. */ -WardenCheckMgr::WardenCheckMgr() : m_lock(0), CheckStore(), CheckResultStore() { } +WardenCheckMgr::WardenCheckMgr() : CheckStore(), CheckResultStore() { } /** * @brief WardenCheckMgr destructor @@ -213,7 +219,7 @@ void WardenCheckMgr::LoadWardenOverrides() uint32 count = 0; - ACE_WRITE_GUARD(LOCK, g, m_lock) + std::unique_lock g(m_lock); do { @@ -259,7 +265,7 @@ WardenCheck* WardenCheckMgr::GetWardenDataById(uint16 build, uint16 id) { WardenCheck* result = NULL; - ACE_READ_GUARD_RETURN(LOCK, g, m_lock, result) + std::shared_lock g(m_lock); for (CheckMap::iterator it = CheckStore.lower_bound(build); it != CheckStore.upper_bound(build); ++it) { if (it->second->CheckId == id) @@ -282,7 +288,7 @@ WardenCheckResult* WardenCheckMgr::GetWardenResultById(uint16 build, uint16 id) { WardenCheckResult* result = NULL; - ACE_READ_GUARD_RETURN(LOCK, g, m_lock, result) + std::shared_lock g(m_lock); for (CheckResultMap::iterator it = CheckResultStore.lower_bound(build); it != CheckResultStore.upper_bound(build); ++it) { if (it->second->Id == id) @@ -305,7 +311,7 @@ void WardenCheckMgr::GetWardenCheckIds(bool isMemCheck, uint16 build, std::list< { idl.clear(); //just to be sure - ACE_READ_GUARD(LOCK, g, m_lock) + std::shared_lock g(m_lock); for (CheckMap::iterator it = CheckStore.lower_bound(build); it != CheckStore.upper_bound(build); ++it) { if (isMemCheck) diff --git a/src/game/Warden/WardenCheckMgr.h b/src/game/Warden/WardenCheckMgr.h index 9f799b2bd3..c7e2a4d0e3 100644 --- a/src/game/Warden/WardenCheckMgr.h +++ b/src/game/Warden/WardenCheckMgr.h @@ -28,6 +28,8 @@ #include #include "BigNumber.h" +#include +#include /** * @brief Warden actions enumeration @@ -125,7 +127,7 @@ class WardenCheckMgr void LoadWardenOverrides(); private: - typedef ACE_RW_Thread_Mutex LOCK; + typedef std::shared_mutex LOCK; typedef std::multimap CheckMap; typedef std::multimap CheckResultMap; diff --git a/src/game/Warden/WardenMac.cpp b/src/game/Warden/WardenMac.cpp index 2cedf88546..0e86f1f82a 100644 --- a/src/game/Warden/WardenMac.cpp +++ b/src/game/Warden/WardenMac.cpp @@ -53,6 +53,8 @@ #include "WardenMac.h" #include "WardenModuleMac.h" #include "GameTime.h" +#include +#include /** * @brief WardenMac constructor diff --git a/src/game/Warden/WardenWin.cpp b/src/game/Warden/WardenWin.cpp index f0d922195a..7979721da9 100644 --- a/src/game/Warden/WardenWin.cpp +++ b/src/game/Warden/WardenWin.cpp @@ -57,6 +57,9 @@ #include "WardenModuleWin.h" #include "WardenCheckMgr.h" #include "GameTime.h" +#include +#include +#include /** * @brief WardenWin constructor diff --git a/src/game/Warden/WardenWin.h b/src/game/Warden/WardenWin.h index 2570c08e62..ea93c24e7f 100644 --- a/src/game/Warden/WardenWin.h +++ b/src/game/Warden/WardenWin.h @@ -27,6 +27,7 @@ #define _WARDEN_WIN_H #include "Warden.h" +#include #if defined(__GNUC__) #pragma pack(1) diff --git a/src/game/WorldHandlers/AccountMgr.cpp b/src/game/WorldHandlers/AccountMgr.cpp index f269a8fc79..c36d886559 100644 --- a/src/game/WorldHandlers/AccountMgr.cpp +++ b/src/game/WorldHandlers/AccountMgr.cpp @@ -48,6 +48,7 @@ #include "Policies/Singleton.h" #include "Util.h" #include "Auth/Sha1.h" +#include /** * @var LoginDatabase @@ -58,7 +59,6 @@ */ extern DatabaseType LoginDatabase; -INSTANTIATE_SINGLETON_1(AccountMgr); /** * @brief Construct AccountMgr singleton diff --git a/src/game/WorldHandlers/AccountMgr.h b/src/game/WorldHandlers/AccountMgr.h index b0d8478784..aa27332daa 100644 --- a/src/game/WorldHandlers/AccountMgr.h +++ b/src/game/WorldHandlers/AccountMgr.h @@ -26,6 +26,7 @@ #define MANGOS_H_ACCMGR #include "Common.h" +#include /** * @brief Account operation result enumeration diff --git a/src/game/WorldHandlers/AddonHandler.cpp b/src/game/WorldHandlers/AddonHandler.cpp index 8ab4dba938..b9d7b25c26 100644 --- a/src/game/WorldHandlers/AddonHandler.cpp +++ b/src/game/WorldHandlers/AddonHandler.cpp @@ -49,8 +49,8 @@ #include "Opcodes.h" #include "Log.h" #include "Policies/Singleton.h" +#include -INSTANTIATE_SINGLETON_1(AddonHandler); /** * @brief Construct AddonHandler singleton diff --git a/src/game/WorldHandlers/ArenaTeamHandler.cpp b/src/game/WorldHandlers/ArenaTeamHandler.cpp index b70adb0cbd..368411db59 100644 --- a/src/game/WorldHandlers/ArenaTeamHandler.cpp +++ b/src/game/WorldHandlers/ArenaTeamHandler.cpp @@ -31,6 +31,7 @@ #include "ArenaTeam.h" #include "World.h" #include "SocialMgr.h" +#include void WorldSession::HandleInspectArenaTeamsOpcode(WorldPacket& recv_data) { diff --git a/src/game/WorldHandlers/AuctionHouseHandler.cpp b/src/game/WorldHandlers/AuctionHouseHandler.cpp index 945599b922..bce211b328 100644 --- a/src/game/WorldHandlers/AuctionHouseHandler.cpp +++ b/src/game/WorldHandlers/AuctionHouseHandler.cpp @@ -51,6 +51,11 @@ #include "Chat.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ /** \addtogroup auctionhouse diff --git a/src/game/WorldHandlers/CellImpl.h b/src/game/WorldHandlers/CellImpl.h index c105477fd3..9cebdc003e 100644 --- a/src/game/WorldHandlers/CellImpl.h +++ b/src/game/WorldHandlers/CellImpl.h @@ -29,7 +29,6 @@ #include "Cell.h" #include "Map.h" #include "TransportCrewSearch.h" -#include inline Cell::Cell(CellPair const& p) { diff --git a/src/game/WorldHandlers/Channel.cpp b/src/game/WorldHandlers/Channel.cpp index 28f6c484dd..70e145fd22 100644 --- a/src/game/WorldHandlers/Channel.cpp +++ b/src/game/WorldHandlers/Channel.cpp @@ -46,6 +46,8 @@ #include "World.h" #include "SocialMgr.h" #include "Chat.h" +#include +#include Channel::Channel(const std::string& name, uint32 channel_id) : m_announce(true), m_moderate(false), m_name(name), m_flags(0), m_channelId(channel_id) @@ -684,17 +686,6 @@ void Channel::Say(Player* player, const char* text, uint32 lang) } ObjectGuid guid = player->GetObjectGuid(); - Player* plr = sObjectMgr.GetPlayer(guid); - bool speakInLocalDef = true; // By Default everyone could chat in local defense in tbc onwards - bool speakInWorldDef = false; - if (plr) - { - if (plr->isGameMaster()) - { - speakInWorldDef = true; - } - - } if (!IsOn(guid)) { diff --git a/src/game/WorldHandlers/Channel.h b/src/game/WorldHandlers/Channel.h index 9cca313633..71965849e7 100644 --- a/src/game/WorldHandlers/Channel.h +++ b/src/game/WorldHandlers/Channel.h @@ -31,6 +31,7 @@ #include "Player.h" #include +#include enum ChatNotify { diff --git a/src/game/WorldHandlers/ChannelHandler.cpp b/src/game/WorldHandlers/ChannelHandler.cpp index b88e3d5b21..6eb7073b94 100644 --- a/src/game/WorldHandlers/ChannelHandler.cpp +++ b/src/game/WorldHandlers/ChannelHandler.cpp @@ -43,6 +43,7 @@ #include "ObjectMgr.h" // for normalizePlayerName #include "ChannelMgr.h" +#include /** * @brief Handles a client's request to join a chat channel. diff --git a/src/game/WorldHandlers/ChannelMgr.cpp b/src/game/WorldHandlers/ChannelMgr.cpp index 164d4d122b..78ff3ccf46 100644 --- a/src/game/WorldHandlers/ChannelMgr.cpp +++ b/src/game/WorldHandlers/ChannelMgr.cpp @@ -42,9 +42,8 @@ #include "ChannelMgr.h" #include "Policies/Singleton.h" #include "World.h" +#include -INSTANTIATE_SINGLETON_1(AllianceChannelMgr); -INSTANTIATE_SINGLETON_1(HordeChannelMgr); /** * @brief Get the appropriate channel manager for a team diff --git a/src/game/WorldHandlers/ChannelMgr.h b/src/game/WorldHandlers/ChannelMgr.h index d24e3c7740..73fb68da8f 100644 --- a/src/game/WorldHandlers/ChannelMgr.h +++ b/src/game/WorldHandlers/ChannelMgr.h @@ -29,6 +29,7 @@ #include "Channel.h" #include +#include class ChannelMgr { diff --git a/src/game/WorldHandlers/CharacterHandler.cpp b/src/game/WorldHandlers/CharacterHandler.cpp index 41520ed9cf..55bf2f020f 100644 --- a/src/game/WorldHandlers/CharacterHandler.cpp +++ b/src/game/WorldHandlers/CharacterHandler.cpp @@ -71,6 +71,9 @@ #ifdef ENABLE_PLAYERBOTS #include "playerbot.h" #include "PlayerbotAIConfig.h" +#include +#include +#include #endif // config option SkipCinematics supported values diff --git a/src/game/WorldHandlers/CharacterHandlerCustomize.cpp b/src/game/WorldHandlers/CharacterHandlerCustomize.cpp index caf8487602..e6090e93ad 100644 --- a/src/game/WorldHandlers/CharacterHandlerCustomize.cpp +++ b/src/game/WorldHandlers/CharacterHandlerCustomize.cpp @@ -74,6 +74,7 @@ #endif /* ENABLE_PLAYERBOTS */ #ifdef ENABLE_PLAYERBOTS #include "PlayerbotAIConfig.h" +#include #endif /* ENABLE_PLAYERBOTS */ /** diff --git a/src/game/WorldHandlers/Chat.cpp b/src/game/WorldHandlers/Chat.cpp index c903215680..1b048de4cc 100644 --- a/src/game/WorldHandlers/Chat.cpp +++ b/src/game/WorldHandlers/Chat.cpp @@ -63,6 +63,11 @@ #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ // Supported shift-links (client generated and server side) diff --git a/src/game/WorldHandlers/Chat.h b/src/game/WorldHandlers/Chat.h index 0f6ebca59f..e9910b84f7 100644 --- a/src/game/WorldHandlers/Chat.h +++ b/src/game/WorldHandlers/Chat.h @@ -29,6 +29,10 @@ #include "SharedDefines.h" #include "ObjectGuid.h" #include "Language.h" +#include +#include +#include +#include struct AreaTrigger; struct AreaTriggerEntry; diff --git a/src/game/WorldHandlers/ChatArgExtract.cpp b/src/game/WorldHandlers/ChatArgExtract.cpp index 0d31147c3e..ab5e4bf731 100644 --- a/src/game/WorldHandlers/ChatArgExtract.cpp +++ b/src/game/WorldHandlers/ChatArgExtract.cpp @@ -66,6 +66,12 @@ #include "CommandMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/ChatHandler.cpp b/src/game/WorldHandlers/ChatHandler.cpp index 9c8170ef3d..230e1b7b35 100644 --- a/src/game/WorldHandlers/ChatHandler.cpp +++ b/src/game/WorldHandlers/ChatHandler.cpp @@ -61,6 +61,9 @@ #ifdef ENABLE_PLAYERBOTS #include "playerbot.h" #include "RandomPlayerbotMgr.h" +#include +#include +#include #endif /** diff --git a/src/game/WorldHandlers/ChatHelp.cpp b/src/game/WorldHandlers/ChatHelp.cpp index ec2ffb88a2..2cbacfddc5 100644 --- a/src/game/WorldHandlers/ChatHelp.cpp +++ b/src/game/WorldHandlers/ChatHelp.cpp @@ -64,6 +64,8 @@ #include "AuctionHouseBot/AuctionHouseBot.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/ChatMessage.cpp b/src/game/WorldHandlers/ChatMessage.cpp index 0acf3dc982..fbf80ccab6 100644 --- a/src/game/WorldHandlers/ChatMessage.cpp +++ b/src/game/WorldHandlers/ChatMessage.cpp @@ -64,6 +64,10 @@ #include "CommandMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ /** @@ -109,7 +113,6 @@ void ChatHandler::SendGlobalSysMessage(const char* str, AccountTypes minSec) // need copy to prevent corruption by strtok call in LineFromMessage original string char* buf = mangos_strdup(str); char* pos = buf; - ObjectGuid senderGuid = m_session ? m_session->GetPlayer()->GetObjectGuid() : ObjectGuid(); while (char* line = LineFromMessage(pos)) { @@ -160,8 +163,6 @@ void ChatHandler::PSendSysMessage(int32 entry, ...) */ void ChatHandler::PSendSysMessageMultiline(int32 entry, ...) { - uint32 linecount = 0; - const char* format = GetMangosString(entry); va_list ap; char str[2048]; @@ -185,7 +186,6 @@ void ChatHandler::PSendSysMessageMultiline(int32 entry, ...) { /* Send the player a system message containing the substring from pos to nextpos - pos */ PSendSysMessage("%s", mangosString.substr(pos, nextpos - pos).c_str()); - ++linecount; } pos = nextpos + 2; // +2 because there are two @ as delimiter } diff --git a/src/game/WorldHandlers/ChatMessageValidation.cpp b/src/game/WorldHandlers/ChatMessageValidation.cpp index 2fdbac05b0..4a2bc18022 100644 --- a/src/game/WorldHandlers/ChatMessageValidation.cpp +++ b/src/game/WorldHandlers/ChatMessageValidation.cpp @@ -65,6 +65,10 @@ #include "CommandMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/CommandMgr.cpp b/src/game/WorldHandlers/CommandMgr.cpp index 2000769951..50e7cebf86 100644 --- a/src/game/WorldHandlers/CommandMgr.cpp +++ b/src/game/WorldHandlers/CommandMgr.cpp @@ -42,10 +42,10 @@ #include "CommandMgr.h" #include "ObjectMgr.h" #include "ProgressBar.h" +#include class ChatCommand; // Forward declaration -INSTANTIATE_SINGLETON_1(CommandMgr); /** * @brief Construct CommandMgr singleton diff --git a/src/game/WorldHandlers/CreatureLinkingMgr.cpp b/src/game/WorldHandlers/CreatureLinkingMgr.cpp index b6f6b9c7fc..cbc9890563 100644 --- a/src/game/WorldHandlers/CreatureLinkingMgr.cpp +++ b/src/game/WorldHandlers/CreatureLinkingMgr.cpp @@ -49,8 +49,9 @@ #include "SharedDefines.h" #include "Creature.h" #include "CreatureAI.h" +#include +#include -INSTANTIATE_SINGLETON_1(CreatureLinkingMgr); #define INVALID_MAP_ID 0xFFFFFFFF diff --git a/src/game/WorldHandlers/CreatureLinkingMgr.h b/src/game/WorldHandlers/CreatureLinkingMgr.h index 20c0eb6854..02196f82ad 100644 --- a/src/game/WorldHandlers/CreatureLinkingMgr.h +++ b/src/game/WorldHandlers/CreatureLinkingMgr.h @@ -44,7 +44,8 @@ #include "Common.h" #include "Policies/Singleton.h" #include "ObjectGuid.h" -#include +#include +#include class Unit; class Creature; diff --git a/src/game/WorldHandlers/DisableMgr.cpp b/src/game/WorldHandlers/DisableMgr.cpp index 03d218b37f..6955cd18f7 100644 --- a/src/game/WorldHandlers/DisableMgr.cpp +++ b/src/game/WorldHandlers/DisableMgr.cpp @@ -44,6 +44,8 @@ #include "ProgressBar.h" #include "World.h" #include "BattleGroundMgr.h" +#include +#include namespace DisableMgr { @@ -448,7 +450,7 @@ bool IsDisabledFor(DisableType type, uint32 entry, Unit const* unit, uint8 flags break; } case DISABLE_TYPE_MAP: - if (Player const* player = unit->ToPlayer()) + if (unit->ToPlayer()) { // [-ZERO] //MapEntry const* mapEntry = sMapStore.LookupEntry(entry); @@ -494,6 +496,8 @@ bool IsDisabledFor(DisableType type, uint32 entry, Unit const* unit, uint8 flags case DISABLE_TYPE_CREATURE_SPAWN: case DISABLE_TYPE_GAMEOBJECT_SPAWN: return (itr->second.flags & SPAWN_DISABLE_CHECK_GUID) == 0 || itr->second.params[0].count(adData) > 0; + default: + break; } return false; diff --git a/src/game/WorldHandlers/DuelHandler.cpp b/src/game/WorldHandlers/DuelHandler.cpp index 9a5d95e2e4..b8841e8c62 100644 --- a/src/game/WorldHandlers/DuelHandler.cpp +++ b/src/game/WorldHandlers/DuelHandler.cpp @@ -43,6 +43,7 @@ #include "WorldSession.h" #include "Log.h" #include "Player.h" +#include /** * @brief Handle duel acceptance from the challenged player diff --git a/src/game/WorldHandlers/DynamicCollision.cpp b/src/game/WorldHandlers/DynamicCollision.cpp index 5659aa795e..4d30fbb501 100644 --- a/src/game/WorldHandlers/DynamicCollision.cpp +++ b/src/game/WorldHandlers/DynamicCollision.cpp @@ -29,6 +29,8 @@ #include #include +#include +#include using world::terrain::Aabb; using world::terrain::Vec3; diff --git a/src/game/WorldHandlers/FusedTerrain.cpp b/src/game/WorldHandlers/FusedTerrain.cpp index a415cd98d8..a581d17fa5 100644 --- a/src/game/WorldHandlers/FusedTerrain.cpp +++ b/src/game/WorldHandlers/FusedTerrain.cpp @@ -34,6 +34,10 @@ #include #include #include +#include +#include +#include +#include using world::terrain::Vec3; using world::terrain::Aabb; diff --git a/src/game/WorldHandlers/GMTicketHandler.cpp b/src/game/WorldHandlers/GMTicketHandler.cpp index ca38cb27e2..b233b06f94 100644 --- a/src/game/WorldHandlers/GMTicketHandler.cpp +++ b/src/game/WorldHandlers/GMTicketHandler.cpp @@ -47,6 +47,7 @@ #include "ObjectAccessor.h" #include "Player.h" #include "Chat.h" +#include /** * @brief Send ticket status to client diff --git a/src/game/WorldHandlers/GameEventMgr.cpp b/src/game/WorldHandlers/GameEventMgr.cpp index 825c39d4f2..230f3d1869 100644 --- a/src/game/WorldHandlers/GameEventMgr.cpp +++ b/src/game/WorldHandlers/GameEventMgr.cpp @@ -56,9 +56,11 @@ #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif -INSTANTIATE_SINGLETON_1(GameEventMgr); /** * @brief Checks whether a specific game event is active at the given time. diff --git a/src/game/WorldHandlers/GameEventMgr.h b/src/game/WorldHandlers/GameEventMgr.h index dd228be2a9..bb1f071e56 100644 --- a/src/game/WorldHandlers/GameEventMgr.h +++ b/src/game/WorldHandlers/GameEventMgr.h @@ -28,6 +28,13 @@ #include "Common.h" #include "SharedDefines.h" #include "Platform/Define.h" +#include +#include +#include +#include +#include +#include +#include #define max_ge_check_delay 86400 // 1 day in seconds diff --git a/src/game/WorldHandlers/GameObjectModel.cpp b/src/game/WorldHandlers/GameObjectModel.cpp index 9a5f921fa5..9a37b20e78 100644 --- a/src/game/WorldHandlers/GameObjectModel.cpp +++ b/src/game/WorldHandlers/GameObjectModel.cpp @@ -30,6 +30,10 @@ #include #include #include +#include +#include +#include +#include using world::terrain::Aabb; using world::terrain::ICollisionModel; diff --git a/src/game/WorldHandlers/GossipDef.cpp b/src/game/WorldHandlers/GossipDef.cpp index 130f6ec5b1..70b0dd06d0 100644 --- a/src/game/WorldHandlers/GossipDef.cpp +++ b/src/game/WorldHandlers/GossipDef.cpp @@ -50,6 +50,7 @@ #include "WorldPacket.h" #include "WorldSession.h" #include "Formulas.h" +#include // Constructor for GossipMenu, initializes the session and reserves space for menu items GossipMenu::GossipMenu(WorldSession* session) : m_session(session) diff --git a/src/game/WorldHandlers/GossipDef.h b/src/game/WorldHandlers/GossipDef.h index 23b5ae6cdb..f1e3f32372 100644 --- a/src/game/WorldHandlers/GossipDef.h +++ b/src/game/WorldHandlers/GossipDef.h @@ -29,6 +29,8 @@ #include "QuestDef.h" #include "NPCHandler.h" #include "ObjectGuid.h" +#include +#include class WorldSession; diff --git a/src/game/WorldHandlers/GridMap.cpp b/src/game/WorldHandlers/GridMap.cpp index 61a488202b..f4c33f5583 100644 --- a/src/game/WorldHandlers/GridMap.cpp +++ b/src/game/WorldHandlers/GridMap.cpp @@ -41,6 +41,7 @@ #include "MoveMap.h" #include "World.h" #include "Policies/Singleton.h" +#include using world::terrain::LiquidInfo; using world::terrain::LiquidKind; @@ -348,9 +349,6 @@ void FusedTerrain::UnloadGrid(int gx, int gy) // TerrainManager -- one shared FusedTerrain per map id ////////////////////////////////////////////////////////////////////////// -#define CLASS_LOCK MaNGOS::ClassLevelLockable -INSTANTIATE_SINGLETON_2(TerrainManager, CLASS_LOCK); -INSTANTIATE_CLASS_MUTEX(TerrainManager, ACE_Thread_Mutex); TerrainManager::TerrainManager() : m_mutex() { @@ -369,7 +367,7 @@ TerrainManager::~TerrainManager() */ FusedTerrain* TerrainManager::LoadTerrain(const uint32 mapId) { - ACE_GUARD_RETURN(LOCK_TYPE, _guard, m_mutex, NULL) + std::lock_guard _guard(m_mutex); TerrainDataMap::const_iterator iter = i_TerrainMap.find(mapId); if (iter == i_TerrainMap.end()) @@ -392,7 +390,7 @@ void TerrainManager::UnloadTerrain(const uint32 mapId) return; } - ACE_GUARD(LOCK_TYPE, _guard, m_mutex) + std::lock_guard _guard(m_mutex); TerrainDataMap::iterator iter = i_TerrainMap.find(mapId); if (iter != i_TerrainMap.end()) @@ -416,7 +414,7 @@ void TerrainManager::UnloadTerrain(const uint32 mapId) */ void TerrainManager::Update(const uint32 diff) { - ACE_GUARD(LOCK_TYPE, _guard, m_mutex) + std::lock_guard _guard(m_mutex); for (TerrainDataMap::iterator it = i_TerrainMap.begin(); it != i_TerrainMap.end(); ++it) { diff --git a/src/game/WorldHandlers/GridMap.h b/src/game/WorldHandlers/GridMap.h index a5ffe7dfad..a57f79fe7a 100644 --- a/src/game/WorldHandlers/GridMap.h +++ b/src/game/WorldHandlers/GridMap.h @@ -35,14 +35,12 @@ #include "Policies/Singleton.h" #include "Utilities/UnorderedMapSet.h" #include "FusedTerrain.h" - -#include - +#include // class for sharing and managing FusedTerrain objects, one per map id -class TerrainManager : public MaNGOS::Singleton > +class TerrainManager : public MaNGOS::Singleton { typedef UNORDERED_MAP TerrainDataMap; - friend class MaNGOS::OperatorNew; + friend class MaNGOS::Singleton; public: FusedTerrain* LoadTerrain(const uint32 mapId); @@ -80,7 +78,7 @@ class TerrainManager : public MaNGOS::Singleton using namespace MaNGOS; diff --git a/src/game/WorldHandlers/GridNotifiers.h b/src/game/WorldHandlers/GridNotifiers.h index d9f6c93bd3..dead844c4c 100644 --- a/src/game/WorldHandlers/GridNotifiers.h +++ b/src/game/WorldHandlers/GridNotifiers.h @@ -36,6 +36,8 @@ #include #include +#include +#include namespace MaNGOS { diff --git a/src/game/WorldHandlers/Group.cpp b/src/game/WorldHandlers/Group.cpp index a4f793f3c3..e00c9bb8c6 100644 --- a/src/game/WorldHandlers/Group.cpp +++ b/src/game/WorldHandlers/Group.cpp @@ -59,6 +59,8 @@ #include "LootMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ #define LOOT_ROLL_TIMEOUT (1*MINUTE*IN_MILLISECONDS) diff --git a/src/game/WorldHandlers/Group.h b/src/game/WorldHandlers/Group.h index 88f26bb99d..5210362baf 100644 --- a/src/game/WorldHandlers/Group.h +++ b/src/game/WorldHandlers/Group.h @@ -60,6 +60,12 @@ #include "LootMgr.h" #include "DBCEnums.h" #include "SharedDefines.h" +#include +#include +#include +#include +#include +#include class WorldSession; class Map; diff --git a/src/game/WorldHandlers/GroupHandler.cpp b/src/game/WorldHandlers/GroupHandler.cpp index 86da8cf7be..8b4ae27faa 100644 --- a/src/game/WorldHandlers/GroupHandler.cpp +++ b/src/game/WorldHandlers/GroupHandler.cpp @@ -53,6 +53,7 @@ #include "Group.h" #include "SocialMgr.h" #include "Util.h" +#include /* differences from off: -you can uninvite yourself - is is useful diff --git a/src/game/WorldHandlers/GroupMemberState.cpp b/src/game/WorldHandlers/GroupMemberState.cpp index 98ab966377..12787d7acf 100644 --- a/src/game/WorldHandlers/GroupMemberState.cpp +++ b/src/game/WorldHandlers/GroupMemberState.cpp @@ -63,6 +63,7 @@ #include "LootMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/GuildHandler.cpp b/src/game/WorldHandlers/GuildHandler.cpp index 83b195d922..92618a1a8f 100644 --- a/src/game/WorldHandlers/GuildHandler.cpp +++ b/src/game/WorldHandlers/GuildHandler.cpp @@ -60,6 +60,7 @@ #include "SocialMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ void WorldSession::HandleGuildQueryOpcode(WorldPacket& recvPacket) @@ -111,7 +112,7 @@ void WorldSession::HandleGuildCreateOpcode(WorldPacket& recvPacket) * @param player The invited player. * @param alreadyInGuild Unused legacy flag for prior guild membership checks. */ -void WorldSession::SendGuildInvite(Player* player, bool alreadyInGuild /*= false*/) +void WorldSession::SendGuildInvite(Player* player, bool /*alreadyInGuild*/ /*= false*/) { Guild* guild = sGuildMgr.GetGuildById(GetPlayer()->GetGuildId()); player->SetGuildIdInvited(GetPlayer()->GetGuildId()); diff --git a/src/game/WorldHandlers/GuildMgr.cpp b/src/game/WorldHandlers/GuildMgr.cpp index ec6b854c0b..ca9ede94ef 100644 --- a/src/game/WorldHandlers/GuildMgr.cpp +++ b/src/game/WorldHandlers/GuildMgr.cpp @@ -47,8 +47,8 @@ #include "Policies/Singleton.h" #include "ProgressBar.h" #include "World.h" +#include -INSTANTIATE_SINGLETON_1(GuildMgr); /** * @brief Construct GuildMgr singleton diff --git a/src/game/WorldHandlers/GuildMgr.h b/src/game/WorldHandlers/GuildMgr.h index 190f583599..da7a508e69 100644 --- a/src/game/WorldHandlers/GuildMgr.h +++ b/src/game/WorldHandlers/GuildMgr.h @@ -26,6 +26,7 @@ #define MANGOS_H_GUILDMGR #include "Common.h" +#include class Guild; class ObjectGuid; diff --git a/src/game/WorldHandlers/InstanceData.cpp b/src/game/WorldHandlers/InstanceData.cpp index 888ceab76c..9b66ab17bb 100644 --- a/src/game/WorldHandlers/InstanceData.cpp +++ b/src/game/WorldHandlers/InstanceData.cpp @@ -42,6 +42,7 @@ #include "InstanceData.h" #include "Database/DatabaseEnv.h" #include "Map.h" +#include /** * @brief Save instance state to database diff --git a/src/game/WorldHandlers/ItemHandler.cpp b/src/game/WorldHandlers/ItemHandler.cpp index eb87313773..ed09115044 100644 --- a/src/game/WorldHandlers/ItemHandler.cpp +++ b/src/game/WorldHandlers/ItemHandler.cpp @@ -52,6 +52,7 @@ #include "Item.h" #include "UpdateData.h" #include "Chat.h" +#include /** * @brief Splits part of an item stack into another destination slot. diff --git a/src/game/WorldHandlers/ItemHandlerVendor.cpp b/src/game/WorldHandlers/ItemHandlerVendor.cpp index 0806a700a1..4100a5fb59 100644 --- a/src/game/WorldHandlers/ItemHandlerVendor.cpp +++ b/src/game/WorldHandlers/ItemHandlerVendor.cpp @@ -54,6 +54,7 @@ #include "Item.h" #include "UpdateData.h" #include "Chat.h" +#include /** * @brief Sells an item stack to a vendor. diff --git a/src/game/WorldHandlers/LFGHandler.cpp b/src/game/WorldHandlers/LFGHandler.cpp index 1bb9385205..8f2c74f921 100644 --- a/src/game/WorldHandlers/LFGHandler.cpp +++ b/src/game/WorldHandlers/LFGHandler.cpp @@ -45,6 +45,7 @@ #include "WorldPacket.h" #include "ObjectMgr.h" #include "World.h" +#include static void AttemptJoin(Player* _player) { diff --git a/src/game/WorldHandlers/LootHandler.cpp b/src/game/WorldHandlers/LootHandler.cpp index e7559a0830..3d6e52e48a 100644 --- a/src/game/WorldHandlers/LootHandler.cpp +++ b/src/game/WorldHandlers/LootHandler.cpp @@ -54,6 +54,8 @@ #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ void WorldSession::HandleAutostoreLootItemOpcode(WorldPacket& recv_data) @@ -168,6 +170,8 @@ void WorldSession::HandleAutostoreLootItemOpcode(WorldPacket& recv_data) } break; } + default: // FREE_FOR_ALL, ROUND_ROBIN: no extra restriction + break; } } diff --git a/src/game/WorldHandlers/Mail.cpp b/src/game/WorldHandlers/Mail.cpp index 28fd238c4b..664a91e2cb 100644 --- a/src/game/WorldHandlers/Mail.cpp +++ b/src/game/WorldHandlers/Mail.cpp @@ -51,6 +51,8 @@ #include "Player.h" #include "BattleGround/BattleGroundMgr.h" #include "Item.h" +#include +#include /** * Creates a new MailSender object. diff --git a/src/game/WorldHandlers/Mail.h b/src/game/WorldHandlers/Mail.h index da7405cb88..d0f8580640 100644 --- a/src/game/WorldHandlers/Mail.h +++ b/src/game/WorldHandlers/Mail.h @@ -43,6 +43,9 @@ #include "Common.h" #include "ObjectGuid.h" #include +#include +#include +#include struct AuctionEntry; class Item; diff --git a/src/game/WorldHandlers/MailHandler.cpp b/src/game/WorldHandlers/MailHandler.cpp index eba86b5436..a6f0233b8a 100644 --- a/src/game/WorldHandlers/MailHandler.cpp +++ b/src/game/WorldHandlers/MailHandler.cpp @@ -52,6 +52,8 @@ #include "WorldSession.h" #include "Opcodes.h" #include "Chat.h" +#include +#include /** * @brief Verifies that the player can legally access the requested mailbox. @@ -233,10 +235,6 @@ void WorldSession::HandleSendMail(WorldPacket& recv_data) return; } - uint32 rc_account = receive - ? receive->GetSession()->GetAccountId() - : sObjectMgr.GetPlayerAccountIdByGUID(rc); - Item* items[MAX_MAIL_ITEMS]; for (uint8 i = 0; i < items_count; ++i) diff --git a/src/game/WorldHandlers/Map.cpp b/src/game/WorldHandlers/Map.cpp index f4843bd858..c9ab35cde7 100644 --- a/src/game/WorldHandlers/Map.cpp +++ b/src/game/WorldHandlers/Map.cpp @@ -71,6 +71,8 @@ #include "LuaEngine.h" #include "ElunaConfig.h" #include "ElunaLoader.h" +#include +#include #endif /* ENABLE_ELUNA */ /** @@ -1491,16 +1493,35 @@ bool Map::CreatureCellRelocation(Creature* c, const Cell &new_cell) */ bool Map::CreatureRespawnRelocation(Creature* c) { - // A crew member has no grid cell to be moved between, and its respawn coord is a deck - // offset rather than a map coordinate -- ComputeCellPair on it would name the cell next - // to the map origin. Its vessel puts it back on its mark. - if (c->GetTransportInfo()) + if (TransportInfo* transportInfo = c->GetTransportInfo()) { - c->CombatStop(); - c->GetMotionMaster()->Clear(); - c->RelocateToRespawnPoint(); - c->GetMotionMaster()->Initialize(); - return true; + // CREW. It has no grid cell to be moved between, and its respawn coord is a deck + // offset rather than a map coordinate -- ComputeCellPair on it would name the cell + // next to the map origin. Its vessel puts it back on its mark. + if (!transportInfo->IsMinion()) + { + c->CombatStop(); + c->GetMotionMaster()->Clear(); + c->RelocateToRespawnPoint(); + c->GetMotionMaster()->Initialize(); + return true; + } + + // A MINION -- a pet, a guardian -- is a citizen of the WORLD that is merely standing + // on the vessel's floor, and we are only here because THE GRID REFUSED IT: the deck + // has carried it into a cell that will not take it. + // + // It must not take the crew path above, and the reason is not subtle. Its respawn + // coord is WORLD data (it was summoned somewhere on the map), not a deck offset. So + // RelocateToRespawnPoint would hand a map coordinate to SetLocalPosition, which would + // read it as an offset, compose it with the vessel's pose into a point further out to + // sea still, be refused by the grid again -- and arrive straight back here. That is an + // unbounded recursion, and it diverges rather than settling: it overflows the stack. + // + // So the minion simply steps off the boat. Off the deck it is an ordinary creature the + // grid has refused, and the ordinary path below is exactly right for it. (Its master's + // vessel will offer it the deck again on its next tick -- see Transport::UpdateMinions.) + transportInfo->GetTransportBase()->UnBoardPassenger(c); } float resp_x, resp_y, resp_z, resp_o; diff --git a/src/game/WorldHandlers/Map.h b/src/game/WorldHandlers/Map.h index 75ef1d6a34..c9c96a9f6b 100644 --- a/src/game/WorldHandlers/Map.h +++ b/src/game/WorldHandlers/Map.h @@ -53,10 +53,6 @@ #include "Common.h" #include "Platform/Define.h" -#include "Policies/ThreadingModel.h" -#include -#include - #include "DBCStructure.h" #include "GridDefines.h" #include "Cell.h" @@ -73,6 +69,10 @@ #endif /* ENABLE_ELUNA */ #include +#include +#include +#include +#include struct CreatureInfo; class Creature; diff --git a/src/game/WorldHandlers/MapManager.cpp b/src/game/WorldHandlers/MapManager.cpp index 7014762861..9222580583 100644 --- a/src/game/WorldHandlers/MapManager.cpp +++ b/src/game/WorldHandlers/MapManager.cpp @@ -57,11 +57,12 @@ #ifdef ENABLE_ELUNA #include "ElunaConfig.h" +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ -#define CLASS_LOCK MaNGOS::ClassLevelLockable -INSTANTIATE_SINGLETON_2(MapManager, CLASS_LOCK); -INSTANTIATE_CLASS_MUTEX(MapManager, ACE_Recursive_Thread_Mutex); MapManager::MapManager() : i_gridCleanUpDelay(sWorld.getConfig(CONFIG_UINT32_INTERVAL_GRIDCLEAN)), m_lock() @@ -184,7 +185,7 @@ void MapManager::InitializeVisibilityDistanceInfo() /// @param id - MapId of the to be created map. @param obj WorldObject for which the map is to be created. Must be player for Instancable maps. Map* MapManager::CreateMap(uint32 id, const WorldObject* obj) { - ACE_GUARD_RETURN(LOCK_TYPE, _guard, m_lock, NULL) + std::lock_guard _guard(m_lock); Map* m = NULL; @@ -205,7 +206,7 @@ Map* MapManager::CreateMap(uint32 id, const WorldObject* obj) else { // create regular non-instanceable map - m = FindMap(id); + m = FindMap_unlocked(id); // m_lock is already held if (m == NULL) { m = new WorldMap(id, i_gridCleanUpDelay); @@ -231,7 +232,7 @@ Map* MapManager::CreateBgMap(uint32 mapid, BattleGround* bg) { sTerrainMgr.LoadTerrain(mapid); - ACE_GUARD_RETURN(LOCK_TYPE, _guard, m_lock, NULL) + std::lock_guard _guard(m_lock); return CreateBattleGroundMap(mapid, sMapMgr.GenerateInstanceId(), bg); } @@ -244,8 +245,13 @@ Map* MapManager::CreateBgMap(uint32 mapid, BattleGround* bg) */ Map* MapManager::FindMap(uint32 mapid, uint32 instanceId) const { - ACE_GUARD_RETURN(LOCK_TYPE, _guard, m_lock, NULL) + std::lock_guard _guard(m_lock); + return FindMap_unlocked(mapid, instanceId); +} +/// Body of FindMap(), for callers that already hold m_lock. See the header. +Map* MapManager::FindMap_unlocked(uint32 mapid, uint32 instanceId) const +{ MapMapType::const_iterator iter = i_maps.find(MapID(mapid, instanceId)); if (iter == i_maps.end()) { @@ -270,7 +276,7 @@ Map* MapManager::FindMap(uint32 mapid, uint32 instanceId) const */ void MapManager::DeleteInstance(uint32 mapid, uint32 instanceId) { - ACE_GUARD(LOCK_TYPE, _guard, m_lock) + std::lock_guard _guard(m_lock); MapMapType::iterator iter = i_maps.find(MapID(mapid, instanceId)); if (iter != i_maps.end()) @@ -443,7 +449,7 @@ uint32 MapManager::GetNumInstances() { uint32 ret = 0; - ACE_GUARD_RETURN(LOCK_TYPE, _guard, m_lock, ret) + std::lock_guard _guard(m_lock); for (MapMapType::iterator itr = i_maps.begin(); itr != i_maps.end(); ++itr) { Map* map = itr->second; @@ -465,7 +471,7 @@ uint32 MapManager::GetNumPlayersInInstances() { uint32 ret = 0; - ACE_GUARD_RETURN(LOCK_TYPE, _guard, m_lock, ret) + std::lock_guard _guard(m_lock); for (MapMapType::iterator itr = i_maps.begin(); itr != i_maps.end(); ++itr) { Map* map = itr->second; @@ -492,14 +498,14 @@ Map* MapManager::CreateInstance(uint32 id, Player* player) // find existing bg map for player NewInstanceId = player->GetBattleGroundId(); MANGOS_ASSERT(NewInstanceId); - map = FindMap(id, NewInstanceId); + map = FindMap_unlocked(id, NewInstanceId); // called under m_lock from CreateMap() MANGOS_ASSERT(map); } else if (DungeonPersistentState* pSave = player->GetBoundInstanceSaveForSelfOrGroup(id)) { // solo/perm/group NewInstanceId = pSave->GetInstanceId(); - map = FindMap(id, NewInstanceId); + map = FindMap_unlocked(id, NewInstanceId); // called under m_lock from CreateMap() // it is possible that the save exists but the map doesn't if (!map) { diff --git a/src/game/WorldHandlers/MapManager.h b/src/game/WorldHandlers/MapManager.h index 354a3db655..60ae5a0a58 100644 --- a/src/game/WorldHandlers/MapManager.h +++ b/src/game/WorldHandlers/MapManager.h @@ -28,10 +28,14 @@ #include "Common.h" #include "Platform/Define.h" #include "Policies/Singleton.h" -#include #include "Map.h" #include "GridStates.h" #include "MapUpdater.h" +#include +#include +#include +#include +#include class Transport; class BattleGround; @@ -57,9 +61,9 @@ struct MapID uint32 nInstanceId; }; -class MapManager : public MaNGOS::Singleton > +class MapManager : public MaNGOS::Singleton { - friend class MaNGOS::OperatorNew; + friend class MaNGOS::Singleton; public: typedef std::map MapMapType; @@ -197,6 +201,17 @@ class MapManager : public MaNGOS::Singleton +#include +#include +#include +#include +#include +#include +#include -INSTANTIATE_SINGLETON_1(MapPersistentStateManager); static uint32 resetEventTypeDelay[MAX_RESET_EVENT_TYPE] = { 0, // not used 3600, 900, 300, 60, // (seconds) normal and official timer delay to inform player about instance reset diff --git a/src/game/WorldHandlers/MapPersistentStateMgr.h b/src/game/WorldHandlers/MapPersistentStateMgr.h index ab9b7bbe55..7a559be0b9 100644 --- a/src/game/WorldHandlers/MapPersistentStateMgr.h +++ b/src/game/WorldHandlers/MapPersistentStateMgr.h @@ -28,7 +28,6 @@ #include "Common.h" #include "Platform/Define.h" #include "Policies/Singleton.h" -#include #include #include #include "Utilities/UnorderedMapSet.h" @@ -37,6 +36,9 @@ #include "DBCStores.h" #include "ObjectGuid.h" #include "PoolManager.h" +#include +#include +#include struct InstanceTemplate; struct MapEntry; @@ -328,7 +330,7 @@ class DungeonResetScheduler ResetTimeQueue m_resetTimeQueue; }; -class MapPersistentStateManager : public MaNGOS::Singleton > +class MapPersistentStateManager : public MaNGOS::Singleton { friend class DungeonResetScheduler; public: // constructors diff --git a/src/game/WorldHandlers/MassMailMgr.cpp b/src/game/WorldHandlers/MassMailMgr.cpp index ac1103a91d..8e893e57c2 100644 --- a/src/game/WorldHandlers/MassMailMgr.cpp +++ b/src/game/WorldHandlers/MassMailMgr.cpp @@ -53,8 +53,8 @@ #include "SharedDefines.h" #include "World.h" #include "ObjectMgr.h" +#include -INSTANTIATE_SINGLETON_1(MassMailMgr); /** * @brief Add mass mail task filtered by race mask @@ -138,7 +138,7 @@ void MassMailMgr::AddMassMailTask(MailDraft* mailProto, const MailSender &sender CharacterDatabase.AsyncPQuery([mailProto, sender](QueryResult* result) { massMailerQueryHandler.HandleQueryCallback(result, mailProto, sender); - }, query); + }, "%s", query); } /** diff --git a/src/game/WorldHandlers/MassMailMgr.h b/src/game/WorldHandlers/MassMailMgr.h index e3c2956f58..0c2069959b 100644 --- a/src/game/WorldHandlers/MassMailMgr.h +++ b/src/game/WorldHandlers/MassMailMgr.h @@ -44,6 +44,7 @@ #include "Common.h" #include "Mail.h" +#include /** * A class to represent the mail send factory to multiple (often all existing) characters. diff --git a/src/game/WorldHandlers/MiscHandler.cpp b/src/game/WorldHandlers/MiscHandler.cpp index af9382de25..450974e2b0 100644 --- a/src/game/WorldHandlers/MiscHandler.cpp +++ b/src/game/WorldHandlers/MiscHandler.cpp @@ -72,6 +72,8 @@ #include "SocialMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ void WorldSession::HandleRepopRequestOpcode(WorldPacket& recv_data) @@ -836,7 +838,7 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recv_data) } // need find areatrigger to inner dungeon for landing point - if (at->target_mapId != corpseMapId) + if (at->target_mapId != uint32(corpseMapId)) { if (AreaTrigger const* corpseAt = sObjectMgr.GetMapEntranceTrigger(corpseMapId)) { diff --git a/src/game/WorldHandlers/MiscHandlerSocial.cpp b/src/game/WorldHandlers/MiscHandlerSocial.cpp index d87117ac40..060dd9ae88 100644 --- a/src/game/WorldHandlers/MiscHandlerSocial.cpp +++ b/src/game/WorldHandlers/MiscHandlerSocial.cpp @@ -77,6 +77,7 @@ #include #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/MoveMap.cpp b/src/game/WorldHandlers/MoveMap.cpp index 880fa7accd..6153b70a1b 100644 --- a/src/game/WorldHandlers/MoveMap.cpp +++ b/src/game/WorldHandlers/MoveMap.cpp @@ -48,6 +48,11 @@ #include "Creature.h" #include "MoveMap.h" #include "MoveMapSharedDefines.h" +#include +#include +#include +#include +#include namespace MMAP { diff --git a/src/game/WorldHandlers/MovementHandler.cpp b/src/game/WorldHandlers/MovementHandler.cpp index 09f0e01ec5..126b31fb1b 100644 --- a/src/game/WorldHandlers/MovementHandler.cpp +++ b/src/game/WorldHandlers/MovementHandler.cpp @@ -90,6 +90,8 @@ uint32 mTimeStamp() #else #include +#include // gettimeofday(), struct timeval (was reached via ACE before) +#include uint32 mTimeStamp() { diff --git a/src/game/WorldHandlers/NPCHandler.cpp b/src/game/WorldHandlers/NPCHandler.cpp index b34a01f867..b38df486ff 100644 --- a/src/game/WorldHandlers/NPCHandler.cpp +++ b/src/game/WorldHandlers/NPCHandler.cpp @@ -69,6 +69,8 @@ #include "Item.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ enum StableResultCode diff --git a/src/game/WorldHandlers/ObjectGridLoader.cpp b/src/game/WorldHandlers/ObjectGridLoader.cpp index 675f221c86..b173d292e1 100644 --- a/src/game/WorldHandlers/ObjectGridLoader.cpp +++ b/src/game/WorldHandlers/ObjectGridLoader.cpp @@ -165,7 +165,7 @@ class ObjectWorldLoader * @param gloader Parent grid loader with cell/grid/map references */ explicit ObjectWorldLoader(ObjectGridLoader& gloader) - : i_cell(gloader.i_cell), i_grid(gloader.i_grid), i_map(gloader.i_map), i_corpses(0) + : i_cell(gloader.i_cell), i_map(gloader.i_map), i_corpses(0) {} /** @@ -181,7 +181,6 @@ class ObjectWorldLoader private: Cell i_cell; ///< Cell coordinates - NGridType& i_grid; ///< Grid being loaded Map* i_map; ///< Owning map public: uint32 i_corpses; ///< Count of loaded corpses diff --git a/src/game/WorldHandlers/PetHandler.cpp b/src/game/WorldHandlers/PetHandler.cpp index dfa5f67795..2cbbbdf282 100644 --- a/src/game/WorldHandlers/PetHandler.cpp +++ b/src/game/WorldHandlers/PetHandler.cpp @@ -51,6 +51,9 @@ #include "CreatureAI.h" #include "Util.h" #include "Pet.h" +#include +#include +#include /** * @brief Handles pet action bar commands, reactions, and spell casts. diff --git a/src/game/WorldHandlers/PetitionsHandler.cpp b/src/game/WorldHandlers/PetitionsHandler.cpp index e5200f55ac..72c2a5fe99 100644 --- a/src/game/WorldHandlers/PetitionsHandler.cpp +++ b/src/game/WorldHandlers/PetitionsHandler.cpp @@ -51,6 +51,8 @@ #include "ArenaTeam.h" #include "GossipDef.h" #include "SocialMgr.h" +#include +#include // Charters ID in item_template #define GUILD_CHARTER 5863 @@ -114,7 +116,6 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recv_data) } uint32 charterid = 0; - uint32 cost = 0; uint32 type = 0; if (pCreature->IsTabardDesigner()) { @@ -126,7 +127,6 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recv_data) } charterid = GUILD_CHARTER; - cost = GUILD_CHARTER_COST; type = 9; } else @@ -142,17 +142,14 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recv_data) { case 1: charterid = ARENA_TEAM_CHARTER_2v2; - cost = ARENA_TEAM_CHARTER_2v2_COST; type = 2; // 2v2 break; case 2: charterid = ARENA_TEAM_CHARTER_3v3; - cost = ARENA_TEAM_CHARTER_3v3_COST; type = 3; // 3v3 break; case 3: charterid = ARENA_TEAM_CHARTER_5v5; - cost = ARENA_TEAM_CHARTER_5v5_COST; type = 5; // 5v5 break; default: @@ -355,21 +352,17 @@ void WorldSession::SendPetitionQueryOpcode(ObjectGuid petitionguid) ObjectGuid ownerGuid; uint32 type; std::string name = "NO_NAME_FOR_GUID"; - uint8 signs = 0; QueryResult* result = CharacterDatabase.PQuery( - "SELECT `ownerguid`, `name`, " - " (SELECT COUNT(`playerguid`) FROM `petition_sign` WHERE `petition_sign`.`petitionguid` = '%u') AS `signs`, " - " `type` " - "FROM `petition` WHERE `petitionguid` = '%u'", petitionLowGuid, petitionLowGuid); + "SELECT `ownerguid`, `name`, `type` " + "FROM `petition` WHERE `petitionguid` = '%u'", petitionLowGuid); if (result) { Field* fields = result->Fetch(); ownerGuid = ObjectGuid(HIGHGUID_PLAYER, fields[0].GetUInt32()); name = fields[1].GetCppString(); - signs = fields[2].GetUInt8(); - type = fields[3].GetUInt32(); + type = fields[2].GetUInt32(); delete result; } else diff --git a/src/game/WorldHandlers/PoolManager.cpp b/src/game/WorldHandlers/PoolManager.cpp index 078c3bfec6..c7be8f1484 100644 --- a/src/game/WorldHandlers/PoolManager.cpp +++ b/src/game/WorldHandlers/PoolManager.cpp @@ -51,8 +51,11 @@ #include "MapPersistentStateMgr.h" #include "World.h" #include "Policies/Singleton.h" +#include +#include +#include +#include -INSTANTIATE_SINGLETON_1(PoolManager); //////////////////////////////////////////////////////////// // template class SpawnedPoolData diff --git a/src/game/WorldHandlers/PoolManager.h b/src/game/WorldHandlers/PoolManager.h index f34b1f2024..8ed40ad562 100644 --- a/src/game/WorldHandlers/PoolManager.h +++ b/src/game/WorldHandlers/PoolManager.h @@ -29,6 +29,12 @@ #include "Platform/Define.h" #include "Creature.h" #include "GameObject.h" +#include +#include +#include +#include +#include +#include class MapPersistentState; struct MapEntry; diff --git a/src/game/WorldHandlers/QueryHandler.cpp b/src/game/WorldHandlers/QueryHandler.cpp index 27ecad7b12..6fd449d589 100644 --- a/src/game/WorldHandlers/QueryHandler.cpp +++ b/src/game/WorldHandlers/QueryHandler.cpp @@ -51,6 +51,8 @@ #include "Player.h" #include "NPCHandler.h" #include "SQLStorages.h" +#include +#include /** * @brief Sends an in-memory name query response for a player. diff --git a/src/game/WorldHandlers/QuestDef.h b/src/game/WorldHandlers/QuestDef.h index 8395075787..0f3b82cbef 100644 --- a/src/game/WorldHandlers/QuestDef.h +++ b/src/game/WorldHandlers/QuestDef.h @@ -29,6 +29,8 @@ #include "Database/DatabaseEnv.h" #include +#include +#include class Player; diff --git a/src/game/WorldHandlers/ScriptAction.cpp b/src/game/WorldHandlers/ScriptAction.cpp index 05980d91bc..2920dec487 100644 --- a/src/game/WorldHandlers/ScriptAction.cpp +++ b/src/game/WorldHandlers/ScriptAction.cpp @@ -69,6 +69,8 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_SD3 #include "system/ScriptDevMgr.h" +#include +#include #endif /* ENABLE_SD3 */ /// Helper function to get Object source or target for Script-Command @@ -1352,8 +1354,6 @@ bool ScriptAction::HandleScriptStep() case SCRIPT_COMMAND_DESPAWN_GO: // 40 { - uint32 goEntry; - GameObject* pGo; if (!m_script->despawnGo.goGuid) { sLog.outErrorDb("Table `db_scripts [type = %d]` has no gameobject defined in SCRIPT_COMMAND_DESPAWN_GO for script id %u", m_type, m_script->id); @@ -1367,7 +1367,12 @@ bool ScriptAction::HandleScriptStep() break; } - pGo = m_map->GetGameObject(ObjectGuid(HIGHGUID_GAMEOBJECT, goData->id, m_script->despawnGo.goGuid)); + GameObject* pGo = m_map->GetGameObject(ObjectGuid(HIGHGUID_GAMEOBJECT, goData->id, m_script->despawnGo.goGuid)); + if (!pGo) + { + sLog.outErrorDb(" DB-SCRIPTS: Process table `db_scripts [type = %d]` id %u, command %u failed for gameobject(guid: %u, buddyEntry: %u).", m_type, m_script->id, m_script->command, m_script->despawnGo.goGuid, m_script->buddyEntry); + break; + } pGo->SetRespawnTime(m_script->despawnGo.respawnTime); pGo->SetLootState(GO_JUST_DEACTIVATED); diff --git a/src/game/WorldHandlers/ScriptMgr.cpp b/src/game/WorldHandlers/ScriptMgr.cpp index 2f7a65c8fe..f85d3a9e4b 100644 --- a/src/game/WorldHandlers/ScriptMgr.cpp +++ b/src/game/WorldHandlers/ScriptMgr.cpp @@ -73,10 +73,10 @@ #endif #include /* std::strcmp */ +#include -INSTANTIATE_SINGLETON_1(ScriptMgr); -ScriptMgr::ScriptMgr() : m_scheduledScripts(0), m_lock(0) +ScriptMgr::ScriptMgr() : m_scheduledScripts(0) { m_dbScripts.resize(DBS_END); diff --git a/src/game/WorldHandlers/ScriptMgr.h b/src/game/WorldHandlers/ScriptMgr.h index 3f8df0aeca..09b27992f9 100644 --- a/src/game/WorldHandlers/ScriptMgr.h +++ b/src/game/WorldHandlers/ScriptMgr.h @@ -28,10 +28,14 @@ #include "Common.h" #include "ObjectGuid.h" #include "DBCEnums.h" -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include struct AreaTriggerEntry; struct SpellEntry; class Aura; @@ -724,12 +728,12 @@ class ScriptMgr DBScripts m_dbScripts; #ifdef _DEBUG // mutex allowing to reload the script binding table; TODO just do it AWAY from any map update, e.g. right after sessions update - ACE_RW_Thread_Mutex m_bindMutex; + std::shared_mutex m_bindMutex; #endif /* _DEBUG */ // atomic op counter for active scripts amount - ACE_Atomic_Op m_scheduledScripts; + std::atomic m_scheduledScripts; char __cache_guard[1024]; - ACE_Thread_Mutex m_lock; + std::mutex m_lock; }; // Starters for events diff --git a/src/game/WorldHandlers/ScriptMgrBinding.cpp b/src/game/WorldHandlers/ScriptMgrBinding.cpp index 321c8ce9ec..2d4874f41a 100644 --- a/src/game/WorldHandlers/ScriptMgrBinding.cpp +++ b/src/game/WorldHandlers/ScriptMgrBinding.cpp @@ -70,6 +70,9 @@ #endif /* CLASSIC */ #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif /* ENABLE_ELUNA */ // ///////////////////////////////////////////////////////// @@ -211,9 +214,8 @@ void ScriptMgr::LoadScriptBinding() bool ScriptMgr::ReloadScriptBinding() { #ifdef _DEBUG - m_bindMutex.acquire_write(); + std::unique_lock guard(m_bindMutex); LoadScriptBinding(); - m_bindMutex.release(); return true; #else return false; @@ -291,7 +293,10 @@ uint32 ScriptMgr::GetScriptId(const char* name) const uint32 ScriptMgr::GetBoundScriptId(ScriptedObjectType entity, int32 entry) { #ifdef _DEBUG - m_bindMutex.acquire_read(); + // Only debug builds can reload the bindings at runtime (ReloadScriptBinding), so + // only they need readers to lock. The guard covers the whole lookup and releases on + // every exit path -- the manual acquire/release pair it replaces did not. + std::shared_lock guard(m_bindMutex); #endif /* _DEBUG */ uint32 id = 0; if (entity < SCRIPTED_MAX_TYPE) @@ -304,9 +309,7 @@ uint32 ScriptMgr::GetBoundScriptId(ScriptedObjectType entity, int32 entry) } else sLog.outErrorScriptLib("asking a script for non-existing entity type %u!", entity); -#ifdef _DEBUG - m_bindMutex.release(); -#endif /* _DEBUG */ + return id; } diff --git a/src/game/WorldHandlers/ScriptMgrDbScripts.cpp b/src/game/WorldHandlers/ScriptMgrDbScripts.cpp index 4d5f06f81c..ab8a01ca50 100644 --- a/src/game/WorldHandlers/ScriptMgrDbScripts.cpp +++ b/src/game/WorldHandlers/ScriptMgrDbScripts.cpp @@ -71,6 +71,8 @@ #endif /* ENABLE_ELUNA */ #ifdef ENABLE_SD3 #include "system/ScriptDevMgr.h" +#include +#include #endif /* ENABLE_SD3 */ /** @@ -81,7 +83,7 @@ */ ScriptChainMap const* ScriptMgr::GetScriptChainMap(DBScriptType type) { - ACE_GUARD_RETURN(ACE_Thread_Mutex, _guard, m_lock, NULL) + std::lock_guard _guard(m_lock); if ((type != DBS_INTERNAL) && type < DBS_END) { return &m_dbScripts[type]; @@ -900,7 +902,7 @@ void ScriptMgr::LoadDbScripts(DBScriptType t) } { - ACE_GUARD(ACE_Thread_Mutex, _g, m_lock) + std::lock_guard _g(m_lock); LoadScripts(t); } diff --git a/src/game/WorldHandlers/SkillDiscovery.cpp b/src/game/WorldHandlers/SkillDiscovery.cpp index 4950ab08d0..02e8adee5e 100644 --- a/src/game/WorldHandlers/SkillDiscovery.cpp +++ b/src/game/WorldHandlers/SkillDiscovery.cpp @@ -34,6 +34,9 @@ #include "Player.h" #include "DBCStores.h" #include +#include +#include +#include struct SkillDiscoveryEntry { diff --git a/src/game/WorldHandlers/Spell.h b/src/game/WorldHandlers/Spell.h index 6a3f6fea05..eb7ff42f14 100644 --- a/src/game/WorldHandlers/Spell.h +++ b/src/game/WorldHandlers/Spell.h @@ -55,6 +55,10 @@ #include "LootMgr.h" #include "Unit.h" #include "Player.h" +#include +#include +#include +#include class WorldSession; class WorldPacket; diff --git a/src/game/WorldHandlers/SpellAuraControl.cpp b/src/game/WorldHandlers/SpellAuraControl.cpp index 3d50dee692..d9b7fb6f2f 100644 --- a/src/game/WorldHandlers/SpellAuraControl.cpp +++ b/src/game/WorldHandlers/SpellAuraControl.cpp @@ -76,6 +76,7 @@ #include "CellImpl.h" #include "Language.h" #include "MapManager.h" +#include static AuraType const frozenAuraTypes[] = { SPELL_AURA_MOD_ROOT, SPELL_AURA_MOD_STUN, SPELL_AURA_NONE }; diff --git a/src/game/WorldHandlers/SpellAuraPeriodic.cpp b/src/game/WorldHandlers/SpellAuraPeriodic.cpp index 377a4e3471..8db312aa29 100644 --- a/src/game/WorldHandlers/SpellAuraPeriodic.cpp +++ b/src/game/WorldHandlers/SpellAuraPeriodic.cpp @@ -76,6 +76,7 @@ #include "CellImpl.h" #include "Language.h" #include "MapManager.h" +#include /** * @brief Applies special proc-trigger spell setup for specific aura spells. @@ -90,8 +91,6 @@ void Aura::HandleAuraProcTriggerSpell(bool apply, bool Real) return; } - Unit* target = GetTarget(); - switch (GetId()) { // some spell have charges by functionality not have its in spell data @@ -201,9 +200,6 @@ void Aura::HandleAuraPeriodicDummy(bool apply, bool Real) Unit* target = GetTarget(); - // For prevent double apply bonuses - bool loading = (target->GetTypeId() == TYPEID_PLAYER && ((Player*)target)->GetSession()->PlayerLoading()); - SpellEntry const* spell = GetSpellProto(); switch (spell->SpellClassSet) { diff --git a/src/game/WorldHandlers/SpellAuras.cpp b/src/game/WorldHandlers/SpellAuras.cpp index 82deb34f3d..1fd0a1b345 100644 --- a/src/game/WorldHandlers/SpellAuras.cpp +++ b/src/game/WorldHandlers/SpellAuras.cpp @@ -70,6 +70,10 @@ #include "GridNotifiersImpl.h" #include "CellImpl.h" #include "MapManager.h" +#include +#include +#include +#include #define NULL_AURA_SLOT 0xFF diff --git a/src/game/WorldHandlers/SpellAuras.h b/src/game/WorldHandlers/SpellAuras.h index f1f04a5069..1fd1e56c34 100644 --- a/src/game/WorldHandlers/SpellAuras.h +++ b/src/game/WorldHandlers/SpellAuras.h @@ -27,6 +27,7 @@ #include "SpellAuraDefines.h" #include "ObjectMgr.h" +#include /** diff --git a/src/game/WorldHandlers/SpellCast.cpp b/src/game/WorldHandlers/SpellCast.cpp index 11f31cf185..f3716b7299 100644 --- a/src/game/WorldHandlers/SpellCast.cpp +++ b/src/game/WorldHandlers/SpellCast.cpp @@ -77,6 +77,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/SpellEffectDamageTeleport.cpp b/src/game/WorldHandlers/SpellEffectDamageTeleport.cpp index 089efa9ce8..404acfd4eb 100644 --- a/src/game/WorldHandlers/SpellEffectDamageTeleport.cpp +++ b/src/game/WorldHandlers/SpellEffectDamageTeleport.cpp @@ -481,7 +481,7 @@ void Spell::EffectTriggerRitualOfSummoning(SpellEffectIndex eff_idx) m_caster->CastSpell(unitTarget, spellInfo, false); } -void Spell::EffectForceCast(SpellEffectIndex eff_idx) +void Spell::EffectForceCast(SpellEffectIndex /*eff_idx*/) { if (!unitTarget) { diff --git a/src/game/WorldHandlers/SpellEffectDummy.cpp b/src/game/WorldHandlers/SpellEffectDummy.cpp index c48bc0513d..61a91c77ed 100644 --- a/src/game/WorldHandlers/SpellEffectDummy.cpp +++ b/src/game/WorldHandlers/SpellEffectDummy.cpp @@ -68,6 +68,8 @@ #include #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/SpellEffectHealPower.cpp b/src/game/WorldHandlers/SpellEffectHealPower.cpp index 8457b6b983..7953ee8f35 100644 --- a/src/game/WorldHandlers/SpellEffectHealPower.cpp +++ b/src/game/WorldHandlers/SpellEffectHealPower.cpp @@ -67,6 +67,7 @@ #include "terrain/Geometry/Vector3.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** @@ -454,7 +455,7 @@ void Spell::EffectHealthLeech(SpellEffectIndex eff_idx) * @param eff_idx The effect index creating the item. * @param itemtype The item entry to create. */ -void Spell::DoCreateItem(SpellEffectIndex eff_idx, uint32 itemtype) +void Spell::DoCreateItem(SpellEffectIndex /*eff_idx*/, uint32 itemtype) { if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) { diff --git a/src/game/WorldHandlers/SpellEffectObjectCombat.cpp b/src/game/WorldHandlers/SpellEffectObjectCombat.cpp index 9508d64001..ec6dbb31f4 100644 --- a/src/game/WorldHandlers/SpellEffectObjectCombat.cpp +++ b/src/game/WorldHandlers/SpellEffectObjectCombat.cpp @@ -67,6 +67,7 @@ #include "terrain/Geometry/Vector3.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/SpellEffectSkillEnchantPet.cpp b/src/game/WorldHandlers/SpellEffectSkillEnchantPet.cpp index 345b50c939..80da53b0ae 100644 --- a/src/game/WorldHandlers/SpellEffectSkillEnchantPet.cpp +++ b/src/game/WorldHandlers/SpellEffectSkillEnchantPet.cpp @@ -67,6 +67,10 @@ #include "terrain/Geometry/Vector3.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/SpellEffectSummonLock.cpp b/src/game/WorldHandlers/SpellEffectSummonLock.cpp index 9c1437b865..90a563b962 100644 --- a/src/game/WorldHandlers/SpellEffectSummonLock.cpp +++ b/src/game/WorldHandlers/SpellEffectSummonLock.cpp @@ -426,22 +426,6 @@ void Spell::DoSummonWild(SpellEffectIndex eff_idx, uint32 forceFaction) return; } - uint32 level = m_caster->getLevel(); - - // level of creature summoned using engineering item based at engineering skill level - if (m_caster->GetTypeId() == TYPEID_PLAYER && m_CastItem) - { - ItemPrototype const* proto = m_CastItem->GetProto(); - if (proto && proto->RequiredSkill == SKILL_ENGINEERING) - { - uint16 skill202 = ((Player*)m_caster)->GetSkillValue(SKILL_ENGINEERING); - if (skill202) - { - level = skill202 / 5; - } - } - } - // select center of summon position float center_x = m_targets.m_destX; float center_y = m_targets.m_destY; @@ -740,6 +724,7 @@ void Spell::DoSummonTotem(SpellEffectIndex eff_idx, uint8 slot_dbc) * @brief Summons a possessed creature and transfers control to the caster. * * @param eff_idx The summon effect index. + * @param forceFaction Faction from the summon properties, 0 to keep the template faction. */ bool Spell::DoSummonPossessed(SpellEffectIndex eff_idx, uint32 forceFaction) { @@ -764,6 +749,11 @@ bool Spell::DoSummonPossessed(SpellEffectIndex eff_idx, uint32 forceFaction) spawnCreature->SetUInt32Value(UNIT_CREATED_BY_SPELL, m_spellInfo->ID); spawnCreature->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED); + if (forceFaction) + { + spawnCreature->setFaction(forceFaction); + } + spawnCreature->SetLevel(m_caster->getLevel()); spawnCreature->SetWalk(m_caster->IsWalking()); diff --git a/src/game/WorldHandlers/SpellEffectTail.cpp b/src/game/WorldHandlers/SpellEffectTail.cpp index 9a35f1f249..aa46954c2d 100644 --- a/src/game/WorldHandlers/SpellEffectTail.cpp +++ b/src/game/WorldHandlers/SpellEffectTail.cpp @@ -67,6 +67,9 @@ #include "terrain/Geometry/Vector3.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif /* ENABLE_ELUNA */ /** @@ -606,7 +609,7 @@ void Spell::EffectPlayMusic(SpellEffectIndex eff_idx) * * @param eff_idx The bind effect index. */ -void Spell::EffectBind(SpellEffectIndex eff_idx) +void Spell::EffectBind(SpellEffectIndex /*eff_idx*/) { if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER) { diff --git a/src/game/WorldHandlers/SpellPackets.cpp b/src/game/WorldHandlers/SpellPackets.cpp index 343384dde6..438243bdac 100644 --- a/src/game/WorldHandlers/SpellPackets.cpp +++ b/src/game/WorldHandlers/SpellPackets.cpp @@ -77,6 +77,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/SpellTargetList.cpp b/src/game/WorldHandlers/SpellTargetList.cpp index 74ab4a9ebc..f901383401 100644 --- a/src/game/WorldHandlers/SpellTargetList.cpp +++ b/src/game/WorldHandlers/SpellTargetList.cpp @@ -77,6 +77,7 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /** diff --git a/src/game/WorldHandlers/SpellTargeting.cpp b/src/game/WorldHandlers/SpellTargeting.cpp index ab28bd220f..3cf7c5c86c 100644 --- a/src/game/WorldHandlers/SpellTargeting.cpp +++ b/src/game/WorldHandlers/SpellTargeting.cpp @@ -77,6 +77,9 @@ #include "DisableMgr.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include +#include +#include #endif /* ENABLE_ELUNA */ template diff --git a/src/game/WorldHandlers/TaxiHandler.cpp b/src/game/WorldHandlers/TaxiHandler.cpp index fac80304bc..cfcb154fcd 100644 --- a/src/game/WorldHandlers/TaxiHandler.cpp +++ b/src/game/WorldHandlers/TaxiHandler.cpp @@ -32,6 +32,7 @@ #include "Player.h" #include "Path.h" #include "WaypointMovementGenerator.h" +#include /** * @brief Handles a client request for the known status of a taxi node. diff --git a/src/game/WorldHandlers/TransportSystem.cpp b/src/game/WorldHandlers/TransportSystem.cpp index 27b8343c35..f565f233da 100644 --- a/src/game/WorldHandlers/TransportSystem.cpp +++ b/src/game/WorldHandlers/TransportSystem.cpp @@ -39,6 +39,10 @@ #include "GameTime.h" #include "MapManager.h" +#include +#include +#include + /* **************************************** TransportBase ****************************************/ TransportBase::TransportBase(WorldObject* owner) : @@ -90,11 +94,38 @@ void TransportBase::UpdateGlobalPositions() m_sinO = sin(pos.o); m_cosO = cos(pos.o); - // Update global positions + // Over a SNAPSHOT, and this is not defensive habit -- the live map cannot survive the walk. + // + // Refreshing a GRID-RESIDENT passenger (a pet) goes out through Map::CreatureRelocation, + // and the grid is entitled to REFUSE it: the deck may have carried it into a cell that will + // not take it. When that happens the minion steps off the boat and unboards itself + // (Map::CreatureRespawnRelocation) -- erasing, re-entrantly, the very element this loop is + // standing on. Iterating m_passengers directly would then walk a dead iterator. + // + // Crew never do this: their refresh is a plain Relocate that touches no grid at all. But + // they share the loop, so the loop has to be safe for the passenger that does. + std::vector > refresh; + refresh.reserve(m_passengers.size()); + for (PassengerMap::const_iterator itr = m_passengers.begin(); itr != m_passengers.end(); ++itr) { - UpdateGlobalPositionOf(itr->first, itr->second->GetLocalPositionX(), itr->second->GetLocalPositionY(), - itr->second->GetLocalPositionZ(), itr->second->GetLocalOrientation()); + refresh.push_back(std::make_pair(itr->first, + Position(itr->second->GetLocalPositionX(), + itr->second->GetLocalPositionY(), + itr->second->GetLocalPositionZ(), + itr->second->GetLocalOrientation()))); + } + + for (std::pair const& entry : refresh) + { + // An earlier iteration's relocation may have put this one ashore. + if (m_passengers.find(entry.first) == m_passengers.end()) + { + continue; + } + + UpdateGlobalPositionOf(entry.first, entry.second.x, entry.second.y, + entry.second.z, entry.second.o); } m_lastPosition = pos; @@ -121,8 +152,6 @@ void TransportBase::UpdateGlobalPositionOf(WorldObject* passenger, float lx, flo float gx, gy, gz, go; CalculateGlobalPositionOf(lx, ly, lz, lo, gx, gy, gz, go); - TransportInfo const* info = passenger->GetTransportInfo(); - // The client parents a unit to a vessel from that unit's MOVEMENT INFO and nothing else: // MovementInfo::Write emits the transport block only when MOVEFLAG_ONTRANSPORT is set. // A server-driven passenger never sets it on its own -- only a player's own client does, @@ -133,8 +162,8 @@ void TransportBase::UpdateGlobalPositionOf(WorldObject* passenger, float lx, flo // // The deck offset is the authoritative coordinate for these passengers (the world one is // a derived cache -- see the comment above), so it is what gets stamped. This covers a - // boarded MINION too: a pet is a grid citizen for the server's bookkeeping, but to the - // client it is just as much on the deck as the crew are, and must ride with it. + // boarded MINION exactly as it covers the crew: to the client a pet is just as much on + // the deck as a deckhand, and must ride with the ship. if (passenger->isType(TYPEMASK_UNIT)) { Unit* boarded = static_cast(passenger); @@ -144,25 +173,24 @@ void TransportBase::UpdateGlobalPositionOf(WorldObject* passenger, float lx, flo GameTime::GetGameTimeMS()); } - // A MINION -- a pet, a guardian -- belongs to the world and is only standing on our - // floor. It is still in a grid cell, it is still found by grid searchers, it still - // fights things. So its world position has to keep being maintained THROUGH the grid: - // relocate it properly, or the ship sails out of its cell and its cell membership -- - // and therefore its visibility, and Map::Remove's idea of where to find it -- goes - // stale and stays stale. - if (info && info->IsGridResident() && passenger->GetTypeId() == TYPEID_UNIT) - { - m_owner->GetMap()->CreatureRelocation(static_cast(passenger), gx, gy, gz, go); - return; - } - - // CREW. Not in any grid, so this is a pure cache write: it stamps the world token that - // an off-ship distance check needs and touches nothing else. CreatureRelocation here - // would be actively wrong -- see the comment above the function. + // A CREW member and a boarded MINION are treated identically here, and that is the + // whole point of the ship-local model: neither is in a world grid cell, so this is a + // pure cache write. It stamps the world token that an off-ship distance check needs -- + // and that the pet subsystem's eventual remove-list pass needs to find the creature in + // a loaded cell (the token tracks the ship, i.e. where the master is standing, which is + // always loaded) -- and touches nothing else. + // + // It deliberately does NOT call Map::CreatureRelocation. A boarded creature is not a + // citizen of the world grid; it is a passenger of the vessel's own container, and + // relocating it through the world grid was exactly the "composed world position" that + // made a moving ship churn the pet's cell membership and blink it out of sight. // - // (A player aboard is not one of these passengers at all: it stays a normal grid - // citizen, the client is authoritative for where it stands, and it tells us both - // coordinate systems in every movement packet. We never move it from here.) + // (A player aboard is not one of these passengers. The player's own client is the sole + // authority for where the player stands; the server relays that when the player moves, + // and while the player is STILL there is nothing to relay -- the client keeps drawing it + // on the deck through the transport parent, needing no help from us. Either way we never + // server-relocate the player from here. The player stays a grid citizen only so it can + // SEE and interact with the world; being seen is the ship's job, like any passenger.) passenger->Relocate(gx, gy, gz, go); } @@ -213,9 +241,9 @@ void TransportBase::UnBoardAllPassengers() } void TransportBase::BoardPassenger(WorldObject* passenger, float lx, float ly, float lz, float lo, - bool gridResident) + bool minion) { - TransportInfo* transportInfo = new TransportInfo(passenger, this, lx, ly, lz, lo, gridResident); + TransportInfo* transportInfo = new TransportInfo(passenger, this, lx, ly, lz, lo, minion); // Insert our new passenger m_passengers.insert(PassengerMap::value_type(passenger, transportInfo)); @@ -256,11 +284,11 @@ void TransportBase::UnBoardPassenger(WorldObject* passenger) /* **************************************** TransportInfo ****************************************/ TransportInfo::TransportInfo(WorldObject* owner, TransportBase* transport, float lx, float ly, float lz, float lo, - bool gridResident) : + bool minion) : m_owner(owner), m_transport(transport), m_localPosition(lx, ly, lz, lo), - m_gridResident(gridResident) + m_minion(minion) { MANGOS_ASSERT(owner && m_transport); } diff --git a/src/game/WorldHandlers/TransportSystem.h b/src/game/WorldHandlers/TransportSystem.h index 687d1128d9..71a225ccbf 100644 --- a/src/game/WorldHandlers/TransportSystem.h +++ b/src/game/WorldHandlers/TransportSystem.h @@ -70,20 +70,24 @@ class TransportBase // is its authoritative one; the world position it also carries is a derived cache // (see UpdateGlobalPositionOf) and must never be treated as the truth. // - // `gridResident` says WHICH KIND of passenger this is, and the difference is real: + // `minion` records WHICH KIND of passenger this is. Both kinds are the same to the + // vessel -- it ticks them, broadcasts them and collides them, and both are in the + // vessel's own container rather than any world grid cell (the ship IS their grid). + // The flag exists only for the ONE thing that differs: lifecycle ownership. // - // false -- CREW. It belongs to the vessel. It is in no grid cell, the vessel - // ticks it and the vessel broadcasts it, and its world position is a - // pure cache that nothing but an off-ship distance check ever reads. + // false -- CREW. Ship-owned. Spawned from `creature_transport`, lives and dies + // with the vessel. // - // true -- A MINION (a pet, a guardian). It belongs to the WORLD and merely - // stands on our floor: it fights, it is targeted by grid searchers, it - // is its master's. It stays a grid citizen, so its world position must - // keep being maintained THROUGH THE GRID -- otherwise its cell - // membership goes stale the moment the ship sails out of it. + // true -- A MINION (a pet, a guardian, a totem). PLAYER-owned. It rides while its + // master is aboard and goes ashore with him; its summon/dismiss/death is + // the pet subsystem's business, not ours. The flag lets the vessel tell + // the two apart when it reconciles who should still be aboard. void BoardPassenger(WorldObject* passenger, float lx, float ly, float lz, float lo, - bool gridResident = false); - void UnBoardPassenger(WorldObject* passenger); + bool minion = false); + // Virtual so the MO-transport override can unlink a boarded creature from the + // vessel's container as it leaves -- see Transport::UnBoardPassenger. This is what + // makes death/dismiss/logout detach cleanly, since they all route through here. + virtual void UnBoardPassenger(WorldObject* passenger); void UnBoardAllPassengers(); PassengerMap const& GetPassengers() const { return m_passengers; } @@ -119,7 +123,7 @@ class TransportInfo { public: explicit TransportInfo(WorldObject* owner, TransportBase* transport, float lx, float ly, float lz, float lo, - bool gridResident = false); + bool minion = false); // Set local positions void SetLocalPosition(float lx, float ly, float lz, float lo); @@ -129,9 +133,10 @@ class TransportInfo TransportBase* GetTransportBase() const { return m_transport; } ObjectGuid GetTransportGuid() const { return m_transport->GetOwner()->GetObjectGuid(); } - /// True for a passenger that is still a citizen of the world grid (a pet), false - /// for one that belongs to the vessel alone (crew). See BoardPassenger. - bool IsGridResident() const { return m_gridResident; } + /// True for a PLAYER-owned minion (pet/guardian/totem), false for ship-owned crew. + /// Both ride the vessel identically; this only distinguishes lifecycle. See + /// BoardPassenger. + bool IsMinion() const { return m_minion; } // Get local position float GetLocalOrientation() const { return m_localPosition.o; } @@ -150,7 +155,7 @@ class TransportInfo WorldObject* m_owner; ///< Passenger TransportBase* m_transport; ///< Transporter Position m_localPosition; - bool m_gridResident; ///< Pet (world's) vs crew (vessel's) + bool m_minion; ///< Player-owned minion vs ship-owned crew }; #endif diff --git a/src/game/WorldHandlers/Transports.cpp b/src/game/WorldHandlers/Transports.cpp index aec997de5a..5df113a4f7 100644 --- a/src/game/WorldHandlers/Transports.cpp +++ b/src/game/WorldHandlers/Transports.cpp @@ -35,10 +35,12 @@ #include "ObjectMgr.h" #include "ObjectGuid.h" #include "Path.h" +#include "Pet.h" #include "Player.h" #include "GameTime.h" #include "TransportCrewSearch.h" #include "World.h" +#include "movement/MoveSpline.h" #include "WorldPacket.h" #include "DBCStores.h" @@ -48,6 +50,9 @@ #include #include #include +#include +#include +#include /** * @brief Loads and initializes all configured global transports. */ @@ -188,6 +193,33 @@ namespace constexpr float DECK_BOARD_SEARCH_UP = 4.0f; constexpr float DECK_BOARD_SEARCH_DOWN = 8.0f; + /// Bearings tried when looking for somewhere to set a minion down next to its master: + /// the one asked for, then a sweep around them. A deck is small and full of bulkheads, + /// so the first choice is often out over the rail. + constexpr uint32 DECK_SPOT_ANGLES = 8; + + /// Chest height for that spot's obstruction probe, so the deck the master is standing + /// ON is not itself read as the thing between them. + constexpr float DECK_SPOT_PROBE_HEIGHT = 1.0f; + + /** + * @brief A totem is PLANTED, not a follower -- and that makes it the one minion the + * boarding rules must leave alone. + * + * It belongs to the square it was dropped on. It comes aboard only by being dropped on + * the deck (where the ordinary board picks it up); it is never CARRIED aboard after a + * master who sailed without it, and -- the case that actually bites -- it is never + * carried ashore after one who left it behind either. A totem whose shaman steps onto + * the pier goes on standing on the deck and sails with the ship until it expires, which + * is what it does on retail. Death or despawn is what takes it off the boat, and + * Creature::RemoveFromWorld already unboards it there. + */ + bool IsPlanted(Unit const* minion) + { + return minion->GetTypeId() == TYPEID_UNIT && + static_cast(minion)->IsTotem(); + } + /** * @brief A player within range of a vessel. * @@ -222,6 +254,28 @@ namespace return false; } + // A passenger is not NEAR the vessel. It is ON it, at zero distance, and it + // stays there whatever the two world positions below happen to say -- so the + // question is never asked of them. + // + // Asking it is a slow disaster. BOTH those positions are fictions when a + // player stands on a deck. The player's is client-authoritative, so it + // FREEZES the moment they stop moving and sending packets. The hull's is only + // real while ObservePose is being fed -- and it is fed by those same packets, + // so it goes stale two seconds later and Update falls back to hopping the + // hull from node to node down the path. One is frozen and the other sails on: + // the gap between them grows without bound and eventually exceeds ANY radius. + // + // At that instant the vessel drops the player from m_observers and sends them + // an out-of-range block for the ship they are standing on. Their client + // deletes the boat under their feet -- and the crew and their pet with it -- + // and they fall in the sea. Standing still on a boat is not an edge case; it + // is what everybody does on a boat. + if (player->GetTransport() == m_vessel) + { + return true; + } + const float dx = player->GetPositionX() - m_vessel->GetPositionX(); const float dy = player->GetPositionY() - m_vessel->GetPositionY(); @@ -585,9 +639,9 @@ uint32 MaNGOS::GatherCrewContainersNear(Map* map, float x, float y, float radius break; } - if (!vessel->HasCrew() || vessel->GetMapId() != map->GetId()) + if (!vessel->HasBoardedCreatures() || vessel->GetMapId() != map->GetId()) { - continue; + continue; // nothing on this deck to find } // The HULL, not the origin: a ship is a hundred yards long, so a blast at the bow @@ -643,6 +697,172 @@ void Transport::UpdateCrew(uint32 diff) crew->Update(diff, diff); } } + + // A boarded minion is in no world cell, so the grid's ObjectUpdater will never tick it + // either -- the ship is its map, and the ship must run its update loop (AI, spline, auras) + // exactly as it does the crew's, or the pet would be a statue on the deck. + for (PassengerMap::value_type const& entry : GetPassengers()) + { + if (!entry.second->IsMinion()) + { + continue; + } + + Creature* minion = static_cast(entry.first); + if (minion->IsInWorld()) + { + minion->Update(diff, diff); + } + } +} + +Transport* Transport::VesselOf(WorldObject const& obj) +{ + if (TransportInfo const* info = obj.GetTransportInfo()) + { + WorldObject* vessel = info->GetTransport(); + + if (vessel && vessel->GetTypeId() == TYPEID_GAMEOBJECT) + { + return static_cast(vessel); + } + + return NULL; + } + + if (obj.GetTypeId() == TYPEID_PLAYER) + { + return static_cast(obj).GetTransport(); + } + + return NULL; +} + +std::optional Transport::LocalPositionOf(WorldObject const& obj) const +{ + if (TransportInfo const* info = obj.GetTransportInfo()) + { + if (info->GetTransport() != this) + { + return std::nullopt; // aboard a DIFFERENT vessel + } + + return Position(info->GetLocalPositionX(), info->GetLocalPositionY(), + info->GetLocalPositionZ(), info->GetLocalOrientation()); + } + + // A player carries no TransportInfo: it tells us its own deck offset in every packet. + if (obj.GetTypeId() == TYPEID_PLAYER) + { + Player const& player = static_cast(obj); + + if (player.GetTransport() == this) + { + Position const* t = player.m_movementInfo.GetTransportPos(); + return Position(t->x, t->y, t->z, t->o); + } + } + + return std::nullopt; +} + +std::optional Transport::DeckSpotNear(WorldObject const& master, float distance2d, + float angle) const +{ + if (!HasDeck()) + { + return std::nullopt; + } + + const auto anchor = LocalPositionOf(master); + if (!anchor) + { + return std::nullopt; // not aboard this vessel + } + + for (uint32 step = 0; step < DECK_SPOT_ANGLES; ++step) + { + const float bearing = anchor->o + angle + + (2 * M_PI_F * float(step) / float(DECK_SPOT_ANGLES)); + + const float lx = anchor->x + distance2d * cos(bearing); + const float ly = anchor->y + distance2d * sin(bearing); + + // Sought from the MASTER's height: the spot is a step away from where it is + // standing, so the deck under it is the deck under them, not whatever lies six + // yards below in the hold. + const auto lz = DeckHeightAt(lx, ly, anchor->z, + DECK_BOARD_SEARCH_UP, DECK_BOARD_SEARCH_DOWN); + if (!lz) + { + continue; // out over the rail + } + + // ...and nothing solid in between, or we would set a pet down on the far side of a + // bulkhead from the master it is supposed to be heeling. + if (IsDeckBlocked(Geometry::Vector3(anchor->x, anchor->y, anchor->z + DECK_SPOT_PROBE_HEIGHT), + Geometry::Vector3(lx, ly, *lz + DECK_SPOT_PROBE_HEIGHT))) + { + continue; + } + + return Position(lx, ly, *lz, anchor->o); + } + + // Every bearing around them was over the rail or behind a bulkhead -- a master wedged + // into a corner of the forecastle. Their OWN square is known good, because they are + // standing on it. A minion briefly in its master's boots is odd; a minion left on the + // pier is broken. + return Position(anchor->x, anchor->y, anchor->z, anchor->o); +} + +void Transport::BoardMinionAt(Unit* minion, float lx, float ly, float lz, float lo) +{ + // A boarded minion is a PASSENGER of the vessel, exactly like a crew member -- the ship + // is its grid. The `true` marks it player-owned (a pet, not crew), which only matters to + // the lifecycle reconciliation in UpdateMinions; to everything else the two are the same. + BoardPassenger(minion, lx, ly, lz, lo, true); + + // Stamp the world cache once, so an off-ship searcher (and the pet subsystem's eventual + // remove-list pass) has a sane world token before the first tick. This ALSO sets + // MOVEFLAG_ONTRANSPORT + the deck offset on its movement info, which is what tells the + // client it belongs to the ship. + UpdateGlobalPositionOf(minion, lx, ly, lz, lo); + + // THE ONE MOVE that makes it ship-local: take its GridReference out of the world cell it + // was standing in and link it into the vessel's own container. From here it is in no map + // cell -- the ship ticks it, the ship broadcasts it, and a grid search near the vessel + // finds it through m_crewMap. Nothing composes a world position for it any more, and so + // nothing can blink it out when the ship's pose drifts. (link() unlinks from the old + // container and links to the new one in one step.) + static_cast(minion)->GetGridRef().link(&m_crewMap, static_cast(minion)); + + // Its motion was planned in the world frame; none of that survives the change of frame. + // Start it over -- the next leg its AI lays goes out as SMSG_MONSTER_MOVE_TRANSPORT, + // which is what parents it to the ship at every client. + // + // (A totem never moves, so this costs it nothing -- but it still needs the frame, or its + // Z would be resolved against the sea floor and it would sink out of the deck.) + minion->GetMotionMaster()->Initialize(); + + DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, "Transport %s boarded minion %s at deck (%f, %f, %f)", + GetName(), minion->GetGuidStr().c_str(), lx, ly, lz); +} + +void Transport::UnBoardPassenger(WorldObject* passenger) +{ + // A boarded minion lives in the vessel's container (m_crewMap); take it out before the + // base class forgets it is a passenger, or the container would keep a pointer to a + // creature that is leaving the world. Crew are unlinked by UnBoardCreature instead, so + // only act on a minion here -- and only while it is actually linked. + TransportInfo* info = passenger->GetTransportInfo(); + + if (info && info->IsMinion() && passenger->GetTypeId() == TYPEID_UNIT) + { + static_cast(passenger)->GetGridRef().unlink(); + } + + TransportBase::UnBoardPassenger(passenger); } void Transport::BoardMinion(Unit* minion) @@ -653,32 +873,125 @@ void Transport::BoardMinion(Unit* minion) } // Where the minion is standing, expressed as a deck offset. The vessel's pose was - // solved from its master's last movement packet, so this is exact -- and if the minion - // is not actually over the deck yet (still on the pier, mid-follow), the drop fails and - // we simply try again next tick, once it has caught up. + // solved from its master's last movement packet, so this is exact. float lx, ly, lz, lo; CalculateLocalPositionOf(minion->GetPositionX(), minion->GetPositionY(), minion->GetPositionZ(), minion->GetOrientation(), lx, ly, lz, lo); - const auto deckZ = DeckHeightAt(lx, ly, lz, DECK_BOARD_SEARCH_UP, DECK_BOARD_SEARCH_DOWN); - if (!deckZ) + // Already over the deck: it was summoned here (CreatureCreatePos puts a summon on the + // deck when its summoner is aboard), or it is crossing a gangplank of a docked ship. + // Board it where it stands. + if (const auto deckZ = DeckHeightAt(lx, ly, lz, DECK_BOARD_SEARCH_UP, DECK_BOARD_SEARCH_DOWN)) { - return; // not over the deck + BoardMinionAt(minion, lx, ly, *deckZ, lo); + return; } - // Grid resident: it keeps its cell, and its world position keeps being maintained - // through the grid. All we lend it is a floor. - BoardPassenger(minion, lx, ly, *deckZ, lo, true); + HaulMinionAboard(minion); +} - // Its motion is now expressed in our frame, so whatever it had planned in the world's - // frame is meaningless. Start it over. - // - // (A totem never moves, so this costs it nothing -- but it still needs the frame, or - // its Z would be resolved against the sea floor and it would sink out of the deck.) - minion->GetMotionMaster()->Initialize(); +void Transport::HaulMinionAboard(Unit* minion) +{ + // A shaman who drops a totem on the pier and then sails has left it on the pier, which + // is exactly right. See IsPlanted. + if (IsPlanted(minion)) + { + return; + } - DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, "Transport %s boarded minion %s at deck (%f, %f, %f)", - GetName(), minion->GetGuidStr().c_str(), lx, ly, *deckZ); + Unit* master = minion->GetOwner(); + if (!master) + { + return; + } + + // Let it RUN to the margin first, exactly as it does on retail. While it still has a + // leg under it, it is making its own way and yanking it off its feet mid-stride would + // read as a bug. We take over only once it has come to rest -- which, ashore of a ship, + // is the moment its route out across the water was refused and it stopped dead. + if (!minion->movespline->Finalized()) + { + return; + } + + const auto spot = DeckSpotNear(*master, PET_FOLLOW_DIST + minion->GetObjectBoundingRadius() + + master->GetObjectBoundingRadius(), PET_FOLLOW_ANGLE); + if (!spot) + { + return; // its master is not aboard after all + } + + // Board FIRST. From this instant its movement info carries the deck offset, so the + // heartbeat below tells the client not merely where it went but WHAT it is now standing + // on -- MovementInfo::Write emits the transport block off MOVEFLAG_ONTRANSPORT, and + // BoardMinionAt has just set it. The other order would announce a world position and + // leave the pet unparented until its next leg. + BoardMinionAt(minion, spot->x, spot->y, spot->z, spot->o); + + // The blink the player actually sees. (BoardMinionAt has already re-filed it in the + // grid at the deck's world position; this is what tells the clients watching.) + minion->SendHeartBeat(); + + DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, "Transport %s hauled minion %s aboard to %s", + GetName(), minion->GetGuidStr().c_str(), master->GetGuidStr().c_str()); +} + +void Transport::PutMinionAshore(Unit* minion) +{ + Unit* master = minion->GetOwner(); + + // Step off the vessel: out of its container (the override unlinks the GridReference) and + // out of its frame. From here the minion is a free creature with no cell. + UnBoardPassenger(minion); + + // Dead, dismissed, or its master has left the map: it is on its way out and there is no + // heel to return it to. Leave it for the pet subsystem's remove list, which finds it by + // its world cache (parked where the ship is -- a cell the master kept loaded). Its motion + // is reset only so nothing tries to walk a leg it planned on the deck. + if (!minion->IsInWorld() || !minion->IsAlive() || !master || !master->IsInWorld() || + master->GetMapId() != minion->GetMapId()) + { + minion->GetMotionMaster()->Initialize(); + return; + } + + // Master stepped straight from our deck onto ANOTHER vessel. Do not drop the minion into + // the water between the two hulls: return it to the world where it stands, and that + // vessel's UpdateMinions will haul it over on its next tick. + if (Transport::VesselOf(*master)) + { + ReturnMinionToWorld(minion, minion->GetPositionX(), minion->GetPositionY(), + minion->GetPositionZ(), minion->GetOrientation()); + return; + } + + // Ashore for real. Put it back into the world grid at its master's heel, on dry ground + // the world frame resolves exactly as it always did for a pet on land. + float x, y, z; + master->GetClosePoint(x, y, z, minion->GetObjectBoundingRadius(), + PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + + ReturnMinionToWorld(minion, x, y, z, master->GetOrientation()); +} + +void Transport::ReturnMinionToWorld(Unit* minion, float x, float y, float z, float o) +{ + Creature* c = static_cast(minion); + Map* map = c->GetMap(); + + // The minion is unlinked from the vessel and still in the object store, but in no cell. + // Take it out of the store and re-add it to the world grid at (x,y,z): Map::Add re-files + // it into the proper cell, restores its ordinary world-grid visibility (a create to the + // players around it) and hands its tick back to the grid. This is the exact inverse of + // the GridReference relink that boarded it -- and it does NOT depend on the creature's + // stale current-cell, which is why it is used instead of a plain relocation. + c->RemoveFromWorld(); + c->Relocate(x, y, z, o); + map->Add(c); + + // World frame again; whatever it had planned on the deck is meaningless. The next leg it + // lays goes out as an ordinary (non-transport) monster-move. + c->GetMotionMaster()->Initialize(); } void Transport::UpdateMinions() @@ -706,12 +1019,20 @@ void Transport::UpdateMinions() for (PassengerMap::value_type const& entry : GetPassengers()) { - if (!entry.second->IsGridResident()) + if (!entry.second->IsMinion()) { continue; // crew: ours, and staying } Unit* minion = static_cast(entry.first); + + // A totem rides the deck it was planted on for as long as it lives, whoever its + // master is and wherever they have got to. See IsPlanted. + if (IsPlanted(minion)) + { + continue; + } + Unit* master = minion->GetOwner(); const bool masterAboard = @@ -726,8 +1047,7 @@ void Transport::UpdateMinions() for (Unit* minion : leaving) { - UnBoardPassenger(minion); - minion->GetMotionMaster()->Initialize(); + PutMinionAshore(minion); } } @@ -1052,9 +1372,112 @@ void Transport::MoveToNextWayPoint() * @param y The destination Y coordinate. * @param z The destination Z coordinate. */ +void Transport::UnBoardAllMinions() +{ + // Snapshot first: ReturnMinionToWorld erases each minion from the passenger map, so we + // must not be iterating it while that happens. + std::vector minions; + + for (PassengerMap::value_type const& entry : GetPassengers()) + { + if (entry.second->IsMinion()) + { + minions.push_back(static_cast(entry.first)); + } + } + + // Called at the START of TeleportTransport, before the master is teleported -- so the + // master (and thus a loaded cell) is still here on the OLD map. Hand each minion back to + // THIS map's grid where it stands: a proper world creature again, which the master's + // imminent far-teleport then unsummons the ordinary way. Left merely unlinked instead, a + // minion would be in limbo when the ship changes map out from under it. + for (Unit* minion : minions) + { + UnBoardPassenger(minion); + ReturnMinionToWorld(minion, minion->GetPositionX(), minion->GetPositionY(), + minion->GetPositionZ(), minion->GetOrientation()); + } +} + +void Transport::JumpWithinMap(float x, float y, float z) +{ + // The hull moves. That is the whole event. + Relocate(x, y, z); + + // The crew and the boarded minions do not move AT ALL: their deck offsets are unchanged, + // and the deck offset is the only coordinate they really have. This refreshes nothing but + // their world CACHE (a plain Relocate -- no grid, no packets), so an off-ship distance + // check still gets a sane answer on the far side of the jump. + UpdateGlobalPositions(); + + // The players are the only passengers the world grid knows about, so the grid has to be + // told which cells they are in now -- otherwise they stay filed beside the port we just + // left, and every search, aggro and spell around them would look in the wrong place. + // + // But this is BOOKKEEPING, not placement. PlayerRelocation moves the cell and refreshes + // visibility; it sends the client nothing. We deliberately do NOT teleport the player and + // do NOT hand it a composed world position: it is still MOVEFLAG_ONTRANSPORT with the same + // deck offset it had a moment ago, and its own client -- which is drawing the vessel jump + // from the very same taxi path we just walked -- puts it back on the deck itself. + for (Player* passenger : m_playerPassengers) + { + if (!passenger || !passenger->IsInWorld()) + { + continue; + } + + const auto local = LocalPositionOf(*passenger); + if (!local) + { + continue; // not really aboard; leave it alone + } + + float gx, gy, gz, go; + CalculateGlobalPositionOf(local->x, local->y, local->z, local->o, gx, gy, gz, go); + + GetMap()->PlayerRelocation(passenger, gx, gy, gz, go); + } + + DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, + "Transport %s jumped within map to (%f, %f, %f); %u player(s) carried", + GetName(), x, y, z, uint32(m_playerPassengers.size())); +} + void Transport::TeleportTransport(uint32 newMapid, float x, float y, float z) { Map const* oldMap = GetMap(); + + // A teleport waypoint that stays on the SAME map (the Auberdine <-> Rut'theran run has + // one) is not a map transfer at all. Nothing leaves the ship, nothing is teleported, and + // no world position is composed for anybody. See JumpWithinMap. + if (newMapid == GetMapId()) + { + JumpWithinMap(x, y, z); + return; + } + + // THE MINIONS DO NOT COME WITH US, and letting go of them must happen BEFORE the vessel + // changes map -- not after, and not never, which is what used to happen. + // + // A crew member belongs to the vessel, so MoveCrewToMap carries its registration across + // the seam by hand. A MINION belongs to the WORLD -- and specifically to the OLD one: it + // is in the old map's object store and filed in one of the old map's grid cells, and + // nothing here is going to move any of that. + // + // So the instant SetMap runs, the vessel's own refresh (MoveCrewToMap -> + // UpdateGlobalPositions) hands a creature that still lives on the old map to the NEW map's + // CreatureRelocation, with coordinates on another continent. The new map was created a + // moment ago and has no grids to speak of, so it cannot find a cell for the thing and + // refuses it -- which sends it to CreatureRespawnRelocation, which for a boarded minion + // comes straight back here. That recursion does not terminate; it overflows the stack and + // takes the server with it. + // + // Nor is there anything to salvage by carrying them: the minions are their masters' + // problem, and their masters are being teleported on the line below. A far teleport + // unsummons a pet and resummons it on the far side, which is exactly what should happen. + // All the vessel has to do is let go. + UnBoardAllMinions(); + Relocate(x, y, z); for (PlayerSet::iterator itr = m_playerPassengers.begin(); itr != m_playerPassengers.end();) @@ -1279,6 +1702,26 @@ void Transport::AppendCrewCreateBlocks(UpdateData& data, Player* observer) crew->BuildCreateUpdateBlockForPlayer(&data, observer); observer->m_clientGUIDs.insert(crew->GetObjectGuid()); } + + // A boarded minion is in no grid cell either -- the ship is its grid -- so it too is + // announced only here. (A new observer coming into range of the vessel thus receives the + // pet on the deck in the same packet as the crew, parented to the ship.) + for (PassengerMap::value_type const& entry : GetPassengers()) + { + if (!entry.second->IsMinion()) + { + continue; + } + + Creature* minion = static_cast(entry.first); + if (!minion->IsInWorld()) + { + continue; + } + + minion->BuildCreateUpdateBlockForPlayer(&data, observer); + observer->m_clientGUIDs.insert(minion->GetObjectGuid()); + } } void Transport::AddObserver(Player* observer) @@ -1295,6 +1738,15 @@ void Transport::RemoveObserver(Player* observer) { observer->m_clientGUIDs.erase(crew->GetObjectGuid()); } + + // Boarded minions ride in the same packet as the crew, so they are forgotten the same way. + for (PassengerMap::value_type const& entry : GetPassengers()) + { + if (entry.second->IsMinion()) + { + observer->m_clientGUIDs.erase(entry.first->GetObjectGuid()); + } + } } /** @@ -1325,6 +1777,19 @@ void Transport::RetainAtClient(Player* observer, GuidSet& clientGuids) const { clientGuids.erase(crew->GetObjectGuid()); } + + // A boarded minion is a passenger with no world cell either, so the elimination sweep + // would destroy it for exactly the same reason it would destroy the crew. Vouch for it + // too. THIS is what stops a pet blinking out when its master stands still: its visibility + // no longer depends on the grid re-finding a composed world position, only on the ship + // saying "he is on my deck." + for (PassengerMap::value_type const& entry : GetPassengers()) + { + if (entry.second->IsMinion()) + { + clientGuids.erase(entry.first->GetObjectGuid()); + } + } } /** @@ -1383,10 +1848,10 @@ void Transport::UpdateVisibility(uint32 diff) // one person guaranteed to be looking straight at them. const bool aboard = (this == observer->GetTransport()); - if (aboard && m_crew.empty()) + if (aboard && GetPassengers().empty()) { m_observers.insert(observer->GetObjectGuid()); - continue; + continue; // nothing aboard to announce to them } UpdateData transData; @@ -1425,6 +1890,13 @@ void Transport::UpdateVisibility(uint32 diff) { crew->BuildOutOfRangeUpdateBlock(&transData); } + for (PassengerMap::value_type const& entry : GetPassengers()) + { + if (entry.second->IsMinion()) + { + entry.first->BuildOutOfRangeUpdateBlock(&transData); + } + } WorldPacket packet; transData.BuildPacket(&packet, true); @@ -1435,6 +1907,13 @@ void Transport::UpdateVisibility(uint32 diff) { gone->m_clientGUIDs.erase(crew->GetObjectGuid()); } + for (PassengerMap::value_type const& entry : GetPassengers()) + { + if (entry.second->IsMinion()) + { + gone->m_clientGUIDs.erase(entry.first->GetObjectGuid()); + } + } } itr = m_observers.erase(itr); diff --git a/src/game/WorldHandlers/Transports.h b/src/game/WorldHandlers/Transports.h index 87fbc12cb7..81f3a179be 100644 --- a/src/game/WorldHandlers/Transports.h +++ b/src/game/WorldHandlers/Transports.h @@ -121,6 +121,18 @@ class Transport : public GameObject, public TransportBase CreatureMapType& GetCrewMap() { return m_crewMap; } bool HasCrew() const { return !m_crew.empty(); } + /// True when the vessel is carrying ANYTHING a grid search should find on its deck -- + /// crew or a boarded minion. Both live in m_crewMap, and both are passengers, so the + /// passenger list is the honest test. Drives GatherCrewContainersNear: a vessel with + /// only a player's pet aboard (no NPC crew) must still be searched. + bool HasBoardedCreatures() const { return !GetPassengers().empty(); } + + /// Override so a boarded creature is unlinked from the vessel's container (m_crewMap) + /// as it leaves -- by ANY route. Death, dismiss and logout all reach here through + /// Creature::RemoveFromWorld, so this one hook keeps the container from ever holding + /// a freed pointer, the same guarantee UnBoardCreature gives the crew. + void UnBoardPassenger(WorldObject* passenger) override; + /** * @brief Keep every player passenger's MINIONS aboard with them -- pet, mini-pet, * guardians, totems. @@ -142,9 +154,62 @@ class Transport : public GameObject, public TransportBase */ void UpdateMinions(); - /// Board one minion, if it is over the deck. See UpdateMinions. + /** + * @brief Get one minion aboard -- by BOARDING it if it is standing over the deck, + * and by HAULING it aboard if it is not. + * + * The haul is not a shortcut, it is the only way aboard, and retail does exactly + * the same thing. A pet ashore follows its master in the WORLD frame, so once the + * master is on the deck the pet's goal is the master's world position -- a point + * over open water. There is no navmesh there, the route fails, and a follow leg + * (MOVE_REQUIRE_PATH) is refused outright: no leg is laid and the pet stops dead. + * + * It can therefore never WALK onto a deck, and a rule that only boards what is + * already standing on one would leave every pet in the game on the pier forever. + * So the pet runs to the margin under its own power, and when it can get no + * further we pick it up and put it at its master's heel. That is what the player + * sees on retail, and it is what they see here. + */ void BoardMinion(Unit* minion); + /** + * @brief The deck offset of anything standing on this vessel. Nothing when it is + * not aboard this one. + * + * There are TWO ways to be on a ship and both have to be read here: + * + * * a CREW member or a boarded MINION carries a TransportInfo, and the offset we + * maintain in it is the truth; + * + * * a PLAYER carries none. It rides on Player::m_transport plus the deck offset + * its own client sends in every movement packet -- which is better than + * anything we could compute, the client being authoritative for where it + * stands. So we read the number it gave us. + * + * Missing the second case would place a pet by converting its master's WORLD + * position through a vessel pose we are only ESTIMATING, when the exact answer was + * sitting in the packet all along. + */ + std::optional LocalPositionOf(WorldObject const& obj) const; + + /// The vessel `obj` is standing on -- as a passenger (its TransportInfo) or as a + /// player (its Player::m_transport) -- or NULL when it is standing on none. The + /// two-ways-to-be-aboard rule of LocalPositionOf, asked as a yes/no. + static Transport* VesselOf(WorldObject const& obj); + + /** + * @brief A spot on the deck `distance2d` yards from `master` at its own facing plus + * `angle`, in DECK coordinates. + * + * Where a summoned or hauled minion is put. The requested bearing is tried first + * and then swept around the master, because a deck is small and cluttered and the + * one spot the caller asked for is very often out over the rail. + * + * @return Nothing when `master` is not aboard this vessel, or it has no deck. + */ + std::optional DeckSpotNear(WorldObject const& master, float distance2d, + float angle) const; + /** * @brief RECOVER THE VESSEL'S TRUE POSE FROM A PLAYER STANDING ON IT. * @@ -275,6 +340,24 @@ class Transport : public GameObject, public TransportBase private: void TeleportTransport(uint32 newMapid, float x, float y, float z); + /** + * @brief A SAME-MAP seam: the vessel jumps along its own path, and NOBODY is teleported. + * + * A teleport waypoint that does not change map is not a map transfer, and treating it + * as one is what drowned the passengers. The old path fell into Player::TeleportTo's + * FAR branch (its near branch excludes anyone on a transport), which composes a WORLD + * position -- waypoint + deck offset -- and ports the player to it. The waypoint's z is + * the ship's PATH z, i.e. the waterline, so the player was being placed at sea level + * instead of on the deck. + * + * There is nothing to compute. The passengers' only real coordinates are their deck + * offsets, and those do not change when the hull moves. The client already interpolates + * the vessel along the taxi path -- teleport nodes included -- and already parents its + * passengers to the hull, so it carries them across on its own. We send no placement at + * all; we only move our own bookkeeping (the players' grid cells) to follow the hull. + */ + void JumpWithinMap(float x, float y, float z); + /// Carry the crew across a map seam with the ship. They stay boarded; only their map /// registration moves. void MoveCrewToMap(Map* newMap); @@ -282,6 +365,29 @@ class Transport : public GameObject, public TransportBase void DoEventIfAny(WayPointMap::value_type const& node, bool departure); void MoveToNextWayPoint(); // move m_next/m_cur to next points + /// Put a minion into the vessel's frame at a deck-local spot, and tell the client + /// it is standing on us. The one place a minion is ever boarded. + void BoardMinionAt(Unit* minion, float lx, float ly, float lz, float lo); + + /// Pick a minion up off the shore and set it down at its master's heel. See + /// BoardMinion for why this is the only way a pet ever gets aboard. + void HaulMinionAboard(Unit* minion); + + /// The mirror of the haul: step a minion off the deck and back into the world's + /// frame, at its master's heel ashore. + void PutMinionAshore(Unit* minion); + + /// The exact inverse of the boarding relink: unlink the minion from the vessel's + /// container (it is already unboarded when we get here) and re-file it into the WORLD + /// grid at (x,y,z) -- a clean RemoveFromWorld + Map::Add, which restores its cell, its + /// ordinary world-grid visibility and its grid-driven tick. + void ReturnMinionToWorld(Unit* minion, float x, float y, float z, float o); + + /// Let go of every minion aboard, WITHOUT touching the crew. Used at the map seam: + /// a minion is a citizen of the map we are leaving, and the vessel must not carry + /// one across. See TeleportTransport. + void UnBoardAllMinions(); + /// The vessel's own update tick over its crew -- this is the ObjectUpdater that /// the grid would have run, had the crew been in a grid. void UpdateCrew(uint32 diff); diff --git a/src/game/WorldHandlers/UnitAuraProcHandler.cpp b/src/game/WorldHandlers/UnitAuraProcHandler.cpp index a6dde6c22f..3ebe8b6b67 100644 --- a/src/game/WorldHandlers/UnitAuraProcHandler.cpp +++ b/src/game/WorldHandlers/UnitAuraProcHandler.cpp @@ -34,6 +34,8 @@ #include "Creature.h" #include "ScriptMgr.h" #include "Util.h" +#include +#include pAuraProcHandler AuraProcHandler[TOTAL_AURAS] = { @@ -1404,10 +1406,10 @@ SpellAuraProcResult Unit::HandleDummyAuraProc(Unit* pVictim, uint32 damage, Aura { switch (triggeredByAura->GetEffIndex()) { - case 0: + case EFFECT_INDEX_0: triggered_spell_id = 31893; break; - case 1: + case EFFECT_INDEX_1: { // damage basepoints[0] = triggerAmount * damage / 100; @@ -1415,6 +1417,8 @@ SpellAuraProcResult Unit::HandleDummyAuraProc(Unit* pVictim, uint32 damage, Aura triggered_spell_id = 32221; break; } + default: + break; } } @@ -1826,7 +1830,7 @@ SpellAuraProcResult Unit::HandleDummyAuraProc(Unit* pVictim, uint32 damage, Aura * * @return SpellAuraProcResult The proc handling result. */ -SpellAuraProcResult Unit::HandleProcTriggerSpellAuraProc(Unit* pVictim, uint32 damage, Aura* triggeredByAura, SpellEntry const* procSpell, uint32 procFlags, uint32 procEx, uint32 cooldown) +SpellAuraProcResult Unit::HandleProcTriggerSpellAuraProc(Unit* pVictim, uint32 damage, Aura* triggeredByAura, SpellEntry const* procSpell, uint32 procFlags, uint32 /*procEx*/, uint32 cooldown) { // Get triggered aura spell info SpellEntry const* auraSpellInfo = triggeredByAura->GetSpellProto(); @@ -2752,11 +2756,11 @@ SpellAuraProcResult Unit::HandleModPowerCostSchoolAuraProc(Unit* /*pVictim*/, ui SpellAuraProcResult Unit::HandleMechanicImmuneResistanceAuraProc(Unit* /*pVictim*/, uint32 /*damage*/, Aura* triggeredByAura, SpellEntry const* procSpell, uint32 /*procFlag*/, uint32 /*procEx*/, uint32 /*cooldown*/) { // Compare mechanic - return !(procSpell == NULL || procSpell->Mechanic != triggeredByAura->GetModifier()->m_miscvalue) + return !(procSpell == NULL || int32(procSpell->Mechanic) != triggeredByAura->GetModifier()->m_miscvalue) ? SPELL_AURA_PROC_OK : SPELL_AURA_PROC_FAILED; } -SpellAuraProcResult Unit::HandleManaShieldAuraProc(Unit* pVictim, uint32 /*damage*/, Aura* triggeredByAura, SpellEntry const* procSpell, uint32 /*procFlag*/, uint32 /*procEx*/, uint32 cooldown) +SpellAuraProcResult Unit::HandleManaShieldAuraProc(Unit* pVictim, uint32 /*damage*/, Aura* triggeredByAura, SpellEntry const* /*procSpell*/, uint32 /*procFlag*/, uint32 /*procEx*/, uint32 cooldown) { SpellEntry const* dummySpell = triggeredByAura->GetSpellProto(); @@ -2878,7 +2882,7 @@ SpellAuraProcResult Unit::HandleModResistanceAuraProc(Unit* /*pVictim*/, uint32 * * @return SpellAuraProcResult The proc handling result. */ -SpellAuraProcResult Unit::HandleRemoveByDamageChanceProc(Unit* pVictim, uint32 damage, Aura* triggeredByAura, SpellEntry const *procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown) +SpellAuraProcResult Unit::HandleRemoveByDamageChanceProc(Unit* /*pVictim*/, uint32 damage, Aura* triggeredByAura, SpellEntry const* /*procSpell*/, uint32 /*procFlag*/, uint32 /*procEx*/, uint32 /*cooldown*/) { // The chance to dispel an aura depends on the damage taken with respect to the casters level. uint32 max_dmg = getLevel() > 8 ? 25 * getLevel() - 150 : 50; @@ -2899,7 +2903,7 @@ SpellAuraProcResult Unit::HandleRemoveByDamageChanceProc(Unit* pVictim, uint32 d * * @return SpellAuraProcResult The proc handling result. */ -SpellAuraProcResult Unit::HandleInvisibilityAuraProc(Unit* pVictim, uint32 damage, Aura* triggeredByAura, SpellEntry const *procSpell, uint32 procFlag, uint32 procEx, uint32 cooldown) +SpellAuraProcResult Unit::HandleInvisibilityAuraProc(Unit* /*pVictim*/, uint32 /*damage*/, Aura* triggeredByAura, SpellEntry const* /*procSpell*/, uint32 /*procFlag*/, uint32 /*procEx*/, uint32 /*cooldown*/) { if (triggeredByAura->GetSpellProto()->HasAttribute(SPELL_ATTR_PASSIVE) || triggeredByAura->GetSpellProto()->HasAttribute(SPELL_ATTR_EX_CANT_BE_REFLECTED)) { diff --git a/src/game/WorldHandlers/WaypointManager.cpp b/src/game/WorldHandlers/WaypointManager.cpp index 4b5a82c9e0..425ef64d50 100644 --- a/src/game/WorldHandlers/WaypointManager.cpp +++ b/src/game/WorldHandlers/WaypointManager.cpp @@ -30,8 +30,9 @@ #include "MapManager.h" #include "ObjectMgr.h" #include "ScriptMgr.h" +#include +#include -INSTANTIATE_SINGLETON_1(WaypointManager); /** * If the emote, spell, model1, or model2 variables are not 0, then the function returns false. diff --git a/src/game/WorldHandlers/WaypointManager.h b/src/game/WorldHandlers/WaypointManager.h index 4e451f20d5..6437815ee0 100644 --- a/src/game/WorldHandlers/WaypointManager.h +++ b/src/game/WorldHandlers/WaypointManager.h @@ -27,6 +27,9 @@ #include "Common.h" #include "Utilities/UnorderedMapSet.h" +#include +#include +#include enum WaypointPathOrigin { diff --git a/src/game/WorldHandlers/Weather.cpp b/src/game/WorldHandlers/Weather.cpp index 00e4038d22..9d55b2a81b 100644 --- a/src/game/WorldHandlers/Weather.cpp +++ b/src/game/WorldHandlers/Weather.cpp @@ -37,6 +37,7 @@ #include "ProgressBar.h" #ifdef ENABLE_ELUNA #include "LuaEngine.h" +#include #endif /* ENABLE_ELUNA */ /// Create the Weather object @@ -48,7 +49,7 @@ Weather::Weather(uint32 zone, WeatherZoneChances const* weatherChances) : m_isPermanentWeather(false) { m_timer.SetInterval(sWorld.getConfig(CONFIG_UINT32_INTERVAL_CHANGEWEATHER)); - DETAIL_FILTER_LOG(LOG_FILTER_WEATHER, "WORLD: Starting weather system for zone %u (change every %u minutes).", m_zone, (m_timer.GetInterval() / (MINUTE * IN_MILLISECONDS))); + DETAIL_FILTER_LOG(LOG_FILTER_WEATHER, "WORLD: Starting weather system for zone %u (change every %u minutes).", m_zone, uint32(m_timer.GetInterval() / (MINUTE * IN_MILLISECONDS))); } /// Launch a weather update diff --git a/src/game/WorldHandlers/World.cpp b/src/game/WorldHandlers/World.cpp index 27ca4de189..4874c79821 100644 --- a/src/game/WorldHandlers/World.cpp +++ b/src/game/WorldHandlers/World.cpp @@ -69,6 +69,7 @@ #include "ScriptMgr.h" #include "CreatureAIRegistry.h" #include "ProgressBar.h" +#include "StartupUI.h" #include "Policies/Singleton.h" #include "BattleGround/BattleGroundMgr.h" #include "OutdoorPvP/OutdoorPvP.h" @@ -111,14 +112,21 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include -INSTANTIATE_SINGLETON_1(World); -volatile bool World::m_stopEvent = false; -uint8 World::m_ExitCode = SHUTDOWN_EXIT_CODE; +// 0 == running. A stop sets STOP_BIT and the exit code together, in one publish. +std::atomic World::m_shutdownState = 0; -ACE_Atomic_Op World::m_worldLoopCounter = 0; +std::atomic World::m_worldLoopCounter = 0; float World::m_MaxVisibleDistanceOnContinents = DEFAULT_VISIBILITY_DISTANCE; float World::m_MaxVisibleDistanceInInstances = DEFAULT_VISIBILITY_INSTANCE; @@ -194,6 +202,7 @@ World::World() m_allowMovement = true; m_ShutdownMask = 0; m_ShutdownTimer = 0; + m_plannedExitCode = SHUTDOWN_EXIT_CODE; m_gameTime = time(NULL); m_startTime = m_gameTime; m_maxActiveSessionCount = 0; @@ -262,6 +271,19 @@ World::~World() } /// Cleanups before world stop +bool World::StartupAborted(const char* phase) +{ + if (!IsStopped()) + { + return false; + } + + sLog.outString(); + sLog.outString("Startup interrupted before '%s' - aborting load.", phase); + + return true; +} + void World::CleanupsBeforeStop() { KickAll(); // save and kick all players @@ -365,6 +387,11 @@ void World::SetInitialWorldSettings() ///- Initialize the random number generator srand((unsigned int)time(NULL)); + ///- Build the opcode dispatch table before any session can receive a packet. + /// An unpopulated entry is all-zero, which reads back as STATUS_AUTHED with a + /// NULL handler and would be dispatched unconditionally. + InitializeOpcodes(); + ///- Time server startup uint32 startupBegin = GameTime::GetGameTimeMS(); @@ -401,8 +428,10 @@ void World::SetInitialWorldSettings() exit(1); } + StartupUI::BeginPhase("Core data"); + ///- Loading strings. Getting no records means core load has to be canceled because no error message can be output. - sLog.outString("Loading MaNGOS strings..."); + StartupUI::Step("Loading MaNGOS strings..."); if (!sObjectMgr.LoadMangosStrings()) { Log::WaitBeforeContinueIfNeed(); @@ -420,35 +449,40 @@ void World::SetInitialWorldSettings() ///- Remove the bones (they should not exist in DB though) and old corpses after a restart CharacterDatabase.PExecute("DELETE FROM `corpse` WHERE `corpse_type` = '0' OR `time` < (UNIX_TIMESTAMP()-'%u')", 3 * DAY); + if (StartupAborted("DBC data stores")) + { + return; + } + ///- Load the DBC files - sLog.outString("Initialize DBC data stores..."); + StartupUI::Step("Initialize DBC data stores..."); LoadDBCStores(m_dataPath); DetectDBCLang(); sObjectMgr.SetDBCLocaleIndex(GetDefaultDbcLocale()); // Get once for all the locale index of DBC language (console/broadcasts) - sLog.outString("Loading SpellTemplate..."); + StartupUI::Step("Loading SpellTemplate..."); sObjectMgr.LoadSpellTemplate(); - sLog.outString("Loading Script Names..."); + StartupUI::Step("Loading Script Names..."); sScriptMgr.LoadScriptNames(); - sLog.outString("Loading InstanceTemplate..."); + StartupUI::Step("Loading InstanceTemplate..."); sObjectMgr.LoadInstanceTemplate(); - sLog.outString("Loading SkillLineAbilityMultiMap Data..."); + StartupUI::Step("Loading SkillLineAbilityMultiMap Data..."); sSpellMgr.LoadSkillLineAbilityMap(); - sLog.outString("Loading SkillRaceClassInfoMultiMap Data..."); + StartupUI::Step("Loading SkillRaceClassInfoMultiMap Data..."); sSpellMgr.LoadSkillRaceClassInfoMap(); ///- Clean up and pack instances - sLog.outString("Cleaning up instances..."); + StartupUI::Step("Cleaning up instances..."); sMapPersistentStateMgr.CleanupInstances(); // must be called before `creature_respawn`/`gameobject_respawn` tables - sLog.outString("Packing instances..."); + StartupUI::Step("Packing instances..."); sMapPersistentStateMgr.PackInstances(); - sLog.outString("Packing groups..."); + StartupUI::Step("Packing groups..."); sObjectMgr.PackGroupIds(); // must be after CleanupInstances ///- Init highest guids before any guid using table loading to prevent using not initialized guids in some code. @@ -461,262 +495,289 @@ void World::SetInitialWorldSettings() // lua state begins uninitialized eluna = nullptr; - sLog.outString("Loading Eluna config..."); + StartupUI::Step("Loading Eluna config..."); sElunaConfig->Initialize(); if (sElunaConfig->IsElunaEnabled()) { ///- Initialize Lua Engine - sLog.outString("Loading Lua scripts..."); + StartupUI::Step("Loading Lua scripts..."); sElunaLoader->LoadScripts(); } #endif /* ENABLE_ELUNA */ - sLog.outString("Loading Page Texts..."); + StartupUI::BeginPhase("World data"); + + StartupUI::Step("Loading Page Texts..."); sObjectMgr.LoadPageTexts(); - sLog.outString("Loading Game Object Templates..."); // must be after LoadPageTexts + StartupUI::Step("Loading Game Object Templates..."); // must be after LoadPageTexts sObjectMgr.LoadGameobjectInfo(); - sLog.outString("Loading GameObject models..."); + StartupUI::Step("Loading GameObject models..."); sLog.outString(); - sLog.outString("Loading Spell Chain Data..."); + StartupUI::Step("Loading Spell Chain Data..."); sSpellMgr.LoadSpellChains(); - sLog.outString("Loading Spell Elixir types..."); + StartupUI::Step("Loading Spell Elixir types..."); sSpellMgr.LoadSpellElixirs(); - sLog.outString("Loading Spell Learn Skills..."); + StartupUI::Step("Loading Spell Learn Skills..."); sSpellMgr.LoadSpellLearnSkills(); // must be after LoadSpellChains - sLog.outString("Loading Spell Learn Spells..."); + StartupUI::Step("Loading Spell Learn Spells..."); sSpellMgr.LoadSpellLearnSpells(); - sLog.outString("Loading Spell Proc Event conditions..."); + StartupUI::Step("Loading Spell Proc Event conditions..."); sSpellMgr.LoadSpellProcEvents(); - sLog.outString("Loading Spell Bonus Data..."); + StartupUI::Step("Loading Spell Bonus Data..."); sSpellMgr.LoadSpellBonuses(); - sLog.outString("Loading Spell Proc Item Enchant..."); + StartupUI::Step("Loading Spell Proc Item Enchant..."); sSpellMgr.LoadSpellProcItemEnchant(); // must be after LoadSpellChains - sLog.outString("Loading Spell Linked definitions..."); + StartupUI::Step("Loading Spell Linked definitions..."); sSpellMgr.LoadSpellLinked(); // must be after LoadSpellChains - sLog.outString("Loading Aggro Spells Definitions..."); + StartupUI::Step("Loading Aggro Spells Definitions..."); sSpellMgr.LoadSpellThreats(); - sLog.outString("Loading NPC Texts..."); + StartupUI::Step("Loading NPC Texts..."); sObjectMgr.LoadGossipText(); - sLog.outString("Loading Item Random Enchantments Table..."); + StartupUI::Step("Loading Item Random Enchantments Table..."); LoadRandomEnchantmentsTable(); - sLog.outString("Loading Disables..."); // must be before loading quests and items + StartupUI::Step("Loading Disables..."); // must be before loading quests and items DisableMgr::LoadDisables(); - sLog.outString("Loading Item Templates..."); // must be after LoadRandomEnchantmentsTable and LoadPageTexts + StartupUI::Step("Loading Item Templates..."); // must be after LoadRandomEnchantmentsTable and LoadPageTexts sObjectMgr.LoadItemPrototypes(); - sLog.outString("Loading Creature Model Based Info Data..."); + StartupUI::Step("Loading Creature Model Based Info Data..."); sObjectMgr.LoadCreatureModelInfo(); - sLog.outString("Loading Equipment templates..."); + StartupUI::Step("Loading Equipment templates..."); sObjectMgr.LoadEquipmentTemplates(); - sLog.outString("Loading Creature Stats..."); + StartupUI::Step("Loading Creature Stats..."); sObjectMgr.LoadCreatureClassLvlStats(); - sLog.outString("Loading Creature templates..."); + StartupUI::Step("Loading Creature templates..."); sObjectMgr.LoadCreatureTemplates(); - sLog.outString("Loading Creature template spells..."); + StartupUI::Step("Loading Creature template spells..."); sObjectMgr.LoadCreatureTemplateSpells(); - sLog.outString("Loading Creature Model for race..."); // must be after creature templates + StartupUI::Step("Loading Creature Model for race..."); // must be after creature templates sObjectMgr.LoadCreatureModelRace(); - sLog.outString("Loading SpellsScriptTarget..."); + StartupUI::Step("Loading SpellsScriptTarget..."); sSpellMgr.LoadSpellScriptTarget(); // must be after LoadCreatureTemplates and LoadGameobjectInfo - sLog.outString("Loading ItemRequiredTarget..."); + StartupUI::Step("Loading ItemRequiredTarget..."); sObjectMgr.LoadItemRequiredTarget(); - sLog.outString("Loading Reputation Reward Rates..."); + StartupUI::Step("Loading Reputation Reward Rates..."); sObjectMgr.LoadReputationRewardRate(); - sLog.outString("Loading Creature Reputation OnKill Data..."); + StartupUI::Step("Loading Creature Reputation OnKill Data..."); sObjectMgr.LoadReputationOnKill(); - sLog.outString("Loading Reputation Spillover Data..."); + StartupUI::Step("Loading Reputation Spillover Data..."); sObjectMgr.LoadReputationSpilloverTemplate(); - sLog.outString("Loading Points Of Interest Data..."); + StartupUI::Step("Loading Points Of Interest Data..."); sObjectMgr.LoadPointsOfInterest(); - sLog.outString("Loading Pet Create Spells..."); + StartupUI::Step("Loading Pet Create Spells..."); sObjectMgr.LoadPetCreateSpells(); - sLog.outString("Loading Creature Data..."); + StartupUI::Step("Loading Creature Data..."); sObjectMgr.LoadCreatures(); - sLog.outString("Loading Creature Addon Data..."); + StartupUI::Step("Loading Creature Addon Data..."); sObjectMgr.LoadCreatureAddons(); // must be after LoadCreatureTemplates() and LoadCreatures() sLog.outString(">>> Creature Addon Data loaded"); + + if (StartupAborted("world object data")) + { + return; + } sLog.outString(); - sLog.outString("Loading Gameobject Data..."); + StartupUI::Step("Loading Gameobject Data..."); sObjectMgr.LoadGameObjects(); - sLog.outString("Loading CreatureLinking Data..."); // must be after Creatures + StartupUI::Step("Loading CreatureLinking Data..."); // must be after Creatures sCreatureLinkingMgr.LoadFromDB(); - sLog.outString("Loading Objects Pooling Data..."); + StartupUI::Step("Loading Objects Pooling Data..."); sPoolMgr.LoadFromDB(); - sLog.outString("Loading Weather Data..."); + StartupUI::Step("Loading Weather Data..."); sWeatherMgr.LoadWeatherZoneChances(); - sLog.outString("Loading Quests..."); + StartupUI::Step("Loading Quests..."); sObjectMgr.LoadQuests(); // must be loaded after DBCs, creature_template, item_template, gameobject tables - sLog.outString("Loading Quests Relations..."); + StartupUI::Step("Loading Quests Relations..."); sObjectMgr.LoadQuestRelations(); // must be after quest load sLog.outString(">>> Quests Relations loaded"); + + if (StartupAborted("game event data")) + { + return; + } sLog.outString(); - sLog.outString("Checking Quest Disables..."); + StartupUI::Step("Checking Quest Disables..."); DisableMgr::CheckQuestDisables(); // must be after loading quests - sLog.outString("Loading Game Event Data..."); // must be after sPoolMgr.LoadFromDB and quests to properly load pool events and quests for events + StartupUI::Step("Loading Game Event Data..."); // must be after sPoolMgr.LoadFromDB and quests to properly load pool events and quests for events sGameEventMgr.LoadFromDB(); sLog.outString(">>> Game Event Data loaded"); + + if (StartupAborted("player create info & level stats")) + { + return; + } sLog.outString(); // Load Conditions - sLog.outString("Loading Conditions..."); + StartupUI::Step("Loading Conditions..."); sObjectMgr.LoadConditions(); - sLog.outString("Creating map persistent states for non-instanceable maps..."); // must be after PackInstances(), LoadCreatures(), sPoolMgr.LoadFromDB(), sGameEventMgr.LoadFromDB(); + StartupUI::Step("Creating map persistent states for non-instanceable maps..."); // must be after PackInstances(), LoadCreatures(), sPoolMgr.LoadFromDB(), sGameEventMgr.LoadFromDB(); sMapPersistentStateMgr.InitWorldMaps(); sLog.outString(); - sLog.outString("Loading Creature Respawn Data..."); // must be after LoadCreatures(), and sMapPersistentStateMgr.InitWorldMaps() + StartupUI::Step("Loading Creature Respawn Data..."); // must be after LoadCreatures(), and sMapPersistentStateMgr.InitWorldMaps() sMapPersistentStateMgr.LoadCreatureRespawnTimes(); - sLog.outString("Loading Gameobject Respawn Data..."); // must be after LoadGameObjects(), and sMapPersistentStateMgr.InitWorldMaps() + StartupUI::Step("Loading Gameobject Respawn Data..."); // must be after LoadGameObjects(), and sMapPersistentStateMgr.InitWorldMaps() sMapPersistentStateMgr.LoadGameobjectRespawnTimes(); - sLog.outString("Loading SpellArea Data..."); // must be after quest load + StartupUI::Step("Loading SpellArea Data..."); // must be after quest load sSpellMgr.LoadSpellAreas(); - sLog.outString("Loading AreaTrigger definitions..."); + StartupUI::Step("Loading AreaTrigger definitions..."); sObjectMgr.LoadAreaTriggerTeleports(); // must be after item template load - sLog.outString("Loading Quest Area Triggers..."); + StartupUI::Step("Loading Quest Area Triggers..."); sObjectMgr.LoadQuestAreaTriggers(); // must be after LoadQuests - sLog.outString("Loading Tavern Area Triggers..."); + StartupUI::Step("Loading Tavern Area Triggers..."); sObjectMgr.LoadTavernAreaTriggers(); #ifdef ENABLE_SD3 - sLog.outString("Loading all script bindings..."); + StartupUI::Step("Loading all script bindings..."); sScriptMgr.LoadScriptBinding(); #endif /* ENABLE_SD3 */ - sLog.outString("Loading Graveyard-zone links..."); + StartupUI::Step("Loading Graveyard-zone links..."); sObjectMgr.LoadGraveyardZones(); - sLog.outString("Loading spell target destination coordinates..."); + StartupUI::Step("Loading spell target destination coordinates..."); sSpellMgr.LoadSpellTargetPositions(); - sLog.outString("Loading SpellAffect definitions..."); + StartupUI::Step("Loading SpellAffect definitions..."); sSpellMgr.LoadSpellAffects(); - sLog.outString("Loading spell pet auras..."); + StartupUI::Step("Loading spell pet auras..."); sSpellMgr.LoadSpellPetAuras(); - sLog.outString("Loading Player Create Info & Level Stats..."); + StartupUI::Step("Loading Player Create Info & Level Stats..."); sObjectMgr.LoadPlayerInfo(); sLog.outString(">>> Player Create Info & Level Stats loaded"); + + if (StartupAborted("loot tables")) + { + return; + } sLog.outString(); - sLog.outString("Loading Exploration BaseXP Data..."); + StartupUI::Step("Loading Exploration BaseXP Data..."); sObjectMgr.LoadExplorationBaseXP(); - sLog.outString("Loading Pet Name Parts..."); + StartupUI::Step("Loading Pet Name Parts..."); sObjectMgr.LoadPetNames(); CharacterDatabaseCleaner::CleanDatabase(); sLog.outString(); - sLog.outString("Loading the max pet number..."); + StartupUI::Step("Loading the max pet number..."); sObjectMgr.LoadPetNumber(); - sLog.outString("Loading pet level stats..."); + StartupUI::Step("Loading pet level stats..."); sObjectMgr.LoadPetLevelInfo(); - sLog.outString("Loading Player Corpses..."); + StartupUI::Step("Loading Player Corpses..."); sObjectMgr.LoadCorpses(); - sLog.outString("Loading Player level dependent mail rewards..."); + StartupUI::Step("Loading Player level dependent mail rewards..."); sObjectMgr.LoadMailLevelRewards(); - sLog.outString("Loading Loot Tables..."); + StartupUI::Step("Loading Loot Tables..."); LoadLootTables(); sLog.outString(">>> Loot Tables loaded"); + + if (StartupAborted("localization strings")) + { + return; + } sLog.outString(); - sLog.outString("Loading Skill Discovery Table..."); + StartupUI::Step("Loading Skill Discovery Table..."); LoadSkillDiscoveryTable(); - sLog.outString("Loading Skill Extra Item Table..."); + StartupUI::Step("Loading Skill Extra Item Table..."); LoadSkillExtraItemTable(); - sLog.outString("Loading Skill Fishing base level requirements..."); + StartupUI::Step("Loading Skill Fishing base level requirements..."); sObjectMgr.LoadFishingBaseSkillLevel(); - sLog.outString("Loading Gossip scripts..."); + StartupUI::Step("Loading Gossip scripts..."); sScriptMgr.LoadDbScripts(DBS_ON_GOSSIP); // must be before gossip menu options sObjectMgr.LoadGossipMenus(); - sLog.outString("Loading Vendors..."); + StartupUI::Step("Loading Vendors..."); sObjectMgr.LoadVendorTemplates(); // must be after load ItemTemplate sObjectMgr.LoadVendors(); // must be after load CreatureTemplate, VendorTemplate, and ItemTemplate - sLog.outString("Loading Trainers..."); + StartupUI::Step("Loading Trainers..."); sObjectMgr.LoadTrainerTemplates(); // must be after load CreatureTemplate sObjectMgr.LoadTrainers(); // must be after load CreatureTemplate, TrainerTemplate - sLog.outString("Loading Waypoint scripts..."); // before loading from creature_movement + StartupUI::Step("Loading Waypoint scripts..."); // before loading from creature_movement sScriptMgr.LoadDbScripts(DBS_ON_CREATURE_MOVEMENT); - sLog.outString("Loading Waypoints..."); + StartupUI::Step("Loading Waypoints..."); sWaypointMgr.Load(); - sLog.outString("Modifying in-memory dbc spell attributes..."); + StartupUI::Step("Modifying in-memory dbc spell attributes..."); sSpellMgr.ModDBCSpellAttributes(); - sLog.outString("Loading ReservedNames..."); + StartupUI::Step("Loading ReservedNames..."); sObjectMgr.LoadReservedPlayersNames(); - sLog.outString("Loading GameObjects for quests..."); + StartupUI::Step("Loading GameObjects for quests..."); sObjectMgr.LoadGameObjectForQuests(); - sLog.outString("Loading BattleMasters..."); + StartupUI::Step("Loading BattleMasters..."); sBattleGroundMgr.LoadBattleMastersEntry(); - sLog.outString("Loading BattleGround event indexes..."); + StartupUI::Step("Loading BattleGround event indexes..."); sBattleGroundMgr.LoadBattleEventIndexes(); - sLog.outString("Loading GameTeleports..."); + StartupUI::Step("Loading GameTeleports..."); sObjectMgr.LoadGameTele(); ///- Loading localization data - sLog.outString("Loading Localization strings..."); + StartupUI::Step("Loading Localization strings..."); sObjectMgr.LoadCreatureLocales(); // must be after CreatureInfo loading sObjectMgr.LoadGameObjectLocales(); // must be after GameobjectInfo loading sObjectMgr.LoadItemLocales(); // must be after ItemPrototypes loading @@ -727,35 +788,49 @@ void World::SetInitialWorldSettings() sObjectMgr.LoadPointOfInterestLocales(); // must be after POI loading sCommandMgr.LoadCommandHelpLocale(); sLog.outString(">>> Localization strings loaded"); + + if (StartupAborted("auctions")) + { + return; + } sLog.outString(); + StartupUI::BeginPhase("Characters and economy"); + ///- Load dynamic data tables from the database - sLog.outString("Loading Auctions..."); + StartupUI::Step("Loading Auctions..."); sAuctionMgr.LoadAuctionItems(); sAuctionMgr.LoadAuctions(); sLog.outString(">>> Auctions loaded"); + + if (StartupAborted("DB scripts")) + { + return; + } sLog.outString(); - sLog.outString("Loading Guilds..."); + StartupUI::Step("Loading Guilds..."); sGuildMgr.LoadGuilds(); - sLog.outString("Loading ArenaTeams..."); + StartupUI::Step("Loading ArenaTeams..."); sObjectMgr.LoadArenaTeams(); - sLog.outString("Loading Groups..."); + StartupUI::Step("Loading Groups..."); sObjectMgr.LoadGroups(); - sLog.outString("Returning old mails..."); + StartupUI::Step("Returning old mails..."); sObjectMgr.ReturnOrDeleteOldMails(false); - sLog.outString("Loading GM tickets..."); + StartupUI::Step("Loading GM tickets..."); sTicketMgr.LoadGMTickets(); + StartupUI::BeginPhase("Scripts"); + #ifdef ENABLE_ELUNA if (sElunaConfig->IsElunaEnabled()) { ///- Run eluna scripts. - sLog.outString("Starting Eluna world state..."); + StartupUI::Step("Starting Eluna world state..."); // use map id -1 for the global Eluna state eluna = new Eluna(nullptr); sLog.outString(); @@ -763,7 +838,7 @@ void World::SetInitialWorldSettings() #endif /*ENABLE_ELUNA*/ ///- Load and initialize DBScripts Engine - sLog.outString("Loading DB-Scripts Engine..."); + StartupUI::Step("Loading DB-Scripts Engine..."); sScriptMgr.LoadDbScripts(DBS_ON_QUEST_START); // must be after load Creature/Gameobject(Template/Data) and QuestTemplate sScriptMgr.LoadDbScripts(DBS_ON_QUEST_END); // must be after load Creature/Gameobject(Template/Data) and QuestTemplate sScriptMgr.LoadDbScripts(DBS_ON_SPELL); // must be after load Creature/Gameobject(Template/Data) @@ -774,20 +849,25 @@ void World::SetInitialWorldSettings() sLog.outString(">>> DB Scripts loaded"); sLog.outString(); - sLog.outString("Loading Scripts text locales..."); // must be after Load*Scripts calls + if (StartupAborted("script libraries")) + { + return; + } + + StartupUI::Step("Loading Scripts text locales..."); // must be after Load*Scripts calls sScriptMgr.LoadDbScriptStrings(); ///- Load and initialize EventAI Scripts - sLog.outString("Loading CreatureEventAI Texts..."); + StartupUI::Step("Loading CreatureEventAI Texts..."); sEventAIMgr.LoadCreatureEventAI_Texts(false); // false, will checked in LoadCreatureEventAI_Scripts - sLog.outString("Loading CreatureEventAI Summons..."); + StartupUI::Step("Loading CreatureEventAI Summons..."); sEventAIMgr.LoadCreatureEventAI_Summons(false); // false, will checked in LoadCreatureEventAI_Scripts - sLog.outString("Loading CreatureEventAI Scripts..."); + StartupUI::Step("Loading CreatureEventAI Scripts..."); sEventAIMgr.LoadCreatureEventAI_Scripts(); - sLog.outString("Initializing Scripts..."); + StartupUI::Step("Initializing Scripts..."); #ifdef ENABLE_SD3 switch (sScriptMgr.LoadScriptLibrary("mangosscript")) { @@ -809,8 +889,10 @@ void World::SetInitialWorldSettings() #endif /* ENABLE_SD3 */ sLog.outString(); + StartupUI::BeginPhase("World systems"); + ///- Initialize game time and timers - sLog.outString("Initialize game time and timers"); + StartupUI::Step("Initialize game time and timers"); m_gameTime = time(NULL); m_startTime = m_gameTime; @@ -833,7 +915,7 @@ void World::SetInitialWorldSettings() m_timers[WUPDATE_AHBOT].SetInterval(20 * IN_MILLISECONDS); // every 20 sec // for AutoBroadcast - sLog.outString("Starting AutoBroadcast System"); + StartupUI::Step("Starting AutoBroadcast System"); if (m_broadcastEnable) { LoadBroadcastStrings(); @@ -864,49 +946,54 @@ void World::SetInitialWorldSettings() Player::InitVisibleBits(); ///- Initialize MapManager - sLog.outString("Starting Map System"); + StartupUI::Step("Starting Map System"); sMapMgr.Initialize(); sLog.outString(); ///- Initialize Battlegrounds - sLog.outString("Starting BattleGround System"); + StartupUI::Step("Starting BattleGround System"); sBattleGroundMgr.CreateInitialBattleGrounds(); sBattleGroundMgr.InitAutomaticArenaPointDistribution(); ///- Initialize Outdoor PvP - sLog.outString("Starting Outdoor PvP System"); + StartupUI::Step("Starting Outdoor PvP System"); sOutdoorPvPMgr.InitOutdoorPvP(); // Not sure if this can be moved up in the sequence (with static data loading) as it uses MapManager - sLog.outString("Loading Transport Crew..."); + StartupUI::Step("Loading Transport Crew..."); sMapMgr.LoadTransportCrew(); // rosters first: LoadTransports spawns them - sLog.outString("Loading Transports..."); + StartupUI::Step("Loading Transports..."); sMapMgr.LoadTransports(); // Initialize Warden - sLog.outString("Loading Warden Checks..."); + StartupUI::Step("Loading Warden Checks..."); sWardenCheckMgr->LoadWardenChecks(); sLog.outString(); - sLog.outString("Loading Warden Action Overrides..."); + StartupUI::Step("Loading Warden Action Overrides..."); sWardenCheckMgr->LoadWardenOverrides(); sLog.outString(); - sLog.outString("Deleting expired bans..."); + StartupUI::Step("Deleting expired bans..."); LoginDatabase.Execute("DELETE FROM `ip_banned` WHERE `unbandate`<=UNIX_TIMESTAMP() AND `unbandate`<>`bandate`"); sLog.outString(); - sLog.outString("Calculate next daily quest reset time..."); + StartupUI::Step("Calculate next daily quest reset time..."); InitDailyQuestResetTime(); sLog.outString(); - sLog.outString("Starting Game Event system..."); + StartupUI::Step("Starting Game Event system..."); uint32 nextGameEvent = sGameEventMgr.Initialize(); m_timers[WUPDATE_EVENTS].SetInterval(nextGameEvent); // depend on next event sLog.outString(); - sLog.outString("Loading grids for active creatures or transports..."); + if (StartupAborted("grid loading for active entities")) + { + return; + } + + StartupUI::Step("Loading grids for active creatures or transports..."); uint32 loadContinentsBegin = GameTime::GetGameTimeMS(); ObjectMgr::LivingWorldStartupStats lwStats = sObjectMgr.LoadActiveEntities(NULL); uint32 loadContinentsMs = GetMSTimeDiffToNow(loadContinentsBegin); @@ -917,7 +1004,7 @@ void World::SetInitialWorldSettings() // Delete all characters which have been deleted X days before Player::DeleteOldCharacters(); - sLog.outString("Initialize AuctionHouseBot..."); + StartupUI::Step("Initialize AuctionHouseBot..."); sAuctionBot.Initialize(); sLog.outString(); @@ -934,105 +1021,130 @@ void World::SetInitialWorldSettings() sPlayerbotAIConfig.Initialize(); #endif - showFooter(); + StartupUI::EndPhase(); uint32 startupDuration = GetMSTimeDiffToNow(startupBegin); - sLog.outString("SERVER STARTUP TIME: %i minutes %i seconds", (startupDuration / 60000), ((startupDuration % 60000) / 1000)); - sLog.outString(); + + // The completion panel says this in a nicer way, but the line is a long-lived + // grep target for ops tooling, so the log file keeps it verbatim. + char startupLine[128]; + snprintf(startupLine, sizeof(startupLine), "SERVER STARTUP TIME: %i minutes %i seconds", + (startupDuration / 60000), ((startupDuration % 60000) / 1000)); + StartupUI::LogOnly(startupLine); + + showFooter(startupDuration); + + ///- World initialization is over: drop the console hooks, so no runtime log + /// line and no reload-time progress bar pays for them. + StartupUI::Shutdown(); +} + +namespace +{ + /// "Eluna, ScriptDev3, Warden" -- or "none" for an empty list. + std::string JoinList(const std::vector& items) + { + std::string joined; + + for (std::size_t i = 0; i < items.size(); ++i) + { + if (i > 0) + { + joined += ", "; + } + joined += items[i]; + } + + return joined.empty() ? std::string("none") : joined; + } } /** - * @brief Prints the startup footer and enabled module summary. + * @brief Draws the completion panel: what this build is, what it is talking to, + * and which optional subsystems came up. + * + * @param startupMs how long world initialization took */ -void World::showFooter() +void World::showFooter(uint32 startupMs) { - std::set modules_; + std::vector enabled; + std::vector disabled; - // ELUNA is either included or disabled + // Eluna and SD3 are either compiled in or not there at all. #ifdef ENABLE_ELUNA - modules_.insert(" Eluna : Enabled"); + enabled.push_back("Eluna"); #endif - // SD3 is either included or disabled #ifdef ENABLE_SD3 - modules_.insert(" ScriptDev3 (SD3) : Enabled"); + enabled.push_back("ScriptDev3"); #endif - // PLAYERBOTS can be included or excluded but also disabled via mangos.conf + // The rest are compiled in but mangosd.conf still decides whether they run. #ifdef ENABLE_PLAYERBOTS - bool playerBotActive = sConfig.GetBoolDefault("PlayerbotAI.DisableBots", true); - if (playerBotActive) + if (sConfig.GetBoolDefault("PlayerbotAI.DisableBots", true)) { - modules_.insert(" PlayerBots : Disabled"); + disabled.push_back("PlayerBots"); } else { - modules_.insert(" PlayerBots : Enabled"); + enabled.push_back("PlayerBots"); } #endif - // Remote Access can be activated / deactivated via mangos.conf - bool raActive = sConfig.GetBoolDefault("Ra.Enable", false); - if (raActive) + if (sConfig.GetBoolDefault("Ra.Enable", false)) { - modules_.insert(" Remote Access (RA) : Enabled"); + enabled.push_back("Remote Access"); } else { - modules_.insert(" Remote Access (RA) : Disabled"); + disabled.push_back("Remote Access"); } - // SOAP can be included or excluded but also disabled via mangos.conf #ifdef ENABLE_SOAP - bool soapActive = sConfig.GetBoolDefault("SOAP.Enabled", false); - if (soapActive) + if (sConfig.GetBoolDefault("SOAP.Enabled", false)) { - modules_.insert(" SOAP : Enabled"); + enabled.push_back("SOAP"); } else { - modules_.insert(" SOAP : Disabled"); + disabled.push_back("SOAP"); } #endif - // Warden is always included, set active or disabled via mangos.conf - bool wardenActive = (sWorld.getConfig(CONFIG_BOOL_WARDEN_WIN_ENABLED) || sWorld.getConfig(CONFIG_BOOL_WARDEN_OSX_ENABLED)); - if (wardenActive) + if (getConfig(CONFIG_BOOL_WARDEN_WIN_ENABLED) || getConfig(CONFIG_BOOL_WARDEN_OSX_ENABLED)) { - modules_.insert(" Warden : Enabled"); + enabled.push_back("Warden"); } else { - modules_.insert(" Warden : Disabled"); + disabled.push_back("Warden"); } - std::string thisClientVersion (EXPECTED_MANGOSD_CLIENT_VERSION); - std::string thisClientBuilds = AcceptableClientBuildsListStr(); + char database[128]; + snprintf(database, sizeof(database), "Rel%s.%s.%s", GitRevision::GetWorldDBVersion(), + GitRevision::GetWorldDBStructure(), GitRevision::GetWorldDBContent()); - std::string sModules; - for (std::set::const_iterator it = modules_.begin(); it != modules_.end(); ++it) + char ready[64]; + if (startupMs >= 60000) { - sModules = sModules + " \n" + *it; + snprintf(ready, sizeof(ready), "ready in %um %02us", startupMs / 60000, (startupMs % 60000) / 1000); } + else + { + snprintf(ready, sizeof(ready), "ready in %.1fs", startupMs / 1000.0); + } + + std::vector rows; + rows.push_back(StartupUI::Row("server", GitRevision::GetProductVersionStr())); + rows.push_back(StartupUI::Row("eluna", GitRevision::GetDepElunaFullRevision())); + rows.push_back(StartupUI::Row("sd3", GitRevision::GetDepSD3FullRevision())); + rows.push_back(StartupUI::Row("database", database)); + rows.push_back(StartupUI::Row("clients", EXPECTED_MANGOSD_CLIENT_VERSION)); + rows.push_back(StartupUI::Row("builds", AcceptableClientBuildsListStr())); + rows.push_back(StartupUI::Row("enabled", JoinList(enabled))); + rows.push_back(StartupUI::Row("disabled", JoinList(disabled))); - sLog.outString("\n" - "_______________________________________________________\n" - "\n" - " MaNGOS Server: World Initialization Complete\n" - "_______________________________________________________\n" - "\n" - " Server Version : %s\n" - " Eluna Version : %s\n" - " SD3 Version : %s\n" - " Database Version : Rel%s.%s.%s\n" - "\n" - " Supporting Clients : %s\n" - " Builds : %s\n" - "\n" - " Module Status -\n%s\n" - "_______________________________________________________\n" - , GitRevision::GetProductVersionStr(), GitRevision::GetDepElunaFullRevision(), GitRevision::GetDepSD3FullRevision(), GitRevision::GetWorldDBVersion(), GitRevision::GetWorldDBStructure(), GitRevision::GetWorldDBContent(), - thisClientVersion.c_str(), thisClientBuilds.c_str(), sModules.c_str()); + StartupUI::Panel("World initialization complete", ready, rows); } /** @@ -1531,15 +1643,16 @@ void World::_UpdateGameTime() m_gameTime = thisTime; ///- if there is a shutdown timer - if (!m_stopEvent && m_ShutdownTimer > 0 && elapsed > 0) + if (!IsStopped() && m_ShutdownTimer > 0 && elapsed > 0) { - ///- ... and it is overdue, stop the world (set m_stopEvent) + ///- ... and it is overdue, commit the planned shutdown if (m_ShutdownTimer <= elapsed) { if (!(m_ShutdownMask & SHUTDOWN_MASK_IDLE) || GetActiveAndQueuedSessionCount() == 0) { - m_stopEvent = true; - } // exist code already set + // The plan becomes the committed stop, exit code and all, in one publish. + StopNow(m_plannedExitCode); + } else { m_ShutdownTimer = 1; @@ -1572,21 +1685,24 @@ void World::_UpdateGameTime() void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode) { // ignore if server shutdown at next tick - if (m_stopEvent) + 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) + // Stage the reason. Nothing is committed yet: a timed shutdown can still be cancelled, + // which is exactly why the plan is kept apart from the one-shot stop signal. + m_plannedExitCode = exitcode; + + ///- If the shutdown time is 0, commit right now (except if 'idle' with remaining sessions) if (time == 0) { if (!(options & SHUTDOWN_MASK_IDLE) || GetActiveAndQueuedSessionCount() == 0) { sObjectAccessor.SaveAllPlayers(); // save all players. - m_stopEvent = true; // exist code already set + StopNow(m_plannedExitCode); } else { @@ -1736,7 +1852,7 @@ void World::LoadScheduledExitConfig() void World::CheckScheduledExit() { - if (!m_scheduledExit.enabled || m_stopEvent) + if (!m_scheduledExit.enabled || IsStopped()) { return; } @@ -1861,8 +1977,8 @@ void World::ShutdownMsg(bool show /*= false*/, Player* player /*= NULL*/) /// Cancel a planned server shutdown void World::ShutdownCancel() { - // nothing cancel or too later - if (!m_ShutdownTimer || m_stopEvent) + // nothing to cancel, or already committed and therefore past the point of no return + if (!m_ShutdownTimer || IsStopped()) { return; } @@ -1873,7 +1989,7 @@ void World::ShutdownCancel() m_ShutdownTimer = 0; m_scheduledExitCountdownActive = false; ResetScheduledExitWarnings(); - m_ExitCode = SHUTDOWN_EXIT_CODE; // to default value + m_plannedExitCode = SHUTDOWN_EXIT_CODE; // back to the default plan SendServerMessage(msgid); DEBUG_LOG("Server %s cancelled.", (m_ShutdownMask & SHUTDOWN_MASK_RESTART) ? "restart" : "shutdown"); diff --git a/src/game/WorldHandlers/World.h b/src/game/WorldHandlers/World.h index 86572adba0..f4c8a3396f 100644 --- a/src/game/WorldHandlers/World.h +++ b/src/game/WorldHandlers/World.h @@ -37,6 +37,11 @@ #include "SharedDefines.h" #include #include +#include +#include +#include +#include +#include #ifdef ENABLE_ELUNA #include "Player.h" @@ -505,7 +510,7 @@ typedef UNORDERED_MAP SessionMap; class World { public: - static ACE_Atomic_Op m_worldLoopCounter; + static std::atomic m_worldLoopCounter; World(); ~World(); @@ -548,7 +553,8 @@ class World void SetMotd(const std::string& motd) { m_motd = motd; } /// Get the current Message of the Day const char* GetMotd() const { return m_motd.c_str(); } - void showFooter(); + /// Draw the world-initialization completion panel. + void showFooter(uint32 startupMs); LocaleConstant GetDefaultDbcLocale() const { return m_defaultDbcLocale; } @@ -599,15 +605,53 @@ class World void ShutdownServ(uint32 time, uint32 options, uint8 exitcode); void ShutdownCancel(); void ShutdownMsg(bool show = false, Player* player = NULL); + /** + * @brief Why the server is stopping, once it is. + * + * Reading this is only meaningful after IsStopped() has returned true: the acquire + * there synchronises with the release in StopNow(), which is what guarantees the + * code you read is the one the stopping thread published. Before any stop, this is + * SHUTDOWN_EXIT_CODE (0), the same default the old plain uint8 had. + */ static uint8 GetExitCode() { - return m_ExitCode; + return uint8(m_shutdownState.load(std::memory_order_acquire) & EXIT_CODE_MASK); } - static void StopNow(uint8 exitcode) { m_stopEvent = true; m_ExitCode = exitcode; } + /** + * @brief Ask the server to stop, and say why. Safe from any thread, and from a + * signal handler. + * + * The stop flag and the exit code live in ONE atomic on purpose. As two variables + * they could not be published race-free: the writer has to store them in some + * order, and a reader is free to land in between — which is exactly what used to + * happen. Ctrl-C would set the flag, the world loop would see it, unwind, and read + * the exit code before RESTART_EXIT_CODE had landed, so the process exited 0 + * ("clean shutdown") and the supervisor never restarted it. One atomic, one publish, + * no window. + * + * First reason wins. A GUID overflow that stops the world with ERROR_EXIT_CODE must + * not be quietly overwritten into a routine SHUTDOWN by whatever asks to stop next; + * the first thing that went wrong is the thing worth reporting. (The old code was + * last-writer-wins, so it lost the error.) + * + * Signal-handler safety comes for free: a lock-free atomic is the only object a + * handler may legally touch. The old version wrote a plain uint8 from the handler, + * which was undefined behaviour outright. + */ + static void StopNow(uint8 exitcode) + { + uint32 running = 0; + m_shutdownState.compare_exchange_strong(running, + STOP_BIT | uint32(exitcode), + std::memory_order_release, + std::memory_order_relaxed); + } + + /// Has a stop been requested? Checked by every loop in the daemon. static bool IsStopped() { - return m_stopEvent; + return (m_shutdownState.load(std::memory_order_acquire) & STOP_BIT) != 0; } void Update(uint32 diff); @@ -746,6 +790,20 @@ class World bool sent; }; + /** + * @brief Cooperative abort point for the startup load. + * + * The load takes minutes on a cold cache, and signals are hooked before it starts, + * so a Ctrl-C landing in the middle of it is the common case rather than an exotic + * one. Nothing used to look at the stop flag until the world loop was reached, so + * the whole load was paid for and *then* thrown away. Called between load phases: + * returns true if a stop has been requested, in which case the caller returns at + * once and the daemon unwinds without ever standing the world up. + * + * @param phase Human-readable name of the phase that was about to run. + */ + bool StartupAborted(const char* phase); + void LoadScheduledExitConfig(); void CheckScheduledExit(); void StartScheduledExit(); @@ -753,8 +811,34 @@ class World void SendScheduledExitWarnings(); void SendScheduledExitWarning(ScheduledExitWarning& warning); - static volatile bool m_stopEvent; - static uint8 m_ExitCode; + /** + * @brief The committed stop signal: "are we stopping" and "why", in one word. + * + * 0 means running. Otherwise STOP_BIT is set and the low byte is the exit code. + * One-shot and cross-thread — written by StopNow() (world thread, console thread, + * RA/SOAP, or a signal handler), read by every loop in the daemon. + * + * This is deliberately NOT the same thing as the *planned* shutdown below. A stop + * that has been committed cannot be taken back; a plan can (.server shutdown cancel). + * Conflating the two into a `volatile bool` plus a bare uint8 is what made the old + * shutdown racy — and `volatile` never bought any of the thread-safety it looked + * like it was buying: it is not atomic and orders nothing against normal loads. + */ + static std::atomic m_shutdownState; + + static constexpr uint32 STOP_BIT = 0x100u; + static constexpr uint32 EXIT_CODE_MASK = 0x0FFu; + + /** + * @brief The exit code a *planned* shutdown will commit with when its timer expires. + * + * World thread only — ShutdownServ() stages it, ShutdownCancel() resets it, and + * Update() commits it via StopNow(). It needs no synchronisation precisely because + * it never leaves that thread, which is the whole point of keeping it separate from + * m_shutdownState. + */ + uint8 m_plannedExitCode; + uint32 m_ShutdownTimer; uint32 m_ShutdownMask; @@ -803,7 +887,7 @@ class World static uint32 m_visibility_observer_sweep_interval; // CLI command holder to be thread safe - ACE_Based::LockedQueue cliCmdQueue; + MaNGOS::LockedQueue cliCmdQueue; // next daily quests reset time time_t m_NextDailyQuestReset; @@ -813,7 +897,7 @@ class World // sessions that are added async void AddSession_(WorldSession* s); - ACE_Based::LockedQueue addSessQueue; + MaNGOS::LockedQueue addSessQueue; // used versions std::string m_DBVersion; diff --git a/src/game/WorldHandlers/WorldConfig.cpp b/src/game/WorldHandlers/WorldConfig.cpp index e43df30454..fd08ca0bde 100644 --- a/src/game/WorldHandlers/WorldConfig.cpp +++ b/src/game/WorldHandlers/WorldConfig.cpp @@ -111,6 +111,8 @@ #endif /* ENABLE_PLAYERBOTS */ #ifdef ENABLE_PLAYERBOTS #include "RandomPlayerbotMgr.h" +#include +#include #endif /* ENABLE_PLAYERBOTS */ /// Initialize config values diff --git a/src/game/movement/MoveSpline.cpp b/src/game/movement/MoveSpline.cpp index 9ebe4fa560..134ac9a2b1 100644 --- a/src/game/movement/MoveSpline.cpp +++ b/src/game/movement/MoveSpline.cpp @@ -26,6 +26,10 @@ #include #include "Log.h" #include "Unit.h" +#include +#include +#include +#include namespace Movement { diff --git a/src/game/movement/MoveSpline.h b/src/game/movement/MoveSpline.h index 469bc4f8a1..3442f9751f 100644 --- a/src/game/movement/MoveSpline.h +++ b/src/game/movement/MoveSpline.h @@ -27,6 +27,8 @@ #include "spline.h" #include "MoveSplineInitArgs.h" +#include +#include namespace Movement { diff --git a/src/game/movement/MoveSplineFlag.h b/src/game/movement/MoveSplineFlag.h index 2d59780e93..4e487ca68b 100644 --- a/src/game/movement/MoveSplineFlag.h +++ b/src/game/movement/MoveSplineFlag.h @@ -26,6 +26,7 @@ #define MANGOSSERVER_MOVESPLINEFLAG_H #include "typedefs.h" +#include namespace Movement { #if defined( __GNUC__ ) diff --git a/src/game/movement/spline.cpp b/src/game/movement/spline.cpp index 5a1c099c38..d1f2a500cd 100644 --- a/src/game/movement/spline.cpp +++ b/src/game/movement/spline.cpp @@ -26,6 +26,9 @@ #include #include "terrain/Geometry/Matrix4.h" #include "terrain/Geometry/Vector4.h" +#include +#include +#include namespace Movement { diff --git a/src/game/movement/spline.h b/src/game/movement/spline.h index b8bcda2166..6dfabae7fc 100644 --- a/src/game/movement/spline.h +++ b/src/game/movement/spline.h @@ -460,5 +460,6 @@ namespace Movement } #include "spline.impl.h" +#include #endif // MANGOSSERVER_SPLINE_H diff --git a/src/game/movement/util.cpp b/src/game/movement/util.cpp index 63bb410f01..9f51663539 100644 --- a/src/game/movement/util.cpp +++ b/src/game/movement/util.cpp @@ -46,6 +46,7 @@ #include "MoveSplineFlag.h" #include +#include namespace Movement { diff --git a/src/game/terrain/Accelerators.cpp b/src/game/terrain/Accelerators.cpp index c9e09aaf5c..4920409cfa 100644 --- a/src/game/terrain/Accelerators.cpp +++ b/src/game/terrain/Accelerators.cpp @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include namespace world::terrain { diff --git a/src/game/terrain/Accelerators.hpp b/src/game/terrain/Accelerators.hpp index 5b10bc9654..bcff3b1ce3 100644 --- a/src/game/terrain/Accelerators.hpp +++ b/src/game/terrain/Accelerators.hpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace world::terrain { diff --git a/src/game/terrain/CollisionModel.cpp b/src/game/terrain/CollisionModel.cpp index ecff9afa57..eb797da647 100644 --- a/src/game/terrain/CollisionModel.cpp +++ b/src/game/terrain/CollisionModel.cpp @@ -1,6 +1,11 @@ #include "terrain/CollisionModel.hpp" #include +#include +#include +#include +#include +#include namespace world::terrain { diff --git a/src/game/terrain/Terrain.hpp b/src/game/terrain/Terrain.hpp index 2f92e5897f..9a1b7004be 100644 --- a/src/game/terrain/Terrain.hpp +++ b/src/game/terrain/Terrain.hpp @@ -19,6 +19,7 @@ #include #include #include +#include namespace world::terrain { diff --git a/src/game/terrain/TileSerializer.cpp b/src/game/terrain/TileSerializer.cpp index 5ef17c177a..1e48e438af 100644 --- a/src/game/terrain/TileSerializer.cpp +++ b/src/game/terrain/TileSerializer.cpp @@ -6,6 +6,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace world::terrain { diff --git a/src/game/terrain/WmoModel.cpp b/src/game/terrain/WmoModel.cpp index 06185a1ebf..783d9a5d41 100644 --- a/src/game/terrain/WmoModel.cpp +++ b/src/game/terrain/WmoModel.cpp @@ -2,6 +2,9 @@ #include #include +#include +#include +#include namespace world::terrain { diff --git a/src/mangosd/AFThread.cpp b/src/mangosd/AFThread.cpp deleted file mode 100644 index eb7e518fa3..0000000000 --- a/src/mangosd/AFThread.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/** - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2025 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -#include "ace/OS.h" -#include "AFThread.h" -#include "World.h" -#include "Log.h" - -/** - * Initializes the anti-freeze watchdog thread with the configured delay interval. - */ -AntiFreezeThread::AntiFreezeThread(uint32 delay) : delaytime_(delay) -{ - m_loops = 0; - w_loops = 0; - m_lastchange = 0; - w_lastchange = 0; -} - -/** - * Activates the anti-freeze watchdog thread. - */ -int AntiFreezeThread::open(void* unused) -{ - activate(); - return 0; -} - -/** - * Monitors the main and world loops and terminates the server if they become stuck. - */ -int AntiFreezeThread::svc(void) -{ - if (!delaytime_) - { - return 0; - } - - sLog.outString("AntiFreeze Thread started (%u seconds max stuck time)", delaytime_ / 1000); - while (!World::IsStopped()) - { - ACE_OS::sleep(1); - - uint32 curtime = getMSTime(); - - // normal work - if (w_loops != World::m_worldLoopCounter.value()) - { - w_lastchange = curtime; - w_loops = World::m_worldLoopCounter.value(); - } - // possible freeze - else if (getMSTimeDiff(w_lastchange, curtime) > delaytime_) - { - sLog.outError("World Thread hangs, kicking out server!"); - *((uint32 volatile*)NULL) = 0; // bang crash - } - } - - sLog.outString("AntiFreeze Thread stopped."); - return 0; -} - diff --git a/src/mangosd/AFThread.h b/src/mangosd/AFThread.h deleted file mode 100644 index 400696f6a6..0000000000 --- a/src/mangosd/AFThread.h +++ /dev/null @@ -1,46 +0,0 @@ -/** - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2025 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -#ifndef ANTIFREEZE_THREAD -#define ANTIFREEZE_THREAD - -#include "ace/Task.h" -#include "Common.h" - -class AntiFreezeThread : public ACE_Task_Base -{ - public: - explicit AntiFreezeThread(uint32 delay); - int open(void*) override; - int svc() override; - - private: - uint32 m_loops; - uint32 m_lastchange; - uint32 w_loops; - uint32 w_lastchange; - uint32 delaytime_; -}; - -#endif \ No newline at end of file diff --git a/src/mangosd/CMakeLists.txt b/src/mangosd/CMakeLists.txt index 4cbd57cb67..26460afe18 100644 --- a/src/mangosd/CMakeLists.txt +++ b/src/mangosd/CMakeLists.txt @@ -24,14 +24,11 @@ #Main Files set(SRC_GRP_MAIN - AFThread.cpp - AFThread.h - CliThread.cpp - CliThread.h - RAThread.cpp - RAThread.h - WorldThread.cpp - WorldThread.h + Master.cpp + Master.h + RASession.cpp + RASession.h + Service.h mangosd.cpp ) source_group("Main" FILES ${SRC_GRP_MAIN}) diff --git a/src/mangosd/CliThread.cpp b/src/mangosd/CliThread.cpp deleted file mode 100644 index 3d4f7144df..0000000000 --- a/src/mangosd/CliThread.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/** - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2025 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -/// \addtogroup mangosd -/// @{ -/// \file - -#include - -#include "CliThread.h" -#include "World.h" -#include "Util.h" -#include "Log.h" - -#ifdef _WIN32 -#include -#endif - -/** - * Prints the interactive mangosd console prompt. - */ -static void prompt(void* callback = NULL, bool status = true) -{ - // Route the prompt through the console writer (verbatim, no newline) so it - // shares the single serialized stdout with bar redraws and log lines and - // cannot overtake queued output -- e.g. the bar frames from a just-finished - // .reload that this same callback follows on the world thread. - sLog.ConsoleEmitRaw("mangos>"); -} - -// Non-blocking keypress detector, when return pressed, return 1, else always return 0 -#if (PLATFORM != PLATFORM_WINDOWS) - -/** - * Checks whether console input is ready without blocking on non-Windows platforms. - */ -static int kb_hit_return() -{ - struct timeval tv; - fd_set fds; - tv.tv_sec = 0; - tv.tv_usec = 0; - FD_ZERO(&fds); - FD_SET(STDIN_FILENO, &fds); - select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); - return FD_ISSET(STDIN_FILENO, &fds); -} -#endif - -/** - * Initializes the CLI thread with optional console beep support. - */ -CliThread::CliThread(bool beep) : beep_(beep) -{ -} - -/// %Thread start -int CliThread::svc() -{ - ACE_OS::sleep(1); - - if (beep_) - { - sLog.ConsoleEmitRaw("\a"); // \a = Alert (through the writer, single-owner stdout) - } - - prompt(); - - ///- As long as the World is running (no World::m_stopEvent), get the command line and handle it - while (!World::IsStopped()) - { -#if (PLATFORM != PLATFORM_WINDOWS) - while (!kb_hit_return() && !World::IsStopped()) - // With this, we limit CLI to 10 commands/second - { - usleep(100); - } - if (World::IsStopped()) - { - break; - } -#endif - char* command_str = fgets(buffer_, sizeof(buffer_), stdin); - if (command_str != NULL) - { - for (int x = 0; command_str[x]; ++x) - { - if (command_str[x] == '\r' || command_str[x] == '\n') - { - command_str[x] = 0; - break; - } - } - - if (!*command_str) - { - prompt(); - continue; - } - - std::string command; - if (!consoleToUtf8(command_str, command)) // convert from console encoding to utf8 - { - prompt(); - continue; - } - - sWorld.QueueCliCommand(new CliCommandHolder(0, SEC_CONSOLE, NULL, command.c_str(), &utf8print, &prompt)); - } - - else if (feof(stdin)) - { - World::StopNow(SHUTDOWN_EXIT_CODE); - } - } - - return 0; -} - -/** - * Unblocks the CLI thread during server shutdown. - */ -void CliThread::cli_shutdown() -{ -#ifdef _WIN32 - - // send keyboard input to safely unblock the CLI thread, which is blocked on fgets - INPUT_RECORD b; - HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); - - b.EventType = KEY_EVENT; - b.Event.KeyEvent.bKeyDown = TRUE; - b.Event.KeyEvent.dwControlKeyState = 0; - b.Event.KeyEvent.uChar.AsciiChar = '\r'; - b.Event.KeyEvent.wVirtualKeyCode = VK_RETURN; - b.Event.KeyEvent.wRepeatCount = 1; - b.Event.KeyEvent.wVirtualScanCode = 0x1c; - - DWORD numb = 0; - BOOL ret = WriteConsoleInput(hStdIn, &b, 1, &numb); - - wait(); -#endif -} diff --git a/src/mangosd/Master.cpp b/src/mangosd/Master.cpp new file mode 100644 index 0000000000..e6fcd5290d --- /dev/null +++ b/src/mangosd/Master.cpp @@ -0,0 +1,764 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2026 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup mangosd +/// @{ +/// \file + +#include "Master.h" + +#include "Config/Config.h" +#include "Database/DatabaseEnv.h" +#include "Log.h" +#include "ProgressBar.h" +#include "MapManager.h" +#include "MassMailMgr.h" +#include "ObjectAccessor.h" +#include "SystemConfig.h" +#include "Timer.h" +#include "Util.h" +#include "World.h" +#include "WorldSocketMgr.h" + +#ifdef ENABLE_SOAP +#include "SOAP/SoapThread.h" +#endif + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include "ServiceWin32.h" +extern int m_ServiceStatus; +#else +#include "PosixDaemon.h" // detachDaemon() +#include // STDIN_FILENO +#include // select(), fd_set +#include +#include +#endif + +extern DatabaseType WorldDatabase; +extern DatabaseType CharacterDatabase; +extern DatabaseType LoginDatabase; +extern uint32 realmID; + +/// Shortest interval between two world ticks. +constexpr std::chrono::milliseconds WORLD_SLEEP_CONST{50}; + +/// How often the console reader looks for a pending line while idle. Fast enough that a +/// typed command feels instant, slow enough that an idle server is not making syscalls. +constexpr std::chrono::milliseconds CLI_POLL_INTERVAL{100}; + +namespace +{ + /// Forward a fully-built progress-bar redraw to the off-thread console writer + /// (verbatim: no prefix, colour or newline), so the bar shares one serialised stdout + /// with the log lines and cannot tear against them. + void BarConsoleSink(char const* bytes, size_t len) + { + sLog.ConsoleEmitRaw(std::string(bytes, len)); + } + + /** + * @brief RAII handle for one opened database. + * + * The point of this: opening the three databases is a ladder, and every rung can + * fail. Done by hand, each failure has to remember to halt the delay threads of + * everything opened so far — which is where the old code's repeated (and easy to get + * wrong) HaltDelayThread() cascades came from. Here, unwinding is automatic, and + * Release() is called only once every database is up. + */ + class DatabaseGuard + { + public: + + explicit DatabaseGuard(DatabaseType& db) : m_db(&db) {} + + ~DatabaseGuard() + { + if (m_db) + { + m_db->HaltDelayThread(); + } + } + + DatabaseGuard(const DatabaseGuard&) = delete; + DatabaseGuard& operator=(const DatabaseGuard&) = delete; + + /// The database is now owned by the caller; stop tracking it. + void Release() { m_db = nullptr; } + + private: + + DatabaseType* m_db; + }; + + /** + * @brief Open one database and verify its schema version. + */ + bool OpenDatabase(DatabaseType& db, const char* infoKey, const char* connKey, + const char* label, DatabaseTypes versionCheck) + { + const std::string dbstring = sConfig.GetStringDefault(infoKey, ""); + if (dbstring.empty()) + { + sLog.outError("%s not specified in configuration file", label); + return false; + } + + const int nConnections = sConfig.GetIntDefault(connKey, 1); + sLog.outString("%s total connections: %i", label, nConnections + 1); + + if (!db.Initialize(dbstring.c_str(), nConnections)) + { + sLog.outError("Can not connect to %s %s", label, dbstring.c_str()); + return false; + } + + return db.CheckDatabaseVersion(versionCheck); + } +} + +// ── Databases ──────────────────────────────────────────────────────────────────── + +bool Master::StartDatabases() +{ + if (!OpenDatabase(WorldDatabase, "WorldDatabaseInfo", "WorldDatabaseConnections", + "World Database", DATABASE_WORLD)) + { + return false; + } + DatabaseGuard worldGuard(WorldDatabase); + + if (!OpenDatabase(CharacterDatabase, "CharacterDatabaseInfo", "CharacterDatabaseConnections", + "Character Database", DATABASE_CHARACTER)) + { + return false; + } + DatabaseGuard characterGuard(CharacterDatabase); + + if (!OpenDatabase(LoginDatabase, "LoginDatabaseInfo", "LoginDatabaseConnections", + "Login Database", DATABASE_REALMD)) + { + return false; + } + DatabaseGuard loginGuard(LoginDatabase); + + sLog.outString(); + + ///- Get the realm Id from the configuration file + realmID = sConfig.GetIntDefault("RealmID", 0); + if (!realmID) + { + sLog.outError("Realm ID not defined in configuration file"); + return false; + } + + sLog.outString("Realm running as realm ID %d", realmID); + sLog.outString(); + + ///- Clean the database before starting + ClearOnlineAccounts(); + + sWorld.LoadDBVersion(); + sLog.outString("Using World DB: %s", sWorld.GetDBVersion()); + sLog.outString(); + + // Everything is up: the databases are ours to keep, so the guards must not unwind. + loginGuard.Release(); + characterGuard.Release(); + worldGuard.Release(); + + return true; +} + +void Master::StopDatabases() +{ + sLog.outString("[shutdown] halting DB delay threads (Login/Character/World)..."); + + // Reverse of the open order in StartDatabases(), which is also the order the + // DatabaseGuards unwind in on a failed startup. The halts are independent, so this + // costs nothing — but having one teardown order instead of two means the shutdown + // path and the startup-failure path can't drift apart later. + LoginDatabase.HaltDelayThread(); + CharacterDatabase.HaltDelayThread(); + WorldDatabase.HaltDelayThread(); + + sLog.outString("[shutdown] DB delay threads halted"); +} + +void Master::ClearOnlineAccounts() +{ + // Cleanup online status for characters hosted at current realm + /// \todo Only accounts with characters logged on *this* realm should have online + /// status reset. Move the online column from 'account' to 'realmcharacters'? + LoginDatabase.PExecute("UPDATE `account` SET `active_realm_id` = 0, `os` = '' WHERE `active_realm_id` = '%u'", realmID); + + CharacterDatabase.Execute("UPDATE `characters` SET `online` = 0 WHERE `online`<>0"); + + // Battleground instance ids reset at server restart + CharacterDatabase.Execute("UPDATE `character_battleground_data` SET `instance_id` = 0"); +} + +// ── Threads ────────────────────────────────────────────────────────────────────── + +/** + * @brief The world heartbeat. + * + * Runs on the caller's thread (main), so there is no world thread to spawn or join any + * more. Ticks the world at most every WORLD_SLEEP_CONST ms, then unwinds the world in + * order: kick the players, flush their sessions, stop the network, unload the maps. + */ +void Master::WorldLoop() +{ + uint32 realPrevTime = getMSTime(); + + sLog.outString("World Updater started (%lldms min update interval)", + static_cast(WORLD_SLEEP_CONST.count())); + + while (!World::IsStopped()) + { + ++World::m_worldLoopCounter; + + const uint32 realCurrTime = getMSTime(); + const uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime); + + sWorld.Update(diff); + realPrevTime = realCurrTime; + + // Sleep off whatever is left of this tick's budget. + const std::chrono::milliseconds executionTime{getMSTimeDiff(realCurrTime, getMSTime())}; + if (executionTime < WORLD_SLEEP_CONST) + { + std::this_thread::sleep_for(WORLD_SLEEP_CONST - executionTime); + } + +#ifdef _WIN32 + if (m_ServiceStatus == 0) // service stopped + { + World::StopNow(SHUTDOWN_EXIT_CODE); + } + + while (m_ServiceStatus == 2) // service paused + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } +#endif + } + + sLog.outString("[shutdown] world loop stopped; entering world shutdown tail"); + + sLog.outString("[shutdown] KickAll: saving + kicking players..."); + sWorld.KickAll(); // save and kick all players + sLog.outString("[shutdown] KickAll done"); + + sLog.outString("[shutdown] final UpdateSessions..."); + sWorld.UpdateSessions(1); // real players unload requires this call + sLog.outString("[shutdown] final UpdateSessions done"); + + sLog.outString("[shutdown] StopNetwork: closing listener + joining network threads..."); + sWorldSocketMgr.StopNetwork(); + sLog.outString("[shutdown] StopNetwork done"); + + sLog.outString("[shutdown] UnloadAll: unloading maps + MapUpdater teardown..."); + sMapMgr.UnloadAll(); // unload all grids (including locked ones) + sLog.outString("[shutdown] UnloadAll returned"); + + sLog.outString("World Updater stopped"); +} + +// ── Services ───────────────────────────────────────────────────────────────────── + +namespace +{ + /** + * @brief Watchdog: abort the process if the world loop stops advancing. + * + * A frozen world is worse than a dead one — clients hang, nothing saves — so this + * deliberately crashes the process rather than letting it sit there. + */ + class FreezeDetectorService : public IService + { + public: + + explicit FreezeDetectorService(uint32 maxStuckMs) : m_maxStuckMs(maxStuckMs) {} + + const char* Name() const override { return "freeze detector"; } + + void Start() override + { + m_thread = std::thread([this] { Run(); }); + } + + void Join() override + { + if (m_thread.joinable()) + { + m_thread.join(); + } + } + + private: + + void Run() + { + sLog.outString("AntiFreeze thread started (%u seconds max stuck time)", + m_maxStuckMs / 1000); + + uint32 lastLoops = 0; + uint32 lastChange = 0; + + while (!World::IsStopped()) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + const uint32 curtime = getMSTime(); + const uint32 loops = World::m_worldLoopCounter.load(); + + if (loops != lastLoops) // normal progress + { + lastLoops = loops; + lastChange = curtime; + } + else if (getMSTimeDiff(lastChange, curtime) > m_maxStuckMs) + { + sLog.outError("World Thread hangs, kicking out server!"); + + // Deliberately die, so the freeze leaves a core/minidump pinned at + // the point it was detected. This used to be a volatile null store, + // which is undefined behaviour: the optimiser is entitled to delete + // it outright, which would silently turn the watchdog into a no-op + // in a Release build. abort() is defined, dumps core on POSIX, and + // still reaches WheatyExceptionReport on Windows via the + // _CALL_REPORTFAULT abort behaviour. + sLog.Flush(); // get the diagnosis to disk first + std::abort(); + } + } + + sLog.outString("AntiFreeze thread stopped"); + } + + uint32 m_maxStuckMs; + std::thread m_thread; + }; + + /// Print the interactive prompt through the console writer, so it shares the single + /// serialised stdout with log lines and progress bars and cannot overtake them. + void CliPrompt(void* /*callbackArg*/ = nullptr, bool /*status*/ = true) + { + sLog.ConsoleEmitRaw("mangos>"); + } + +#if PLATFORM != PLATFORM_WINDOWS + /// Non-blocking check for pending console input. + int kb_hit_return() + { + struct timeval tv; + fd_set fds; + tv.tv_sec = 0; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv); + return FD_ISSET(STDIN_FILENO, &fds); + } +#endif + + /** + * @brief Console reader: turns stdin lines into CLI commands queued to the world thread. + * + * The only service that has to override RequestStop(): on Windows it parks inside + * fgets(), which no flag can interrupt, so it has to be woken by hand. + */ + class ConsoleService : public IService + { + public: + + explicit ConsoleService(bool beep) : m_beep(beep) {} + + const char* Name() const override { return "console"; } + + void Start() override + { + m_thread = std::thread([this] { Run(); }); + } + + void RequestStop() override + { +#ifdef _WIN32 + if (!m_thread.joinable()) + { + return; + } + + // The reader is parked in fgets(); feed it a synthetic Return so it wakes, + // sees that the world has stopped, and leaves. (On POSIX it polls instead, + // so it notices the flag on its own and needs nothing here.) + INPUT_RECORD record; + record.EventType = KEY_EVENT; + record.Event.KeyEvent.bKeyDown = TRUE; + record.Event.KeyEvent.dwControlKeyState = 0; + record.Event.KeyEvent.uChar.AsciiChar = '\r'; + record.Event.KeyEvent.wVirtualKeyCode = VK_RETURN; + record.Event.KeyEvent.wRepeatCount = 1; + record.Event.KeyEvent.wVirtualScanCode = 0x1c; + + DWORD written = 0; + WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &record, 1, &written); +#endif + } + + void Join() override + { + if (m_thread.joinable()) + { + m_thread.join(); + } + } + + private: + + void Run() + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + + if (m_beep) + { + sLog.ConsoleEmitRaw("\a"); // \a = Alert, via the single-owner stdout + } + + CliPrompt(); + + char buffer[256]; + + while (!World::IsStopped()) + { +#if PLATFORM != PLATFORM_WINDOWS + // Poll rather than block, so shutdown does not have to interrupt a + // parked read. The interval is what bounds the cost: this used to sleep + // 100 *micro*seconds despite claiming to cap the console at ~10 + // commands/second, so an idle server sat here spinning through ~20k + // syscalls/second for its entire uptime. + while (!kb_hit_return() && !World::IsStopped()) + { + std::this_thread::sleep_for(CLI_POLL_INTERVAL); + } + + if (World::IsStopped()) + { + break; + } +#endif + char* command_str = fgets(buffer, sizeof(buffer), stdin); + if (!command_str) + { + if (feof(stdin)) + { + World::StopNow(SHUTDOWN_EXIT_CODE); + } + continue; + } + + for (int x = 0; command_str[x]; ++x) + { + if (command_str[x] == '\r' || command_str[x] == '\n') + { + command_str[x] = 0; + break; + } + } + + if (!*command_str) + { + CliPrompt(); + continue; + } + + std::string command; + if (!consoleToUtf8(command_str, command)) // console encoding to utf8 + { + CliPrompt(); + continue; + } + + sWorld.QueueCliCommand(new CliCommandHolder(0, SEC_CONSOLE, nullptr, + command.c_str(), + &utf8print, &CliPrompt)); + } + } + + bool m_beep; + std::thread m_thread; + }; + + /// The remote-access (telnet) listener. The socket, and its accept loop, belong to + /// RaServer; this only gives it the same lifecycle as everything else. + class RaService : public IService + { + public: + + RaService(uint16 port, const std::string& bindIp) : m_port(port), m_bindIp(bindIp) {} + + const char* Name() const override { return "remote access"; } + + void Start() override { m_server.Start(m_port, m_bindIp); } + + // The listener is not a thread we join; closing it *is* the join. + void Join() override { m_server.Stop(); } + + private: + + uint16 m_port; + std::string m_bindIp; + RaServer m_server; + }; + +#ifdef ENABLE_SOAP + /// The SOAP listener. Its loop polls World::IsStopped() on a 3-second accept timeout, + /// so it needs no wake-up — it leaves on its own within one timeout. + class SoapService : public IService + { + public: + + SoapService(const std::string& ip, uint16 port) : m_ip(ip), m_port(port) {} + + const char* Name() const override { return "SOAP"; } + + void Start() override + { + m_thread = std::thread(SoapThread, m_ip, m_port); + } + + void Join() override + { + if (m_thread.joinable()) + { + m_thread.join(); + } + } + + private: + + std::string m_ip; + uint16 m_port; + std::thread m_thread; + }; +#endif + +} + +void Master::StartService(std::unique_ptr service) +{ + service->Start(); + m_services.push_back(std::move(service)); +} + +void Master::StopServices() +{ + // Ask everyone to stop first, then join. Doing it in one pass instead would mean each + // service is only *told* to stop once the one before it has fully exited, serialising + // shutdowns that could have overlapped. + for (const std::unique_ptr& service : m_services) + { + service->RequestStop(); + } + + // Join in reverse order of start, so a service may rely on anything started before it + // still being alive while it winds down. + while (!m_services.empty()) + { + const std::unique_ptr& service = m_services.back(); + + sLog.outString("[shutdown] joining %s...", service->Name()); + service->Join(); + sLog.outString("[shutdown] %s joined", service->Name()); + + m_services.pop_back(); + } +} + +// ── Run ────────────────────────────────────────────────────────────────────────── + +int Master::Run() +{ + // Register this thread with the MySQL client library BEFORE it issues its first query. + // + // The main thread is a MySQL client thread like any other: it runs every synchronous + // query of the startup load — the schema checks in StartDatabases(), then the thousands + // that SetInitialWorldSettings() fires over the next few minutes. mysql_thread_init() + // has to precede all of them. + // + // It used to be called *after* SetInitialWorldSettings() had already finished the entire + // load, which is far too late to mean anything. It survived only because mysql_init() + // initialises the calling thread implicitly, making the explicit call a no-op that + // looked load-bearing. Relying on that is relying on a libmysqlclient implementation + // detail; put the call where the contract actually requires it instead. + // + // (No matching ThreadEnd() here on purpose: this thread lives for the whole process, and + // its thread-local block is released by mysql_library_end() when the DatabaseType globals + // are destroyed. The threads that genuinely come and go — the SqlDelayThreads — pair + // their own init/end around run(), which is where it matters.) + WorldDatabase.ThreadStart(); + + ///- Start the databases + if (!StartDatabases()) + { + return 1; + } + + // Move console output off the world/map-update threads. Started only after the + // fallible init above, so an early-return error path never leaves a writer thread + // running into stdio teardown — but before SetInitialWorldSettings(), whose spawn + // burst is exactly the hot console path this exists to cover. + sLog.StartConsoleThread(); + + // The writer now owns stdout, so route progress-bar redraws through it too: the bars + // were previously raw printf from the loading thread and could tear against it. Must + // follow StartConsoleThread, since ConsoleEmitRaw falls back to a synchronous write + // whenever the writer is not running. + BarGoLink::SetConsoleSink(&BarConsoleSink); + + ///- Set Realm to Offline, in case a crash happened. Only used once. + LoginDatabase.DirectPExecute("UPDATE `realmlist` SET `realmflags` = `realmflags` | %u WHERE `id` = '%u'", + REALM_FLAG_OFFLINE, realmID); + + ///- Initialize the World + sWorld.SetInitialWorldSettings(); + + // A stop can already have been asked for: signals are hooked before Run(), and the load + // above takes minutes on a cold cache, so Ctrl-C during it is both likely and, until + // now, useless — nothing looked at the flag until the world loop was reached, by which + // point the whole load had been paid for anyway. Bail out here instead of standing up a + // listener and a thread fleet purely to tear them all down on the next line. + if (World::IsStopped()) + { + sLog.outString("[shutdown] stop requested during startup; skipping world run"); + StopDatabases(); + return World::GetExitCode(); + } + +#ifndef _WIN32 + detachDaemon(); +#endif + + // Set the realm flags from configuration, and mark the realm online. + const uint8 recommendedornew = sWorld.getConfig(CONFIG_BOOL_REALM_RECOMMENDED_OR_NEW) + ? REALM_FLAG_NEW_PLAYERS : REALM_FLAG_RECOMMENDED; + const uint8 realmstatus = sWorld.getConfig(CONFIG_BOOL_REALM_RECOMMENDED_OR_NEW_ENABLED) + ? recommendedornew : uint8(REALM_FLAG_NONE); + + std::string builds = AcceptableClientBuildsListStr(); + LoginDatabase.escape_string(builds); + LoginDatabase.DirectPExecute("UPDATE `realmlist` SET `realmflags` = %u, `population` = 0, `realmbuilds` = '%s' WHERE `id` = '%u'", + realmstatus, builds.c_str(), realmID); + + // The server is up: async DB requests are allowed from here (they are forbidden + // during startup, which is why this is not done any earlier). + CharacterDatabase.AllowAsyncTransactions(); + WorldDatabase.AllowAsyncTransactions(); + LoginDatabase.AllowAsyncTransactions(); + + ///- Start the world listener + const std::string bindIp = sConfig.GetStringDefault("BindIP", "0.0.0.0"); + const uint16 worldPort = uint16(sWorld.getConfig(CONFIG_UINT32_PORT_WORLD)); + + if (sWorldSocketMgr.StartNetwork(worldPort, bindIp) == -1) + { + sLog.outError("Failed to start network"); + World::StopNow(ERROR_EXIT_CODE); + StopDatabases(); + return 1; + } + + ///- Start the remote access listener + if (sConfig.GetBoolDefault("Ra.Enable", false)) + { + StartService(std::unique_ptr( + new RaService(uint16(sConfig.GetIntDefault("Ra.Port", 3443)), + sConfig.GetStringDefault("Ra.IP", "0.0.0.0")))); + } + + ///- Start the SOAP listener + if (sConfig.GetBoolDefault("SOAP.Enabled", false)) + { +#ifdef ENABLE_SOAP + StartService(std::unique_ptr( + new SoapService(sConfig.GetStringDefault("SOAP.IP", "127.0.0.1"), + uint16(sConfig.GetIntDefault("SOAP.Port", 7878))))); +#else + sLog.outError("SOAP is enabled but wasn't included during compilation, not activating it."); +#endif + } + + ///- Start the freeze detector + const uint32 maxStuckMs = 1000 * uint32(sConfig.GetIntDefault("MaxCoreStuckTime", 0)); + if (maxStuckMs) + { + StartService(std::unique_ptr(new FreezeDetectorService(maxStuckMs))); + } + + ///- Start the console +#ifdef _WIN32 + const bool consoleEnabled = sConfig.GetBoolDefault("Console.Enable", true) && + (m_ServiceStatus == -1); // no console when run as a service +#else + const bool consoleEnabled = sConfig.GetBoolDefault("Console.Enable", true); +#endif + if (consoleEnabled) + { + StartService(std::unique_ptr( + new ConsoleService(sConfig.GetBoolDefault("BeepAtStart", true)))); + } + + ///- Run the world. Returns once World::StopNow() has been called and the world has + /// finished unwinding. + WorldLoop(); + + sLog.outString("[shutdown] world loop returned; joining auxiliary threads"); + + ///- Wind the services down in the reverse of the order they were started. + StopServices(); + + sLog.outString("Halting process..."); + + ///- Set the realm offline again + LoginDatabase.DirectPExecute("UPDATE `realmlist` SET `realmflags` = `realmflags` | %u WHERE `id` = '%u'", + REALM_FLAG_OFFLINE, realmID); + + ///- Clean the account database before leaving + ClearOnlineAccounts(); + + // Send any still-queued mass mails before the DB connections go down. + sMassMailMgr.Update(true); + + StopDatabases(); + + return World::GetExitCode(); +} diff --git a/src/mangosd/Master.h b/src/mangosd/Master.h new file mode 100644 index 0000000000..8bc4976913 --- /dev/null +++ b/src/mangosd/Master.h @@ -0,0 +1,96 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2026 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup mangosd +/// @{ +/// \file + +#ifndef MANGOS_H_MASTER +#define MANGOS_H_MASTER + +#include "Common.h" +#include "RASession.h" +#include "Service.h" + +#include +#include + +/** + * @brief Brings the world daemon up, runs it, and takes it back down. + * + * Owns everything with a lifetime: the databases, the listening sockets, and the + * auxiliary threads (freeze detector, console, remote access, SOAP). The world + * heartbeat itself runs on the caller's thread — Run() only returns once the world has + * stopped and everything else has been joined. + * + * This replaces four ACE_Task_Base subclasses (WorldThread, RAThread, CliThread, + * AntiFreezeThread) and the global ACE_Thread_Manager::wait() that used to reap them. + */ +class Master +{ + public: + + Master() = default; + + Master(const Master&) = delete; + Master& operator=(const Master&) = delete; + + /** + * @brief Run the server to completion. + * @return The process exit code. + */ + int Run(); + + private: + + /// Open the three databases and check their schema versions. + bool StartDatabases(); + + /// Flush and close the databases (in the reverse order they were opened). + void StopDatabases(); + + /// Reset the online flags left behind by an unclean shutdown. + void ClearOnlineAccounts(); + + /// The world heartbeat. Runs on the calling thread and returns once stopped. + void WorldLoop(); + + /** + * @brief Start an auxiliary service and take ownership of it. + * + * Order matters, and now it is the only thing that has to: services are joined in + * exactly the reverse of the order they were started here, so a service may safely + * depend on anything started before it. + */ + void StartService(std::unique_ptr service); + + /// Ask every service to stop, then join them in reverse order of start. + void StopServices(); + + /// The auxiliary threads: freeze watchdog, console, remote access, SOAP. + std::vector> m_services; +}; + +#endif +/// @} diff --git a/src/mangosd/RASession.cpp b/src/mangosd/RASession.cpp new file mode 100644 index 0000000000..b148993fc6 --- /dev/null +++ b/src/mangosd/RASession.cpp @@ -0,0 +1,332 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2026 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup mangosd +/// @{ +/// \file + +#include "RASession.h" + +#include "AccountMgr.h" +#include "Config/Config.h" +#include "Language.h" +#include "Log.h" +#include "ObjectMgr.h" +#include "World.h" + +#include +#include +#include +#include +#include +#include +#include + +RASession::RASession() + : m_closed(false), + m_stage(NONE), + m_accountId(0), + m_accessLevel(SEC_PLAYER), + m_commandsPending(0) +{ + m_secure = sConfig.GetBoolDefault("RA.Secure", true); + m_stricted = sConfig.GetBoolDefault("RA.Stricted", false); + m_minLevel = AccountTypes(sConfig.GetIntDefault("RA.MinLevel", SEC_ADMINISTRATOR)); +} + +RASession::~RASession() +{ + sLog.outRALog("Connection was closed."); +} + +void RASession::Send(const char* message) +{ + if (!message || m_closed.load() || !m_sender) + { + return; + } + + m_sender(reinterpret_cast(message), strlen(message)); +} + +void RASession::Close() +{ + if (m_closed.exchange(true)) + { + return; + } + + if (m_closer) + { + m_closer(); + } +} + +void RASession::onClose() +{ + m_closed.store(true); +} + +/// Greet the peer with the MOTD and the username prompt. +std::vector RASession::onConnect() +{ + sLog.outRALog("Incoming connection from %s.", m_address.c_str()); + + Send(sWorld.GetMotd()); + Send("\r\n"); + Send(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_USER)); + + return {}; +} + +/** + * @brief Split the incoming byte stream into lines (network thread). + * + * Telnet is a stream like any other, so a read may carry a partial line, several lines, + * or both; whatever is left over stays in m_input for the next read. + */ +std::vector RASession::onData(const uint8_t* data, size_t len) +{ + if (m_closed.load()) + { + return {}; + } + + m_input.append(reinterpret_cast(data), len); + + for (;;) + { + const std::string::size_type eol = m_input.find_first_of("\r\n"); + if (eol == std::string::npos) + { + break; + } + + const std::string line = m_input.substr(0, eol); + + // Swallow the whole line terminator, however the client spells it (\r, \n, \r\n). + std::string::size_type next = m_input.find_first_not_of("\r\n", eol); + m_input.erase(0, next == std::string::npos ? m_input.size() : next); + + HandleLine(line); + + if (m_closed.load()) + { + break; + } + } + + return {}; +} + +void RASession::HandleLine(const std::string& line) +{ + switch (m_stage) + { + case NONE: HandleUsername(line); break; + case LG: HandlePassword(line); break; + case OK: HandleCommand(line); break; + } +} + +void RASession::HandleUsername(const std::string& line) +{ + m_accountId = sAccountMgr.GetId(line); + + ///- If the user is not found, deny access + if (!m_accountId) + { + Send("-No such user.\r\n"); + sLog.outRALog("User %s does not exist.", line.c_str()); + + if (m_secure) + { + Close(); + return; + } + + Send("\r\n"); + Send(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_USER)); + return; + } + + m_accessLevel = sAccountMgr.GetSecurity(m_accountId); + + ///- if gmlevel is too low, deny access + if (m_accessLevel < m_minLevel) + { + Send("-Not enough privileges.\r\n"); + sLog.outRALog("User %s has no privilege.", line.c_str()); + + if (m_secure) + { + Close(); + return; + } + + Send("\r\n"); + Send(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_USER)); + return; + } + + ///- allow a remotely connected admin to use console-level commands, per config + if (m_accessLevel >= SEC_ADMINISTRATOR && !m_stricted) + { + m_accessLevel = SEC_CONSOLE; + } + + m_stage = LG; + Send(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_PASS)); +} + +void RASession::HandlePassword(const std::string& line) +{ + if (sAccountMgr.CheckPassword(m_accountId, line)) + { + m_stage = OK; + + Send("+Logged in.\r\n"); + sLog.outRALog("User account %u has logged in.", m_accountId); + Send("mangos>"); + return; + } + + ///- Else deny access + Send("-Wrong pass.\r\n"); + sLog.outRALog("User account %u has failed to log in.", m_accountId); + + if (m_secure) + { + Close(); + return; + } + + Send("\r\n"); + Send(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_PASS)); +} + +void RASession::HandleCommand(const std::string& line) +{ + if (line.empty()) + { + Send("mangos>"); + return; + } + + sLog.outRALog("Got '%s' cmd.", line.c_str()); + + if (line.compare(0, 4, "quit") == 0) + { + Close(); + return; + } + + { + // The command runs later, on the world thread, and is handed a bare pointer back + // to us. Keep ourselves alive until it reports finished, so a peer that + // disconnects mid-command cannot pull the session out from under the callback. + std::lock_guard guard(m_commandLock); + + if (m_commandsPending++ == 0) + { + m_keepAlive = std::static_pointer_cast(shared_from_this()); + } + } + + sWorld.QueueCliCommand(new CliCommandHolder(m_accountId, m_accessLevel, this, line.c_str(), + &RASession::CommandPrint, + &RASession::CommandFinished)); +} + +void RASession::ReleaseCommand() +{ + // Drop the self-reference outside the lock: it may be the last one, and destroying + // the session while holding its own mutex would be a fine way to crash. + std::shared_ptr expiring; + + { + std::lock_guard guard(m_commandLock); + + if (--m_commandsPending == 0) + { + expiring = std::move(m_keepAlive); + m_keepAlive.reset(); + } + } +} + +/// Command output (world thread). +void RASession::CommandPrint(void* callbackArg, const char* text) +{ + if (!text) + { + return; + } + + static_cast(callbackArg)->Send(text); +} + +/// Command completion (world thread). +void RASession::CommandFinished(void* callbackArg, bool /*success*/) +{ + RASession* session = static_cast(callbackArg); + + session->Send("mangos>"); + session->ReleaseCommand(); +} + +// ── RaServer ───────────────────────────────────────────────────────────────────── + +bool RaServer::Start(uint16_t port, const std::string& bindIp) +{ + if (m_started) + { + return false; + } + + net::SessionFactory factory = []() -> std::shared_ptr + { + return std::make_shared(); + }; + + if (!m_server.start(port, std::move(factory), bindIp)) + { + sLog.outError("RaServer: failed to listen on %s:%u", + (bindIp.empty() ? "0.0.0.0" : bindIp.c_str()), unsigned(port)); + return false; + } + + return m_started = true; +} + +void RaServer::Stop() +{ + if (!m_started) + { + return; + } + + m_server.stop(); + m_started = false; +} diff --git a/src/mangosd/RASession.h b/src/mangosd/RASession.h new file mode 100644 index 0000000000..f34588c273 --- /dev/null +++ b/src/mangosd/RASession.h @@ -0,0 +1,151 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2026 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup mangosd +/// @{ +/// \file + +#ifndef MANGOS_H_RASESSION +#define MANGOS_H_RASESSION + +#include "Common.h" +#include "SharedDefines.h" + +#include "net/Server.hpp" + +#include +#include +#include +#include +#include +#include +#include + +/** + * @brief One remote-access (telnet) connection. + * + * A line-oriented login shell: username, password, then commands, which are queued to + * the world thread exactly as the local CLI's are. This is the second protocol the + * shared networking engine was built to carry — the ACE reactor, acceptor and + * hand-rolled output buffer it used to need are all gone, and the transport supplies + * them for free. + */ +class RASession : public net::ISession +{ + public: + + RASession(); + ~RASession() override; + + // ── net::ISession ──────────────────────────────────────────────────────── + + void setPeerAddress(const std::string& address) override { m_address = address; } + void setSender(net::Sender sender) override { m_sender = std::move(sender); } + void setCloser(net::Closer closer) override { m_closer = std::move(closer); } + + std::vector onConnect() override; + std::vector onData(const uint8_t* data, size_t len) override; + void onClose() override; + + bool closed() const override { return m_closed.load(); } + + private: + + /// Stages of the login handshake. + enum Stage + { + NONE, ///< nothing entered yet; awaiting the username + LG, ///< username accepted; awaiting the password + OK ///< authenticated; accepting commands + }; + + /// Write a line to the peer. Thread-safe: the world thread calls this from the + /// command callbacks, and the Sender is a no-op once the connection is gone. + void Send(const char* message); + + /// Ask the transport to close this connection. + void Close(); + + /// Handle one complete line of input (network thread). + void HandleLine(const std::string& line); + + void HandleUsername(const std::string& line); + void HandlePassword(const std::string& line); + void HandleCommand(const std::string& line); + + /// World-thread callbacks handed to CliCommandHolder. + static void CommandPrint(void* callbackArg, const char* text); + static void CommandFinished(void* callbackArg, bool success); + + /// Release the keep-alive taken when a command was queued. + void ReleaseCommand(); + + std::string m_address; + net::Sender m_sender; + net::Closer m_closer; + + std::atomic m_closed; + + /// Partial line carried over between reads (telnet is a byte stream too). + std::string m_input; + + Stage m_stage; + uint32 m_accountId; + AccountTypes m_accessLevel; + + bool m_secure; ///< drop the connection on a bad user/password + bool m_stricted; ///< forbid SEC_CONSOLE-only commands remotely + AccountTypes m_minLevel; ///< lowest account level allowed to connect + + // A queued command is executed later, on the world thread, and hands back a raw + // pointer to this session. Hold a reference to ourselves for as long as any + // command is outstanding, so the session cannot be destroyed underneath a + // callback if the peer disconnects mid-command. + std::mutex m_commandLock; + std::shared_ptr m_keepAlive; + int m_commandsPending; +}; + +/** + * @brief Owns the remote-access listening socket. + */ +class RaServer +{ + public: + + RaServer() : m_started(false) {} + ~RaServer() { Stop(); } + + /// Bind and start accepting RA connections. Returns false on failure. + bool Start(uint16_t port, const std::string& bindIp); + void Stop(); + + private: + + net::Server m_server; + bool m_started; +}; + +#endif +/// @} diff --git a/src/mangosd/RAThread.cpp b/src/mangosd/RAThread.cpp deleted file mode 100644 index 3aedeb3d1b..0000000000 --- a/src/mangosd/RAThread.cpp +++ /dev/null @@ -1,438 +0,0 @@ -/** - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2025 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -/// \addtogroup mangosd -/// @{ -/// \file - -#include -#include -#include -#include -#include - -#include "RAThread.h" - -#include "AccountMgr.h" -#include "Log.h" -#include "World.h" -#include "Util.h" -#include "Language.h" -#include "Config.h" -#include "ObjectMgr.h" - -class RASocket: protected ACE_Svc_Handler < ACE_SOCK_STREAM, ACE_NULL_SYNCH> -{ - typedef ACE_Svc_Handler < ACE_SOCK_STREAM, ACE_NULL_SYNCH> Base; - - enum { RA_BUFF_SIZE = 8192 }; - - public: - friend class ACE_Acceptor; - - int sendf(const char* msg) - { - ACE_GUARD_RETURN(ACE_Thread_Mutex, Guard, outBufferLock, -1); - - if (closing_) - { - return -1; - } - - int msgLen = strlen(msg); - - if (msgLen + outputBufferLen > RA_BUFF_SIZE) - { - return -1; - } - - ACE_OS::memcpy(outputBuffer + outputBufferLen, msg, msgLen); - outputBufferLen += msgLen; - - if (!outActive) - { - if (reactor()->schedule_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - { - sLog.outError("RASocket::sendf error while schedule_wakeup"); - return -1; - } - outActive = true; - } - return 0; - } - - protected: - RASocket(void) : Base(),outBufferLock(), outActive(false), inputBufferLen(0), - outputBufferLen(0), stage(NONE) - { - bSecure = sConfig.GetBoolDefault("RA.Secure", true); - bStricted = sConfig.GetBoolDefault("RA.Stricted", false); - iMinLevel = AccountTypes(sConfig.GetIntDefault("RA.MinLevel", SEC_ADMINISTRATOR)); - reference_counting_policy().value(ACE_Event_Handler::Reference_Counting_Policy::ENABLED); - } - - virtual ~RASocket(void) - { - peer().close(); - sLog.outRALog("Connection was closed."); - } - - int open(void* unused) override - { - if (reactor()->register_handler(this, ACE_Event_Handler::READ_MASK | ACE_Event_Handler::WRITE_MASK) == -1) - { - sLog.outError("RASocket::open: unable to accept connection from client, error = %s", ACE_OS::strerror(errno)); - return -1; - } - - ACE_INET_Addr remote_addr; - - if (peer().get_remote_addr(remote_addr) == -1) - { - sLog.outError("RASocket::open: peer ().get_remote_addr errno = %s", ACE_OS::strerror(errno)); - return -1; - } - - sLog.outRALog("Incoming connection from %s.", remote_addr.get_host_addr()); - - ///- print Motd - sendf(sWorld.GetMotd()); - sendf("\r\n"); - sendf(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_USER)); - - return 0; - } - - int close(u_long unused) override - { - if (closing_) - { - return -1; - } - - shutdown(); - - closing_ = true; - - remove_reference(); - return 0; - } - - int handle_input(ACE_HANDLE = ACE_INVALID_HANDLE) override - { - if (closing_) - { - sLog.outError("Called RASocket::handle_input with closing_ = true"); - return -1; - } - - size_t readBytes = peer().recv(inputBuffer + inputBufferLen, RA_BUFF_SIZE - inputBufferLen - 1); - - if (readBytes <= 0) - { - DEBUG_LOG("read %zu bytes in RASocket::handle_input", readBytes); - return -1; - } - - ///- Discard data after line break or line feed - bool gotenter = false; - for (; readBytes > 0 ; --readBytes) - { - char c = inputBuffer[inputBufferLen]; - if (c == '\r' || c == '\n') - { - gotenter = true; - break; - } - ++inputBufferLen; - } - - if (gotenter) - { - inputBuffer[inputBufferLen] = 0; - inputBufferLen = 0; - switch (stage) - { - case NONE: - { - std::string szLogin = inputBuffer; - accId = sAccountMgr.GetId(szLogin); - - ///- If the user is not found, deny access - if (!accId) - { - sendf("-No such user.\r\n"); - sLog.outRALog("User %s does not exist.", szLogin.c_str()); - if (bSecure) - { - handle_output(); - return -1; - } - sendf("\r\n"); - sendf(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_USER)); - break; - } - - accAccessLevel = sAccountMgr.GetSecurity(accId); - - ///- if gmlevel is too low, deny access - if (accAccessLevel < iMinLevel) - { - sendf("-Not enough privileges.\r\n"); - sLog.outRALog("User %s has no privilege.", szLogin.c_str()); - if (bSecure) - { - handle_output(); - return -1; - } - sendf("\r\n"); - sendf(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_USER)); - break; - } - - ///- allow by remotely connected admin use console level commands dependent from config setting - if (accAccessLevel >= SEC_ADMINISTRATOR && !bStricted) - { - accAccessLevel = SEC_CONSOLE; - } - - stage = LG; - sendf(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_PASS)); - break; - } - case LG: - { - // login+pass ok - std::string pw = inputBuffer; - - if (sAccountMgr.CheckPassword(accId, pw)) - { - stage = OK; - - sendf("+Logged in.\r\n"); - sLog.outRALog("User account %u has logged in.", accId); - sendf("mangos>"); - } - else - { - ///- Else deny access - sendf("-Wrong pass.\r\n"); - sLog.outRALog("User account %u has failed to log in.", accId); - if (bSecure) - { - handle_output(); - return -1; - } - sendf("\r\n"); - sendf(sObjectMgr.GetMangosStringForDBCLocale(LANG_RA_PASS)); - } - break; - } - case OK: - if (strlen(inputBuffer)) - { - sLog.outRALog("Got '%s' cmd.", inputBuffer); - if (strncmp(inputBuffer, "quit", 4) == 0) - { - return -1; - } - else - { - CliCommandHolder* cmd = new CliCommandHolder(accId, accAccessLevel, this, inputBuffer, - &RASocket::zprint, &RASocket::commandFinished); - sWorld.QueueCliCommand(cmd); - } - } - else - { - sendf("mangos>"); - } - break; - } - } - // no enter yet? wait for next input... - return 0; - } - - int handle_output(ACE_HANDLE h = ACE_INVALID_HANDLE) override - { - ACE_GUARD_RETURN(ACE_Thread_Mutex, Guard, outBufferLock, -1); - - if (closing_) - { - return -1; - } - - if (!outputBufferLen) - { - if (reactor()->cancel_wakeup(this, ACE_Event_Handler::WRITE_MASK) == -1) - { - return -1; - } - outActive = false; - return 0; - } -#ifdef MSG_NOSIGNAL - ssize_t n = peer().send(outputBuffer, outputBufferLen, MSG_NOSIGNAL); -#else - ssize_t n = peer().send(outputBuffer, outputBufferLen); -#endif // MSG_NOSIGNAL - - if (n <= 0) - { - return -1; - } - - ACE_OS::memmove(outputBuffer, outputBuffer + n, outputBufferLen - n); - - outputBufferLen -= n; - - return 0; - } - - int handle_close(ACE_HANDLE h = ACE_INVALID_HANDLE, - ACE_Reactor_Mask mask = ACE_Event_Handler::ALL_EVENTS_MASK) override - { - if (closing_) - { - return -1; - } - - ACE_GUARD_RETURN(ACE_Thread_Mutex, Guard, outBufferLock, -1); - - closing_ = true; - - if (h == ACE_INVALID_HANDLE) - { - peer().close_writer(); - } - remove_reference(); - return 0; - } - - private: - bool outActive; - - char inputBuffer[RA_BUFF_SIZE]; - uint32 inputBufferLen; - - char outputBuffer[RA_BUFF_SIZE]; - uint32 outputBufferLen; - - uint32 accId; - AccountTypes accAccessLevel; - bool bSecure; /**< kick on wrong pass, non exist. user OR user with no priv. will protect from DOS, bruteforce attacks */ - bool bStricted; /**< not allow execute console only commands (SEC_CONSOLE) remotly */ - AccountTypes iMinLevel; - - enum - { - NONE, // initial value - LG, // only login was entered - OK // both login and pass were given, they were correct and user has enough priv. - } stage; - - ACE_Thread_Mutex outBufferLock; - - static void zprint(void* callbackArg, const char* szText) - { - if (!szText) - { - return; - } - - ((RASocket*)callbackArg)->sendf(szText); - } - - static void commandFinished(void* callbackArg, bool success) - { - RASocket* raSocket = (RASocket*)callbackArg; - raSocket->sendf("mangos>"); - } -}; - -/** - * Initializes the remote access listener thread with the configured host and port. - */ -RAThread::RAThread(uint16 port, const char* host) : listen_addr(port, host) -{ - ACE_Reactor_Impl* imp = 0; - - imp = new ACE_TP_Reactor(); - imp->max_notify_iterations(128); - - m_Reactor = new ACE_Reactor(imp, 1); - m_Acceptor = new RAAcceptor; -} - -/** - * Releases the remote access reactor and acceptor resources. - */ -RAThread::~RAThread() -{ - delete m_Reactor; - delete m_Acceptor; -} - -/** - * Opens the remote access listener socket and activates the thread. - */ -int RAThread::open(void* unused) -{ - if (m_Acceptor->open(listen_addr, m_Reactor, ACE_NONBLOCK) == -1) - { - sLog.outError("MaNGOS RA can not bind to port %d on %s\n", listen_addr.get_port_number(), listen_addr.get_host_addr()); - return -1; - } - activate(); - return 0; -} - -/** - * Runs the remote access reactor event loop until shutdown. - */ -int RAThread::svc() -{ - sLog.outString("Remote Access Thread started (listening on %s:%d)", - listen_addr.get_host_addr(), - listen_addr.get_port_number()); - - while (!m_Reactor->reactor_event_loop_done()) - { - ACE_Time_Value interval(0, 10000); - - if (m_Reactor->run_reactor_event_loop(interval) == -1) - { - break; - } - - if (World::IsStopped()) - { - m_Acceptor->close(); - break; - } - } - sLog.outString("Remote Access Thread stopped"); - return 0; -} diff --git a/src/mangosd/SOAP/SoapThread.cpp b/src/mangosd/SOAP/SoapThread.cpp index 3ba9e4f577..2cc5912809 100644 --- a/src/mangosd/SOAP/SoapThread.cpp +++ b/src/mangosd/SOAP/SoapThread.cpp @@ -29,6 +29,7 @@ #include "World.h" #include "soapStub.h" +#include /** * Runs the SOAP listener loop on the configured host and port. @@ -47,8 +48,13 @@ void SoapThread(const std::string& host, uint16 port) if (!soap_valid_socket(soap_bind(&soap, host.c_str(), port, 100))) { - sLog.outError("SoapThread: couldn't bind to %s:%d", host.c_str(), port); - exit(-1); + // Losing the SOAP port is not a reason to take the world down with us: the world + // is already running, and exiting from this thread would strand it mid-tick with + // players connected. Report and leave; the rest of the server carries on without + // remote SOAP, exactly as the RA listener does when its own bind fails. + sLog.outError("SoapThread: couldn't bind to %s:%d, SOAP disabled", host.c_str(), port); + soap_done(&soap); + return; } sLog.outString("SoapThread: Bound to http://%s:%d", host.c_str(), port); @@ -102,7 +108,8 @@ int ns1__executeCommand(soap* soap, char* command, char** result) if (!sAccountMgr.CheckPassword(accountId, soap->passwd)) { - sLog.outString("SoapThread: Client sent an invalid password for account %s", soap->passwd); + // Log the account, never the secret: this line lands in a file that gets shared. + sLog.outString("SoapThread: Client sent an invalid password for account %s", soap->userid); return 401; } diff --git a/src/mangosd/SOAP/SoapThread.h b/src/mangosd/SOAP/SoapThread.h index 3b94b65e27..f208fc5e26 100644 --- a/src/mangosd/SOAP/SoapThread.h +++ b/src/mangosd/SOAP/SoapThread.h @@ -27,7 +27,6 @@ #include "Common.h" -#include #include #include diff --git a/src/mangosd/Service.h b/src/mangosd/Service.h new file mode 100644 index 0000000000..ad011cf9ad --- /dev/null +++ b/src/mangosd/Service.h @@ -0,0 +1,77 @@ +/** + * MaNGOS is a full featured server for World of Warcraft, supporting + * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 + * + * Copyright (C) 2005-2026 MaNGOS + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * World of Warcraft, and all World of Warcraft or Warcraft art, images, + * and lore are copyrighted by Blizzard Entertainment, Inc. + */ + +/// \addtogroup mangosd +/// @{ +/// \file + +#ifndef MANGOS_H_SERVICE +#define MANGOS_H_SERVICE + +/** + * @brief One auxiliary thread of the daemon, with a uniform lifecycle. + * + * The freeze watchdog, the console reader, the remote-access listener and SOAP each used to + * carry their own bespoke start and their own bespoke shutdown dance, hand-wired into + * Master::Run() in an order that existed only as the sequence those statements happened to + * appear in. Nobody had written that order down, and nothing enforced it — so the teardown + * was correct by coincidence rather than by construction, and every new background thread + * added another private ritual to remember. + * + * Behind this interface they all start the same way, are asked to stop the same way, and are + * joined in exactly the reverse of the order they were started. + * + * The stop *signal* is not part of this interface: it is global (World::StopNow / + * World::IsStopped), and every service loop already watches it. RequestStop() exists only + * for the services that park in a blocking call and therefore need a nudge before they can + * notice a flag that was set while they were asleep. + */ +class IService +{ + public: + + virtual ~IService() = default; + + /// Name of this service, for the shutdown log. + virtual const char* Name() const = 0; + + /// Bring the service up. Called exactly once. + virtual void Start() = 0; + + /** + * @brief Wake a service that is parked in a blocking call. + * + * The world has already been asked to stop by the time this runs; a service whose + * loop simply polls World::IsStopped() needs nothing here and can take the default. + * Only a service blocked inside a syscall it cannot otherwise leave (the console + * reader, sitting in fgets()) has to override this. + */ + virtual void RequestStop() {} + + /// Block until the service's thread has exited. Called once, after RequestStop(). + virtual void Join() = 0; +}; + +#endif +/// @} diff --git a/src/mangosd/WorldThread.cpp b/src/mangosd/WorldThread.cpp deleted file mode 100644 index ca83c8837f..0000000000 --- a/src/mangosd/WorldThread.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/** - * MaNGOS is a full featured server for World of Warcraft, supporting - * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 - * - * Copyright (C) 2005-2025 MaNGOS - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - * World of Warcraft, and all World of Warcraft or Warcraft art, images, - * and lore are copyrighted by Blizzard Entertainment, Inc. - */ - -/** \file - \ingroup mangosd - */ - -#include "Common.h" -#include "WorldSocket.h" -#include "WorldSocketMgr.h" -#include "World.h" -#include "WorldThread.h" -#include "Timer.h" -#include "ObjectAccessor.h" -#include "MapManager.h" -#include "Database/DatabaseEnv.h" - -#include -#include - -#ifdef ENABLE_ELUNA -#include "LuaEngine.h" -#endif /* ENABLE_ELUNA */ - -#define WORLD_SLEEP_CONST 50 - -#ifdef WIN32 -#include "ServiceWin32.h" -extern int m_ServiceStatus; -#endif - -/** - * Initializes the world thread listener with the configured host and port. - */ -WorldThread::WorldThread(uint16 port, const char* host) : listen_addr(port, host) -{ -} - -/** - * Starts the world socket network listener and activates the world thread. - */ -int WorldThread::open(void* unused) -{ - if (sWorldSocketMgr->StartNetwork(listen_addr) == -1) - { - sLog.outError("Failed to start network"); - Log::WaitBeforeContinueIfNeed(); - World::StopNow(ERROR_EXIT_CODE); - return -1; - } - - activate(); - return 0; -} - -/// Heartbeat for the World -int WorldThread::svc() -{ - uint32 realCurrTime = 0; - uint32 realPrevTime = getMSTime(); - sLog.outString("World Updater Thread started (%dms min update interval)", WORLD_SLEEP_CONST); - - ///- While we have not World::m_stopEvent, update the world - while (!World::IsStopped()) - { - ++World::m_worldLoopCounter; - realCurrTime = getMSTime(); - - uint32 diff = getMSTimeDiff(realPrevTime, realCurrTime); - - sWorld.Update(diff); - realPrevTime = realCurrTime; - - uint32 executionTimeDiff = getMSTimeDiff(realCurrTime, getMSTime()); - - // we know exactly how long it took to update the world, if the update took less than WORLD_SLEEP_CONST, sleep for WORLD_SLEEP_CONST - world update time - if (executionTimeDiff < WORLD_SLEEP_CONST) - { - std::this_thread::sleep_for(std::chrono::milliseconds(WORLD_SLEEP_CONST - executionTimeDiff)); - } -#ifdef _WIN32 - if (m_ServiceStatus == 0) // service stopped - { - World::StopNow(SHUTDOWN_EXIT_CODE); - } - - while (m_ServiceStatus == 2) // service paused - { - Sleep(1000); - } -#endif - } - sLog.outString("[shutdown] world loop stopped; entering world-thread shutdown tail"); - sLog.outString("[shutdown] KickAll: saving + kicking players..."); - sWorld.KickAll(); // save and kick all players - sLog.outString("[shutdown] KickAll done"); - sLog.outString("[shutdown] final UpdateSessions..."); - sWorld.UpdateSessions(1); // real players unload required UpdateSessions call - sLog.outString("[shutdown] final UpdateSessions done"); - sLog.outString("[shutdown] StopNetwork: ending reactor + joining network threads..."); - sWorldSocketMgr->StopNetwork(); - sLog.outString("[shutdown] StopNetwork done"); - sLog.outString("[shutdown] UnloadAll: unloading maps + MapUpdater teardown..."); - sMapMgr.UnloadAll(); // unload all grids (including locked in memory) - sLog.outString("[shutdown] UnloadAll returned; world thread exiting"); - - sLog.outString("World Updater Thread stopped"); - return 0; -} diff --git a/src/mangosd/mangosd.conf.dist.in b/src/mangosd/mangosd.conf.dist.in index 63538ef454..235acacf0e 100644 --- a/src/mangosd/mangosd.conf.dist.in +++ b/src/mangosd/mangosd.conf.dist.in @@ -769,6 +769,16 @@ SD3ErrorLogFile = "scriptdev3-errors.log" # Default: 1 (true) # 0 (false) # +# Console.Style +# Presentation of the startup output on the console. The log file is the +# same either way. +# Default: "auto" (draw the boxed, coloured loading UI when stdout is an +# interactive console; plain text when it is redirected +# to a file or a pipe, or when running as a service) +# "fancy" (force the loading UI on, for a terminal that cannot be +# detected as one) +# "plain" (never; plain text, as older releases printed it) +# # WaitAtStartupError # After startup error report wait or some time before continue (and possible close console window) # -1 (wait until press) @@ -875,6 +885,7 @@ PetUnsummonAtMount = 0 Event.Announce = 0 BeepAtStart = 1 ShowProgressBars = 1 +Console.Style = "auto" WaitAtStartupError = 10 PlayerCommands = 0 Motd = "Welcome to Mangos One" diff --git a/src/mangosd/mangosd.cpp b/src/mangosd/mangosd.cpp index 67fdc2407e..368f239a25 100644 --- a/src/mangosd/mangosd.cpp +++ b/src/mangosd/mangosd.cpp @@ -2,7 +2,7 @@ * MaNGOS is a full featured server for World of Warcraft, supporting * the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 * - * Copyright (C) 2005-2025 MaNGOS + * Copyright (C) 2005-2026 MaNGOS * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,56 +22,36 @@ * and lore are copyrighted by Blizzard Entertainment, Inc. */ -/** - * @file mangosd.cpp - * @brief World server daemon entry point - * - * This file implements the main entry point for the MaNGOS world server - * daemon (mangosd). It handles: - * - Command line argument parsing - * - Service/daemon mode initialization - * - Database connections (World, Character, Login) - * - Server subsystem initialization - * - Multiple thread management (World, CLI, Auto-freeze, SOAP) - * - Main event loop and shutdown - * - * The world server is responsible for running the game simulation, - * handling player connections, and managing game state. - * - * @addtogroup mangosd Mangos Daemon - * @{ - */ +/// \addtogroup mangosd MaNGOS Daemon +/// @{ +/// \file +/// +/// Entry point of the world daemon: parse the command line, load the config, handle the +/// service/daemon plumbing, then hand over to Master, which owns everything with a +/// lifetime and runs the world to completion. -#include -#include -#include #include "Auth/OpenSSLProvider.h" -#include -#include - #include "Common.h" -#include "Database/DatabaseEnv.h" #include "Config/Config.h" +#include "ConsoleStyle.h" +#include "Database/DatabaseEnv.h" #include "GitRevision.h" -#include "ProgressBar.h" #include "Log.h" +#include "Master.h" +#include "ProgressBar.h" +#include "ScriptMgr.h" +#include "StartupUI.h" #include "SystemConfig.h" +#include "Util.h" +#include "World.h" #include "AuctionHouseBot.h" #include "revision_data.h" -#include "World.h" -#include "Util.h" -#include "DBCStores.h" -#include "MassMailMgr.h" -#include "ScriptMgr.h" -#include "WorldThread.h" -#include "CliThread.h" -#include "AFThread.h" -#include "RAThread.h" - -#ifdef ENABLE_SOAP -#include "SOAP/SoapThread.h" -#endif +#include +#include +#include +#include +#include #ifdef _WIN32 #include "ServiceWin32.h" @@ -81,6 +61,7 @@ char serviceName[] = "MaNGOS"; // service short name char serviceLongName[] = "MaNGOS World Service"; // service long name char serviceDescription[] = "MaNGOS World Service - no description available"; +/// -1 = not a service, 0 = stopped, 2 = paused int m_ServiceStatus = -1; #else @@ -93,310 +74,245 @@ DatabaseType LoginDatabase; ///< Accessor to the uint32 realmID = 0; ///< Id of the realm -/** - * @brief Clear online status for realm accounts on startup - * - * Resets the 'online' status for all accounts that were marked as - * connected to this realm. This handles cases where the server - * crashed without properly logging out all players. - * - * Also resets character online status and battleground instance data. - */ -static void clear_online_accounts() -{ - // Cleanup online status for characters hosted at current realm - /// \todo Only accounts with characters logged on *this* realm should have online status reset. Move the online column from 'account' to 'realmcharacters'? - LoginDatabase.PExecute("UPDATE `account` SET `active_realm_id` = 0, `os` = '' WHERE `active_realm_id` = '%u'", realmID); - - CharacterDatabase.Execute("UPDATE `characters` SET `online` = 0 WHERE `online`<>0"); - - // Battleground instance ids reset at server restart - CharacterDatabase.Execute("UPDATE `character_battleground_data` SET `instance_id` = 0"); -} - -/** - * @brief Initialize database connections - * @return true if all databases connected successfully, false otherwise - * - * Connects to three databases: - * - World Database: Contains game data (creatures, items, quests, etc.) - * - Character Database: Contains player character data - * - Login Database: References realm authentication data - * - * Validates database versions and connection counts from configuration. - * On failure, properly cleans up any connections that were established. - */ -static bool start_db() +namespace { - ///- Get world database info from configuration file - std::string dbstring = sConfig.GetStringDefault("WorldDatabaseInfo", ""); - int nConnections = sConfig.GetIntDefault("WorldDatabaseConnections", 1); - if (dbstring.empty()) + /// Handle termination signals by asking the world to stop; the shutdown itself + /// unwinds on the main thread, where it is safe to do real work. + void OnSignal(int s) { - sLog.outError("Database not specified in configuration file"); - return false; - } - sLog.outString("World Database total connections: %i", nConnections + 1); - - ///- Initialise the world database - if (!WorldDatabase.Initialize(dbstring.c_str(), nConnections)) - { - sLog.outError("Can not connect to world database %s", dbstring.c_str()); - return false; - } + switch (s) + { + case SIGINT: + World::StopNow(RESTART_EXIT_CODE); + break; + case SIGTERM: +#ifdef _WIN32 + case SIGBREAK: +#endif + World::StopNow(SHUTDOWN_EXIT_CODE); + break; + } - ///- Check the World database version - if (!WorldDatabase.CheckDatabaseVersion(DATABASE_WORLD)) - { - ///- Wait for already started DB delay threads to end - WorldDatabase.HaltDelayThread(); - return false; + signal(s, OnSignal); } - dbstring = sConfig.GetStringDefault("CharacterDatabaseInfo", ""); - nConnections = sConfig.GetIntDefault("CharacterDatabaseConnections", 1); - if (dbstring.empty()) + void HookSignals() { - sLog.outError("Character Database not specified in configuration file"); - - ///- Wait for already started DB delay threads to end - WorldDatabase.HaltDelayThread(); - return false; + signal(SIGINT, OnSignal); + signal(SIGTERM, OnSignal); +#ifdef _WIN32 + signal(SIGBREAK, OnSignal); +#endif } - sLog.outString("Character Database total connections: %i", nConnections + 1); - ///- Initialise the Character database - if (!CharacterDatabase.Initialize(dbstring.c_str(), nConnections)) + void UnhookSignals() { - sLog.outError("Can not connect to Character database %s", dbstring.c_str()); - - ///- Wait for already started DB delay threads to end - WorldDatabase.HaltDelayThread(); - return false; + signal(SIGINT, 0); + signal(SIGTERM, 0); +#ifdef _WIN32 + signal(SIGBREAK, 0); +#endif } - ///- Check the Character database version - if (!CharacterDatabase.CheckDatabaseVersion(DATABASE_CHARACTER)) + void Usage(const char* prog) { - ///- Wait for already started DB delay threads to end - WorldDatabase.HaltDelayThread(); - CharacterDatabase.HaltDelayThread(); - return false; + sLog.outString("Usage: \n %s []\n" + " -v, --version print version and exit\n\r" + " -c use config_file as configuration file\n\r" + " -a, --ahbot use config_file as ahbot configuration file\n\r" + " --console-demo [