Core/Vehicle: Make the passenger join event asynchronous

Prevents a crash in the following scenario:
Player 1 enters vehicle X
Seat on vehicle X is taken by Player 2
Player 2 is removed from vehicle X
Vehicle X dismisses (but player 1 did not even finish AddPasssenger
call)

Fixes #4271
This commit is contained in:
Machiavelli
2013-02-16 02:11:42 +01:00
parent a75fe0b2bf
commit 60cef65b5c
3 changed files with 139 additions and 68 deletions

View File

@@ -332,6 +332,7 @@ class UnitAI;
class Totem;
class Transport;
class Vehicle;
class VehicleJoinEvent;
class TransportBase;
class SpellCastTargets;
@@ -2183,6 +2184,7 @@ class Unit : public WorldObject
uint32 GetRedirectThreatPercent() { return _redirectThreadInfo.GetThreatPct(); }
Unit* GetRedirectThreatTarget() { return _redirectThreadInfo.GetTargetGUID() ? GetUnit(*this, _redirectThreadInfo.GetTargetGUID()) : NULL; }
friend class VehicleJoinEvent;
bool IsAIEnabled, NeedChangeAI;
bool CreateVehicleKit(uint32 id, uint32 creatureEntry);
void RemoveVehicleKit();

View File

@@ -30,9 +30,10 @@
#include "SpellInfo.h"
#include "MoveSplineInit.h"
#include "TemporarySummon.h"
#include "EventProcessor.h"
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)
{
for (uint32 i = 0; i < MAX_VEHICLE_SEATS; ++i)
{
@@ -41,7 +42,7 @@ _me(unit), _vehicleInfo(vehInfo), _usableSeatNum(0), _creatureEntry(creatureEntr
{
Seats.insert(std::make_pair(i, VehicleSeat(veSeat)));
if (veSeat->CanEnterOrExit())
++_usableSeatNum;
++UsableSeatNum;
}
}
@@ -123,10 +124,12 @@ void Vehicle::Uninstall()
"Check Uninstall/PassengerBoarded script hooks for errors.", _me->GetGUIDLow(), _me->GetEntry());
return;
}
_status = STATUS_UNINSTALLING;
sLog->outDebug(LOG_FILTER_VEHICLES, "Vehicle::Uninstall Entry: %u, GuidLow: %u", _creatureEntry, _me->GetGUIDLow());
RemoveAllPassengers();
if (GetBase()->GetTypeId() == TYPEID_UNIT)
sScriptMgr->OnUninstall(this);
}
@@ -136,14 +139,14 @@ void Vehicle::Reset(bool evading /*= false*/)
sLog->outDebug(LOG_FILTER_VEHICLES, "Vehicle::Reset Entry: %u, GuidLow: %u", _creatureEntry, _me->GetGUIDLow());
if (_me->GetTypeId() == TYPEID_PLAYER)
{
if (_usableSeatNum)
if (UsableSeatNum)
_me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PLAYER_VEHICLE);
}
else
{
ApplyAllImmunities();
InstallAllAccessories(evading);
if (_usableSeatNum)
if (UsableSeatNum)
_me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK);
}
@@ -215,6 +218,19 @@ void Vehicle::RemoveAllPassengers()
// ASSERT(!itr->second.passenger);
}
void Vehicle::RemovePendingPassengers()
{
sLog->outDebug(LOG_FILTER_VEHICLES, "Vehicle::RemovePendingPassengers. Entry: %u, GuidLow: %u", _creatureEntry, _me->GetGUIDLow());
/// Setting to_Abort to true will cause @VehicleJoinEvent::Abort to be executed on next @Unit::UpdateEvents call
/// This will properly "reset" the pending join process for the passenger.
while (VehicleJoinEvent* e = _pendingJoinEvents.front())
{
e->to_Abort = true;
_pendingJoinEvents.pop_front();
}
}
bool Vehicle::HasEmptySeat(int8 seatId) const
{
SeatMap::const_iterator seat = Seats.find(seatId);
@@ -317,7 +333,17 @@ bool Vehicle::AddPassenger(Unit* unit, int8 seatId)
if (unit->GetVehicle() != this)
return false;
// The seat selection code may kick other passengers off the vehicle.
// While the validity of the following may be arguable, it is possible that when such a passenger
// exits the vehicle will dismiss. That's why the actual adding the passenger to the vehicle is scheduled
// asynchronously, so it can be cancelled easily in case the vehicle is uninstalled meanwhile.
SeatMap::iterator seat;
sLog->outDebug(LOG_FILTER_VEHICLES, "Unit %s scheduling enter vehicle entry %u id %u dbguid %u seat %d",
unit->GetName().c_str(), _me->GetEntry(), _vehicleInfo->m_ID, _me->GetGUIDLow(), (int32)seat->first);
VehicleJoinEvent* e = new VehicleJoinEvent(this, unit, seat);
unit->m_Events.AddEvent(e, unit->m_Events.CalculateTime(0));
_pendingJoinEvents.push_back(e);
if (seatId < 0) // no specific seat requirement
{
for (seat = Seats.begin(); seat != Seats.end(); ++seat)
@@ -325,13 +351,19 @@ bool Vehicle::AddPassenger(Unit* unit, int8 seatId)
break;
if (seat == Seats.end()) // no available seat
{
CancelJoinEvent(e);
return false;
}
}
else
{
seat = Seats.find(seatId);
if (seat == Seats.end())
{
CancelJoinEvent(e);
return false;
}
if (seat->second.Passenger)
{
@@ -344,65 +376,6 @@ bool Vehicle::AddPassenger(Unit* unit, int8 seatId)
ASSERT(!seat->second.Passenger);
}
sLog->outDebug(LOG_FILTER_VEHICLES, "Unit %s enter vehicle entry %u id %u dbguid %u seat %d", unit->GetName().c_str(), _me->GetEntry(), _vehicleInfo->m_ID, _me->GetGUIDLow(), (int32)seat->first);
seat->second.Passenger = unit->GetGUID();
if (seat->second.SeatInfo->CanEnterOrExit())
{
ASSERT(_usableSeatNum);
--_usableSeatNum;
if (!_usableSeatNum)
{
if (_me->GetTypeId() == TYPEID_PLAYER)
_me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PLAYER_VEHICLE);
else
_me->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK);
}
}
if (seat->second.SeatInfo->m_flags && !(seat->second.SeatInfo->m_flags & VEHICLE_SEAT_FLAG_ALLOW_TURNING))
unit->AddUnitState(UNIT_STATE_ONVEHICLE);
unit->AddUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
VehicleSeatEntry const* veSeat = seat->second.SeatInfo;
unit->m_movementInfo.t_pos.m_positionX = veSeat->m_attachmentOffsetX;
unit->m_movementInfo.t_pos.m_positionY = veSeat->m_attachmentOffsetY;
unit->m_movementInfo.t_pos.m_positionZ = veSeat->m_attachmentOffsetZ;
unit->m_movementInfo.t_pos.SetOrientation(0);
unit->m_movementInfo.t_time = 0; // 1 for player
unit->m_movementInfo.t_seat = seat->first;
unit->m_movementInfo.t_guid = _me->GetGUID();
if (_me->GetTypeId() == TYPEID_UNIT && unit->GetTypeId() == TYPEID_PLAYER &&
seat->second.SeatInfo->m_flags & VEHICLE_SEAT_FLAG_CAN_CONTROL)
ASSERT(_me->SetCharmedBy(unit, CHARM_TYPE_VEHICLE))
if (_me->IsInWorld())
{
unit->SendClearTarget(); // SMSG_BREAK_TARGET
unit->SetControlled(true, UNIT_STATE_ROOT); // SMSG_FORCE_ROOT - In some cases we send SMSG_SPLINE_MOVE_ROOT here (for creatures)
// also adds MOVEMENTFLAG_ROOT
Movement::MoveSplineInit init(unit);
init.DisableTransportPathTransformations();
init.MoveTo(veSeat->m_attachmentOffsetX, veSeat->m_attachmentOffsetY, veSeat->m_attachmentOffsetZ);
init.SetFacing(0.0f);
init.SetTransportEnter();
init.Launch();
if (_me->GetTypeId() == TYPEID_UNIT)
{
if (_me->ToCreature()->IsAIEnabled)
_me->ToCreature()->AI()->PassengerBoarded(unit, seat->first, true);
// update all passenger's positions
//Passenger's spline OR vehicle movement will update positions
//RelocatePassengers(_me->GetPositionX(), _me->GetPositionY(), _me->GetPositionZ(), _me->GetOrientation());
}
}
if (GetBase()->GetTypeId() == TYPEID_UNIT)
sScriptMgr->OnAddPassenger(this, unit, seatId);
return true;
}
@@ -419,14 +392,14 @@ void Vehicle::RemovePassenger(Unit* unit)
seat->second.Passenger = 0;
if (seat->second.SeatInfo->CanEnterOrExit())
{
if (!_usableSeatNum)
if (!UsableSeatNum)
{
if (_me->GetTypeId() == TYPEID_PLAYER)
_me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PLAYER_VEHICLE);
else
_me->SetFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK);
}
++_usableSeatNum;
++UsableSeatNum;
}
unit->ClearUnitState(UNIT_STATE_ONVEHICLE);
@@ -549,3 +522,78 @@ void Vehicle::CalculatePassengerOffset(float& x, float& y, float& z, float& o)
y = (iny - inx * tan(GetBase()->GetOrientation())) / (cos(GetBase()->GetOrientation()) + std::sin(GetBase()->GetOrientation()) * tan(GetBase()->GetOrientation()));
x = (inx + iny * tan(GetBase()->GetOrientation())) / (cos(GetBase()->GetOrientation()) + std::sin(GetBase()->GetOrientation()) * tan(GetBase()->GetOrientation()));
}
void Vehicle::CancelJoinEvent(VehicleJoinEvent* e)
{
e->to_Abort = true;
_pendingJoinEvents.pop_back();
}
bool VehicleJoinEvent::Execute(uint64, uint32)
{
ASSERT(Passenger->GetVehicle() == Target);
Seat->second.Passenger = Passenger->GetGUID();
if (Seat->second.SeatInfo->CanEnterOrExit())
{
ASSERT(Target->UsableSeatNum);
--(Target->UsableSeatNum);
if (!Target->UsableSeatNum)
{
if (Target->GetBase()->GetTypeId() == TYPEID_PLAYER)
Target->GetBase()->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_PLAYER_VEHICLE);
else
Target->GetBase()->RemoveFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_SPELLCLICK);
}
}
if (Seat->second.SeatInfo->m_flags && !(Seat->second.SeatInfo->m_flags & VEHICLE_SEAT_FLAG_ALLOW_TURNING))
Passenger->AddUnitState(UNIT_STATE_ONVEHICLE);
Passenger->AddUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT);
VehicleSeatEntry const* veSeat = Seat->second.SeatInfo;
Passenger->m_movementInfo.t_pos.m_positionX = veSeat->m_attachmentOffsetX;
Passenger->m_movementInfo.t_pos.m_positionY = veSeat->m_attachmentOffsetY;
Passenger->m_movementInfo.t_pos.m_positionZ = veSeat->m_attachmentOffsetZ;
Passenger->m_movementInfo.t_pos.SetOrientation(0.0f);
Passenger->m_movementInfo.t_time = 0; // 1 for player
Passenger->m_movementInfo.t_seat = Seat->first;
Passenger->m_movementInfo.t_guid = Target->GetBase()->GetGUID();
if (Target->GetBase()->GetTypeId() == TYPEID_UNIT && Passenger->GetTypeId() == TYPEID_PLAYER &&
Seat->second.SeatInfo->m_flags & VEHICLE_SEAT_FLAG_CAN_CONTROL)
ASSERT(Target->GetBase()->SetCharmedBy(Passenger, CHARM_TYPE_VEHICLE))
if (Target->GetBase()->IsInWorld())
{
Passenger->SendClearTarget(); // SMSG_BREAK_TARGET
Passenger->SetControlled(true, UNIT_STATE_ROOT); // SMSG_FORCE_ROOT - In some cases we send SMSG_SPLINE_MOVE_ROOT here (for creatures)
// also adds MOVEMENTFLAG_ROOT
Movement::MoveSplineInit init(Passenger);
init.DisableTransportPathTransformations();
init.MoveTo(veSeat->m_attachmentOffsetX, veSeat->m_attachmentOffsetY, veSeat->m_attachmentOffsetZ);
init.SetFacing(0.0f);
init.SetTransportEnter();
init.Launch();
if (Target->GetBase()->GetTypeId() == TYPEID_UNIT)
{
if (Target->GetBase()->ToCreature()->IsAIEnabled)
Target->GetBase()->ToCreature()->AI()->PassengerBoarded(Passenger, Seat->first, true);
// update all passenger's positions
//Passenger's spline OR vehicle movement will update positions
//RelocatePassengers(_me->GetPositionX(), _me->GetPositionY(), _me->GetPositionZ(), _me->GetOrientation());
}
}
if (Target->GetBase()->GetTypeId() == TYPEID_UNIT)
sScriptMgr->OnAddPassenger(Target, Passenger, Seat->first);
return true;
}
void VehicleJoinEvent::Abort(uint64)
{
Passenger->m_vehicle = NULL;
}

View File

@@ -23,12 +23,12 @@
#include "Object.h"
#include "VehicleDefines.h"
#include "Unit.h"
#include <deque>
struct VehicleEntry;
class Unit;
typedef std::set<uint64> GuidSet;
class VehicleJoinEvent;
class Vehicle : public TransportBase
{
@@ -54,6 +54,7 @@ class Vehicle : public TransportBase
void RemovePassenger(Unit* passenger);
void RelocatePassengers();
void RemoveAllPassengers();
void RemovePendingPassengers();
void Dismiss();
void TeleportVehicle(float x, float y, float z, float ang);
bool IsVehicleInUse() { return Seats.begin() != Seats.end(); }
@@ -65,6 +66,10 @@ class Vehicle : public TransportBase
VehicleSeatEntry const* GetSeatForPassenger(Unit* passenger);
protected:
friend class VehicleJoinEvent;
uint32 UsableSeatNum; // Number of seats that match VehicleSeatEntry::UsableByPlayer, used for proper display flags
protected:
friend bool Unit::CreateVehicleKit(uint32 id, uint32 creatureEntry);
Vehicle(Unit* unit, VehicleEntry const* vehInfo, uint32 creatureEntry);
@@ -91,9 +96,25 @@ class Vehicle : public TransportBase
Unit* _me;
VehicleEntry const* _vehicleInfo;
GuidSet vehiclePlayers;
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;
Position m_lastShootPos;
std::deque<VehicleJoinEvent*> _pendingJoinEvents;
void CancelJoinEvent(VehicleJoinEvent* e);
};
class VehicleJoinEvent : public BasicEvent
{
friend class Vehicle;
protected:
VehicleJoinEvent(Vehicle* v, Unit* u, SeatMap::iterator& itr) : Target(v), Passenger(u), Seat(itr) {}
bool Execute(uint64, uint32);
void Abort(uint64);
Vehicle* Target;
Unit* Passenger;
SeatMap::iterator& Seat;
};
#endif