Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 5 additions & 1 deletion .github/workflows/windows_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,25 @@ jobs:
with:
repository: 'mod-playerbots/azerothcore-wotlk'
ref: 'Playerbot'
path: 'ac'
- name: Checkout Playerbot Module
uses: actions/checkout@v3
with:
repository: 'mod-playerbots/mod-playerbots'
path: 'modules/mod-playerbots'
#path: 'modules/mod-playerbots'
path: ac/modules/mod-playerbots
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.13
- name: Configure OS
shell: bash
working-directory: ac
env:
CONTINUOUS_INTEGRATION: true
run: |
./acore.sh install-deps
- name: Build
shell: bash
working-directory: ac
run: |
export CTOOLS_BUILD=all
./acore.sh compiler build
4 changes: 4 additions & 0 deletions conf/playerbots.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,10 @@ AiPlayerbot.SitDelay = 20000
# ReturnDelay has a minimum value of 2000 - lower values will cause a crash!
AiPlayerbot.ReturnDelay = 2000
AiPlayerbot.LootDelay = 1000
# Default delay in milliseconds before bots engage in combat (0 = no delay)
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value of 3000ms differs from the documented default in the code (0 = no delay). The config comment states 'Default delay in milliseconds before bots engage in combat (0 = no delay)' but sets it to 3000. This inconsistency is confusing. Either change the default to 0 to match the documentation, or update the comment to clarify that 3000ms is the recommended default.

Suggested change
# Default delay in milliseconds before bots engage in combat (0 = no delay)
# Delay in milliseconds before bots engage in combat (default: 3000 ms; set to 0 for no delay)

Copilot uses AI. Check for mistakes.
# Requires "combat delay" strategy to be active (.bot strategy add combat delay)
# Only applies to PvE targets - PvP targets are attacked immediately
AiPlayerbot.CombatDelay = 3000

#
#
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <iostream>
#include "Config.h"
#include "NewRpgInfo.h"
#include "PlayerbotDungeonSuggestionMgr.h"
#include "PlayerbotDungeonRepository.h"
#include "PlayerbotFactory.h"
#include "Playerbots.h"
#include "PlayerbotGuildMgr.h"
Expand Down Expand Up @@ -82,6 +82,7 @@ bool PlayerbotAIConfig::Initialize()
sitDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.SitDelay", 20000);
returnDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.ReturnDelay", 2000);
lootDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.LootDelay", 1000);
combatDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.CombatDelay", 3000);
minBotsForGreaterBuff = sConfigMgr->GetOption<int32>("AiPlayerbot.MinBotsForGreaterBuff", 3);
rpWarningCooldown = sConfigMgr->GetOption<int32>("AiPlayerbot.RPWarningCooldown", 30);
disabledWithoutRealPlayerLoginDelay = sConfigMgr->GetOption<int32>("AiPlayerbot.DisabledWithoutRealPlayerLoginDelay", 30);
Expand Down Expand Up @@ -678,7 +679,7 @@ bool PlayerbotAIConfig::Initialize()

if (sPlayerbotAIConfig->randomBotSuggestDungeons)
{
sPlayerbotDungeonSuggestionMgr->LoadDungeonSuggestions();
sPlayerbotDungeonRepository->LoadDungeonSuggestions();
}

excludedHunterPetFamilies.clear();
Expand Down
2 changes: 1 addition & 1 deletion src/PlayerbotAIConfig.h → src/Config/PlayerbotAIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class PlayerbotAIConfig
bool allowAccountBots, allowGuildBots, allowTrustedAccountBots;
bool randomBotGuildNearby, randomBotInvitePlayer, inviteChat;
uint32 globalCoolDown, reactDelay, maxWaitForMove, disableMoveSplinePath, maxMovementSearchTime, expireActionTime,
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay;
dispelAuraDuration, passiveDelay, repeatDelay, errorDelay, rpgDelay, sitDelay, returnDelay, lootDelay, combatDelay;
bool dynamicReactDelay;
float sightDistance, spellDistance, reactDistance, grindDistance, lootDistance, shootDistance, fleeDistance,
tooCloseDistance, meleeDistance, followDistance, whisperDistance, contactDistance, aoeRadius, rpgDistance,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -27,32 +27,32 @@
#include "WarriorAiObjectContext.h"
#include "WorldPacketActionContext.h"
#include "WorldPacketTriggerContext.h"
#include "dungeons/DungeonStrategyContext.h"
#include "dungeons/wotlk/WotlkDungeonActionContext.h"
#include "dungeons/wotlk/WotlkDungeonTriggerContext.h"
#include "raids/RaidStrategyContext.h"
#include "raids/aq20/RaidAq20ActionContext.h"
#include "raids/aq20/RaidAq20TriggerContext.h"
#include "raids/moltencore/RaidMcActionContext.h"
#include "raids/moltencore/RaidMcTriggerContext.h"
#include "raids/blackwinglair/RaidBwlActionContext.h"
#include "raids/blackwinglair/RaidBwlTriggerContext.h"
#include "raids/karazhan/RaidKarazhanActionContext.h"
#include "raids/karazhan/RaidKarazhanTriggerContext.h"
#include "raids/magtheridon/RaidMagtheridonActionContext.h"
#include "raids/magtheridon/RaidMagtheridonTriggerContext.h"
#include "raids/gruulslair/RaidGruulsLairActionContext.h"
#include "raids/gruulslair/RaidGruulsLairTriggerContext.h"
#include "raids/eyeofeternity/RaidEoEActionContext.h"
#include "raids/eyeofeternity/RaidEoETriggerContext.h"
#include "raids/vaultofarchavon/RaidVoAActionContext.h"
#include "raids/vaultofarchavon/RaidVoATriggerContext.h"
#include "raids/obsidiansanctum/RaidOsActionContext.h"
#include "raids/obsidiansanctum/RaidOsTriggerContext.h"
#include "raids/onyxia/RaidOnyxiaActionContext.h"
#include "raids/onyxia/RaidOnyxiaTriggerContext.h"
#include "raids/icecrown/RaidIccActionContext.h"
#include "raids/icecrown/RaidIccTriggerContext.h"
#include "Extend/DungeonAi/DungeonStrategyContext.h"
#include "Extend/DungeonAi/Wotlk/WotlkDungeonActionContext.h"
#include "Extend/DungeonAi/Wotlk/WotlkDungeonTriggerContext.h"
#include "Extend/RaidAi/RaidStrategyContext.h"
#include "Extend/RaidAi/Aq20/RaidAq20ActionContext.h"
#include "Extend/RaidAi/Aq20/RaidAq20TriggerContext.h"
#include "Extend/RaidAi/MoltenCore/RaidMcActionContext.h"
#include "Extend/RaidAi/MoltenCore/RaidMcTriggerContext.h"
#include "Extend/RaidAi/BlackwingLair/RaidBwlActionContext.h"
#include "Extend/RaidAi/BlackwingLair/RaidBwlTriggerContext.h"
#include "Extend/RaidAi/Karazhan/RaidKarazhanActionContext.h"
#include "Extend/RaidAi/Karazhan/RaidKarazhanTriggerContext.h"
#include "Extend/RaidAi/Magtheridon/RaidMagtheridonActionContext.h"
#include "Extend/RaidAi/Magtheridon/RaidMagtheridonTriggerContext.h"
#include "Extend/RaidAi/GruulsLair/RaidGruulsLairActionContext.h"
#include "Extend/RaidAi/GruulsLair/RaidGruulsLairTriggerContext.h"
#include "Extend/RaidAi/EyeOfEternity/RaidEoEActionContext.h"
#include "Extend/RaidAi/EyeOfEternity/RaidEoETriggerContext.h"
#include "Extend/RaidAi/VaultOfArchavon/RaidVoAActionContext.h"
#include "Extend/RaidAi/VaultOfArchavon/RaidVoATriggerContext.h"
#include "Extend/RaidAi/ObsidianSanctum/RaidOsActionContext.h"
#include "Extend/RaidAi/ObsidianSanctum/RaidOsTriggerContext.h"
#include "Extend/RaidAi/Onyxia/RaidOnyxiaActionContext.h"
#include "Extend/RaidAi/Onyxia/RaidOnyxiaTriggerContext.h"
#include "Extend/RaidAi/Icecrown/RaidIccActionContext.h"
#include "Extend/RaidAi/Icecrown/RaidIccTriggerContext.h"

SharedNamedObjectContextList<Strategy> AiObjectContext::sharedStrategyContexts;
SharedNamedObjectContextList<Action> AiObjectContext::sharedActionContexts;
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions src/strategy/Engine.cpp → src/Core/Ai/Engine/Engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

#include "Action.h"
#include "Event.h"
#include "PerformanceMonitor.h"
#include "PerfMonitor.h"
#include "Playerbots.h"
#include "Queue.h"
#include "Strategy.h"
Expand Down Expand Up @@ -204,7 +204,7 @@ bool Engine::DoNextAction(Unit* unit, uint32 depth, bool minimal)
}
}

PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_ACTION, action->getName(), &aiObjectContext->performanceStack);
PerfMonitorOperation* pmo = sPerfMonitor->start(PERF_MON_ACTION, action->getName(), &aiObjectContext->performanceStack);
actionExecuted = ListenAndExecute(action, event);
if (pmo)
pmo->finish();
Expand Down Expand Up @@ -456,8 +456,8 @@ void Engine::ProcessTriggers(bool minimal)
if (minimal && node->getFirstRelevance() < 100)
continue;

PerformanceMonitorOperation* pmo =
sPerformanceMonitor->start(PERF_MON_TRIGGER, trigger->getName(), &aiObjectContext->performanceStack);
PerfMonitorOperation* pmo =
sPerfMonitor->start(PERF_MON_TRIGGER, trigger->getName(), &aiObjectContext->performanceStack);
Event event = trigger->Check();
if (pmo)
pmo->finish();
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ void PlayerbotAIBase::UpdateAI(uint32 elapsed, bool minimal)
if (totalPmo)
totalPmo->finish();

totalPmo = sPerformanceMonitor->start(PERF_MON_TOTAL, "PlayerbotAIBase::FullTick");
totalPmo = sPerfMonitor->start(PERF_MON_TOTAL, "PlayerbotAIBase::FullTick");

if (nextAICheckDelay > elapsed)
nextAICheckDelay -= elapsed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class PlayerbotAIBase

protected:
uint32 nextAICheckDelay;
class PerformanceMonitorOperation* totalPmo = nullptr;
class PerfMonitorOperation* totalPmo = nullptr;

private:
bool _isBotAI;
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
6 changes: 3 additions & 3 deletions src/strategy/Value.cpp → src/Core/Ai/Engine/Value/Value.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include "Value.h"

#include "PerformanceMonitor.h"
#include "PerfMonitor.h"
#include "Playerbots.h"
#include "Timer.h"

Expand Down Expand Up @@ -123,7 +123,7 @@ Unit* UnitCalculatedValue::Get()
{
if (checkInterval < 2)
{
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(
PerfMonitorOperation* pmo = sPerfMonitor->start(
PERF_MON_VALUE, this->getName(), this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
if (pmo)
Expand All @@ -135,7 +135,7 @@ Unit* UnitCalculatedValue::Get()
if (!lastCheckTime || now - lastCheckTime >= checkInterval)
{
lastCheckTime = now;
PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(
PerfMonitorOperation* pmo = sPerfMonitor->start(
PERF_MON_VALUE, this->getName(), this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
if (pmo)
Expand Down
12 changes: 6 additions & 6 deletions src/strategy/Value.h → src/Core/Ai/Engine/Value/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

#include "AiObject.h"
#include "ObjectGuid.h"
#include "PerformanceMonitor.h"
#include "PerfMonitor.h"
#include "Timer.h"
#include "Unit.h"

Expand Down Expand Up @@ -72,7 +72,7 @@ class CalculatedValue : public UntypedValue, public Value<T>
{
if (checkInterval < 2)
{
// PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_VALUE, this->getName(),
// PerfMonitorOperation* pmo = sPerfMonitor->start(PERF_MON_VALUE, this->getName(),
// this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
// if (pmo)
Expand All @@ -84,7 +84,7 @@ class CalculatedValue : public UntypedValue, public Value<T>
if (!lastCheckTime || now - lastCheckTime >= checkInterval)
{
lastCheckTime = now;
// PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_VALUE, this->getName(),
// PerfMonitorOperation* pmo = sPerfMonitor->start(PERF_MON_VALUE, this->getName(),
// this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
// if (pmo)
Expand All @@ -105,7 +105,7 @@ class CalculatedValue : public UntypedValue, public Value<T>
{
if (checkInterval < 2)
{
// PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_VALUE, this->getName(),
// PerfMonitorOperation* pmo = sPerfMonitor->start(PERF_MON_VALUE, this->getName(),
// this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
// if (pmo)
Expand All @@ -117,7 +117,7 @@ class CalculatedValue : public UntypedValue, public Value<T>
if (!lastCheckTime || now - lastCheckTime >= checkInterval)
{
lastCheckTime = now;
// PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(PERF_MON_VALUE, this->getName(),
// PerfMonitorOperation* pmo = sPerfMonitor->start(PERF_MON_VALUE, this->getName(),
// this->context ? &this->context->performanceStack : nullptr);
value = Calculate();
// if (pmo)
Expand Down Expand Up @@ -154,7 +154,7 @@ class SingleCalculatedValue : public CalculatedValue<T>
{
this->lastCheckTime = now;

PerformanceMonitorOperation* pmo = sPerformanceMonitor->start(
PerfMonitorOperation* pmo = sPerfMonitor->start(
PERF_MON_VALUE, this->getName(), this->context ? &this->context->performanceStack : nullptr);
this->value = this->Calculate();
if (pmo)
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ bool AcceptInvitationAction::Execute(Event event)
if (sRandomPlayerbotMgr->IsRandomBot(bot))
botAI->SetMaster(inviter);
// else
// sPlayerbotDbStore->Save(botAI);
// sPlayerbotRepository->Save(botAI);

botAI->ResetStrategies();
botAI->ChangeStrategy("+follow,-lfg,-bg", BOT_STATE_NON_COMBAT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@

#include "AttackAction.h"

#include <unordered_map>
#include "CreatureAI.h"
#include "Event.h"
#include "LastMovementValue.h"
#include "LootObjectStack.h"
#include "PlayerbotAI.h"
#include "PlayerbotAIConfig.h"
#include "Playerbots.h"
#include "ServerFacade.h"
#include "SharedDefines.h"
#include "Unit.h"
#include "Timer.h"

bool AttackAction::Execute(Event /*event*/)
{
Expand Down Expand Up @@ -43,14 +46,14 @@ bool AttackMyTargetAction::Execute(Event /*event*/)
}

botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({guid});
bool result = Attack(botAI->GetUnit(guid));
bool result = Attack(botAI->GetUnit(guid), true, true); // Skip combat delay for explicit commands
if (result)
context->GetValue<ObjectGuid>("pull target")->Set(guid);

return result;
}

bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/, bool skipCombatDelay /*false*/)
{
Unit* oldTarget = context->GetValue<Unit*>("current target")->Get();
bool shouldMelee = bot->IsWithinMeleeRange(target) || botAI->IsMelee(bot);
Expand All @@ -59,6 +62,35 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)
bool inCombat = botAI->GetState() == BOT_STATE_COMBAT;
bool sameAttackMode = bot->HasUnitState(UNIT_STATE_MELEE_ATTACKING) == shouldMelee;

// Combat delay check - prevent immediate engagement (not for PvP or explicit commands)
bool isPvPTarget = target->IsPlayer() || (target->IsPet() && target->GetOwner() && target->GetOwner()->IsPlayer());
if (!skipCombatDelay && botAI->HasStrategy("combat delay", BOT_STATE_NON_COMBAT) && sPlayerbotAIConfig->combatDelay > 0 && !sameTarget && !inCombat && !isPvPTarget)
{
static std::unordered_map<ObjectGuid, uint32> targetFirstSeenTime;
Copy link

Copilot AI Jan 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The static map targetFirstSeenTime is shared across all bots and accessed without thread synchronization, which could lead to race conditions if multiple threads execute attacks concurrently. Similar patterns in this codebase (e.g., WarlockActions.cpp) use mutex protection with std::lock_guard. Add a static mutex and protect all accesses to targetFirstSeenTime.

Copilot uses AI. Check for mistakes.
static uint32 lastCleanupTime = 0;
ObjectGuid targetGuid = target->GetGUID();
uint32 currentTime = getMSTime();
if (targetFirstSeenTime.find(targetGuid) == targetFirstSeenTime.end())
targetFirstSeenTime[targetGuid] = currentTime;

uint32 timeSinceFirstSeen = currentTime - targetFirstSeenTime[targetGuid];
if (timeSinceFirstSeen < sPlayerbotAIConfig->combatDelay)
return false;

// Clean up old entries to prevent memory leak
if (currentTime - lastCleanupTime > 60000)
{
lastCleanupTime = currentTime;
for (auto it = targetFirstSeenTime.begin(); it != targetFirstSeenTime.end();)
{
if (currentTime - it->second > sPlayerbotAIConfig->combatDelay + 10000)
it = targetFirstSeenTime.erase(it);
else
++it;
}
}
}

if (bot->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE ||
bot->HasUnitState(UNIT_STATE_IN_FLIGHT))
{
Expand Down Expand Up @@ -191,4 +223,4 @@ bool AttackAction::Attack(Unit* target, bool /*with_pet*/ /*true*/)

bool AttackDuelOpponentAction::isUseful() { return AI_VALUE(Unit*, "duel target"); }

bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target")); }
bool AttackDuelOpponentAction::Execute(Event /*event*/) { return Attack(AI_VALUE(Unit*, "duel target"), true, true); }
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class AttackAction : public MovementAction
bool Execute(Event event) override;

protected:
bool Attack(Unit* target, bool with_pet = true);
bool Attack(Unit* target, bool with_pet = true, bool skipCombatDelay = false);
};

class AttackMyTargetAction : public AttackAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "ChangeStrategyAction.h"

#include "Event.h"
#include "PlayerbotDbStore.h"
#include "PlayerbotRepository.h"
#include "Playerbots.h"

bool ChangeCombatStrategyAction::Execute(Event event)
Expand All @@ -24,7 +24,7 @@ bool ChangeCombatStrategyAction::Execute(Event event)
case '+':
case '-':
case '~':
sPlayerbotDbStore->Save(botAI);
sPlayerbotRepository->Save(botAI);
break;
case '?':
break;
Expand Down Expand Up @@ -62,7 +62,7 @@ bool ChangeNonCombatStrategyAction::Execute(Event event)
case '+':
case '-':
case '~':
sPlayerbotDbStore->Save(botAI);
sPlayerbotRepository->Save(botAI);
break;
case '?':
break;
Expand Down
File renamed without changes.
Loading