From d770e0a9a014e5e180ec6b5fea6acf09a2afcd23 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Thu, 30 Jan 2025 07:16:59 -0600 Subject: [PATCH] fix(BotAI/BattleGrounds) Bots AI improved based around capture points (#927) * Less Flag Derp * Comments --- src/strategy/actions/BattleGroundTactics.cpp | 181 +++++++++++++------ 1 file changed, 129 insertions(+), 52 deletions(-) diff --git a/src/strategy/actions/BattleGroundTactics.cpp b/src/strategy/actions/BattleGroundTactics.cpp index 4f457d29..452031eb 100644 --- a/src/strategy/actions/BattleGroundTactics.cpp +++ b/src/strategy/actions/BattleGroundTactics.cpp @@ -4069,26 +4069,45 @@ bool BGTactics::startNewPathFree(std::vector const& vPaths) return moveToObjectiveWp(currentPath, currentPoint, reverse); } +/** + * @brief Handles flag/base capturing gameplay in battlegrounds + * + * This function manages the logic for capturing flags and bases in various battlegrounds. + * It handles: + * - Enemy detection and combat near objectives + * - Coordination with friendly players who are capturing + * - Different capture mechanics for each battleground type + * - Proper positioning and movement + * + * @param vPaths Vector of possible paths the bot can take + * @param vFlagIds Vector of flag/base GameObjects that can be captured + * @return true if handling a flag/base action, false otherwise + */ bool BGTactics::atFlag(std::vector const& vPaths, std::vector const& vFlagIds) { + // Basic sanity checks Battleground* bg = bot->GetBattleground(); if (!bg) return false; + // Get the actual BG type (in case of random BG) BattlegroundTypeId bgType = bg->GetBgTypeID(); if (bgType == BATTLEGROUND_RB) bgType = bg->GetBgTypeID(true); + // Initialize vectors for nearby objects and players GuidVector closeObjects; GuidVector closePlayers; float flagRange = 0.0f; + // Set up appropriate search ranges and object lists based on BG type switch (bgType) { case BATTLEGROUND_AV: case BATTLEGROUND_AB: case BATTLEGROUND_IC: { + // For territory control BGs, use standard interaction range closeObjects = *context->GetValue("closest game objects"); closePlayers = *context->GetValue("closest friendly players"); flagRange = INTERACTION_DISTANCE; @@ -4097,6 +4116,7 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetValue("nearest game objects no los"); closePlayers = *context->GetValue("closest friendly players"); flagRange = 25.0f; @@ -4109,41 +4129,109 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetUnit(guid)) - { - if (Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL)) - { - if (spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER) - { - resetObjective(); - startNewPathBegin(vPaths); - return false; - } - } - } - } - } - - // std::ostringstream out; out << "Found " << closeObjects.size() << " nearby objects"; - // bot->Say(out.str(), LANG_UNIVERSAL); - + // First identify which flag/base we're trying to interact with + GameObject* targetFlag = nullptr; for (ObjectGuid const guid : closeObjects) { GameObject* go = botAI->GetGameObject(guid); if (!go) continue; + // Check if this object is a valid capture target std::vector::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry()); if (f == vFlagIds.end()) continue; + // Verify the object is active and ready if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY) continue; + // Check if we're in range (using double range for enemy detection) + float const dist = bot->GetDistance(go); + if (flagRange && dist > flagRange * 2.0f) + continue; + + targetFlag = go; + break; + } + + // If we found a valid flag/base to interact with + if (targetFlag) + { + // Check for enemy players near the flag using bot's targeting system + Unit* enemyPlayer = AI_VALUE(Unit*, "enemy player target"); + if (enemyPlayer && enemyPlayer->IsAlive()) + { + // If enemy is near the flag, engage them before attempting capture + float enemyDist = enemyPlayer->GetDistance(targetFlag); + if (enemyDist < flagRange * 2.0f) + { + // Set enemy as current target and let combat AI handle it + context->GetValue("current target")->Set(enemyPlayer); + return false; + } + } + } + + // Check if friendly players are already capturing + if (!closePlayers.empty()) + { + // Track number of friendly players capturing and the closest one + uint32 numCapturing = 0; + Unit* capturingPlayer = nullptr; + for (auto& guid : closePlayers) + { + if (Unit* pFriend = botAI->GetUnit(guid)) + { + // Check if they're casting the capture spell + if (Spell* spell = pFriend->GetCurrentSpell(CURRENT_GENERIC_SPELL)) + { + if (spell->m_spellInfo->Id == SPELL_CAPTURE_BANNER) + { + numCapturing++; + capturingPlayer = pFriend; + } + } + } + } + + // If friendlies are capturing, stay to defend but don't capture + if (numCapturing > 0 && capturingPlayer) + { + // Move away if too close to avoid crowding + if (bot->GetDistance2d(capturingPlayer) < 3.0f) + { + float angle = bot->GetAngle(capturingPlayer); + float x = bot->GetPositionX() + 5.0f * cos(angle); + float y = bot->GetPositionY() + 5.0f * sin(angle); + MoveTo(bot->GetMapId(), x, y, bot->GetPositionZ()); + } + + // Reset objective and take new path for defending + resetObjective(); + startNewPathBegin(vPaths); + return true; + } + } + + // Area is clear of enemies and no friendlies are capturing + // Proceed with capture mechanics + for (ObjectGuid const guid : closeObjects) + { + GameObject* go = botAI->GetGameObject(guid); + if (!go) + continue; + + // Validate this is a capture target + std::vector::const_iterator f = find(vFlagIds.begin(), vFlagIds.end(), go->GetEntry()); + if (f == vFlagIds.end()) + continue; + + // Check object is active + if (!go->isSpawned() || go->GetGoState() != GO_STATE_READY) + continue; + + // Verify we can interact with it if (!bot->CanUseBattlegroundObject(go) && bgType != BATTLEGROUND_WS) continue; @@ -4151,41 +4239,39 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vector flagRange) continue; - bool atBase = bgType == BATTLEGROUND_WS ? go->GetEntry() == vFlagsWS[bot->GetTeamId()] - : bgType == BATTLEGROUND_EY ? go->GetEntry() == vFlagsEY[0] - : false; + // Special handling for WSG and EY base flags + bool atBase = bgType == BATTLEGROUND_WS ? go->GetEntry() == vFlagsWS[bot->GetTeamId()] + : bgType == BATTLEGROUND_EY ? go->GetEntry() == vFlagsEY[0] + : false; + // Don't capture own flag in WSG unless carrying enemy flag if (atBase && bgType == BATTLEGROUND_WS && !(bot->HasAura(BG_WS_SPELL_WARSONG_FLAG) || bot->HasAura(BG_WS_SPELL_SILVERWING_FLAG))) continue; + // Handle capture mechanics based on BG type switch (bgType) { case BATTLEGROUND_AV: case BATTLEGROUND_AB: case BATTLEGROUND_IC: { + // Prevent capturing from inside flag pole if (dist == 0.0f) { - // this is to prevent bots capping while standing INSIDE the flag pole (which can be thick enough to - // hide player entirely) note that dist is taking into account size of object and bot (it's the - // space between outside of both) so moveDist needs to as well float const moveDist = bot->GetObjectSize() + go->GetObjectSize() + 0.1f; return MoveTo(bot->GetMapId(), go->GetPositionX() + (urand(0, 1) ? -moveDist : moveDist), - go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ()); + go->GetPositionY() + (urand(0, 1) ? -moveDist : moveDist), go->GetPositionZ()); } + + // Dismount before capturing if (bot->IsMounted()) bot->RemoveAurasByType(SPELL_AURA_MOUNTED); if (bot->IsInDisallowedMountForm()) bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); - // std::ostringstream out; - // out << "Flag is nearby, using " << go->GetName(); - // bot->Say(out.str(), LANG_UNIVERSAL); - // botAI->SetNextCheckDelay(10000); - - // cast banner spell + // Cast the capture spell SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_CAPTURE_BANNER); if (!spellInfo) return false; @@ -4196,17 +4282,14 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorWaitForSpellCast(spell); - // WorldPacket data(CMSG_GAMEOBJ_USE); - // data << go->GetGUID(); - // bot->GetSession()->HandleGameObjectUseOpcode(data); resetObjective(); return true; - break; } case BATTLEGROUND_WS: { if (dist < INTERACTION_DISTANCE) { + // Handle flag capture at base if (atBase) { if (bot->GetTeamId() == TEAM_HORDE) @@ -4221,20 +4304,17 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetSession()->HandleAreaTriggerOpcode(data); } - // std::ostringstream out; - // out << "Capturing flag!"; - // bot->Say(out.str(), LANG_UNIVERSAL); return true; } + // Dismount before picking up flag if (bot->IsMounted()) bot->RemoveAurasByType(SPELL_AURA_MOUNTED); if (bot->IsInDisallowedMountForm()) bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); - // std::ostringstream out; out << "Flag is nearby, using " << go->GetName(); - // bot->Say(out.str(), LANG_UNIVERSAL); + // Pick up the flag WorldPacket data(CMSG_GAMEOBJ_USE); data << go->GetGUID(); bot->GetSession()->HandleGameObjectUseOpcode(data); @@ -4244,25 +4324,22 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetName() << " " << go->GetPositionX() << " " << - // go->GetPositionY() << " Distance:" << sServerFacade->GetDistance2d(bot, go->GetPositionX(), - // go->GetPositionY()); bot->Say(out.str(), LANG_UNIVERSAL); + // Move to flag if not in range return MoveTo(bot->GetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ()); } - break; } case BATTLEGROUND_EY: { if (dist < INTERACTION_DISTANCE) { + // Dismount before interacting if (bot->IsMounted()) bot->RemoveAurasByType(SPELL_AURA_MOUNTED); if (bot->IsInDisallowedMountForm()) bot->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT); - // Flag at center requires casting spell + // Handle center flag differently (requires spell cast) if (atBase) { SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(SPELL_CAPTURE_BANNER); @@ -4277,7 +4354,7 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetGUID(); bot->GetSession()->HandleGameObjectUseOpcode(data); @@ -4287,9 +4364,9 @@ bool BGTactics::atFlag(std::vector const& vPaths, std::vectorGetMapId(), go->GetPositionX(), go->GetPositionY(), go->GetPositionZ()); } - break; } default: break;