From aa6f8153a19fbb1b596a743a276bff7b71adb3f6 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Fri, 1 Aug 2025 01:18:16 -0700 Subject: [PATCH 01/20] Tame Chat Action / Pet Chat Action (stances/commands) Hello everyone, I am on a quest to make bot's pets completely functional, focuses on solving issues #1351 , #1230 , and #1137 . This PR achieves the following: 1. Changes the current "pet" chat command to "tame", which is more intuitive that only hunters can use it. The modes are "tame name (name)", "tame id (id)", "tame family (family)", "tame rename (new name)", and "tame abandon". Tame abandon is new - it simply abandons the current pet. Also, now, if you type in "tame family" by itself, it will list the available families. See pictures below for examples. 2. Added new "pet" chat command, with the following modes: "pet passive", "pet aggressive", "pet defensive", "pet stance" (shows current pet stance), "pet attack", "pet follow", and "pet stay". Previously, the pet's stance was not changeable, and there were some less than desired effects from summonable pets - see the issues above. 3. New config option: AiPlayerbot.DefaultPetStance, which changes the stance that all bot's pets are summoned as. This makes sure when feral spirits or treants are summoned by shamans and druids, they are immediately set to this configured stance. Set as 1 as default, which is defensive. (0 = Passive, 1 = Defensive, 2 = Aggressive) 4. New config option: AiPlayerbot.PetChatCommandDebug, which enables debug messages for the "pet" chat command. By default it is set to 0, which is disabled, but if you would like to see when pet's stances are changed, or when you tell the pet to attack/follow/stay, and when pet spells are auto-toggled, this is an option. I made this for myself mainly to test the command - if anyone notices any wierd pet behavior, this will be an option to help them report it as well. 5. Modified FollowActions to not constantly execute the petfollow action, overriding any stay or attack commands issued. 6. Modified GenericActions to have TogglePetSpellAutoCastAction optionally log when spells are toggled based on AiPlayerbot.PetChatCommandDebug. 7. Modified PetAttackAction to not attack if the pet is set to passive, and not override the pet's stance to passive every time it was executed. 8. Modified CombatStrategy.cpp to not constantly issue the petattack command, respecting the "pet stay" and "pet follow" commands. Pets will still automatically attack the enemy if set to aggressive or defensive. 9. Warlocks, Priests, Hunters, Shamans, Mages, Druids, and DKs (all classes that have summons): Added a "new pet" trigger that executes the "set pet stance" action. The "new pet" trigger happens only once, upon summoning a pet. It sets the pet's stance from AiPlayerbot.DefaultPetStance's value. --- conf/playerbots.conf.dist | 10 + src/PlayerbotAIConfig.cpp | 2 + src/PlayerbotAIConfig.h | 2 + src/strategy/actions/ActionContext.h | 4 +- src/strategy/actions/ChatActionContext.h | 11 +- src/strategy/actions/FollowActions.cpp | 11 +- src/strategy/actions/GenericActions.cpp | 108 +++- src/strategy/actions/GenericActions.h | 10 + src/strategy/actions/PetAction.cpp | 553 +++++++----------- src/strategy/actions/PetAction.h | 15 +- src/strategy/actions/TameAction.cpp | 500 ++++++++++++++++ src/strategy/actions/TameAction.h | 34 ++ .../actions/WorldPacketActionContext.h | 3 + .../GenericDKNonCombatStrategy.cpp | 4 +- .../deathknight/GenericDKStrategy.cpp | 4 + .../druid/GenericDruidNonCombatStrategy.cpp | 2 + src/strategy/druid/GenericDruidStrategy.cpp | 3 +- .../generic/ChatCommandHandlerStrategy.cpp | 8 +- src/strategy/generic/CombatStrategy.cpp | 6 +- .../hunter/GenericHunterNonCombatStrategy.cpp | 2 + src/strategy/mage/FrostMageStrategy.cpp | 3 +- src/strategy/priest/GenericPriestStrategy.cpp | 1 + .../priest/PriestNonCombatStrategy.cpp | 2 + src/strategy/shaman/GenericShamanStrategy.cpp | 4 +- .../shaman/ShamanNonCombatStrategy.cpp | 4 + src/strategy/triggers/ChatTriggerContext.h | 8 +- src/strategy/triggers/GenericTriggers.cpp | 43 ++ src/strategy/triggers/GenericTriggers.h | 12 + src/strategy/triggers/TriggerContext.h | 2 + .../GenericWarlockNonCombatStrategy.cpp | 1 + 30 files changed, 1000 insertions(+), 372 deletions(-) create mode 100644 src/strategy/actions/TameAction.cpp create mode 100644 src/strategy/actions/TameAction.h diff --git a/conf/playerbots.conf.dist b/conf/playerbots.conf.dist index 3b4a676f..7b624fe8 100644 --- a/conf/playerbots.conf.dist +++ b/conf/playerbots.conf.dist @@ -699,6 +699,16 @@ AiPlayerbot.AutoUpgradeEquip = 1 # Default: 0 (disabled) AiPlayerbot.HunterWolfPet = 0 +# Default pet stance when a bot summons a pet +# 0 = Passive, 1 = Defensive, 2 = Aggressive +# Default: 1 (Defensive) +AiPlayerbot.DefaultPetStance = 1 + +# Enable/disable debug messages about pet commands +# 0 = Disabled, 1 = Enabled +# Default = 0 (disabled) +AiPlayerbot.PetChatCommandDebug = 0 + # # # diff --git a/src/PlayerbotAIConfig.cpp b/src/PlayerbotAIConfig.cpp index 8db503ac..fd6fc7e8 100644 --- a/src/PlayerbotAIConfig.cpp +++ b/src/PlayerbotAIConfig.cpp @@ -575,6 +575,8 @@ bool PlayerbotAIConfig::Initialize() autoPickTalents = sConfigMgr->GetOption("AiPlayerbot.AutoPickTalents", true); autoUpgradeEquip = sConfigMgr->GetOption("AiPlayerbot.AutoUpgradeEquip", false); hunterWolfPet = sConfigMgr->GetOption("AiPlayerbot.HunterWolfPet", 0); + defaultPetStance = sConfigMgr->GetOption("AiPlayerbot.DefaultPetStance", 1); + petChatCommandDebug = sConfigMgr->GetOption("AiPlayerbot.PetChatCommandDebug", 0); autoLearnTrainerSpells = sConfigMgr->GetOption("AiPlayerbot.AutoLearnTrainerSpells", true); autoLearnQuestSpells = sConfigMgr->GetOption("AiPlayerbot.AutoLearnQuestSpells", false); autoTeleportForLevel = sConfigMgr->GetOption("AiPlayerbot.AutoTeleportForLevel", false); diff --git a/src/PlayerbotAIConfig.h b/src/PlayerbotAIConfig.h index 620288a3..0c7a3f62 100644 --- a/src/PlayerbotAIConfig.h +++ b/src/PlayerbotAIConfig.h @@ -333,6 +333,8 @@ public: bool autoPickTalents; bool autoUpgradeEquip; int32 hunterWolfPet; + int32 defaultPetStance; + int32 petChatCommandDebug; bool autoLearnTrainerSpells; bool autoDoQuests; bool enableNewRpgStrategy; diff --git a/src/strategy/actions/ActionContext.h b/src/strategy/actions/ActionContext.h index 06ee6461..2beee322 100644 --- a/src/strategy/actions/ActionContext.h +++ b/src/strategy/actions/ActionContext.h @@ -244,7 +244,8 @@ public: creators["rpg mount anim"] = &ActionContext::rpg_mount_anim; creators["toggle pet spell"] = &ActionContext::toggle_pet_spell; - creators["pet attack"] = &ActionContext::pet_attack; + creators["pet attack"] = &ActionContext::pet_attack; + creators["set pet stance"] = &ActionContext::set_pet_stance; creators["new rpg status update"] = &ActionContext::new_rpg_status_update; creators["new rpg go grind"] = &ActionContext::new_rpg_go_grind; @@ -431,6 +432,7 @@ private: static Action* toggle_pet_spell(PlayerbotAI* ai) { return new TogglePetSpellAutoCastAction(ai); } static Action* pet_attack(PlayerbotAI* ai) { return new PetAttackAction(ai); } + static Action* set_pet_stance(PlayerbotAI* ai) { return new SetPetStanceAction(ai); } static Action* new_rpg_status_update(PlayerbotAI* ai) { return new NewRpgStatusUpdateAction(ai); } static Action* new_rpg_go_grind(PlayerbotAI* ai) { return new NewRpgGoGrindAction(ai); } diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index bc111ac6..9de7c511 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -78,9 +78,10 @@ #include "OpenItemAction.h" #include "UnlockItemAction.h" #include "UnlockTradedItemAction.h" -#include "PetAction.h" +#include "TameAction.h" #include "TellGlyphsAction.h" #include "EquipGlyphsAction.h" +#include "PetAction.h" class ChatActionContext : public NamedObjectContext { @@ -190,9 +191,11 @@ public: creators["lfg"] = &ChatActionContext::lfg; creators["calc"] = &ChatActionContext::calc; creators["wipe"] = &ChatActionContext::wipe; - creators["pet"] = &ChatActionContext::pet; + creators["tame"] = &ChatActionContext::tame; creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs + creators["pet"] = &ChatActionContext::pet; + creators["pet attack"] = &ChatActionContext::pet_attack; } private: @@ -299,9 +302,11 @@ private: static Action* join(PlayerbotAI* ai) { return new JoinGroupAction(ai); } static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); } static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); } - static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); } + static Action* tame(PlayerbotAI* botAI) { return new TameAction(botAI); } static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs + static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); } + static Action* pet_attack(PlayerbotAI* botAI) { return new PetAction(botAI, "attack"); } }; #endif diff --git a/src/strategy/actions/FollowActions.cpp b/src/strategy/actions/FollowActions.cpp index 5302748d..24079293 100644 --- a/src/strategy/actions/FollowActions.cpp +++ b/src/strategy/actions/FollowActions.cpp @@ -36,10 +36,13 @@ bool FollowAction::Execute(Event event) true, priority, true); } - if (Pet* pet = bot->GetPet()) - { - botAI->PetFollow(); - } + // This section has been commented out because it was forcing the pet to + // follow the bot on every "follow" action tick, overriding any attack or + // stay commands that might have been issued by the player. + // if (Pet* pet = bot->GetPet()) + // { + // botAI->PetFollow(); + // } // if (moved) // botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); diff --git a/src/strategy/actions/GenericActions.cpp b/src/strategy/actions/GenericActions.cpp index a7c2a9cb..277175e7 100644 --- a/src/strategy/actions/GenericActions.cpp +++ b/src/strategy/actions/GenericActions.cpp @@ -4,9 +4,19 @@ */ #include "GenericActions.h" - +#include "PlayerbotAI.h" +#include "Player.h" +#include "Pet.h" +#include "PlayerbotAIConfig.h" #include "CreatureAI.h" #include "Playerbots.h" +#include "CharmInfo.h" +#include "SharedDefines.h" +#include "ObjectGuid.h" +#include "SpellMgr.h" +#include "SpellInfo.h" +#include +#include enum PetSpells { @@ -100,6 +110,11 @@ bool TogglePetSpellAutoCastAction::Execute(Event event) toggled = true; } } + + // Debug message if pet spells have been toggled and debug is enabled + if (toggled && sPlayerbotAIConfig->petChatCommandDebug == 1) + botAI->TellMaster("Pet autocast spells have been toggled."); + return toggled; } @@ -107,22 +122,23 @@ bool PetAttackAction::Execute(Event event) { Guardian* pet = bot->GetGuardianPet(); if (!pet) - { return false; - } + + // Do not attack if the pet's stance is set to "passive". + if (pet->GetReactState() == REACT_PASSIVE) + return false; Unit* target = AI_VALUE(Unit*, "current target"); if (!target) - { return false; - } if (!bot->IsValidAttackTarget(target)) - { return false; - } - pet->SetReactState(REACT_PASSIVE); + // This section has been commented because it was overriding the + // pet's stance to "passive" every time the attack action was executed. + // pet->SetReactState(REACT_PASSIVE); + pet->ClearUnitState(UNIT_STATE_FOLLOW); pet->AttackStop(); pet->SetTarget(target->GetGUID()); @@ -136,3 +152,79 @@ bool PetAttackAction::Execute(Event event) pet->ToCreature()->AI()->AttackStart(target); return true; } + +bool SetPetStanceAction::Execute(Event /*event*/) +{ + // Get the bot player object from the AI + Player* bot = botAI->GetBot(); + + // Prepare a list to hold all controlled pet and guardian creatures + std::vector targets; + + // Add the bot's main pet (if it exists) to the target list + Pet* pet = bot->GetPet(); + if (pet) + targets.push_back(pet); + + // Loop through all units controlled by the bot (could be pets, guardians, etc.) + for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr) + { + // Only add creatures (skip players, vehicles, etc.) + Creature* creature = dynamic_cast(*itr); + if (!creature) + continue; + // Avoid adding the main pet twice + if (pet && creature == pet) + continue; + targets.push_back(creature); + } + + // If there are no controlled pets or guardians, notify the player and exit + if (targets.empty()) + { + botAI->TellError("You have no pet or guardian pet."); + return false; + } + + // Get the default pet stance from the configuration + int32 stance = sPlayerbotAIConfig->defaultPetStance; + ReactStates react = REACT_DEFENSIVE; + std::string stanceText = "defensive (from config, fallback)"; + + // Map the config stance integer to a ReactStates value and a message + switch (stance) + { + case 0: + react = REACT_PASSIVE; + stanceText = "passive (from config)"; + break; + case 1: + react = REACT_DEFENSIVE; + stanceText = "defensive (from config)"; + break; + case 2: + react = REACT_AGGRESSIVE; + stanceText = "aggressive (from config)"; + break; + default: + react = REACT_DEFENSIVE; + stanceText = "defensive (from config, fallback)"; + break; + } + + // Apply the stance to all target creatures (pets/guardians) + for (Creature* target : targets) + { + target->SetReactState(react); + CharmInfo* charmInfo = target->GetCharmInfo(); + // If the creature has a CharmInfo, set the player-visible stance as well + if (charmInfo) + charmInfo->SetPlayerReactState(react); + } + + // If debug is enabled in config, inform the master of the new stance + if (sPlayerbotAIConfig->petChatCommandDebug == 1) + botAI->TellMaster("Pet stance set to " + stanceText + " (applied to all pets/guardians)."); + + return true; +} diff --git a/src/strategy/actions/GenericActions.h b/src/strategy/actions/GenericActions.h index d3347df5..1bc8d77e 100644 --- a/src/strategy/actions/GenericActions.h +++ b/src/strategy/actions/GenericActions.h @@ -7,6 +7,8 @@ #define _PLAYERBOT_GENERICACTIONS_H #include "AttackAction.h" +#include "Action.h" +#include "PlayerbotAI.h" class PlayerbotAI; @@ -33,4 +35,12 @@ public: virtual bool Execute(Event event) override; }; +class SetPetStanceAction : public Action +{ +public: + SetPetStanceAction(PlayerbotAI* botAI) : Action(botAI, "set pet stance") {} + + bool Execute(Event event) override; +}; + #endif diff --git a/src/strategy/actions/PetAction.cpp b/src/strategy/actions/PetAction.cpp index dae760a7..e149b42e 100644 --- a/src/strategy/actions/PetAction.cpp +++ b/src/strategy/actions/PetAction.cpp @@ -4,373 +4,254 @@ */ #include "PetAction.h" -#include -#include -#include + +#include "CharmInfo.h" +#include "Creature.h" +#include "CreatureAI.h" #include "Pet.h" -#include "SpellMgr.h" -#include "DBCStructure.h" -#include "Log.h" -#include "ObjectMgr.h" #include "Player.h" #include "PlayerbotAI.h" -#include "PlayerbotFactory.h" -#include -#include -#include "WorldSession.h" - -bool IsExoticPet(const CreatureTemplate* creature) -{ - // Use the IsExotic() method from CreatureTemplate - return creature && creature->IsExotic(); -} - -bool HasBeastMastery(Player* bot) -{ - // Beast Mastery talent aura ID for WotLK is 53270 - return bot->HasAura(53270); -} +#include "SharedDefines.h" bool PetAction::Execute(Event event) { + // Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.) std::string param = event.getParam(); - std::istringstream iss(param); - std::string mode, value; - iss >> mode; - std::getline(iss, value); - value.erase(0, value.find_first_not_of(" ")); // trim leading spaces + if (param.empty() && !defaultCmd.empty()) + { + param = defaultCmd; + } - bool found = false; - - // Reset lastPetName/Id each time - lastPetName = ""; - lastPetId = 0; - - if (mode == "name" && !value.empty()) + if (param.empty()) { - found = SetPetByName(value); - } - else if (mode == "id" && !value.empty()) - { - try - { - uint32 id = std::stoul(value); - found = SetPetById(id); - } - catch (...) - { - botAI->TellError("Invalid pet id."); - } - } - else if (mode == "family" && !value.empty()) - { - found = SetPetByFamily(value); - } - else if (mode == "rename" && !value.empty()) - { - found = RenamePet(value); - } - else - { - botAI->TellError("Usage: pet name | pet id | pet family | pet rename "); + // If no parameter is provided, show usage instructions and return. + botAI->TellError("Usage: pet "); return false; } - if (!found) - return false; - - // For non-rename commands, initialize pet and give feedback - if (mode != "rename") - { - Player* bot = botAI->GetBot(); - PlayerbotFactory factory(bot, bot->GetLevel()); - factory.InitPet(); - factory.InitPetTalents(); - - if (!lastPetName.empty() && lastPetId != 0) - { - std::ostringstream oss; - oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << "."; - botAI->TellMaster(oss.str()); - } - else - { - botAI->TellMaster("Pet changed and initialized!"); - } - } - - return true; -} - -bool PetAction::SetPetByName(const std::string& name) -{ - // Convert the input to lowercase for case-insensitive comparison - std::string lowerName = name; - std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); - - CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); Player* bot = botAI->GetBot(); - for (auto itr = creatures->begin(); itr != creatures->end(); ++itr) - { - const CreatureTemplate& creature = itr->second; - std::string creatureName = creature.Name; - std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower); + // Collect all controlled pets and guardians, except totems, into the targets vector. + std::vector targets; + Pet* pet = bot->GetPet(); + if (pet) + targets.push_back(pet); - // Only match if names match (case-insensitive) - if (creatureName == lowerName) + for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr) + { + Creature* creature = dynamic_cast(*itr); + if (!creature) + continue; + if (pet && creature == pet) + continue; + if (creature->IsTotem()) + continue; + targets.push_back(creature); + } + + // If no pets or guardians are found, notify and return. + if (targets.empty()) + { + botAI->TellError("You have no pet or guardian pet."); + return false; + } + + ReactStates react; + std::string stanceText; + + // Handle stance commands: aggressive, defensive, or passive. + if (param == "aggressive") + { + react = REACT_AGGRESSIVE; + stanceText = "aggressive"; + } + else if (param == "defensive") + { + react = REACT_DEFENSIVE; + stanceText = "defensive"; + } + else if (param == "passive") + { + react = REACT_PASSIVE; + stanceText = "passive"; + } + // The "stance" command simply reports the current stance of each pet/guardian. + else if (param == "stance") + { + for (Creature* target : targets) { - // Check if the pet is tameable at all - if (!creature.IsTameable(true)) + std::string type = target->IsPet() ? "pet" : "guardian"; + std::string name = target->GetName(); + std::string stance; + switch (target->GetReactState()) + { + case REACT_AGGRESSIVE: + stance = "aggressive"; + break; + case REACT_DEFENSIVE: + stance = "defensive"; + break; + case REACT_PASSIVE: + stance = "passive"; + break; + default: + stance = "unknown"; + break; + } + botAI->TellMaster("Current stance of " + type + " \"" + name + "\": " + stance + "."); + } + return true; + } + // The "attack" command forces pets/guardians to attack the master's selected target. + else if (param == "attack") + { + // Try to get the master's selected target. + Player* master = botAI->GetMaster(); + Unit* targetUnit = nullptr; + + if (master) + { + ObjectGuid masterTargetGuid = master->GetTarget(); + if (!masterTargetGuid.IsEmpty()) + { + targetUnit = botAI->GetUnit(masterTargetGuid); + } + } + + // If no valid target is selected, show an error and return. + if (!targetUnit) + { + botAI->TellError("No valid target selected by master."); + return false; + } + if (!targetUnit->IsAlive()) + { + botAI->TellError("Target is not alive."); + return false; + } + if (!bot->IsValidAttackTarget(targetUnit)) + { + botAI->TellError("Target is not a valid attack target for the bot."); + return false; + } + + bool didAttack = false; + // For each controlled pet/guardian, command them to attack the selected target. + for (Creature* petCreature : targets) + { + CharmInfo* charmInfo = petCreature->GetCharmInfo(); + if (!charmInfo) continue; - // Exotic pet check with talent requirement - if (IsExoticPet(&creature) && !HasBeastMastery(bot)) + petCreature->ClearUnitState(UNIT_STATE_FOLLOW); + // Only command attack if not already attacking the target, or if not currently under command attack. + if (petCreature->GetVictim() != targetUnit || + (petCreature->GetVictim() == targetUnit && !charmInfo->IsCommandAttack())) { - botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); - return false; + if (petCreature->GetVictim()) + petCreature->AttackStop(); + + if (!petCreature->IsPlayer() && petCreature->ToCreature()->IsAIEnabled) + { + // For AI-enabled creatures (NPC pets/guardians): issue attack command and set flags. + charmInfo->SetIsCommandAttack(true); + charmInfo->SetIsAtStay(false); + charmInfo->SetIsFollowing(false); + charmInfo->SetIsCommandFollow(false); + charmInfo->SetIsReturning(false); + + petCreature->ToCreature()->AI()->AttackStart(targetUnit); + + didAttack = true; + } + else // For charmed player pets/guardians + { + if (petCreature->GetVictim() && petCreature->GetVictim() != targetUnit) + petCreature->AttackStop(); + + charmInfo->SetIsCommandAttack(true); + charmInfo->SetIsAtStay(false); + charmInfo->SetIsFollowing(false); + charmInfo->SetIsCommandFollow(false); + charmInfo->SetIsReturning(false); + + petCreature->Attack(targetUnit, true); + didAttack = true; + } + } + } + // Inform the master if the command succeeded or failed. + if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1) + botAI->TellMaster("Pet commanded to attack your target."); + else if (!didAttack) + botAI->TellError("Pet did not attack. (Already attacking or unable to attack target)"); + return didAttack; + } + // The "follow" command makes all pets/guardians follow the bot. + else if (param == "follow") + { + botAI->PetFollow(); + if (sPlayerbotAIConfig->petChatCommandDebug == 1) + botAI->TellMaster("Pet commanded to follow."); + return true; + } + // The "stay" command causes all pets/guardians to stop and stay in place. + else if (param == "stay") + { + for (Creature* target : targets) + { + // If not already in controlled motion, stop movement and set to idle. + bool controlledMotion = + target->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE; + if (!controlledMotion) + { + target->StopMovingOnCurrentPos(); + target->GetMotionMaster()->Clear(false); + target->GetMotionMaster()->MoveIdle(); } - // Final tameable check based on hunter's actual ability - if (!creature.IsTameable(bot->CanTameExoticPets())) - continue; + CharmInfo* charmInfo = target->GetCharmInfo(); + if (charmInfo) + { + // Set charm/pet state flags for "stay". + charmInfo->SetCommandState(COMMAND_STAY); + charmInfo->SetIsCommandAttack(false); + charmInfo->SetIsCommandFollow(false); + charmInfo->SetIsFollowing(false); + charmInfo->SetIsReturning(false); + charmInfo->SetIsAtStay(!controlledMotion); + charmInfo->SaveStayPosition(controlledMotion); + if (target->ToPet()) + target->ToPet()->ClearCastWhenWillAvailable(); - lastPetName = creature.Name; - lastPetId = creature.Entry; - return CreateAndSetPet(creature.Entry); + charmInfo->SetForcedSpell(0); + charmInfo->SetForcedTargetGUID(); + } } + if (sPlayerbotAIConfig->petChatCommandDebug == 1) + botAI->TellMaster("Pet commanded to stay."); + return true; } - - botAI->TellError("No tameable pet found with name: " + name); - return false; -} - -bool PetAction::SetPetById(uint32 id) -{ - CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id); - Player* bot = botAI->GetBot(); - - if (creature) + // Unknown command: show usage instructions and return. + else { - // Check if the pet is tameable at all - if (!creature->IsTameable(true)) - { - botAI->TellError("No tameable pet found with id: " + std::to_string(id)); - return false; - } - - // Exotic pet check with talent requirement - if (IsExoticPet(creature) && !HasBeastMastery(bot)) - { - botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); - return false; - } - - // Final tameable check based on hunter's actual ability - if (!creature->IsTameable(bot->CanTameExoticPets())) - { - botAI->TellError("No tameable pet found with id: " + std::to_string(id)); - return false; - } - - lastPetName = creature->Name; - lastPetId = creature->Entry; - return CreateAndSetPet(creature->Entry); - } - - botAI->TellError("No tameable pet found with id: " + std::to_string(id)); - return false; -} - -bool PetAction::SetPetByFamily(const std::string& family) -{ - std::string lowerFamily = family; - std::transform(lowerFamily.begin(), lowerFamily.end(), lowerFamily.begin(), ::tolower); - - CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); - Player* bot = botAI->GetBot(); - - std::vector candidates; - bool foundExotic = false; - - for (auto itr = creatures->begin(); itr != creatures->end(); ++itr) - { - const CreatureTemplate& creature = itr->second; - - if (!creature.IsTameable(true)) // allow exotics for search - continue; - - CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family); - if (!familyEntry) - continue; - - std::string familyName = familyEntry->Name[0]; - std::transform(familyName.begin(), familyName.end(), familyName.begin(), ::tolower); - - if (familyName != lowerFamily) - continue; - - // Exotic/BM check - if (IsExoticPet(&creature)) - { - foundExotic = true; - if (!HasBeastMastery(bot)) - continue; - } - - if (!creature.IsTameable(bot->CanTameExoticPets())) - continue; - - candidates.push_back(&creature); - } - - if (candidates.empty()) - { - if (foundExotic && !HasBeastMastery(bot)) - botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); - else - botAI->TellError("No tameable pet found with family: " + family); + botAI->TellError("Unknown pet command: " + param + + ". Use: pet "); return false; } - // Randomly select one from candidates - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, candidates.size() - 1); - - const CreatureTemplate* selected = candidates[dis(gen)]; - - lastPetName = selected->Name; - lastPetId = selected->Entry; - return CreateAndSetPet(selected->Entry); -} - -bool PetAction::RenamePet(const std::string& newName) -{ - Player* bot = botAI->GetBot(); - Pet* pet = bot->GetPet(); - if (!pet) + // For stance commands, apply the chosen stance to all targets. + for (Creature* target : targets) { - botAI->TellError("You have no pet to rename."); - return false; + target->SetReactState(react); + CharmInfo* charmInfo = target->GetCharmInfo(); + if (charmInfo) + charmInfo->SetPlayerReactState(react); } - // Length check (WoW max pet name is 12 characters) - if (newName.empty() || newName.length() > 12) - { - botAI->TellError("Pet name must be between 1 and 12 alphabetic characters."); - return false; - } - - // Alphabetic character check - for (char c : newName) - { - if (!std::isalpha(static_cast(c))) - { - botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z)."); - return false; - } - } - - // Normalize case: capitalize first letter, lower the rest - std::string normalized = newName; - normalized[0] = std::toupper(normalized[0]); - for (size_t i = 1; i < normalized.size(); ++i) - normalized[i] = std::tolower(normalized[i]); - - // Forbidden name check - if (sObjectMgr->IsReservedName(normalized)) - { - botAI->TellError("That pet name is forbidden. Please choose another name."); - return false; - } - - // Set the pet's name, save to DB, and send instant client update - pet->SetName(normalized); - pet->SavePetToDB(PET_SAVE_AS_CURRENT); - bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry()); - - botAI->TellMaster("Your pet has been renamed to " + normalized + "!"); - botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet."); - - // Dismiss pet - bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true); - // Recall pet using Hunter's Call Pet spell (spellId 883) - if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883)) - { - bot->CastSpell(bot, 883, true); - } - - return true; -} - -bool PetAction::CreateAndSetPet(uint32 creatureEntry) -{ - Player* bot = botAI->GetBot(); - if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10) - { - botAI->TellError("Only level 10+ hunters can have pets."); - return false; - } - - CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry); - if (!creature) - { - botAI->TellError("Creature template not found."); - return false; - } - - // Remove current pet(s) - if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet) - { - bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT); - bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT); - } - if (bot->GetPetStable() && bot->GetPetStable()->GetUnslottedHunterPet()) - { - bot->GetPetStable()->UnslottedPets.clear(); - bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT); - bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT); - } - - // Actually create the new pet - Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0); - if (!pet) - { - botAI->TellError("Failed to create pet."); - return false; - } - - // Set pet level and add to world - pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel() - 1); - pet->GetMap()->AddToMap(pet->ToCreature()); - pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel()); - bot->SetMinion(pet, true); - pet->InitTalentForLevel(); - pet->SavePetToDB(PET_SAVE_AS_CURRENT); - bot->PetSpellInitialize(); - - // Set stats - pet->InitStatsForLevel(bot->GetLevel()); - pet->SetLevel(bot->GetLevel()); - pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS))); - pet->SetHealth(pet->GetMaxHealth()); - - // Enable autocast for active spells - for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) - { - if (itr->second.state == PETSPELL_REMOVED) - continue; - - SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); - if (!spellInfo) - continue; - - if (spellInfo->IsPassive()) - continue; - - pet->ToggleAutocast(spellInfo, true); - } + // Inform the master of the new stance if debug is enabled. + if (sPlayerbotAIConfig->petChatCommandDebug == 1) + botAI->TellMaster("Pet stance set to " + stanceText + "."); return true; } diff --git a/src/strategy/actions/PetAction.h b/src/strategy/actions/PetAction.h index 74009e86..d3cd846a 100644 --- a/src/strategy/actions/PetAction.h +++ b/src/strategy/actions/PetAction.h @@ -10,25 +10,20 @@ #include "Action.h" #include "PlayerbotFactory.h" +#include "Unit.h" class PlayerbotAI; class PetAction : public Action { public: - PetAction(PlayerbotAI* botAI) : Action(botAI, "pet") {} + PetAction(PlayerbotAI* botAI, const std::string& defaultCmd = "") : Action(botAI, "pet"), defaultCmd(defaultCmd) {} bool Execute(Event event) override; private: - bool SetPetByName(const std::string& name); - bool SetPetById(uint32 id); - bool SetPetByFamily(const std::string& family); - bool RenamePet(const std::string& newName); - - bool CreateAndSetPet(uint32 creatureEntry); - - std::string lastPetName; - uint32 lastPetId = 0; + bool warningEnabled = true; + std::string defaultCmd; }; + #endif diff --git a/src/strategy/actions/TameAction.cpp b/src/strategy/actions/TameAction.cpp new file mode 100644 index 00000000..2a3c36db --- /dev/null +++ b/src/strategy/actions/TameAction.cpp @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it + * and/or modify it under version 2 of the License, or (at your option), any later version. + */ + +#include "TameAction.h" +#include +#include +#include +#include +#include +#include +#include "DBCStructure.h" +#include "Log.h" +#include "ObjectMgr.h" +#include "Pet.h" +#include "Player.h" +#include "PlayerbotAI.h" +#include "PlayerbotFactory.h" +#include "SpellMgr.h" +#include "WorldSession.h" + +bool IsExoticPet(const CreatureTemplate* creature) +{ + // Use the IsExotic() method from CreatureTemplate + return creature && creature->IsExotic(); +} + +bool HasBeastMastery(Player* bot) +{ + // Beast Mastery talent aura ID for WotLK is 53270 + return bot->HasAura(53270); +} + +bool TameAction::Execute(Event event) +{ + // Parse the user's input command into mode and value (e.g. "name wolf", "id 1234", etc.) + std::string param = event.getParam(); + std::istringstream iss(param); + std::string mode, value; + iss >> mode; + std::getline(iss, value); + value.erase(0, value.find_first_not_of(" ")); // Remove leading spaces from value + + bool found = false; + + // Reset any previous pet name/id state + lastPetName = ""; + lastPetId = 0; + + // If the command is "family" with no value, list all available pet families + if (mode == "family" && value.empty()) + { + std::set normalFamilies; + std::set exoticFamilies; + Player* bot = botAI->GetBot(); + + // Loop over all creature templates and collect tameable families + CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); + for (auto itr = creatures->begin(); itr != creatures->end(); ++itr) + { + const CreatureTemplate& creature = itr->second; + if (!creature.IsTameable(true)) + continue; + + CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family); + if (!familyEntry) + continue; + + std::string familyName = familyEntry->Name[0]; + if (familyName.empty()) + continue; + + if (creature.IsExotic()) + exoticFamilies.insert(familyName); + else + normalFamilies.insert(familyName); + } + + // Build the output message for the user + std::ostringstream oss; + oss << "Available pet families: "; + size_t count = 0; + for (const auto& name : normalFamilies) + { + if (count++ != 0) + oss << ", "; + oss << name; + } + if (!exoticFamilies.empty()) + { + if (!normalFamilies.empty()) + oss << " | "; + oss << "Exotic: "; + count = 0; + for (const auto& name : exoticFamilies) + { + if (count++ != 0) + oss << ", "; + oss << name; + } + } + + botAI->TellError(oss.str()); + return true; + } + + // Handle "tame abandon" command to give up your current pet + if (mode == "abandon") + { + return abandonPet(); + } + + // Try to process the command based on mode and value + if (mode == "name" && !value.empty()) + { + found = SetPetByName(value); + } + else if (mode == "id" && !value.empty()) + { + // Try to convert value to an integer and set pet by ID + try + { + uint32 id = std::stoul(value); + found = SetPetById(id); + } + catch (...) + { + botAI->TellError("Invalid tame id."); + } + } + else if (mode == "family" && !value.empty()) + { + found = SetPetByFamily(value); + } + else if (mode == "rename" && !value.empty()) + { + found = RenamePet(value); + } + else + { + // Unrecognized command or missing argument; show usage + botAI->TellError( + "Usage: tame name | tame id | tame family | tame rename | tame abandon"); + return false; + } + + // If the requested tame/rename failed, return failure + if (!found) + return false; + + // For all non-rename commands, initialize the new pet and talents, then notify the master + if (mode != "rename") + { + Player* bot = botAI->GetBot(); + PlayerbotFactory factory(bot, bot->GetLevel()); + factory.InitPet(); + factory.InitPetTalents(); + + if (!lastPetName.empty() && lastPetId != 0) + { + std::ostringstream oss; + oss << "Pet changed to " << lastPetName << ", ID: " << lastPetId << "."; + botAI->TellMaster(oss.str()); + } + else + { + botAI->TellMaster("Pet changed and initialized!"); + } + } + + return true; +} + +bool TameAction::SetPetByName(const std::string& name) +{ + // Make a lowercase copy of the input name for case-insensitive comparison + std::string lowerName = name; + std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower); + + // Get the full list of creature templates from the object manager + CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); + Player* bot = botAI->GetBot(); + + // Iterate through all creature templates + for (auto itr = creatures->begin(); itr != creatures->end(); ++itr) + { + const CreatureTemplate& creature = itr->second; + std::string creatureName = creature.Name; + // Convert creature's name to lowercase for comparison + std::transform(creatureName.begin(), creatureName.end(), creatureName.begin(), ::tolower); + + // If the input name matches this creature's name + if (creatureName == lowerName) + { + // Skip if the creature isn't tameable at all + if (!creature.IsTameable(true)) + continue; + + // If the creature is exotic and the bot doesn't have Beast Mastery, show error and fail + if (IsExoticPet(&creature) && !HasBeastMastery(bot)) + { + botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); + return false; + } + + // Skip if the creature isn't tameable by this bot (respecting exotic pet rules) + if (!creature.IsTameable(bot->CanTameExoticPets())) + continue; + + // Store the found pet's name and entry ID for later use/feedback + lastPetName = creature.Name; + lastPetId = creature.Entry; + // Create and set this pet for the bot + return CreateAndSetPet(creature.Entry); + } + } + + // If no suitable pet found, show an error and return failure + botAI->TellError("No tameable pet found with name: " + name); + return false; +} + +bool TameAction::SetPetById(uint32 id) +{ + // Look up the creature template by its numeric entry/id + CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(id); + Player* bot = botAI->GetBot(); + + // Proceed only if a valid creature was found + if (creature) + { + // Check if this creature is ever tameable (ignore bot's own restrictions for now) + if (!creature->IsTameable(true)) + { + // If not tameable at all, show an error and fail + botAI->TellError("No tameable pet found with id: " + std::to_string(id)); + return false; + } + + // If it's an exotic pet, make sure the bot has the Beast Mastery talent + if (IsExoticPet(creature) && !HasBeastMastery(bot)) + { + botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); + return false; + } + + // Check if the bot is actually allowed to tame this pet (honoring exotic pet rules) + if (!creature->IsTameable(bot->CanTameExoticPets())) + { + botAI->TellError("No tameable pet found with id: " + std::to_string(id)); + return false; + } + + // Remember this pet's name and id for later feedback + lastPetName = creature->Name; + lastPetId = creature->Entry; + // Set and create the pet for the bot + return CreateAndSetPet(creature->Entry); + } + + // If no valid creature was found by id, show an error + botAI->TellError("No tameable pet found with id: " + std::to_string(id)); + return false; +} + +bool TameAction::SetPetByFamily(const std::string& family) +{ + // Convert the input family name to lowercase for case-insensitive comparison + std::string lowerFamily = family; + std::transform(lowerFamily.begin(), lowerFamily.end(), lowerFamily.begin(), ::tolower); + + // Get all creature templates from the object manager + CreatureTemplateContainer const* creatures = sObjectMgr->GetCreatureTemplates(); + Player* bot = botAI->GetBot(); + + // Prepare a list of candidate creatures and track if any exotic pet is found + std::vector candidates; + bool foundExotic = false; + + // Iterate through all creature templates + for (auto itr = creatures->begin(); itr != creatures->end(); ++itr) + { + const CreatureTemplate& creature = itr->second; + + // Skip if this creature is never tameable + if (!creature.IsTameable(true)) + continue; + + // Look up the family entry for this creature + CreatureFamilyEntry const* familyEntry = sCreatureFamilyStore.LookupEntry(creature.family); + if (!familyEntry) + continue; + + // Compare the family name in a case-insensitive way + std::string familyName = familyEntry->Name[0]; + std::transform(familyName.begin(), familyName.end(), familyName.begin(), ::tolower); + + if (familyName != lowerFamily) + continue; + + // If the creature is exotic, check Beast Mastery talent requirements + if (IsExoticPet(&creature)) + { + foundExotic = true; + if (!HasBeastMastery(bot)) + continue; + } + + // Only add as candidate if this bot is allowed to tame it (including exotic rules) + if (!creature.IsTameable(bot->CanTameExoticPets())) + continue; + + candidates.push_back(&creature); + } + + // If no candidates found, inform the user of the reason and return false + if (candidates.empty()) + { + if (foundExotic && !HasBeastMastery(bot)) + botAI->TellError("I cannot use exotic pets unless I have the Beast Mastery talent."); + else + botAI->TellError("No tameable pet found with family: " + family); + return false; + } + + // Randomly select one candidate from the list to tame + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, candidates.size() - 1); + + const CreatureTemplate* selected = candidates[dis(gen)]; + + // Save the selected pet's name and id for feedback + lastPetName = selected->Name; + lastPetId = selected->Entry; + // Attempt to create and set the new pet for the bot + return CreateAndSetPet(selected->Entry); +} + +bool TameAction::RenamePet(const std::string& newName) +{ + Player* bot = botAI->GetBot(); + Pet* pet = bot->GetPet(); + // Check if the bot currently has a pet + if (!pet) + { + botAI->TellError("You have no pet to rename."); + return false; + } + + // Validate the new name: must not be empty and max 12 characters + if (newName.empty() || newName.length() > 12) + { + botAI->TellError("Pet name must be between 1 and 12 alphabetic characters."); + return false; + } + + // Ensure all characters in the new name are alphabetic + for (char c : newName) + { + if (!std::isalpha(static_cast(c))) + { + botAI->TellError("Pet name must only contain alphabetic characters (A-Z, a-z)."); + return false; + } + } + + // Normalize the name: capitalize the first letter, lowercase the rest + std::string normalized = newName; + normalized[0] = std::toupper(normalized[0]); + for (size_t i = 1; i < normalized.size(); ++i) + normalized[i] = std::tolower(normalized[i]); + + // Check if the new name is reserved or forbidden + if (sObjectMgr->IsReservedName(normalized)) + { + botAI->TellError("That pet name is forbidden. Please choose another name."); + return false; + } + + // Set the pet's name and save it to the database + pet->SetName(normalized); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + bot->GetSession()->SendPetNameQuery(pet->GetGUID(), pet->GetEntry()); + + // Notify the master about the rename and give a tip to update the client name display + botAI->TellMaster("Your pet has been renamed to " + normalized + "!"); + botAI->TellMaster("If you do not see the new name, please dismiss and recall your pet."); + + // Remove the current pet and (re-)cast Call Pet spell if the bot is a hunter + bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT, true); + if (bot->getClass() == CLASS_HUNTER && bot->HasSpell(883)) + { + bot->CastSpell(bot, 883, true); + } + + return true; +} + +bool TameAction::CreateAndSetPet(uint32 creatureEntry) +{ + Player* bot = botAI->GetBot(); + // Ensure the player is a hunter and at least level 10 (required for pets) + if (bot->getClass() != CLASS_HUNTER || bot->GetLevel() < 10) + { + botAI->TellError("Only level 10+ hunters can have pets."); + return false; + } + + // Retrieve the creature template for the given entry (pet species info) + CreatureTemplate const* creature = sObjectMgr->GetCreatureTemplate(creatureEntry); + if (!creature) + { + botAI->TellError("Creature template not found."); + return false; + } + + // If the bot already has a current pet or an unslotted pet, remove them to avoid conflicts + if (bot->GetPetStable() && bot->GetPetStable()->CurrentPet) + { + bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT); + bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT); + } + if (bot->GetPetStable() && bot->GetPetStable()->GetUnslottedHunterPet()) + { + bot->GetPetStable()->UnslottedPets.clear(); + bot->RemovePet(nullptr, PET_SAVE_AS_CURRENT); + bot->RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT); + } + + // Create the new tamed pet from the specified creature entry + Pet* pet = bot->CreateTamedPetFrom(creatureEntry, 0); + if (!pet) + { + botAI->TellError("Failed to create pet."); + return false; + } + + // Set the pet's level to one below the bot's current level, then add to the map and set to full level + pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel() - 1); + pet->GetMap()->AddToMap(pet->ToCreature()); + pet->SetUInt32Value(UNIT_FIELD_LEVEL, bot->GetLevel()); + // Set the pet as the bot's active minion + bot->SetMinion(pet, true); + // Initialize talents appropriate for the pet's level + pet->InitTalentForLevel(); + // Save pet to the database as the current pet + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + // Initialize available pet spells + bot->PetSpellInitialize(); + + // Further initialize pet stats to match the bot's level + pet->InitStatsForLevel(bot->GetLevel()); + pet->SetLevel(bot->GetLevel()); + // Set happiness and health of the pet to maximum values + pet->SetPower(POWER_HAPPINESS, pet->GetMaxPower(Powers(POWER_HAPPINESS))); + pet->SetHealth(pet->GetMaxHealth()); + + // Enable autocast for all active (not removed) non-passive spells the pet knows + for (PetSpellMap::const_iterator itr = pet->m_spells.begin(); itr != pet->m_spells.end(); ++itr) + { + if (itr->second.state == PETSPELL_REMOVED) + continue; + + SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(itr->first); + if (!spellInfo) + continue; + + if (spellInfo->IsPassive()) + continue; + + pet->ToggleAutocast(spellInfo, true); + } + + return true; +} + +bool TameAction::abandonPet() +{ + // Get the bot player and its current pet (if any) + Player* bot = botAI->GetBot(); + Pet* pet = bot->GetPet(); + + // Check if the bot has a pet and that it is a hunter pet + if (pet && pet->getPetType() == HUNTER_PET) + { + // Remove the pet from the bot and mark it as deleted in the database + bot->RemovePet(pet, PET_SAVE_AS_DELETED); + // Inform the bot's master/player that the pet was abandoned + botAI->TellMaster("Your pet has been abandoned."); + return true; + } + else + { + // If there is no hunter pet, show an error message + botAI->TellError("You have no hunter pet to abandon."); + return false; + } +} diff --git a/src/strategy/actions/TameAction.h b/src/strategy/actions/TameAction.h new file mode 100644 index 00000000..daac1755 --- /dev/null +++ b/src/strategy/actions/TameAction.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it + * and/or modify it under version 2 of the License, or (at your option), any later version. + */ + +#ifndef _PLAYERBOT_TAMEACTION_H +#define _PLAYERBOT_TAMEACTION_H + +#include +#include "Action.h" +#include "PlayerbotFactory.h" + +class PlayerbotAI; + +class TameAction : public Action +{ +public: + TameAction(PlayerbotAI* botAI) : Action(botAI, "tame") {} + + bool Execute(Event event) override; + +private: + bool SetPetByName(const std::string& name); + bool SetPetById(uint32 id); + bool SetPetByFamily(const std::string& family); + bool RenamePet(const std::string& newName); + bool CreateAndSetPet(uint32 creatureEntry); + bool abandonPet(); + + std::string lastPetName; + uint32 lastPetId = 0; +}; + +#endif diff --git a/src/strategy/actions/WorldPacketActionContext.h b/src/strategy/actions/WorldPacketActionContext.h index 88540cfd..0df4bd06 100644 --- a/src/strategy/actions/WorldPacketActionContext.h +++ b/src/strategy/actions/WorldPacketActionContext.h @@ -41,6 +41,7 @@ #include "UseMeetingStoneAction.h" #include "NamedObjectContext.h" #include "ReleaseSpiritAction.h" +#include "PetAction.h" class PlayerbotAI; @@ -70,6 +71,7 @@ public: creators["trade status extended"] = &WorldPacketActionContext::trade_status_extended; creators["store loot"] = &WorldPacketActionContext::store_loot; creators["self resurrect"] = &WorldPacketActionContext::self_resurrect; + creators["pet"] = &WorldPacketActionContext::pet; // quest creators["talk to quest giver"] = &WorldPacketActionContext::turn_in_quest; @@ -139,6 +141,7 @@ private: static Action* tell_not_enough_reputation(PlayerbotAI* botAI) { return new TellMasterAction(botAI, "Not enough reputation"); } static Action* tell_cannot_equip(PlayerbotAI* botAI) { return new InventoryChangeFailureAction(botAI); } static Action* self_resurrect(PlayerbotAI* botAI) { return new SelfResurrectAction(botAI); } + static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); } // quest static Action* quest_update_add_kill(PlayerbotAI* ai) { return new QuestUpdateAddKillAction(ai); } diff --git a/src/strategy/deathknight/GenericDKNonCombatStrategy.cpp b/src/strategy/deathknight/GenericDKNonCombatStrategy.cpp index 2a696322..99163441 100644 --- a/src/strategy/deathknight/GenericDKNonCombatStrategy.cpp +++ b/src/strategy/deathknight/GenericDKNonCombatStrategy.cpp @@ -50,7 +50,9 @@ void GenericDKNonCombatStrategy::InitTriggers(std::vector& trigger triggers.push_back( new TriggerNode("bone shield", NextAction::array(0, new NextAction("bone shield", 21.0f), nullptr))); triggers.push_back( - new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 11.0f), NULL))); + new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), NULL))); + triggers.push_back( + new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), NULL))); } void DKBuffDpsStrategy::InitTriggers(std::vector& triggers) diff --git a/src/strategy/deathknight/GenericDKStrategy.cpp b/src/strategy/deathknight/GenericDKStrategy.cpp index 55146a56..a1810a8c 100644 --- a/src/strategy/deathknight/GenericDKStrategy.cpp +++ b/src/strategy/deathknight/GenericDKStrategy.cpp @@ -171,6 +171,10 @@ void GenericDKStrategy::InitTriggers(std::vector& triggers) // NextAction::array(0, new NextAction("anti magic zone", ACTION_EMERGENCY + 1), nullptr))); triggers.push_back( new TriggerNode("no pet", NextAction::array(0, new NextAction("raise dead", ACTION_NORMAL + 5), nullptr))); + triggers.push_back( + new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); + triggers.push_back( + new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr))); triggers.push_back( new TriggerNode("mind freeze", NextAction::array(0, new NextAction("mind freeze", ACTION_HIGH + 1), nullptr))); triggers.push_back( diff --git a/src/strategy/druid/GenericDruidNonCombatStrategy.cpp b/src/strategy/druid/GenericDruidNonCombatStrategy.cpp index bc897948..4ca6c15a 100644 --- a/src/strategy/druid/GenericDruidNonCombatStrategy.cpp +++ b/src/strategy/druid/GenericDruidNonCombatStrategy.cpp @@ -153,6 +153,8 @@ void GenericDruidNonCombatStrategy::InitTriggers(std::vector& trig triggers.push_back( new TriggerNode("party member remove curse", NextAction::array(0, new NextAction("remove curse on party", ACTION_DISPEL + 7), nullptr))); + triggers.push_back( + new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr))); } GenericDruidBuffStrategy::GenericDruidBuffStrategy(PlayerbotAI* botAI) : NonCombatStrategy(botAI) diff --git a/src/strategy/druid/GenericDruidStrategy.cpp b/src/strategy/druid/GenericDruidStrategy.cpp index ef0ac2ea..da9427a8 100644 --- a/src/strategy/druid/GenericDruidStrategy.cpp +++ b/src/strategy/druid/GenericDruidStrategy.cpp @@ -122,7 +122,8 @@ void GenericDruidStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("combat party member dead", NextAction::array(0, new NextAction("rebirth", ACTION_HIGH + 9), NULL))); triggers.push_back(new TriggerNode("being attacked", - NextAction::array(0, new NextAction("nature's grasp", ACTION_HIGH + 1), nullptr))); + NextAction::array(0, new NextAction("nature's grasp", ACTION_HIGH + 1), nullptr))); + triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr))); } void DruidCureStrategy::InitTriggers(std::vector& triggers) diff --git a/src/strategy/generic/ChatCommandHandlerStrategy.cpp b/src/strategy/generic/ChatCommandHandlerStrategy.cpp index 89237eed..681cfacc 100644 --- a/src/strategy/generic/ChatCommandHandlerStrategy.cpp +++ b/src/strategy/generic/ChatCommandHandlerStrategy.cpp @@ -102,9 +102,11 @@ void ChatCommandHandlerStrategy::InitTriggers(std::vector& trigger new TriggerNode("unlock traded item", NextAction::array(0, new NextAction("unlock traded item", relevance), nullptr))); triggers.push_back( new TriggerNode("wipe", NextAction::array(0, new NextAction("wipe", relevance), nullptr))); - triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr))); + triggers.push_back(new TriggerNode("tame", NextAction::array(0, new NextAction("tame", relevance), nullptr))); triggers.push_back(new TriggerNode("glyphs", NextAction::array(0, new NextAction("glyphs", relevance), nullptr))); // Added for custom Glyphs triggers.push_back(new TriggerNode("glyph equip", NextAction::array(0, new NextAction("glyph equip", relevance), nullptr))); // Added for custom Glyphs + triggers.push_back(new TriggerNode("pet", NextAction::array(0, new NextAction("pet", relevance), nullptr))); + triggers.push_back(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", relevance), nullptr))); } ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : PassTroughStrategy(botAI) @@ -184,7 +186,9 @@ ChatCommandHandlerStrategy::ChatCommandHandlerStrategy(PlayerbotAI* botAI) : Pas supported.push_back("qi"); supported.push_back("unlock items"); supported.push_back("unlock traded item"); - supported.push_back("pet"); + supported.push_back("tame"); supported.push_back("glyphs"); // Added for custom Glyphs supported.push_back("glyph equip"); // Added for custom Glyphs + supported.push_back("pet"); + supported.push_back("pet attack"); } diff --git a/src/strategy/generic/CombatStrategy.cpp b/src/strategy/generic/CombatStrategy.cpp index 5026d3b3..77982143 100644 --- a/src/strategy/generic/CombatStrategy.cpp +++ b/src/strategy/generic/CombatStrategy.cpp @@ -22,8 +22,10 @@ void CombatStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("combat stuck", NextAction::array(0, new NextAction("reset", 1.0f), nullptr))); triggers.push_back(new TriggerNode("not facing target", NextAction::array(0, new NextAction("set facing", ACTION_MOVE + 7), nullptr))); - triggers.push_back( - new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr))); + // triggers.push_back(new TriggerNode("pet attack", NextAction::array(0, new NextAction("pet attack", 40.0f), nullptr))); + // The pet-attack trigger is commented out because it was forcing the bot's pet to attack, overriding stay and follow commands. + // Pets will automatically attack the bot's enemy if they are in "defensive" or "aggressive" + // stance, or if the master issues an attack command. // triggers.push_back(new TriggerNode("combat long stuck", NextAction::array(0, new NextAction("hearthstone", 0.9f), // new NextAction("repop", 0.8f), nullptr))); } diff --git a/src/strategy/hunter/GenericHunterNonCombatStrategy.cpp b/src/strategy/hunter/GenericHunterNonCombatStrategy.cpp index 203c2fea..7167cd17 100644 --- a/src/strategy/hunter/GenericHunterNonCombatStrategy.cpp +++ b/src/strategy/hunter/GenericHunterNonCombatStrategy.cpp @@ -63,6 +63,8 @@ void HunterPetStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("call pet", 60.0f), nullptr))); triggers.push_back( new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); + triggers.push_back( + new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr))); triggers.push_back( new TriggerNode("pet not happy", NextAction::array(0, new NextAction("feed pet", 60.0f), nullptr))); triggers.push_back( diff --git a/src/strategy/mage/FrostMageStrategy.cpp b/src/strategy/mage/FrostMageStrategy.cpp index 37a4551b..a532679c 100644 --- a/src/strategy/mage/FrostMageStrategy.cpp +++ b/src/strategy/mage/FrostMageStrategy.cpp @@ -61,7 +61,8 @@ void FrostMageStrategy::InitTriggers(std::vector& triggers) // Pet/Defensive triggers triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("summon water elemental", 30.0f), nullptr))); - triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 29.5f), nullptr))); + triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); + triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr))); triggers.push_back(new TriggerNode("medium health", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr))); triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("ice barrier", 29.0f), nullptr))); diff --git a/src/strategy/priest/GenericPriestStrategy.cpp b/src/strategy/priest/GenericPriestStrategy.cpp index ce924b5a..0c14de9d 100644 --- a/src/strategy/priest/GenericPriestStrategy.cpp +++ b/src/strategy/priest/GenericPriestStrategy.cpp @@ -58,6 +58,7 @@ void GenericPriestStrategy::InitTriggers(std::vector& triggers) triggers.push_back(new TriggerNode("often", NextAction::array(0, new NextAction("apply oil", 1.0f), nullptr))); triggers.push_back(new TriggerNode("being attacked", NextAction::array(0, new NextAction("power word: shield", ACTION_HIGH + 1), nullptr))); + triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr))); } PriestCureStrategy::PriestCureStrategy(PlayerbotAI* botAI) : Strategy(botAI) diff --git a/src/strategy/priest/PriestNonCombatStrategy.cpp b/src/strategy/priest/PriestNonCombatStrategy.cpp index be596054..0db6e22e 100644 --- a/src/strategy/priest/PriestNonCombatStrategy.cpp +++ b/src/strategy/priest/PriestNonCombatStrategy.cpp @@ -56,6 +56,8 @@ void PriestNonCombatStrategy::InitTriggers(std::vector& triggers) triggers.push_back( new TriggerNode("group heal setting", NextAction::array(0, new NextAction("circle of healing on party", 27.0f), NULL))); + triggers.push_back(new TriggerNode("new pet", + NextAction::array(0, new NextAction("set pet stance", 10.0f), nullptr))); } void PriestBuffStrategy::InitTriggers(std::vector& triggers) diff --git a/src/strategy/shaman/GenericShamanStrategy.cpp b/src/strategy/shaman/GenericShamanStrategy.cpp index b04063e0..8f8aabe7 100644 --- a/src/strategy/shaman/GenericShamanStrategy.cpp +++ b/src/strategy/shaman/GenericShamanStrategy.cpp @@ -133,6 +133,8 @@ void GenericShamanStrategy::InitTriggers(std::vector& triggers) // NextAction("riptide", 26.0f), nullptr))); triggers.push_back(new TriggerNode("heroism", NextAction::array(0, new NextAction("heroism", 31.0f), nullptr))); triggers.push_back(new TriggerNode("bloodlust", NextAction::array(0, new NextAction("bloodlust", 30.0f), nullptr))); + triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); + triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 65.0f), nullptr))); } void ShamanBuffDpsStrategy::InitTriggers(std::vector& triggers) @@ -187,4 +189,4 @@ void ShamanHealerDpsStrategy::InitTriggers(std::vector& triggers) new TriggerNode("medium aoe and healer should attack", NextAction::array(0, new NextAction("chain lightning", ACTION_DEFAULT + 0.3f), nullptr))); -} \ No newline at end of file +} diff --git a/src/strategy/shaman/ShamanNonCombatStrategy.cpp b/src/strategy/shaman/ShamanNonCombatStrategy.cpp index ad4dcb82..7e6df52c 100644 --- a/src/strategy/shaman/ShamanNonCombatStrategy.cpp +++ b/src/strategy/shaman/ShamanNonCombatStrategy.cpp @@ -49,6 +49,10 @@ void ShamanNonCombatStrategy::InitTriggers(std::vector& triggers) new TriggerNode("cure disease", NextAction::array(0, new NextAction("cure disease", 31.0f), nullptr))); triggers.push_back(new TriggerNode("party member cure disease", NextAction::array(0, new NextAction("cure disease on party", 30.0f), nullptr))); + triggers.push_back( + new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); + triggers.push_back( + new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 65.0f), nullptr))); } void ShamanNonCombatStrategy::InitMultipliers(std::vector& multipliers) diff --git a/src/strategy/triggers/ChatTriggerContext.h b/src/strategy/triggers/ChatTriggerContext.h index aabbf529..cd04375b 100644 --- a/src/strategy/triggers/ChatTriggerContext.h +++ b/src/strategy/triggers/ChatTriggerContext.h @@ -133,9 +133,11 @@ public: creators["calc"] = &ChatTriggerContext::calc; creators["qi"] = &ChatTriggerContext::qi; creators["wipe"] = &ChatTriggerContext::wipe; - creators["pet"] = &ChatTriggerContext::pet; + creators["tame"] = &ChatTriggerContext::tame; creators["glyphs"] = &ChatTriggerContext::glyphs; // Added for custom Glyphs creators["glyph equip"] = &ChatTriggerContext::glyph_equip; // Added for custom Glyphs + creators["pet"] = &ChatTriggerContext::pet; + creators["pet attack"] = &ChatTriggerContext::pet_attack; } private: @@ -248,9 +250,11 @@ private: static Trigger* calc(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "calc"); } static Trigger* qi(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "qi"); } static Trigger* wipe(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "wipe"); } - static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); } + static Trigger* tame(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "tame"); } static Trigger* glyphs(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "glyphs"); } // Added for custom Glyphs static Trigger* glyph_equip(PlayerbotAI* ai) { return new ChatCommandTrigger(ai, "glyph equip"); } // Added for custom Glyphs + static Trigger* pet(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet"); } + static Trigger* pet_attack(PlayerbotAI* botAI) { return new ChatCommandTrigger(botAI, "pet attack"); } }; #endif diff --git a/src/strategy/triggers/GenericTriggers.cpp b/src/strategy/triggers/GenericTriggers.cpp index c8131bd4..a907085b 100644 --- a/src/strategy/triggers/GenericTriggers.cpp +++ b/src/strategy/triggers/GenericTriggers.cpp @@ -20,6 +20,8 @@ #include "TemporarySummon.h" #include "ThreatMgr.h" #include "Timer.h" +#include "PlayerbotAI.h" +#include "Player.h" bool LowManaTrigger::IsActive() { @@ -685,3 +687,44 @@ bool AmmoCountTrigger::IsActive() return ItemCountTrigger::IsActive(); } + +bool NewPetTrigger::IsActive() +{ + // Get the bot player object from the AI + Player* bot = botAI->GetBot(); + + // Try to get the current pet; initialize guardian and GUID to null/empty + Pet* pet = bot ? bot->GetPet() : nullptr; + Guardian* guardian = nullptr; + ObjectGuid currentPetGuid = ObjectGuid::Empty; + + // If bot has a pet, get its GUID + if (pet) + { + currentPetGuid = pet->GetGUID(); + } + else + { + // If no pet, try to get a guardian pet and its GUID + guardian = bot ? bot->GetGuardianPet() : nullptr; + if (guardian) + currentPetGuid = guardian->GetGUID(); + } + + // If the current pet or guardian GUID has changed (including becoming empty), reset the trigger state + if (currentPetGuid != lastPetGuid) + { + triggered = false; + lastPetGuid = currentPetGuid; + } + + // If there's a valid current pet/guardian (non-empty GUID) and we haven't triggered yet, activate trigger + if (currentPetGuid != ObjectGuid::Empty && !triggered) + { + triggered = true; + return true; + } + + // Otherwise, do not activate + return false; +} diff --git a/src/strategy/triggers/GenericTriggers.h b/src/strategy/triggers/GenericTriggers.h index 96691224..d6664122 100644 --- a/src/strategy/triggers/GenericTriggers.h +++ b/src/strategy/triggers/GenericTriggers.h @@ -943,4 +943,16 @@ public: bool IsActive() override { return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL); } }; +class NewPetTrigger : public Trigger +{ +public: + NewPetTrigger(PlayerbotAI* ai) : Trigger(ai, "new pet"), lastPetGuid(ObjectGuid::Empty), triggered(false) {} + + bool IsActive() override; + +private: + ObjectGuid lastPetGuid; + bool triggered; +}; + #endif diff --git a/src/strategy/triggers/TriggerContext.h b/src/strategy/triggers/TriggerContext.h index 8fcf60bc..984c1dea 100644 --- a/src/strategy/triggers/TriggerContext.h +++ b/src/strategy/triggers/TriggerContext.h @@ -227,6 +227,7 @@ public: creators["do quest status"] = &TriggerContext::do_quest_status; creators["travel flight status"] = &TriggerContext::travel_flight_status; creators["can self resurrect"] = &TriggerContext::can_self_resurrect; + creators["new pet"] = &TriggerContext::new_pet; } private: @@ -425,6 +426,7 @@ private: static Trigger* do_quest_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_DO_QUEST); } static Trigger* travel_flight_status(PlayerbotAI* botAI) { return new NewRpgStatusTrigger(botAI, RPG_TRAVEL_FLIGHT); } static Trigger* can_self_resurrect(PlayerbotAI* ai) { return new SelfResurrectTrigger(ai); } + static Trigger* new_pet(PlayerbotAI* ai) { return new NewPetTrigger(ai); } }; #endif diff --git a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp index 2fd3c78c..49707e14 100644 --- a/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp +++ b/src/strategy/warlock/GenericWarlockNonCombatStrategy.cpp @@ -79,6 +79,7 @@ void GenericWarlockNonCombatStrategy::InitTriggers(std::vector& tr { NonCombatStrategy::InitTriggers(triggers); triggers.push_back(new TriggerNode("has pet", NextAction::array(0, new NextAction("toggle pet spell", 60.0f), nullptr))); + triggers.push_back(new TriggerNode("new pet", NextAction::array(0, new NextAction("set pet stance", 60.0f), nullptr))); triggers.push_back(new TriggerNode("no pet", NextAction::array(0, new NextAction("fel domination", 30.0f), nullptr))); triggers.push_back(new TriggerNode("no soul shard", NextAction::array(0, new NextAction("create soul shard", 60.0f), nullptr))); triggers.push_back(new TriggerNode("too many soul shards", NextAction::array(0, new NextAction("destroy soul shard", 60.0f), nullptr))); From ffc96b664ec682c85576e64e3a999f55ef0facc4 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Fri, 1 Aug 2025 01:35:11 -0700 Subject: [PATCH 02/20] Turning off Spirit Wolf Leap I had to add an exception to turn off autocasting for the Spirit Wolf Leap - they were still leaping into battle despite their stance. --- src/strategy/actions/GenericActions.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/strategy/actions/GenericActions.cpp b/src/strategy/actions/GenericActions.cpp index 277175e7..21120343 100644 --- a/src/strategy/actions/GenericActions.cpp +++ b/src/strategy/actions/GenericActions.cpp @@ -33,7 +33,8 @@ enum PetSpells PET_DEVOUR_MAGIC_4 = 19736, PET_DEVOUR_MAGIC_5 = 27276, PET_DEVOUR_MAGIC_6 = 27277, - PET_DEVOUR_MAGIC_7 = 48011 + PET_DEVOUR_MAGIC_7 = 48011, + PET_SPIRIT_WOLF_LEAP = 58867 }; static std::vector disabledPetSpells = { @@ -41,7 +42,7 @@ static std::vector disabledPetSpells = { PET_COWER, PET_LEAP, PET_SPELL_LOCK_1, PET_SPELL_LOCK_2, PET_DEVOUR_MAGIC_1, PET_DEVOUR_MAGIC_2, PET_DEVOUR_MAGIC_3, - PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7 + PET_DEVOUR_MAGIC_4, PET_DEVOUR_MAGIC_5, PET_DEVOUR_MAGIC_6, PET_DEVOUR_MAGIC_7, PET_SPIRIT_WOLF_LEAP }; bool MeleeAction::isUseful() From f7e64589e8a4c7b8587cfd512bb0afec4d913f03 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Fri, 1 Aug 2025 23:54:12 -0700 Subject: [PATCH 03/20] Update ChatActionContext.h --- src/strategy/actions/ChatActionContext.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 4521292e..6beb4130 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -193,8 +193,8 @@ public: creators["calc"] = &ChatActionContext::calc; creators["wipe"] = &ChatActionContext::wipe; creators["tame"] = &ChatActionContext::tame; - creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs - creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs + creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs + creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs creators["pet"] = &ChatActionContext::pet; creators["pet attack"] = &ChatActionContext::pet_attack; creators["roll"] = &ChatActionContext::roll_action; From 548746c25fa5db229330bd945e2cb5ef23076e73 Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Fri, 1 Aug 2025 23:55:25 -0700 Subject: [PATCH 04/20] Revert "Update ChatActionContext.h" This reverts commit f7e64589e8a4c7b8587cfd512bb0afec4d913f03. --- src/strategy/actions/ChatActionContext.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 6beb4130..4521292e 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -193,8 +193,8 @@ public: creators["calc"] = &ChatActionContext::calc; creators["wipe"] = &ChatActionContext::wipe; creators["tame"] = &ChatActionContext::tame; - creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs - creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs + creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs + creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs creators["pet"] = &ChatActionContext::pet; creators["pet attack"] = &ChatActionContext::pet_attack; creators["roll"] = &ChatActionContext::roll_action; From ab345b88470e6e3371d9fd1874f4ea3ae655194b Mon Sep 17 00:00:00 2001 From: ThePenguinMan96 Date: Mon, 4 Aug 2025 17:55:29 -0700 Subject: [PATCH 05/20] Changes requested I fixed what was requested of me. Built it, tested it - all functions are working. --- src/strategy/actions/ChatActionContext.h | 8 ++++---- src/strategy/actions/GenericActions.cpp | 3 --- src/strategy/actions/TameAction.cpp | 4 ++-- src/strategy/actions/TameAction.h | 2 +- src/strategy/triggers/GenericTriggers.cpp | 6 ++++-- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/strategy/actions/ChatActionContext.h b/src/strategy/actions/ChatActionContext.h index 4521292e..cf9d8c51 100644 --- a/src/strategy/actions/ChatActionContext.h +++ b/src/strategy/actions/ChatActionContext.h @@ -193,8 +193,8 @@ public: creators["calc"] = &ChatActionContext::calc; creators["wipe"] = &ChatActionContext::wipe; creators["tame"] = &ChatActionContext::tame; - creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs - creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs + creators["glyphs"] = &ChatActionContext::glyphs; // Added for custom Glyphs + creators["glyph equip"] = &ChatActionContext::glyph_equip; // Added for custom Glyphs creators["pet"] = &ChatActionContext::pet; creators["pet attack"] = &ChatActionContext::pet_attack; creators["roll"] = &ChatActionContext::roll_action; @@ -305,8 +305,8 @@ private: static Action* calc(PlayerbotAI* ai) { return new TellCalculateItemAction(ai); } static Action* wipe(PlayerbotAI* ai) { return new WipeAction(ai); } static Action* tame(PlayerbotAI* botAI) { return new TameAction(botAI); } - static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs - static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs + static Action* glyphs(PlayerbotAI* botAI) { return new TellGlyphsAction(botAI); } // Added for custom Glyphs + static Action* glyph_equip(PlayerbotAI* ai) { return new EquipGlyphsAction(ai); } // Added for custom Glyphs static Action* pet(PlayerbotAI* botAI) { return new PetAction(botAI); } static Action* pet_attack(PlayerbotAI* botAI) { return new PetAction(botAI, "attack"); } static Action* roll_action(PlayerbotAI* botAI) { return new RollAction(botAI); } diff --git a/src/strategy/actions/GenericActions.cpp b/src/strategy/actions/GenericActions.cpp index 21120343..ae8010e1 100644 --- a/src/strategy/actions/GenericActions.cpp +++ b/src/strategy/actions/GenericActions.cpp @@ -156,9 +156,6 @@ bool PetAttackAction::Execute(Event event) bool SetPetStanceAction::Execute(Event /*event*/) { - // Get the bot player object from the AI - Player* bot = botAI->GetBot(); - // Prepare a list to hold all controlled pet and guardian creatures std::vector targets; diff --git a/src/strategy/actions/TameAction.cpp b/src/strategy/actions/TameAction.cpp index 2a3c36db..8446c24c 100644 --- a/src/strategy/actions/TameAction.cpp +++ b/src/strategy/actions/TameAction.cpp @@ -108,7 +108,7 @@ bool TameAction::Execute(Event event) // Handle "tame abandon" command to give up your current pet if (mode == "abandon") { - return abandonPet(); + return AbandonPet(); } // Try to process the command based on mode and value @@ -476,7 +476,7 @@ bool TameAction::CreateAndSetPet(uint32 creatureEntry) return true; } -bool TameAction::abandonPet() +bool TameAction::AbandonPet() { // Get the bot player and its current pet (if any) Player* bot = botAI->GetBot(); diff --git a/src/strategy/actions/TameAction.h b/src/strategy/actions/TameAction.h index daac1755..018ba5a0 100644 --- a/src/strategy/actions/TameAction.h +++ b/src/strategy/actions/TameAction.h @@ -25,7 +25,7 @@ private: bool SetPetByFamily(const std::string& family); bool RenamePet(const std::string& newName); bool CreateAndSetPet(uint32 creatureEntry); - bool abandonPet(); + bool AbandonPet(); std::string lastPetName; uint32 lastPetId = 0; diff --git a/src/strategy/triggers/GenericTriggers.cpp b/src/strategy/triggers/GenericTriggers.cpp index a907085b..5f1935a9 100644 --- a/src/strategy/triggers/GenericTriggers.cpp +++ b/src/strategy/triggers/GenericTriggers.cpp @@ -692,9 +692,11 @@ bool NewPetTrigger::IsActive() { // Get the bot player object from the AI Player* bot = botAI->GetBot(); + if (!bot) + return false; // Try to get the current pet; initialize guardian and GUID to null/empty - Pet* pet = bot ? bot->GetPet() : nullptr; + Pet* pet = bot->GetPet(); Guardian* guardian = nullptr; ObjectGuid currentPetGuid = ObjectGuid::Empty; @@ -706,7 +708,7 @@ bool NewPetTrigger::IsActive() else { // If no pet, try to get a guardian pet and its GUID - guardian = bot ? bot->GetGuardianPet() : nullptr; + guardian = bot->GetGuardianPet(); if (guardian) currentPetGuid = guardian->GetGUID(); } From 872e4176137b66c83ebcb03932fa8ff1e39fd791 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Sun, 10 Aug 2025 21:23:02 +0200 Subject: [PATCH 06/20] Playerbots/LFG: fix false not eligible & dungeon 0/type 0, add clear diagnostics (#1521) Tested --- src/PlayerbotMgr.cpp | 18 ++++-- src/Playerbots.cpp | 133 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 145 insertions(+), 6 deletions(-) diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 0b6d3967..182177c2 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -529,6 +529,9 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) { botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot)); } + + botAI->Reset(true); // Reset transient states (incl. LFG "proposal") to avoid the "one or more players are not eligible" error after reconnect. + sPlayerbotDbStore->Load(botAI); if (master && !master->HasUnitState(UNIT_STATE_IN_FLIGHT)) @@ -548,16 +551,21 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) if (master && master->GetGroup() && !group) { Group* mgroup = master->GetGroup(); - if (mgroup->GetMembersCount() >= 5) + // if (mgroup->GetMembersCount() >= 5) + if (mgroup->GetMembersCount() + 1 > 5) // only convert in raid if the add of THIS bot make group > 5 { if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup()) { mgroup->ConvertToRaid(); } - if (mgroup->isRaidGroup()) - { - mgroup->AddMember(bot); - } + //if (mgroup->isRaidGroup()) + //{ + //mgroup->AddMember(bot); + //} + mgroup->AddMember(bot); + + LOG_DEBUG("playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}", + (int)mgroup->GetMembersCount(), mgroup->isRaidGroup() ? 1 : 0, mgroup->isLFGGroup() ? 1 : 0); } else { diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index 13b04e0e..e39171b1 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -30,6 +30,7 @@ #include "cs_playerbots.h" #include "cmath" #include "BattleGroundTactics.h" +#include "ObjectAccessor.h" class PlayerbotsDatabaseScript : public DatabaseScript { @@ -311,7 +312,7 @@ class PlayerbotsScript : public PlayerbotScript public: PlayerbotsScript() : PlayerbotScript("PlayerbotsScript") {} - bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) override + /*bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) override { bool nonBotFound = false; for (ObjectGuid const& guid : guidsList.guids) @@ -325,7 +326,137 @@ public: } return nonBotFound; + }*/ + + // New LFG Function + bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) + { + const size_t totalSlots = guidsList.guids.size(); + size_t ignoredEmpty = 0, ignoredNonPlayer = 0; + size_t offlinePlayers = 0, botPlayers = 0, realPlayers = 0; + bool groupGuidSeen = false; + + LOG_DEBUG("playerbots", "[LFG] check start: slots={}", totalSlots); + + for (size_t i = 0; i < totalSlots; ++i) + { + ObjectGuid const& guid = guidsList.guids[i]; + + // 1) Placeholders to ignore + if (guid.IsEmpty()) + { + ++ignoredEmpty; + LOG_DEBUG("playerbots", "[LFG] slot {}: -> ignored", i); + continue; + } + + // Group GUID: in the original implementation this counted as "non-bot found" + if (guid.IsGroup()) + { + groupGuidSeen = true; + LOG_DEBUG("playerbots", "[LFG] slot {}: -> counts as having a real player (compat)", i); + continue; + } + + // Other non-Player GUIDs: various placeholders, ignore them + if (!guid.IsPlayer()) + { + ++ignoredNonPlayer; + LOG_DEBUG("playerbots", "[LFG] slot {}: guid={} (non-player/high={}) -> ignored", i, + static_cast(guid.GetRawValue()), (unsigned)guid.GetHigh()); + continue; + } + + // 2) Player present? + Player* player = ObjectAccessor::FindPlayer(guid); + if (!player) + { + ++offlinePlayers; + LOG_DEBUG("playerbots", "[LFG] slot {}: player guid={} is offline/not in world", i, + static_cast(guid.GetRawValue())); + continue; + } + + // 3) Bot or real player? + if (GET_PLAYERBOT_AI(player) != nullptr) + { + ++botPlayers; + LOG_DEBUG("playerbots", "[LFG] slot {}: BOT {} (lvl {}, class {})", i, player->GetName().c_str(), + player->GetLevel(), player->getClass()); + } + else + { + ++realPlayers; + LOG_DEBUG("playerbots", "[LFG] slot {}: REAL {} (lvl {}, class {})", i, player->GetName().c_str(), + player->GetLevel(), player->getClass()); + } + } + + // "Ultra-early phase" detection: only placeholders => DO NOT VETO + const bool onlyPlaceholders = (realPlayers + botPlayers + (groupGuidSeen ? 1 : 0)) == 0 && + (ignoredEmpty + ignoredNonPlayer) == totalSlots; + + // "Soft" LFG preflight if we actually see players AND at least one offline + if (!onlyPlaceholders && offlinePlayers > 0) + { + // Find a plausible leader: prefer a real online player, otherwise any online player + Player* leader = nullptr; + + for (ObjectGuid const& guid : guidsList.guids) + if (guid.IsPlayer()) + if (Player* p = ObjectAccessor::FindPlayer(guid)) + if (GET_PLAYERBOT_AI(p) == nullptr) + { + leader = p; + break; + } + + if (!leader) + for (ObjectGuid const& guid : guidsList.guids) + if (guid.IsPlayer()) + if (Player* p = ObjectAccessor::FindPlayer(guid)) + { + leader = p; + break; + } + + if (leader) + { + Group* g = leader->GetGroup(); + if (g) + { + LOG_DEBUG("playerbots", "[LFG-RESET] group members={}, isRaid={}, isLFGGroup={}", + (int)g->GetMembersCount(), g->isRaidGroup() ? 1 : 0, g->isLFGGroup() ? 1 : 0); + + // "Soft" reset of LFG states on the bots' AI side (proposal/role-check, etc.) + for (GroupReference* ref = g->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member) + continue; + + if (PlayerbotAI* ai = GET_PLAYERBOT_AI(member)) + ai->Reset(true); + } + } + } + + LOG_DEBUG("playerbots", "[LFG] preflight soft-reset triggered (offline detected) -> allowQueue=no (retry)"); + return false; // ask the client to retry right after the reset + } + + // "Hybrid" policy: permissive if only placeholders; otherwise original logic + bool allowQueue = onlyPlaceholders ? true : ((offlinePlayers == 0) && (realPlayers >= 1 || groupGuidSeen)); + + LOG_DEBUG("playerbots", + "[LFG] summary: slots={}, real={}, bots={}, offline={}, ignored(empty+nonPlayer)={}, " + "groupGuidSeen={} -> allowQueue={}", + totalSlots, realPlayers, botPlayers, offlinePlayers, (ignoredEmpty + ignoredNonPlayer), + (groupGuidSeen ? "yes" : "no"), (allowQueue ? "yes" : "no")); + + return allowQueue; } + // End LFG void OnPlayerbotCheckKillTask(Player* player, Unit* victim) override { From 380312ffd231fd5e663a8a17daa80dd39906e3f0 Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Sun, 10 Aug 2025 22:59:34 +0200 Subject: [PATCH 07/20] nullptr fix (#1523) --- src/factory/PlayerbotFactory.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 24c130d7..32d495a7 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -1020,14 +1020,16 @@ void PlayerbotFactory::ClearSkills() } bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0); bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0); + // unlearn default race/class skills - PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass()); - for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) - { - uint32 skillId = itr->SkillId; - if (!bot->HasSkill(skillId)) - continue; - bot->SetSkill(skillId, 0, 0, 0); + if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass())) { + for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) + { + uint32 skillId = itr->SkillId; + if (!bot->HasSkill(skillId)) + continue; + bot->SetSkill(skillId, 0, 0, 0); + } } } From ddfa919154529fee59e7ba30d2ebe29c0ae4abdf Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Mon, 11 Aug 2025 01:11:54 +0200 Subject: [PATCH 08/20] Dont wait to travel when in combat. (#1524) Prevents bot adding a travel delay when in combat --- src/strategy/actions/MoveToTravelTargetAction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/actions/MoveToTravelTargetAction.cpp b/src/strategy/actions/MoveToTravelTargetAction.cpp index 6782ea3f..9ae28919 100644 --- a/src/strategy/actions/MoveToTravelTargetAction.cpp +++ b/src/strategy/actions/MoveToTravelTargetAction.cpp @@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event) WorldLocation location = *target->getPosition(); Group* group = bot->GetGroup(); - if (group && !urand(0, 1) && bot == botAI->GetGroupMaster()) + if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { From e4ea8e2694b0f6d098a945c6f863526cd14f9b3f Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:27:25 +0200 Subject: [PATCH 09/20] Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks (#1529) --- src/BroadcastHelper.cpp | 17 ++++++- src/PlayerbotAI.cpp | 30 +++++++++++- src/PlayerbotMgr.cpp | 95 +++++++++++++++++++++++++++++--------- src/Playerbots.cpp | 31 +++++++++++-- src/RandomPlayerbotMgr.cpp | 87 ++++++++++++++++++++++++++++------ src/TravelMgr.cpp | 58 ++++++++++++++++++++++- 6 files changed, 276 insertions(+), 42 deletions(-) diff --git a/src/BroadcastHelper.cpp b/src/BroadcastHelper.cpp index 85bc5221..2de066f6 100644 --- a/src/BroadcastHelper.cpp +++ b/src/BroadcastHelper.cpp @@ -947,8 +947,21 @@ bool BroadcastHelper::BroadcastSuggestThunderfury(PlayerbotAI* ai, Player* bot) { std::map placeholders; ItemTemplate const* thunderfuryProto = sObjectMgr->GetItemTemplate(19019); - placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); - + // placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); // Old code + // [Crash fix] Protect from nil AI : a real player doesn't have PlayerbotAI. + // Before: direct deref GET_PLAYERBOT_AI(bot)->... could crash World/General. + if (auto* ai = GET_PLAYERBOT_AI(bot)) + { + if (auto* chat = ai->GetChatHelper()) + placeholders["%thunderfury_link"] = chat->FormatItem(thunderfuryProto); + else + placeholders["%thunderfury_link"] = ""; // fallback: no chat helper + } + else + { + placeholders["%thunderfury_link"] = ""; // fallback: no d'AI (real player) + } + // End crash fix return BroadcastToChannelWithGlobalChance( ai, BOT_TEXT2("thunderfury_spam", placeholders), diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 998310f9..3f67b505 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -2373,7 +2373,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry) return name; } -std::vector PlayerbotAI::GetPlayersInGroup() +/*std::vector PlayerbotAI::GetPlayersInGroup() { std::vector members; @@ -2392,6 +2392,34 @@ std::vector PlayerbotAI::GetPlayersInGroup() members.push_back(ref->GetSource()); } + return members; +}*/ + +std::vector PlayerbotAI::GetPlayersInGroup() +{ + std::vector members; + + Group* group = bot->GetGroup(); + if (!group) + return members; + + for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) + { + Player* member = ref->GetSource(); + if (!member) + continue; + + // Celaning, we don't call 2 times GET_PLAYERBOT_AI and never reference it if nil + if (auto* ai = GET_PLAYERBOT_AI(member)) + { + // If it's a bot (not real player) => we ignor it + if (!ai->IsRealPlayer()) + continue; + } + + members.push_back(member); + } + return members; } diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 182177c2..dee1437c 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -36,10 +36,28 @@ #include "BroadcastHelper.h" #include "PlayerbotDbStore.h" #include "WorldSessionMgr.h" -#include "DatabaseEnv.h" // Added for gender choice -#include // Added for gender choice -#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) +#include "DatabaseEnv.h" +#include +#include "Log.h" #include // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) +#include "TravelMgr.h" + +namespace { + // [Crash fix] Centralize clearing of pointer values in the AI context + static void ClearAIContextPointerValues(PlayerbotAI* ai) + { + if (!ai) return; + if (AiObjectContext* ctx = ai->GetAiObjectContext()) + { + // Known today + if (auto* tt = ctx->GetValue("travel target")) + tt->Set(nullptr); + + // TODO: add other pointer-type values here if you have any + // e.g.: ctx->GetValue("some key")->Set(nullptr); + } + } +} class BotInitGuard { @@ -124,7 +142,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer); if (!mgr) { - LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); + LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); return; } uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); @@ -222,13 +240,33 @@ void PlayerbotHolder::UpdateSessions() } } -void PlayerbotHolder::HandleBotPackets(WorldSession* session) +/*void PlayerbotHolder::HandleBotPackets(WorldSession* session) { WorldPacket* packet; while (session->GetPacketQueue().next(packet)) { OpcodeClient opcode = static_cast(packet->GetOpcode()); ClientOpcodeHandler const* opHandle = opcodeTable[opcode]; + opHandle->Call(session, *packet); + delete packet; + } +}*/ + +void PlayerbotHolder::HandleBotPackets(WorldSession* session) // [Crash Fix] Secure packet dispatch (avoid calling on a null handler) +{ + WorldPacket* packet; + while (session->GetPacketQueue().next(packet)) + { + const OpcodeClient opcode = static_cast(packet->GetOpcode()); + const ClientOpcodeHandler* opHandle = opcodeTable[opcode]; + + if (!opHandle) + { + // Unknown handler: drop cleanly + delete packet; + continue; + } + opHandle->Call(session, *packet); delete packet; } @@ -318,10 +356,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) sPlayerbotDbStore->Save(botAI); } - LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str()); + LOG_DEBUG("mod-playerbots", "Bot {} logging out", bot->GetName().c_str()); bot->SaveToDB(false, false); - WorldSession* botWorldSessionPtr = bot->GetSession(); + // WorldSession* botWorldSessionPtr = bot->GetSession(); + WorldSession* botWorldSessionPtr = bot->GetSession(); // Small safeguard on the session (as a precaution) + if (!botWorldSessionPtr) + return; WorldSession* masterWorldSessionPtr = nullptr; if (botWorldSessionPtr->isLogingOut()) @@ -354,11 +395,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) logout = true; } - TravelTarget* target = nullptr; + /*TravelTarget* target = nullptr; if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. { target = botAI->GetAiObjectContext()->GetValue("travel target")->Get(); - } + }*/ + // [Crash fix] Centralized cleanup of pointer values in the context + ClearAIContextPointerValues(botAI); // Peiru: Allow bots to always instant logout to see if this resolves logout crashes logout = true; @@ -375,19 +418,25 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) botWorldSessionPtr->HandleLogoutRequestOpcode(data); if (!bot) { - RemoveFromPlayerbotsMap(guid); + /*RemoveFromPlayerbotsMap(guid); delete botWorldSessionPtr; if (target) - delete target; + delete target;*/ + // [Crash fix] bot can be destroyed by the logout request: clean up without touching old pointers + RemoveFromPlayerbotsMap(guid); + delete botWorldSessionPtr; } return; } else { - RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + /*RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap delete botWorldSessionPtr; // finally delete the bot's WorldSession if (target) - delete target; + delete target;*/ + // [Crash fix] no more deleting 'target' here: ownership handled by the AI/Context + RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + delete botWorldSessionPtr; // finally delete the bot's WorldSession } return; } // if instant logout possible, do it @@ -420,11 +469,11 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) sPlayerbotDbStore->Save(botAI); } - LOG_DEBUG("playerbots", "Bot {} logged out", bot->GetName().c_str()); + LOG_DEBUG("mod-playerbots", "Bot {} logged out", bot->GetName().c_str()); bot->SaveToDB(false, false); - if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. + /*if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. { TravelTarget* target = botAI->GetAiObjectContext()->GetValue("travel target")->Get(); if (target) @@ -433,7 +482,9 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap - delete botAI; + delete botAI;*/ + // [Crash fix] Centralized cleanup of pointer values in the context + ClearAIContextPointerValues(botAI); } } @@ -564,7 +615,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) //} mgroup->AddMember(bot); - LOG_DEBUG("playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}", + LOG_DEBUG("mod-playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}", (int)mgroup->GetMembersCount(), mgroup->isRaidGroup() ? 1 : 0, mgroup->isLFGGroup() ? 1 : 0); } else @@ -744,9 +795,11 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje } } - if (GET_PLAYERBOT_AI(bot)) + // if (GET_PLAYERBOT_AI(bot)) + if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Tidy/Crash fix] Acquire AI once and reuse; avoid multiple GET_PLAYERBOT_AI calls. { - if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster()) + // if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster()) + if (Player* master = ai->GetMaster()) { if (master->GetSession()->GetSecurity() <= SEC_PLAYER && sPlayerbotAIConfig->autoInitOnly && cmd != "init=auto") @@ -1601,7 +1654,7 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot) botAI->SetMaster(master); botAI->ResetStrategies(); - LOG_INFO("playerbots", "Bot {} logged in", bot->GetName().c_str()); + LOG_INFO("mod-playerbots", "Bot {} logged in", bot->GetName().c_str()); } void PlayerbotMgr::OnPlayerLogin(Player* player) @@ -1794,7 +1847,7 @@ void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntr { delete it->second; _playerbotsAIMap.erase(it); - LOG_DEBUG("playerbots", "Removed stale AI for GUID {}", + LOG_DEBUG("mod-playerbots", "Removed stale AI for GUID {}", static_cast(guid.GetRawValue())); } diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index e39171b1..d7fb2ea5 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -137,7 +137,7 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override { - if (type == CHAT_MSG_WHISPER) + /*if (type == CHAT_MSG_WHISPER) { if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver)) { @@ -145,14 +145,23 @@ public: return false; } - } + }*/ + + if (type == CHAT_MSG_WHISPER && receiver) // [Crash Fix] Add non-null receiver check to avoid calling on a null pointer in edge cases. + { + if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver)) + { + botAI->HandleCommand(type, msg, player); + return false; + } + } return true; } void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override { - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + /*for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { if (Player* member = itr->GetSource()) { @@ -161,6 +170,18 @@ public: botAI->HandleCommand(type, msg, player); } } + }*/ + if (!group) return; // [Crash Fix] 'group' should not be null in this hook, but this safeguard prevents a crash if the caller changes or in case of an unexpected call. + + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + { + Player* member = itr->GetSource(); + if (!member) continue; + + if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(member)) + { + botAI->HandleCommand(type, msg, player); + } } } @@ -177,7 +198,9 @@ public: { if (bot->GetGuildId() == player->GetGuildId()) { - GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player); + // GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player); + if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Crash Fix] Possible crash source because we don't check if the returned pointer is not null + ai->HandleCommand(type, msg, player); } } } diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 1b69d7f3..658e43d3 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -1725,7 +1725,11 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& } // Prevent blink to be detected by visible real players - if (botAI->HasPlayerNearby(150.0f)) + /*if (botAI->HasPlayerNearby(150.0f)) + { + break; + }*/ + if (botAI && botAI->HasPlayerNearby(150.0f)) // [Crash fix] 'botAI' can be null earlier in the function. { break; } @@ -2333,8 +2337,10 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot) PlayerbotsDatabase.Execute(stmt); // teleport to a random inn for bot level - if (GET_PLAYERBOT_AI(bot)) - GET_PLAYERBOT_AI(bot)->Reset(true); + /*if (GET_PLAYERBOT_AI(bot)) + GET_PLAYERBOT_AI(bot)->Reset(true);*/ + if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference. + ai->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); @@ -2374,8 +2380,10 @@ void RandomPlayerbotMgr::RandomizeMin(Player* bot) PlayerbotsDatabase.Execute(stmt); // teleport to a random inn for bot level - if (GET_PLAYERBOT_AI(bot)) - GET_PLAYERBOT_AI(bot)->Reset(true); + /*if (GET_PLAYERBOT_AI(bot)) + GET_PLAYERBOT_AI(bot)->Reset(true);*/ + if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference. + ai->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); @@ -2468,7 +2476,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot) bool RandomPlayerbotMgr::IsRandomBot(Player* bot) { - if (bot && GET_PLAYERBOT_AI(bot)) + /*if (bot && GET_PLAYERBOT_AI(bot)) { if (GET_PLAYERBOT_AI(bot)->IsRealPlayer()) return false; @@ -2478,6 +2486,17 @@ bool RandomPlayerbotMgr::IsRandomBot(Player* bot) return IsRandomBot(bot->GetGUID().GetCounter()); } + return false;*/ + + if (bot) // [Tidy] Single AI acquisition + same logic. + { + if (auto* ai = GET_PLAYERBOT_AI(bot)) + { + if (ai->IsRealPlayer()) + return false; + } + return IsRandomBot(bot->GetGUID().GetCounter()); + } return false; } @@ -2495,7 +2514,7 @@ bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot) bool RandomPlayerbotMgr::IsAddclassBot(Player* bot) { - if (bot && GET_PLAYERBOT_AI(bot)) + /*if (bot && GET_PLAYERBOT_AI(bot)) { if (GET_PLAYERBOT_AI(bot)->IsRealPlayer()) return false; @@ -2505,6 +2524,17 @@ bool RandomPlayerbotMgr::IsAddclassBot(Player* bot) return IsAddclassBot(bot->GetGUID().GetCounter()); } + return false;*/ + + if (bot) // [Tidy] Single AI acquisition + same logic. + { + if (auto* ai = GET_PLAYERBOT_AI(bot)) + { + if (ai->IsRealPlayer()) + return false; + } + return IsAddclassBot(bot->GetGUID().GetCounter()); + } return false; } @@ -2844,8 +2874,9 @@ void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Play continue; } } - - GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); + // GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); // Possible crash source because we don't check if the returned pointer is not null + if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Protect the call on a null AI (World/General chat path). + ai->HandleCommand(type, text, fromPlayer); } } @@ -2918,7 +2949,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + /*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI && member == player && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster()))) { if (!bot->InBattleground()) @@ -2929,6 +2960,20 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) } break; + }*/ + if (auto* botAI = GET_PLAYERBOT_AI(bot)) // [Tidy] Avoid GET_PLAYERBOT_AI(...) on a potentially null master. + { + Player* master = botAI->GetMaster(); + if (member == player && (!master || GET_PLAYERBOT_AI(master))) + { + if (!bot->InBattleground()) + { + botAI->SetMaster(player); + botAI->ResetStrategies(); + botAI->TellMaster("Hello"); + } + break; + } } } } @@ -3067,13 +3112,29 @@ void RandomPlayerbotMgr::PrintStats() lvlPerClass[bot->getClass()] += bot->GetLevel(); lvlPerRace[bot->getRace()] += bot->GetLevel(); - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + /*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI->AllowActivity()) ++active; if (botAI->GetAiObjectContext()->GetValue("random bot update")->Get()) - ++update; - + ++update;*/ + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); // [Crash fix] Declare botAI in the loop scope and exit early if null, + if (!botAI) + continue; // real player / no AI → ignore this bot for stats + + if (botAI->AllowActivity()) + ++active; + + // Secure access to the context and the value + if (AiObjectContext* ctx = botAI->GetAiObjectContext()) + { + if (auto* v = ctx->GetValue("random bot update")) + if (v->Get()) + ++update; + } + // End CrashFix + uint32 botId = bot->GetGUID().GetCounter(); if (!GetEventValue(botId, "randomize")) ++randomize; diff --git a/src/TravelMgr.cpp b/src/TravelMgr.cpp index 69d510b9..6b46fb5a 100644 --- a/src/TravelMgr.cpp +++ b/src/TravelMgr.cpp @@ -1227,7 +1227,7 @@ std::string const QuestObjectiveTravelDestination::getTitle() return out.str(); } -bool RpgTravelDestination::isActive(Player* bot) +/*bool RpgTravelDestination::isActive(Player* bot) // Old Code { PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); AiObjectContext* context = botAI->GetAiObjectContext(); @@ -1264,6 +1264,62 @@ bool RpgTravelDestination::isActive(Player* bot) ReputationRank reaction = bot->GetReputationRank(factionEntry->faction); return reaction > REP_NEUTRAL; +}*/ + +bool RpgTravelDestination::isActive(Player* bot) +{ + // [Crash fix] Never dereference the AI if the player is real (null AI). + if (!bot) + return false; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI) + return false; // real player (no AI) => inactive destination + + AiObjectContext* context = botAI->GetAiObjectContext(); + if (!context) + return false; + + CreatureTemplate const* cInfo = GetCreatureTemplate(); + if (!cInfo) + return false; + + bool isUsefull = false; + + if (cInfo->npcflag & UNIT_NPC_FLAG_VENDOR) + if (AI_VALUE2_LAZY(bool, "group or", "should sell,can sell,following party,near leader")) + isUsefull = true; + + if (cInfo->npcflag & UNIT_NPC_FLAG_REPAIR) + if (AI_VALUE2_LAZY(bool, "group or", "should repair,can repair,following party,near leader")) + isUsefull = true; + + if (!isUsefull) + return false; + + // [Crash fix] Read the ignore list via 'context' and check that the Value exists + GuidSet const* ignoreList = nullptr; + if (auto* value = context->GetValue("ignore rpg target")) + ignoreList = &value->Get(); + + if (ignoreList) + { + for (ObjectGuid const& guid : *ignoreList) + { + if (guid.GetEntry() == getEntry()) + return false; + } + } + + // Secure access to the faction template + if (FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction)) + { + ReputationRank reaction = bot->GetReputationRank(factionEntry->faction); + return reaction > REP_NEUTRAL; + } + + // As a precaution, if the faction is not found, consider inactive + return false; } CreatureTemplate const* RpgTravelDestination::GetCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); } From 2e0a161623eaa97b7d9ceea076779ae0cabeb877 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:27:38 +0200 Subject: [PATCH 10/20] =?UTF-8?q?[Fix=20issue=20#1528]=20Close=20small=20w?= =?UTF-8?q?indow=20where=20the=20=E2=80=9Cin=20a=20BG/arena=E2=80=9D=20sta?= =?UTF-8?q?te=20can=20change=20between=20the=20check=20(InBattleground()?= =?UTF-8?q?=20/=20InArena())=20and=20grabbing=20the=20pointer=20(GetBattle?= =?UTF-8?q?ground()),=20which=20leads=20to=20a=20null=20dereference.=20(#1?= =?UTF-8?q?530)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks * Fix Issue 1528 --- src/RandomPlayerbotMgr.cpp | 54 +++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 658e43d3..def80972 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -994,9 +994,18 @@ void RandomPlayerbotMgr::CheckBgQueue() isRated = ginfo.IsRated; } - if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) || + /*if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) || (player->InArena() && player->GetBattleground()->isRated())) + isRated = true;*/ + if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID())) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 + { isRated = true; + } + else if (Battleground const* bg = player->GetBattleground()) + { + if (player->InArena() && bg->isRated()) + isRated = true; + } if (isRated) BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount++; @@ -1011,15 +1020,24 @@ void RandomPlayerbotMgr::CheckBgQueue() else BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount++; - // If a player has joined the BG, update the instance count in BattlegroundData (for consistency) + /*// If a player has joined the BG, update the instance count in BattlegroundData (for consistency) if (player->InBattleground()) { std::vector* instanceIds = nullptr; uint32 instanceId = player->GetBattleground()->GetInstanceID(); - instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances; - if (instanceIds && + instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;*/ + // If a player has joined the BG, update the instance count in BattlegroundData (for consistency) + if (Battleground const* bg = player->GetBattleground()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 + { + std::vector* instanceIds = nullptr; + uint32 instanceId = bg->GetInstanceID(); + + instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances; + + if (instanceIds && std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end()) + instanceIds->push_back(instanceId); BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size(); @@ -1082,10 +1100,20 @@ void RandomPlayerbotMgr::CheckBgQueue() isRated = ginfo.IsRated; } - if (bgQueue.IsPlayerInvitedToRatedArena(guid) || (bot->InArena() && bot->GetBattleground()->isRated())) + /*if (bgQueue.IsPlayerInvitedToRatedArena(guid) || (bot->InArena() && bot->GetBattleground()->isRated())) + isRated = true;*/ + if (bgQueue.IsPlayerInvitedToRatedArena(guid)) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 + { isRated = true; - - if (isRated) + } + else if (Battleground const* bg = bot->GetBattleground()) + { + if (bot->InArena() && bg->isRated()) + isRated = true; + } + // END [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 + + if (isRated) BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount++; else BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++; @@ -1098,10 +1126,15 @@ void RandomPlayerbotMgr::CheckBgQueue() BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++; } - if (bot->InBattleground()) + /*if (bot->InBattleground()) { std::vector* instanceIds = nullptr; - uint32 instanceId = bot->GetBattleground()->GetInstanceID(); + uint32 instanceId = bot->GetBattleground()->GetInstanceID();*/ + if (Battleground const* bg = bot->GetBattleground()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 + { + std::vector* instanceIds = nullptr; + uint32 instanceId = bg->GetInstanceID(); + //END [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 bool isArena = false; bool isRated = false; @@ -1109,7 +1142,8 @@ void RandomPlayerbotMgr::CheckBgQueue() if (bot->InArena()) { isArena = true; - if (bot->GetBattleground()->isRated()) + // if (bot->GetBattleground()->isRated()) + if (bg->isRated()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 { isRated = true; instanceIds = &BattlegroundData[queueTypeId][bracketId].ratedArenaInstances; From c6b0424c29b6a1bf5b3574135128d30d19838411 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:00:31 +0200 Subject: [PATCH 11/20] =?UTF-8?q?[Fix=20issue=20#1527]=20:=20startup=20cra?= =?UTF-8?q?sh=20in=20tank=20target=20selection=20=E2=80=94=20add=20TOCTOU?= =?UTF-8?q?=20&=20null-safety=20guards=20(#1532)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks * Fix Issue 1528 * Fix Issue #1527 --- src/strategy/values/TankTargetValue.cpp | 126 +++++++++++++++++++++++- src/strategy/values/TargetValue.cpp | 24 ++++- 2 files changed, 144 insertions(+), 6 deletions(-) diff --git a/src/strategy/values/TankTargetValue.cpp b/src/strategy/values/TankTargetValue.cpp index bcef1a3a..048ef8a5 100644 --- a/src/strategy/values/TankTargetValue.cpp +++ b/src/strategy/values/TankTargetValue.cpp @@ -14,7 +14,7 @@ class FindTargetForTankStrategy : public FindNonCcTargetStrategy public: FindTargetForTankStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minThreat(0) {} - void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override + /*void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override { if (!creature || !creature->IsAlive()) { @@ -37,6 +37,43 @@ public: return; } } + if (minThreat >= threat) + { + minThreat = threat; + result = creature; + } + }*/ + + void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override + { + // [Crash fix] Filter out anything that is not ready/valid + if (!creature || !creature->IsAlive() || !creature->IsInWorld() || creature->IsDuringRemoveFromWorld()) + return; + + if (!threatMgr) + return; + + Player* bot = botAI->GetBot(); + if (!bot) + return; + + float threat = threatMgr->GetThreat(bot); + + if (!result || !result->IsAlive() || !result->IsInWorld() || result->IsDuringRemoveFromWorld()) + { + // [Crash fix] If the previous target has become invalid, restart cleanly + minThreat = threat; + result = creature; + } + + // Neglect si la victime actuelle est le MT (ou s'il n'y a pas de victime) + if (HostileReference* cv = threatMgr->getCurrentVictim()) + { + Unit* victim = cv->getTarget(); + if (victim && victim->ToPlayer() && botAI->IsMainTank(victim->ToPlayer())) + return; + } + if (minThreat >= threat) { minThreat = threat; @@ -53,7 +90,7 @@ class FindTankTargetSmartStrategy : public FindTargetStrategy public: FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {} - void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override + /*void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override { if (Group* group = botAI->GetBot()->GetGroup()) { @@ -69,8 +106,32 @@ public: { result = attacker; } + }*/ + void CheckAttacker(Unit* attacker, ThreatMgr* /*threatMgr*/) override + { + // [Crash fix] Protect against null/out-of-world/being-removed units + if (!attacker || !attacker->IsAlive() || !attacker->IsInWorld() || attacker->IsDuringRemoveFromWorld()) + return; + + if (Player* me = botAI->GetBot()) + { + if (Group* group = me->GetGroup()) + { + ObjectGuid guid = group->GetTargetIcon(4); + if (guid && attacker->GetGUID() == guid) + return; + } + } + + // [Crash fix] If 'result' has become invalid, forget it + if (result && (!result->IsAlive() || !result->IsInWorld() || result->IsDuringRemoveFromWorld())) + result = nullptr; + + if (!result || IsBetter(attacker, result)) + result = attacker; } - bool IsBetter(Unit* new_unit, Unit* old_unit) + + /*bool IsBetter(Unit* new_unit, Unit* old_unit) { Player* bot = botAI->GetBot(); // if group has multiple tanks, main tank just focus on the current target @@ -97,8 +158,47 @@ public: return new_dis < old_dis; } return new_threat < old_threat; + }*/ + bool IsBetter(Unit* new_unit, Unit* old_unit) + { + // [Crash fix] If either one is invalid, decide straight away + if (!new_unit || !new_unit->IsAlive() || !new_unit->IsInWorld() || new_unit->IsDuringRemoveFromWorld()) + return false; + if (!old_unit || !old_unit->IsAlive() || !old_unit->IsInWorld() || old_unit->IsDuringRemoveFromWorld()) + return true; + + Player* bot = botAI->GetBot(); + if (!bot) + return false; + + // if multiple tanks, logically focus on the current target + Unit* currentTarget = botAI->GetAiObjectContext()->GetValue("current target")->Get(); + if (currentTarget && botAI->IsMainTank(bot) && botAI->GetGroupTankNum(bot) > 1) + { + if (old_unit == currentTarget) + return false; + if (new_unit == currentTarget) + return true; + } + + float new_threat = new_unit->GetThreatMgr().GetThreat(bot); + float old_threat = old_unit->GetThreatMgr().GetThreat(bot); + float new_dis = bot->GetDistance(new_unit); + float old_dis = bot->GetDistance(old_unit); + + // hasAggro? -> withinMelee? -> threat + int nl = GetIntervalLevel(new_unit); + int ol = GetIntervalLevel(old_unit); + if (nl != ol) + return nl > ol; + + if (nl == 2) + return new_dis < old_dis; + + return new_threat < old_threat; } - int32_t GetIntervalLevel(Unit* unit) + + /*int32_t GetIntervalLevel(Unit* unit) { if (!botAI->HasAggro(unit)) { @@ -109,12 +209,28 @@ public: return 1; } return 0; + }*/ + int32_t GetIntervalLevel(Unit* unit) + { + // [Crash fix] Basic guards + if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld()) + return 0; + + if (!botAI->HasAggro(unit)) + return 2; + + if (Player* bot = botAI->GetBot()) + { + if (bot->IsWithinMeleeRange(unit)) + return 1; + } + return 0; } }; Unit* TankTargetValue::Calculate() { - // FindTargetForTankStrategy strategy(botAI); + // [Note] Using the "smart" strategy below. Guards have been added in CheckAttacker/IsBetter. FindTankTargetSmartStrategy strategy(botAI); return FindTarget(&strategy); } diff --git a/src/strategy/values/TargetValue.cpp b/src/strategy/values/TargetValue.cpp index ebffd328..9b613681 100644 --- a/src/strategy/values/TargetValue.cpp +++ b/src/strategy/values/TargetValue.cpp @@ -14,7 +14,7 @@ Unit* FindTargetStrategy::GetResult() { return result; } -Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) +/*Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) { GuidVector attackers = botAI->GetAiObjectContext()->GetValue("attackers")->Get(); for (ObjectGuid const guid : attackers) @@ -27,6 +27,28 @@ Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) strategy->CheckAttacker(unit, &ThreatMgr); } + return strategy->GetResult(); +}*/ + +Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) +{ + // [Crash fix] The very first AI tick can occur before everything is "in world". + // Filter out units that are non-living / being removed / out of world. + AiObjectContext* ctx = botAI->GetAiObjectContext(); + if (!ctx) + return strategy->GetResult(); + + GuidVector attackers = ctx->GetValue("attackers")->Get(); + for (ObjectGuid const& guid : attackers) + { + Unit* unit = botAI->GetUnit(guid); + if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld()) + continue; + + ThreatMgr& threatMgrRef = unit->GetThreatMgr(); + strategy->CheckAttacker(unit, &threatMgrRef); + } + return strategy->GetResult(); } From 4e3ac609bd23d991150d956d4e69ee6de2fcf2bf Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Tue, 12 Aug 2025 01:53:48 +0200 Subject: [PATCH 12/20] Fix: prevent MoveSplineInitArgs::Validate velocity asserts (velocity > 0.01f) for bots, pets, and charmed units (#1534) * MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full * Update BotMovementUtils.h --- src/BotMovementUtils.h | 35 +++++++++++++++++++ src/PlayerbotAI.cpp | 19 +++++++++- src/PlayerbotMgr.cpp | 11 ++++++ src/strategy/actions/AreaTriggerAction.cpp | 14 +++++++- src/strategy/actions/MovementActions.cpp | 17 +++++++++ .../raids/naxxramas/RaidNaxxActions.cpp | 18 +++++++++- 6 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 src/BotMovementUtils.h diff --git a/src/BotMovementUtils.h b/src/BotMovementUtils.h new file mode 100644 index 00000000..8b32fcb1 --- /dev/null +++ b/src/BotMovementUtils.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it + * and/or modify it under version 2 of the License, or (at your option), any later version. + */ + +#pragma once +#include "Unit.h" +#include "Player.h" +#include "MotionMaster.h" + +inline bool CanStartMoveSpline(Player* bot) { + if (!bot) return false; + if (!bot->IsAlive()) return false; + if (bot->IsBeingTeleported() || bot->IsInFlight()) return false; + if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL) || bot->HasRootAura() || + bot->HasStunAura() || bot->IsCharmed() || bot->isFrozen() || bot->IsPolymorphed()) + return false; + if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE) + return false; + if (bot->GetSpeed(MOVE_RUN) <= 0.01f) return false; + return true; +} + +inline bool CanStartMoveSpline(Unit* u) { + if (!u) return false; + if (!u->IsAlive()) return false; + if (u->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING)) + return false; + if (u->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE) + return false; + if (u->GetSpeed(MOVE_RUN) <= 0.01f) return false; + return true; +} + + diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 3f67b505..50675564 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -57,6 +57,7 @@ #include "Unit.h" #include "UpdateTime.h" #include "Vehicle.h" +#include "BotMovementUtils.h" const int SPELL_TITAN_GRIP = 49152; @@ -6325,11 +6326,27 @@ void PlayerbotAI::PetFollow() if (!pet) return; pet->AttackStop(); - pet->InterruptNonMeleeSpells(false); + /* pet->InterruptNonMeleeSpells(false); pet->ClearInPetCombat(); pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, pet->GetFollowAngle()); + if (pet->ToPet()) + pet->ToPet()->ClearCastWhenWillAvailable();*/ + // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] + pet->InterruptNonMeleeSpells(false); + pet->ClearInPetCombat(); + + if (CanStartMoveSpline(pet)) + { + pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, pet->GetFollowAngle()); + } + else + { + pet->StopMovingOnCurrentPos(); // on n’envoie pas d’ordre invalide + } + if (pet->ToPet()) pet->ToPet()->ClearCastWhenWillAvailable(); + //End Fix CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) return; diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index dee1437c..4ac1809a 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -591,6 +591,17 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) bot->CleanupAfterTaxiFlight(); } + // [Fix MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full: 0x00000000000019ba Type: Player Low: 6586] Ensure valid speeds before any next movement command + bot->StopMoving(); + bot->UpdateSpeed(MOVE_WALK, true); + bot->UpdateSpeed(MOVE_RUN, true); + bot->UpdateSpeed(MOVE_SWIM, true); + bot->UpdateSpeed(MOVE_FLIGHT, true); // OK even if not flying + + if (bot->GetSpeed(MOVE_RUN) <= 0.01f) // Belt-and-suspenders: if the run speed has stayed ~0, reset to the default rate + bot->SetSpeedRate(MOVE_RUN, 1.0f); + // End Fix + // check activity botAI->AllowActivity(ALL_ACTIVITY, true); diff --git a/src/strategy/actions/AreaTriggerAction.cpp b/src/strategy/actions/AreaTriggerAction.cpp index 5937a0f7..741058b4 100644 --- a/src/strategy/actions/AreaTriggerAction.cpp +++ b/src/strategy/actions/AreaTriggerAction.cpp @@ -9,6 +9,7 @@ #include "LastMovementValue.h" #include "Playerbots.h" #include "Transport.h" +#include "BotMovementUtils.h" bool ReachAreaTriggerAction::Execute(Event event) { @@ -40,7 +41,18 @@ bool ReachAreaTriggerAction::Execute(Event event) return true; } - bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z); + // bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z); + // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] + if (CanStartMoveSpline(bot)) + { + bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z); + } + else + { + bot->StopMovingOnCurrentPos(); + botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); + return false; + } float distance = bot->GetDistance(at->x, at->y, at->z); float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay; diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index d3e596e3..c4e45505 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -42,6 +42,7 @@ #include "Vehicle.h" #include "WaypointMovementGenerator.h" #include "Corpse.h" +#include "BotMovementUtils.h" MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) { @@ -81,6 +82,10 @@ bool MovementAction::JumpTo(uint32 mapId, float x, float y, float z, MovementPri float botZ = bot->GetPositionZ(); float speed = bot->GetSpeed(MOVE_RUN); MotionMaster& mm = *bot->GetMotionMaster(); + // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] + if (!CanStartMoveSpline(bot)) + return false; + // End Fix mm.Clear(); mm.MoveJump(x, y, z, speed, speed, 1); AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), 1000, priority); @@ -207,6 +212,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, if (distance > 0.01f) { MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot + // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] + if (!CanStartMoveSpline(bot)) + return false; + // End Fix mm.Clear(); if (!backwards) { @@ -242,6 +251,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, // botAI->InterruptSpell(); // } MotionMaster& mm = *bot->GetMotionMaster(); + //[Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] + if (!CanStartMoveSpline(bot)) + return false; + // End Fix mm.Clear(); if (!backwards) { @@ -284,6 +297,10 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, // } MotionMaster& mm = *bot->GetMotionMaster(); G3D::Vector3 endP = path.back(); + // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] + if (!CanStartMoveSpline(bot)) + return false; + // End Fix mm.Clear(); if (!backwards) { diff --git a/src/strategy/raids/naxxramas/RaidNaxxActions.cpp b/src/strategy/raids/naxxramas/RaidNaxxActions.cpp index cf12bb8e..059cea79 100644 --- a/src/strategy/raids/naxxramas/RaidNaxxActions.cpp +++ b/src/strategy/raids/naxxramas/RaidNaxxActions.cpp @@ -9,6 +9,7 @@ #include "RaidNaxxStrategy.h" #include "ScriptedCreature.h" #include "SharedDefines.h" +#include "BotMovementUtils.h" bool GrobbulusGoBehindAction::Execute(Event event) { @@ -258,11 +259,26 @@ bool RazuviousUseObedienceCrystalAction::Execute(Event event) return false; } if (charm->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == NULL_MOTION_TYPE) - { + /*{ charm->GetMotionMaster()->Clear(); charm->GetMotionMaster()->MoveChase(target); + charm->GetAI()->AttackStart(target); + }*/ + // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] + { + if (CanStartMoveSpline(charm)) + { + charm->GetMotionMaster()->Clear(); + charm->GetMotionMaster()->MoveChase(target); + } + else + { + charm->StopMoving(); + } + charm->GetAI()->AttackStart(target); } + // End Fix Aura* forceObedience = botAI->GetAura("force obedience", charm); uint32 duration_time; if (!forceObedience) From ca2e2ef0dbd8dcfb16123db65ae638424550e50c Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Tue, 12 Aug 2025 01:54:17 +0200 Subject: [PATCH 13/20] [Fix] teleport to invalid map or invalid coordinates (x , y , z 200000, o ) given when teleporting player (g UI d full type player low , name , map , x , y , z , o ) (#1538) * MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full * Update BotMovementUtils.h * Playerbots: guard against invalid-Z teleports --- src/PlayerbotAI.cpp | 1 + src/Playerbots.h | 42 +++++++++++++++++++ src/RandomPlayerbotMgr.cpp | 6 ++- .../actions/BattleGroundJoinAction.cpp | 3 +- src/strategy/actions/BattleGroundTactics.cpp | 6 ++- src/strategy/actions/ReleaseSpiritAction.cpp | 6 ++- .../actions/ReviveFromCorpseAction.cpp | 6 ++- .../raids/icecrown/RaidIccActions.cpp | 3 +- .../raids/ulduar/RaidUlduarActions.cpp | 16 +++++-- .../raids/vaultofarchavon/RaidVoAActions.cpp | 8 +++- src/strategy/rpg/NewRpgBaseAction.cpp | 3 +- 11 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 50675564..878888e1 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -721,6 +721,7 @@ void PlayerbotAI::HandleTeleportAck() bot->GetSession()->HandleMoveWorldportAck(); } // SetNextCheckDelay(urand(2000, 5000)); + SetNextCheckDelay(urand(500, 1500)); // short delay to break bursts without hindering gameplay if (sPlayerbotAIConfig->applyInstanceStrategies) ApplyInstanceStrategies(bot->GetMapId(), true); EvaluateHealerDpsStrategy(); diff --git a/src/Playerbots.h b/src/Playerbots.h index 5b82771b..3ed03f23 100644 --- a/src/Playerbots.h +++ b/src/Playerbots.h @@ -49,4 +49,46 @@ int strcmpi(char const* s1, char const* s2); #define GAI_VALUE(type, name) sSharedValueContext->getGlobalValue(name)->Get() #define GAI_VALUE2(type, name, param) sSharedValueContext->getGlobalValue(name, param)->Get() +// ---- Safe teleport wrappers (module-only) ---- +#include "Map.h" +#include +#include "TravelMgr.h" + +inline bool TeleportToSafe(Player* p, uint32 mapId, float x, float y, float z, float o) +{ + if (!p) return false; + + // If the height is invalid (-200000) or not finite, attempt ONE correction on the same map. + if (z <= -199000.0f || !std::isfinite(z)) + { + if (p->GetMapId() == mapId && p->GetMap()) + { + float hz = p->GetMap()->GetHeight(p->GetPhaseMask(), x, y, p->GetPositionZ(), true); + if (hz > -199000.0f && std::isfinite(hz)) + z = hz; + else + return false; // still invalid -> cancel the TP + } + else + { + return false; // different map: do not "guess" the height here + } + } + return p->TeleportTo(mapId, x, y, z, o); +} + +inline bool TeleportToSafe(Player* p, Position const& pos) +{ + // Position doesn't have mapId: we keep actual bot map + return TeleportToSafe(p, p->GetMapId(), + pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), + pos.GetOrientation()); +} + +inline bool TeleportToSafe(Player* p, WorldPosition pos) +{ + return TeleportToSafe(p, pos.getMapId(), pos.getX(), pos.getY(), pos.getZ(), pos.getO()); +} +// ---- /Safe teleport wrappers ---- + #endif diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index def80972..4f1cd1bb 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -1772,7 +1772,8 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI) botAI->Reset(true); - bot->TeleportTo(loc.GetMapId(), x, y, z, 0); + //bot->TeleportTo(loc.GetMapId(), x, y, z, 0); + TeleportToSafe(bot, loc.GetMapId(), x, y, z, 0); // [Fix] Avoid silly teleports bot->SendMovementFlagUpdate(); if (pmo) @@ -3047,7 +3048,8 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) } while (true); } - player->TeleportTo(botPos); + // player->TeleportTo(botPos); + TeleportToSafe(player, botPos); // [Fix] Avoid silly teleports // player->Relocate(botPos.getX(), botPos.getY(), botPos.getZ(), botPos.getO()); } diff --git a/src/strategy/actions/BattleGroundJoinAction.cpp b/src/strategy/actions/BattleGroundJoinAction.cpp index 09523fd2..1e9d9598 100644 --- a/src/strategy/actions/BattleGroundJoinAction.cpp +++ b/src/strategy/actions/BattleGroundJoinAction.cpp @@ -176,7 +176,8 @@ bool BGJoinAction::gatherArenaTeam(ArenaType type) continue; memberBotAI->Reset(); - member->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0); + // member->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0); + TeleportToSafe(member, bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0); LOG_INFO("playerbots", "Bot {} <{}>: Member of <{}>", member->GetGUID().ToString().c_str(), member->GetName().c_str(), arenateam->GetName().c_str()); diff --git a/src/strategy/actions/BattleGroundTactics.cpp b/src/strategy/actions/BattleGroundTactics.cpp index fab932f7..0dfcd823 100644 --- a/src/strategy/actions/BattleGroundTactics.cpp +++ b/src/strategy/actions/BattleGroundTactics.cpp @@ -4289,9 +4289,11 @@ bool ArenaTactics::moveToCenter(Battleground* bg) { // they like to hang around at the tip of the pipes doing nothing, so we just teleport them down if (bot->GetDistance(1333.07f, 817.18f, 13.35f) < 4) - bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); + // bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); + TeleportToSafe(bot, bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); // [Fix] Avaid silly teleport if (bot->GetDistance(1250.13f, 764.79f, 13.34f) < 4) - bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); + // bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); + TeleportToSafe(bot, bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); // [Fix] Avaid silly teleport } break; case BATTLEGROUND_RV: diff --git a/src/strategy/actions/ReleaseSpiritAction.cpp b/src/strategy/actions/ReleaseSpiritAction.cpp index cff34f8d..12228603 100644 --- a/src/strategy/actions/ReleaseSpiritAction.cpp +++ b/src/strategy/actions/ReleaseSpiritAction.cpp @@ -147,7 +147,8 @@ bool AutoReleaseSpiritAction::HandleBattlegroundSpiritHealer() // and in IOC it's not within clicking range when they res in own base // Teleport to nearest friendly Spirit Healer when not currently in range of one. - bot->TeleportTo(bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f); + // bot->TeleportTo(bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f); + TeleportToSafe(bot, bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f); // [Fix] Avoid silly teleport RESET_AI_VALUE(bool, "combat::self target"); RESET_AI_VALUE(WorldPosition, "current position"); } @@ -244,7 +245,8 @@ int64 RepopAction::CalculateDeadTime() const void RepopAction::PerformGraveyardTeleport(const GraveyardStruct* graveyard) const { - bot->TeleportTo(graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f); + // bot->TeleportTo(graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f); + TeleportToSafe(bot, graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f); // [Fix] Avoid Silly teleport RESET_AI_VALUE(bool, "combat::self target"); RESET_AI_VALUE(WorldPosition, "current position"); } diff --git a/src/strategy/actions/ReviveFromCorpseAction.cpp b/src/strategy/actions/ReviveFromCorpseAction.cpp index 960f438f..f55904cb 100644 --- a/src/strategy/actions/ReviveFromCorpseAction.cpp +++ b/src/strategy/actions/ReviveFromCorpseAction.cpp @@ -169,7 +169,8 @@ bool FindCorpseAction::Execute(Event event) if (deadTime > delay) { bot->GetMotionMaster()->Clear(); - bot->TeleportTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); + // bot->TeleportTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); + TeleportToSafe(bot, moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); // [fix] Avoid Silly Teleport } moved = true; @@ -350,7 +351,8 @@ bool SpiritHealerAction::Execute(Event event) // if (!botAI->HasActivePlayerMaster()) // { context->GetValue("death count")->Set(dCount + 1); - return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); + // return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); + return TeleportToSafe(bot, ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); // [Fix] Avoid Silly teleport // } // LOG_INFO("playerbots", "Bot {} {}:{} <{}> can't find a spirit healer", bot->GetGUID().ToString().c_str(), diff --git a/src/strategy/raids/icecrown/RaidIccActions.cpp b/src/strategy/raids/icecrown/RaidIccActions.cpp index eaad5d2f..0d135b59 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.cpp +++ b/src/strategy/raids/icecrown/RaidIccActions.cpp @@ -957,7 +957,8 @@ bool IccGunshipTeleportHordeAction::Execute(Event event) bool IccGunshipTeleportHordeAction::TeleportTo(const Position& position) { - return bot->TeleportTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), + // return bot->TeleportTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), + return TeleportToSafe(bot, bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(),// [Fix]Avoid silly teleport bot->GetOrientation()); } diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index 3d180820..8517a0bf 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -1357,10 +1357,14 @@ bool KologarnMarkDpsTargetAction::Execute(Event event) bool KologarnFallFromFloorAction::Execute(Event event) { - return bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionX(), + /*return bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionX(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionY(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionZ(), - ULDUAR_KOLOGARN_RESTORE_POSITION.GetOrientation()); + ULDUAR_KOLOGARN_RESTORE_POSITION.GetOrientation());*/ + return TeleportToSafe(bot, bot->GetMapId(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionX(), // [Fix] Avoid silly teleport + ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionY(), + ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionZ(), + ULDUAR_KOLOGARN_RESTORE_POSITION.GetOrientation()); } bool KologarnFallFromFloorAction::isUseful() @@ -1407,14 +1411,18 @@ bool KologarnEyebeamAction::Execute(Event event) KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI); if (runToLeftSide) { - teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionX(), + // teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionX(), + teleportedToPoint = TeleportToSafe(bot, bot->GetMapId(), + ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionX(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionY(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionZ(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetOrientation()); } else { - teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionX(), + // teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionX(), + teleportedToPoint = TeleportToSafe(bot, bot->GetMapId(), + ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionX(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionY(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionZ(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetOrientation()); diff --git a/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp b/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp index 05d6328e..b41c670f 100644 --- a/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp +++ b/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp @@ -175,9 +175,13 @@ bool EmalonOverchargeAction::isUseful() bool EmalonFallFromFloorAction::Execute(Event event) { - return bot->TeleportTo(bot->GetMapId(), VOA_EMALON_RESTORE_POSITION.GetPositionX(), + /*return bot->TeleportTo(bot->GetMapId(), VOA_EMALON_RESTORE_POSITION.GetPositionX(), VOA_EMALON_RESTORE_POSITION.GetPositionY(), VOA_EMALON_RESTORE_POSITION.GetPositionZ(), - VOA_EMALON_RESTORE_POSITION.GetOrientation()); + VOA_EMALON_RESTORE_POSITION.GetOrientation());*/ + return TeleportToSafe(bot, bot->GetMapId(), VOA_EMALON_RESTORE_POSITION.GetPositionX(), //[Fix] Avoid Silly Teleport + VOA_EMALON_RESTORE_POSITION.GetPositionY(), + VOA_EMALON_RESTORE_POSITION.GetPositionZ(), + VOA_EMALON_RESTORE_POSITION.GetOrientation()); } bool EmalonFallFromFloorAction::isUseful() diff --git a/src/strategy/rpg/NewRpgBaseAction.cpp b/src/strategy/rpg/NewRpgBaseAction.cpp index 5e71c5c5..9b0ab2a1 100644 --- a/src/strategy/rpg/NewRpgBaseAction.cpp +++ b/src/strategy/rpg/NewRpgBaseAction.cpp @@ -67,7 +67,8 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) bot->GetName(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name); - return bot->TeleportTo(dest); + // return bot->TeleportTo(dest); + return TeleportToSafe(bot, dest); //[Fix] Avoid Silly teleport } float dis = bot->GetExactDist(dest); From 3fff58df1a2058894e9b758be07869aec87c2c70 Mon Sep 17 00:00:00 2001 From: Alex Dcnh <140754794+Wishmaster117@users.noreply.github.com> Date: Tue, 12 Aug 2025 08:15:22 +0200 Subject: [PATCH 14/20] [Large server fix] #1537 Serialize playerBots/botLoading with a mutex and use snapshot-based loops to fix concurrency crashes (#1540) * MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full * Update BotMovementUtils.h * Playerbots: guard against invalid-Z teleports * Update PlayerbotMgr.cpp --- src/PlayerbotMgr.cpp | 472 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 446 insertions(+), 26 deletions(-) diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 4ac1809a..02410335 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -41,6 +41,10 @@ #include "Log.h" #include // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) #include "TravelMgr.h" +#include +#include + +static std::mutex g_botMapsMx; // protect playerBots and botLoading namespace { // [Crash fix] Centralize clearing of pointer values in the AI context @@ -105,9 +109,16 @@ public: void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId) { - // bot is loading + /*// bot is loading if (botLoading.find(playerGuid) != botLoading.end()) - return; + return;*/ + + // bot is loading (protégé) + { + std::lock_guard lk(g_botMapsMx); + if (botLoading.find(playerGuid) != botLoading.end()) + return; + } // has bot already been added? Player* bot = ObjectAccessor::FindConnectedPlayer(playerGuid); @@ -145,7 +156,16 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); return; } - uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); + + // read botLoading.size() locked + size_t loadingCount = 0; + { + std::lock_guard lk(g_botMapsMx); + loadingCount = botLoading.size(); + } + + // uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); + uint32 count = mgr->GetPlayerbotsCount() + static_cast(loadingCount); if (count >= sPlayerbotAIConfig->maxAddedBots) { allowed = false; @@ -161,14 +181,22 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId } return; } - std::shared_ptr holder = - std::make_shared(this, masterAccountId, accountId, playerGuid); + // std::shared_ptr holder = + // std::make_shared(this, masterAccountId, accountId, playerGuid); + auto holder = std::make_shared(this, masterAccountId, accountId, playerGuid); if (!holder->Initialize()) { return; } - botLoading.insert(playerGuid); + // botLoading.insert(playerGuid); + // Protected insert + { + std::lock_guard lk(g_botMapsMx); + if (botLoading.find(playerGuid) != botLoading.end()) + return; // already loging + botLoading.insert(playerGuid); // we reserve the GUID + } // Always login in with world session to avoid race condition sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)) @@ -185,7 +213,11 @@ bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId) void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder) { + // Copy immediatly holder value + const ObjectGuid guid = holder.GetGuid(); + const uint32 masterAccountId = holder.GetMasterAccountId(); uint32 botAccountId = holder.GetAccountId(); + // At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this // allows channels to work as intended) WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, @@ -200,11 +232,16 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId); botSession->LogoutPlayer(true); delete botSession; - botLoading.erase(holder.GetGuid()); + // botLoading.erase(holder.GetGuid()); + { + std::lock_guard lk(g_botMapsMx); + botLoading.erase(guid); + } return; } - uint32 masterAccount = holder.GetMasterAccountId(); + // uint32 masterAccount = holder.GetMasterAccountId(); + uint32 masterAccount = masterAccountId; // Avoid read in 'holder' after login WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr; // Check if masterSession->GetPlayer() is valid @@ -217,10 +254,14 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con sRandomPlayerbotMgr->OnPlayerLogin(bot); OnBotLogin(bot); - botLoading.erase(holder.GetGuid()); + // botLoading.erase(holder.GetGuid()); + { + std::lock_guard lk(g_botMapsMx); + botLoading.erase(guid); + } } -void PlayerbotHolder::UpdateSessions() +/*void PlayerbotHolder::UpdateSessions() { for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) { @@ -238,6 +279,29 @@ void PlayerbotHolder::UpdateSessions() HandleBotPackets(bot->GetSession()); } } +}*/ + +void PlayerbotHolder::UpdateSessions() +{ + PlayerBotMap botsCopy; + { + std::lock_guard lk(g_botMapsMx); + botsCopy = playerBots; + } + + for (const auto& kv : botsCopy) + { + Player* const bot = kv.second; + if (bot->IsBeingTeleported()) + { + if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot)) + botAI->HandleTeleportAck(); + } + else if (bot->IsInWorld()) + { + HandleBotPackets(bot->GetSession()); + } + } } /*void PlayerbotHolder::HandleBotPackets(WorldSession* session) @@ -287,8 +351,27 @@ void PlayerbotHolder::LogoutAllBots() } */ - PlayerBotMap bots = playerBots; + /*PlayerBotMap bots = playerBots; for (auto& itr : bots) + { + Player* bot = itr.second; + if (!bot) + continue; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI || botAI->IsRealPlayer()) + continue; + + LogoutPlayerBot(bot->GetGUID()); + }*/ + // Snapshot under lock for safe iteration + PlayerBotMap botsCopy; + { + std::lock_guard lk(g_botMapsMx); + botsCopy = playerBots; + } + + for (auto& itr : botsCopy) { Player* bot = itr.second; if (!bot) @@ -302,7 +385,7 @@ void PlayerbotHolder::LogoutAllBots() } } -void PlayerbotMgr::CancelLogout() +/*void PlayerbotMgr::CancelLogout() { Player* master = GetMaster(); if (!master) @@ -323,6 +406,53 @@ void PlayerbotMgr::CancelLogout() } } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI || botAI->IsRealPlayer()) + continue; + + if (botAI->GetMaster() != master) + continue; + + if (bot->GetSession()->isLogingOut()) + { + WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); + bot->GetSession()->HandleLogoutCancelOpcode(data); + } + } +}*/ + +void PlayerbotMgr::CancelLogout() +{ + Player* master = GetMaster(); + if (!master) + return; + + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (!botAI || botAI->IsRealPlayer()) + continue; + + if (bot->GetSession()->isLogingOut()) + { + WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); + bot->GetSession()->HandleLogoutCancelOpcode(data); + botAI->TellMaster("Logout cancelled!"); + } + } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -489,33 +619,47 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) } void PlayerbotHolder::RemoveFromPlayerbotsMap(ObjectGuid guid) +// { +// playerBots.erase(guid); +// } +// Protected erase { + std::lock_guard lk(g_botMapsMx); playerBots.erase(guid); } Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const { + std::lock_guard lk(g_botMapsMx); // We protect PlayerBotMap::const_iterator it = playerBots.find(playerGuid); - return (it == playerBots.end()) ? 0 : it->second; + return (it == playerBots.end()) ? nullptr : it->second;// (nullptr) } Player* PlayerbotHolder::GetPlayerBot(ObjectGuid::LowType lowGuid) const { ObjectGuid playerGuid = ObjectGuid::Create(lowGuid); + std::lock_guard lk(g_botMapsMx); // We protect PlayerBotMap::const_iterator it = playerBots.find(playerGuid); - return (it == playerBots.end()) ? 0 : it->second; + return (it == playerBots.end()) ? nullptr : it->second; } void PlayerbotHolder::OnBotLogin(Player* const bot) { // Prevent duplicate login - if (playerBots.find(bot->GetGUID()) != playerBots.end()) + /*if (playerBots.find(bot->GetGUID()) != playerBots.end()) { return; + }*/ + { + std::lock_guard lk(g_botMapsMx); + if (playerBots.find(bot->GetGUID()) != playerBots.end()) + return; + + playerBots[bot->GetGUID()] = bot; } sPlayerbotsMgr->AddPlayerbotData(bot, true); - playerBots[bot->GetGUID()] = bot; + // playerBots[bot->GetGUID()] = bot; OnBotLoginInternal(bot); @@ -1230,8 +1374,13 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg // If the user requested a specific gender, skip any character that doesn't match. if (gender != -1 && GetOfflinePlayerGender(guid) != gender) continue; - if (botLoading.find(guid) != botLoading.end()) - continue; + /*if (botLoading.find(guid) != botLoading.end()) + continue;*/ + { + std::lock_guard lk(g_botMapsMx); + if (botLoading.find(guid) != botLoading.end()) + continue; + } if (ObjectAccessor::FindConnectedPlayer(guid)) continue; uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid); @@ -1295,12 +1444,25 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (charnameStr == "!" && master && master->GetSession()->GetSecurity() > SEC_GAMEMASTER) { - for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) + /*for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) { if (Player* bot = i->second) if (bot->IsInWorld()) bots.insert(bot->GetName()); - } + }*/ + // Snapshot under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) + botsCopy.push_back(i->second); + } + for (Player* const bot : botsCopy) + { + if (bot && bot->IsInWorld()) + bots.insert(bot->GetName()); + } + } std::vector chars = split(charnameStr, ','); @@ -1404,7 +1566,7 @@ uint32 PlayerbotHolder::GetAccountId(ObjectGuid guid) return 0; } -std::string const PlayerbotHolder::ListBots(Player* master) +/*std::string const PlayerbotHolder::ListBots(Player* master) { std::set bots; std::map classNames; @@ -1490,6 +1652,103 @@ std::string const PlayerbotHolder::ListBots(Player* master) out << online[name] << name << " " << classes[name]; } + return out.str(); +}*/ + +std::string const PlayerbotHolder::ListBots(Player* master) +{ + std::set bots; + std::map classNames; + + classNames[CLASS_DEATH_KNIGHT] = "Death Knight"; + classNames[CLASS_DRUID] = "Druid"; + classNames[CLASS_HUNTER] = "Hunter"; + classNames[CLASS_MAGE] = "Mage"; + classNames[CLASS_PALADIN] = "Paladin"; + classNames[CLASS_PRIEST] = "Priest"; + classNames[CLASS_ROGUE] = "Rogue"; + classNames[CLASS_SHAMAN] = "Shaman"; + classNames[CLASS_WARLOCK] = "Warlock"; + classNames[CLASS_WARRIOR] = "Warrior"; + classNames[CLASS_DEATH_KNIGHT] = "DeathKnight"; + + std::map online; + std::vector names; + std::map classes; + + // Snapshot under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + std::string const name = bot->GetName(); + bots.insert(name); + + names.push_back(name); + online[name] = "+"; + classes[name] = classNames[bot->getClass()]; + } + + if (master) + { + QueryResult results = CharacterDatabase.Query( + "SELECT class, name FROM characters WHERE account = {}", + master->GetSession()->GetAccountId()); + + if (results) + { + do + { + Field* fields = results->Fetch(); + uint8 cls = fields[0].Get(); + std::string const name = fields[1].Get(); + if (bots.find(name) == bots.end() && name != master->GetSession()->GetPlayerName()) + { + names.push_back(name); + online[name] = "-"; + classes[name] = classNames[cls]; + } + } while (results->NextRow()); + } + } + + std::sort(names.begin(), names.end()); + + if (master) + { + if (Group* group = master->GetGroup()) + { + Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); + for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) + { + Player* member = ObjectAccessor::FindPlayer(itr->guid); + if (member && sRandomPlayerbotMgr->IsRandomBot(member)) + { + std::string const name = member->GetName(); + + names.push_back(name); + online[name] = "+"; + classes[name] = classNames[member->getClass()]; + } + } + } + } + + std::ostringstream out; + bool first = true; + out << "Bot roster: "; + for (std::vector::iterator i = names.begin(); i != names.end(); ++i) + { + if (first) first = false; else out << ", "; + std::string const name = *i; + out << online[name] << name << " " << classes[name]; + } + return out.str(); } @@ -1516,7 +1775,7 @@ std::string const PlayerbotHolder::LookupBots(Player* master) return ret_msg; } -uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) +/*uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) { uint32 count = 0; for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) @@ -1528,6 +1787,25 @@ uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) } } return count; +}*/ + +uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) +{ + uint32 count = 0; + + // Snapshot under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + if (bot && bot->IsInWorld() && bot->getClass() == cls) + ++count; + + return count; } PlayerbotMgr::PlayerbotMgr(Player* const master) : PlayerbotHolder(), master(master), lastErrorTell(0) {} @@ -1544,7 +1822,7 @@ void PlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) CheckTellErrors(elapsed); } -void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) +/*void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) { Player* master = GetMaster(); if (!master) @@ -1570,6 +1848,47 @@ void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) botAI->HandleCommand(type, text, master); } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI && botAI->GetMaster() == master) + botAI->HandleCommand(type, text, master); + } +}*/ + +void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) +{ + Player* master = GetMaster(); + if (!master) + return; + + if (text.find(sPlayerbotAIConfig->commandSeparator) != std::string::npos) + { + std::vector commands; + split(commands, text, sPlayerbotAIConfig->commandSeparator.c_str()); + for (std::vector::iterator i = commands.begin(); i != commands.end(); ++i) + HandleCommand(type, *i); + return; + } + + // Snapshot of "master" bots under lock to avoid race conditions + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) + botAI->HandleCommand(type, text, master); + } + + // Random bots : unchanges for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -1580,7 +1899,7 @@ void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) } } -void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) +/*void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -1601,6 +1920,53 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) botAI->HandleMasterIncomingPacket(packet); } + switch (packet.GetOpcode()) + { + // if master is logging out, log out all bots + case CMSG_LOGOUT_REQUEST: + { + LogoutAllBots(); + break; + } + // if master cancelled logout, cancel too + case CMSG_LOGOUT_CANCEL: + { + CancelLogout(); + break; + } + } +}*/ + +void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) +{ + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + if (!bot) + continue; + + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) + botAI->HandleMasterIncomingPacket(packet); + } + + // Boucle random bots (inchangée) + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI && botAI->GetMaster() == GetMaster()) + botAI->HandleMasterIncomingPacket(packet); + } + switch (packet.GetOpcode()) { // if master is logging out, log out all bots @@ -1618,7 +1984,8 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) } } -void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) + +/*void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -1628,6 +1995,33 @@ void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) botAI->HandleMasterOutgoingPacket(packet); } + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI && botAI->GetMaster() == GetMaster()) + botAI->HandleMasterOutgoingPacket(packet); + } +}*/ +void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) +{ + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + if (botAI) + botAI->HandleMasterOutgoingPacket(packet); + } + + // Random bots loop unchanged for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -1638,7 +2032,7 @@ void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) } } -void PlayerbotMgr::SaveToDB() +/*void PlayerbotMgr::SaveToDB() { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -1653,6 +2047,32 @@ void PlayerbotMgr::SaveToDB() if (GET_PLAYERBOT_AI(bot) && GET_PLAYERBOT_AI(bot)->GetMaster() == GetMaster()) bot->SaveToDB(false, false); } +}*/ + +void PlayerbotMgr::SaveToDB() +{ + // Snapshot of "master" bots under lock + std::vector botsCopy; + { + std::lock_guard lk(g_botMapsMx); + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) + botsCopy.push_back(it->second); + } + + for (Player* const bot : botsCopy) + { + if (bot) + bot->SaveToDB(false, false); + } + + for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); + it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + PlayerbotAI* ai = GET_PLAYERBOT_AI(bot); + if (ai && ai->GetMaster() == GetMaster()) + bot->SaveToDB(false, false); + } } void PlayerbotMgr::OnBotLoginInternal(Player* const bot) From 8d51092d4234e7cb606c54f557930312c974d90c Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Tue, 12 Aug 2025 22:10:47 +0200 Subject: [PATCH 15/20] As requested revert for threadfixes last few days (#1552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Revert "[Large server fix] #1537 Serialize playerBots/botLoading with a mutex and use snapshot-based loops to fix concurrency crashes (#1540)" This reverts commit 3fff58df1a2058894e9b758be07869aec87c2c70. * Revert "[Fix] teleport to invalid map or invalid coordinates (x , y , z 200000, o ) given when teleporting player (g UI d full type player low , name , map , x , y , z , o ) (#1538)" This reverts commit ca2e2ef0dbd8dcfb16123db65ae638424550e50c. * Revert "Fix: prevent MoveSplineInitArgs::Validate velocity asserts (velocity > 0.01f) for bots, pets, and charmed units (#1534)" This reverts commit 4e3ac609bd23d991150d956d4e69ee6de2fcf2bf. * Revert "[Fix issue #1527] : startup crash in tank target selection — add TOCTOU & null-safety guards (#1532)" This reverts commit c6b0424c29b6a1bf5b3574135128d30d19838411. * Revert "[Fix issue #1528] Close small window where the “in a BG/arena” state can change between the check (InBattleground() / InArena()) and grabbing the pointer (GetBattleground()), which leads to a null dereference. (#1530)" This reverts commit 2e0a161623eaa97b7d9ceea076779ae0cabeb877. * Revert "Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks (#1529)" This reverts commit e4ea8e2694b0f6d098a945c6f863526cd14f9b3f. * Revert "Dont wait to travel when in combat. (#1524)" This reverts commit ddfa919154529fee59e7ba30d2ebe29c0ae4abdf. * Revert "nullptr fix (#1523)" This reverts commit 380312ffd231fd5e663a8a17daa80dd39906e3f0. * Revert "Playerbots/LFG: fix false not eligible & dungeon 0/type 0, add clear diagnostics (#1521)" This reverts commit 872e4176137b66c83ebcb03932fa8ff1e39fd791. * Revert "nullptr exception (#1520)" This reverts commit 3d28a815089fd0a878a6a1d469db657c6030d4b2. * Revert "Removed bot freezing at startup and system message, not relevant anymore (#1519)" This reverts commit bcd6f5bc066d5e8a54f2d37b7dfc54e5db0dd2d1. --- src/BotMovementUtils.h | 35 - src/BroadcastHelper.cpp | 17 +- src/PlayerbotAI.cpp | 63 +- src/PlayerbotAI.h | 1 + src/PlayerbotMgr.cpp | 604 ++---------------- src/Playerbots.cpp | 168 +---- src/Playerbots.h | 42 -- src/RandomPlayerbotMgr.cpp | 147 +---- src/TravelMgr.cpp | 58 +- src/factory/PlayerbotFactory.cpp | 31 +- src/strategy/actions/AreaTriggerAction.cpp | 14 +- .../actions/BattleGroundJoinAction.cpp | 3 +- src/strategy/actions/BattleGroundTactics.cpp | 6 +- .../actions/MoveToTravelTargetAction.cpp | 2 +- src/strategy/actions/MovementActions.cpp | 17 - src/strategy/actions/ReleaseSpiritAction.cpp | 6 +- .../actions/ReviveFromCorpseAction.cpp | 6 +- .../raids/icecrown/RaidIccActions.cpp | 3 +- .../raids/naxxramas/RaidNaxxActions.cpp | 18 +- .../raids/ulduar/RaidUlduarActions.cpp | 16 +- .../raids/vaultofarchavon/RaidVoAActions.cpp | 8 +- src/strategy/rpg/NewRpgBaseAction.cpp | 3 +- src/strategy/values/TankTargetValue.cpp | 126 +--- src/strategy/values/TargetValue.cpp | 24 +- 24 files changed, 144 insertions(+), 1274 deletions(-) delete mode 100644 src/BotMovementUtils.h diff --git a/src/BotMovementUtils.h b/src/BotMovementUtils.h deleted file mode 100644 index 8b32fcb1..00000000 --- a/src/BotMovementUtils.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2016+ AzerothCore , released under GNU GPL v2 license, you may redistribute it - * and/or modify it under version 2 of the License, or (at your option), any later version. - */ - -#pragma once -#include "Unit.h" -#include "Player.h" -#include "MotionMaster.h" - -inline bool CanStartMoveSpline(Player* bot) { - if (!bot) return false; - if (!bot->IsAlive()) return false; - if (bot->IsBeingTeleported() || bot->IsInFlight()) return false; - if (bot->HasUnitState(UNIT_STATE_LOST_CONTROL) || bot->HasRootAura() || - bot->HasStunAura() || bot->IsCharmed() || bot->isFrozen() || bot->IsPolymorphed()) - return false; - if (bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE) - return false; - if (bot->GetSpeed(MOVE_RUN) <= 0.01f) return false; - return true; -} - -inline bool CanStartMoveSpline(Unit* u) { - if (!u) return false; - if (!u->IsAlive()) return false; - if (u->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_CONFUSED | UNIT_STATE_FLEEING)) - return false; - if (u->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE) - return false; - if (u->GetSpeed(MOVE_RUN) <= 0.01f) return false; - return true; -} - - diff --git a/src/BroadcastHelper.cpp b/src/BroadcastHelper.cpp index 2de066f6..85bc5221 100644 --- a/src/BroadcastHelper.cpp +++ b/src/BroadcastHelper.cpp @@ -947,21 +947,8 @@ bool BroadcastHelper::BroadcastSuggestThunderfury(PlayerbotAI* ai, Player* bot) { std::map placeholders; ItemTemplate const* thunderfuryProto = sObjectMgr->GetItemTemplate(19019); - // placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); // Old code - // [Crash fix] Protect from nil AI : a real player doesn't have PlayerbotAI. - // Before: direct deref GET_PLAYERBOT_AI(bot)->... could crash World/General. - if (auto* ai = GET_PLAYERBOT_AI(bot)) - { - if (auto* chat = ai->GetChatHelper()) - placeholders["%thunderfury_link"] = chat->FormatItem(thunderfuryProto); - else - placeholders["%thunderfury_link"] = ""; // fallback: no chat helper - } - else - { - placeholders["%thunderfury_link"] = ""; // fallback: no d'AI (real player) - } - // End crash fix + placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); + return BroadcastToChannelWithGlobalChance( ai, BOT_TEXT2("thunderfury_spam", placeholders), diff --git a/src/PlayerbotAI.cpp b/src/PlayerbotAI.cpp index 878888e1..09a6ef82 100644 --- a/src/PlayerbotAI.cpp +++ b/src/PlayerbotAI.cpp @@ -57,7 +57,6 @@ #include "Unit.h" #include "UpdateTime.h" #include "Vehicle.h" -#include "BotMovementUtils.h" const int SPELL_TITAN_GRIP = 49152; @@ -721,7 +720,6 @@ void PlayerbotAI::HandleTeleportAck() bot->GetSession()->HandleMoveWorldportAck(); } // SetNextCheckDelay(urand(2000, 5000)); - SetNextCheckDelay(urand(500, 1500)); // short delay to break bursts without hindering gameplay if (sPlayerbotAIConfig->applyInstanceStrategies) ApplyInstanceStrategies(bot->GetMapId(), true); EvaluateHealerDpsStrategy(); @@ -2375,7 +2373,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry) return name; } -/*std::vector PlayerbotAI::GetPlayersInGroup() +std::vector PlayerbotAI::GetPlayersInGroup() { std::vector members; @@ -2394,34 +2392,6 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry) members.push_back(ref->GetSource()); } - return members; -}*/ - -std::vector PlayerbotAI::GetPlayersInGroup() -{ - std::vector members; - - Group* group = bot->GetGroup(); - if (!group) - return members; - - for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member) - continue; - - // Celaning, we don't call 2 times GET_PLAYERBOT_AI and never reference it if nil - if (auto* ai = GET_PLAYERBOT_AI(member)) - { - // If it's a bot (not real player) => we ignor it - if (!ai->IsRealPlayer()) - continue; - } - - members.push_back(member); - } - return members; } @@ -4244,6 +4214,19 @@ bool PlayerbotAI::AllowActive(ActivityType activityType) } } + // only keep updating till initializing time has completed, + // which prevents unneeded expensive GameTime calls. + if (_isBotInitializing) + { + _isBotInitializing = GameTime::GetUptime().count() < sPlayerbotAIConfig->maxRandomBots * 0.11; + + // no activity allowed during bot initialization + if (_isBotInitializing) + { + return false; + } + } + // General exceptions if (activityType == PACKET_ACTIVITY) { @@ -6327,27 +6310,11 @@ void PlayerbotAI::PetFollow() if (!pet) return; pet->AttackStop(); - /* pet->InterruptNonMeleeSpells(false); + pet->InterruptNonMeleeSpells(false); pet->ClearInPetCombat(); pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, pet->GetFollowAngle()); - if (pet->ToPet()) - pet->ToPet()->ClearCastWhenWillAvailable();*/ - // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] - pet->InterruptNonMeleeSpells(false); - pet->ClearInPetCombat(); - - if (CanStartMoveSpline(pet)) - { - pet->GetMotionMaster()->MoveFollow(bot, PET_FOLLOW_DIST, pet->GetFollowAngle()); - } - else - { - pet->StopMovingOnCurrentPos(); // on n’envoie pas d’ordre invalide - } - if (pet->ToPet()) pet->ToPet()->ClearCastWhenWillAvailable(); - //End Fix CharmInfo* charmInfo = pet->GetCharmInfo(); if (!charmInfo) return; diff --git a/src/PlayerbotAI.h b/src/PlayerbotAI.h index c7bd5e62..3fa7b5b6 100644 --- a/src/PlayerbotAI.h +++ b/src/PlayerbotAI.h @@ -611,6 +611,7 @@ private: Item* FindItemInInventory(std::function checkItem) const; void HandleCommands(); void HandleCommand(uint32 type, const std::string& text, Player& fromPlayer, const uint32 lang = LANG_UNIVERSAL); + bool _isBotInitializing = false; protected: Player* bot; diff --git a/src/PlayerbotMgr.cpp b/src/PlayerbotMgr.cpp index 02410335..0b6d3967 100644 --- a/src/PlayerbotMgr.cpp +++ b/src/PlayerbotMgr.cpp @@ -36,32 +36,10 @@ #include "BroadcastHelper.h" #include "PlayerbotDbStore.h" #include "WorldSessionMgr.h" -#include "DatabaseEnv.h" -#include -#include "Log.h" +#include "DatabaseEnv.h" // Added for gender choice +#include // Added for gender choice +#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) #include // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION) -#include "TravelMgr.h" -#include -#include - -static std::mutex g_botMapsMx; // protect playerBots and botLoading - -namespace { - // [Crash fix] Centralize clearing of pointer values in the AI context - static void ClearAIContextPointerValues(PlayerbotAI* ai) - { - if (!ai) return; - if (AiObjectContext* ctx = ai->GetAiObjectContext()) - { - // Known today - if (auto* tt = ctx->GetValue("travel target")) - tt->Set(nullptr); - - // TODO: add other pointer-type values here if you have any - // e.g.: ctx->GetValue("some key")->Set(nullptr); - } - } -} class BotInitGuard { @@ -109,16 +87,9 @@ public: void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId) { - /*// bot is loading + // bot is loading if (botLoading.find(playerGuid) != botLoading.end()) - return;*/ - - // bot is loading (protégé) - { - std::lock_guard lk(g_botMapsMx); - if (botLoading.find(playerGuid) != botLoading.end()) - return; - } + return; // has bot already been added? Player* bot = ObjectAccessor::FindConnectedPlayer(playerGuid); @@ -153,19 +124,10 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer); if (!mgr) { - LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); + LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue()); return; } - - // read botLoading.size() locked - size_t loadingCount = 0; - { - std::lock_guard lk(g_botMapsMx); - loadingCount = botLoading.size(); - } - - // uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); - uint32 count = mgr->GetPlayerbotsCount() + static_cast(loadingCount); + uint32 count = mgr->GetPlayerbotsCount() + botLoading.size(); if (count >= sPlayerbotAIConfig->maxAddedBots) { allowed = false; @@ -181,22 +143,14 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId } return; } - // std::shared_ptr holder = - // std::make_shared(this, masterAccountId, accountId, playerGuid); - auto holder = std::make_shared(this, masterAccountId, accountId, playerGuid); + std::shared_ptr holder = + std::make_shared(this, masterAccountId, accountId, playerGuid); if (!holder->Initialize()) { return; } - // botLoading.insert(playerGuid); - // Protected insert - { - std::lock_guard lk(g_botMapsMx); - if (botLoading.find(playerGuid) != botLoading.end()) - return; // already loging - botLoading.insert(playerGuid); // we reserve the GUID - } + botLoading.insert(playerGuid); // Always login in with world session to avoid race condition sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder)) @@ -213,11 +167,7 @@ bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId) void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder const& holder) { - // Copy immediatly holder value - const ObjectGuid guid = holder.GetGuid(); - const uint32 masterAccountId = holder.GetMasterAccountId(); uint32 botAccountId = holder.GetAccountId(); - // At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this // allows channels to work as intended) WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, @@ -232,16 +182,11 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId); botSession->LogoutPlayer(true); delete botSession; - // botLoading.erase(holder.GetGuid()); - { - std::lock_guard lk(g_botMapsMx); - botLoading.erase(guid); - } + botLoading.erase(holder.GetGuid()); return; } - // uint32 masterAccount = holder.GetMasterAccountId(); - uint32 masterAccount = masterAccountId; // Avoid read in 'holder' after login + uint32 masterAccount = holder.GetMasterAccountId(); WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr; // Check if masterSession->GetPlayer() is valid @@ -254,14 +199,10 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con sRandomPlayerbotMgr->OnPlayerLogin(bot); OnBotLogin(bot); - // botLoading.erase(holder.GetGuid()); - { - std::lock_guard lk(g_botMapsMx); - botLoading.erase(guid); - } + botLoading.erase(holder.GetGuid()); } -/*void PlayerbotHolder::UpdateSessions() +void PlayerbotHolder::UpdateSessions() { for (PlayerBotMap::const_iterator itr = GetPlayerBotsBegin(); itr != GetPlayerBotsEnd(); ++itr) { @@ -279,58 +220,15 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con HandleBotPackets(bot->GetSession()); } } -}*/ - -void PlayerbotHolder::UpdateSessions() -{ - PlayerBotMap botsCopy; - { - std::lock_guard lk(g_botMapsMx); - botsCopy = playerBots; - } - - for (const auto& kv : botsCopy) - { - Player* const bot = kv.second; - if (bot->IsBeingTeleported()) - { - if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot)) - botAI->HandleTeleportAck(); - } - else if (bot->IsInWorld()) - { - HandleBotPackets(bot->GetSession()); - } - } } -/*void PlayerbotHolder::HandleBotPackets(WorldSession* session) +void PlayerbotHolder::HandleBotPackets(WorldSession* session) { WorldPacket* packet; while (session->GetPacketQueue().next(packet)) { OpcodeClient opcode = static_cast(packet->GetOpcode()); ClientOpcodeHandler const* opHandle = opcodeTable[opcode]; - opHandle->Call(session, *packet); - delete packet; - } -}*/ - -void PlayerbotHolder::HandleBotPackets(WorldSession* session) // [Crash Fix] Secure packet dispatch (avoid calling on a null handler) -{ - WorldPacket* packet; - while (session->GetPacketQueue().next(packet)) - { - const OpcodeClient opcode = static_cast(packet->GetOpcode()); - const ClientOpcodeHandler* opHandle = opcodeTable[opcode]; - - if (!opHandle) - { - // Unknown handler: drop cleanly - delete packet; - continue; - } - opHandle->Call(session, *packet); delete packet; } @@ -351,27 +249,8 @@ void PlayerbotHolder::LogoutAllBots() } */ - /*PlayerBotMap bots = playerBots; + PlayerBotMap bots = playerBots; for (auto& itr : bots) - { - Player* bot = itr.second; - if (!bot) - continue; - - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (!botAI || botAI->IsRealPlayer()) - continue; - - LogoutPlayerBot(bot->GetGUID()); - }*/ - // Snapshot under lock for safe iteration - PlayerBotMap botsCopy; - { - std::lock_guard lk(g_botMapsMx); - botsCopy = playerBots; - } - - for (auto& itr : botsCopy) { Player* bot = itr.second; if (!bot) @@ -385,7 +264,7 @@ void PlayerbotHolder::LogoutAllBots() } } -/*void PlayerbotMgr::CancelLogout() +void PlayerbotMgr::CancelLogout() { Player* master = GetMaster(); if (!master) @@ -406,53 +285,6 @@ void PlayerbotHolder::LogoutAllBots() } } - for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); - it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) - { - Player* const bot = it->second; - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (!botAI || botAI->IsRealPlayer()) - continue; - - if (botAI->GetMaster() != master) - continue; - - if (bot->GetSession()->isLogingOut()) - { - WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); - bot->GetSession()->HandleLogoutCancelOpcode(data); - } - } -}*/ - -void PlayerbotMgr::CancelLogout() -{ - Player* master = GetMaster(); - if (!master) - return; - - // Snapshot of "master" bots under lock - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - botsCopy.push_back(it->second); - } - - for (Player* const bot : botsCopy) - { - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (!botAI || botAI->IsRealPlayer()) - continue; - - if (bot->GetSession()->isLogingOut()) - { - WorldPackets::Character::LogoutCancel data = WorldPacket(CMSG_LOGOUT_CANCEL); - bot->GetSession()->HandleLogoutCancelOpcode(data); - botAI->TellMaster("Logout cancelled!"); - } - } - for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -486,13 +318,10 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) sPlayerbotDbStore->Save(botAI); } - LOG_DEBUG("mod-playerbots", "Bot {} logging out", bot->GetName().c_str()); + LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str()); bot->SaveToDB(false, false); - // WorldSession* botWorldSessionPtr = bot->GetSession(); - WorldSession* botWorldSessionPtr = bot->GetSession(); // Small safeguard on the session (as a precaution) - if (!botWorldSessionPtr) - return; + WorldSession* botWorldSessionPtr = bot->GetSession(); WorldSession* masterWorldSessionPtr = nullptr; if (botWorldSessionPtr->isLogingOut()) @@ -525,13 +354,11 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) logout = true; } - /*TravelTarget* target = nullptr; + TravelTarget* target = nullptr; if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. { target = botAI->GetAiObjectContext()->GetValue("travel target")->Get(); - }*/ - // [Crash fix] Centralized cleanup of pointer values in the context - ClearAIContextPointerValues(botAI); + } // Peiru: Allow bots to always instant logout to see if this resolves logout crashes logout = true; @@ -548,25 +375,19 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid) botWorldSessionPtr->HandleLogoutRequestOpcode(data); if (!bot) { - /*RemoveFromPlayerbotsMap(guid); - delete botWorldSessionPtr; - if (target) - delete target;*/ - // [Crash fix] bot can be destroyed by the logout request: clean up without touching old pointers RemoveFromPlayerbotsMap(guid); delete botWorldSessionPtr; + if (target) + delete target; } return; } else { - /*RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap + RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap delete botWorldSessionPtr; // finally delete the bot's WorldSession if (target) - delete target;*/ - // [Crash fix] no more deleting 'target' here: ownership handled by the AI/Context - RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap - delete botWorldSessionPtr; // finally delete the bot's WorldSession + delete target; } return; } // if instant logout possible, do it @@ -599,11 +420,11 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) sPlayerbotDbStore->Save(botAI); } - LOG_DEBUG("mod-playerbots", "Bot {} logged out", bot->GetName().c_str()); + LOG_DEBUG("playerbots", "Bot {} logged out", bot->GetName().c_str()); bot->SaveToDB(false, false); - /*if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. + if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values. { TravelTarget* target = botAI->GetAiObjectContext()->GetValue("travel target")->Get(); if (target) @@ -612,54 +433,38 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid) RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap - delete botAI;*/ - // [Crash fix] Centralized cleanup of pointer values in the context - ClearAIContextPointerValues(botAI); + delete botAI; } } void PlayerbotHolder::RemoveFromPlayerbotsMap(ObjectGuid guid) -// { -// playerBots.erase(guid); -// } -// Protected erase { - std::lock_guard lk(g_botMapsMx); playerBots.erase(guid); } Player* PlayerbotHolder::GetPlayerBot(ObjectGuid playerGuid) const { - std::lock_guard lk(g_botMapsMx); // We protect PlayerBotMap::const_iterator it = playerBots.find(playerGuid); - return (it == playerBots.end()) ? nullptr : it->second;// (nullptr) + return (it == playerBots.end()) ? 0 : it->second; } Player* PlayerbotHolder::GetPlayerBot(ObjectGuid::LowType lowGuid) const { ObjectGuid playerGuid = ObjectGuid::Create(lowGuid); - std::lock_guard lk(g_botMapsMx); // We protect PlayerBotMap::const_iterator it = playerBots.find(playerGuid); - return (it == playerBots.end()) ? nullptr : it->second; + return (it == playerBots.end()) ? 0 : it->second; } void PlayerbotHolder::OnBotLogin(Player* const bot) { // Prevent duplicate login - /*if (playerBots.find(bot->GetGUID()) != playerBots.end()) + if (playerBots.find(bot->GetGUID()) != playerBots.end()) { return; - }*/ - { - std::lock_guard lk(g_botMapsMx); - if (playerBots.find(bot->GetGUID()) != playerBots.end()) - return; - - playerBots[bot->GetGUID()] = bot; } sPlayerbotsMgr->AddPlayerbotData(bot, true); - // playerBots[bot->GetGUID()] = bot; + playerBots[bot->GetGUID()] = bot; OnBotLoginInternal(bot); @@ -724,9 +529,6 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) { botAI->ResetStrategies(!sRandomPlayerbotMgr->IsRandomBot(bot)); } - - botAI->Reset(true); // Reset transient states (incl. LFG "proposal") to avoid the "one or more players are not eligible" error after reconnect. - sPlayerbotDbStore->Load(botAI); if (master && !master->HasUnitState(UNIT_STATE_IN_FLIGHT)) @@ -735,17 +537,6 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) bot->CleanupAfterTaxiFlight(); } - // [Fix MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full: 0x00000000000019ba Type: Player Low: 6586] Ensure valid speeds before any next movement command - bot->StopMoving(); - bot->UpdateSpeed(MOVE_WALK, true); - bot->UpdateSpeed(MOVE_RUN, true); - bot->UpdateSpeed(MOVE_SWIM, true); - bot->UpdateSpeed(MOVE_FLIGHT, true); // OK even if not flying - - if (bot->GetSpeed(MOVE_RUN) <= 0.01f) // Belt-and-suspenders: if the run speed has stayed ~0, reset to the default rate - bot->SetSpeedRate(MOVE_RUN, 1.0f); - // End Fix - // check activity botAI->AllowActivity(ALL_ACTIVITY, true); @@ -757,21 +548,16 @@ void PlayerbotHolder::OnBotLogin(Player* const bot) if (master && master->GetGroup() && !group) { Group* mgroup = master->GetGroup(); - // if (mgroup->GetMembersCount() >= 5) - if (mgroup->GetMembersCount() + 1 > 5) // only convert in raid if the add of THIS bot make group > 5 + if (mgroup->GetMembersCount() >= 5) { if (!mgroup->isRaidGroup() && !mgroup->isLFGGroup() && !mgroup->isBGGroup() && !mgroup->isBFGroup()) { mgroup->ConvertToRaid(); } - //if (mgroup->isRaidGroup()) - //{ - //mgroup->AddMember(bot); - //} - mgroup->AddMember(bot); - - LOG_DEBUG("mod-playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}", - (int)mgroup->GetMembersCount(), mgroup->isRaidGroup() ? 1 : 0, mgroup->isLFGGroup() ? 1 : 0); + if (mgroup->isRaidGroup()) + { + mgroup->AddMember(bot); + } } else { @@ -950,11 +736,9 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje } } - // if (GET_PLAYERBOT_AI(bot)) - if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Tidy/Crash fix] Acquire AI once and reuse; avoid multiple GET_PLAYERBOT_AI calls. + if (GET_PLAYERBOT_AI(bot)) { - // if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster()) - if (Player* master = ai->GetMaster()) + if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster()) { if (master->GetSession()->GetSecurity() <= SEC_PLAYER && sPlayerbotAIConfig->autoInitOnly && cmd != "init=auto") @@ -1374,13 +1158,8 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg // If the user requested a specific gender, skip any character that doesn't match. if (gender != -1 && GetOfflinePlayerGender(guid) != gender) continue; - /*if (botLoading.find(guid) != botLoading.end()) - continue;*/ - { - std::lock_guard lk(g_botMapsMx); - if (botLoading.find(guid) != botLoading.end()) - continue; - } + if (botLoading.find(guid) != botLoading.end()) + continue; if (ObjectAccessor::FindConnectedPlayer(guid)) continue; uint32 guildId = sCharacterCache->GetCharacterGuildIdByGuid(guid); @@ -1444,25 +1223,12 @@ std::vector PlayerbotHolder::HandlePlayerbotCommand(char const* arg if (charnameStr == "!" && master && master->GetSession()->GetSecurity() > SEC_GAMEMASTER) { - /*for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) + for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) { if (Player* bot = i->second) if (bot->IsInWorld()) bots.insert(bot->GetName()); - }*/ - // Snapshot under lock - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator i = GetPlayerBotsBegin(); i != GetPlayerBotsEnd(); ++i) - botsCopy.push_back(i->second); - } - for (Player* const bot : botsCopy) - { - if (bot && bot->IsInWorld()) - bots.insert(bot->GetName()); - } - + } } std::vector chars = split(charnameStr, ','); @@ -1566,7 +1332,7 @@ uint32 PlayerbotHolder::GetAccountId(ObjectGuid guid) return 0; } -/*std::string const PlayerbotHolder::ListBots(Player* master) +std::string const PlayerbotHolder::ListBots(Player* master) { std::set bots; std::map classNames; @@ -1652,103 +1418,6 @@ uint32 PlayerbotHolder::GetAccountId(ObjectGuid guid) out << online[name] << name << " " << classes[name]; } - return out.str(); -}*/ - -std::string const PlayerbotHolder::ListBots(Player* master) -{ - std::set bots; - std::map classNames; - - classNames[CLASS_DEATH_KNIGHT] = "Death Knight"; - classNames[CLASS_DRUID] = "Druid"; - classNames[CLASS_HUNTER] = "Hunter"; - classNames[CLASS_MAGE] = "Mage"; - classNames[CLASS_PALADIN] = "Paladin"; - classNames[CLASS_PRIEST] = "Priest"; - classNames[CLASS_ROGUE] = "Rogue"; - classNames[CLASS_SHAMAN] = "Shaman"; - classNames[CLASS_WARLOCK] = "Warlock"; - classNames[CLASS_WARRIOR] = "Warrior"; - classNames[CLASS_DEATH_KNIGHT] = "DeathKnight"; - - std::map online; - std::vector names; - std::map classes; - - // Snapshot under lock - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - botsCopy.push_back(it->second); - } - - for (Player* const bot : botsCopy) - { - std::string const name = bot->GetName(); - bots.insert(name); - - names.push_back(name); - online[name] = "+"; - classes[name] = classNames[bot->getClass()]; - } - - if (master) - { - QueryResult results = CharacterDatabase.Query( - "SELECT class, name FROM characters WHERE account = {}", - master->GetSession()->GetAccountId()); - - if (results) - { - do - { - Field* fields = results->Fetch(); - uint8 cls = fields[0].Get(); - std::string const name = fields[1].Get(); - if (bots.find(name) == bots.end() && name != master->GetSession()->GetPlayerName()) - { - names.push_back(name); - online[name] = "-"; - classes[name] = classNames[cls]; - } - } while (results->NextRow()); - } - } - - std::sort(names.begin(), names.end()); - - if (master) - { - if (Group* group = master->GetGroup()) - { - Group::MemberSlotList const& groupSlot = group->GetMemberSlots(); - for (Group::member_citerator itr = groupSlot.begin(); itr != groupSlot.end(); itr++) - { - Player* member = ObjectAccessor::FindPlayer(itr->guid); - if (member && sRandomPlayerbotMgr->IsRandomBot(member)) - { - std::string const name = member->GetName(); - - names.push_back(name); - online[name] = "+"; - classes[name] = classNames[member->getClass()]; - } - } - } - } - - std::ostringstream out; - bool first = true; - out << "Bot roster: "; - for (std::vector::iterator i = names.begin(); i != names.end(); ++i) - { - if (first) first = false; else out << ", "; - std::string const name = *i; - out << online[name] << name << " " << classes[name]; - } - return out.str(); } @@ -1775,7 +1444,7 @@ std::string const PlayerbotHolder::LookupBots(Player* master) return ret_msg; } -/*uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) +uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) { uint32 count = 0; for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) @@ -1787,25 +1456,6 @@ std::string const PlayerbotHolder::LookupBots(Player* master) } } return count; -}*/ - -uint32 PlayerbotHolder::GetPlayerbotsCountByClass(uint32 cls) -{ - uint32 count = 0; - - // Snapshot under lock - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - botsCopy.push_back(it->second); - } - - for (Player* const bot : botsCopy) - if (bot && bot->IsInWorld() && bot->getClass() == cls) - ++count; - - return count; } PlayerbotMgr::PlayerbotMgr(Player* const master) : PlayerbotHolder(), master(master), lastErrorTell(0) {} @@ -1822,42 +1472,6 @@ void PlayerbotMgr::UpdateAIInternal(uint32 elapsed, bool /*minimal*/) CheckTellErrors(elapsed); } -/*void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) -{ - Player* master = GetMaster(); - if (!master) - return; - - if (text.find(sPlayerbotAIConfig->commandSeparator) != std::string::npos) - { - std::vector commands; - split(commands, text, sPlayerbotAIConfig->commandSeparator.c_str()); - for (std::vector::iterator i = commands.begin(); i != commands.end(); ++i) - { - HandleCommand(type, *i); - } - - return; - } - - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - { - Player* const bot = it->second; - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (botAI) - botAI->HandleCommand(type, text, master); - } - - for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); - it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) - { - Player* const bot = it->second; - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (botAI && botAI->GetMaster() == master) - botAI->HandleCommand(type, text, master); - } -}*/ - void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) { Player* master = GetMaster(); @@ -1869,26 +1483,21 @@ void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) std::vector commands; split(commands, text, sPlayerbotAIConfig->commandSeparator.c_str()); for (std::vector::iterator i = commands.begin(); i != commands.end(); ++i) + { HandleCommand(type, *i); + } + return; } - // Snapshot of "master" bots under lock to avoid race conditions - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - botsCopy.push_back(it->second); - } - - for (Player* const bot : botsCopy) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { + Player* const bot = it->second; PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI) botAI->HandleCommand(type, text, master); } - // Random bots : unchanges for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -1899,65 +1508,18 @@ void PlayerbotMgr::HandleCommand(uint32 type, std::string const text) } } -/*void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) -{ - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - { - Player* const bot = it->second; - if (!bot) - continue; - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (botAI) - botAI->HandleMasterIncomingPacket(packet); - } - - for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); - it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) - { - Player* const bot = it->second; - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (botAI && botAI->GetMaster() == GetMaster()) - botAI->HandleMasterIncomingPacket(packet); - } - - switch (packet.GetOpcode()) - { - // if master is logging out, log out all bots - case CMSG_LOGOUT_REQUEST: - { - LogoutAllBots(); - break; - } - // if master cancelled logout, cancel too - case CMSG_LOGOUT_CANCEL: - { - CancelLogout(); - break; - } - } -}*/ - void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) { - // Snapshot of "master" bots under lock - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - botsCopy.push_back(it->second); - } - - for (Player* const bot : botsCopy) + for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { + Player* const bot = it->second; if (!bot) continue; - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI) botAI->HandleMasterIncomingPacket(packet); } - // Boucle random bots (inchangée) for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -1984,8 +1546,7 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) } } - -/*void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) +void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -1995,33 +1556,6 @@ void PlayerbotMgr::HandleMasterIncomingPacket(WorldPacket const& packet) botAI->HandleMasterOutgoingPacket(packet); } - for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); - it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) - { - Player* const bot = it->second; - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (botAI && botAI->GetMaster() == GetMaster()) - botAI->HandleMasterOutgoingPacket(packet); - } -}*/ -void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) -{ - // Snapshot of "master" bots under lock - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - botsCopy.push_back(it->second); - } - - for (Player* const bot : botsCopy) - { - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (botAI) - botAI->HandleMasterOutgoingPacket(packet); - } - - // Random bots loop unchanged for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) { @@ -2032,7 +1566,7 @@ void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) } } -/*void PlayerbotMgr::SaveToDB() +void PlayerbotMgr::SaveToDB() { for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) { @@ -2047,32 +1581,6 @@ void PlayerbotMgr::HandleMasterOutgoingPacket(WorldPacket const& packet) if (GET_PLAYERBOT_AI(bot) && GET_PLAYERBOT_AI(bot)->GetMaster() == GetMaster()) bot->SaveToDB(false, false); } -}*/ - -void PlayerbotMgr::SaveToDB() -{ - // Snapshot of "master" bots under lock - std::vector botsCopy; - { - std::lock_guard lk(g_botMapsMx); - for (PlayerBotMap::const_iterator it = GetPlayerBotsBegin(); it != GetPlayerBotsEnd(); ++it) - botsCopy.push_back(it->second); - } - - for (Player* const bot : botsCopy) - { - if (bot) - bot->SaveToDB(false, false); - } - - for (PlayerBotMap::const_iterator it = sRandomPlayerbotMgr->GetPlayerBotsBegin(); - it != sRandomPlayerbotMgr->GetPlayerBotsEnd(); ++it) - { - Player* const bot = it->second; - PlayerbotAI* ai = GET_PLAYERBOT_AI(bot); - if (ai && ai->GetMaster() == GetMaster()) - bot->SaveToDB(false, false); - } } void PlayerbotMgr::OnBotLoginInternal(Player* const bot) @@ -2085,7 +1593,7 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot) botAI->SetMaster(master); botAI->ResetStrategies(); - LOG_INFO("mod-playerbots", "Bot {} logged in", bot->GetName().c_str()); + LOG_INFO("playerbots", "Bot {} logged in", bot->GetName().c_str()); } void PlayerbotMgr::OnPlayerLogin(Player* player) @@ -2278,7 +1786,7 @@ void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntr { delete it->second; _playerbotsAIMap.erase(it); - LOG_DEBUG("mod-playerbots", "Removed stale AI for GUID {}", + LOG_DEBUG("playerbots", "Removed stale AI for GUID {}", static_cast(guid.GetRawValue())); } diff --git a/src/Playerbots.cpp b/src/Playerbots.cpp index d7fb2ea5..76b32efa 100644 --- a/src/Playerbots.cpp +++ b/src/Playerbots.cpp @@ -30,7 +30,6 @@ #include "cs_playerbots.h" #include "cmath" #include "BattleGroundTactics.h" -#include "ObjectAccessor.h" class PlayerbotsDatabaseScript : public DatabaseScript { @@ -109,7 +108,7 @@ public: "|cffcccccchttps://github.com/liyunfan1223/mod-playerbots|r"); } - /*if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin) + if (sPlayerbotAIConfig->enabled || sPlayerbotAIConfig->randomBotAutologin) { std::string roundedTime = std::to_string(std::ceil((sPlayerbotAIConfig->maxRandomBots * 0.11 / 60) * 10) / 10.0); @@ -118,7 +117,7 @@ public: ChatHandler(player->GetSession()).SendSysMessage( "|cff00ff00Playerbots:|r bot initialization at server startup takes about '" + roundedTime + "' minutes."); - }*/ + } } } @@ -137,7 +136,7 @@ public: bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override { - /*if (type == CHAT_MSG_WHISPER) + if (type == CHAT_MSG_WHISPER) { if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver)) { @@ -145,23 +144,14 @@ public: return false; } - }*/ - - if (type == CHAT_MSG_WHISPER && receiver) // [Crash Fix] Add non-null receiver check to avoid calling on a null pointer in edge cases. - { - if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver)) - { - botAI->HandleCommand(type, msg, player); - return false; - } - } + } return true; } void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override { - /*for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) + for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) { if (Player* member = itr->GetSource()) { @@ -170,18 +160,6 @@ public: botAI->HandleCommand(type, msg, player); } } - }*/ - if (!group) return; // [Crash Fix] 'group' should not be null in this hook, but this safeguard prevents a crash if the caller changes or in case of an unexpected call. - - for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next()) - { - Player* member = itr->GetSource(); - if (!member) continue; - - if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(member)) - { - botAI->HandleCommand(type, msg, player); - } } } @@ -198,9 +176,7 @@ public: { if (bot->GetGuildId() == player->GetGuildId()) { - // GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player); - if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Crash Fix] Possible crash source because we don't check if the returned pointer is not null - ai->HandleCommand(type, msg, player); + GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player); } } } @@ -335,7 +311,7 @@ class PlayerbotsScript : public PlayerbotScript public: PlayerbotsScript() : PlayerbotScript("PlayerbotsScript") {} - /*bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) override + bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) override { bool nonBotFound = false; for (ObjectGuid const& guid : guidsList.guids) @@ -349,137 +325,7 @@ public: } return nonBotFound; - }*/ - - // New LFG Function - bool OnPlayerbotCheckLFGQueue(lfg::Lfg5Guids const& guidsList) - { - const size_t totalSlots = guidsList.guids.size(); - size_t ignoredEmpty = 0, ignoredNonPlayer = 0; - size_t offlinePlayers = 0, botPlayers = 0, realPlayers = 0; - bool groupGuidSeen = false; - - LOG_DEBUG("playerbots", "[LFG] check start: slots={}", totalSlots); - - for (size_t i = 0; i < totalSlots; ++i) - { - ObjectGuid const& guid = guidsList.guids[i]; - - // 1) Placeholders to ignore - if (guid.IsEmpty()) - { - ++ignoredEmpty; - LOG_DEBUG("playerbots", "[LFG] slot {}: -> ignored", i); - continue; - } - - // Group GUID: in the original implementation this counted as "non-bot found" - if (guid.IsGroup()) - { - groupGuidSeen = true; - LOG_DEBUG("playerbots", "[LFG] slot {}: -> counts as having a real player (compat)", i); - continue; - } - - // Other non-Player GUIDs: various placeholders, ignore them - if (!guid.IsPlayer()) - { - ++ignoredNonPlayer; - LOG_DEBUG("playerbots", "[LFG] slot {}: guid={} (non-player/high={}) -> ignored", i, - static_cast(guid.GetRawValue()), (unsigned)guid.GetHigh()); - continue; - } - - // 2) Player present? - Player* player = ObjectAccessor::FindPlayer(guid); - if (!player) - { - ++offlinePlayers; - LOG_DEBUG("playerbots", "[LFG] slot {}: player guid={} is offline/not in world", i, - static_cast(guid.GetRawValue())); - continue; - } - - // 3) Bot or real player? - if (GET_PLAYERBOT_AI(player) != nullptr) - { - ++botPlayers; - LOG_DEBUG("playerbots", "[LFG] slot {}: BOT {} (lvl {}, class {})", i, player->GetName().c_str(), - player->GetLevel(), player->getClass()); - } - else - { - ++realPlayers; - LOG_DEBUG("playerbots", "[LFG] slot {}: REAL {} (lvl {}, class {})", i, player->GetName().c_str(), - player->GetLevel(), player->getClass()); - } - } - - // "Ultra-early phase" detection: only placeholders => DO NOT VETO - const bool onlyPlaceholders = (realPlayers + botPlayers + (groupGuidSeen ? 1 : 0)) == 0 && - (ignoredEmpty + ignoredNonPlayer) == totalSlots; - - // "Soft" LFG preflight if we actually see players AND at least one offline - if (!onlyPlaceholders && offlinePlayers > 0) - { - // Find a plausible leader: prefer a real online player, otherwise any online player - Player* leader = nullptr; - - for (ObjectGuid const& guid : guidsList.guids) - if (guid.IsPlayer()) - if (Player* p = ObjectAccessor::FindPlayer(guid)) - if (GET_PLAYERBOT_AI(p) == nullptr) - { - leader = p; - break; - } - - if (!leader) - for (ObjectGuid const& guid : guidsList.guids) - if (guid.IsPlayer()) - if (Player* p = ObjectAccessor::FindPlayer(guid)) - { - leader = p; - break; - } - - if (leader) - { - Group* g = leader->GetGroup(); - if (g) - { - LOG_DEBUG("playerbots", "[LFG-RESET] group members={}, isRaid={}, isLFGGroup={}", - (int)g->GetMembersCount(), g->isRaidGroup() ? 1 : 0, g->isLFGGroup() ? 1 : 0); - - // "Soft" reset of LFG states on the bots' AI side (proposal/role-check, etc.) - for (GroupReference* ref = g->GetFirstMember(); ref; ref = ref->next()) - { - Player* member = ref->GetSource(); - if (!member) - continue; - - if (PlayerbotAI* ai = GET_PLAYERBOT_AI(member)) - ai->Reset(true); - } - } - } - - LOG_DEBUG("playerbots", "[LFG] preflight soft-reset triggered (offline detected) -> allowQueue=no (retry)"); - return false; // ask the client to retry right after the reset - } - - // "Hybrid" policy: permissive if only placeholders; otherwise original logic - bool allowQueue = onlyPlaceholders ? true : ((offlinePlayers == 0) && (realPlayers >= 1 || groupGuidSeen)); - - LOG_DEBUG("playerbots", - "[LFG] summary: slots={}, real={}, bots={}, offline={}, ignored(empty+nonPlayer)={}, " - "groupGuidSeen={} -> allowQueue={}", - totalSlots, realPlayers, botPlayers, offlinePlayers, (ignoredEmpty + ignoredNonPlayer), - (groupGuidSeen ? "yes" : "no"), (allowQueue ? "yes" : "no")); - - return allowQueue; } - // End LFG void OnPlayerbotCheckKillTask(Player* player, Unit* victim) override { diff --git a/src/Playerbots.h b/src/Playerbots.h index 3ed03f23..5b82771b 100644 --- a/src/Playerbots.h +++ b/src/Playerbots.h @@ -49,46 +49,4 @@ int strcmpi(char const* s1, char const* s2); #define GAI_VALUE(type, name) sSharedValueContext->getGlobalValue(name)->Get() #define GAI_VALUE2(type, name, param) sSharedValueContext->getGlobalValue(name, param)->Get() -// ---- Safe teleport wrappers (module-only) ---- -#include "Map.h" -#include -#include "TravelMgr.h" - -inline bool TeleportToSafe(Player* p, uint32 mapId, float x, float y, float z, float o) -{ - if (!p) return false; - - // If the height is invalid (-200000) or not finite, attempt ONE correction on the same map. - if (z <= -199000.0f || !std::isfinite(z)) - { - if (p->GetMapId() == mapId && p->GetMap()) - { - float hz = p->GetMap()->GetHeight(p->GetPhaseMask(), x, y, p->GetPositionZ(), true); - if (hz > -199000.0f && std::isfinite(hz)) - z = hz; - else - return false; // still invalid -> cancel the TP - } - else - { - return false; // different map: do not "guess" the height here - } - } - return p->TeleportTo(mapId, x, y, z, o); -} - -inline bool TeleportToSafe(Player* p, Position const& pos) -{ - // Position doesn't have mapId: we keep actual bot map - return TeleportToSafe(p, p->GetMapId(), - pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), - pos.GetOrientation()); -} - -inline bool TeleportToSafe(Player* p, WorldPosition pos) -{ - return TeleportToSafe(p, pos.getMapId(), pos.getX(), pos.getY(), pos.getZ(), pos.getO()); -} -// ---- /Safe teleport wrappers ---- - #endif diff --git a/src/RandomPlayerbotMgr.cpp b/src/RandomPlayerbotMgr.cpp index 4f1cd1bb..1b69d7f3 100644 --- a/src/RandomPlayerbotMgr.cpp +++ b/src/RandomPlayerbotMgr.cpp @@ -994,18 +994,9 @@ void RandomPlayerbotMgr::CheckBgQueue() isRated = ginfo.IsRated; } - /*if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) || + if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID()) || (player->InArena() && player->GetBattleground()->isRated())) - isRated = true;*/ - if (bgQueue.IsPlayerInvitedToRatedArena(player->GetGUID())) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 - { isRated = true; - } - else if (Battleground const* bg = player->GetBattleground()) - { - if (player->InArena() && bg->isRated()) - isRated = true; - } if (isRated) BattlegroundData[queueTypeId][bracketId].ratedArenaPlayerCount++; @@ -1020,24 +1011,15 @@ void RandomPlayerbotMgr::CheckBgQueue() else BattlegroundData[queueTypeId][bracketId].bgHordePlayerCount++; - /*// If a player has joined the BG, update the instance count in BattlegroundData (for consistency) + // If a player has joined the BG, update the instance count in BattlegroundData (for consistency) if (player->InBattleground()) { std::vector* instanceIds = nullptr; uint32 instanceId = player->GetBattleground()->GetInstanceID(); - instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances;*/ - // If a player has joined the BG, update the instance count in BattlegroundData (for consistency) - if (Battleground const* bg = player->GetBattleground()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 - { - std::vector* instanceIds = nullptr; - uint32 instanceId = bg->GetInstanceID(); - - instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances; - - if (instanceIds && + instanceIds = &BattlegroundData[queueTypeId][bracketId].bgInstances; + if (instanceIds && std::find(instanceIds->begin(), instanceIds->end(), instanceId) == instanceIds->end()) - instanceIds->push_back(instanceId); BattlegroundData[queueTypeId][bracketId].bgInstanceCount = instanceIds->size(); @@ -1100,20 +1082,10 @@ void RandomPlayerbotMgr::CheckBgQueue() isRated = ginfo.IsRated; } - /*if (bgQueue.IsPlayerInvitedToRatedArena(guid) || (bot->InArena() && bot->GetBattleground()->isRated())) - isRated = true;*/ - if (bgQueue.IsPlayerInvitedToRatedArena(guid)) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 - { + if (bgQueue.IsPlayerInvitedToRatedArena(guid) || (bot->InArena() && bot->GetBattleground()->isRated())) isRated = true; - } - else if (Battleground const* bg = bot->GetBattleground()) - { - if (bot->InArena() && bg->isRated()) - isRated = true; - } - // END [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 - - if (isRated) + + if (isRated) BattlegroundData[queueTypeId][bracketId].ratedArenaBotCount++; else BattlegroundData[queueTypeId][bracketId].skirmishArenaBotCount++; @@ -1126,15 +1098,10 @@ void RandomPlayerbotMgr::CheckBgQueue() BattlegroundData[queueTypeId][bracketId].bgHordeBotCount++; } - /*if (bot->InBattleground()) + if (bot->InBattleground()) { std::vector* instanceIds = nullptr; - uint32 instanceId = bot->GetBattleground()->GetInstanceID();*/ - if (Battleground const* bg = bot->GetBattleground()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 - { - std::vector* instanceIds = nullptr; - uint32 instanceId = bg->GetInstanceID(); - //END [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 + uint32 instanceId = bot->GetBattleground()->GetInstanceID(); bool isArena = false; bool isRated = false; @@ -1142,8 +1109,7 @@ void RandomPlayerbotMgr::CheckBgQueue() if (bot->InArena()) { isArena = true; - // if (bot->GetBattleground()->isRated()) - if (bg->isRated()) // [Crash Fix] Issue Crash in RandomPlayerbotMgr:1018 #1528 + if (bot->GetBattleground()->isRated()) { isRated = true; instanceIds = &BattlegroundData[queueTypeId][bracketId].ratedArenaInstances; @@ -1759,11 +1725,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& } // Prevent blink to be detected by visible real players - /*if (botAI->HasPlayerNearby(150.0f)) - { - break; - }*/ - if (botAI && botAI->HasPlayerNearby(150.0f)) // [Crash fix] 'botAI' can be null earlier in the function. + if (botAI->HasPlayerNearby(150.0f)) { break; } @@ -1772,8 +1734,7 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector& PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI) botAI->Reset(true); - //bot->TeleportTo(loc.GetMapId(), x, y, z, 0); - TeleportToSafe(bot, loc.GetMapId(), x, y, z, 0); // [Fix] Avoid silly teleports + bot->TeleportTo(loc.GetMapId(), x, y, z, 0); bot->SendMovementFlagUpdate(); if (pmo) @@ -2372,10 +2333,8 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot) PlayerbotsDatabase.Execute(stmt); // teleport to a random inn for bot level - /*if (GET_PLAYERBOT_AI(bot)) - GET_PLAYERBOT_AI(bot)->Reset(true);*/ - if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference. - ai->Reset(true); + if (GET_PLAYERBOT_AI(bot)) + GET_PLAYERBOT_AI(bot)->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); @@ -2415,10 +2374,8 @@ void RandomPlayerbotMgr::RandomizeMin(Player* bot) PlayerbotsDatabase.Execute(stmt); // teleport to a random inn for bot level - /*if (GET_PLAYERBOT_AI(bot)) - GET_PLAYERBOT_AI(bot)->Reset(true);*/ - if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference. - ai->Reset(true); + if (GET_PLAYERBOT_AI(bot)) + GET_PLAYERBOT_AI(bot)->Reset(true); if (bot->GetGroup()) bot->RemoveFromGroup(); @@ -2511,7 +2468,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot) bool RandomPlayerbotMgr::IsRandomBot(Player* bot) { - /*if (bot && GET_PLAYERBOT_AI(bot)) + if (bot && GET_PLAYERBOT_AI(bot)) { if (GET_PLAYERBOT_AI(bot)->IsRealPlayer()) return false; @@ -2521,17 +2478,6 @@ bool RandomPlayerbotMgr::IsRandomBot(Player* bot) return IsRandomBot(bot->GetGUID().GetCounter()); } - return false;*/ - - if (bot) // [Tidy] Single AI acquisition + same logic. - { - if (auto* ai = GET_PLAYERBOT_AI(bot)) - { - if (ai->IsRealPlayer()) - return false; - } - return IsRandomBot(bot->GetGUID().GetCounter()); - } return false; } @@ -2549,7 +2495,7 @@ bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot) bool RandomPlayerbotMgr::IsAddclassBot(Player* bot) { - /*if (bot && GET_PLAYERBOT_AI(bot)) + if (bot && GET_PLAYERBOT_AI(bot)) { if (GET_PLAYERBOT_AI(bot)->IsRealPlayer()) return false; @@ -2559,17 +2505,6 @@ bool RandomPlayerbotMgr::IsAddclassBot(Player* bot) return IsAddclassBot(bot->GetGUID().GetCounter()); } - return false;*/ - - if (bot) // [Tidy] Single AI acquisition + same logic. - { - if (auto* ai = GET_PLAYERBOT_AI(bot)) - { - if (ai->IsRealPlayer()) - return false; - } - return IsAddclassBot(bot->GetGUID().GetCounter()); - } return false; } @@ -2909,9 +2844,8 @@ void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Play continue; } } - // GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); // Possible crash source because we don't check if the returned pointer is not null - if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Protect the call on a null AI (World/General chat path). - ai->HandleCommand(type, text, fromPlayer); + + GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); } } @@ -2984,7 +2918,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next()) { Player* member = gref->GetSource(); - /*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI && member == player && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster()))) { if (!bot->InBattleground()) @@ -2995,20 +2929,6 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) } break; - }*/ - if (auto* botAI = GET_PLAYERBOT_AI(bot)) // [Tidy] Avoid GET_PLAYERBOT_AI(...) on a potentially null master. - { - Player* master = botAI->GetMaster(); - if (member == player && (!master || GET_PLAYERBOT_AI(master))) - { - if (!bot->InBattleground()) - { - botAI->SetMaster(player); - botAI->ResetStrategies(); - botAI->TellMaster("Hello"); - } - break; - } } } } @@ -3048,8 +2968,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player) } while (true); } - // player->TeleportTo(botPos); - TeleportToSafe(player, botPos); // [Fix] Avoid silly teleports + player->TeleportTo(botPos); // player->Relocate(botPos.getX(), botPos.getY(), botPos.getZ(), botPos.getO()); } @@ -3148,29 +3067,13 @@ void RandomPlayerbotMgr::PrintStats() lvlPerClass[bot->getClass()] += bot->GetLevel(); lvlPerRace[bot->getRace()] += bot->GetLevel(); - /*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); + PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); if (botAI->AllowActivity()) ++active; if (botAI->GetAiObjectContext()->GetValue("random bot update")->Get()) - ++update;*/ - - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); // [Crash fix] Declare botAI in the loop scope and exit early if null, - if (!botAI) - continue; // real player / no AI → ignore this bot for stats - - if (botAI->AllowActivity()) - ++active; - - // Secure access to the context and the value - if (AiObjectContext* ctx = botAI->GetAiObjectContext()) - { - if (auto* v = ctx->GetValue("random bot update")) - if (v->Get()) - ++update; - } - // End CrashFix - + ++update; + uint32 botId = bot->GetGUID().GetCounter(); if (!GetEventValue(botId, "randomize")) ++randomize; diff --git a/src/TravelMgr.cpp b/src/TravelMgr.cpp index 6b46fb5a..69d510b9 100644 --- a/src/TravelMgr.cpp +++ b/src/TravelMgr.cpp @@ -1227,7 +1227,7 @@ std::string const QuestObjectiveTravelDestination::getTitle() return out.str(); } -/*bool RpgTravelDestination::isActive(Player* bot) // Old Code +bool RpgTravelDestination::isActive(Player* bot) { PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); AiObjectContext* context = botAI->GetAiObjectContext(); @@ -1264,62 +1264,6 @@ std::string const QuestObjectiveTravelDestination::getTitle() ReputationRank reaction = bot->GetReputationRank(factionEntry->faction); return reaction > REP_NEUTRAL; -}*/ - -bool RpgTravelDestination::isActive(Player* bot) -{ - // [Crash fix] Never dereference the AI if the player is real (null AI). - if (!bot) - return false; - - PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); - if (!botAI) - return false; // real player (no AI) => inactive destination - - AiObjectContext* context = botAI->GetAiObjectContext(); - if (!context) - return false; - - CreatureTemplate const* cInfo = GetCreatureTemplate(); - if (!cInfo) - return false; - - bool isUsefull = false; - - if (cInfo->npcflag & UNIT_NPC_FLAG_VENDOR) - if (AI_VALUE2_LAZY(bool, "group or", "should sell,can sell,following party,near leader")) - isUsefull = true; - - if (cInfo->npcflag & UNIT_NPC_FLAG_REPAIR) - if (AI_VALUE2_LAZY(bool, "group or", "should repair,can repair,following party,near leader")) - isUsefull = true; - - if (!isUsefull) - return false; - - // [Crash fix] Read the ignore list via 'context' and check that the Value exists - GuidSet const* ignoreList = nullptr; - if (auto* value = context->GetValue("ignore rpg target")) - ignoreList = &value->Get(); - - if (ignoreList) - { - for (ObjectGuid const& guid : *ignoreList) - { - if (guid.GetEntry() == getEntry()) - return false; - } - } - - // Secure access to the faction template - if (FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction)) - { - ReputationRank reaction = bot->GetReputationRank(factionEntry->faction); - return reaction > REP_NEUTRAL; - } - - // As a precaution, if the faction is not found, consider inactive - return false; } CreatureTemplate const* RpgTravelDestination::GetCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); } diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 32d495a7..0fe95176 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -164,16 +164,15 @@ void PlayerbotFactory::Init() { continue; } - ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId); - if (proto) { - if (proto->ItemLevel < 60) - continue; - if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE) - continue; + if (proto->ItemLevel < 60) + continue; + + if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE) + { + continue; } - if (sRandomItemMgr->IsTestItem(gemId)) continue; @@ -181,11 +180,9 @@ void PlayerbotFactory::Init() { continue; } - // LOG_INFO("playerbots", "Add {} to enchantment gems", gemId); enchantGemIdCache.push_back(gemId); } - LOG_INFO("playerbots", "Loading {} enchantment gems", enchantGemIdCache.size()); } @@ -1020,16 +1017,14 @@ void PlayerbotFactory::ClearSkills() } bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0); bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0); - // unlearn default race/class skills - if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass())) { - for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) - { - uint32 skillId = itr->SkillId; - if (!bot->HasSkill(skillId)) - continue; - bot->SetSkill(skillId, 0, 0, 0); - } + PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass()); + for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) + { + uint32 skillId = itr->SkillId; + if (!bot->HasSkill(skillId)) + continue; + bot->SetSkill(skillId, 0, 0, 0); } } diff --git a/src/strategy/actions/AreaTriggerAction.cpp b/src/strategy/actions/AreaTriggerAction.cpp index 741058b4..5937a0f7 100644 --- a/src/strategy/actions/AreaTriggerAction.cpp +++ b/src/strategy/actions/AreaTriggerAction.cpp @@ -9,7 +9,6 @@ #include "LastMovementValue.h" #include "Playerbots.h" #include "Transport.h" -#include "BotMovementUtils.h" bool ReachAreaTriggerAction::Execute(Event event) { @@ -41,18 +40,7 @@ bool ReachAreaTriggerAction::Execute(Event event) return true; } - // bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z); - // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] - if (CanStartMoveSpline(bot)) - { - bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z); - } - else - { - bot->StopMovingOnCurrentPos(); - botAI->SetNextCheckDelay(sPlayerbotAIConfig->reactDelay); - return false; - } + bot->GetMotionMaster()->MovePoint(at->map, at->x, at->y, at->z); float distance = bot->GetDistance(at->x, at->y, at->z); float delay = 1000.0f * distance / bot->GetSpeed(MOVE_RUN) + sPlayerbotAIConfig->reactDelay; diff --git a/src/strategy/actions/BattleGroundJoinAction.cpp b/src/strategy/actions/BattleGroundJoinAction.cpp index 1e9d9598..09523fd2 100644 --- a/src/strategy/actions/BattleGroundJoinAction.cpp +++ b/src/strategy/actions/BattleGroundJoinAction.cpp @@ -176,8 +176,7 @@ bool BGJoinAction::gatherArenaTeam(ArenaType type) continue; memberBotAI->Reset(); - // member->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0); - TeleportToSafe(member, bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0); + member->TeleportTo(bot->GetMapId(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), 0); LOG_INFO("playerbots", "Bot {} <{}>: Member of <{}>", member->GetGUID().ToString().c_str(), member->GetName().c_str(), arenateam->GetName().c_str()); diff --git a/src/strategy/actions/BattleGroundTactics.cpp b/src/strategy/actions/BattleGroundTactics.cpp index 0dfcd823..fab932f7 100644 --- a/src/strategy/actions/BattleGroundTactics.cpp +++ b/src/strategy/actions/BattleGroundTactics.cpp @@ -4289,11 +4289,9 @@ bool ArenaTactics::moveToCenter(Battleground* bg) { // they like to hang around at the tip of the pipes doing nothing, so we just teleport them down if (bot->GetDistance(1333.07f, 817.18f, 13.35f) < 4) - // bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); - TeleportToSafe(bot, bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); // [Fix] Avaid silly teleport + bot->TeleportTo(bg->GetMapId(), 1330.96f, 816.75f, 3.2f, bot->GetOrientation()); if (bot->GetDistance(1250.13f, 764.79f, 13.34f) < 4) - // bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); - TeleportToSafe(bot, bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); // [Fix] Avaid silly teleport + bot->TeleportTo(bg->GetMapId(), 1252.19f, 765.41f, 3.2f, bot->GetOrientation()); } break; case BATTLEGROUND_RV: diff --git a/src/strategy/actions/MoveToTravelTargetAction.cpp b/src/strategy/actions/MoveToTravelTargetAction.cpp index 9ae28919..6782ea3f 100644 --- a/src/strategy/actions/MoveToTravelTargetAction.cpp +++ b/src/strategy/actions/MoveToTravelTargetAction.cpp @@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event) WorldLocation location = *target->getPosition(); Group* group = bot->GetGroup(); - if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat()) + if (group && !urand(0, 1) && bot == botAI->GetGroupMaster()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) { diff --git a/src/strategy/actions/MovementActions.cpp b/src/strategy/actions/MovementActions.cpp index c4e45505..d3e596e3 100644 --- a/src/strategy/actions/MovementActions.cpp +++ b/src/strategy/actions/MovementActions.cpp @@ -42,7 +42,6 @@ #include "Vehicle.h" #include "WaypointMovementGenerator.h" #include "Corpse.h" -#include "BotMovementUtils.h" MovementAction::MovementAction(PlayerbotAI* botAI, std::string const name) : Action(botAI, name) { @@ -82,10 +81,6 @@ bool MovementAction::JumpTo(uint32 mapId, float x, float y, float z, MovementPri float botZ = bot->GetPositionZ(); float speed = bot->GetSpeed(MOVE_RUN); MotionMaster& mm = *bot->GetMotionMaster(); - // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] - if (!CanStartMoveSpline(bot)) - return false; - // End Fix mm.Clear(); mm.MoveJump(x, y, z, speed, speed, 1); AI_VALUE(LastMovement&, "last movement").Set(mapId, x, y, z, bot->GetOrientation(), 1000, priority); @@ -212,10 +207,6 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, if (distance > 0.01f) { MotionMaster& mm = *vehicleBase->GetMotionMaster(); // need to move vehicle, not bot - // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] - if (!CanStartMoveSpline(bot)) - return false; - // End Fix mm.Clear(); if (!backwards) { @@ -251,10 +242,6 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, // botAI->InterruptSpell(); // } MotionMaster& mm = *bot->GetMotionMaster(); - //[Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] - if (!CanStartMoveSpline(bot)) - return false; - // End Fix mm.Clear(); if (!backwards) { @@ -297,10 +284,6 @@ bool MovementAction::MoveTo(uint32 mapId, float x, float y, float z, bool idle, // } MotionMaster& mm = *bot->GetMotionMaster(); G3D::Vector3 endP = path.back(); - // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] - if (!CanStartMoveSpline(bot)) - return false; - // End Fix mm.Clear(); if (!backwards) { diff --git a/src/strategy/actions/ReleaseSpiritAction.cpp b/src/strategy/actions/ReleaseSpiritAction.cpp index 12228603..cff34f8d 100644 --- a/src/strategy/actions/ReleaseSpiritAction.cpp +++ b/src/strategy/actions/ReleaseSpiritAction.cpp @@ -147,8 +147,7 @@ bool AutoReleaseSpiritAction::HandleBattlegroundSpiritHealer() // and in IOC it's not within clicking range when they res in own base // Teleport to nearest friendly Spirit Healer when not currently in range of one. - // bot->TeleportTo(bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f); - TeleportToSafe(bot, bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f); // [Fix] Avoid silly teleport + bot->TeleportTo(bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f); RESET_AI_VALUE(bool, "combat::self target"); RESET_AI_VALUE(WorldPosition, "current position"); } @@ -245,8 +244,7 @@ int64 RepopAction::CalculateDeadTime() const void RepopAction::PerformGraveyardTeleport(const GraveyardStruct* graveyard) const { - // bot->TeleportTo(graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f); - TeleportToSafe(bot, graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f); // [Fix] Avoid Silly teleport + bot->TeleportTo(graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f); RESET_AI_VALUE(bool, "combat::self target"); RESET_AI_VALUE(WorldPosition, "current position"); } diff --git a/src/strategy/actions/ReviveFromCorpseAction.cpp b/src/strategy/actions/ReviveFromCorpseAction.cpp index f55904cb..960f438f 100644 --- a/src/strategy/actions/ReviveFromCorpseAction.cpp +++ b/src/strategy/actions/ReviveFromCorpseAction.cpp @@ -169,8 +169,7 @@ bool FindCorpseAction::Execute(Event event) if (deadTime > delay) { bot->GetMotionMaster()->Clear(); - // bot->TeleportTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); - TeleportToSafe(bot, moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); // [fix] Avoid Silly Teleport + bot->TeleportTo(moveToPos.getMapId(), moveToPos.getX(), moveToPos.getY(), moveToPos.getZ(), 0); } moved = true; @@ -351,8 +350,7 @@ bool SpiritHealerAction::Execute(Event event) // if (!botAI->HasActivePlayerMaster()) // { context->GetValue("death count")->Set(dCount + 1); - // return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); - return TeleportToSafe(bot, ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); // [Fix] Avoid Silly teleport + return bot->TeleportTo(ClosestGrave->Map, ClosestGrave->x, ClosestGrave->y, ClosestGrave->z, 0.f); // } // LOG_INFO("playerbots", "Bot {} {}:{} <{}> can't find a spirit healer", bot->GetGUID().ToString().c_str(), diff --git a/src/strategy/raids/icecrown/RaidIccActions.cpp b/src/strategy/raids/icecrown/RaidIccActions.cpp index 0d135b59..eaad5d2f 100644 --- a/src/strategy/raids/icecrown/RaidIccActions.cpp +++ b/src/strategy/raids/icecrown/RaidIccActions.cpp @@ -957,8 +957,7 @@ bool IccGunshipTeleportHordeAction::Execute(Event event) bool IccGunshipTeleportHordeAction::TeleportTo(const Position& position) { - // return bot->TeleportTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), - return TeleportToSafe(bot, bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(),// [Fix]Avoid silly teleport + return bot->TeleportTo(bot->GetMapId(), position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), bot->GetOrientation()); } diff --git a/src/strategy/raids/naxxramas/RaidNaxxActions.cpp b/src/strategy/raids/naxxramas/RaidNaxxActions.cpp index 059cea79..cf12bb8e 100644 --- a/src/strategy/raids/naxxramas/RaidNaxxActions.cpp +++ b/src/strategy/raids/naxxramas/RaidNaxxActions.cpp @@ -9,7 +9,6 @@ #include "RaidNaxxStrategy.h" #include "ScriptedCreature.h" #include "SharedDefines.h" -#include "BotMovementUtils.h" bool GrobbulusGoBehindAction::Execute(Event event) { @@ -259,26 +258,11 @@ bool RazuviousUseObedienceCrystalAction::Execute(Event event) return false; } if (charm->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == NULL_MOTION_TYPE) - /*{ + { charm->GetMotionMaster()->Clear(); charm->GetMotionMaster()->MoveChase(target); - charm->GetAI()->AttackStart(target); - }*/ - // [Fix: MoveSplineInitArgs::Validate: expression 'velocity > 0.01f' failed for GUID Full:] - { - if (CanStartMoveSpline(charm)) - { - charm->GetMotionMaster()->Clear(); - charm->GetMotionMaster()->MoveChase(target); - } - else - { - charm->StopMoving(); - } - charm->GetAI()->AttackStart(target); } - // End Fix Aura* forceObedience = botAI->GetAura("force obedience", charm); uint32 duration_time; if (!forceObedience) diff --git a/src/strategy/raids/ulduar/RaidUlduarActions.cpp b/src/strategy/raids/ulduar/RaidUlduarActions.cpp index 8517a0bf..3d180820 100644 --- a/src/strategy/raids/ulduar/RaidUlduarActions.cpp +++ b/src/strategy/raids/ulduar/RaidUlduarActions.cpp @@ -1357,14 +1357,10 @@ bool KologarnMarkDpsTargetAction::Execute(Event event) bool KologarnFallFromFloorAction::Execute(Event event) { - /*return bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionX(), + return bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionX(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionY(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionZ(), - ULDUAR_KOLOGARN_RESTORE_POSITION.GetOrientation());*/ - return TeleportToSafe(bot, bot->GetMapId(), ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionX(), // [Fix] Avoid silly teleport - ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionY(), - ULDUAR_KOLOGARN_RESTORE_POSITION.GetPositionZ(), - ULDUAR_KOLOGARN_RESTORE_POSITION.GetOrientation()); + ULDUAR_KOLOGARN_RESTORE_POSITION.GetOrientation()); } bool KologarnFallFromFloorAction::isUseful() @@ -1411,18 +1407,14 @@ bool KologarnEyebeamAction::Execute(Event event) KologarnEyebeamTrigger kologarnEyebeamTrigger(botAI); if (runToLeftSide) { - // teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionX(), - teleportedToPoint = TeleportToSafe(bot, bot->GetMapId(), - ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionX(), + teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionX(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionY(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetPositionZ(), ULDUAR_KOLOGARN_EYEBEAM_LEFT_POSITION.GetOrientation()); } else { - // teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionX(), - teleportedToPoint = TeleportToSafe(bot, bot->GetMapId(), - ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionX(), + teleportedToPoint = bot->TeleportTo(bot->GetMapId(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionX(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionY(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetPositionZ(), ULDUAR_KOLOGARN_EYEBEAM_RIGHT_POSITION.GetOrientation()); diff --git a/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp b/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp index b41c670f..05d6328e 100644 --- a/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp +++ b/src/strategy/raids/vaultofarchavon/RaidVoAActions.cpp @@ -175,13 +175,9 @@ bool EmalonOverchargeAction::isUseful() bool EmalonFallFromFloorAction::Execute(Event event) { - /*return bot->TeleportTo(bot->GetMapId(), VOA_EMALON_RESTORE_POSITION.GetPositionX(), + return bot->TeleportTo(bot->GetMapId(), VOA_EMALON_RESTORE_POSITION.GetPositionX(), VOA_EMALON_RESTORE_POSITION.GetPositionY(), VOA_EMALON_RESTORE_POSITION.GetPositionZ(), - VOA_EMALON_RESTORE_POSITION.GetOrientation());*/ - return TeleportToSafe(bot, bot->GetMapId(), VOA_EMALON_RESTORE_POSITION.GetPositionX(), //[Fix] Avoid Silly Teleport - VOA_EMALON_RESTORE_POSITION.GetPositionY(), - VOA_EMALON_RESTORE_POSITION.GetPositionZ(), - VOA_EMALON_RESTORE_POSITION.GetOrientation()); + VOA_EMALON_RESTORE_POSITION.GetOrientation()); } bool EmalonFallFromFloorAction::isUseful() diff --git a/src/strategy/rpg/NewRpgBaseAction.cpp b/src/strategy/rpg/NewRpgBaseAction.cpp index 9b0ab2a1..5e71c5c5 100644 --- a/src/strategy/rpg/NewRpgBaseAction.cpp +++ b/src/strategy/rpg/NewRpgBaseAction.cpp @@ -67,8 +67,7 @@ bool NewRpgBaseAction::MoveFarTo(WorldPosition dest) bot->GetName(), bot->GetPositionX(), bot->GetPositionY(), bot->GetPositionZ(), bot->GetMapId(), dest.GetPositionX(), dest.GetPositionY(), dest.GetPositionZ(), dest.getMapId(), bot->GetZoneId(), zone_name); - // return bot->TeleportTo(dest); - return TeleportToSafe(bot, dest); //[Fix] Avoid Silly teleport + return bot->TeleportTo(dest); } float dis = bot->GetExactDist(dest); diff --git a/src/strategy/values/TankTargetValue.cpp b/src/strategy/values/TankTargetValue.cpp index 048ef8a5..bcef1a3a 100644 --- a/src/strategy/values/TankTargetValue.cpp +++ b/src/strategy/values/TankTargetValue.cpp @@ -14,7 +14,7 @@ class FindTargetForTankStrategy : public FindNonCcTargetStrategy public: FindTargetForTankStrategy(PlayerbotAI* botAI) : FindNonCcTargetStrategy(botAI), minThreat(0) {} - /*void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override + void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override { if (!creature || !creature->IsAlive()) { @@ -37,43 +37,6 @@ public: return; } } - if (minThreat >= threat) - { - minThreat = threat; - result = creature; - } - }*/ - - void CheckAttacker(Unit* creature, ThreatMgr* threatMgr) override - { - // [Crash fix] Filter out anything that is not ready/valid - if (!creature || !creature->IsAlive() || !creature->IsInWorld() || creature->IsDuringRemoveFromWorld()) - return; - - if (!threatMgr) - return; - - Player* bot = botAI->GetBot(); - if (!bot) - return; - - float threat = threatMgr->GetThreat(bot); - - if (!result || !result->IsAlive() || !result->IsInWorld() || result->IsDuringRemoveFromWorld()) - { - // [Crash fix] If the previous target has become invalid, restart cleanly - minThreat = threat; - result = creature; - } - - // Neglect si la victime actuelle est le MT (ou s'il n'y a pas de victime) - if (HostileReference* cv = threatMgr->getCurrentVictim()) - { - Unit* victim = cv->getTarget(); - if (victim && victim->ToPlayer() && botAI->IsMainTank(victim->ToPlayer())) - return; - } - if (minThreat >= threat) { minThreat = threat; @@ -90,7 +53,7 @@ class FindTankTargetSmartStrategy : public FindTargetStrategy public: FindTankTargetSmartStrategy(PlayerbotAI* botAI) : FindTargetStrategy(botAI) {} - /*void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override + void CheckAttacker(Unit* attacker, ThreatMgr* threatMgr) override { if (Group* group = botAI->GetBot()->GetGroup()) { @@ -106,32 +69,8 @@ public: { result = attacker; } - }*/ - void CheckAttacker(Unit* attacker, ThreatMgr* /*threatMgr*/) override - { - // [Crash fix] Protect against null/out-of-world/being-removed units - if (!attacker || !attacker->IsAlive() || !attacker->IsInWorld() || attacker->IsDuringRemoveFromWorld()) - return; - - if (Player* me = botAI->GetBot()) - { - if (Group* group = me->GetGroup()) - { - ObjectGuid guid = group->GetTargetIcon(4); - if (guid && attacker->GetGUID() == guid) - return; - } - } - - // [Crash fix] If 'result' has become invalid, forget it - if (result && (!result->IsAlive() || !result->IsInWorld() || result->IsDuringRemoveFromWorld())) - result = nullptr; - - if (!result || IsBetter(attacker, result)) - result = attacker; } - - /*bool IsBetter(Unit* new_unit, Unit* old_unit) + bool IsBetter(Unit* new_unit, Unit* old_unit) { Player* bot = botAI->GetBot(); // if group has multiple tanks, main tank just focus on the current target @@ -158,47 +97,8 @@ public: return new_dis < old_dis; } return new_threat < old_threat; - }*/ - bool IsBetter(Unit* new_unit, Unit* old_unit) - { - // [Crash fix] If either one is invalid, decide straight away - if (!new_unit || !new_unit->IsAlive() || !new_unit->IsInWorld() || new_unit->IsDuringRemoveFromWorld()) - return false; - if (!old_unit || !old_unit->IsAlive() || !old_unit->IsInWorld() || old_unit->IsDuringRemoveFromWorld()) - return true; - - Player* bot = botAI->GetBot(); - if (!bot) - return false; - - // if multiple tanks, logically focus on the current target - Unit* currentTarget = botAI->GetAiObjectContext()->GetValue("current target")->Get(); - if (currentTarget && botAI->IsMainTank(bot) && botAI->GetGroupTankNum(bot) > 1) - { - if (old_unit == currentTarget) - return false; - if (new_unit == currentTarget) - return true; - } - - float new_threat = new_unit->GetThreatMgr().GetThreat(bot); - float old_threat = old_unit->GetThreatMgr().GetThreat(bot); - float new_dis = bot->GetDistance(new_unit); - float old_dis = bot->GetDistance(old_unit); - - // hasAggro? -> withinMelee? -> threat - int nl = GetIntervalLevel(new_unit); - int ol = GetIntervalLevel(old_unit); - if (nl != ol) - return nl > ol; - - if (nl == 2) - return new_dis < old_dis; - - return new_threat < old_threat; } - - /*int32_t GetIntervalLevel(Unit* unit) + int32_t GetIntervalLevel(Unit* unit) { if (!botAI->HasAggro(unit)) { @@ -209,28 +109,12 @@ public: return 1; } return 0; - }*/ - int32_t GetIntervalLevel(Unit* unit) - { - // [Crash fix] Basic guards - if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld()) - return 0; - - if (!botAI->HasAggro(unit)) - return 2; - - if (Player* bot = botAI->GetBot()) - { - if (bot->IsWithinMeleeRange(unit)) - return 1; - } - return 0; } }; Unit* TankTargetValue::Calculate() { - // [Note] Using the "smart" strategy below. Guards have been added in CheckAttacker/IsBetter. + // FindTargetForTankStrategy strategy(botAI); FindTankTargetSmartStrategy strategy(botAI); return FindTarget(&strategy); } diff --git a/src/strategy/values/TargetValue.cpp b/src/strategy/values/TargetValue.cpp index 9b613681..ebffd328 100644 --- a/src/strategy/values/TargetValue.cpp +++ b/src/strategy/values/TargetValue.cpp @@ -14,7 +14,7 @@ Unit* FindTargetStrategy::GetResult() { return result; } -/*Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) +Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) { GuidVector attackers = botAI->GetAiObjectContext()->GetValue("attackers")->Get(); for (ObjectGuid const guid : attackers) @@ -27,28 +27,6 @@ Unit* FindTargetStrategy::GetResult() { return result; } strategy->CheckAttacker(unit, &ThreatMgr); } - return strategy->GetResult(); -}*/ - -Unit* TargetValue::FindTarget(FindTargetStrategy* strategy) -{ - // [Crash fix] The very first AI tick can occur before everything is "in world". - // Filter out units that are non-living / being removed / out of world. - AiObjectContext* ctx = botAI->GetAiObjectContext(); - if (!ctx) - return strategy->GetResult(); - - GuidVector attackers = ctx->GetValue("attackers")->Get(); - for (ObjectGuid const& guid : attackers) - { - Unit* unit = botAI->GetUnit(guid); - if (!unit || !unit->IsAlive() || !unit->IsInWorld() || unit->IsDuringRemoveFromWorld()) - continue; - - ThreatMgr& threatMgrRef = unit->GetThreatMgr(); - strategy->CheckAttacker(unit, &threatMgrRef); - } - return strategy->GetResult(); } From 3e0f23536ddb28f75620796473cef6ba6f39a740 Mon Sep 17 00:00:00 2001 From: Crow Date: Sat, 16 Aug 2025 05:04:00 -0500 Subject: [PATCH 16/20] Update README.md (#1567) --- README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 8c857063..d5c1f182 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ English | 中文 + | + Español

@@ -16,23 +18,25 @@ # Playerbots Module -`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot). Features include: +`mod-playerbots` is an [AzerothCore](https://www.azerothcore.org/) module that adds player-like bots to a server. The project is based off [IKE3's Playerbots](https://github.com/ike3/mangosbot) and requires a custom branch of AzerothCore to compile and run: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). -- Bots that utilize real player data, allowing players to interact with their other characters, form parties, level up, and more; -- Random bots that wander through the world and behave like players, simulating the MMO experience; -- Bots capable of running raids and battlegrounds; +Features include: + +- The ability to log in alt characters as bots, allowing players to interact with their other characters, form parties, level up, and more; +- Random bots that wander through the world, complete quests, and otherwise behave like players, simulating the MMO experience; +- Bots capable of running most raids and battlegrounds; - Highly configurable settings to define how bots behave; - Excellent performance, even when running thousands of bots. **This project is still under development**. If you encounter any errors or experience crashes, we kindly request that you [report them as GitHub issues](https://github.com/liyunfan1223/mod-playerbots/issues/new?template=bug_report.md). Your valuable feedback will help us improve this project collaboratively. -**Playerbots Module** has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project. +`mod-playerbots` has a **[Discord server](https://discord.gg/NQm5QShwf9)** where you can discuss the project, ask questions, and get involved in the community! ## Installation ### Classic Installation -`mod-playerbots` requires a custom branch of AzerothCore to work: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run: +As noted above, `mod-playerbots` requires a custom branch of AzerothCore: [liyunfan1223/azerothcore-wotlk/tree/Playerbot](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). To install the module, simply run: ```bash git clone https://github.com/liyunfan1223/azerothcore-wotlk.git --branch=Playerbot @@ -81,21 +85,21 @@ Use `docker compose up -d --build` to build and run the server. For more informa ## Documentation -The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, and recommended configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome. +The [Playerbots Wiki](https://github.com/liyunfan1223/mod-playerbots/wiki) contains an extensive overview of addons, commands, raids with programmed bot strategies, and recommended performance configurations. Please note that documentation may be incomplete or out-of-date in some sections. Contributions are welcome. ## Frequently Asked Questions - **Why aren't my bots casting spells?** Please make sure that the necessary English DBC file (enUS) is present. - **What platforms are supported?** We support Ubuntu, Windows, and macOS. Other Linux distros may work, but will not receive support. -- **Why isn't my source compiling?** Please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue. +- **Why isn't my source compiling?** Please ensure that you are compiling with the required [custom branch of AzerothCore](https://github.com/liyunfan1223/azerothcore-wotlk/tree/Playerbot). Additionally, please [check the build status of our CI](https://github.com/liyunfan1223/mod-playerbots/actions). If the latest build is failing, rever to the last successful commit until we address the issue. ## Addons Typically, bots are controlled via chat commands. For larger bot groups, this can be unwieldy. As an alternative, community members have developed client Add-Ons to allow controlling bots through the in-game UI. We recommend you check out their projects: -- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio) -- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) -- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) +- [Multibot](https://github.com/Macx-Lio/MultiBot) (by Macx-Lio), which includes English, Chinese, French, German, Korean, Russian, and Spanish support [note: active development is temporarily continuing on a fork in Macx-Lio's absence (https://github.com/Wishmaster117/MultiBot)] +- [Unbot Addon (zh)](https://github.com/liyunfan1223/unbot-addon) (Chinese version by Liyunfan) [note: no longer under active development] +- [Unbot Addon (en)](https://github.com/noisiver/unbot-addon/tree/english) (English version translated by @Revision) [note: no longer under active development] ## Acknowledgements From 6cb9f56c4e209b7ad9a1e6b06bae9989b29ae42f Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Sat, 16 Aug 2025 15:29:09 +0200 Subject: [PATCH 17/20] nullptr fix (#1557) * nullptr fix * Update PlayerbotFactory.cpp --- src/factory/PlayerbotFactory.cpp | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 0fe95176..94b54964 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -164,22 +164,33 @@ void PlayerbotFactory::Init() { continue; } + ItemTemplate const* proto = sObjectMgr->GetItemTemplate(gemId); - - if (proto->ItemLevel < 60) + if (!proto) + { continue; - + } + + if (proto->ItemLevel < 60) + { + continue; + } + if (proto->Flags & ITEM_FLAG_UNIQUE_EQUIPPABLE) { continue; } + if (sRandomItemMgr->IsTestItem(gemId)) - continue; - - if (!proto || !sGemPropertiesStore.LookupEntry(proto->GemProperties)) + { + continue; + } + + if (!sGemPropertiesStore.LookupEntry(proto->GemProperties)) { continue; } + // LOG_INFO("playerbots", "Add {} to enchantment gems", gemId); enchantGemIdCache.push_back(gemId); } From 4f5f7d286ecc9580f7ad08f24f17ef3f0b18ef3b Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Sat, 16 Aug 2025 15:29:44 +0200 Subject: [PATCH 18/20] nullptr fix (#1555) --- src/factory/PlayerbotFactory.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/factory/PlayerbotFactory.cpp b/src/factory/PlayerbotFactory.cpp index 94b54964..e239d449 100644 --- a/src/factory/PlayerbotFactory.cpp +++ b/src/factory/PlayerbotFactory.cpp @@ -1028,15 +1028,18 @@ void PlayerbotFactory::ClearSkills() } bot->SetUInt32Value(PLAYER_SKILL_INDEX(0), 0); bot->SetUInt32Value(PLAYER_SKILL_INDEX(1), 0); + // unlearn default race/class skills - PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass()); - for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) - { - uint32 skillId = itr->SkillId; - if (!bot->HasSkill(skillId)) - continue; - bot->SetSkill(skillId, 0, 0, 0); - } + if (PlayerInfo const* info = sObjectMgr->GetPlayerInfo(bot->getRace(), bot->getClass())) + { + for (PlayerCreateInfoSkills::const_iterator itr = info->skills.begin(); itr != info->skills.end(); ++itr) + { + uint32 skillId = itr->SkillId; + if (!bot->HasSkill(skillId)) + continue; + bot->SetSkill(skillId, 0, 0, 0); + } + } } void PlayerbotFactory::ClearEverything() From fa7b863035efd4a6f68450bdff907c6acab9812c Mon Sep 17 00:00:00 2001 From: Vigerus Date: Sun, 17 Aug 2025 15:42:26 +0200 Subject: [PATCH 19/20] NamedObjectContext improvement, remove unneccessary pass by copy, allow lambda ObjectCreators (#1561) Co-authored-by: Viger --- src/strategy/NamedObjectContext.cpp | 8 ++-- src/strategy/NamedObjectContext.h | 57 +++++++++++++---------------- 2 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/strategy/NamedObjectContext.cpp b/src/strategy/NamedObjectContext.cpp index d1ea0e1c..ae3a7079 100644 --- a/src/strategy/NamedObjectContext.cpp +++ b/src/strategy/NamedObjectContext.cpp @@ -14,10 +14,10 @@ void Qualified::Qualify(int qual) qualifier = out.str(); } -std::string const Qualified::MultiQualify(std::vector qualifiers, const std::string& separator, const std::string_view brackets) +std::string const Qualified::MultiQualify(const std::vector& qualifiers, const std::string& separator, const std::string_view brackets) { std::stringstream out; - for (uint8 i = 0; i < qualifiers.size(); i++) + for (uint8 i = 0; i < qualifiers.size(); ++i) { const std::string& qualifier = qualifiers[i]; if (i == qualifiers.size() - 1) @@ -40,13 +40,13 @@ std::string const Qualified::MultiQualify(std::vector qualifiers, c } } -std::vector Qualified::getMultiQualifiers(std::string const qualifier1) +std::vector Qualified::getMultiQualifiers(const std::string& qualifier1) { std::istringstream iss(qualifier1); return {std::istream_iterator{iss}, std::istream_iterator{}}; } -int32 Qualified::getMultiQualifier(std::string const qualifier1, uint32 pos) +int32 Qualified::getMultiQualifier(const std::string& qualifier1, uint32 pos) { return std::stoi(getMultiQualifiers(qualifier1)[pos]); } diff --git a/src/strategy/NamedObjectContext.h b/src/strategy/NamedObjectContext.h index c7ecffc6..69b38ce1 100644 --- a/src/strategy/NamedObjectContext.h +++ b/src/strategy/NamedObjectContext.h @@ -11,6 +11,7 @@ #include #include #include +#include #include "Common.h" @@ -29,10 +30,10 @@ public: std::string const getQualifier() { return qualifier; } - static std::string const MultiQualify(std::vector qualifiers, const std::string& separator, + static std::string const MultiQualify(const std::vector& qualifiers, const std::string& separator, const std::string_view brackets = "{}"); - static std::vector getMultiQualifiers(std::string const qualifier1); - static int32 getMultiQualifier(std::string const qualifier1, uint32 pos); + static std::vector getMultiQualifiers(const std::string& qualifier1); + static int32 getMultiQualifier(const std::string& qualifier1, uint32 pos); protected: std::string qualifier; @@ -42,11 +43,11 @@ template class NamedObjectFactory { public: - typedef T* (*ObjectCreator)(PlayerbotAI* botAI); + using ObjectCreator = std::function; std::unordered_map creators; public: - T* create(std::string name, PlayerbotAI* botAI) + virtual T* create(std::string name, PlayerbotAI* botAI) { size_t found = name.find("::"); std::string qualifier; @@ -59,11 +60,9 @@ public: if (creators.find(name) == creators.end()) return nullptr; - ObjectCreator creator = creators[name]; - if (!creator) - return nullptr; + ObjectCreator& creator = creators[name]; - T* object = (*creator)(botAI); + T* object = creator(botAI); Qualified* q = dynamic_cast(object); if (q && found != std::string::npos) q->Qualify(qualifier); @@ -74,7 +73,7 @@ public: std::set supports() { std::set keys; - for (typename std::unordered_map::iterator it = creators.begin(); + for (typename std::unordered_map::const_iterator it = creators.begin(); it != creators.end(); it++) keys.insert(it->first); @@ -93,7 +92,7 @@ public: virtual ~NamedObjectContext() { Clear(); } - T* create(std::string const name, PlayerbotAI* botAI) + virtual T* create(std::string name, PlayerbotAI* botAI) override { if (created.find(name) == created.end()) return created[name] = NamedObjectFactory::create(name, botAI); @@ -103,7 +102,7 @@ public: void Clear() { - for (typename std::unordered_map::iterator i = created.begin(); i != created.end(); i++) + for (typename std::unordered_map::const_iterator i = created.begin(); i != created.end(); i++) { if (i->second) delete i->second; @@ -134,13 +133,13 @@ template class SharedNamedObjectContextList { public: - typedef T* (*ObjectCreator)(PlayerbotAI* botAI); + using ObjectCreator = std::function; std::unordered_map creators; std::vector*> contexts; ~SharedNamedObjectContextList() { - for (typename std::vector*>::iterator i = contexts.begin(); i != contexts.end(); i++) + for (typename std::vector*>::const_iterator i = contexts.begin(); i != contexts.end(); i++) delete *i; } @@ -158,7 +157,7 @@ template class NamedObjectContextList { public: - typedef T* (*ObjectCreator)(PlayerbotAI* botAI); + using ObjectCreator = std::function; const std::unordered_map& creators; const std::vector*>& contexts; std::unordered_map created; @@ -170,7 +169,7 @@ public: ~NamedObjectContextList() { - for (typename std::unordered_map::iterator i = created.begin(); i != created.end(); i++) + for (typename std::unordered_map::const_iterator i = created.begin(); i != created.end(); i++) { if (i->second) delete i->second; @@ -192,11 +191,9 @@ public: if (creators.find(name) == creators.end()) return nullptr; - ObjectCreator creator = creators.at(name); - if (!creator) - return nullptr; + const ObjectCreator& creator = creators.at(name); - T* object = (*creator)(botAI); + T* object = creator(botAI); Qualified* q = dynamic_cast(object); if (q && found != std::string::npos) q->Qualify(qualifier); @@ -204,7 +201,7 @@ public: return object; } - T* GetContextObject(std::string const name, PlayerbotAI* botAI) + T* GetContextObject(const std::string& name, PlayerbotAI* botAI) { if (created.find(name) == created.end()) { @@ -214,7 +211,7 @@ public: return created[name]; } - std::set GetSiblings(std::string const name) + std::set GetSiblings(const std::string& name) { for (auto i = contexts.begin(); i != contexts.end(); i++) { @@ -240,7 +237,7 @@ public: { std::set supported = (*i)->supports(); - for (std::set::iterator j = supported.begin(); j != supported.end(); j++) + for (std::set::const_iterator j = supported.begin(); j != supported.end(); ++j) result.insert(*j); } @@ -250,7 +247,7 @@ public: std::set GetCreated() { std::set result; - for (typename std::unordered_map::iterator i = created.begin(); i != created.end(); i++) + for (typename std::unordered_map::const_iterator i = created.begin(); i != created.end(); i++) { result.insert(i->first); } @@ -263,13 +260,13 @@ template class NamedObjectFactoryList { public: - typedef T* (*ObjectCreator)(PlayerbotAI* botAI); + using ObjectCreator = std::function; std::vector*> factories; std::unordered_map creators; virtual ~NamedObjectFactoryList() { - for (typename std::vector*>::iterator i = factories.begin(); i != factories.end(); i++) + for (typename std::vector*>::const_iterator i = factories.begin(); i != factories.end(); i++) delete *i; } @@ -286,11 +283,9 @@ public: if (creators.find(name) == creators.end()) return nullptr; - ObjectCreator creator = creators[name]; - if (!creator) - return nullptr; + const ObjectCreator& creator = creators[name]; - T* object = (*creator)(botAI); + T* object = creator(botAI); Qualified* q = dynamic_cast(object); if (q && found != std::string::npos) q->Qualify(qualifier); @@ -307,7 +302,7 @@ public: } } - T* GetContextObject(std::string const name, PlayerbotAI* botAI) + T* GetContextObject(const std::string& name, PlayerbotAI* botAI) { if (T* object = create(name, botAI)) return object; From b369b1f9aee382e95e8eb13cfb19279303d4d5ca Mon Sep 17 00:00:00 2001 From: bash <31279994+hermensbas@users.noreply.github.com> Date: Mon, 18 Aug 2025 02:38:06 +0200 Subject: [PATCH 20/20] MoveToTravelTargetAction prevent delay when in combat (#1558) --- src/strategy/actions/MoveToTravelTargetAction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/strategy/actions/MoveToTravelTargetAction.cpp b/src/strategy/actions/MoveToTravelTargetAction.cpp index 6782ea3f..9ae28919 100644 --- a/src/strategy/actions/MoveToTravelTargetAction.cpp +++ b/src/strategy/actions/MoveToTravelTargetAction.cpp @@ -18,7 +18,7 @@ bool MoveToTravelTargetAction::Execute(Event event) WorldLocation location = *target->getPosition(); Group* group = bot->GetGroup(); - if (group && !urand(0, 1) && bot == botAI->GetGroupMaster()) + if (group && !urand(0, 1) && bot == botAI->GetGroupMaster() && !bot->IsInCombat()) { for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next()) {