Type1Error 95c572bf48
feat(playerbots): staggered taxi take-off for bots (#1281)
* feat(playerbots): staggered taxi take-off for bots

Adds four new configurable settings to playerbots.conf:

- AiPlayerbot.BotTaxiDelayMinMs:   Min random delay before the 1st follower bot clicks the flight-master
- AiPlayerbot.BotTaxiDelayMaxMs:   Upper bound for the overall taxi delay window – larger spreads big raids
- AiPlayerbot.BotTaxiGapMs:        Fixed gap added per group-slot so bots never take off together
- AiPlayerbot.BotTaxiGapJitterMs:  Extra small randomness added to each gap so launches don’t look robotic

These options allow server owners to fine-tune how bots queue up and take off from flight masters, making their behavior appear more natural.

Closes #1017 : Bots use Flight master nearly the same time.

* fixed build errors

Was missing a header and variable declarations.
2025-05-10 13:45:15 +02:00

131 lines
4.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, 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 "TaxiAction.h"
#include "Event.h"
#include "LastMovementValue.h"
#include "Playerbots.h"
#include "PlayerbotAIConfig.h"
#include "Config.h"
bool TaxiAction::Execute(Event event)
{
botAI->RemoveShapeshift();
LastMovement& movement = context->GetValue<LastMovement&>("last taxi")->Get();
WorldPacket& p = event.getPacket();
std::string const param = event.getParam();
if ((!p.empty() && (p.GetOpcode() == CMSG_TAXICLEARALLNODES || p.GetOpcode() == CMSG_TAXICLEARNODE)) ||
param == "clear")
{
movement.taxiNodes.clear();
movement.Set(nullptr);
botAI->TellMaster("I am ready for the next flight");
return true;
}
GuidVector units = *context->GetValue<GuidVector>("nearest npcs");
for (ObjectGuid const guid : units)
{
Creature* npc = bot->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_FLIGHTMASTER);
if (!npc)
continue;
uint32 curloc = sObjectMgr->GetNearestTaxiNode(npc->GetPositionX(), npc->GetPositionY(), npc->GetPositionZ(),
npc->GetMapId(), bot->GetTeamId());
std::vector<uint32> nodes;
for (uint32 i = 0; i < sTaxiPathStore.GetNumRows(); ++i)
{
if (TaxiPathEntry const* entry = sTaxiPathStore.LookupEntry(i))
if (entry->from == curloc)
{
uint8 field = uint8((i - 1) / 32);
if (field < TaxiMaskSize)
nodes.push_back(i);
}
}
// stagger bot takeoff
uint32 delayMin = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMinMs", 350u, false);
uint32 delayMax = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiDelayMaxMs", 5000u, false);
uint32 gapMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapMs", 200u, false);
uint32 gapJitterMs = sConfigMgr->GetOption<uint32>("AiPlayerbot.BotTaxiGapJitterMs", 100u, false);
// Only for follower bots
if (botAI->HasRealPlayerMaster())
{
uint32 index = botAI->GetGroupSlotIndex(bot);
uint32 delay = delayMin + index * gapMs + urand(0, gapJitterMs);
delay = std::min(delay, delayMax);
// Store the npcs GUID so we can re-acquire the pointer later
ObjectGuid npcGuid = npc->GetGUID();
// schedule the take-off
botAI->AddTimedEvent(
[bot = bot, &movement, npcGuid]() -> void
{
if (Creature* npcPtr = ObjectAccessor::GetCreature(*bot, npcGuid))
if (!movement.taxiNodes.empty())
bot->ActivateTaxiPathTo(movement.taxiNodes, npcPtr);
},
delay);
botAI->SetNextCheckDelay(delay + 50);
return true;
}
if (param == "?")
{
botAI->TellMasterNoFacing("=== Taxi ===");
uint32 index = 1;
for (uint32 node : nodes)
{
TaxiPathEntry const* entry = sTaxiPathStore.LookupEntry(node);
if (!entry)
continue;
TaxiNodesEntry const* dest = sTaxiNodesStore.LookupEntry(entry->to);
if (!dest)
continue;
std::ostringstream out;
out << index++ << ": " << dest->name[0];
botAI->TellMasterNoFacing(out.str());
}
return true;
}
uint32 selected = atoi(param.c_str());
if (selected)
{
uint32 path = nodes[selected - 1];
TaxiPathEntry const* entry = sTaxiPathStore.LookupEntry(path);
if (!entry)
return false;
return bot->ActivateTaxiPathTo({entry->from, entry->to}, npc, 0);
}
if (!movement.taxiNodes.empty() && !bot->ActivateTaxiPathTo(movement.taxiNodes, npc))
{
movement.taxiNodes.clear();
movement.Set(nullptr);
botAI->TellError("I can't fly with you");
return false;
}
return true;
}
botAI->TellError("Cannot find any flightmaster to talk");
return false;
}