diff --git a/src/modules/Playerbot/Commands/PlayerbotCommands.cpp b/src/modules/Playerbot/Commands/PlayerbotCommands.cpp index abe7f6ba7..e6df17349 100644 --- a/src/modules/Playerbot/Commands/PlayerbotCommands.cpp +++ b/src/modules/Playerbot/Commands/PlayerbotCommands.cpp @@ -50,7 +50,7 @@ namespace Playerbot { PlayerbotCommandScript::PlayerbotCommandScript() : CommandScript("PlayerbotCommandScript") { } - ChatCommandTable PlayerbotCommandScript::GetCommands() const + std::span PlayerbotCommandScript::GetCommands() const { static ChatCommandTable botFormationCommandTable = { @@ -1210,7 +1210,7 @@ namespace Playerbot oss << "4. line - Horizontal line for maximum width coverage\n"; oss << "5. column - Vertical single-file march formation\n"; oss << "6. scatter - Spread formation for anti-AoE tactics\n"; - oss << "7. circle - 360° perimeter coverage formation\n"; + oss << "7. circle - 360� perimeter coverage formation\n"; oss << "8. dungeon - Optimized dungeon formation (tank/healer/dps roles)\n"; oss << "9. raid - Raid formation with 5-person groups\n"; oss << "\n"; diff --git a/src/modules/Playerbot/Commands/PlayerbotCommands.h b/src/modules/Playerbot/Commands/PlayerbotCommands.h index 2e072addf..70ad51499 100644 --- a/src/modules/Playerbot/Commands/PlayerbotCommands.h +++ b/src/modules/Playerbot/Commands/PlayerbotCommands.h @@ -25,6 +25,7 @@ #include "RBAC.h" #include "ScriptMgr.h" #include +#include #include namespace Playerbot @@ -65,7 +66,7 @@ namespace Playerbot public: PlayerbotCommandScript(); - Trinity::ChatCommands::ChatCommandTable GetCommands() const; + std::span GetCommands() const; private: // Bot spawning commands diff --git a/src/modules/Playerbot/Humanization/Collections/TransmogManager.cpp b/src/modules/Playerbot/Humanization/Collections/TransmogManager.cpp index d14a2a63e..1e457c732 100644 --- a/src/modules/Playerbot/Humanization/Collections/TransmogManager.cpp +++ b/src/modules/Playerbot/Humanization/Collections/TransmogManager.cpp @@ -31,6 +31,10 @@ void TransmogManager::CollectAppearancesFromEquipment() if (!_bot) return; + // Guard against invalid bot state + if (!_bot->IsInWorld() || !_bot->GetSession()) + return; + // Obtain session once and guard against NULL session auto sess = _bot->GetSession(); if (!sess) @@ -100,6 +104,10 @@ bool TransmogManager::ApplyOutfit(std::string const& outfitName) if (!_bot) return false; + // Guard against invalid bot state + if (!_bot->IsInWorld() || !_bot->GetSession()) + return false; + auto it = _savedOutfits.find(outfitName); if (it == _savedOutfits.end()) return false; @@ -143,8 +151,12 @@ bool TransmogManager::ApplyOutfit(std::string const& outfitName) if (applied > 0) { - // Force visual update for nearby players - _bot->SetVisibleItemSlot(0, nullptr); // Trigger update + // Force visual update - iterate through all equipment slots properly + for (uint8 slot = 0; slot < EQUIPMENT_SLOT_TABARD; ++slot) + { + Item* item = _bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + _bot->SetVisibleItemSlot(slot, item); + } TC_LOG_DEBUG("module.playerbot", "TransmogManager: Bot {} applied outfit '{}' ({} slots)", _bot->GetName(), outfitName, applied); } @@ -171,6 +183,10 @@ void TransmogManager::ClearTransmog() if (!_bot) return; + // Guard against invalid bot state + if (!_bot->IsInWorld() || !_bot->GetSession()) + return; + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) { Item* item = _bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); @@ -181,6 +197,13 @@ void TransmogManager::ClearTransmog() item->SetState(ITEM_CHANGED, _bot); } + // Update visual slots + for (uint8 slot = 0; slot < EQUIPMENT_SLOT_TABARD; ++slot) + { + Item* item = _bot->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + _bot->SetVisibleItemSlot(slot, item); + } + TC_LOG_DEBUG("module.playerbot", "TransmogManager: Bot {} cleared all transmog", _bot->GetName()); } @@ -202,6 +225,10 @@ bool TransmogManager::ShouldVisitTransmogrifier() const if (!_bot) return false; + // Guard against invalid bot state + if (!_bot->IsInWorld()) + return false; + uint32 currentTime = GameTime::GetGameTimeMS(); if (currentTime - _lastTransmogVisitMs < TRANSMOG_VISIT_COOLDOWN_MS) return false; @@ -257,6 +284,10 @@ void TransmogManager::Update(uint32 /*diff*/) if (!_bot) return; + // Guard against invalid bot state + if (!_bot->IsInWorld()) + return; + uint32 currentTime = GameTime::GetGameTimeMS(); if (currentTime - _lastUpdateMs < UPDATE_INTERVAL_MS) return; @@ -326,7 +357,7 @@ uint32 TransmogManager::GetItemAppearanceId(Item const* item) TransmogManager* TransmogCoordinator::GetManager(Player* bot) { - if (!bot) + if (!bot || !bot->IsInWorld()) return nullptr; ObjectGuid guid = bot->GetGUID(); diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index f1a9ef143..c2d9e4527 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1273,6 +1273,12 @@ bool Player::TeleportTo(TeleportLocation const& teleportLocation, TeleportToOpti return false; } + if (IsBeingTeleportedFar()) + { + TC_LOG_WARN("entities.player.teleport", "Player::TeleportTo: Player '{}' ({}) has stuck teleport state, resetting", GetGUID().ToString(), GetName()); + ResetTeleportState(); + } + // preparing unsummon pet if lost (we must get pet before teleportation or will not find it later) Pet* pet = GetPet(); @@ -1518,6 +1524,16 @@ bool Player::TeleportToBGEntryPoint() return TeleportTo(m_bgData.joinPos); } +void Player::ResetTeleportState() +{ + SetSemaphoreTeleportFar(false); + SetSemaphoreTeleportNear(false); + SetDelayedTeleportFlag(false); + RemoveUnitFlag(UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_ON_TAXI); + GetMotionMaster()->Remove(FLIGHT_MOTION_TYPE); + TC_LOG_DEBUG("entities.player.teleport", "Player::ResetTeleportState: Reset teleport state for player {}", GetName()); +} + void Player::ProcessDelayedOperations() { if (m_DelayedOperations == 0) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 6056171af..af410db52 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1236,6 +1236,7 @@ class TC_GAME_API Player final : public Unit, public GridObject bool TeleportTo(WorldLocation const& loc, TeleportToOptions options = TELE_TO_NONE, Optional instanceId = {}, uint32 teleportSpellId = 0); bool TeleportTo(TeleportLocation const& teleportLocation, TeleportToOptions options = TELE_TO_NONE, uint32 teleportSpellId = 0); bool TeleportToBGEntryPoint(); + void ResetTeleportState(); bool HasSummonPending() const; void SendSummonRequestFrom(Unit* summoner); diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index a8da7df4c..231161060 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -170,6 +170,8 @@ void WorldSession::HandleMoveWorldportAck() garrison->SendRemoteInfo(); } + player->UpdateZoneAndAreaId(); + // flight fast teleport case if (player->IsInFlight()) { diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 4b836f816..270351898 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -1018,9 +1018,23 @@ void Spell::EffectTeleportUnits() if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET) return; - if (!unitTarget || unitTarget->IsInFlight()) + if (!unitTarget) return; + if (unitTarget->IsInFlight()) + { + if (Player* player = unitTarget->ToPlayer()) + { + TC_LOG_WARN("spells", "Spell::EffectTeleportUnits - Player {} is stuck in flight state, resetting", player->GetName()); + player->GetMotionMaster()->Remove(FLIGHT_MOTION_TYPE); + player->RemoveUnitFlag(UNIT_FLAG_REMOVE_CLIENT_CONTROL | UNIT_FLAG_ON_TAXI); + } + else + { + return; + } + } + // If not exist data for dest location - return if (!m_targets.HasDst()) {