mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-13 09:07:19 +00:00
* Revert "[Large server fix] #1537 Serialize playerBots/botLoading with a mutex and use snapshot-based loops to fix concurrency crashes (#1540)" This reverts commit3fff58df1a. * Revert "[Fix] teleport to invalid map or invalid coordinates (x , y , z 200000, o ) given when teleporting player (g UI d full type player low , name , map , x , y , z , o ) (#1538)" This reverts commitca2e2ef0db. * Revert "Fix: prevent MoveSplineInitArgs::Validate velocity asserts (velocity > 0.01f) for bots, pets, and charmed units (#1534)" This reverts commit4e3ac609bd. * Revert "[Fix issue #1527] : startup crash in tank target selection — add TOCTOU & null-safety guards (#1532)" This reverts commitc6b0424c29. * Revert "[Fix issue #1528] Close small window where the “in a BG/arena” state can change between the check (InBattleground() / InArena()) and grabbing the pointer (GetBattleground()), which leads to a null dereference. (#1530)" This reverts commit2e0a161623. * Revert "Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks (#1529)" This reverts commite4ea8e2694. * Revert "Dont wait to travel when in combat. (#1524)" This reverts commitddfa919154. * Revert "nullptr fix (#1523)" This reverts commit380312ffd2. * Revert "Playerbots/LFG: fix false not eligible & dungeon 0/type 0, add clear diagnostics (#1521)" This reverts commit872e417613. * Revert "nullptr exception (#1520)" This reverts commit3d28a81508. * Revert "Removed bot freezing at startup and system message, not relevant anymore (#1519)" This reverts commitbcd6f5bc06.
267 lines
7.4 KiB
C++
267 lines
7.4 KiB
C++
/*
|
|
* 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 "ReleaseSpiritAction.h"
|
|
#include "ServerFacade.h"
|
|
#include "Event.h"
|
|
#include "GameGraveyard.h"
|
|
#include "NearestNpcsValue.h"
|
|
#include "ObjectDefines.h"
|
|
#include "ObjectGuid.h"
|
|
#include "Playerbots.h"
|
|
#include "ServerFacade.h"
|
|
#include "Corpse.h"
|
|
#include "Log.h"
|
|
|
|
// ReleaseSpiritAction implementation
|
|
bool ReleaseSpiritAction::Execute(Event event)
|
|
{
|
|
if (bot->IsAlive())
|
|
{
|
|
if (!bot->InBattleground())
|
|
{
|
|
botAI->TellMasterNoFacing("I am not dead, will wait here");
|
|
// -follow in bg is overwriten each tick with +follow
|
|
// +stay in bg causes stuttering effect as bot is cycled between +stay and +follow each tick
|
|
botAI->ChangeStrategy("-follow,+stay", BOT_STATE_NON_COMBAT);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if (bot->GetCorpse() && bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
|
|
{
|
|
botAI->TellMasterNoFacing("I am already a spirit");
|
|
return false;
|
|
}
|
|
|
|
const WorldPacket& packet = event.getPacket();
|
|
const std::string message = !packet.empty() && packet.GetOpcode() == CMSG_REPOP_REQUEST
|
|
? "Releasing..."
|
|
: "Meet me at the graveyard";
|
|
botAI->TellMasterNoFacing(message);
|
|
|
|
IncrementDeathCount();
|
|
bot->DurabilityRepairAll(false, 1.0f, false);
|
|
LogRelease("released");
|
|
|
|
WorldPacket releasePacket(CMSG_REPOP_REQUEST);
|
|
releasePacket << uint8(0);
|
|
bot->GetSession()->HandleRepopRequestOpcode(releasePacket);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ReleaseSpiritAction::IncrementDeathCount() const
|
|
{
|
|
// Death Count to prevent skeleton piles
|
|
Player* master = botAI->GetMaster();
|
|
if (!master || GET_PLAYERBOT_AI(master))
|
|
{
|
|
uint32 deathCount = AI_VALUE(uint32, "death count");
|
|
context->GetValue<uint32>("death count")->Set(deathCount + 1);
|
|
}
|
|
}
|
|
|
|
void ReleaseSpiritAction::LogRelease(const std::string& releaseMsg, bool isAutoRelease) const
|
|
{
|
|
const std::string teamPrefix = bot->GetTeamId() == TEAM_ALLIANCE ? "A" : "H";
|
|
|
|
LOG_DEBUG("playerbots", "Bot {} {}:{} <{}> {}",
|
|
bot->GetGUID().ToString().c_str(),
|
|
teamPrefix,
|
|
bot->GetLevel(),
|
|
bot->GetName().c_str(),
|
|
releaseMsg.c_str());
|
|
}
|
|
|
|
// AutoReleaseSpiritAction implementation
|
|
bool AutoReleaseSpiritAction::Execute(Event event)
|
|
{
|
|
IncrementDeathCount();
|
|
bot->DurabilityRepairAll(false, 1.0f, false);
|
|
LogRelease("auto released", true);
|
|
|
|
WorldPacket packet(CMSG_REPOP_REQUEST);
|
|
packet << uint8(0);
|
|
bot->GetSession()->HandleRepopRequestOpcode(packet);
|
|
|
|
LogRelease("releases spirit", true);
|
|
|
|
if (bot->InBattleground())
|
|
{
|
|
return HandleBattlegroundSpiritHealer();
|
|
}
|
|
|
|
botAI->SetNextCheckDelay(1000);
|
|
return true;
|
|
}
|
|
|
|
bool AutoReleaseSpiritAction::isUseful()
|
|
{
|
|
if (!bot->isDead() || bot->InArena())
|
|
return false;
|
|
|
|
if (bot->InBattleground())
|
|
return ShouldDelayBattlegroundRelease();
|
|
|
|
if (bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
|
|
return false;
|
|
|
|
return ShouldAutoRelease();
|
|
}
|
|
|
|
bool AutoReleaseSpiritAction::HandleBattlegroundSpiritHealer()
|
|
{
|
|
constexpr uint32_t RESURRECT_DELAY = 15;
|
|
const time_t now = time(nullptr);
|
|
|
|
if ((now - m_bgGossipTime < RESURRECT_DELAY) &&
|
|
bot->HasAura(SPELL_WAITING_FOR_RESURRECT))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
float bgRange = 2000.0f;
|
|
GuidVector npcs = NearestNpcsValue(botAI, bgRange);
|
|
Unit* spiritHealer = nullptr;
|
|
|
|
for (const auto& guid : npcs)
|
|
{
|
|
Unit* unit = botAI->GetUnit(guid);
|
|
if (unit && unit->IsFriendlyTo(bot) && unit->IsSpiritService())
|
|
{
|
|
spiritHealer = unit;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!spiritHealer)
|
|
return false;
|
|
|
|
if (bot->GetDistance(spiritHealer) >= INTERACTION_DISTANCE)
|
|
{
|
|
// Bot needs to actually click spirit-healer in BG to get res timer going
|
|
// and in IOC it's not within clicking range when they res in own base
|
|
|
|
// Teleport to nearest friendly Spirit Healer when not currently in range of one.
|
|
bot->TeleportTo(bot->GetMapId(), spiritHealer->GetPositionX(), spiritHealer->GetPositionY(), spiritHealer->GetPositionZ(), 0.f);
|
|
RESET_AI_VALUE(bool, "combat::self target");
|
|
RESET_AI_VALUE(WorldPosition, "current position");
|
|
}
|
|
else if (!botAI->IsRealPlayer())
|
|
{
|
|
m_bgGossipTime = now;
|
|
WorldPacket packet(CMSG_GOSSIP_HELLO);
|
|
packet << spiritHealer->GetGUID();
|
|
bot->GetSession()->HandleGossipHelloOpcode(packet);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AutoReleaseSpiritAction::ShouldAutoRelease() const
|
|
{
|
|
if (!bot->GetGroup())
|
|
return true;
|
|
|
|
Player* groupMaster = botAI->GetGroupMaster();
|
|
if (!groupMaster || groupMaster == bot)
|
|
return true;
|
|
|
|
if (!botAI->HasActivePlayerMaster())
|
|
return true;
|
|
|
|
if (botAI->HasActivePlayerMaster() &&
|
|
groupMaster->GetMapId() == bot->GetMapId() &&
|
|
bot->GetMap() &&
|
|
(bot->GetMap()->IsRaid() || bot->GetMap()->IsDungeon()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return sServerFacade->IsDistanceGreaterThan(
|
|
AI_VALUE2(float, "distance", "master target"),
|
|
sPlayerbotAIConfig->sightDistance);
|
|
}
|
|
|
|
bool AutoReleaseSpiritAction::ShouldDelayBattlegroundRelease() const
|
|
{
|
|
// The below delays release to spirit with 6 seconds.
|
|
// This prevents currently casted (ranged) spells to be re-directed to the died bot's ghost.
|
|
const int32_t botId = bot->GetGUID().GetRawValue();
|
|
|
|
// If the bot already is a spirit, erase release time and return true
|
|
if (bot->HasPlayerFlag(PLAYER_FLAGS_GHOST))
|
|
{
|
|
m_botReleaseTimes.erase(botId);
|
|
return true;
|
|
}
|
|
|
|
// Delay release to spirit.
|
|
const time_t now = time(nullptr);
|
|
constexpr time_t RELEASE_DELAY = 6;
|
|
|
|
auto& lastReleaseTime = m_botReleaseTimes[botId];
|
|
if (lastReleaseTime == 0)
|
|
lastReleaseTime = now;
|
|
|
|
if (now - lastReleaseTime < RELEASE_DELAY)
|
|
return false;
|
|
|
|
m_botReleaseTimes.erase(botId);
|
|
return true;
|
|
}
|
|
|
|
bool RepopAction::Execute(Event event)
|
|
{
|
|
const GraveyardStruct* graveyard = GetGrave(
|
|
AI_VALUE(uint32, "death count") > 10 ||
|
|
CalculateDeadTime() > 30 * MINUTE
|
|
);
|
|
|
|
if (!graveyard)
|
|
return false;
|
|
|
|
PerformGraveyardTeleport(graveyard);
|
|
return true;
|
|
}
|
|
|
|
bool RepopAction::isUseful()
|
|
{
|
|
return !bot->InBattleground();
|
|
}
|
|
|
|
int64 RepopAction::CalculateDeadTime() const
|
|
{
|
|
if (Corpse* corpse = bot->GetCorpse())
|
|
return time(nullptr) - corpse->GetGhostTime();
|
|
|
|
return bot->isDead() ? 0 : 60 * MINUTE;
|
|
}
|
|
|
|
void RepopAction::PerformGraveyardTeleport(const GraveyardStruct* graveyard) const
|
|
{
|
|
bot->TeleportTo(graveyard->Map, graveyard->x, graveyard->y, graveyard->z, 0.f);
|
|
RESET_AI_VALUE(bool, "combat::self target");
|
|
RESET_AI_VALUE(WorldPosition, "current position");
|
|
}
|
|
|
|
// SelfResurrectAction implementation for Warlock's Soulstone Resurrection/Shaman's Reincarnation
|
|
bool SelfResurrectAction::Execute(Event event)
|
|
{
|
|
if (!bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL))
|
|
{
|
|
WorldPacket packet(CMSG_SELF_RES);
|
|
bot->GetSession()->HandleSelfResOpcode(packet);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
bool SelfResurrectAction::isUseful()
|
|
{
|
|
return !bot->IsAlive() && bot->GetUInt32Value(PLAYER_SELF_RES_SPELL);
|
|
}
|