Compare commits

...

7 Commits

Author SHA1 Message Date
bashermens
5c9451b73f Quickfix, XP gain feature has huge performance impact 2025-12-25 02:00:40 +01:00
privatecore
88016789ba Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK (#1937)
**Original issue:**
https://github.com/mod-playerbots/mod-playerbots/issues/1902
**Original cause:** charmed bot (with lost client control) got rooted at
the same time.

**How to reproduce:**
1. Spawn creatures 11350 (x3) and 11830 (x3) using the command: `.npc
add <entry>` in a quiet place.
2. Create any party with random bots or alt bots (should be 60-65
levels), make sure there is at least one healer.
3. Set the healer's mana to a high value, like 100M, using command:
`.mod mana <value>`.
4. Start the fight while continuously respawning creatures with: `.resp
all`.
5. When console starts to spam 'heartbeat' messages, check your party
members' movement flags to identify which one has `MOVEMENTFLAG_ROOT`
0x00000800 (2048) using the command: `.debug move`.

This PR will not fix ALL 'heartbeat' issues, as
`ServerFacade::SetFacingTo` still sends `SendMovementFlagUpdate` while
bots can have the `MOVEMENTFLAG_ROOT` flag.
2025-12-25 00:01:42 +01:00
bashermens
6be860c967 [Stability] Various crash fixes based on Regrad fixes and crashlogs. (#1928)
These contains various fixes, fixes that have history worked one in past
more then once as person as group, aswell @Wishmaster117. But due
various reasons we had to drop them due priority or simply timewise.
These fixes have recollected again by @Regrad based on his crash logs.

Most crash logs we have, i am talking 30+ of them, to many to post here.
@Regrad running a larger server 100+ real players with bots, which means
he will walk into issues that most of us wont or are extremely difficult
to reproduce.

@Regrad used LLM to solve them based on crash log and mentioned his
server crashes almost disappeared, instead of redoing every single PR
and pull them apart. I tried to keep his bunch of changes together as
whole, reviewed them, some redone, verified again etc etc. This is not
how would normally do this. But since i want @Regrad being able to
confirm, we need this in a package as a whole. Pulling them apart in the
current situation is simply to much, to complicated in the verification
process.

So this PR is open and in my opinion has priority above others, but
@Regrad is only person who can give the green light for the
mod-playerbot changes for now.

I, we spend huge amount of time into these issues over last couple of
months. I will put other PR's on hold for abit.

---------

Signed-off-by: Engardium <regradius@gmail.com>
Co-authored-by: Engardium <regradius@gmail.com>
2025-12-24 13:24:29 +01:00
Alex Dcnh
9971622093 Core - Fixe raid markers persists after target dead causes issue with bots running off (#1845)
This PR fixes #1833 where bots could keep chasing a raid target icon
(usually skull) across very large distances or even different maps after
the mark was set. It replaces the previous attempt with a simpler design
that keeps values generic and moves context logic into triggers/actions.

Changes

- RtiTargetValue now ignores RTI targets that are farther than
sPlayerbotAIConfig->sightDistance from the bot or the master (same map
only), while still using AttackersValue::IsValidTarget and LOS checks.
- AttackersValue is reverted to its original behavior (no special RTI +
IsInCombat logic).
- RTI triggers only react to "rti target" in combat, as suggested in
review (“that condition should be check in trigger…”).
- AttackRtiTargetAction keeps a small local fallback: if "rti target" is
null, it resolves the raid icon directly from the group so chat commands
like “attack rti target” still work, including out of combat.

Behavior

- Bots no longer run across the world to chase a stale skull far away
from the group.
- When the group comes back near the marked mob (within sight distance),
the skull is again used as a normal focus hint.
- Automatic RTI behavior is limited to combat via triggers, while
explicit chat commands still work out of combat.

Testing

- Marked a mob with skull, killed it, moved far away / changed zone,
then let it respawn: bots did not chase it from afar.
- In a normal dungeon/raid pack, bots still focus skull in combat.
- Passing near a previously marked mob: bots only react once it is back
within sight distance.

---------

Co-authored-by: Keleborn <22352763+Celandriel@users.noreply.github.com>
2025-12-23 20:42:29 +01:00
Crow
895df9b197 Magtheridon timer keys fix (#1936)
Same deal as Karazhan--changing map id to instance id for timer keys to
prevent conflicts from multiple groups (plus other tweaks with respect
to the timers). This strategy could use a broader refactor, but that's
for another day--this PR is just to address the timer logic.
2025-12-23 09:04:36 +01:00
Crow
467b63b840 Fix Karazhan timers (#1926)
I'm not 100% sure but think that the current implementation of timers
for Attumen, Netherspite, and Nightbane with the instance's map id as
the key would result in the timers being shared across separate raids
running the instance at the same time. I've refactored to use the
instance id as the key for all shared timers so to the extent this was
an issue, it shouldn't be anymore.

I also made some minor tweaks to the tank positioning for BBW and
Curator as I noticed there was some weirdness in the logic that I
neglected to address with the refactor.
2025-12-21 21:40:19 +01:00
HennyWilly
66f5f597bb Re-enable no-xp-feature with better checks (#1935)
The feature `Disable bot XP gain when master has XP turned off` (#1910)
was unintentionally disabled in #1929.
This PR re-enables it with better checks in order to prevent crashes
when playing together with the individual progression module.
2025-12-21 21:28:39 +01:00
25 changed files with 724 additions and 565 deletions

View File

@@ -242,8 +242,8 @@ void PlayerbotAI::UpdateAI(uint32 elapsed, bool minimal)
nextAICheckDelay = 0;
// Early return if bot is in invalid state
if (!bot || !bot->IsInWorld() || !bot->GetSession() || bot->GetSession()->isLogingOut() ||
bot->IsDuringRemoveFromWorld())
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
return;
// Handle cheat options (set bot health and power if cheats are enabled)
@@ -713,39 +713,59 @@ void PlayerbotAI::HandleTeleportAck()
if (IsRealPlayer())
return;
// Clearing motion generators and stopping movement which prevents
// conflicts between teleport and any active motion (walk, run, swim, flight, etc.)
bot->GetMotionMaster()->Clear(true);
bot->StopMoving();
// Near teleport (within map/instance)
if (bot->IsBeingTeleportedNear())
{
// Temporary fix for instance can not enter
if (!bot->IsInWorld())
{
bot->GetMap()->AddPlayerToMap(bot);
}
while (bot->IsInWorld() && bot->IsBeingTeleportedNear())
{
Player* plMover = bot->m_mover->ToPlayer();
if (!plMover)
return;
WorldPacket p = WorldPacket(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << (uint32)0; // supposed to be flags? not used currently
p << (uint32)0; // time - not currently used
bot->GetSession()->HandleMoveTeleportAck(p);
};
// Previous versions manually added the bot to the map if it was not in the world.
// not needed: HandleMoveTeleportAckOpcode() safely attaches the player to the map
// and clears IsBeingTeleportedNear().
Player* plMover = bot->m_mover->ToPlayer();
if (!plMover)
return;
// Send the near teleport ACK packet
WorldPacket p(MSG_MOVE_TELEPORT_ACK, 20);
p << plMover->GetPackGUID();
p << uint32(0);
p << uint32(0);
bot->GetSession()->HandleMoveTeleportAck(p);
// Simulate teleport latency and prevent AI from running too early (used cmangos delays)
SetNextCheckDelay(urand(1000, 2000));
}
// Far teleport (worldport / different map)
if (bot->IsBeingTeleportedFar())
{
while (bot->IsBeingTeleportedFar())
// Handle far teleport ACK:
// Moves the bot to the new map, clears IsBeingTeleportedFar(), updates session/map references
bot->GetSession()->HandleMoveWorldportAck();
// Ensure bot now has a valid map. If this fails, there is a core/session bug?
if (!bot->GetMap())
{
bot->GetSession()->HandleMoveWorldportAck();
LOG_ERROR("playerbot", "Bot {} has no map after worldport ACK", bot->GetGUID().ToString());
}
// SetNextCheckDelay(urand(2000, 5000));
// Instance strategies after teleport
if (sPlayerbotAIConfig->applyInstanceStrategies)
ApplyInstanceStrategies(bot->GetMapId(), true);
// healer DPS strategies if restrictions are enabled
if (sPlayerbotAIConfig->restrictHealerDPS)
EvaluateHealerDpsStrategy();
// Reset AI state to to before teleport conditions
Reset(true);
// Slightly longer delay to simulate far teleport latency (used cmangos delays)
SetNextCheckDelay(urand(2000, 5000));
}
SetNextCheckDelay(sPlayerbotAIConfig->globalCoolDown);
@@ -988,10 +1008,10 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
{
if (packet.empty())
return;
if (!bot || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
{
return;
}
switch (packet.GetOpcode())
{
case SMSG_SPELL_FAILURE:
@@ -1159,7 +1179,26 @@ void PlayerbotAI::HandleBotOutgoingPacket(WorldPacket const& packet)
return;
}
case SMSG_MOVE_KNOCK_BACK: // handle knockbacks
case SMSG_FORCE_MOVE_ROOT: // CMSG_FORCE_MOVE_ROOT_ACK
case SMSG_FORCE_MOVE_UNROOT: // CMSG_FORCE_MOVE_UNROOT_ACK
{
// Quick fix for CMSG_FORCE_MOVE_ROOT_ACK and CMSG_FORCE_MOVE_UNROOT_ACK:
// this should resolve issues with MOVEMENTFLAG_ROOT being permanently set
// when rooted during lost client control (charm + root effects)
// @see https://github.com/azerothcore/azerothcore-wotlk/pull/23147
bool forceRoot = (packet.GetOpcode() == SMSG_FORCE_MOVE_ROOT);
if (forceRoot)
{
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_MASK_MOVING_FLY);
bot->m_movementInfo.AddMovementFlag(MOVEMENTFLAG_ROOT);
bot->StopMoving();
}
else
bot->m_movementInfo.RemoveMovementFlag(MOVEMENTFLAG_ROOT);
return;
}
case SMSG_MOVE_KNOCK_BACK: // CMSG_MOVE_KNOCK_BACK_ACK
{
WorldPacket p(packet);
p.rpos(0);
@@ -1331,10 +1370,6 @@ void PlayerbotAI::DoNextAction(bool min)
bool isBotAlive = bot->IsAlive();
if (currentEngine != engines[BOT_STATE_DEAD] && !isBotAlive)
{
bot->StopMoving();
bot->GetMotionMaster()->Clear();
bot->GetMotionMaster()->MoveIdle();
// Death Count to prevent skeleton piles
// Player* master = GetMaster(); // warning here - whipowill
if (!HasActivePlayerMaster() && !bot->InBattleground())
@@ -1355,6 +1390,8 @@ void PlayerbotAI::DoNextAction(bool min)
// Change engine if just ressed
if (currentEngine == engines[BOT_STATE_DEAD] && isBotAlive)
{
bot->SendMovementFlagUpdate();
ChangeEngine(BOT_STATE_NON_COMBAT);
return;
}
@@ -4103,8 +4140,7 @@ Player* PlayerbotAI::FindNewMaster()
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (!member || member == bot || !member->IsInWorld() ||
!member->IsInSameRaidWith(bot))
if (!member || member == bot || !member->IsInWorld() || !member->IsInSameRaidWith(bot))
continue;
PlayerbotAI* memberBotAI = GET_PLAYERBOT_AI(member);
@@ -4339,6 +4375,11 @@ inline bool ZoneHasRealPlayers(Player* bot)
bool PlayerbotAI::AllowActive(ActivityType activityType)
{
// Early return if bot is in invalid state
if (!bot || !bot->GetSession() || !bot->IsInWorld() || bot->IsBeingTeleported() ||
bot->GetSession()->isLogingOut() || bot->IsDuringRemoveFromWorld())
return false;
// when botActiveAlone is 100% and smartScale disabled
if (sPlayerbotAIConfig->botActiveAlone >= 100 && !sPlayerbotAIConfig->botActiveAloneSmartScale)
{
@@ -4429,10 +4470,8 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if ((!member || !member->IsInWorld()) && member->GetMapId() != bot->GetMapId())
{
if (!member || !member->IsInWorld() || member->GetMapId() != bot->GetMapId())
continue;
}
if (member == bot)
{
@@ -4483,23 +4522,23 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
// HasFriend
if (sPlayerbotAIConfig->BotActiveAloneForceWhenIsFriend)
{
if (!bot || !bot->IsInWorld() || !bot->GetGUID())
// shouldnt be needed analyse in future
if (!bot->GetGUID())
return false;
for (auto& player : sRandomPlayerbotMgr->GetPlayers())
{
if (!player || !player->IsInWorld())
if (!player || !player->GetSession() || !player->IsInWorld() || player->IsDuringRemoveFromWorld() ||
player->GetSession()->isLogingOut())
continue;
Player* connectedPlayer = ObjectAccessor::FindPlayer(player->GetGUID());
if (!connectedPlayer)
PlayerbotAI* playerAI = GET_PLAYERBOT_AI(player);
if (!playerAI || !playerAI->IsRealPlayer())
continue;
// if a real player has the bot as a friend
PlayerSocial* social = player->GetSocial();
if (!social)
continue;
if (social->HasFriend(bot->GetGUID()))
if (social && social->HasFriend(bot->GetGUID()))
return true;
}
}
@@ -4513,7 +4552,7 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
}
}
// Bots don't need to move using PathGenerator.
// Bots don't need react to PathGenerator activities
if (activityType == DETAILED_MOVE_ACTIVITY)
{
return false;
@@ -4549,15 +4588,25 @@ bool PlayerbotAI::AllowActive(ActivityType activityType)
bool PlayerbotAI::AllowActivity(ActivityType activityType, bool checkNow)
{
if (!allowActiveCheckTimer[activityType])
allowActiveCheckTimer[activityType] = time(nullptr);
const int activityIndex = static_cast<int>(activityType);
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityType] + 5))
return allowActive[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);
if (!checkNow && time(nullptr) < (allowActiveCheckTimer[activityIndex] + 5))
return allowActive[activityIndex];
const bool allowed = AllowActive(activityType);
allowActive[activityIndex] = allowed;
allowActiveCheckTimer[activityIndex] = time(nullptr);
bool allowed = AllowActive(activityType);
allowActive[activityType] = allowed;
allowActiveCheckTimer[activityType] = time(nullptr);
return allowed;
}
@@ -5341,15 +5390,13 @@ Item* PlayerbotAI::FindStoneFor(Item* weapon) const
if (!item_template)
return nullptr;
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE
};
static const std::vector<uint32_t> uPrioritizedSharpStoneIds = {
ADAMANTITE_SHARPENING_STONE, FEL_SHARPENING_STONE, ELEMENTAL_SHARPENING_STONE, DENSE_SHARPENING_STONE,
SOLID_SHARPENING_STONE, HEAVY_SHARPENING_STONE, COARSE_SHARPENING_STONE, ROUGH_SHARPENING_STONE};
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE
};
static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
ADAMANTITE_WEIGHTSTONE, FEL_WEIGHTSTONE, DENSE_WEIGHTSTONE, SOLID_WEIGHTSTONE,
HEAVY_WEIGHTSTONE, COARSE_WEIGHTSTONE, ROUGH_WEIGHTSTONE};
Item* stone = nullptr;
ItemTemplate const* pProto = weapon->GetTemplate();
@@ -5385,7 +5432,6 @@ static const std::vector<uint32_t> uPrioritizedWeightStoneIds = {
Item* PlayerbotAI::FindOilFor(Item* weapon) const
{
if (!weapon)
return nullptr;
@@ -5394,12 +5440,12 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
return nullptr;
static const std::vector<uint32_t> uPrioritizedWizardOilIds = {
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL,
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL};
static const std::vector<uint32_t> uPrioritizedManaOilIds = {
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL,
BRILLIANT_WIZARD_OIL, SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
BRILLIANT_MANA_OIL, SUPERIOR_MANA_OIL, LESSER_MANA_OIL, MINOR_MANA_OIL, BRILLIANT_WIZARD_OIL,
SUPERIOR_WIZARD_OIL, WIZARD_OIL, LESSER_WIZARD_OIL, MINOR_WIZARD_OIL};
Item* oil = nullptr;
int botClass = bot->getClass();
@@ -5415,22 +5461,22 @@ Item* PlayerbotAI::FindOilFor(Item* weapon) const
prioritizedOils = &uPrioritizedWizardOilIds;
break;
case CLASS_DRUID:
if (specTab == 0) // Balance
if (specTab == 0) // Balance
prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 1) // Feral
else if (specTab == 1) // Feral
prioritizedOils = nullptr;
else // Restoration (specTab == 2) or any other/unspecified spec
else // Restoration (specTab == 2) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds;
break;
case CLASS_HUNTER:
prioritizedOils = &uPrioritizedManaOilIds;
break;
case CLASS_PALADIN:
if (specTab == 1) // Protection
if (specTab == 1) // Protection
prioritizedOils = &uPrioritizedWizardOilIds;
else if (specTab == 2) // Retribution
else if (specTab == 2) // Retribution
prioritizedOils = nullptr;
else // Holy (specTab == 0) or any other/unspecified spec
else // Holy (specTab == 0) or any other/unspecified spec
prioritizedOils = &uPrioritizedManaOilIds;
break;
default:

View File

@@ -9,9 +9,10 @@
#include <cstring>
#include <istream>
#include <string>
#include <openssl/sha.h>
#include <unordered_set>
#include <openssl/sha.h>
#include <iomanip>
#include <algorithm>
#include "ChannelMgr.h"
#include "CharacterCache.h"
@@ -34,12 +35,9 @@
#include "RandomPlayerbotMgr.h"
#include "SharedDefines.h"
#include "WorldSession.h"
#include "ChannelMgr.h"
#include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#include "WorldSessionMgr.h"
#include "DatabaseEnv.h" // Added for gender choice
#include <algorithm> // Added for gender choice
#include "DatabaseEnv.h"
class BotInitGuard
{
@@ -68,6 +66,7 @@ private:
};
std::unordered_set<ObjectGuid> BotInitGuard::botsBeingInitialized;
std::unordered_set<ObjectGuid> PlayerbotHolder::botLoading;
PlayerbotHolder::PlayerbotHolder() : PlayerbotAIBase(false) {}
class PlayerbotLoginQueryHolder : public LoginQueryHolder
@@ -76,13 +75,12 @@ private:
uint32 masterAccountId;
PlayerbotHolder* playerbotHolder;
public:
PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount), playerbotHolder(playerbotHolder)
PlayerbotLoginQueryHolder(uint32 masterAccount, uint32 accountId, ObjectGuid guid)
: LoginQueryHolder(accountId, guid), masterAccountId(masterAccount)
{
}
uint32 GetMasterAccountId() const { return masterAccountId; }
PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; }
};
void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId)
@@ -143,7 +141,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
return;
}
std::shared_ptr<PlayerbotLoginQueryHolder> holder =
std::make_shared<PlayerbotLoginQueryHolder>(this, masterAccountId, accountId, playerGuid);
std::make_shared<PlayerbotLoginQueryHolder>(masterAccountId, accountId, playerGuid);
if (!holder->Initialize())
{
return;
@@ -153,8 +151,27 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
// Always login in with world session to avoid race condition
sWorld->AddQueryHolderCallback(CharacterDatabase.DelayQueryHolder(holder))
.AfterComplete([this](SQLQueryHolderBase const& holder)
{ HandlePlayerBotLoginCallback(static_cast<PlayerbotLoginQueryHolder const&>(holder)); });
.AfterComplete(
[](SQLQueryHolderBase const& queryHolder)
{
PlayerbotLoginQueryHolder const& holder = static_cast<PlayerbotLoginQueryHolder const&>(queryHolder);
PlayerbotHolder* mgr = sRandomPlayerbotMgr; // could be null
uint32 masterAccountId = holder.GetMasterAccountId();
if (masterAccountId)
{
// verify and find current world session of master
WorldSession* masterSession = sWorldSessionMgr->FindSession(masterAccountId);
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterPlayer)
mgr = GET_PLAYERBOT_MGR(masterPlayer);
}
if (mgr)
mgr->HandlePlayerBotLoginCallback(holder);
else
PlayerbotHolder::botLoading.erase(holder.GetGuid());
});
}
bool PlayerbotHolder::IsAccountLinked(uint32 accountId, uint32 linkedAccountId)
@@ -169,8 +186,9 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
uint32 botAccountId = holder.GetAccountId();
// At login DBC locale should be what the server is set to use by default (as spells etc are hardcoded to ENUS this
// allows channels to work as intended)
WorldSession* botSession = new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING,
time_t(0), sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
WorldSession* botSession =
new WorldSession(botAccountId, "", 0x0, nullptr, SEC_PLAYER, EXPANSION_WRATH_OF_THE_LICH_KING, time_t(0),
sWorld->GetDefaultDbcLocale(), 0, false, false, 0, true);
botSession->HandlePlayerLoginFromDB(holder); // will delete lqh
@@ -181,26 +199,27 @@ void PlayerbotHolder::HandlePlayerBotLoginCallback(PlayerbotLoginQueryHolder con
LOG_DEBUG("mod-playerbots", "Bot player could not be loaded for account ID: {}", botAccountId);
botSession->LogoutPlayer(true);
delete botSession;
botLoading.erase(holder.GetGuid());
PlayerbotHolder::botLoading.erase(holder.GetGuid());
return;
}
uint32 masterAccount = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccount ? sWorldSessionMgr->FindSession(masterAccount) : nullptr;
uint32 masterAccountId = holder.GetMasterAccountId();
WorldSession* masterSession = masterAccountId ? sWorldSessionMgr->FindSession(masterAccountId) : nullptr;
// Check if masterSession->GetPlayer() is valid
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterSession && !masterPlayer)
{
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}", masterAccount);
LOG_DEBUG("mod-playerbots", "Master session found but no player is associated for master account ID: {}",
masterAccountId);
}
sRandomPlayerbotMgr->OnPlayerLogin(bot);
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), this);
auto op = std::make_unique<OnBotLoginOperation>(bot->GetGUID(), masterAccountId);
sPlayerbotWorldProcessor->QueueOperation(std::move(op));
botLoading.erase(holder.GetGuid());
PlayerbotHolder::botLoading.erase(holder.GetGuid());
}
void PlayerbotHolder::UpdateSessions()

View File

@@ -60,7 +60,7 @@ protected:
virtual void OnBotLoginInternal(Player* const bot) = 0;
PlayerBotMap playerBots;
std::unordered_set<ObjectGuid> botLoading;
static std::unordered_set<ObjectGuid> botLoading;
};
class PlayerbotMgr : public PlayerbotHolder

View File

@@ -9,6 +9,7 @@
#include "Group.h"
#include "GroupMgr.h"
#include "GuildMgr.h"
#include "Playerbots.h"
#include "ObjectAccessor.h"
#include "PlayerbotOperation.h"
#include "Player.h"
@@ -16,6 +17,8 @@
#include "PlayerbotMgr.h"
#include "PlayerbotDbStore.h"
#include "RandomPlayerbotMgr.h"
#include "WorldSession.h"
#include "WorldSessionMgr.h"
// Group invite operation
class GroupInviteOperation : public PlayerbotOperation
@@ -468,18 +471,31 @@ private:
class OnBotLoginOperation : public PlayerbotOperation
{
public:
OnBotLoginOperation(ObjectGuid botGuid, PlayerbotHolder* holder)
: m_botGuid(botGuid), m_holder(holder)
OnBotLoginOperation(ObjectGuid botGuid, uint32 masterAccountId)
: m_botGuid(botGuid), m_masterAccountId(masterAccountId)
{
}
bool Execute() override
{
// find and verify bot still exists
Player* bot = ObjectAccessor::FindConnectedPlayer(m_botGuid);
if (!bot || !m_holder)
if (!bot)
return false;
m_holder->OnBotLogin(bot);
PlayerbotHolder* holder = sRandomPlayerbotMgr;
if (m_masterAccountId)
{
WorldSession* masterSession = sWorldSessionMgr->FindSession(m_masterAccountId);
Player* masterPlayer = masterSession ? masterSession->GetPlayer() : nullptr;
if (masterPlayer)
holder = GET_PLAYERBOT_MGR(masterPlayer);
}
if (!holder)
return false;
holder->OnBotLogin(bot);
return true;
}
@@ -487,14 +503,11 @@ public:
uint32 GetPriority() const override { return 100; }
std::string GetName() const override { return "OnBotLogin"; }
bool IsValid() const override
{
return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr;
}
bool IsValid() const override { return ObjectAccessor::FindConnectedPlayer(m_botGuid) != nullptr; }
private:
ObjectGuid m_botGuid;
PlayerbotHolder* m_holder;
uint32 m_masterAccountId = 0;
};
#endif

View File

@@ -17,14 +17,28 @@ PlayerbotSecurity::PlayerbotSecurity(Player* const bot) : bot(bot)
PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* reason, bool ignoreGroup)
{
// Basic pointer validity checks
if (!bot || !from || !from->GetSession())
{
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_DENY_ALL;
}
// GMs always have full access
if (from->GetSession()->GetSecurity() >= SEC_GAMEMASTER)
return PLAYERBOT_SECURITY_ALLOW_ALL;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
{
if (reason)
*reason = PLAYERBOT_DENY_NONE;
return PLAYERBOT_SECURITY_DENY_ALL;
}
if (botAI->IsOpposing(from))
{
if (reason)
@@ -35,6 +49,7 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
if (sPlayerbotAIConfig->IsInRandomAccountList(account))
{
// (duplicate check in case of faction change)
if (botAI->IsOpposing(from))
{
if (reason)
@@ -43,27 +58,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_DENY_ALL;
}
// if (sLFGMgr->GetState(bot->GetGUID()) != lfg::LFG_STATE_NONE)
// {
// if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
// {
// if (reason)
// *reason = PLAYERBOT_DENY_LFG;
Group* fromGroup = from->GetGroup();
Group* botGroup = bot->GetGroup();
// return PLAYERBOT_SECURITY_TALK;
// }
// }
Group* group = from->GetGroup();
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() == from)
if (fromGroup && botGroup && fromGroup == botGroup && !ignoreGroup)
{
return PLAYERBOT_SECURITY_ALLOW_ALL;
}
if (botAI->GetMaster() == from)
return PLAYERBOT_SECURITY_ALLOW_ALL;
if (group && group == bot->GetGroup() && !ignoreGroup && botAI->GetMaster() != from)
{
if (reason)
*reason = PLAYERBOT_DENY_NOT_YOURS;
return PLAYERBOT_SECURITY_TALK;
}
@@ -75,27 +80,34 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK;
}
if (sPlayerbotAIConfig->groupInvitationPermission <= 1 && (int32)bot->GetLevel() - (int8)from->GetLevel() > 5)
if (sPlayerbotAIConfig->groupInvitationPermission <= 1)
{
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
int32 levelDiff = int32(bot->GetLevel()) - int32(from->GetLevel());
if (levelDiff > 5)
{
if (reason)
*reason = PLAYERBOT_DENY_LOW_LEVEL;
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
{
if (reason)
*reason = PLAYERBOT_DENY_LOW_LEVEL;
return PLAYERBOT_SECURITY_TALK;
return PLAYERBOT_SECURITY_TALK;
}
}
}
int32 botGS = (int32)botAI->GetEquipGearScore(bot/*, false, false*/);
int32 fromGS = (int32)botAI->GetEquipGearScore(from/*, false, false*/);
if (sPlayerbotAIConfig->gearscorecheck)
int32 botGS = static_cast<int32>(botAI->GetEquipGearScore(bot));
int32 fromGS = static_cast<int32>(botAI->GetEquipGearScore(from));
if (sPlayerbotAIConfig->gearscorecheck && botGS && bot->GetLevel() > 15 && botGS > fromGS)
{
if (botGS && bot->GetLevel() > 15 && botGS > fromGS &&
static_cast<float>(100 * (botGS - fromGS) / botGS) >=
static_cast<float>(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel()))
uint32 diffPct = uint32(100 * (botGS - fromGS) / botGS);
uint32 reqPct = uint32(12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel());
if (diffPct >= reqPct)
{
if (reason)
*reason = PLAYERBOT_DENY_GEARSCORE;
return PLAYERBOT_SECURITY_TALK;
}
}
@@ -111,35 +123,17 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
}
}
/*if (bot->isDead())
// If the bot is not in the group, we offer an invite
botGroup = bot->GetGroup();
if (!botGroup)
{
if (reason)
*reason = PLAYERBOT_DENY_DEAD;
return PLAYERBOT_SECURITY_TALK;
}*/
group = bot->GetGroup();
if (!group)
{
/*if (bot->GetMapId() != from->GetMapId() || bot->GetDistance(from) > sPlayerbotAIConfig->whisperDistance)
{
if (!bot->GetGuildId() || bot->GetGuildId() != from->GetGuildId())
{
if (reason)
*reason = PLAYERBOT_DENY_FAR;
return PLAYERBOT_SECURITY_TALK;
}
}*/
if (reason)
*reason = PLAYERBOT_DENY_INVITE;
return PLAYERBOT_SECURITY_INVITE;
}
if (!ignoreGroup && group->IsFull())
if (!ignoreGroup && botGroup->IsFull())
{
if (reason)
*reason = PLAYERBOT_DENY_FULL_GROUP;
@@ -147,27 +141,22 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
return PLAYERBOT_SECURITY_TALK;
}
if (!ignoreGroup && group->GetLeaderGUID() != bot->GetGUID())
if (!ignoreGroup && botGroup->GetLeaderGUID() != bot->GetGUID())
{
if (reason)
*reason = PLAYERBOT_DENY_NOT_LEADER;
return PLAYERBOT_SECURITY_TALK;
}
else
{
if (reason)
*reason = PLAYERBOT_DENY_IS_LEADER;
return PLAYERBOT_SECURITY_INVITE;
}
// The bot is the group leader, you can invite the initiator
if (reason)
*reason = PLAYERBOT_DENY_INVITE;
*reason = PLAYERBOT_DENY_IS_LEADER;
return PLAYERBOT_SECURITY_INVITE;
}
// Non-random bots: only their master has full access
if (botAI->GetMaster() == from)
return PLAYERBOT_SECURITY_ALLOW_ALL;
@@ -179,8 +168,13 @@ PlayerbotSecurityLevel PlayerbotSecurity::LevelFor(Player* from, DenyReason* rea
bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent, Player* from, bool ignoreGroup)
{
// If something is wrong with the pointers, we silently refuse
if (!bot || !from || !from->GetSession())
return false;
DenyReason reason = PLAYERBOT_DENY_NONE;
PlayerbotSecurityLevel realLevel = LevelFor(from, &reason, ignoreGroup);
if (realLevel >= level || from == bot)
return true;
@@ -189,11 +183,17 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
Player* master = botAI->GetMaster();
if (master && botAI && botAI->IsOpposing(master) && master->GetSession()->GetSecurity() < SEC_GAMEMASTER)
if (!botAI)
return false;
Player* master = botAI->GetMaster();
if (master && botAI->IsOpposing(master))
if (WorldSession* session = master->GetSession())
if (session->GetSecurity() < SEC_GAMEMASTER)
return false;
std::ostringstream out;
switch (realLevel)
{
case PLAYERBOT_SECURITY_DENY_ALL:
@@ -206,19 +206,20 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
out << "I'll do it later";
break;
case PLAYERBOT_DENY_LOW_LEVEL:
out << "You are too low level: |cffff0000" << (uint32)from->GetLevel() << "|cffffffff/|cff00ff00"
<< (uint32)bot->GetLevel();
out << "You are too low level: |cffff0000" << uint32(from->GetLevel()) << "|cffffffff/|cff00ff00"
<< uint32(bot->GetLevel());
break;
case PLAYERBOT_DENY_GEARSCORE:
{
int botGS = (int)botAI->GetEquipGearScore(bot/*, false, false*/);
int fromGS = (int)botAI->GetEquipGearScore(from/*, false, false*/);
int botGS = int(botAI->GetEquipGearScore(bot));
int fromGS = int(botAI->GetEquipGearScore(from));
int diff = (100 * (botGS - fromGS) / botGS);
int req = 12 * sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL) / from->GetLevel();
out << "Your gearscore is too low: |cffff0000" << fromGS << "|cffffffff/|cff00ff00" << botGS
<< " |cffff0000" << diff << "%|cffffffff/|cff00ff00" << req << "%";
break;
}
break;
case PLAYERBOT_DENY_NOT_YOURS:
out << "I have a master already";
break;
@@ -237,13 +238,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
case PLAYERBOT_DENY_FAR:
{
out << "You must be closer to invite me to your group. I am in ";
if (AreaTableEntry const* entry = sAreaTableStore.LookupEntry(bot->GetAreaId()))
{
out << " |cffffffff(|cffff0000" << entry->area_name[0] << "|cffffffff)";
}
break;
}
break;
case PLAYERBOT_DENY_FULL_GROUP:
out << "I am in a full group. Will do it later";
break;
@@ -251,15 +249,10 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
out << "I am currently leading a group. I can invite you if you want.";
break;
case PLAYERBOT_DENY_NOT_LEADER:
if (botAI->GetGroupLeader())
{
out << "I am in a group with " << botAI->GetGroupLeader()->GetName()
<< ". You can ask him for invite.";
}
if (Player* leader = botAI->GetGroupLeader())
out << "I am in a group with " << leader->GetName() << ". You can ask him for invite.";
else
{
out << "I am in a group with someone else. You can ask him for invite.";
}
break;
case PLAYERBOT_DENY_BG:
out << "I am in a queue for BG. Will do it later";
@@ -283,10 +276,14 @@ bool PlayerbotSecurity::CheckLevelFor(PlayerbotSecurityLevel level, bool silent,
std::string const text = out.str();
ObjectGuid guid = from->GetGUID();
time_t lastSaid = whispers[guid][text];
if (!lastSaid || (time(nullptr) - lastSaid) >= sPlayerbotAIConfig->repeatDelay / 1000)
{
whispers[guid][text] = time(nullptr);
bot->Whisper(text, LANG_UNIVERSAL, from);
// Additional protection against crashes during logout
if (bot->IsInWorld() && from->IsInWorld())
bot->Whisper(text, LANG_UNIVERSAL, from);
}
return false;

View File

@@ -124,24 +124,49 @@ public:
}
}
bool OnPlayerBeforeTeleport(Player* player, uint32 mapid, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
bool OnPlayerBeforeTeleport(Player* /*player*/, uint32 /*mapid*/, float /*x*/, float /*y*/, float /*z*/, float /*orientation*/, uint32 /*options*/, Unit* /*target*/) override
{
// Only apply to bots to prevent affecting real players
if (!player || !player->GetSession()->IsBot())
/* for now commmented out until proven its actually required
* havent seen any proof CleanVisibilityReferences() is needed
// If the player is not safe to touch, do nothing
if (!player)
return true;
// If changing maps, proactively clean visibility references to prevent
// stale pointers in other players' visibility maps during the teleport.
// This fixes a race condition where:
// 1. Bot A teleports and its visible objects start getting cleaned up
// 2. Bot B is simultaneously updating visibility and tries to access objects in Bot A's old visibility map
// 3. Those objects may already be freed, causing a segmentation fault
if (player->GetMapId() != mapid && player->IsInWorld())
{
player->GetObjectVisibilityContainer().CleanVisibilityReferences();
}
// If same map or not in world do nothing
if (!player->IsInWorld() || player->GetMapId() == mapid)
return true;
return true; // Allow teleport to continue
// If real player do nothing
PlayerbotAI* ai = GET_PLAYERBOT_AI(player);
if (!ai || ai->IsRealPlayer())
return true;
// Cross-map bot teleport: defer visibility reference cleanup.
// CleanVisibilityReferences() erases this bot's GUID from other objects' visibility containers.
// This is intentionally done via the event queue (instead of directly here) because erasing
// from other players' visibility maps inside the teleport call stack can hit unsafe re-entrancy
// or iterator invalidation while visibility updates are in progress
ObjectGuid guid = player->GetGUID();
player->m_Events.AddEventAtOffset(
[guid, mapid]()
{
// do nothing, if the player is not safe to touch
Player* p = ObjectAccessor::FindPlayer(guid);
if (!p || !p->IsInWorld() || p->IsDuringRemoveFromWorld())
return;
// do nothing if we are already on the target map
if (p->GetMapId() == mapid)
return;
p->GetObjectVisibilityContainer().CleanVisibilityReferences();
},
Milliseconds(0));
*/
return true;
}
void OnPlayerAfterUpdate(Player* player, uint32 diff) override
@@ -239,39 +264,34 @@ public:
void OnPlayerGiveXP(Player* player, uint32& amount, Unit* /*victim*/, uint8 /*xpSource*/) override
{
if (sPlayerbotAIConfig->randomBotXPRate == 1.0f || !player || !player->IsInWorld())
// early return
if (sPlayerbotAIConfig->randomBotXPRate == 1.0 || !player)
return;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(player);
if (!botAI || !sRandomPlayerbotMgr->IsRandomBot(player))
// no XP multiplier, when player is no bot.
if (!player->GetSession()->IsBot() || !sRandomPlayerbotMgr->IsRandomBot(player))
return;
// No XP gain if master is a real player with XP gain disabled
if (const Player* master = botAI->GetMaster())
{
if (WorldSession* masterSession = master->GetSession();
masterSession && !masterSession->IsBot() && master->HasPlayerFlag(PLAYER_FLAGS_NO_XP_GAIN))
{
amount = 0; // disable XP multiplier
return;
}
}
// No XP multiplier if bot is in a group with at least one real player
// no XP multiplier, when bot is in a group with a real player.
if (Group* group = player->GetGroup())
{
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
if (Player* member = gref->GetSource())
Player* member = gref->GetSource();
if (!member)
{
if (!member->GetSession()->IsBot())
return;
continue;
}
if (!member->GetSession()->IsBot())
{
return;
}
}
}
// Otherwise apply XP multiplier
amount = static_cast<uint32>(std::round(amount * sPlayerbotAIConfig->randomBotXPRate));
// otherwise apply bot XP multiplier.
amount = static_cast<uint32>(std::round(static_cast<float>(amount) * sPlayerbotAIConfig->randomBotXPRate));
}
};

View File

@@ -26,7 +26,6 @@
#include "DatabaseEnv.h"
#include "Define.h"
#include "FleeManager.h"
#include "GameTime.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "GuildMgr.h"
@@ -670,9 +669,9 @@ void RandomPlayerbotMgr::AssignAccountTypes()
uint32 toAssign = neededAddClassAccounts - existingAddClassAccounts;
uint32 assigned = 0;
for (int i = allRandomBotAccounts.size() - 1; i >= 0 && assigned < toAssign; i--)
for (size_t idx = allRandomBotAccounts.size(); idx-- > 0 && assigned < toAssign;)
{
uint32 accountId = allRandomBotAccounts[i];
uint32 accountId = allRandomBotAccounts[idx];
if (currentAssignments[accountId] == 0) // Unassigned
{
PlayerbotsDatabase.Execute("UPDATE playerbots_account_type SET account_type = 2, assignment_date = NOW() WHERE account_id = {}", accountId);
@@ -1425,7 +1424,7 @@ bool RandomPlayerbotMgr::ProcessBot(uint32 bot)
LOG_DEBUG("playerbots", "Bot #{}: log out", bot);
SetEventValue(bot, "add", 0, 0);
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
currentBots.remove(bot);
if (player)
LogoutPlayerBot(botGUID);
@@ -2709,69 +2708,73 @@ std::vector<uint32> RandomPlayerbotMgr::GetBgBots(uint32 bracket)
return std::move(BgBots);
}
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const event)
CachedEvent* RandomPlayerbotMgr::FindEvent(uint32 bot, std::string const& event)
{
// load all events at once on first event load
if (eventCache[bot].empty())
BotEventCache& cache = eventCache[bot];
// Load once
if (!cache.loaded)
{
cache.events.clear();
PlayerbotsDatabasePreparedStatement* stmt =
PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_SEL_RANDOM_BOTS_BY_OWNER_AND_BOT);
stmt->SetData(0, 0);
stmt->SetData(1, bot);
if (PreparedQueryResult result = PlayerbotsDatabase.Query(stmt))
{
do
{
Field* fields = result->Fetch();
std::string const eventName = fields[0].Get<std::string>();
CachedEvent e;
e.value = fields[1].Get<uint32>();
e.lastChangeTime = fields[2].Get<uint32>();
e.validIn = fields[3].Get<uint32>();
e.data = fields[4].Get<std::string>();
eventCache[bot][eventName] = std::move(e);
cache.events.emplace(fields[0].Get<std::string>(), std::move(e));
} while (result->NextRow());
}
cache.loaded = true;
}
CachedEvent& e = eventCache[bot][event];
/*if (e.IsEmpty())
auto it = cache.events.find(event);
if (it == cache.events.end())
return nullptr;
CachedEvent& e = it->second;
// remove expired events
if (e.validIn && (NowSeconds() - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
{
QueryResult results = PlayerbotsDatabase.Query("SELECT `value`, `time`, validIn, `data` FROM
playerbots_random_bots WHERE owner = 0 AND bot = {} AND event = {}", bot, event.c_str());
if (results)
{
Field* fields = results->Fetch();
e.value = fields[0].Get<uint32>();
e.lastChangeTime = fields[1].Get<uint32>();
e.validIn = fields[2].Get<uint32>();
e.data = fields[3].Get<std::string>();
}
cache.events.erase(it);
return nullptr;
}
*/
if ((time(0) - e.lastChangeTime) >= e.validIn && event != "specNo" && event != "specLink")
e.value = 0;
return e.value;
return &e;
}
std::string const RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const event)
uint32 RandomPlayerbotMgr::GetEventValue(uint32 bot, std::string const& event)
{
std::string data = "";
if (GetEventValue(bot, event))
{
CachedEvent e = eventCache[bot][event];
data = e.data;
}
if (CachedEvent* e = FindEvent(bot, event))
return e->value;
return data;
return 0;
}
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
std::string const data)
std::string RandomPlayerbotMgr::GetEventData(uint32 bot, std::string const& event)
{
if (CachedEvent* e = FindEvent(bot, event))
return e->data;
return "";
}
uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
std::string const& data)
{
PlayerbotsDatabaseTransaction trans = PlayerbotsDatabase.BeginTransaction();
@@ -2787,43 +2790,55 @@ uint32 RandomPlayerbotMgr::SetEventValue(uint32 bot, std::string const event, ui
stmt = PlayerbotsDatabase.GetPreparedStatement(PLAYERBOTS_INS_RANDOM_BOTS);
stmt->SetData(0, 0);
stmt->SetData(1, bot);
stmt->SetData(2, static_cast<uint32>(GameTime::GetGameTime().count()));
stmt->SetData(2, NowSeconds());
stmt->SetData(3, validIn);
stmt->SetData(4, event.c_str());
stmt->SetData(5, value);
if (data != "")
{
if (!data.empty())
stmt->SetData(6, data.c_str());
}
else
{
stmt->SetData(6);
}
stmt->SetData(6); // NULL
trans->Append(stmt);
}
PlayerbotsDatabase.CommitTransaction(trans);
CachedEvent e(value, (uint32)time(nullptr), validIn, data);
eventCache[bot][event] = std::move(e);
// Update in-memory cache
BotEventCache& cache = eventCache[bot];
cache.loaded = true;
if (!value)
{
cache.events.erase(event);
return 0;
}
CachedEvent& e = cache.events[event]; // create-on-write is OK here
e.value = value;
e.lastChangeTime = NowSeconds();
e.validIn = validIn;
e.data = data;
return value;
}
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const type) { return GetEventValue(bot, type); }
uint32 RandomPlayerbotMgr::GetValue(uint32 bot, std::string const& type) { return GetEventValue(bot, type); }
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const type)
uint32 RandomPlayerbotMgr::GetValue(Player* bot, std::string const& type)
{
return GetValue(bot->GetGUID().GetCounter(), type);
}
std::string const RandomPlayerbotMgr::GetData(uint32 bot, std::string const type) { return GetEventData(bot, type); }
std::string RandomPlayerbotMgr::GetData(uint32 bot, std::string const& type) { return GetEventData(bot, type); }
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const type, uint32 value, std::string const data)
void RandomPlayerbotMgr::SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data)
{
SetEventValue(bot, type, value, sPlayerbotAIConfig->maxRandomBotInWorldTime, data);
}
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const type, uint32 value, std::string const data)
void RandomPlayerbotMgr::SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data)
{
SetValue(bot->GetGUID().GetCounter(), type, value, data);
}
@@ -3112,7 +3127,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
void RandomPlayerbotMgr::OnPlayerLoginError(uint32 bot)
{
SetEventValue(bot, "add", 0, 0);
currentBots.erase(std::remove(currentBots.begin(), currentBots.end(), bot), currentBots.end());
currentBots.remove(bot);
}
Player* RandomPlayerbotMgr::GetRandomPlayer()
@@ -3494,7 +3509,8 @@ void RandomPlayerbotMgr::Remove(Player* bot)
stmt->SetData(1, owner.GetCounter());
PlayerbotsDatabase.Execute(stmt);
eventCache[owner.GetCounter()].clear();
uint32 botId = owner.GetCounter();
eventCache.erase(botId);
LogoutPlayerBot(owner);
}
@@ -3511,7 +3527,7 @@ CreatureData const* RandomPlayerbotMgr::GetCreatureDataByEntry(uint32 entry)
return nullptr;
}
ObjectGuid const RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
ObjectGuid RandomPlayerbotMgr::GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId)
{
ObjectGuid battleMasterGUID = ObjectGuid::Empty;

View File

@@ -9,6 +9,7 @@
#include "NewRpgInfo.h"
#include "ObjectGuid.h"
#include "PlayerbotMgr.h"
#include "GameTime.h"
struct BattlegroundInfo
{
@@ -45,25 +46,20 @@ class ChatHandler;
class PerformanceMonitorOperation;
class WorldLocation;
class CachedEvent
struct CachedEvent
{
public:
CachedEvent() : value(0), lastChangeTime(0), validIn(0), data("") {}
CachedEvent(const CachedEvent& other)
: value(other.value), lastChangeTime(other.lastChangeTime), validIn(other.validIn), data(other.data)
{
}
CachedEvent(uint32 value, uint32 lastChangeTime, uint32 validIn, std::string const data = "")
: value(value), lastChangeTime(lastChangeTime), validIn(validIn), data(data)
{
}
bool IsEmpty() { return !lastChangeTime; }
uint32 value;
uint32 lastChangeTime;
uint32 validIn;
uint32 value = 0;
uint32 lastChangeTime = 0;
uint32 validIn = 0;
std::string data;
bool IsEmpty() const { return !lastChangeTime; }
};
struct BotEventCache
{
bool loaded = false;
std::unordered_map<std::string, CachedEvent> events;
};
// https://gist.github.com/bradley219/5373998
@@ -139,13 +135,13 @@ public:
void Revive(Player* player);
void ChangeStrategy(Player* player);
void ChangeStrategyOnce(Player* player);
uint32 GetValue(Player* bot, std::string const type);
uint32 GetValue(uint32 bot, std::string const type);
std::string const GetData(uint32 bot, std::string const type);
void SetValue(uint32 bot, std::string const type, uint32 value, std::string const data = "");
void SetValue(Player* bot, std::string const type, uint32 value, std::string const data = "");
uint32 GetValue(Player* bot, std::string const& type);
uint32 GetValue(uint32 bot, std::string const& type);
std::string GetData(uint32 bot, std::string const& type);
void SetValue(uint32 bot, std::string const& type, uint32 value, std::string const& data = "");
void SetValue(Player* bot, std::string const& type, uint32 value, std::string const& data = "");
void Remove(Player* bot);
ObjectGuid const GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
ObjectGuid GetBattleMasterGUID(Player* bot, BattlegroundTypeId bgTypeId);
CreatureData const* GetCreatureDataByEntry(uint32 entry);
void LoadBattleMastersCache();
std::map<uint32, std::map<uint32, BattlegroundInfo>> BattlegroundData;
@@ -203,10 +199,11 @@ private:
bool _isBotInitializing = true;
bool _isBotLogging = true;
NewRpgStatistic rpgStasticTotal;
uint32 GetEventValue(uint32 bot, std::string const event);
std::string const GetEventData(uint32 bot, std::string const event);
uint32 SetEventValue(uint32 bot, std::string const event, uint32 value, uint32 validIn,
std::string const data = "");
CachedEvent* FindEvent(uint32 bot, std::string const& event);
uint32 GetEventValue(uint32 bot, std::string const& event);
std::string GetEventData(uint32 bot, std::string const& event);
uint32 SetEventValue(uint32 bot, std::string const& event, uint32 value, uint32 validIn,
std::string const& data = "");
void GetBots();
std::vector<uint32> GetBgBots(uint32 bracket);
time_t BgCheckTimer;
@@ -228,7 +225,7 @@ private:
// std::map<uint32, std::vector<WorldLocation>> rpgLocsCache;
std::map<uint32, std::map<uint32, std::vector<WorldLocation>>> rpgLocsCacheLevel;
std::map<TeamId, std::map<BattlegroundTypeId, std::vector<uint32>>> BattleMastersCache;
std::map<uint32, std::map<std::string, CachedEvent>> eventCache;
std::unordered_map<uint32, BotEventCache> eventCache;
std::list<uint32> currentBots;
uint32 bgBotsCount;
uint32 playersLevel;
@@ -238,6 +235,7 @@ private:
std::vector<uint32> addClassTypeAccounts; // Accounts marked as AddClass (type 2)
//void ScaleBotActivity(); // Deprecated function
static inline uint32 NowSeconds() { return static_cast<uint32>(GameTime::GetGameTime().count()); }
};
#define sRandomPlayerbotMgr RandomPlayerbotMgr::instance()

View File

@@ -23,7 +23,5 @@ bool AcceptResurrectAction::Execute(Event event)
packet << uint8(1); // accept
bot->GetSession()->HandleResurrectResponseOpcode(packet); // queue the packet to get around race condition
botAI->ChangeEngine(BOT_STATE_NON_COMBAT);
return true;
}

View File

@@ -224,42 +224,36 @@ bool BuyAction::Execute(Event event)
bool BuyAction::BuyItem(VendorItemData const* tItems, ObjectGuid vendorguid, ItemTemplate const* proto)
{
uint32 oldCount = AI_VALUE2(uint32, "item count", proto->Name1);
if (!tItems)
if (!tItems || !proto)
return false;
uint32 itemId = proto->ItemId;
for (uint32 slot = 0; slot < tItems->GetItemCount(); slot++)
uint32 oldCount = bot->GetItemCount(itemId, false);
for (uint32 slot = 0; slot < tItems->GetItemCount(); ++slot)
{
if (tItems->GetItem(slot)->item == itemId)
if (tItems->GetItem(slot)->item != itemId)
continue;
uint32 botMoney = bot->GetMoney();
if (botAI->HasCheat(BotCheatMask::gold))
bot->SetMoney(10000000);
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
if (botAI->HasCheat(BotCheatMask::gold))
bot->SetMoney(botMoney);
uint32 newCount = bot->GetItemCount(itemId, false);
if (newCount > oldCount)
{
uint32 botMoney = bot->GetMoney();
if (botAI->HasCheat(BotCheatMask::gold))
{
bot->SetMoney(10000000);
}
bot->BuyItemFromVendorSlot(vendorguid, slot, itemId, 1, NULL_BAG, NULL_SLOT);
if (botAI->HasCheat(BotCheatMask::gold))
{
bot->SetMoney(botMoney);
}
if (oldCount <
AI_VALUE2(
uint32, "item count",
proto->Name1)) // BuyItem Always returns false (unless unique) so we have to check the item counts.
{
std::ostringstream out;
out << "Buying " << ChatHelper::FormatItem(proto);
botAI->TellMaster(out.str());
return true;
}
return false;
std::ostringstream out;
out << "Buying " << ChatHelper::FormatItem(proto);
botAI->TellMaster(out.str());
return true;
}
return false;
}
return false;

View File

@@ -10,6 +10,7 @@
#include "LootObjectStack.h"
#include "NewRpgStrategy.h"
#include "Playerbots.h"
#include "RtiTargetValue.h"
#include "PossibleRpgTargetsValue.h"
#include "PvpTriggers.h"
#include "ServerFacade.h"
@@ -87,9 +88,7 @@ bool DropTargetAction::Execute(Event event)
{
Spell const* spell = bot->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL); // Get the current spell being cast by the bot
if (spell && spell->m_spellInfo->Id == 75) //Check spell is not nullptr before accessing m_spellInfo
{
bot->InterruptSpell(CURRENT_AUTOREPEAT_SPELL); // Interrupt Auto Shot
}
}
bot->AttackStop();
@@ -142,6 +141,23 @@ bool AttackRtiTargetAction::Execute(Event event)
{
Unit* rtiTarget = AI_VALUE(Unit*, "rti target");
// Fallback: if the "rti target" value did not resolve a valid unit yet,
// try to resolve the raid icon directly from the group.
if (!rtiTarget)
{
if (Group* group = bot->GetGroup())
{
std::string const rti = AI_VALUE(std::string, "rti");
int32 const index = RtiTargetValue::GetRtiIndex(rti);
if (index >= 0)
{
ObjectGuid const guid = group->GetTargetIcon(index);
if (!guid.IsEmpty())
rtiTarget = botAI->GetUnit(guid);
}
}
}
if (rtiTarget && rtiTarget->IsInWorld() && rtiTarget->GetMapId() == bot->GetMapId())
{
botAI->GetAiObjectContext()->GetValue<GuidVector>("prioritized targets")->Set({rtiTarget->GetGUID()});
@@ -153,9 +169,7 @@ bool AttackRtiTargetAction::Execute(Event event)
}
}
else
{
botAI->TellError("I dont see my rti attack target");
}
return false;
}

View File

@@ -3,8 +3,11 @@
* and/or modify it under version 3 of the License, or (at your option), any later version.
*/
#include "ReadyCheckAction.h"
#include <memory>
#include <mutex>
#include <vector>
#include "ReadyCheckAction.h"
#include "Event.h"
#include "Playerbots.h"
@@ -27,14 +30,17 @@ std::string const formatPercent(std::string const name, uint8 value, float perce
class ReadyChecker
{
public:
virtual ~ReadyChecker() = default;
virtual bool Check(PlayerbotAI* botAI, AiObjectContext* context) = 0;
virtual std::string const getName() = 0;
virtual bool PrintAlways() { return true; }
static std::vector<ReadyChecker*> checkers;
static std::vector<std::unique_ptr<ReadyChecker>> checkers;
static std::once_flag initFlag;
};
std::vector<ReadyChecker*> ReadyChecker::checkers;
std::vector<std::unique_ptr<ReadyChecker>> ReadyChecker::checkers;
std::once_flag ReadyChecker::initFlag;
class HealthChecker : public ReadyChecker
{
@@ -160,25 +166,30 @@ bool ReadyCheckAction::Execute(Event event)
bool ReadyCheckAction::ReadyCheck()
{
if (ReadyChecker::checkers.empty())
{
ReadyChecker::checkers.push_back(new HealthChecker());
ReadyChecker::checkers.push_back(new ManaChecker());
ReadyChecker::checkers.push_back(new DistanceChecker());
ReadyChecker::checkers.push_back(new HunterChecker());
std::call_once(
ReadyChecker::initFlag,
[]()
{
ReadyChecker::checkers.reserve(8);
ReadyChecker::checkers.push_back(new ItemCountChecker("food", "Food"));
ReadyChecker::checkers.push_back(new ManaPotionChecker("drink", "Water"));
ReadyChecker::checkers.push_back(new ItemCountChecker("healing potion", "Hpot"));
ReadyChecker::checkers.push_back(new ManaPotionChecker("mana potion", "Mpot"));
}
ReadyChecker::checkers.emplace_back(std::make_unique<HealthChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<ManaChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<DistanceChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<HunterChecker>());
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("food", "Food"));
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("drink", "Water"));
ReadyChecker::checkers.emplace_back(std::make_unique<ItemCountChecker>("healing potion", "Hpot"));
ReadyChecker::checkers.emplace_back(std::make_unique<ManaPotionChecker>("mana potion", "Mpot"));
});
bool result = true;
for (std::vector<ReadyChecker*>::iterator i = ReadyChecker::checkers.begin(); i != ReadyChecker::checkers.end();
++i)
for (auto const& checkerPtr : ReadyChecker::checkers)
{
ReadyChecker* checker = *i;
bool ok = checker->Check(botAI, context);
if (!checkerPtr)
continue;
bool ok = checkerPtr->Check(botAI, context);
result = result && ok;
}

View File

@@ -44,7 +44,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
Unit* attumenMounted = GetFirstAliveUnitByEntry(botAI, NPC_ATTUMEN_THE_HUNTSMAN_MOUNTED);
if (attumenMounted)
{
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithStar(bot, attumenMounted);
SetRtiTarget(botAI, "star", attumenMounted);
@@ -57,7 +57,7 @@ bool AttumenTheHuntsmanMarkTargetAction::Execute(Event event)
}
else if (Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight"))
{
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithStar(bot, midnight);
if (!botAI->IsAssistTankOfIndex(bot, 0))
@@ -131,8 +131,10 @@ bool AttumenTheHuntsmanManageDpsTimerAction::Execute(Event event)
if (!midnight)
return false;
const uint32 instanceId = midnight->GetMap()->GetInstanceId();
if (midnight && midnight->GetHealth() == midnight->GetMaxHealth())
attumenDpsWaitTimer.erase(KARAZHAN_MAP_ID);
attumenDpsWaitTimer.erase(instanceId);
// Midnight is still present as a separate (invisible) unit after Attumen mounts
// So this block can be reached
@@ -143,7 +145,7 @@ bool AttumenTheHuntsmanManageDpsTimerAction::Execute(Event event)
const time_t now = std::time(nullptr);
if (attumenMounted)
attumenDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now);
attumenDpsWaitTimer.try_emplace(instanceId, now);
return false;
}
@@ -178,7 +180,7 @@ bool MoroesMarkTargetAction::Execute(Event event)
if (target)
{
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithSkull(bot, target);
SetRtiTarget(botAI, "skull", target);
@@ -297,19 +299,18 @@ bool BigBadWolfPositionBossAction::Execute(Event event)
if (wolf->GetVictim() == bot)
{
const Position& position = BIG_BAD_WOLF_BOSS_POSITION;
const float maxStep = 2.0f;
float dist = wolf->GetExactDist2d(position);
float distanceToPosition = wolf->GetExactDist2d(position);
if (dist > 0.0f && dist > maxStep)
if (distanceToPosition > 2.0f)
{
float dX = position.GetPositionX() - wolf->GetPositionX();
float dY = position.GetPositionY() - wolf->GetPositionY();
float moveDist = std::min(maxStep, dist);
float moveX = wolf->GetPositionX() + (dX / dist) * moveDist;
float moveY = wolf->GetPositionY() + (dY / dist) * moveDist;
float moveDist = std::min(5.0f, distanceToPosition);
float moveX = wolf->GetPositionX() + (dX / distanceToPosition) * moveDist;
float moveY = wolf->GetPositionY() + (dY / distanceToPosition) * moveDist;
return MoveTo(KARAZHAN_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
MovementPriority::MOVEMENT_COMBAT, true, true);
}
}
@@ -404,7 +405,7 @@ bool TheCuratorMarkAstralFlareAction::Execute(Event event)
if (!flare)
return false;
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
MarkTargetWithSkull(bot, flare);
SetRtiTarget(botAI, "skull", flare);
@@ -429,17 +430,17 @@ bool TheCuratorPositionBossAction::Execute(Event event)
if (curator->GetVictim() == bot)
{
const Position& position = THE_CURATOR_BOSS_POSITION;
const float maxDistance = 3.0f;
float distanceToBossPosition = curator->GetExactDist2d(position);
float distanceToPosition = curator->GetExactDist2d(position);
if (distanceToBossPosition > maxDistance)
if (distanceToPosition > 2.0f)
{
float dX = position.GetPositionX() - curator->GetPositionX();
float dY = position.GetPositionY() - curator->GetPositionY();
float mX = position.GetPositionX() + (dX / distanceToBossPosition) * maxDistance;
float mY = position.GetPositionY() + (dY / distanceToBossPosition) * maxDistance;
float moveDist = std::min(10.0f, distanceToPosition);
float moveX = position.GetPositionX() + (dX / distanceToPosition) * moveDist;
float moveY = position.GetPositionY() + (dY / distanceToPosition) * moveDist;
return MoveTo(KARAZHAN_MAP_ID, mX, mY, position.GetPositionZ(), false, false, false, false,
return MoveTo(KARAZHAN_MAP_ID, moveX, moveY, position.GetPositionZ(), false, false, false, false,
MovementPriority::MOVEMENT_COMBAT, true, false);
}
}
@@ -997,6 +998,7 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
if (!netherspite)
return false;
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
const ObjectGuid botGuid = bot->GetGUID();
const time_t now = std::time(nullptr);
@@ -1005,8 +1007,8 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
if (netherspite->GetHealth() == netherspite->GetMaxHealth() &&
!netherspite->HasAura(SPELL_GREEN_BEAM_HEAL))
{
if (IsMapIDTimerManager(botAI, bot))
netherspiteDpsWaitTimer.insert_or_assign(KARAZHAN_MAP_ID, now);
if (IsInstanceTimerManager(botAI, bot))
netherspiteDpsWaitTimer.insert_or_assign(instanceId, now);
if (botAI->IsTank(bot) && !bot->HasAura(SPELL_RED_BEAM_DEBUFF))
{
@@ -1016,8 +1018,8 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
}
else if (netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{
if (IsMapIDTimerManager(botAI, bot))
netherspiteDpsWaitTimer.erase(KARAZHAN_MAP_ID);
if (IsInstanceTimerManager(botAI, bot))
netherspiteDpsWaitTimer.erase(instanceId);
if (botAI->IsTank(bot))
{
@@ -1027,8 +1029,8 @@ bool NetherspiteManageTimersAndTrackersAction::Execute(Event event)
}
else if (!netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
{
if (IsMapIDTimerManager(botAI, bot))
netherspiteDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now);
if (IsInstanceTimerManager(botAI, bot))
netherspiteDpsWaitTimer.try_emplace(instanceId, now);
if (botAI->IsTank(bot) && bot->HasAura(SPELL_RED_BEAM_DEBUFF))
{
@@ -1443,6 +1445,7 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (!nightbane)
return false;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const ObjectGuid botGuid = bot->GetGUID();
const time_t now = std::time(nullptr);
@@ -1455,18 +1458,18 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid);
if (IsMapIDTimerManager(botAI, bot))
nightbaneDpsWaitTimer.erase(KARAZHAN_MAP_ID);
if (IsInstanceTimerManager(botAI, bot))
nightbaneDpsWaitTimer.erase(instanceId);
}
// Erase flight phase timer and Rain of Bones tracker on ground phase and start DPS wait timer
else if (nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
{
nightbaneRainOfBonesHit.erase(botGuid);
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
{
nightbaneFlightPhaseStartTimer.erase(KARAZHAN_MAP_ID);
nightbaneDpsWaitTimer.try_emplace(KARAZHAN_MAP_ID, now);
nightbaneFlightPhaseStartTimer.erase(instanceId);
nightbaneDpsWaitTimer.try_emplace(instanceId, now);
}
}
// Erase DPS wait timer and tank and ranged position tracking and start flight phase timer
@@ -1479,10 +1482,10 @@ bool NightbaneManageTimersAndTrackersAction::Execute(Event event)
if (botAI->IsRanged(bot))
nightbaneRangedStep.erase(botGuid);
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
{
nightbaneDpsWaitTimer.erase(KARAZHAN_MAP_ID);
nightbaneFlightPhaseStartTimer.try_emplace(KARAZHAN_MAP_ID, now);
nightbaneDpsWaitTimer.erase(instanceId);
nightbaneFlightPhaseStartTimer.try_emplace(instanceId, now);
}
}

View File

@@ -105,8 +105,8 @@ namespace KarazhanHelpers
}
}
// Only one bot is needed to set/reset mapwide timers
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot)
// Only one bot is needed to set/reset instance-wide timers
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{

View File

@@ -112,7 +112,7 @@ namespace KarazhanHelpers
void MarkTargetWithCircle(Player* bot, Unit* target);
void MarkTargetWithMoon(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
Unit* GetFirstAliveUnit(const std::vector<Unit*>& units);
Unit* GetFirstAliveUnitByEntry(PlayerbotAI* botAI, uint32 entry);
Unit* GetNearestPlayerInRadius(Player* bot, float radius);

View File

@@ -61,10 +61,11 @@ float AttumenTheHuntsmanWaitForDpsMultiplier::GetValue(Action* action)
if (!attumenMounted)
return 1.0f;
const uint32 instanceId = attumenMounted->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = attumenDpsWaitTimer.find(KARAZHAN_MAP_ID);
auto it = attumenDpsWaitTimer.find(instanceId);
if (it == attumenDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot))
@@ -201,10 +202,11 @@ float NetherspiteWaitForDpsMultiplier::GetValue(Action* action)
if (!netherspite || netherspite->HasAura(SPELL_NETHERSPITE_BANISHED))
return 1.0f;
const uint32 instanceId = netherspite->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 5;
auto it = netherspiteDpsWaitTimer.find(KARAZHAN_MAP_ID);
auto it = netherspiteDpsWaitTimer.find(instanceId);
if (it == netherspiteDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsTank(bot))
@@ -299,10 +301,11 @@ float NightbaneWaitForDpsMultiplier::GetValue(Action* action)
if (!nightbane || nightbane->GetPositionZ() > NIGHTBANE_FLIGHT_Z)
return 1.0f;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 dpsWaitSeconds = 8;
auto it = nightbaneDpsWaitTimer.find(KARAZHAN_MAP_ID);
auto it = nightbaneDpsWaitTimer.find(instanceId);
if (it == nightbaneDpsWaitTimer.end() || (now - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot))

View File

@@ -40,7 +40,7 @@ bool AttumenTheHuntsmanAttumenIsMountedTrigger::IsActive()
bool AttumenTheHuntsmanBossWipesAggroWhenMountingTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* midnight = AI_VALUE2(Unit*, "find target", "midnight");
@@ -110,7 +110,7 @@ bool BigBadWolfBossIsChasingLittleRedRidingHoodTrigger::IsActive()
bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* romulo = AI_VALUE2(Unit*, "find target", "romulo");
@@ -126,7 +126,7 @@ bool RomuloAndJulianneBothBossesRevivedTrigger::IsActive()
bool WizardOfOzNeedTargetPriorityTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* dorothee = AI_VALUE2(Unit*, "find target", "dorothee");
@@ -178,7 +178,7 @@ bool TheCuratorBossAstralFlaresCastArcingSearTrigger::IsActive()
bool TerestianIllhoofNeedTargetPriorityTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* illhoof = AI_VALUE2(Unit*, "find target", "terestian illhoof");
@@ -202,7 +202,7 @@ bool ShadeOfAranFlameWreathIsActiveTrigger::IsActive()
// Exclusion of Banish is so the player may Banish elementals if they wish
bool ShadeOfAranConjuredElementalsSummonedTrigger::IsActive()
{
if (!IsMapIDTimerManager(botAI, bot))
if (!IsInstanceTimerManager(botAI, bot))
return false;
Unit* elemental = AI_VALUE2(Unit*, "find target", "conjured elemental");
@@ -279,7 +279,7 @@ bool NetherspiteBossIsBanishedTrigger::IsActive()
bool NetherspiteNeedToManageTimersAndTrackersTrigger::IsActive()
{
if (!botAI->IsTank(bot) && !IsMapIDTimerManager(botAI, bot))
if (!botAI->IsTank(bot) && !IsInstanceTimerManager(botAI, bot))
return false;
Unit* netherspite = AI_VALUE2(Unit*, "find target", "netherspite");
@@ -370,11 +370,12 @@ bool NightbaneBossIsFlyingTrigger::IsActive()
if (!nightbane || nightbane->GetPositionZ() <= NIGHTBANE_FLIGHT_Z)
return false;
const uint32 instanceId = nightbane->GetMap()->GetInstanceId();
const time_t now = std::time(nullptr);
const uint8 flightPhaseDurationSeconds = 35;
return nightbaneFlightPhaseStartTimer.find(KARAZHAN_MAP_ID) != nightbaneFlightPhaseStartTimer.end() &&
(now - nightbaneFlightPhaseStartTimer[KARAZHAN_MAP_ID] < flightPhaseDurationSeconds);
return nightbaneFlightPhaseStartTimer.find(instanceId) != nightbaneFlightPhaseStartTimer.end() &&
(now - nightbaneFlightPhaseStartTimer[instanceId] < flightPhaseDurationSeconds);
}
bool NightbaneNeedToManageTimersAndTrackersTrigger::IsActive()

View File

@@ -401,14 +401,20 @@ std::unordered_map<ObjectGuid, bool> MagtheridonSpreadRangedAction::hasReachedIn
bool MagtheridonSpreadRangedAction::Execute(Event event)
{
Unit* magtheridon = AI_VALUE2(Unit*, "find target", "magtheridon");
if (!magtheridon)
return false;
Group* group = bot->GetGroup();
if (!group)
return false;
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
// Wait for 6 seconds after Magtheridon activates to spread
const uint8 spreadWaitSeconds = 6;
auto it = magtheridonSpreadWaitTimer.find(bot->GetMapId());
if (it == magtheridonSpreadWaitTimer.end() ||
auto it = spreadWaitTimer.find(instanceId);
if (it == spreadWaitTimer.end() ||
(time(nullptr) - it->second) < spreadWaitSeconds)
return false;
@@ -416,8 +422,8 @@ bool MagtheridonSpreadRangedAction::Execute(Event event)
if (cubeIt != botToCubeAssignment.end())
{
time_t now = time(nullptr);
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
if (timerIt != magtheridonBlastNovaTimer.end())
auto timerIt = blastNovaTimer.find(instanceId);
if (timerIt != blastNovaTimer.end())
{
time_t lastBlastNova = timerIt->second;
if (now - lastBlastNova >= 49)
@@ -559,8 +565,8 @@ bool MagtheridonUseManticronCubeAction::HandleCubeRelease(Unit* magtheridon, Gam
bool MagtheridonUseManticronCubeAction::ShouldActivateCubeLogic(Unit* magtheridon)
{
auto timerIt = magtheridonBlastNovaTimer.find(bot->GetMapId());
if (timerIt == magtheridonBlastNovaTimer.end())
auto timerIt = blastNovaTimer.find(magtheridon->GetMap()->GetInstanceId());
if (timerIt == blastNovaTimer.end())
return false;
time_t now = time(nullptr);
@@ -650,52 +656,38 @@ bool MagtheridonManageTimersAndAssignmentsAction::Execute(Event event)
if (!magtheridon)
return false;
uint32 mapId = magtheridon->GetMapId();
const uint32 instanceId = magtheridon->GetMap()->GetInstanceId();
const time_t now = time(nullptr);
bool blastNovaActive = magtheridon->HasUnitState(UNIT_STATE_CASTING) &&
magtheridon->FindCurrentSpellBySpellId(SPELL_BLAST_NOVA);
bool lastBlastNova = lastBlastNovaState[mapId];
bool lastBlastNova = lastBlastNovaState[instanceId];
if (lastBlastNova && !blastNovaActive && IsMapIDTimerManager(botAI, bot))
magtheridonBlastNovaTimer[mapId] = time(nullptr);
lastBlastNovaState[mapId] = blastNovaActive;
if (lastBlastNova && !blastNovaActive && IsInstanceTimerManager(botAI, bot))
blastNovaTimer[instanceId] = now;
if (IsMapIDTimerManager(botAI, bot))
lastBlastNovaState[instanceId] = blastNovaActive;
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
{
if (!magtheridon->HasAura(SPELL_SHADOW_CAGE))
if (IsInstanceTimerManager(botAI, bot))
{
if (magtheridonSpreadWaitTimer.find(mapId) == magtheridonSpreadWaitTimer.end())
magtheridonSpreadWaitTimer[mapId] = time(nullptr);
if (magtheridonBlastNovaTimer.find(mapId) == magtheridonBlastNovaTimer.end())
magtheridonBlastNovaTimer[mapId] = time(nullptr);
if (magtheridonAggroWaitTimer.find(mapId) == magtheridonAggroWaitTimer.end())
magtheridonAggroWaitTimer[mapId] = time(nullptr);
spreadWaitTimer.try_emplace(instanceId, now);
blastNovaTimer.try_emplace(instanceId, now);
dpsWaitTimer.try_emplace(instanceId, now);
}
}
if (magtheridon->HasAura(SPELL_SHADOW_CAGE))
else
{
if (!MagtheridonSpreadRangedAction::initialPositions.empty())
MagtheridonSpreadRangedAction::initialPositions.clear();
MagtheridonSpreadRangedAction::initialPositions.clear();
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
botToCubeAssignment.clear();
if (!MagtheridonSpreadRangedAction::hasReachedInitialPosition.empty())
MagtheridonSpreadRangedAction::hasReachedInitialPosition.clear();
if (!botToCubeAssignment.empty())
botToCubeAssignment.clear();
if (IsMapIDTimerManager(botAI, bot))
if (IsInstanceTimerManager(botAI, bot))
{
if (magtheridonSpreadWaitTimer.find(mapId) != magtheridonSpreadWaitTimer.end())
magtheridonSpreadWaitTimer.erase(mapId);
if (magtheridonBlastNovaTimer.find(mapId) != magtheridonBlastNovaTimer.end())
magtheridonBlastNovaTimer.erase(mapId);
if (magtheridonAggroWaitTimer.find(mapId) != magtheridonAggroWaitTimer.end())
magtheridonAggroWaitTimer.erase(mapId);
spreadWaitTimer.erase(instanceId);
blastNovaTimer.erase(instanceId);
dpsWaitTimer.erase(instanceId);
}
}

View File

@@ -170,9 +170,9 @@ namespace MagtheridonHelpers
}
std::unordered_map<uint32, bool> lastBlastNovaState;
std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
std::unordered_map<uint32, time_t> blastNovaTimer;
std::unordered_map<uint32, time_t> spreadWaitTimer;
std::unordered_map<uint32, time_t> dpsWaitTimer;
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z)
{
@@ -209,14 +209,14 @@ namespace MagtheridonHelpers
return true;
}
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot)
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot)
{
if (Group* group = bot->GetGroup())
{
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (member && member->IsAlive() && !botAI->IsMainTank(member) && GET_PLAYERBOT_AI(member))
if (member && member->IsAlive() && botAI->IsDps(member) && GET_PLAYERBOT_AI(member))
return member == bot;
}
}

View File

@@ -54,7 +54,7 @@ namespace MagtheridonHelpers
void MarkTargetWithCross(Player* bot, Unit* target);
void SetRtiTarget(PlayerbotAI* botAI, const std::string& rtiName, Unit* target);
bool IsSafeFromMagtheridonHazards(PlayerbotAI* botAI, Player* bot, float x, float y, float z);
bool IsMapIDTimerManager(PlayerbotAI* botAI, Player* bot);
bool IsInstanceTimerManager(PlayerbotAI* botAI, Player* bot);
struct Location
{
@@ -82,9 +82,9 @@ namespace MagtheridonHelpers
std::vector<CubeInfo> GetAllCubeInfosByDbGuids(Map* map, const std::vector<uint32>& cubeDbGuids);
void AssignBotsToCubesByGuidAndCoords(Group* group, const std::vector<CubeInfo>& cubes, PlayerbotAI* botAI);
extern std::unordered_map<uint32, bool> lastBlastNovaState;
extern std::unordered_map<uint32, time_t> magtheridonBlastNovaTimer;
extern std::unordered_map<uint32, time_t> magtheridonSpreadWaitTimer;
extern std::unordered_map<uint32, time_t> magtheridonAggroWaitTimer;
extern std::unordered_map<uint32, time_t> blastNovaTimer;
extern std::unordered_map<uint32, time_t> spreadWaitTimer;
extern std::unordered_map<uint32, time_t> dpsWaitTimer;
}
#endif

View File

@@ -41,10 +41,10 @@ float MagtheridonWaitToAttackMultiplier::GetValue(Action* action)
if (!magtheridon || magtheridon->HasAura(SPELL_SHADOW_CAGE))
return 1.0f;
const uint8 aggroWaitSeconds = 6;
auto it = magtheridonAggroWaitTimer.find(bot->GetMapId());
if (it == magtheridonAggroWaitTimer.end() ||
(time(nullptr) - it->second) < aggroWaitSeconds)
const uint8 dpsWaitSeconds = 6;
auto it = dpsWaitTimer.find(magtheridon->GetMap()->GetInstanceId());
if (it == dpsWaitTimer.end() ||
(time(nullptr) - it->second) < dpsWaitSeconds)
{
if (!botAI->IsMainTank(bot) && (dynamic_cast<AttackAction*>(action) ||
(!botAI->IsHeal(bot) && dynamic_cast<CastSpellAction*>(action))))

View File

@@ -7,4 +7,14 @@
#include "Playerbots.h"
bool NoRtiTrigger::IsActive() { return !AI_VALUE(Unit*, "rti target"); }
bool NoRtiTrigger::IsActive()
{
// Do not auto-react to raid icons while out of combat.
// Out-of-combat RTI usage (explicit chat commands) is handled by chat triggers,
// not by this generic trigger.
if (!bot->IsInCombat())
return false;
Unit* target = AI_VALUE(Unit*, "rti target");
return target != nullptr;
}

View File

@@ -21,11 +21,37 @@ bool IsSameLocation(WorldLocation const& a, WorldLocation const& b)
bool Formation::IsNullLocation(WorldLocation const& loc) { return IsSameLocation(loc, Formation::NullLocation); }
bool ValidateTargetContext(Unit* a, Unit* b, Map*& outMap)
{
if (!a || !b || a == b)
return false;
if (!a->IsInWorld() || !b->IsInWorld())
return false;
if (a->IsDuringRemoveFromWorld() || b->IsDuringRemoveFromWorld())
return false;
Map* map = a->GetMap();
if (!map || map != b->GetMap())
return false;
outMap = map;
return true;
}
bool ValidateTargetContext(Unit* a, Unit* b)
{
Map* unused = nullptr;
return ValidateTargetContext(a, b, unused);
}
WorldLocation MoveAheadFormation::GetLocation()
{
Player* master = GetMaster();
if (!master || master == bot)
return WorldLocation();
if (!ValidateTargetContext(master, bot))
return Formation::NullLocation;
WorldLocation loc = GetLocationInternal();
if (Formation::IsNullLocation(loc))
@@ -40,7 +66,7 @@ WorldLocation MoveAheadFormation::GetLocation()
// float ori = master->GetOrientation();
// float x1 = x + sPlayerbotAIConfig->tooCloseDistance * cos(ori);
// float y1 = y + sPlayerbotAIConfig->tooCloseDistance * sin(ori);
// float ground = master->GetMap()->GetHeight(x1, y1, z);
// float ground = map->GetHeight(x1, y1, z);
// if (ground > INVALID_HEIGHT)
// {
// x = x1;
@@ -48,7 +74,7 @@ WorldLocation MoveAheadFormation::GetLocation()
// }
// }
// float ground = master->GetMap()->GetHeight(x, y, z);
// float ground = map->GetHeight(x, y, z);
// if (ground <= INVALID_HEIGHT)
// return Formation::NullLocation;
@@ -81,16 +107,17 @@ public:
WorldLocation GetLocationInternal() override
{
Player* master = GetMaster();
if (!master)
return WorldLocation();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range;
float y = master->GetPositionY() + sin(angle) * range;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range;
y = master->GetPositionY() + sin(angle) * range;
@@ -111,8 +138,9 @@ public:
WorldLocation GetLocationInternal() override
{
Player* master = GetMaster();
if (!master)
return WorldLocation();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
@@ -120,49 +148,28 @@ public:
time_t now = time(nullptr);
if (!lastChangeTime || now - lastChangeTime >= 3)
{
Player* master = GetMaster();
if (!master)
return WorldLocation();
lastChangeTime = now;
float range = sPlayerbotAIConfig->followDistance;
float angle = GetFollowAngle();
time_t now = time(nullptr);
if (!lastChangeTime || now - lastChangeTime >= 3)
{
lastChangeTime = now;
dx = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
dy = (urand(0, 10) / 10.0 - 0.5) * sPlayerbotAIConfig->tooCloseDistance;
dr = sqrt(dx * dx + dy * dy);
}
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
// bot->GetMap()->CheckCollisionAndGetValidCoords(bot, bot->GetPositionX(), bot->GetPositionY(),
// bot->GetPositionZ(), x, y, z);
return WorldLocation(master->GetMapId(), x, y, z);
dx = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
dy = (urand(0, 10) / 10.0f - 0.5f) * sPlayerbotAIConfig->tooCloseDistance;
dr = std::sqrt(dx * dx + dy * dy);
}
float x = master->GetPositionX() + cos(angle) * range + dx;
float y = master->GetPositionY() + sin(angle) * range + dy;
float x = master->GetPositionX() + std::cos(angle) * range + dx;
float y = master->GetPositionY() + std::sin(angle) * range + dy;
float z = master->GetPositionZ() + master->GetHoverHeight();
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + dx;
y = master->GetPositionY() + sin(angle) * range + dy;
// Recompute a clean fallback and clamp Z
x = master->GetPositionX() + std::cos(angle) * range + dx;
y = master->GetPositionY() + std::sin(angle) * range + dy;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(master->GetMapId(), x, y, z);
}
@@ -182,16 +189,18 @@ public:
WorldLocation GetLocation() override
{
float range = 2.0f;
Unit* target = AI_VALUE(Unit*, "current target");
Player* master = GetMaster();
if (!target && target != bot)
// Fix: if no target OR target is the bot, fall back to master
if (!target || target == bot)
target = master;
if (!target)
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = 2.0f;
switch (bot->getClass())
{
case CLASS_HUNTER:
@@ -214,14 +223,16 @@ public:
float x = target->GetPositionX() + cos(angle) * range;
float y = target->GetPositionY() + sin(angle) * range;
float z = target->GetPositionZ();
if (!target->GetMap()->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
target->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(target, target->GetPositionX(), target->GetPositionY(),
target->GetPositionZ(), x, y, z))
{
x = target->GetPositionX() + cos(angle) * range;
y = target->GetPositionY() + sin(angle) * range;
z = target->GetPositionZ();
target->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
@@ -249,18 +260,18 @@ public:
float orientation = master->GetOrientation();
std::vector<Player*> players;
GroupReference* gref = group->GetFirstMember();
while (gref)
players.reserve(group->GetMembersCount());
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member != master)
players.push_back(member);
if (!member || member == master)
continue;
gref = gref->next();
players.push_back(member);
}
players.insert(players.begin() + group->GetMembersCount() / 2, master);
players.insert(players.begin() + players.size() / 2, master);
return MoveLine(players, 0.0f, x, y, z, orientation, range);
}
};
@@ -289,19 +300,17 @@ public:
std::vector<Player*> tanks;
std::vector<Player*> dps;
GroupReference* gref = group->GetFirstMember();
while (gref)
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
if (member != master)
{
if (botAI->IsTank(member))
tanks.push_back(member);
else
dps.push_back(member);
}
if (!member || member == master)
continue;
gref = gref->next();
if (botAI->IsTank(member))
tanks.push_back(member);
else
dps.push_back(member);
}
if (botAI->IsTank(master))
@@ -310,25 +319,21 @@ public:
dps.insert(dps.begin() + (dps.size() + 1) / 2, master);
if (botAI->IsTank(bot) && botAI->IsTank(master))
{
return MoveLine(tanks, 0.0f, x, y, z, orientation, range);
}
if (!botAI->IsTank(bot) && !botAI->IsTank(master))
{
return MoveLine(dps, 0.0f, x, y, z, orientation, range);
}
if (botAI->IsTank(bot) && !botAI->IsTank(master))
{
float diff = tanks.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
float diff = (tanks.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(tanks, diff, x + cos(orientation) * range, y + sin(orientation) * range, z, orientation,
range);
}
if (!botAI->IsTank(bot) && botAI->IsTank(master))
{
float diff = dps.size() % 2 == 0 ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
float diff = (dps.size() % 2 == 0) ? -sPlayerbotAIConfig->tooCloseDistance / 2.0f : 0.0f;
return MoveLine(dps, diff, x - cos(orientation) * range, y - sin(orientation) * range, z, orientation,
range);
}
@@ -344,65 +349,69 @@ public:
WorldLocation GetLocation() override
{
Player* master = GetMaster();
Map* map = nullptr;
if (!ValidateTargetContext(master, bot, map))
return Formation::NullLocation;
float range = sPlayerbotAIConfig->farDistance;
float followRange = sPlayerbotAIConfig->followDistance;
Player* master = GetMaster();
if (!master)
return Formation::NullLocation;
if (sServerFacade->GetDistance2d(bot, master) <= range)
return Formation::NullLocation;
float angle = master->GetAngle(bot);
float angleToBot = master->GetAngle(bot);
float followAngle = GetFollowAngle();
float x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
float y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
float x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
float y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
float z = master->GetPositionZ();
float ground = master->GetMapHeight(x, y, z + 30.0f);
if (ground <= INVALID_HEIGHT)
{
float minDist = 0, minX = 0, minY = 0;
for (float angle = 0.0f; angle <= 2 * M_PI; angle += M_PI / 16.0f)
float minDist = 0.f;
float minX = 0.f, minY = 0.f;
for (float a = 0.0f; a <= 2 * M_PI; a += M_PI / 16.0f)
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
float dist = sServerFacade->GetDistance2d(bot, x, y);
float ground = master->GetMapHeight(x, y, z + 30.0f);
if (ground > INVALID_HEIGHT && (!minDist || minDist > dist))
float tx = master->GetPositionX() + cos(a) * range + cos(followAngle) * followRange;
float ty = master->GetPositionY() + sin(a) * range + sin(followAngle) * followRange;
float dist = sServerFacade->GetDistance2d(bot, tx, ty);
float tg = master->GetMapHeight(tx, ty, z + 30.0f);
if (tg > INVALID_HEIGHT && (!minDist || dist < minDist))
{
minDist = dist;
minX = x;
minY = y;
minX = tx;
minY = ty;
}
}
if (minDist)
if (!minDist)
return Formation::NullLocation;
float lz = z;
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), minX, minY, lz))
{
if (!master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), minX, minY, z);
lz = z + master->GetHoverHeight();
master->UpdateAllowedPositionZ(minX, minY, lz);
}
return Formation::NullLocation;
return WorldLocation(bot->GetMapId(), minX, minY, lz);
}
if (!master->GetMap()->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
if (!map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), x, y, z))
{
x = master->GetPositionX() + cos(angle) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angle) * range + sin(followAngle) * followRange;
x = master->GetPositionX() + cos(angleToBot) * range + cos(followAngle) * followRange;
y = master->GetPositionY() + sin(angleToBot) * range + sin(followAngle) * followRange;
z = master->GetPositionZ() + master->GetHoverHeight();
master->UpdateAllowedPositionZ(x, y, z);
}
return WorldLocation(bot->GetMapId(), x, y, z);
}
};
@@ -653,31 +662,36 @@ WorldLocation MoveFormation::MoveSingleLine(std::vector<Player*> line, float dif
float orientation, float range)
{
float count = line.size();
float angle = orientation - M_PI / 2.0f;
float x = cx + cos(angle) * (range * floor(count / 2.0f) + diff);
float y = cy + sin(angle) * (range * floor(count / 2.0f) + diff);
float angleLeft = orientation - M_PI / 2.0f;
float x0 = cx + std::cos(angleLeft) * (range * std::floor(count / 2.0f) + diff);
float y0 = cy + std::sin(angleLeft) * (range * std::floor(count / 2.0f) + diff);
uint32 index = 0;
for (Player* member : line)
{
if (member == bot)
{
float angle = orientation + M_PI / 2.0f;
float angleRight = orientation + M_PI / 2.0f;
float radius = range * index;
float lx = x + cos(angle) * radius;
float ly = y + sin(angle) * radius;
float lx = x0 + std::cos(angleRight) * radius;
float ly = y0 + std::sin(angleRight) * radius;
float lz = cz;
Player* master = botAI->GetMaster();
if (!master || !master->GetMap()->CheckCollisionAndGetValidCoords(
master, master->GetPositionX(), master->GetPositionY(), master->GetPositionZ(), lx, ly, lz))
Map* map = master ? master->GetMap() : nullptr;
// if not fully in world ignore collision corrections.
if (!master || !map || !bot || map != bot->GetMap() || !master->IsInWorld() ||
master->IsDuringRemoveFromWorld() || !bot->IsInWorld() || bot->IsDuringRemoveFromWorld())
{
lx = x + cos(angle) * radius;
ly = y + sin(angle) * radius;
lz = cz;
return WorldLocation(bot->GetMapId(), lx, ly, lz);
}
// if fully loaded check collision and applies coordinate corrections if needed
map->CheckCollisionAndGetValidCoords(master, master->GetPositionX(), master->GetPositionY(),
master->GetPositionZ(), lx, ly, lz);
return WorldLocation(bot->GetMapId(), lx, ly, lz);
}

View File

@@ -80,7 +80,7 @@ ItemUsage ItemUsageValue::Calculate()
return ITEM_USAGE_USE;
if (proto->Class == ITEM_CLASS_CONSUMABLE &&
(proto->MaxCount == 0 || AI_VALUE2(uint32, "item count", proto->Name1) < proto->MaxCount))
(proto->MaxCount == 0 || bot->GetItemCount(itemId, false) < proto->MaxCount))
{
std::string const foodType = GetConsumableType(proto, bot->GetPower(POWER_MANA));
@@ -520,7 +520,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
{
if (quest->RequiredItemId[i] == proto->ItemId)
{
if (AI_VALUE2(uint32, "item count", proto->Name1) >= quest->RequiredItemCount[i])
if (player->GetItemCount(proto->ItemId, false) >= quest->RequiredItemCount[i])
continue;
return true; // Item is directly required for a quest
@@ -549,7 +549,7 @@ bool ItemUsageValue::IsItemUsefulForQuest(Player* player, ItemTemplate const* pr
{
if (quest->RequiredItemId[j] == createdItemId)
{
if (AI_VALUE2(uint32, "item count", createdItemId) >= quest->RequiredItemCount[j])
if (player->GetItemCount(createdItemId, false) >= quest->RequiredItemCount[j])
continue;
return true; // Item is useful because it creates a required quest item

View File

@@ -64,5 +64,15 @@ Unit* RtiTargetValue::Calculate()
sPlayerbotAIConfig->sightDistance))
return nullptr;
// Also prevent chasing raid icon targets that are too far away from the master,
// even if they are technically visible to the bot.
if (Player* master = botAI->GetMaster())
{
if (master->IsInWorld() && master->GetMapId() == unit->GetMapId() &&
sServerFacade->IsDistanceGreaterThan(sServerFacade->GetDistance2d(master, unit),
sPlayerbotAIConfig->sightDistance))
return nullptr;
}
return unit;
}