Skip to content
Draft
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions conf/playerbots.conf.dist
Original file line number Diff line number Diff line change
Expand Up @@ -866,22 +866,32 @@ AiPlayerbot.ExcludedHunterPetFamilies = ""
#
####################################################################################################


####################################################################################################
# ACTIVITIES
# ACTIVITY
#
# BotActiveAlone (%)
# - Determines the percentage of bots that remain active when no real players are nearby.
# - Default is 40% (which is practise kinda translates into 45-50%).
# - If `botActiveAloneSmartScale` is enabled, automatically temporarily down scale activity based on latency.
# - 40% will be activated in a random rotation for the amount of seconds specified in <BotActiveAloneDurationSeconds>.
# - There are multiple conditions when bots will forced to be active e.g. when in BG/instance/attacked, some are configurable below.
#- When 100$ all bots will be active without any rotation or logic applied but comes with performance hit.
#
# Specify percent of active bots
# The default is 100% but will be automatically adjusted if botActiveAloneSmartScale
# is enabled. Regardless, this value is only applied to inactive areas where no real players
# are detected. When real players are nearby, the value is always enforced to 100%
AiPlayerbot.BotActiveAlone = 100
AiPlayerbot.BotActiveAlone = 10
AiPlayerbot.BotActiveAloneDurationSeconds = 30

# Force botActiveAlone when bot is within the specified distance of a real player
# Some additional rules that enforces the bot to be active
#
# - bot is within this distance from a real player.
# - bot is in the same zone as a real player.
# - bot is in the same continent as a real player.
# - bot is a real player's friend.
# - bot is in a real player's guild.
#
AiPlayerbot.BotActiveAloneForceWhenInRadius = 150
AiPlayerbot.BotActiveAloneForceWhenInZone = 1
AiPlayerbot.BotActiveAloneForceWhenInMap = 0
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 1
AiPlayerbot.BotActiveAloneForceWhenIsFriend = 0
AiPlayerbot.BotActiveAloneForceWhenInGuild = 1

# SmartScale (automatic scaling of percentage of active bots based on latency)
Expand Down
10 changes: 7 additions & 3 deletions src/Bot/Engine/PlayerbotAIBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void PlayerbotAIBase::UpdateAI(uint32 elapsed, bool minimal)
return;

UpdateAIInternal(elapsed, minimal);
YieldThread();
YieldThread(nullptr);
}

void PlayerbotAIBase::SetNextCheckDelay(uint32 const delay)
Expand All @@ -49,10 +49,14 @@ void PlayerbotAIBase::IncreaseNextCheckDelay(uint32 delay)

bool PlayerbotAIBase::CanUpdateAI() { return nextAICheckDelay == 0; }

void PlayerbotAIBase::YieldThread(uint32 delay)
void PlayerbotAIBase::YieldThread(Player* bot, uint32 delay)
{
if (nextAICheckDelay < delay)
nextAICheckDelay = delay;
{
// Adding a deterministic per-bot slight offset (0–200 ms) to stagger updates and prevent cpu spikes.
uint32 offset = bot ? (bot->GetGUID().GetCounter() % 201) : 0;
nextAICheckDelay = delay + offset;
}
}

bool PlayerbotAIBase::IsActive() { return nextAICheckDelay < sPlayerbotAIConfig.maxWaitForMove; }
Expand Down
3 changes: 2 additions & 1 deletion src/Bot/Engine/PlayerbotAIBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include "Define.h"
#include "PlayerbotAIConfig.h"
#include "Player.h"

class PlayerbotAIBase
{
Expand All @@ -17,7 +18,7 @@ class PlayerbotAIBase
bool CanUpdateAI();
void SetNextCheckDelay(uint32 const delay);
void IncreaseNextCheckDelay(uint32 delay);
void YieldThread(uint32 delay = sPlayerbotAIConfig.reactDelay);
void YieldThread(Player* bot, uint32 delay = sPlayerbotAIConfig.reactDelay);
virtual void UpdateAI(uint32 elapsed, bool minimal = false);
virtual void UpdateAIInternal(uint32 elapsed, bool minimal = false) = 0;
bool IsActive();
Expand Down
76 changes: 39 additions & 37 deletions src/Bot/PlayerbotAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
if (spellTarget && !spellTarget->IsAlive() && !spellInfo->IsAllowingDeadTarget())
{
InterruptSpell();
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}

Expand All @@ -288,7 +288,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
if (goSpellTarget && !goSpellTarget->isSpawned())
{
InterruptSpell();
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}

Expand Down Expand Up @@ -320,7 +320,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
if (isHeal && isSingleTarget && spellTarget && spellTarget->IsFullHealth())
{
InterruptSpell();
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}

Expand All @@ -332,7 +332,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
}

// Wait for spell cast
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
return;
}
}
Expand Down Expand Up @@ -368,7 +368,7 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)

// Update internal AI
UpdateAIInternal(elapsed, minimal);
YieldThread(GetReactDelay());
YieldThread(bot, GetReactDelay());
}

// Helper function for UpdateAI to check group membership and handle removal if necessary
Expand Down Expand Up @@ -4339,21 +4339,27 @@ Player* PlayerbotAI::GetGroupLeader()
return master;
}

uint32 PlayerbotAI::GetFixedBotNumer(uint32 maxNum, float cyclePerMin)
uint32 PlayerbotAI::GetFixedBotNumber(uint32 maxNum)
{
uint32 randseed = rand32(); // Seed random number
uint32 randnum = bot->GetGUID().GetCounter() + randseed; // Semi-random but fixed number for each bot.
if (maxNum == 0)
return 0;

if (cyclePerMin > 0)
{
uint32 cycle = floor(getMSTime() / (1000)); // Semi-random number adds 1 each second.
cycle = cycle * cyclePerMin / 60; // Cycles cyclePerMin per minute.
randnum += cycle; // Make the random number cylce.
}
// Deterministic pseudo-random hash based on the bot GUID evenly distributed across active slots
uint32 id = bot->GetGUID().GetCounter();
uint32 h = id;
h ^= h >> 16;
h *= 0x7feb352d;
h ^= h >> 15;
h *= 0x846ca68b;
h ^= h >> 16;

randnum =
(randnum % (maxNum + 1)); // Loops the randomnumber at maxNum. Bassically removes all the numbers above 99.
return randnum; // Now we have a number unique for each bot between 0 and maxNum that increases by cyclePerMin.
// Current time slot
uint32 timeSlot = (getMSTime() / 1000) / sPlayerbotAIConfig.BotActiveAloneDurationSeconds;

// Mix timeSlot into the hash to reshuffle every rotation window
uint32 mixed = h ^ (timeSlot * 0x9e3779b9); // with multiplicative constant

return mixed % maxNum;
}

/*
Expand All @@ -4370,7 +4376,7 @@ enum GrouperType

GrouperType PlayerbotAI::GetGrouperType()
{
uint32 grouperNumber = GetFixedBotNumer(100, 0);
uint32 grouperNumber = GetFixedBotNumber(100);

if (grouperNumber < 20 && !HasRealPlayerMaster())
return GrouperType::SOLO;
Expand All @@ -4392,7 +4398,7 @@ GrouperType PlayerbotAI::GetGrouperType()

GuilderType PlayerbotAI::GetGuilderType()
{
uint32 grouperNumber = GetFixedBotNumer(100, 0);
uint32 grouperNumber = GetFixedBotNumber(100);

if (grouperNumber < 20 && !HasRealPlayerMaster())
return GuilderType::SOLO;
Expand Down Expand Up @@ -4716,44 +4722,40 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
// situations are usable for scaling when enabled.
// #######################################################################################

// Below is code to have a specified % of bots active at all times.
// The default is 100%. With 1% of all bots going active or inactive each minute.
// Base percentage of bots to be active
uint32 mod = sPlayerbotAIConfig.botActiveAlone > 100 ? 100 : sPlayerbotAIConfig.botActiveAlone;

// Apply SmartScale if enabled
if (sPlayerbotAIConfig.botActiveAloneSmartScale &&
bot->GetLevel() >= sPlayerbotAIConfig.botActiveAloneSmartScaleWhenMinLevel &&
bot->GetLevel() <= sPlayerbotAIConfig.botActiveAloneSmartScaleWhenMaxLevel)
{
mod = AutoScaleActivity(mod);
mod = AutoScaleActivity(mod); // mod reflects on latency throttling
}

uint32 ActivityNumber =
GetFixedBotNumer(100, sPlayerbotAIConfig.botActiveAlone * static_cast<float>(mod) / 100 * 0.01f);
// Get deterministic bucket + timeSlot
uint32 ActivityNumber = GetFixedBotNumber(100);

return ActivityNumber <=
(sPlayerbotAIConfig.botActiveAlone * mod) /
100; // The given percentage of bots should be active and rotate 1% of those active bots each minute.
// Check if this bot is in the active set
return ActivityNumber < mod; // mod is directly the number of bots active (0–100)
}

bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
{
const int activityIndex = static_cast<int>(activityType);

// Unknown/out-of-range avoid blocking, added logging for further analysing should not happen in the first place.
if (activityIndex <= 0 || activityIndex >= MAX_ACTIVITY_TYPE)
{
LOG_ERROR("playerbots", "AllowActivity received invalid activity type value: {}", activityIndex);
return true;
}

if (!allowActiveCheckTimer[activityIndex])
allowActiveCheckTimer[activityIndex] = time(nullptr);
allowActiveCheckTimer[activityIndex] = getMSTime();

// 4500ms base + 0–499ms per-bot offset = 4500–4999ms, capping at just under 5 seconds
uint32 offset = bot->GetGUID().GetCounter() % 500;

if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
if (!checkNow && getMSTime() < (allowActiveCheckTimer[activityIndex] + 4500 + offset))
return allowActive[activityIndex];

const bool allowed = AllowActive(activityType);
allowActive[activityIndex] = allowed;
allowActiveCheckTimer[activityIndex] = time(nullptr);
allowActiveCheckTimer[activityIndex] = getMSTime();

return allowed;
}
Expand Down
3 changes: 1 addition & 2 deletions src/Bot/PlayerbotAI.h
Original file line number Diff line number Diff line change
Expand Up @@ -540,8 +540,7 @@ class PlayerbotAI : public PlayerbotAIBase
// Checks if the bot is summoned as alt of a player
bool IsAlt();
Player* GetGroupLeader();
// Returns a semi-random (cycling) number that is fixed for each bot.
uint32 GetFixedBotNumer(uint32 maxNum = 100, float cyclePerMin = 1);
uint32 GetFixedBotNumber(uint32 maxNum = 100);
GrouperType GetGrouperType();
GuilderType GetGuilderType();
bool HasPlayerNearby(WorldPosition* pos, float range = sPlayerbotAIConfig.reactDistance);
Expand Down
5 changes: 3 additions & 2 deletions src/PlayerbotAIConfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -595,11 +595,12 @@ bool PlayerbotAIConfig::Initialize()
randomBotHordeRatio = sConfigMgr->GetOption<int32>("AiPlayerbot.RandomBotHordeRatio", 50);
disableDeathKnightLogin = sConfigMgr->GetOption<bool>("AiPlayerbot.DisableDeathKnightLogin", 0);
limitTalentsExpansion = sConfigMgr->GetOption<bool>("AiPlayerbot.LimitTalentsExpansion", 0);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 100);
botActiveAlone = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAlone", 10);
BotActiveAloneDurationSeconds = sConfigMgr->GetOption<int32>("AiPlayerbot.BotActiveAloneDurationSeconds", 30);
BotActiveAloneForceWhenInRadius = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotActiveAloneForceWhenInRadius", 150);
BotActiveAloneForceWhenInZone = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInZone", 1);
BotActiveAloneForceWhenInMap = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInMap", 0);
BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 1);
BotActiveAloneForceWhenIsFriend = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenIsFriend", 0);
BotActiveAloneForceWhenInGuild = sConfigMgr->GetOption<bool>("AiPlayerbot.BotActiveAloneForceWhenInGuild", 1);
botActiveAloneSmartScale = sConfigMgr->GetOption<bool>("AiPlayerbot.botActiveAloneSmartScale", 1);
botActiveAloneSmartScaleDiffLimitfloor = sConfigMgr->GetOption<uint32>("AiPlayerbot.botActiveAloneSmartScaleDiffLimitfloor", 50);
Expand Down
1 change: 1 addition & 0 deletions src/PlayerbotAIConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ class PlayerbotAIConfig
bool disableDeathKnightLogin;
bool limitTalentsExpansion;
uint32 botActiveAlone;
uint32 BotActiveAloneDurationSeconds;
uint32 BotActiveAloneForceWhenInRadius;
bool BotActiveAloneForceWhenInZone;
bool BotActiveAloneForceWhenInMap;
Expand Down
Loading