From 687ceeea7088aeec9361e1dff2ef24fb8752ca38 Mon Sep 17 00:00:00 2001 From: blinkysc <37940565+blinkysc@users.noreply.github.com> Date: Sun, 19 Apr 2026 10:09:51 -0500 Subject: [PATCH] fix(Core/Vehicles): Prevent accessory double-install and respawn orphans (#25499) Co-authored-by: blinkysc --- src/server/game/Entities/Creature/Creature.cpp | 7 ++++--- src/server/game/Entities/Vehicle/Vehicle.cpp | 16 +++++++++++++--- src/server/game/Entities/Vehicle/Vehicle.h | 1 + src/server/game/Grids/GridObjectLoader.cpp | 5 +++++ src/server/game/Maps/Map.cpp | 3 +-- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index b438d8400..e4cf2f2a8 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1113,9 +1113,6 @@ bool Creature::AIM_Initialize(CreatureAI* ai) IsAIEnabled = true; i_AI->InitializeAI(); - // Xinef: Initialize vehicle if it is not summoned! - if (GetVehicleKit() && m_spawnId) - GetVehicleKit()->Reset(); return true; } @@ -2052,6 +2049,10 @@ void Creature::Respawn(bool force) if (getDeathState() == DeathState::Dead) { + // TempSummons (no m_spawnId) shouldn't be resurrected here; TempSummon::Update UnSummons them on the next tick once deathState is Dead. + if (!m_spawnId && !force) + return; + if (m_spawnId) { GetMap()->RemoveCreatureRespawnTime(m_spawnId); diff --git a/src/server/game/Entities/Vehicle/Vehicle.cpp b/src/server/game/Entities/Vehicle/Vehicle.cpp index c4ce55658..09293b5a4 100644 --- a/src/server/game/Entities/Vehicle/Vehicle.cpp +++ b/src/server/game/Entities/Vehicle/Vehicle.cpp @@ -29,7 +29,8 @@ #include Vehicle::Vehicle(Unit* unit, VehicleEntry const* vehInfo, uint32 creatureEntry) : - _me(unit), _vehicleInfo(vehInfo), _usableSeatNum(0), _creatureEntry(creatureEntry), _status(STATUS_NONE) + _me(unit), _vehicleInfo(vehInfo), _usableSeatNum(0), _creatureEntry(creatureEntry), _status(STATUS_NONE), + _accessoriesInstalled(false) { for (uint32 i = 0; i < MAX_VEHICLE_SEATS; ++i) { @@ -87,8 +88,16 @@ void Vehicle::Install() void Vehicle::InstallAllAccessories(bool evading) { - if (GetBase()->IsPlayer() || !evading) - RemoveAllPassengers(); // We might have aura's saved in the DB with now invalid casters - remove + // Clear stale control-vehicle auras only on the first non-evade reset; + // a re-Reset would otherwise eject a just-seated passenger and, for + // accessories, fire a spurious SMART_EVENT_PASSENGER_REMOVED. + if (GetBase()->IsPlayer() || (!evading && !_accessoriesInstalled)) + RemoveAllPassengers(); + + // Mark the initial reset as done even if this vehicle has no accessory + // list, so subsequent Resets don't eject passengers on vehicles without + // accessories (e.g. WG demolisher/catapult). + _accessoriesInstalled = true; VehicleAccessoryList const* accessories = sObjectMgr->GetVehicleAccessoryList(this); if (!accessories) @@ -111,6 +120,7 @@ void Vehicle::Uninstall() _status = STATUS_UNINSTALLING; LOG_DEBUG("vehicles", "Vehicle::Uninstall {}", _me->GetGUID().ToString()); RemoveAllPassengers(); + _accessoriesInstalled = false; if (_me && _me->IsCreature()) { diff --git a/src/server/game/Entities/Vehicle/Vehicle.h b/src/server/game/Entities/Vehicle/Vehicle.h index d7359760c..c5153ca80 100644 --- a/src/server/game/Entities/Vehicle/Vehicle.h +++ b/src/server/game/Entities/Vehicle/Vehicle.h @@ -96,6 +96,7 @@ private: uint32 _usableSeatNum; // Number of seats that match VehicleSeatEntry::UsableByPlayer, used for proper display flags uint32 _creatureEntry; // Can be different than me->GetBase()->GetEntry() in case of players Status _status; + bool _accessoriesInstalled; }; class VehicleDespawnEvent : public BasicEvent diff --git a/src/server/game/Grids/GridObjectLoader.cpp b/src/server/game/Grids/GridObjectLoader.cpp index a0a82fe4b..bad4fe91f 100644 --- a/src/server/game/Grids/GridObjectLoader.cpp +++ b/src/server/game/Grids/GridObjectLoader.cpp @@ -24,6 +24,7 @@ #include "GridNotifiers.h" #include "ObjectMgr.h" #include "Transport.h" +#include "Vehicle.h" template void GridObjectLoader::AddObjectHelper(Map* map, T* obj) @@ -53,6 +54,10 @@ void GridObjectLoader::LoadCreatures(CellGuidSet const& guid_set, Map* map) AddObjectHelper(map, obj); + // Grid load bypasses Map::AddToMap, so seat accessories here. + if (Vehicle* vehicle = obj->GetVehicleKit()) + vehicle->Reset(); + if (!obj->IsMoveInLineOfSightDisabled() && obj->GetDefaultMovementType() == IDLE_MOTION_TYPE && !obj->isNeedNotify(NOTIFY_VISIBILITY_CHANGED | NOTIFY_AI_RELOCATION)) { if (obj->IsAlive() && !obj->HasUnitState(UNIT_STATE_SIGHTLESS) && obj->HasReactState(REACT_AGGRESSIVE) && !obj->IsImmuneToNPC()) diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 57b18d5f4..324d19f13 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -346,8 +346,7 @@ bool Map::AddToMap(T* obj, bool checkTransport) //also, trigger needs to cast spell, if not update, cannot see visual obj->UpdateObjectVisibility(true); - // Xinef: little hack for vehicles, accessories have to be added after visibility update so they wont fall off the vehicle, moved from Creature::AIM_Initialize - // Initialize vehicle, this is done only for summoned npcs, DB creatures are handled by grid loaders + // Post-visibility so accessories seat after the vehicle's create packet reaches clients. if (obj->IsCreature()) if (Vehicle* vehicle = obj->ToCreature()->GetVehicleKit()) vehicle->Reset();