Files
mod-playerbots/src/Ai/Base/Actions/AttackAction.cpp
bashermens 13fff46fa0 Improper singletons migration to clean Meyer's singletons (cherry-pick) (#2082)
# Pull Request

- Applies the clean and corrected singletons, Meyer pattern. (cherry
picked from @SmashingQuasar )

Testing by just playing the game in various ways. Been tested by myself
@Celandriel and @SmashingQuasar
---

## Complexity & Impact

- Does this change add new decision branches?
    - [x] No
    - [ ] Yes (**explain below**)

- Does this change increase per-bot or per-tick processing?
    - [x] No
    - [ ] Yes (**describe and justify impact**)

- Could this logic scale poorly under load?
    - [x] No
    - [ ] Yes (**explain why**)

---

## Defaults & Configuration

- Does this change modify default bot behavior?
    - [x] No
    - [ ] Yes (**explain why**)

---

## AI Assistance

- Was AI assistance (e.g. ChatGPT or similar tools) used while working
on this change?
    - [x] No
    - [ ] Yes (**explain below**)
---

## Final Checklist

- [x] Stability is not compromised
- [x] Performance impact is understood, tested, and acceptable
- [x] Added logic complexity is justified and explained
- [x] Documentation updated if needed

---

## Notes for Reviewers

Anything that significantly improves realism at the cost of stability or
performance should be carefully discussed
before merging.

---------

Co-authored-by: Nicolas Lebacq <nicolas.cordier@outlook.com>
Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2026-01-30 21:49:37 +01:00

195 lines
5.5 KiB
C++

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "AttackAction.h"
#include "CreatureAI.h"
#include "Event.h"
#include "LastMovementValue.h"
#include "LootObjectStack.h"
#include "PlayerbotAI.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "Unit.h"
bool AttackAction::Execute(Event /*event*/)
{
Unit* target = GetTarget();
if (!target)
return false;
if (!target->IsInWorld())
return false;
return Attack(target);
}
bool AttackMyTargetAction::Execute(Event /*event*/)
{
Player* master = GetMaster();
if (!master)
return false;
ObjectGuid guid = master->GetTarget();
if (!guid)
{
if (verbose)
botAI->TellError("You have no target");
return false;
}
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({guid});
bool result = Attack(botAI->GetUnit(guid));
if (result)
context->GetValue<ObjectGuid>("pull target")->Set(guid);
return result;
}
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
{
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
bool sameTarget = oldTarget == target && bot->GetVictim() == target;
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;
if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
if (verbose)
botAI->TellError("I cannot attack in flight");
return false;
}
if (!target)
{
if (verbose)
botAI->TellError("I have no target");
return false;
}
if (!target->IsInWorld())
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is no longer in the world.");
return false;
}
// Check if bot OR target is in prohibited zone/area (skip for duels)
if ((target->IsPlayer() || target->IsPet()) &&
(!bot->duel || bot->duel->Opponent != target) &&
(sPlayerbotAIConfig.IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId()) ||
sPlayerbotAIConfig.IsPvpProhibited(target->GetZoneId(), target->GetAreaId())))
{
if (verbose)
botAI->TellError("I cannot attack other players in PvP prohibited areas.");
return false;
}
if (bot->IsFriendlyTo(target))
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is friendly to me.");
return false;
}
if (target->isDead())
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is dead.");
return false;
}
if (!bot->IsWithinLOSInMap(target))
{
if (verbose)
botAI->TellError(std::string(target->GetName()) + " is not in my sight.");
return false;
}
if (sameTarget && inCombat && sameAttackMode)
{
if (verbose)
botAI->TellError("I am already attacking " + std::string(target->GetName()) + ".");
return false;
}
if (!bot->IsValidAttackTarget(target))
{
if (verbose)
botAI->TellError("I cannot attack an invalid target.");
return false;
}
// if (bot->IsMounted() && bot->IsWithinLOSInMap(target))
// {
// WorldPacket emptyPacket;
// bot->GetSession()->HandleCancelMountAuraOpcode(emptyPacket);
// }
ObjectGuid guid = target->GetGUID();
bot->SetSelection(target->GetGUID());
context->GetValue<Unit*>("old target")->Set(oldTarget);
context->GetValue<Unit*>("current target")->Set(target);
context->GetValue<LootObjectStack*>("available loot")->Get()->Add(guid);
LastMovement& lastMovement = AI_VALUE(LastMovement&, "last movement");
bool moveControlled = bot->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE;
if (lastMovement.priority < MovementPriority::MOVEMENT_COMBAT && bot->isMoving() && !moveControlled)
{
AI_VALUE(LastMovement&, "last movement").clear();
bot->GetMotionMaster()->Clear(false);
bot->StopMoving();
}
if (botAI->CanMove() && !bot->HasInArc(CAST_ANGLE_IN_FRONT, target))
ServerFacade::instance().SetFacingTo(bot, target);
botAI->ChangeEngine(BOT_STATE_COMBAT);
bot->Attack(target, shouldMelee);
/* prevent pet dead immediately in group */
// if (bot->GetMap()->IsDungeon() && bot->GetGroup() && !target->IsInCombat())
// {
// with_pet = false;
// }
// if (Pet* pet = bot->GetPet())
// {
// if (with_pet)
// {
// pet->SetReactState(REACT_DEFENSIVE);
// pet->SetTarget(target->GetGUID());
// pet->GetCharmInfo()->SetIsCommandAttack(true);
// pet->AI()->AttackStart(target);
// }
// else
// {
// pet->SetReactState(REACT_PASSIVE);
// pet->GetCharmInfo()->SetIsCommandFollow(true);
// pet->GetCharmInfo()->IsReturning();
// }
// }
return true;
}
bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }