2 * Copyright (C) 2005-2013 MaNGOS <http://getmangos.com/>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 * @addtogroup TransportSystem
24 * This file contains the code needed for CMaNGOS to support vehicles
25 * Currently implemented
26 * - Board to board a passenger onto a vehicle (includes checks)
27 * - Unboard to unboard a passenger from the vehicle
28 * - SwitchSeat to switch to another seat of the same vehicle
29 * - CanBoard to check if a passenger can board a vehicle
30 * - Internal helper to set the controlling and spells for a vehicle's seat
31 * - Internal helper to control the available seats of a vehicle
35 #include "SharedDefines.h"
36 #include "ObjectGuid.h"
40 #include "ObjectMgr.h"
41 #include "SQLStorages.h"
44 #include "movement/MoveSplineInit.h"
45 #include "movement/MoveSpline.h"
46 #include "MapManager.h"
47 #include "TemporarySummon.h"
49 void ObjectMgr::LoadVehicleAccessory()
51 sVehicleAccessoryStorage
.Load();
53 sLog
.outString(">> Loaded %u vehicle accessories", sVehicleAccessoryStorage
.GetRecordCount());
57 for (SQLMultiStorage::SQLSIterator
<VehicleAccessory
> itr
= sVehicleAccessoryStorage
.getDataBegin
<VehicleAccessory
>(); itr
< sVehicleAccessoryStorage
.getDataEnd
<VehicleAccessory
>(); ++itr
)
59 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(itr
->vehicleEntry
))
61 sLog
.outErrorDb("Table `vehicle_accessory` has entry (vehicle entry: %u, seat %u, passenger %u) where vehicle_entry is invalid, skip vehicle.", itr
->vehicleEntry
, itr
->seatId
, itr
->passengerEntry
);
62 sVehicleAccessoryStorage
.EraseEntry(itr
->vehicleEntry
);
65 if (!sCreatureStorage
.LookupEntry
<CreatureInfo
>(itr
->passengerEntry
))
67 sLog
.outErrorDb("Table `vehicle_accessory` has entry (vehicle entry: %u, seat %u, passenger %u) where accessory_entry is invalid, skip vehicle.", itr
->vehicleEntry
, itr
->seatId
, itr
->passengerEntry
);
68 sVehicleAccessoryStorage
.EraseEntry(itr
->vehicleEntry
);
71 if (itr
->seatId
>= MAX_VEHICLE_SEAT
)
73 sLog
.outErrorDb("Table `vehicle_accessory` has entry (vehicle entry: %u, seat %u, passenger %u) where seat is invalid (must be between 0 and %u), skip vehicle.", itr
->vehicleEntry
, itr
->seatId
, itr
->passengerEntry
, MAX_VEHICLE_SEAT
- 1);
74 sVehicleAccessoryStorage
.EraseEntry(itr
->vehicleEntry
);
81 * Constructor of VehicleInfo
83 * @param owner MUST be provided owner of the vehicle (type Unit)
84 * @param vehicleEntry MUST be provided dbc-entry of the vehicle
85 * @param overwriteNpcEntry Use to overwrite the GetEntry() result for selecting associated passengers
87 * This function will initialise the VehicleInfo of the vehicle owner
88 * Also the seat-map is created here
90 VehicleInfo::VehicleInfo(Unit
* owner
, VehicleEntry
const* vehicleEntry
, uint32 overwriteNpcEntry
) : TransportBase(owner
),
91 m_vehicleEntry(vehicleEntry
),
94 m_overwriteNpcEntry(overwriteNpcEntry
),
95 m_isInitialized(false)
97 MANGOS_ASSERT(vehicleEntry
);
99 // Initial fill of available seats for the vehicle
100 for (uint8 i
= 0; i
< MAX_VEHICLE_SEAT
; ++i
)
102 if (uint32 seatId
= vehicleEntry
->m_seatID
[i
])
104 if (VehicleSeatEntry
const* seatEntry
= sVehicleSeatStore
.LookupEntry(seatId
))
106 m_vehicleSeats
.insert(VehicleSeatMap::value_type(i
, seatEntry
));
108 if (IsUsableSeatForCreature(seatEntry
->m_flags
))
109 m_creatureSeats
|= 1 << i
;
111 if (IsUsableSeatForPlayer(seatEntry
->m_flags
))
112 m_playerSeats
|= 1 << i
;
118 VehicleInfo::~VehicleInfo()
120 ((Unit
*)m_owner
)->RemoveSpellsCausingAura(SPELL_AURA_CONTROL_VEHICLE
);
122 RemoveAccessoriesFromMap(); // Remove accessories (for example required with player vehicles)
125 void VehicleInfo::Initialize()
127 if (!m_overwriteNpcEntry
)
128 m_overwriteNpcEntry
= m_owner
->GetEntry();
130 // Loading passengers (rough version only!)
131 SQLMultiStorage::SQLMSIteratorBounds
<VehicleAccessory
> bounds
= sVehicleAccessoryStorage
.getBounds
<VehicleAccessory
>(m_overwriteNpcEntry
);
132 for (SQLMultiStorage::SQLMultiSIterator
<VehicleAccessory
> itr
= bounds
.first
; itr
!= bounds
.second
; ++itr
)
134 if (Creature
* summoned
= m_owner
->SummonCreature(itr
->passengerEntry
, m_owner
->GetPositionX(), m_owner
->GetPositionY(), m_owner
->GetPositionZ(), m_owner
->GetOrientation(), TEMPSUMMON_DEAD_DESPAWN
, 0))
136 m_accessoryGuids
.insert(summoned
->GetObjectGuid());
137 int32 basepoint0
= itr
->seatId
+ 1;
138 summoned
->CastCustomSpell((Unit
*)m_owner
, SPELL_RIDE_VEHICLE_HARDCODED
, &basepoint0
, NULL
, NULL
, true);
141 m_isInitialized
= true;
145 * This function will board a passenger onto a vehicle
147 * @param passenger MUST be provided. This Unit will be boarded onto the vehicles (if it checks out)
148 * @param seat Seat to which the passenger will be boarded (if can, elsewise an alternative will be selected if possible)
150 void VehicleInfo::Board(Unit
* passenger
, uint8 seat
)
152 MANGOS_ASSERT(passenger
);
154 DEBUG_LOG("VehicleInfo::Board: Try to board passenger %s to seat %u", passenger
->GetGuidStr().c_str(), seat
);
156 // This check is also called in Spell::CheckCast()
157 if (!CanBoard(passenger
))
160 // Use the planned seat only if the seat is valid, possible to choose and empty
161 if (!IsSeatAvailableFor(passenger
, seat
))
162 if (!GetUsableSeatFor(passenger
, seat
))
165 VehicleSeatEntry
const* seatEntry
= GetSeatEntry(seat
);
166 MANGOS_ASSERT(seatEntry
);
168 // ToDo: Unboard passenger from a MOTransport when they are properly implemented
169 /*if (TransportInfo* transportInfo = passenger->GetTransportInfo())
171 WorldObject* transporter = transportInfo->GetTransport();
173 // Must be a MO transporter
174 MANGOS_ASSERT(transporter->GetObjectGuid().IsMOTransport());
176 ((Transport*)transporter)->UnBoardPassenger(passenger);
179 DEBUG_LOG("VehicleInfo::Board: Board passenger: %s to seat %u", passenger
->GetGuidStr().c_str(), seat
);
181 // Calculate passengers local position
182 float lx
, ly
, lz
, lo
;
183 CalculateBoardingPositionOf(passenger
->GetPositionX(), passenger
->GetPositionY(), passenger
->GetPositionZ(), passenger
->GetOrientation(), lx
, ly
, lz
, lo
);
185 BoardPassenger(passenger
, lx
, ly
, lz
, lo
, seat
); // Use TransportBase to store the passenger
187 // Set data for createobject packets
188 passenger
->m_movementInfo
.SetTransportData(m_owner
->GetObjectGuid(), lx
, ly
, lz
, lo
, 0, seat
);
190 if (passenger
->GetTypeId() == TYPEID_PLAYER
)
192 Player
* pPlayer
= (Player
*)passenger
;
193 pPlayer
->RemovePet(PET_SAVE_AS_CURRENT
);
195 WorldPacket
data(SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA
);
196 pPlayer
->GetSession()->SendPacket(&data
);
198 // SMSG_BREAK_TARGET (?)
201 if (!passenger
->IsRooted())
202 passenger
->SetRoot(true);
204 Movement::MoveSplineInit
init(*passenger
);
205 init
.MoveTo(0.0f
, 0.0f
, 0.0f
); // ToDo: Set correct local coords
206 init
.SetFacing(0.0f
); // local orientation ? ToDo: Set proper orientation!
207 init
.SetBoardVehicle();
210 // Apply passenger modifications
211 ApplySeatMods(passenger
, seatEntry
->m_flags
);
215 * This function will switch the seat of a passenger on the same vehicle
217 * @param passenger MUST be provided. This Unit will change its seat on the vehicle
218 * @param seat Seat to which the passenger will be switched
220 void VehicleInfo::SwitchSeat(Unit
* passenger
, uint8 seat
)
222 MANGOS_ASSERT(passenger
);
224 DEBUG_LOG("VehicleInfo::SwitchSeat: passenger: %s try to switch to seat %u", passenger
->GetGuidStr().c_str(), seat
);
226 // Switching seats is not possible
227 if (m_vehicleEntry
->m_flags
& VEHICLE_FLAG_DISABLE_SWITCH
)
230 PassengerMap::const_iterator itr
= m_passengers
.find(passenger
);
231 MANGOS_ASSERT(itr
!= m_passengers
.end());
233 // We are already boarded to this seat
234 if (itr
->second
->GetTransportSeat() == seat
)
237 // Check if it's a valid seat
238 if (!IsSeatAvailableFor(passenger
, seat
))
241 VehicleSeatEntry
const* seatEntry
= GetSeatEntry(itr
->second
->GetTransportSeat());
242 MANGOS_ASSERT(seatEntry
);
244 // Switching seats is only allowed if this flag is set
245 if (~seatEntry
->m_flags
& SEAT_FLAG_CAN_SWITCH
)
248 // Remove passenger modifications of the old seat
249 RemoveSeatMods(passenger
, seatEntry
->m_flags
);
252 itr
->second
->SetTransportSeat(seat
);
254 Movement::MoveSplineInit
init(*passenger
);
255 init
.MoveTo(0.0f
, 0.0f
, 0.0f
); // ToDo: Set correct local coords
256 //if (oldorientation != neworientation) (?)
257 //init.SetFacing(0.0f); // local orientation ? ToDo: Set proper orientation!
258 // It seems that Seat switching is sent without SplineFlag BoardVehicle
261 // Get seatEntry of new seat
262 seatEntry
= GetSeatEntry(seat
);
263 MANGOS_ASSERT(seatEntry
);
265 // Apply passenger modifications of the new seat
266 ApplySeatMods(passenger
, seatEntry
->m_flags
);
270 * This function will Unboard a passenger
272 * @param passenger MUST be provided. This Unit will be unboarded from the vehicle
273 * @param changeVehicle If set, the passenger is expected to be directly boarded to another vehicle,
274 * and hence he will not be unboarded but only removed from this vehicle.
276 void VehicleInfo::UnBoard(Unit
* passenger
, bool changeVehicle
)
278 MANGOS_ASSERT(passenger
);
280 DEBUG_LOG("VehicleInfo::Unboard: passenger: %s", passenger
->GetGuidStr().c_str());
282 PassengerMap::const_iterator itr
= m_passengers
.find(passenger
);
283 MANGOS_ASSERT(itr
!= m_passengers
.end());
285 VehicleSeatEntry
const* seatEntry
= GetSeatEntry(itr
->second
->GetTransportSeat());
286 MANGOS_ASSERT(seatEntry
);
288 UnBoardPassenger(passenger
); // Use TransportBase to remove the passenger from storage list
290 if (!changeVehicle
) // Send expected unboarding packages
292 // Update movementInfo
293 passenger
->m_movementInfo
.ClearTransportData();
295 if (passenger
->GetTypeId() == TYPEID_PLAYER
)
297 Player
* pPlayer
= (Player
*)passenger
;
298 pPlayer
->ResummonPetTemporaryUnSummonedIfAny();
300 // SMSG_PET_DISMISS_SOUND (?)
303 if (passenger
->IsRooted())
304 passenger
->SetRoot(false);
306 Movement::MoveSplineInit
init(*passenger
);
307 // ToDo: Set proper unboard coordinates
308 init
.MoveTo(m_owner
->GetPositionX(), m_owner
->GetPositionY(), m_owner
->GetPositionZ());
309 init
.SetExitVehicle();
312 // Despawn if passenger was accessory
313 if (passenger
->GetTypeId() == TYPEID_UNIT
&& m_accessoryGuids
.find(passenger
->GetObjectGuid()) != m_accessoryGuids
.end())
315 // TODO Same TODO as in VehicleInfo::RemoveAccessoriesFromMap
316 ((Creature
*)passenger
)->ForcedDespawn(5000);
317 m_accessoryGuids
.erase(passenger
->GetObjectGuid());
321 // Remove passenger modifications
322 RemoveSeatMods(passenger
, seatEntry
->m_flags
);
324 // Some creature vehicles get despawned after passenger unboarding
325 if (m_owner
->GetTypeId() == TYPEID_UNIT
)
327 // TODO: Guesswork, but seems to be fairly near correct
328 // Only if the passenger was on control seat? Also depending on some flags
329 if ((seatEntry
->m_flags
& SEAT_FLAG_CAN_CONTROL
) &&
330 !(m_vehicleEntry
->m_flags
& (VEHICLE_FLAG_UNK4
| VEHICLE_FLAG_UNK20
)))
332 if (((Creature
*)m_owner
)->IsTemporarySummon())
333 ((Creature
*)m_owner
)->ForcedDespawn(1000);
339 * This function will check if a passenger can be boarded
341 * @param passenger Unit that attempts to board onto a vehicle
343 bool VehicleInfo::CanBoard(Unit
* passenger
) const
348 // Passenger is this vehicle
349 if (passenger
== m_owner
)
352 // Passenger is already on this vehicle (in this case switching seats is required)
353 if (passenger
->IsBoarded() && passenger
->GetTransportInfo()->GetTransport() == m_owner
)
356 // Prevent circular boarding: passenger (could only be vehicle) must not have m_owner on board
357 if (passenger
->IsVehicle() && passenger
->GetVehicleInfo()->HasOnBoard(m_owner
))
360 // Check if we have at least one empty seat
361 if (!GetEmptySeats())
364 // Passenger is already boarded
365 if (m_passengers
.find(passenger
) != m_passengers
.end())
368 // Check for empty player seats
369 if (passenger
->GetTypeId() == TYPEID_PLAYER
)
370 return GetEmptySeatsMask() & m_playerSeats
;
372 // Check for empty creature seats
373 return GetEmptySeatsMask() & m_creatureSeats
;
376 Unit
* VehicleInfo::GetPassenger(uint8 seat
) const
378 for (PassengerMap::const_iterator itr
= m_passengers
.begin(); itr
!= m_passengers
.end(); ++itr
)
379 if (itr
->second
->GetTransportSeat() == seat
)
380 return (Unit
*)itr
->first
;
385 // Helper function to undo the turning of the vehicle to calculate a relative position of the passenger when boarding
386 void VehicleInfo::CalculateBoardingPositionOf(float gx
, float gy
, float gz
, float go
, float& lx
, float& ly
, float& lz
, float& lo
) const
388 NormalizeRotatedPosition(gx
- m_owner
->GetPositionX(), gy
- m_owner
->GetPositionY(), lx
, ly
);
390 lz
= gz
- m_owner
->GetPositionZ();
391 lo
= NormalizeOrientation(go
- m_owner
->GetOrientation());
394 void VehicleInfo::RemoveAccessoriesFromMap()
396 // Remove all accessories
397 for (GuidSet::const_iterator itr
= m_accessoryGuids
.begin(); itr
!= m_accessoryGuids
.end(); ++itr
)
399 if (Creature
* pAccessory
= m_owner
->GetMap()->GetCreature(*itr
))
401 // TODO - unclear how long to despawn, also maybe some flag etc depending
402 pAccessory
->ForcedDespawn(5000);
405 m_accessoryGuids
.clear();
406 m_isInitialized
= false;
409 /* ************************************************************************************************
410 * Helper function for seat control
411 * ***********************************************************************************************/
413 /// Get the Vehicle SeatEntry of a seat by position
414 VehicleSeatEntry
const* VehicleInfo::GetSeatEntry(uint8 seat
) const
416 VehicleSeatMap::const_iterator itr
= m_vehicleSeats
.find(seat
);
417 return itr
!= m_vehicleSeats
.end() ? itr
->second
: NULL
;
421 * This function will get a usable seat for a passenger
423 * @param passenger MUST be provided. Unit for which to try to get a free seat
424 * @param seat will contain an available seat if returned true
425 * @return return TRUE if and only if an available seat was found. In this case @seat will contain the id
427 bool VehicleInfo::GetUsableSeatFor(Unit
* passenger
, uint8
& seat
) const
429 MANGOS_ASSERT(passenger
);
431 uint8 possibleSeats
= (passenger
->GetTypeId() == TYPEID_PLAYER
) ? (GetEmptySeatsMask() & m_playerSeats
) : (GetEmptySeatsMask() & m_creatureSeats
);
433 // No usable seats available
440 for (uint8 i
= 1; seat
< MAX_VEHICLE_SEAT
; i
<<= 1, ++seat
)
441 if (possibleSeats
& i
)
447 /// Returns if a @passenger could board onto @seat - @passenger MUST be provided
448 bool VehicleInfo::IsSeatAvailableFor(Unit
* passenger
, uint8 seat
) const
450 MANGOS_ASSERT(passenger
);
452 return seat
< MAX_VEHICLE_SEAT
&&
453 (GetEmptySeatsMask() & (passenger
->GetTypeId() == TYPEID_PLAYER
? m_playerSeats
: m_creatureSeats
) & (1 << seat
));
456 /// Wrapper to collect all taken seats
457 uint8
VehicleInfo::GetTakenSeatsMask() const
459 uint8 takenSeatsMask
= 0;
461 for (PassengerMap::const_iterator itr
= m_passengers
.begin(); itr
!= m_passengers
.end(); ++itr
)
462 takenSeatsMask
|= 1 << itr
->second
->GetTransportSeat();
464 return takenSeatsMask
;
467 bool VehicleInfo:: IsUsableSeatForPlayer(uint32 seatFlags
) const
469 return seatFlags
& SEAT_FLAG_USABLE
;
472 /// Add control and such modifiers to a passenger if required
473 void VehicleInfo::ApplySeatMods(Unit
* passenger
, uint32 seatFlags
)
475 Unit
* pVehicle
= (Unit
*)m_owner
; // Vehicles are alawys Unit
477 if (passenger
->GetTypeId() == TYPEID_PLAYER
)
479 Player
* pPlayer
= (Player
*)passenger
;
481 if (seatFlags
& SEAT_FLAG_CAN_CONTROL
)
483 pPlayer
->GetCamera().SetView(pVehicle
);
485 pPlayer
->SetCharm(pVehicle
);
486 pVehicle
->SetCharmerGuid(pPlayer
->GetObjectGuid());
488 pVehicle
->addUnitState(UNIT_STAT_CONTROLLED
);
489 pVehicle
->SetFlag(UNIT_FIELD_FLAGS
, UNIT_FLAG_PLAYER_CONTROLLED
);
491 pPlayer
->SetClientControl(pVehicle
, 1);
492 pPlayer
->SetMover(pVehicle
);
494 // Unconfirmed - default speed handling
495 if (pVehicle
->GetTypeId() == TYPEID_UNIT
)
497 if (!pPlayer
->IsWalking() && pVehicle
->IsWalking())
499 ((Creature
*)pVehicle
)->SetWalk(false, true);
501 else if (pPlayer
->IsWalking() && !pVehicle
->IsWalking())
503 ((Creature
*)pVehicle
)->SetWalk(true, true);
508 if (seatFlags
& (SEAT_FLAG_USABLE
| SEAT_FLAG_CAN_CAST
))
510 CharmInfo
* charmInfo
= pVehicle
->InitCharmInfo(pVehicle
);
511 charmInfo
->InitVehicleCreateSpells();
513 pPlayer
->PossessSpellInitialize();
516 else if (passenger
->GetTypeId() == TYPEID_UNIT
)
518 if (seatFlags
& SEAT_FLAG_CAN_CONTROL
)
520 passenger
->SetCharm(pVehicle
);
521 pVehicle
->SetCharmerGuid(passenger
->GetObjectGuid());
526 /// Remove control and such modifiers to a passenger if they were added
527 void VehicleInfo::RemoveSeatMods(Unit
* passenger
, uint32 seatFlags
)
529 Unit
* pVehicle
= (Unit
*)m_owner
;
531 if (passenger
->GetTypeId() == TYPEID_PLAYER
)
533 Player
* pPlayer
= (Player
*)passenger
;
535 if (seatFlags
& SEAT_FLAG_CAN_CONTROL
)
537 pPlayer
->SetCharm(NULL
);
538 pVehicle
->SetCharmerGuid(ObjectGuid());
540 pPlayer
->SetClientControl(pVehicle
, 0);
541 pPlayer
->SetMover(NULL
);
543 pVehicle
->clearUnitState(UNIT_STAT_CONTROLLED
);
544 pVehicle
->RemoveFlag(UNIT_FIELD_FLAGS
, UNIT_FLAG_PLAYER_CONTROLLED
);
546 // must be called after movement control unapplying
547 pPlayer
->GetCamera().ResetView();
550 if (seatFlags
& (SEAT_FLAG_USABLE
| SEAT_FLAG_CAN_CAST
))
551 pPlayer
->RemovePetActionBar();
553 else if (passenger
->GetTypeId() == TYPEID_UNIT
)
555 if (seatFlags
& SEAT_FLAG_CAN_CONTROL
)
557 passenger
->SetCharm(NULL
);
558 pVehicle
->SetCharmerGuid(ObjectGuid());