/* * 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 . */ #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 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 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> 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 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::GetWeakPtr() const { return _me->GetVehicleKitWeakPtr(); }