1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
|
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Vehicle.h"
#include "Battleground.h"
#include "CharmInfo.h"
#include "Common.h"
#include "CreatureAI.h"
#include "DB2Stores.h"
#include "EventProcessor.h"
#include "Log.h"
#include "MotionMaster.h"
#include "MoveSplineInit.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "SpellAuraEffects.h"
#include "TemporarySummon.h"
#include "Unit.h"
#include <sstream>
Vehicle::Vehicle(Unit* unit, VehicleEntry const* vehInfo, uint32 creatureEntry) :
UsableSeatNum(0), _me(unit), _vehicleInfo(vehInfo), _creatureEntry(creatureEntry), _status(STATUS_NONE)
{
for (int8 i = 0; i < MAX_VEHICLE_SEATS; ++i)
{
if (uint32 seatId = _vehicleInfo->SeatID[i])
if (VehicleSeatEntry const* veSeat = sVehicleSeatStore.LookupEntry(seatId))
{
VehicleSeatAddon const* addon = sObjectMgr->GetVehicleSeatAddon(seatId);
Seats.insert(std::make_pair(i, VehicleSeat(veSeat, addon)));
if (veSeat->CanEnterOrExit())
++UsableSeatNum;
}
}
// Set or remove correct flags based on available seats. Will overwrite db data (if wrong).
if (UsableSeatNum)
_me->SetNpcFlag((_me->GetTypeId() == TYPEID_PLAYER ? UNIT_NPC_FLAG_PLAYER_VEHICLE : UNIT_NPC_FLAG_SPELLCLICK));
else if (!unit->m_unitData->InteractSpellID)
_me->RemoveNpcFlag((_me->GetTypeId() == TYPEID_PLAYER ? UNIT_NPC_FLAG_PLAYER_VEHICLE : UNIT_NPC_FLAG_SPELLCLICK));
InitMovementInfoForBase();
}
Vehicle::~Vehicle()
{
/// @Uninstall must be called before this.
ASSERT(_status == STATUS_UNINSTALLING);
for (SeatMap::const_iterator itr = Seats.begin(); itr != Seats.end(); ++itr)
ASSERT(itr->second.IsEmpty());
}
/**
* @fn void Vehicle::Install()
*
* @brief Initializes power type for vehicle. Nothing more.
*
* @author Machiavelli
* @date 17-2-2013
*/
void Vehicle::Install()
{
_status = STATUS_INSTALLED;
if (GetBase()->GetTypeId() == TYPEID_UNIT)
sScriptMgr->OnInstall(this);
}
void Vehicle::InstallAllAccessories(bool evading)
{
if (GetBase()->GetTypeId() == TYPEID_PLAYER || !evading)
RemoveAllPassengers(); // We might have aura's saved in the DB with now invalid casters - remove
VehicleAccessoryList const* accessories = sObjectMgr->GetVehicleAccessoryList(this);
if (!accessories)
return;
for (VehicleAccessoryList::const_iterator itr = accessories->begin(); itr != accessories->end(); ++itr)
if (!evading || itr->IsMinion) // only install minions on evade mode
InstallAccessory(itr->AccessoryEntry, itr->SeatId, itr->IsMinion, itr->SummonedType, itr->SummonTime, itr->RideSpellID);
}
/**
* @fn void Vehicle::Uninstall()
*
* @brief Removes all passengers and sets status to STATUS_UNINSTALLING.
* No new passengers can be added to the vehicle after this call.
*
* @author Machiavelli
* @date 17-2-2013
*/
void Vehicle::Uninstall()
{
/// @Prevent recursive uninstall call. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
if (_status == STATUS_UNINSTALLING && !GetBase()->HasUnitTypeMask(UNIT_MASK_MINION))
{
TC_LOG_ERROR("entities.vehicle", "Vehicle {} Entry: {} attempts to uninstall, but already has STATUS_UNINSTALLING! "
"Check Uninstall/PassengerBoarded script hooks for errors.", _me->GetGUID().ToString(), _creatureEntry);
return;
}
_status = STATUS_UNINSTALLING;
TC_LOG_DEBUG("entities.vehicle", "Vehicle::Uninstall Entry: {}, {}", _creatureEntry, _me->GetGUID().ToString());
RemoveAllPassengers();
if (GetBase()->GetTypeId() == TYPEID_UNIT)
sScriptMgr->OnUninstall(this);
}
/**
* @fn void Vehicle::Reset(bool evading )
*
* @brief Reapplies immunities and reinstalls accessories. Only has effect for creatures.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param evading true if called from CreatureAI::EnterEvadeMode
*/
void Vehicle::Reset(bool evading /*= false*/)
{
if (GetBase()->GetTypeId() != TYPEID_UNIT)
return;
TC_LOG_DEBUG("entities.vehicle", "Vehicle::Reset (Entry: {}, {}, DBGuid: {})", GetCreatureEntry(), _me->GetGUID().ToString(), _me->ToCreature()->GetSpawnId());
ApplyAllImmunities();
if (GetBase()->IsAlive())
InstallAllAccessories(evading);
sScriptMgr->OnReset(this);
}
/**
* @fn void Vehicle::ApplyAllImmunities()
*
* @brief Applies specific immunities that cannot be set in DB.
*
* @author Machiavelli
* @date 17-2-2013
*/
void Vehicle::ApplyAllImmunities()
{
// This couldn't be done in DB, because some spells have MECHANIC_NONE
// Vehicles should be immune on Knockback ...
_me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, true);
_me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, true);
// Mechanical units & vehicles ( which are not Bosses, they have own immunities in DB ) should be also immune on healing ( exceptions in switch below )
if (_me->ToCreature() && _me->ToCreature()->GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL && !_me->ToCreature()->isWorldBoss())
{
// Heal & dispel ...
_me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEAL, true);
_me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_HEAL_PCT, true);
_me->ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_DISPEL, true);
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_PERIODIC_HEAL, true);
// ... Shield & Immunity grant spells ...
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_SCHOOL_IMMUNITY, true);
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_UNATTACKABLE, true);
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_SCHOOL_ABSORB, true);
_me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_BANISH, true);
_me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_SHIELD, true);
_me->ApplySpellImmune(0, IMMUNITY_MECHANIC, MECHANIC_IMMUNE_SHIELD, true);
// ... Resistance, Split damage, Change stats ...
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_DAMAGE_SHIELD, true);
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_SPLIT_DAMAGE_PCT, true);
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_RESISTANCE, true);
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_STAT, true);
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, true);
}
// Different immunities for vehicles goes below
switch (GetVehicleInfo()->ID)
{
// code below prevents a bug with movable cannons
case 160: // Strand of the Ancients
case 244: // Wintergrasp
case 510: // Isle of Conquest
case 452: // Isle of Conquest
case 543: // Isle of Conquest
_me->SetControlled(true, UNIT_STATE_ROOT);
// why we need to apply this? we can simple add immunities to slow mechanic in DB
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DECREASE_SPEED, true);
break;
case 335: // Salvaged Chopper
case 336: // Salvaged Siege Engine
case 338: // Salvaged Demolisher
_me->ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN, false); // Battering Ram
break;
default:
break;
}
}
/**
* @fn void Vehicle::RemoveAllPassengers()
*
* @brief Removes all current and pending passengers from the vehicle.
*
* @author Machiavelli
* @date 17-2-2013
*/
void Vehicle::RemoveAllPassengers()
{
TC_LOG_DEBUG("entities.vehicle", "Vehicle::RemoveAllPassengers. Entry: {}, {}", _creatureEntry, _me->GetGUID().ToString());
/// 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.
{
/// Update vehicle pointer in every pending join event - Abort may be called after vehicle is deleted
Vehicle* eventVehicle = _status != STATUS_UNINSTALLING ? this : nullptr;
while (!_pendingJoinEvents.empty())
{
VehicleJoinEvent* e = _pendingJoinEvents.front();
e->ScheduleAbort();
e->Target = eventVehicle;
_pendingJoinEvents.pop_front();
}
}
// Passengers always cast an aura with SPELL_AURA_CONTROL_VEHICLE on the vehicle
// We just remove the aura and the unapply handler will make the target leave the vehicle.
// We don't need to iterate over Seats
_me->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE);
// Aura script might cause the vehicle to be despawned in the middle of handling SPELL_AURA_CONTROL_VEHICLE removal
// In that case, aura effect has already been unregistered but passenger may still be found in Seats
for (auto const& [_, seat] : Seats)
if (Unit* passenger = ObjectAccessor::GetUnit(*_me, seat.Passenger.Guid))
passenger->_ExitVehicle();
}
/**
* @fn bool Vehicle::HasEmptySeat(int8 seatId) const
*
* @brief Checks if vehicle's seat specified by 'seatId' is empty.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param seatId Identifier for the seat.
*
* @return true if empty seat, false if not.
*/
bool Vehicle::HasEmptySeat(int8 seatId) const
{
SeatMap::const_iterator seat = Seats.find(seatId);
if (seat == Seats.end())
return false;
return seat->second.IsEmpty();
}
/**
* @fn Unit* Vehicle::GetPassenger(int8 seatId) const
*
* @brief Gets a passenger on specified seat.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param seatId Seat to look on.
*
* @return null if it not found, else pointer to passenger if in world
*/
Unit* Vehicle::GetPassenger(int8 seatId) const
{
SeatMap::const_iterator seat = Seats.find(seatId);
if (seat == Seats.end())
return nullptr;
return ObjectAccessor::GetUnit(*GetBase(), seat->second.Passenger.Guid);
}
/**
* @fn SeatMap::const_iterator Vehicle::GetNextEmptySeat(int8 seatId, bool next) const
*
* @brief Gets the next empty seat based on current seat.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param seatId Identifier for the current seat.
* @param next true if iterating forward, false means iterating backwards.
*
* @return The next empty seat.
*/
SeatMap::const_iterator Vehicle::GetNextEmptySeat(int8 seatId, bool next) const
{
SeatMap::const_iterator seat = Seats.find(seatId);
if (seat == Seats.end())
return seat;
while (!seat->second.IsEmpty() || HasPendingEventForSeat(seat->first) || (!seat->second.SeatInfo->CanEnterOrExit() && !seat->second.SeatInfo->IsUsableByOverride()))
{
if (next)
{
if (++seat == Seats.end())
seat = Seats.begin();
}
else
{
if (seat == Seats.begin())
seat = Seats.end();
--seat;
}
// Make sure we don't loop indefinetly
if (seat->first == seatId)
return Seats.end();
}
return seat;
}
/**
* @fn VehicleSeatAddon const* Vehicle::GetSeatAddonForSeatOfPassenger(Unit const* passenger) const
*
* @brief Gets the vehicle seat addon data for the seat of a passenger
*
* @author Ovahlord
* @date 28-1-2020
*
* @param passenger Identifier for the current seat user
*
* @return The seat addon data for the currently used seat of a passenger
*/
VehicleSeatAddon const* Vehicle::GetSeatAddonForSeatOfPassenger(Unit const* passenger) const
{
for (SeatMap::const_iterator itr = Seats.begin(); itr != Seats.end(); itr++)
if (!itr->second.IsEmpty() && itr->second.Passenger.Guid == passenger->GetGUID())
return itr->second.SeatAddon;
return nullptr;
}
/**
* @fn void Vehicle::InstallAccessory(uint32 entry, int8 seatId, bool minion, uint8 type,
* uint32 summonTime)
*
* @brief Installs an accessory.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param entry The NPC entry of accessory.
* @param seatId Identifier for the seat to add the accessory to.
* @param minion true if accessory considered a 'minion'. Implies that the accessory will despawn when the vehicle despawns.
* Essentially that it has no life without the vehicle. Their fates are bound.
* @param type See enum @SummonType.
* @param summonTime Time after which the minion is despawned in case of a timed despawn @type specified.
*/
void Vehicle::InstallAccessory(uint32 entry, int8 seatId, bool minion, uint8 type, uint32 summonTime, Optional<uint32> rideSpellId /*= {}*/)
{
/// @Prevent adding accessories when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
if (_status == STATUS_UNINSTALLING)
{
TC_LOG_ERROR("entities.vehicle", "Vehicle ({}, Entry: {}) attempts to install accessory (Entry: {}) on seat {} with STATUS_UNINSTALLING! "
"Check Uninstall/PassengerBoarded script hooks for errors.", _me->GetGUID().ToString(),
GetCreatureEntry(), entry, (int32)seatId);
return;
}
TC_LOG_DEBUG("entities.vehicle", "Vehicle ({}, Entry {}): installing accessory (Entry: {}) on seat: {}",
_me->GetGUID().ToString(), GetCreatureEntry(), entry, (int32)seatId);
TempSummon* accessory = _me->SummonCreature(entry, *_me, TempSummonType(type), Milliseconds(summonTime));
ASSERT(accessory);
if (minion)
accessory->AddUnitTypeMask(UNIT_MASK_ACCESSORY);
if (rideSpellId)
_me->HandleSpellClick(accessory, seatId, *rideSpellId);
else
_me->HandleSpellClick(accessory, seatId);
/// If for some reason adding accessory to vehicle fails it will unsummon in
/// @VehicleJoinEvent::Abort
}
/**
* @fn bool Vehicle::AddPassenger(Unit* unit, int8 seatId)
*
* @brief Attempts to add a passenger to the vehicle on 'seatId'.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param unit The prospective passenger.
* @param seatId Identifier for the seat. Value of -1 indicates the next available seat.
*
* @return true if it succeeds, false if it fails.
*/
bool Vehicle::AddVehiclePassenger(Unit* unit, int8 seatId)
{
/// @Prevent adding passengers when vehicle is uninstalling. (Bad script in OnUninstall/OnRemovePassenger/PassengerBoarded hook.)
if (_status == STATUS_UNINSTALLING)
{
TC_LOG_ERROR("entities.vehicle", "Passenger {}, attempting to board vehicle {} during uninstall! SeatId: {}",
unit->GetGUID().ToString(), _me->GetGUID().ToString(), (int32)seatId);
return false;
}
TC_LOG_DEBUG("entities.vehicle", "Unit {} scheduling enter vehicle (entry: {}, vehicleId: {}, guid: {} on seat {}",
unit->GetName(), _me->GetEntry(), _vehicleInfo->ID, _me->GetGUID().ToString(), (int32)seatId);
// 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;
VehicleJoinEvent* e = new VehicleJoinEvent(this, unit);
unit->m_Events.AddEvent(e, unit->m_Events.CalculateTime(0s));
if (seatId < 0) // no specific seat requirement
{
for (seat = Seats.begin(); seat != Seats.end(); ++seat)
if (seat->second.IsEmpty() && !HasPendingEventForSeat(seat->first) && (seat->second.SeatInfo->CanEnterOrExit() || seat->second.SeatInfo->IsUsableByOverride()))
break;
if (seat == Seats.end()) // no available seat
{
e->ScheduleAbort();
return false;
}
e->Seat = seat;
_pendingJoinEvents.push_back(e);
}
else
{
seat = Seats.find(seatId);
if (seat == Seats.end())
{
e->ScheduleAbort();
return false;
}
e->Seat = seat;
_pendingJoinEvents.push_back(e);
if (!seat->second.IsEmpty())
{
Unit* passenger = ObjectAccessor::GetUnit(*GetBase(), seat->second.Passenger.Guid);
ASSERT(passenger);
passenger->ExitVehicle();
}
ASSERT(seat->second.IsEmpty());
}
return true;
}
/**
* @fn void Vehicle::RemovePassenger(Unit* unit)
*
* @brief Removes the passenger from the vehicle.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param [in, out] unit The passenger to remove.
*/
Vehicle* Vehicle::RemovePassenger(WorldObject* passenger)
{
Unit* unit = passenger->ToUnit();
if (!unit)
return nullptr;
if (unit->GetVehicle() != this)
return nullptr;
SeatMap::iterator seat = GetSeatIteratorForPassenger(unit);
ASSERT(seat != Seats.end());
TC_LOG_DEBUG("entities.vehicle", "Unit {} exit vehicle entry {} id {} guid {} seat {}",
unit->GetName(), _me->GetEntry(), _vehicleInfo->ID, _me->GetGUID().ToString(), (int32)seat->first);
if (seat->second.SeatInfo->CanEnterOrExit() && ++UsableSeatNum)
_me->SetNpcFlag((_me->GetTypeId() == TYPEID_PLAYER ? UNIT_NPC_FLAG_PLAYER_VEHICLE : UNIT_NPC_FLAG_SPELLCLICK));
// Enable gravity for passenger when he did not have it active before entering the vehicle
if (seat->second.SeatInfo->Flags & VEHICLE_SEAT_FLAG_DISABLE_GRAVITY && !seat->second.Passenger.IsGravityDisabled)
unit->SetDisableGravity(false);
// Remove UNIT_FLAG_UNINTERACTIBLE if passenger did not have it before entering vehicle
if (seat->second.SeatInfo->Flags & VEHICLE_SEAT_FLAG_PASSENGER_NOT_SELECTABLE && !seat->second.Passenger.IsUninteractible)
unit->SetUninteractible(false);
seat->second.Passenger.Reset();
if (_me->GetTypeId() == TYPEID_UNIT && unit->GetTypeId() == TYPEID_PLAYER && seat->second.SeatInfo->Flags & VEHICLE_SEAT_FLAG_CAN_CONTROL)
_me->RemoveCharmedBy(unit);
if (_me->IsInWorld())
{
if (!_me->GetTransport())
unit->m_movementInfo.ResetTransport();
else
unit->m_movementInfo.transport = _me->m_movementInfo.transport;
}
// only for flyable vehicles
if (unit->IsFlying())
{
VehicleTemplate const* vehicleTemplate = sObjectMgr->GetVehicleTemplate(this);
if (!vehicleTemplate || !vehicleTemplate->CustomFlags.HasFlag(VehicleCustomFlags::DontForceParachuteOnExit))
_me->CastSpell(unit, VEHICLE_SPELL_PARACHUTE, true);
}
if (_me->GetTypeId() == TYPEID_UNIT && _me->ToCreature()->IsAIEnabled())
_me->ToCreature()->AI()->PassengerBoarded(unit, seat->first, false);
if (GetBase()->GetTypeId() == TYPEID_UNIT)
sScriptMgr->OnRemovePassenger(this, unit);
unit->SetVehicle(nullptr);
return this;
}
/**
* @fn void Vehicle::RelocatePassengers()
*
* @brief Relocate passengers. Must be called after m_base::Relocate
*
* @author Machiavelli
* @date 17-2-2013
*/
void Vehicle::RelocatePassengers()
{
ASSERT(_me->GetMap());
std::vector<std::pair<Unit*, Position>> seatRelocation;
seatRelocation.reserve(Seats.size());
// not sure that absolute position calculation is correct, it must depend on vehicle pitch angle
for (SeatMap::const_iterator itr = Seats.begin(); itr != Seats.end(); ++itr)
{
if (Unit* passenger = ObjectAccessor::GetUnit(*GetBase(), itr->second.Passenger.Guid))
{
ASSERT(passenger->IsInWorld());
float px, py, pz, po;
passenger->m_movementInfo.transport.pos.GetPosition(px, py, pz, po);
CalculatePassengerPosition(px, py, pz, &po);
seatRelocation.emplace_back(passenger, Position(px, py, pz, po));
}
}
for (auto const& [passenger, position] : seatRelocation)
UpdatePassengerPosition(_me->GetMap(), passenger, position.GetPositionX(), position.GetPositionY(), position.GetPositionZ(), position.GetOrientation(), false);
}
/**
* @fn bool Vehicle::IsVehicleInUse() const
*
* @brief Returns information whether the vehicle is currently used by any unit
*
* @author Shauren
* @date 26-2-2013
*
* @return true if any passenger is boarded on vehicle, false otherwise.
*/
bool Vehicle::IsVehicleInUse() const
{
for (SeatMap::const_iterator itr = Seats.begin(); itr != Seats.end(); ++itr)
if (!itr->second.IsEmpty())
return true;
return false;
}
bool Vehicle::IsControllableVehicle() const
{
for (SeatMap::const_iterator itr = Seats.begin(); itr != Seats.end(); ++itr)
if (itr->second.SeatInfo->HasFlag(VEHICLE_SEAT_FLAG_CAN_CONTROL))
return true;
return false;
}
/**
* @fn void Vehicle::InitMovementInfoForBase()
*
* @brief Sets correct MovementFlags2 based on VehicleFlags from DBC.
*
* @author Machiavelli
* @date 17-2-2013
*/
void Vehicle::InitMovementInfoForBase()
{
uint32 vehicleFlags = GetVehicleInfo()->Flags;
if (vehicleFlags & VEHICLE_FLAG_NO_STRAFE)
_me->AddExtraUnitMovementFlag(MOVEMENTFLAG2_NO_STRAFE);
if (vehicleFlags & VEHICLE_FLAG_NO_JUMPING)
_me->AddExtraUnitMovementFlag(MOVEMENTFLAG2_NO_JUMPING);
if (vehicleFlags & VEHICLE_FLAG_FULLSPEEDTURNING)
_me->AddExtraUnitMovementFlag(MOVEMENTFLAG2_FULL_SPEED_TURNING);
if (vehicleFlags & VEHICLE_FLAG_ALLOW_PITCHING)
_me->AddExtraUnitMovementFlag(MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING);
if (vehicleFlags & VEHICLE_FLAG_FULLSPEEDPITCHING)
_me->AddExtraUnitMovementFlag(MOVEMENTFLAG2_FULL_SPEED_PITCHING);
_me->m_movementInfo.pitch = GetPitch();
}
/**
* @fn VehicleSeatEntry const* Vehicle::GetSeatForPassenger(Unit* passenger)
*
* @brief Returns information on the seat of specified passenger, represented by the format in VehicleSeat.dbc
*
* @author Machiavelli
* @date 17-2-2013
*
* @param [in, out] The passenger for which we check the seat info.
*
* @return null if passenger not found on vehicle, else the DBC record for the seat.
*/
VehicleSeatEntry const* Vehicle::GetSeatForPassenger(Unit const* passenger) const
{
for (SeatMap::const_iterator itr = Seats.begin(); itr != Seats.end(); ++itr)
if (itr->second.Passenger.Guid == passenger->GetGUID())
return itr->second.SeatInfo;
return nullptr;
}
/**
* @fn SeatMap::iterator Vehicle::GetSeatIteratorForPassenger(Unit* passenger)
*
* @brief Gets seat iterator for specified passenger.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param [in, out] passenger Passenger to look up.
*
* @return The seat iterator for specified passenger if it's found on the vehicle. Otherwise Seats.end() (invalid iterator).
*/
SeatMap::iterator Vehicle::GetSeatIteratorForPassenger(Unit* passenger)
{
SeatMap::iterator itr;
for (itr = Seats.begin(); itr != Seats.end(); ++itr)
if (itr->second.Passenger.Guid == passenger->GetGUID())
return itr;
return Seats.end();
}
/**
* @fn uint8 Vehicle::GetAvailableSeatCount() const
*
* @brief Gets the available seat count.
*
* @author Machiavelli
* @date 17-2-2013
*
* @return The available seat count.
*/
uint8 Vehicle::GetAvailableSeatCount() const
{
uint8 ret = 0;
SeatMap::const_iterator itr;
for (itr = Seats.begin(); itr != Seats.end(); ++itr)
if (itr->second.IsEmpty() && !HasPendingEventForSeat(itr->first) && (itr->second.SeatInfo->CanEnterOrExit() || itr->second.SeatInfo->IsUsableByOverride()))
++ret;
return ret;
}
/**
* @fn void Vehicle::RemovePendingEvent(VehicleJoinEvent* e)
*
* @brief Removes @VehicleJoinEvent objects from pending join event store.
* This method only removes it after it's executed or aborted to prevent leaving
* pointers to deleted events.
*
* @author Shauren
* @date 22-2-2013
*
* @param [in] e The VehicleJoinEvent* to remove from pending event store.
*/
void Vehicle::RemovePendingEvent(VehicleJoinEvent* e)
{
for (PendingJoinEventContainer::iterator itr = _pendingJoinEvents.begin(); itr != _pendingJoinEvents.end(); ++itr)
{
if (*itr == e)
{
_pendingJoinEvents.erase(itr);
break;
}
}
}
/**
* @fn void Vehicle::RemovePendingEventsForSeat(uint8 seatId)
*
* @brief Removes any pending events for given seatId. Executed when a @VehicleJoinEvent::Execute is called
*
* @author Machiavelli
* @date 23-2-2013
*
* @param seatId Identifier for the seat.
*/
void Vehicle::RemovePendingEventsForSeat(int8 seatId)
{
for (PendingJoinEventContainer::iterator itr = _pendingJoinEvents.begin(); itr != _pendingJoinEvents.end();)
{
if ((*itr)->Seat->first == seatId)
{
(*itr)->ScheduleAbort();
_pendingJoinEvents.erase(itr++);
}
else
++itr;
}
}
/**
* @fn void Vehicle::RemovePendingEventsForSeat(uint8 seatId)
*
* @brief Removes any pending events for given passenger. Executed when vehicle control aura is removed while adding passenger is in progress
*
* @author Shauren
* @date 13-2-2013
*
* @param passenger Unit that is supposed to enter the vehicle.
*/
void Vehicle::RemovePendingEventsForPassenger(Unit* passenger)
{
for (PendingJoinEventContainer::iterator itr = _pendingJoinEvents.begin(); itr != _pendingJoinEvents.end();)
{
if ((*itr)->Passenger == passenger)
{
(*itr)->ScheduleAbort();
_pendingJoinEvents.erase(itr++);
}
else
++itr;
}
}
/**
* @fn bool VehicleJoinEvent::Execute(uint64, uint32)
*
* @brief Actually adds the passenger @Passenger to vehicle @Target.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param parameter1 Unused
* @param parameter2 Unused.
*
* @return true, cannot fail.
*
*/
bool VehicleJoinEvent::Execute(uint64, uint32)
{
ASSERT(Passenger->IsInWorld());
ASSERT(Target && Target->GetBase()->IsInWorld());
Unit::AuraEffectList const& vehicleAuras = Target->GetBase()->GetAuraEffectsByType(SPELL_AURA_CONTROL_VEHICLE);
auto itr = std::find_if(vehicleAuras.begin(), vehicleAuras.end(), [this](AuraEffect const* aurEff) -> bool
{
return aurEff->GetCasterGUID() == Passenger->GetGUID();
});
ASSERT(itr != vehicleAuras.end());
AuraApplication const* aurApp = (*itr)->GetBase()->GetApplicationOfTarget(Target->GetBase()->GetGUID());
ASSERT(aurApp && !aurApp->GetRemoveMode());
Target->RemovePendingEventsForSeat(Seat->first);
Target->RemovePendingEventsForPassenger(Passenger);
// Passenger might've died in the meantime - abort if this is the case
if (!Passenger->IsAlive())
{
Abort(0);
return true;
}
//It's possible that multiple vehicle join
//events are executed in the same update
if (Passenger->GetVehicle())
Passenger->ExitVehicle();
Passenger->SetVehicle(Target);
Seat->second.Passenger.Guid = Passenger->GetGUID();
Seat->second.Passenger.IsUninteractible = Passenger->IsUninteractible();
Seat->second.Passenger.IsGravityDisabled = Passenger->HasUnitMovementFlag(MOVEMENTFLAG_DISABLE_GRAVITY);
if (Seat->second.SeatInfo->CanEnterOrExit())
{
ASSERT(Target->UsableSeatNum);
--(Target->UsableSeatNum);
if (!Target->UsableSeatNum)
{
if (Target->GetBase()->GetTypeId() == TYPEID_PLAYER)
Target->GetBase()->RemoveNpcFlag(UNIT_NPC_FLAG_PLAYER_VEHICLE);
else
Target->GetBase()->RemoveNpcFlag(UNIT_NPC_FLAG_SPELLCLICK);
}
}
Passenger->InterruptSpell(CURRENT_GENERIC_SPELL);
Passenger->InterruptSpell(CURRENT_AUTOREPEAT_SPELL);
Passenger->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags::Mount);
Passenger->RemoveAurasByType(SPELL_AURA_MOUNTED);
VehicleSeatEntry const* veSeat = Seat->second.SeatInfo;
VehicleSeatAddon const* veSeatAddon = Seat->second.SeatAddon;
Player* player = Passenger->ToPlayer();
if (player)
{
player->StopCastingCharm();
player->StopCastingBindSight();
player->SendOnCancelExpectedVehicleRideAura();
if (!veSeat->HasFlag(VEHICLE_SEAT_FLAG_B_KEEP_PET))
player->UnsummonPetTemporaryIfAny();
}
if (veSeat->HasFlag(VEHICLE_SEAT_FLAG_DISABLE_GRAVITY))
Passenger->SetDisableGravity(true);
float o = veSeatAddon ? veSeatAddon->SeatOrientationOffset : 0.f;
float x = veSeat->AttachmentOffset.X;
float y = veSeat->AttachmentOffset.Y;
float z = veSeat->AttachmentOffset.Z;
Passenger->m_movementInfo.transport.pos.Relocate(x, y, z, o);
Passenger->m_movementInfo.transport.time = 0;
Passenger->m_movementInfo.transport.seat = Seat->first;
Passenger->m_movementInfo.transport.guid = Target->GetBase()->GetGUID();
Passenger->m_movementInfo.transport.vehicleId = Target->GetVehicleInfo()->ID;
if (Target->GetBase()->GetTypeId() == TYPEID_UNIT && Passenger->GetTypeId() == TYPEID_PLAYER &&
Seat->second.SeatInfo->HasFlag(VEHICLE_SEAT_FLAG_CAN_CONTROL))
{
// handles SMSG_CLIENT_CONTROL
if (!Target->GetBase()->SetCharmedBy(Passenger, CHARM_TYPE_VEHICLE, aurApp))
{
// charming failed, probably aura was removed by relocation/scripts/whatever
Abort(0);
return true;
}
}
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
std::function<void(Movement::MoveSplineInit&)> initializer = [=](Movement::MoveSplineInit& init)
{
init.DisableTransportPathTransformations();
init.MoveTo(x, y, z, false, true);
init.SetFacing(o);
init.SetTransportEnter();
};
Passenger->GetMotionMaster()->LaunchMoveSpline(std::move(initializer), EVENT_VEHICLE_BOARD, MOTION_PRIORITY_HIGHEST);
for (auto const& [guid, threatRef] : Passenger->GetThreatManager().GetThreatenedByMeList())
threatRef->GetOwner()->GetThreatManager().AddThreat(Target->GetBase(), threatRef->GetThreat(), nullptr, true, true);
if (Creature* creature = Target->GetBase()->ToCreature())
{
if (CreatureAI* ai = creature->AI())
ai->PassengerBoarded(Passenger, Seat->first, true);
sScriptMgr->OnAddPassenger(Target, Passenger, Seat->first);
// Actually quite a redundant hook. Could just use OnAddPassenger and check for unit typemask inside script.
if (Passenger->HasUnitTypeMask(UNIT_MASK_ACCESSORY))
sScriptMgr->OnInstallAccessory(Target, Passenger->ToCreature());
}
return true;
}
/**
* @fn void VehicleJoinEvent::Abort(uint64)
*
* @brief Aborts the event. Implies that unit @Passenger will not be boarding vehicle @Target after all.
*
* @author Machiavelli
* @date 17-2-2013
*
* @param parameter1 Unused
*/
void VehicleJoinEvent::Abort(uint64)
{
/// Check if the Vehicle was already uninstalled, in which case all auras were removed already
if (Target)
{
TC_LOG_DEBUG("entities.vehicle", "Passenger {}, board on vehicle {} SeatId: {} cancelled",
Passenger->GetGUID().ToString(), Target->GetBase()->GetGUID().ToString(), (int32)Seat->first);
/// Remove the pending event when Abort was called on the event directly
Target->RemovePendingEvent(this);
/// @SPELL_AURA_CONTROL_VEHICLE auras can be applied even when the passenger is not (yet) on the vehicle.
/// When this code is triggered it means that something went wrong in @Vehicle::AddVehiclePassenger, and we should remove
/// the aura manually.
Target->GetBase()->RemoveAurasByType(SPELL_AURA_CONTROL_VEHICLE, Passenger->GetGUID());
}
else
TC_LOG_DEBUG("entities.vehicle", "Passenger {}, board on uninstalled vehicle SeatId: {} cancelled",
Passenger->GetGUID().ToString(), (int32)Seat->first);
if (Passenger->IsInWorld() && Passenger->HasUnitTypeMask(UNIT_MASK_ACCESSORY))
Passenger->ToCreature()->DespawnOrUnsummon();
}
bool Vehicle::HasPendingEventForSeat(int8 seatId) const
{
for (PendingJoinEventContainer::const_iterator itr = _pendingJoinEvents.begin(); itr != _pendingJoinEvents.end(); ++itr)
{
if ((*itr)->Seat->first == seatId)
return true;
}
return false;
}
Milliseconds Vehicle::GetDespawnDelay()
{
if (VehicleTemplate const* vehicleTemplate = sObjectMgr->GetVehicleTemplate(this))
return vehicleTemplate->DespawnDelay;
return 1ms;
}
float Vehicle::GetPitch()
{
if (VehicleTemplate const* vehicleTemplate = sObjectMgr->GetVehicleTemplate(this))
if (vehicleTemplate->Pitch)
return *vehicleTemplate->Pitch;
return std::clamp(0.0f, _vehicleInfo->PitchMin, _vehicleInfo->PitchMax);
}
std::string Vehicle::GetDebugInfo() const
{
std::stringstream sstr;
sstr << "Vehicle seats:\n";
for (SeatMap::const_iterator itr = Seats.begin(); itr != Seats.end(); itr++)
{
sstr << "seat " << std::to_string(itr->first) << ": " << (itr->second.IsEmpty() ? "empty" : itr->second.Passenger.Guid.ToString()) << "\n";
}
sstr << "Vehicle pending events:";
if (_pendingJoinEvents.empty())
{
sstr << " none";
}
else
{
sstr << "\n";
for (PendingJoinEventContainer::const_iterator itr = _pendingJoinEvents.begin(); itr != _pendingJoinEvents.end(); ++itr)
{
sstr << "seat " << std::to_string((*itr)->Seat->first) << ": " << (*itr)->Passenger->GetGUID().ToString() << "\n";
}
}
return sstr.str();
}
Trinity::unique_weak_ptr<Vehicle> Vehicle::GetWeakPtr() const
{
return _me->GetVehicleKitWeakPtr();
}
|