/* * 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 "GameObject.h" #include "ArtifactPackets.h" #include "AzeriteItem.h" #include "AzeritePackets.h" #include "Battleground.h" #include "BattlegroundPackets.h" #include "CellImpl.h" #include "Containers.h" #include "CreatureAISelector.h" #include "DB2Stores.h" #include "DatabaseEnv.h" #include "G3DPosition.hpp" #include "GameEventSender.h" #include "GameObjectAI.h" #include "GameObjectModel.h" #include "GameObjectPackets.h" #include "GameTime.h" #include "GossipDef.h" #include "GridNotifiersImpl.h" #include "Group.h" #include "Item.h" #include "ItemBonusMgr.h" #include "Log.h" #include "Loot.h" #include "LootMgr.h" #include "Map.h" #include "MapManager.h" #include "MapUtils.h" #include "MiscPackets.h" #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "OutdoorPvPMgr.h" #include "PhasingHandler.h" #include "PoolMgr.h" #include "QueryPackets.h" #include "SpellAuras.h" #include "SpellMgr.h" #include "Transport.h" #include "Util.h" #include "Vignette.h" #include "World.h" #include #include #include #include void GameObjectTemplate::InitializeQueryData() { for (uint8 loc = LOCALE_enUS; loc < TOTAL_LOCALES; ++loc) { if (!sWorld->getBoolConfig(CONFIG_LOAD_LOCALES) && loc != DEFAULT_LOCALE) continue; QueryData[loc] = BuildQueryData(static_cast(loc)); } } WorldPacket GameObjectTemplate::BuildQueryData(LocaleConstant loc) const { WorldPackets::Query::QueryGameObjectResponse queryTemp; queryTemp.GameObjectID = entry; queryTemp.Allow = true; WorldPackets::Query::GameObjectStats& stats = queryTemp.Stats; stats.Type = type; stats.DisplayID = displayId; stats.Name[0] = name; stats.IconName = IconName; stats.CastBarCaption = castBarCaption; stats.UnkString = unk1; if (loc != LOCALE_enUS) if (GameObjectLocale const* gameObjectLocale = sObjectMgr->GetGameObjectLocale(entry)) { ObjectMgr::GetLocaleString(gameObjectLocale->Name, loc, stats.Name[0]); ObjectMgr::GetLocaleString(gameObjectLocale->CastBarCaption, loc, stats.CastBarCaption); ObjectMgr::GetLocaleString(gameObjectLocale->Unk1, loc, stats.UnkString); } stats.Size = size; if (std::vector const* items = sObjectMgr->GetGameObjectQuestItemList(entry)) for (int32 item : *items) stats.QuestItems.push_back(item); memcpy(stats.Data.data(), raw.data, MAX_GAMEOBJECT_DATA * sizeof(int32)); stats.ContentTuningId = ContentTuningId; queryTemp.Write(); queryTemp.ShrinkToFit(); return queryTemp.Move(); } bool QuaternionData::isUnit() const { return fabs(x * x + y * y + z * z + w * w - 1.0f) < 1e-5f; } void QuaternionData::toEulerAnglesZYX(float& Z, float& Y, float& X) const { G3D::Matrix3(G3D::Quat(x, y, z, w)).toEulerAnglesZYX(Z, Y, X); } QuaternionData QuaternionData::fromEulerAnglesZYX(float Z, float Y, float X) { G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(Z, Y, X)); return QuaternionData(quat.x, quat.y, quat.z, quat.w); } GameObjectTypeBase::CustomCommand::~CustomCommand() = default; namespace GameObjectType { //11 GAMEOBJECT_TYPE_TRANSPORT class Transport : public GameObjectTypeBase, public TransportBase { public: static constexpr Milliseconds PositionUpdateInterval = 50ms; explicit Transport(GameObject& owner) : GameObjectTypeBase(owner), _animationInfo(sTransportMgr->GetTransportAnimInfo(owner.GetGOInfo()->entry)), _pathProgress(GameTime::GetGameTimeMS() % GetTransportPeriod()), _stateChangeTime(GameTime::GetGameTimeMS()), _stateChangeProgress(_pathProgress), _autoCycleBetweenStopFrames(false) { GameObjectTemplate const* goInfo = _owner.GetGOInfo(); if (goInfo->transport.Timeto2ndfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto2ndfloor); if (goInfo->transport.Timeto3rdfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto3rdfloor); if (goInfo->transport.Timeto4thfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto4thfloor); if (goInfo->transport.Timeto5thfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto5thfloor); if (goInfo->transport.Timeto6thfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto6thfloor); if (goInfo->transport.Timeto7thfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto7thfloor); if (goInfo->transport.Timeto8thfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto8thfloor); if (goInfo->transport.Timeto9thfloor > 0) { _stopFrames.push_back(goInfo->transport.Timeto9thfloor); if (goInfo->transport.Timeto10thfloor > 0) _stopFrames.push_back(goInfo->transport.Timeto10thfloor); } } } } } } } } if (!_stopFrames.empty()) { _pathProgress = 0; _stateChangeProgress = 0; } _positionUpdateTimer.Reset(PositionUpdateInterval); } void Update(uint32 diff) override { if (!_animationInfo) return; _positionUpdateTimer.Update(diff); if (!_positionUpdateTimer.Passed()) return; _positionUpdateTimer.Reset(PositionUpdateInterval); uint32 now = GameTime::GetGameTimeMS(); uint32 period = GetTransportPeriod(); uint32 newProgress = 0; if (_stopFrames.empty()) newProgress = now % period; else { int32 stopTargetTime = 0; if (_owner.GetGoState() == GO_STATE_TRANSPORT_ACTIVE) stopTargetTime = 0; else stopTargetTime = _stopFrames[_owner.GetGoState() - GO_STATE_TRANSPORT_STOPPED]; if (now < uint32(*_owner.m_gameObjectData->Level)) { int32 timeToStop = _owner.m_gameObjectData->Level - _stateChangeTime; float stopSourcePathPct = float(_stateChangeProgress) / float(period); float stopTargetPathPct = float(stopTargetTime) / float(period); float timeSinceStopProgressPct = float(now - _stateChangeTime) / float(timeToStop); float progressPct; if (!_owner.HasDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT)) { if (_owner.GetGoState() == GO_STATE_TRANSPORT_ACTIVE) stopTargetPathPct = 1.0f; float pathPctBetweenStops = stopTargetPathPct - stopSourcePathPct; if (pathPctBetweenStops < 0.0f) pathPctBetweenStops += 1.0f; progressPct = pathPctBetweenStops * timeSinceStopProgressPct + stopSourcePathPct; if (progressPct > 1.0f) progressPct = progressPct - 1.0f; } else { float pathPctBetweenStops = stopSourcePathPct - stopTargetPathPct; if (pathPctBetweenStops < 0.0f) pathPctBetweenStops += 1.0f; progressPct = stopSourcePathPct - pathPctBetweenStops * timeSinceStopProgressPct; if (progressPct < 0.0f) progressPct += 1.0f; } newProgress = uint32(float(period) * progressPct) % period; } else newProgress = stopTargetTime; if (int32(newProgress) == stopTargetTime && newProgress != _pathProgress) { uint32 eventId = [&]() { switch (_owner.GetGoState() - GO_STATE_TRANSPORT_ACTIVE) { case 0: return _owner.GetGOInfo()->transport.Reached1stfloor; case 1: return _owner.GetGOInfo()->transport.Reached2ndfloor; case 2: return _owner.GetGOInfo()->transport.Reached3rdfloor; case 3: return _owner.GetGOInfo()->transport.Reached4thfloor; case 4: return _owner.GetGOInfo()->transport.Reached5thfloor; case 5: return _owner.GetGOInfo()->transport.Reached6thfloor; case 6: return _owner.GetGOInfo()->transport.Reached7thfloor; case 7: return _owner.GetGOInfo()->transport.Reached8thfloor; case 8: return _owner.GetGOInfo()->transport.Reached9thfloor; case 9: return _owner.GetGOInfo()->transport.Reached10thfloor; default: return 0u; } }(); if (eventId) GameEvents::Trigger(eventId, &_owner, &_owner); if (_autoCycleBetweenStopFrames) { GOState currentState = _owner.GetGoState(); GOState newState; if (currentState == GO_STATE_TRANSPORT_ACTIVE) newState = GO_STATE_TRANSPORT_STOPPED; else if (currentState - GO_STATE_TRANSPORT_ACTIVE == int32(_stopFrames.size())) newState = GOState(currentState - 1); else if (_owner.HasDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT)) newState = GOState(currentState - 1); else newState = GOState(currentState + 1); _owner.SetGoState(newState); } } } if (_pathProgress == newProgress) return; _pathProgress = newProgress; TransportAnimationEntry const* oldAnimation = _animationInfo->GetPrevAnimNode(newProgress); TransportAnimationEntry const* newAnimation = _animationInfo->GetNextAnimNode(newProgress); if (oldAnimation && newAnimation) { G3D::Matrix3 pathRotation = G3D::Quat(_owner.m_gameObjectData->ParentRotation->x, _owner.m_gameObjectData->ParentRotation->y, _owner.m_gameObjectData->ParentRotation->z, _owner.m_gameObjectData->ParentRotation->w).toRotationMatrix(); G3D::Vector3 prev(oldAnimation->Pos.X, oldAnimation->Pos.Y, oldAnimation->Pos.Z); G3D::Vector3 next(newAnimation->Pos.X, newAnimation->Pos.Y, newAnimation->Pos.Z); G3D::Vector3 dst = next; if (prev != next) { float animProgress = float(newProgress - oldAnimation->TimeIndex) / float(newAnimation->TimeIndex - oldAnimation->TimeIndex); dst = prev.lerp(next, animProgress); } dst = dst * pathRotation; dst += PositionToVector3(&_owner.GetStationaryPosition()); _owner.GetMap()->GameObjectRelocation(&_owner, dst.x, dst.y, dst.z, _owner.GetOrientation()); } TransportRotationEntry const* oldRotation = _animationInfo->GetPrevAnimRotation(newProgress); TransportRotationEntry const* newRotation = _animationInfo->GetNextAnimRotation(newProgress); if (oldRotation && newRotation) { G3D::Quat prev(oldRotation->Rot[0], oldRotation->Rot[1], oldRotation->Rot[2], oldRotation->Rot[3]); G3D::Quat next(newRotation->Rot[0], newRotation->Rot[1], newRotation->Rot[2], newRotation->Rot[3]); G3D::Quat rotation = next; if (prev != next) { float animProgress = float(newProgress - oldRotation->TimeIndex) / float(newRotation->TimeIndex - oldRotation->TimeIndex); rotation = prev.slerp(next, animProgress); } _owner.SetLocalRotation(rotation.x, rotation.y, rotation.z, rotation.w); _owner.UpdateModelPosition(); } // update progress marker for client _owner.SetPathProgressForClient(float(_pathProgress) / float(period)); } void OnStateChanged(GOState oldState, GOState newState) override { ASSERT(newState >= GO_STATE_TRANSPORT_ACTIVE); // transports without stop frames just keep animating in state 24 if (_stopFrames.empty()) { if (newState != GO_STATE_TRANSPORT_ACTIVE) _owner.SetGoState(GO_STATE_TRANSPORT_ACTIVE); return; } int32 stopPathProgress = 0; if (newState != GO_STATE_TRANSPORT_ACTIVE) { ASSERT(newState < GOState(GO_STATE_TRANSPORT_STOPPED + MAX_GO_STATE_TRANSPORT_STOP_FRAMES)); uint32 stopFrame = newState - GO_STATE_TRANSPORT_STOPPED; ASSERT(stopFrame < _stopFrames.size()); stopPathProgress = _stopFrames[stopFrame]; } _stateChangeTime = GameTime::GetGameTimeMS(); _stateChangeProgress = _pathProgress; uint32 timeToStop = std::abs(int32(_pathProgress) - stopPathProgress); _owner.SetLevel(GameTime::GetGameTimeMS() + timeToStop); _owner.SetPathProgressForClient(float(_pathProgress) / float(GetTransportPeriod())); if (oldState == GO_STATE_ACTIVE || oldState == newState) { // initialization if (int32(_pathProgress) > stopPathProgress) _owner.SetDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT); else _owner.RemoveDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT); return; } int32 pauseTimesCount = _stopFrames.size(); int32 newToOldStateDelta = newState - oldState; if (newToOldStateDelta < 0) newToOldStateDelta += pauseTimesCount + 1; int32 oldToNewStateDelta = oldState - newState; if (oldToNewStateDelta < 0) oldToNewStateDelta += pauseTimesCount + 1; // this additional check is neccessary because client doesn't check dynamic flags on progress update // instead it multiplies progress from dynamicflags field by -1 and then compares that against 0 // when calculating path progress while we simply check the flag if (!_owner.HasDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT)) bool isAtStartOfPath = _stateChangeProgress == 0; if (oldToNewStateDelta < newToOldStateDelta && !isAtStartOfPath) _owner.SetDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT); else _owner.RemoveDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT); } void OnRelocated() override { UpdatePassengerPositions(); } void UpdatePassengerPositions() { for (WorldObject* passenger : _passengers) { float x, y, z, o; passenger->m_movementInfo.transport.pos.GetPosition(x, y, z, o); CalculatePassengerPosition(x, y, z, &o); UpdatePassengerPosition(_owner.GetMap(), passenger, x, y, z, o, true); } } uint32 GetTransportPeriod() const { if (_animationInfo) return _animationInfo->TotalTime; return 1; } std::vector const* GetPauseTimes() const { return &_stopFrames; } ObjectGuid GetTransportGUID() const override { return _owner.GetGUID(); } float GetTransportOrientation() const override { return _owner.GetOrientation(); } void AddPassenger(WorldObject* passenger) override { if (!_owner.IsInWorld()) return; if (_passengers.insert(passenger).second) { passenger->SetTransport(this); passenger->m_movementInfo.transport.guid = GetTransportGUID(); TC_LOG_DEBUG("entities.transport", "Object {} boarded transport {}.", passenger->GetName(), _owner.GetName()); } } TransportBase* RemovePassenger(WorldObject* passenger) override { if (_passengers.erase(passenger) > 0) { passenger->SetTransport(nullptr); passenger->m_movementInfo.transport.Reset(); TC_LOG_DEBUG("entities.transport", "Object {} removed from transport {}.", passenger->GetName(), _owner.GetName()); if (Player* plr = passenger->ToPlayer()) plr->SetFallInformation(0, plr->GetPositionZ()); } return this; } void CalculatePassengerPosition(float& x, float& y, float& z, float* o) const override { TransportBase::CalculatePassengerPosition(x, y, z, o, _owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ(), _owner.GetOrientation()); } void CalculatePassengerOffset(float& x, float& y, float& z, float* o) const override { TransportBase::CalculatePassengerOffset(x, y, z, o, _owner.GetPositionX(), _owner.GetPositionY(), _owner.GetPositionZ(), _owner.GetOrientation()); } int32 GetMapIdForSpawning() const override { return _owner.GetGOInfo()->transport.SpawnMap; } void SetAutoCycleBetweenStopFrames(bool on) { _autoCycleBetweenStopFrames = on; } private: TransportAnimation const* _animationInfo; uint32 _pathProgress; uint32 _stateChangeTime; uint32 _stateChangeProgress; std::vector _stopFrames; bool _autoCycleBetweenStopFrames; TimeTracker _positionUpdateTimer; std::unordered_set _passengers; }; SetTransportAutoCycleBetweenStopFrames::SetTransportAutoCycleBetweenStopFrames(bool on) : _on(on) { } void SetTransportAutoCycleBetweenStopFrames::Execute(GameObjectTypeBase& type) const { if (Transport* transport = dynamic_cast(&type)) transport->SetAutoCycleBetweenStopFrames(_on); } class NewFlag : public GameObjectTypeBase { public: explicit NewFlag(GameObject& owner) : GameObjectTypeBase(owner), _state(FlagState::InBase), _respawnTime(0), _takenFromBaseTime(0) { } void SetState(FlagState newState, Player* player) { if (_state == newState) return; FlagState oldState = _state; _state = newState; if (player && newState == FlagState::Taken) _carrierGUID = player->GetGUID(); else _carrierGUID = ObjectGuid::Empty; if (newState == FlagState::Taken && oldState == FlagState::InBase) _takenFromBaseTime = GameTime::GetGameTime(); else if (newState == FlagState::InBase || newState == FlagState::Respawning) _takenFromBaseTime = 0; _owner.UpdateObjectVisibility(); if (newState == FlagState::Respawning) _respawnTime = GameTime::GetGameTimeMS() + _owner.GetGOInfo()->newflag.RespawnTime; else _respawnTime = 0; if (ZoneScript* zoneScript = _owner.GetZoneScript()) zoneScript->OnFlagStateChange(&_owner, oldState, _state, player); } void Update([[maybe_unused]] uint32 diff) override { if (_state == FlagState::Respawning && GameTime::GetGameTimeMS() >= _respawnTime) SetState(FlagState::InBase, nullptr); } bool IsNeverVisibleFor([[maybe_unused]] WorldObject const* seer, [[maybe_unused]] bool allowServersideObjects) const override { return _state != FlagState::InBase; } FlagState GetState() const { return _state; } ObjectGuid const& GetCarrierGUID() const { return _carrierGUID; } time_t GetTakenFromBaseTime() const { return _takenFromBaseTime; } private: FlagState _state; time_t _respawnTime; ObjectGuid _carrierGUID; time_t _takenFromBaseTime; }; SetNewFlagState::SetNewFlagState(FlagState state, Player* player) : _state(state), _player(player) { } void SetNewFlagState::Execute(GameObjectTypeBase& type) const { if (NewFlag* newFlag = dynamic_cast(&type)) newFlag->SetState(_state, _player); } class ControlZone : public GameObjectTypeBase { public: explicit ControlZone(GameObject& owner) : GameObjectTypeBase(owner), _value(static_cast(owner.GetGOInfo()->controlZone.startingValue)) { if (owner.GetMap()->Instanceable()) _heartbeatRate = 1s; else if (owner.GetGOInfo()->controlZone.FrequentHeartbeat) _heartbeatRate = 2500ms; else _heartbeatRate = 5s; _heartbeatTracker.Reset(_heartbeatRate); _previousTeamId = GetControllingTeam(); _contestedTriggered = false; } void Update(uint32 diff) override { if (_owner.HasFlag(GO_FLAG_NOT_SELECTABLE)) return; _heartbeatTracker.Update(diff); if (_heartbeatTracker.Passed()) { _heartbeatTracker.Reset(_heartbeatRate); HandleHeartbeat(); } } TeamId GetControllingTeam() const { if (_value < GetMaxHordeValue()) return TEAM_HORDE; if (_value > GetMinAllianceValue()) return TEAM_ALLIANCE; return TEAM_NEUTRAL; } GuidUnorderedSet const* GetInsidePlayers() const { return &_insidePlayers; } void ActivateObject(GameObjectActions action, int32 /*param*/, WorldObject* /*spellCaster*/, uint32 /*spellId*/, int32 /*effectIndex*/) override { switch (action) { case GameObjectActions::MakeInert: for (ObjectGuid const& guid : _insidePlayers) if (Player* player = ObjectAccessor::GetPlayer(_owner, guid)) player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0); _insidePlayers.clear(); break; default: break; } } void SetValue(float value) { _value = RoundToInterval(value, 0.0f, 100.0f); } void HandleHeartbeat() { // update player list inside control zone std::vector targetList; SearchTargets(targetList); TeamId oldControllingTeam = GetControllingTeam(); float pointsGained = CalculatePointsPerSecond(targetList) * _heartbeatRate.count() / 1000.0f; if (pointsGained == 0) return; int32 oldRoundedValue = static_cast(_value); SetValue(_value + pointsGained); int32 roundedValue = static_cast(_value); if (oldRoundedValue == roundedValue) return; TeamId newControllingTeam = GetControllingTeam(); TeamId assaultingTeam = pointsGained > 0 ? TEAM_ALLIANCE : TEAM_HORDE; if (oldControllingTeam != newControllingTeam) _contestedTriggered = false; if (oldControllingTeam != TEAM_ALLIANCE && newControllingTeam == TEAM_ALLIANCE) TriggerEvent(_owner.GetGOInfo()->controlZone.ProgressEventAlliance); else if (oldControllingTeam != TEAM_HORDE && newControllingTeam == TEAM_HORDE) TriggerEvent(_owner.GetGOInfo()->controlZone.ProgressEventHorde); else if (oldControllingTeam == TEAM_HORDE && newControllingTeam == TEAM_NEUTRAL) TriggerEvent(_owner.GetGOInfo()->controlZone.NeutralEventHorde); else if (oldControllingTeam == TEAM_ALLIANCE && newControllingTeam == TEAM_NEUTRAL) TriggerEvent(_owner.GetGOInfo()->controlZone.NeutralEventAlliance); if (roundedValue == 100 && newControllingTeam == TEAM_ALLIANCE && assaultingTeam == TEAM_ALLIANCE) TriggerEvent(_owner.GetGOInfo()->controlZone.CaptureEventAlliance); else if (roundedValue == 0 && newControllingTeam == TEAM_HORDE && assaultingTeam == TEAM_HORDE) TriggerEvent(_owner.GetGOInfo()->controlZone.CaptureEventHorde); if (oldRoundedValue == 100 && assaultingTeam == TEAM_HORDE && !_contestedTriggered) { TriggerEvent(_owner.GetGOInfo()->controlZone.ContestedEventHorde); _contestedTriggered = true; } else if (oldRoundedValue == 0 && assaultingTeam == TEAM_ALLIANCE && !_contestedTriggered) { TriggerEvent(_owner.GetGOInfo()->controlZone.ContestedEventAlliance); _contestedTriggered = true; } for (Player* player : targetList) player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, roundedValue); } void SearchTargets(std::vector& targetList) { Trinity::AnyUnitInObjectRangeCheck check(&_owner, _owner.GetGOInfo()->controlZone.radius, true); Trinity::PlayerListSearcher searcher(&_owner, targetList, check); Cell::VisitWorldObjects(&_owner, searcher, _owner.GetGOInfo()->controlZone.radius); HandleUnitEnterExit(targetList); } float CalculatePointsPerSecond(std::vector const& targetList) const { int32 hordePlayers = 0; int32 alliancePlayers = 0; for (Player const* player : targetList) { if (!player->IsOutdoorPvPActive()) continue; if (player->GetTeamId() == TEAM_HORDE) hordePlayers++; else alliancePlayers++; } int8 factionCoefficient = 0; // alliance superiority = 1; horde superiority = -1 if (alliancePlayers > hordePlayers) factionCoefficient = 1; else if (hordePlayers > alliancePlayers) factionCoefficient = -1; float const timeNeeded = CalculateTimeNeeded(hordePlayers, alliancePlayers); if (timeNeeded == 0.0f) return 0.0f; return 100.0f / timeNeeded * static_cast(factionCoefficient); } float CalculateTimeNeeded(int32 hordePlayers, int32 alliancePlayers) const { uint32 const uncontestedTime = _owner.GetGOInfo()->controlZone.UncontestedTime; uint32 const delta = std::abs(alliancePlayers - hordePlayers); uint32 const minSuperiority = _owner.GetGOInfo()->controlZone.minSuperiority; if (delta < minSuperiority) return 0.0f; // return the uncontested time if controlzone is not contested if (uncontestedTime && (hordePlayers == 0 || alliancePlayers == 0)) return static_cast(uncontestedTime); uint32 const minTime = _owner.GetGOInfo()->controlZone.minTime; uint32 const maxTime = _owner.GetGOInfo()->controlZone.maxTime; uint32 const maxSuperiority = _owner.GetGOInfo()->controlZone.maxSuperiority; float const slope = static_cast(minTime - maxTime) / static_cast(std::max(maxSuperiority - minSuperiority, 1)); float const intercept = static_cast(maxTime) - slope * static_cast(minSuperiority); return slope * static_cast(delta) + intercept; } void HandleUnitEnterExit(std::vector const& newTargetList) { GuidUnorderedSet exitPlayers(std::move(_insidePlayers)); std::vector enteringPlayers; for (Player* unit : newTargetList) { if (exitPlayers.erase(unit->GetGUID()) == 0) // erase(key_type) returns number of elements erased enteringPlayers.push_back(unit); _insidePlayers.insert(unit->GetGUID()); } for (Player* player : enteringPlayers) { player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 1); player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, static_cast(_value)); player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate3, _owner.GetGOInfo()->controlZone.neutralPercent); } for (ObjectGuid const& exitPlayerGuid : exitPlayers) { if (Player* player = ObjectAccessor::GetPlayer(_owner, exitPlayerGuid)) { player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0); } } } float GetMaxHordeValue() const { // ex: if neutralPercent is 40; then 0 - 30 is Horde Controlled return 50.0f - _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f; } float GetMinAllianceValue() const { // ex: if neutralPercent is 40; then 70 - 100 is Alliance Controlled return 50.0f + _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f; } void TriggerEvent(uint32 eventId) const { if (eventId <= 0) return; _owner.GetMap()->UpdateSpawnGroupConditions(); GameEvents::Trigger(eventId, &_owner, nullptr); } uint32 GetStartingValue() const { return _owner.GetGOInfo()->controlZone.startingValue; } private: Milliseconds _heartbeatRate; TimeTracker _heartbeatTracker; GuidUnorderedSet _insidePlayers; TeamId _previousTeamId; float _value; bool _contestedTriggered; }; SetControlZoneValue::SetControlZoneValue(Optional value /*= { }*/) : _value(value) { } void SetControlZoneValue::Execute(GameObjectTypeBase& type) const { if (ControlZone* controlZone = dynamic_cast(&type)) { uint32 value = controlZone->GetStartingValue(); if (_value.has_value()) value = *_value; controlZone->SetValue(value); } } } GameObject::GameObject() : WorldObject(false), MapObject(), m_model(nullptr), m_goValue(), m_stringIds(), m_AI(nullptr), m_respawnCompatibilityMode(false), _animKitId(0), _worldEffectID(0) { m_objectType |= TYPEMASK_GAMEOBJECT; m_objectTypeId = TYPEID_GAMEOBJECT; m_updateFlag.Stationary = true; m_updateFlag.Rotation = true; m_entityFragments.Add(WowCS::EntityFragment::Tag_GameObject, false); m_respawnTime = 0; m_respawnDelayTime = 300; m_despawnDelay = 0; m_despawnRespawnTime = 0s; m_restockTime = 0; m_lootState = GO_NOT_READY; m_spawnedByDefault = true; m_usetimes = 0; m_spellId = 0; m_cooldownTime = 0; m_prevGoState = GO_STATE_ACTIVE; m_goInfo = nullptr; m_goData = nullptr; m_packedRotation = 0; m_goTemplateAddon = nullptr; m_spawnId = UI64LIT(0); ResetLootMode(); // restore default loot mode m_stationaryPosition.Relocate(0.0f, 0.0f, 0.0f, 0.0f); } GameObject::~GameObject() { delete m_AI; delete m_model; } void GameObject::AIM_Destroy() { delete m_AI; m_AI = nullptr; } bool GameObject::AIM_Initialize() { AIM_Destroy(); m_AI = FactorySelector::SelectGameObjectAI(this); if (!m_AI) return false; m_AI->InitializeAI(); return true; } std::string const& GameObject::GetAIName() const { return sObjectMgr->GetGameObjectTemplate(GetEntry())->AIName; } void GameObject::CleanupsBeforeDelete(bool finalCleanup) { SetVignette(0); WorldObject::CleanupsBeforeDelete(finalCleanup); RemoveFromOwner(); } void GameObject::RemoveFromOwner() { ObjectGuid ownerGUID = GetOwnerGUID(); if (!ownerGUID) return; if (Unit* owner = ObjectAccessor::GetUnit(*this, ownerGUID)) { owner->RemoveGameObject(this, false); ASSERT(!GetOwnerGUID()); return; } // This happens when a mage portal is despawned after the caster changes map (for example using the portal) TC_LOG_DEBUG("misc", "Removed GameObject ({} Entry: {} SpellId: {} LinkedGO: {}) that just lost any reference to the owner ({}) GO list", GetGUID().ToString(), GetGOInfo()->entry, m_spellId, GetGOInfo()->GetLinkedGameObjectEntry(), ownerGUID.ToString()); SetOwnerGUID(ObjectGuid::Empty); } void GameObject::AddToWorld() { ///- Register the gameobject for guid lookup if (!IsInWorld()) { if (m_zoneScript) m_zoneScript->OnGameObjectCreate(this); GetMap()->GetObjectsStore().Insert(this); if (m_spawnId) GetMap()->GetGameObjectBySpawnIdStore().insert(std::make_pair(m_spawnId, this)); // The state can be changed after GameObject::Create but before GameObject::AddToWorld bool toggledState = GetGoType() == GAMEOBJECT_TYPE_CHEST ? getLootState() == GO_READY : (GetGoState() == GO_STATE_READY || IsTransport()); if (m_model) { if (Transport* trans = ToTransport()) trans->SetDelayedAddModelToMap(); else GetMap()->InsertGameObjectModel(*m_model); } EnableCollision(toggledState); WorldObject::AddToWorld(); } } void GameObject::RemoveFromWorld() { ///- Remove the gameobject from the accessor if (IsInWorld()) { if (m_zoneScript) m_zoneScript->OnGameObjectRemove(this); RemoveFromOwner(); if (m_model) if (GetMap()->ContainsGameObjectModel(*m_model)) GetMap()->RemoveGameObjectModel(*m_model); // If linked trap exists, despawn it if (GameObject* linkedTrap = GetLinkedTrap()) linkedTrap->DespawnOrUnsummon(); WorldObject::RemoveFromWorld(); if (m_spawnId) Trinity::Containers::MultimapErasePair(GetMap()->GetGameObjectBySpawnIdStore(), m_spawnId, this); GetMap()->GetObjectsStore().Remove(this); } } bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit, bool dynamic, ObjectGuid::LowType spawnid) { ASSERT(map); SetMap(map); Relocate(pos); m_stationaryPosition.Relocate(pos); if (!IsPositionValid()) { TC_LOG_ERROR("misc", "Gameobject (Spawn id: {} Entry: {}) not created. Suggested coordinates isn't valid (X: {} Y: {})", GetSpawnId(), entry, pos.GetPositionX(), pos.GetPositionY()); return false; } // Set if this object can handle dynamic spawns if (!dynamic) SetRespawnCompatibilityMode(); UpdatePositionData(); SetZoneScript(); if (m_zoneScript) { entry = m_zoneScript->GetGameObjectEntry(m_spawnId, entry); if (!entry) return false; } GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry); if (!goInfo) { TC_LOG_ERROR("sql.sql", "Gameobject (Spawn id: {} Entry: {}) not created: non-existing entry in `gameobject_template`. Map: {} (X: {} Y: {} Z: {})", GetSpawnId(), entry, map->GetId(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); return false; } if (goInfo->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT) { TC_LOG_ERROR("sql.sql", "Gameobject (Spawn id: {} Entry: {}) not created: gameobject type GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT cannot be manually created.", GetSpawnId(), entry); return false; } ObjectGuid guid; if (goInfo->type != GAMEOBJECT_TYPE_TRANSPORT) guid = ObjectGuid::Create(map->GetId(), goInfo->entry, map->GenerateLowGuid()); else { guid = ObjectGuid::Create(map->GenerateLowGuid()); m_updateFlag.ServerTime = true; } Object::_Create(guid); m_goInfo = goInfo; m_goTemplateAddon = sObjectMgr->GetGameObjectTemplateAddon(entry); if (goInfo->type >= MAX_GAMEOBJECT_TYPE) { TC_LOG_ERROR("sql.sql", "Gameobject ({} Spawn id: {} Entry: {}) not created: non-existing GO type '{}' in `gameobject_template`. It will crash client if created.", guid.ToString(), GetSpawnId(), entry, goInfo->type); return false; } SetLocalRotation(rotation.x, rotation.y, rotation.z, rotation.w); GameObjectAddon const* gameObjectAddon = sObjectMgr->GetGameObjectAddon(GetSpawnId()); // For most of gameobjects is (0, 0, 0, 1) quaternion, there are only some transports with not standard rotation QuaternionData parentRotation; if (gameObjectAddon) parentRotation = gameObjectAddon->ParentRotation; SetParentRotation(parentRotation); SetObjectScale(goInfo->size); if (GameObjectOverride const* goOverride = GetGameObjectOverride()) { SetFaction(goOverride->Faction); ReplaceAllFlags(GameObjectFlags(goOverride->Flags)); } if (m_goTemplateAddon) { if (m_goTemplateAddon->WorldEffectID) { m_updateFlag.GameObject = true; SetWorldEffectID(m_goTemplateAddon->WorldEffectID); } if (m_goTemplateAddon->AIAnimKitID) _animKitId = m_goTemplateAddon->AIAnimKitID; } SetEntry(goInfo->entry); // set name for logs usage, doesn't affect anything ingame SetName(goInfo->name); SetDisplayId(goInfo->displayId); CreateModel(); // GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3 SetGoType(GameobjectTypes(goInfo->type)); m_prevGoState = goState; SetGoState(goState); SetGoArtKit(artKit); switch (goInfo->type) { case GAMEOBJECT_TYPE_FISHINGHOLE: SetGoAnimProgress(animProgress); m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock); break; case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: { m_goValue.Building.DestructibleHitpoint = sObjectMgr->GetDestructibleHitpoint(GetGOInfo()->destructibleBuilding.HealthRec); m_goValue.Building.Health = m_goValue.Building.DestructibleHitpoint ? m_goValue.Building.DestructibleHitpoint->GetMaxHealth() : 0; SetGoAnimProgress(255); // yes, even after the updatefield rewrite this garbage hack is still in client QuaternionData reinterpretId; memcpy(&reinterpretId.x, &m_goInfo->destructibleBuilding.DestructibleModelRec, sizeof(float)); SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::ParentRotation), reinterpretId); break; } case GAMEOBJECT_TYPE_TRANSPORT: { m_updateFlag.GameObject = true; m_goTypeImpl = std::make_unique(*this); if (goInfo->transport.startOpen) SetGoState(GO_STATE_TRANSPORT_STOPPED); else SetGoState(GO_STATE_TRANSPORT_ACTIVE); SetGoAnimProgress(animProgress); setActive(true); break; } case GAMEOBJECT_TYPE_FISHINGNODE: SetLevel(0); SetGoAnimProgress(255); break; case GAMEOBJECT_TYPE_TRAP: if (GetGOInfo()->trap.stealthed) { m_stealth.AddFlag(STEALTH_TRAP); m_stealth.AddValue(STEALTH_TRAP, 70); } if (GetGOInfo()->trap.stealthAffected) { m_invisibility.AddFlag(INVISIBILITY_TRAP); m_invisibility.AddValue(INVISIBILITY_TRAP, 300); } break; case GAMEOBJECT_TYPE_CONTROL_ZONE: m_goTypeImpl = std::make_unique(*this); setActive(true); break; case GAMEOBJECT_TYPE_NEW_FLAG: m_goTypeImpl = std::make_unique(*this); if (map->Instanceable()) setActive(true); break; case GAMEOBJECT_TYPE_NEW_FLAG_DROP: if (map->Instanceable()) setActive(true); break; case GAMEOBJECT_TYPE_PHASEABLE_MO: RemoveFlag(GameObjectFlags(0xF00)); SetFlag(GameObjectFlags((m_goInfo->phaseableMO.AreaNameSet & 0xF) << 8)); break; case GAMEOBJECT_TYPE_CAPTURE_POINT: SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::SpellVisualID), m_goInfo->capturePoint.SpellVisual1); m_goValue.CapturePoint.AssaultTimer = 0; m_goValue.CapturePoint.LastTeamCapture = TEAM_NEUTRAL; m_goValue.CapturePoint.State = WorldPackets::Battleground::BattlegroundCapturePointState::Neutral; UpdateCapturePoint(); if (map->Instanceable()) setActive(true); break; default: SetGoAnimProgress(animProgress); break; } if (gameObjectAddon) { if (gameObjectAddon->InvisibilityValue) { m_invisibility.AddFlag(gameObjectAddon->invisibilityType); m_invisibility.AddValue(gameObjectAddon->invisibilityType, gameObjectAddon->InvisibilityValue); } if (gameObjectAddon->WorldEffectID) { m_updateFlag.GameObject = true; SetWorldEffectID(gameObjectAddon->WorldEffectID); } if (gameObjectAddon->AIAnimKitID) _animKitId = gameObjectAddon->AIAnimKitID; } if (uint32 vignetteId = GetGOInfo()->GetSpawnVignette()) SetVignette(vignetteId); LastUsedScriptID = GetGOInfo()->ScriptId; m_stringIds[AsUnderlyingType(StringIdType::Template)] = &goInfo->StringId; AIM_Initialize(); if (spawnid) m_spawnId = spawnid; if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry()) { if (GameObject* linkedGo = GameObject::CreateGameObject(linkedEntry, map, pos, rotation, 255, GO_STATE_READY)) { SetLinkedTrap(linkedGo); if (!map->AddToMap(linkedGo)) delete linkedGo; } } // Check if GameObject is Infinite if (goInfo->IsInfiniteGameObject()) SetVisibilityDistanceOverride(VisibilityDistanceType::Infinite); // Check if GameObject is Gigantic if (goInfo->IsGiganticGameObject()) SetVisibilityDistanceOverride(VisibilityDistanceType::Gigantic); // Check if GameObject is Large if (goInfo->IsLargeGameObject()) SetVisibilityDistanceOverride(VisibilityDistanceType::Large); return true; } GameObject* GameObject::CreateGameObject(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit /*= 0*/) { GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry); if (!goInfo) return nullptr; GameObject* go = new GameObject(); if (!go->Create(entry, map, pos, rotation, animProgress, goState, artKit, false, 0)) { delete go; return nullptr; } return go; } GameObject* GameObject::CreateGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap /*= true*/) { GameObject* go = new GameObject(); if (!go->LoadFromDB(spawnId, map, addToMap)) { delete go; return nullptr; } return go; } void GameObject::Update(uint32 diff) { WorldObject::Update(diff); if (AI()) AI()->UpdateAI(diff); else if (!AIM_Initialize()) TC_LOG_ERROR("misc", "Could not initialize GameObjectAI"); if (m_despawnDelay) { if (m_despawnDelay > diff) m_despawnDelay -= diff; else { m_despawnDelay = 0; DespawnOrUnsummon(0ms, m_despawnRespawnTime); } } if (m_goTypeImpl) m_goTypeImpl->Update(diff); if (m_perPlayerState) { for (auto itr = m_perPlayerState->begin(); itr != m_perPlayerState->end(); ) { if (itr->second.ValidUntil > GameTime::GetSystemTime()) { ++itr; continue; } Player* seer = ObjectAccessor::GetPlayer(*this, itr->first); bool needsStateUpdate = itr->second.State != GetGoState(); bool despawned = itr->second.Despawned; itr = m_perPlayerState->erase(itr); if (seer) { if (despawned) { seer->UpdateVisibilityOf(this); } else if (needsStateUpdate) { UF::ObjectData::Base objMask; UF::GameObjectData::Base goMask; goMask.MarkChanged(&UF::GameObjectData::State); UpdateData udata(GetMapId()); BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), seer); WorldPacket packet; udata.BuildPacket(&packet); seer->SendDirectMessage(&packet); } } } } switch (m_lootState) { case GO_NOT_READY: { switch (GetGoType()) { case GAMEOBJECT_TYPE_TRAP: { // Arming Time for GAMEOBJECT_TYPE_TRAP (6) GameObjectTemplate const* goInfo = GetGOInfo(); // Bombs if (goInfo->trap.charges == 2) // Hardcoded tooltip value m_cooldownTime = GameTime::GetGameTimeMS() + 10 * IN_MILLISECONDS; else if (Unit* owner = GetOwner()) if (owner->IsInCombat()) m_cooldownTime = GameTime::GetGameTimeMS() + goInfo->trap.startDelay * IN_MILLISECONDS; SetLootState(GO_READY); break; } case GAMEOBJECT_TYPE_FISHINGNODE: { // fishing code (bobber ready) if (GameTime::GetGameTime() > m_respawnTime - FISHING_BOBBER_READY_TIME) { // splash bobber (bobber ready now) Unit* caster = GetOwner(); if (caster && caster->GetTypeId() == TYPEID_PLAYER) SendCustomAnim(0); m_lootState = GO_READY; // can be successfully open with some chance } return; } case GAMEOBJECT_TYPE_CHEST: if (m_restockTime > GameTime::GetGameTime()) return; // If there is no restock timer, or if the restock timer passed, the chest becomes ready to loot m_restockTime = 0; m_lootState = GO_READY; ClearLoot(); UpdateDynamicFlagsForNearbyPlayers(); break; default: m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY break; } [[fallthrough]]; } case GO_READY: { if (m_respawnCompatibilityMode) { if (m_respawnTime > 0) // timer on { time_t now = GameTime::GetGameTime(); if (m_respawnTime <= now) // timer expired { ObjectGuid dbtableHighGuid = ObjectGuid::Create(GetMapId(), GetEntry(), m_spawnId); time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); if (linkedRespawntime) // Can't respawn, the master is dead { ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); if (targetGuid == dbtableHighGuid) // if linking self, never respawn SetRespawnTime(WEEK); else m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little SaveRespawnTime(); return; } m_respawnTime = 0; m_SkillupList.clear(); m_usetimes = 0; switch (GetGoType()) { case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now { Unit* caster = GetOwner(); if (caster && caster->GetTypeId() == TYPEID_PLAYER) { caster->ToPlayer()->RemoveGameObject(this, false); caster->ToPlayer()->SendDirectMessage(WorldPackets::GameObject::FishEscaped().Write()); } // can be delete m_lootState = GO_JUST_DEACTIVATED; return; } case GAMEOBJECT_TYPE_DOOR: case GAMEOBJECT_TYPE_BUTTON: // We need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for battlegrounds) if (GetGoState() != GO_STATE_READY) ResetDoorOrButton(); break; case GAMEOBJECT_TYPE_FISHINGHOLE: // Initialize a new max fish count on respawn m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock); break; default: break; } // Despawn timer if (!m_spawnedByDefault) { // Can be despawned or destroyed SetLootState(GO_JUST_DEACTIVATED); return; } // Call AI Reset (required for example in SmartAI to clear one time events) if (AI()) AI()->Reset(); // Respawn timer uint32 poolid = GetGameObjectData() ? GetGameObjectData()->poolId : 0; if (poolid) sPoolMgr->UpdatePool(GetMap()->GetPoolData(), poolid, GetSpawnId()); else GetMap()->AddToMap(this); } } } // Set respawn timer if (!m_respawnCompatibilityMode && m_respawnTime > 0) SaveRespawnTime(); if (isSpawned()) { GameObjectTemplate const* goInfo = GetGOInfo(); if (goInfo->type == GAMEOBJECT_TYPE_TRAP) { if (GameTime::GetGameTimeMS() < m_cooldownTime) break; // Type 2 (bomb) does not need to be triggered by a unit and despawns after casting its spell. if (goInfo->trap.charges == 2) { SetLootState(GO_ACTIVATED); break; } // Type 0 despawns after being triggered, type 1 does not. /// @todo This is activation radius. Casting radius must be selected from spell data. float radius = goInfo->trap.radius / 2.f; // this division seems to date back to when the field was called diameter, don't think it is still relevant. if (!radius) break; // Pointer to appropriate target if found any Unit* target = nullptr; if (GetOwner() || goInfo->trap.Checkallunits) { // Hunter trap: Search units which are unfriendly to the trap's owner Trinity::NearestAttackableNoTotemUnitInObjectRangeCheck checker(this, radius); Trinity::UnitLastSearcher searcher(this, target, checker); Cell::VisitAllObjects(this, searcher, radius); } else { // Environmental trap: Any player Player* player = nullptr; Trinity::AnyUnitInObjectRangeCheck checker(this, radius); Trinity::PlayerSearcher searcher(this, player, checker); Cell::VisitWorldObjects(this, searcher, radius); target = player; } if (target) SetLootState(GO_ACTIVATED, target); } else if (goInfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT) { bool hordeCapturing = m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::ContestedHorde; bool allianceCapturing = m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::ContestedAlliance; if (hordeCapturing || allianceCapturing) { if (m_goValue.CapturePoint.AssaultTimer <= diff) { m_goValue.CapturePoint.State = hordeCapturing ? WorldPackets::Battleground::BattlegroundCapturePointState::HordeCaptured : WorldPackets::Battleground::BattlegroundCapturePointState::AllianceCaptured; if (hordeCapturing) { m_goValue.CapturePoint.State = WorldPackets::Battleground::BattlegroundCapturePointState::HordeCaptured; if (BattlegroundMap* map = GetMap()->ToBattlegroundMap()) { if (Battleground* bg = map->GetBG()) { if (goInfo->capturePoint.CaptureEventHorde) GameEvents::Trigger(goInfo->capturePoint.CaptureEventHorde, this, this); bg->SendBroadcastText(goInfo->capturePoint.CaptureBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE); } } } else { m_goValue.CapturePoint.State = WorldPackets::Battleground::BattlegroundCapturePointState::AllianceCaptured; if (BattlegroundMap* map = GetMap()->ToBattlegroundMap()) { if (Battleground* bg = map->GetBG()) { if (goInfo->capturePoint.CaptureEventAlliance) GameEvents::Trigger(goInfo->capturePoint.CaptureEventAlliance, this, this); bg->SendBroadcastText(goInfo->capturePoint.CaptureBroadcastAlliance, CHAT_MSG_BG_SYSTEM_ALLIANCE); } } } m_goValue.CapturePoint.LastTeamCapture = hordeCapturing ? TEAM_HORDE : TEAM_ALLIANCE; UpdateCapturePoint(); } else m_goValue.CapturePoint.AssaultTimer -= diff; } } else if (uint32 max_charges = goInfo->GetCharges()) { if (m_usetimes >= max_charges) { m_usetimes = 0; SetLootState(GO_JUST_DEACTIVATED); // can be despawned or destroyed } } } break; } case GO_ACTIVATED: { switch (GetGoType()) { case GAMEOBJECT_TYPE_DOOR: case GAMEOBJECT_TYPE_BUTTON: if (m_cooldownTime && GameTime::GetGameTimeMS() >= m_cooldownTime) ResetDoorOrButton(); break; case GAMEOBJECT_TYPE_GOOBER: if (GameTime::GetGameTimeMS() >= m_cooldownTime) { RemoveFlag(GO_FLAG_IN_USE); SetLootState(GO_JUST_DEACTIVATED); m_cooldownTime = 0; } break; case GAMEOBJECT_TYPE_CHEST: if (m_loot) { m_loot->Update(); // Non-consumable chest was partially looted and restock time passed, restock all loot now if (GetGOInfo()->chest.consumable == 0 && m_restockTime && GameTime::GetGameTime() >= m_restockTime) { m_restockTime = 0; m_lootState = GO_READY; ClearLoot(); UpdateDynamicFlagsForNearbyPlayers(); } } for (auto&& [playerOwner, loot] : m_personalLoot) loot->Update(); break; case GAMEOBJECT_TYPE_TRAP: { GameObjectTemplate const* goInfo = GetGOInfo(); if (goInfo->trap.charges == 2 && goInfo->trap.spell) { /// @todo nullptr target won't work for target type 1 CastSpell(nullptr, goInfo->trap.spell); SetLootState(GO_JUST_DEACTIVATED); } else if (Unit* target = ObjectAccessor::GetUnit(*this, m_lootStateUnitGUID)) { // Some traps do not have a spell but should be triggered CastSpellExtraArgs args; args.SetOriginalCaster(GetOwnerGUID()); if (goInfo->trap.spell) CastSpell(target, goInfo->trap.spell, args); // Template value or 4 seconds m_cooldownTime = GameTime::GetGameTimeMS() + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)) * IN_MILLISECONDS; if (goInfo->trap.charges == 1) SetLootState(GO_JUST_DEACTIVATED); else if (!goInfo->trap.charges) SetLootState(GO_READY); } break; } default: break; } break; } case GO_JUST_DEACTIVATED: { // If nearby linked trap exists, despawn it if (GameObject* linkedTrap = GetLinkedTrap()) linkedTrap->DespawnOrUnsummon(); //if Gameobject should cast spell, then this, but some GOs (type = 10) should be destroyed if (GetGoType() == GAMEOBJECT_TYPE_GOOBER) { uint32 spellId = GetGOInfo()->goober.spell; if (spellId) { for (GuidSet::const_iterator it = m_unique_users.begin(); it != m_unique_users.end(); ++it) // m_unique_users can contain only player GUIDs if (Player* owner = ObjectAccessor::GetPlayer(*this, *it)) owner->CastSpell(owner, spellId, false); m_unique_users.clear(); m_usetimes = 0; } SetGoState(GO_STATE_READY); //any return here in case battleground traps if (GameObjectOverride const* goOverride = GetGameObjectOverride()) if (goOverride->Flags & GO_FLAG_NODESPAWN) return; } ClearLoot(); // Do not delete chests or goobers that are not consumed on loot, while still allowing them to despawn when they expire if summoned bool isSummonedAndExpired = (GetOwner() || GetSpellId()) && m_respawnTime == 0; if ((GetGoType() == GAMEOBJECT_TYPE_CHEST || GetGoType() == GAMEOBJECT_TYPE_GOOBER) && !GetGOInfo()->IsDespawnAtAction() && !isSummonedAndExpired) { if (GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.chestRestockTime > 0) { // Start restock timer when the chest is fully looted m_restockTime = GameTime::GetGameTime() + GetGOInfo()->chest.chestRestockTime; SetLootState(GO_NOT_READY); UpdateDynamicFlagsForNearbyPlayers(); } else SetLootState(GO_READY); UpdateObjectVisibility(); return; } else if (!GetOwnerGUID().IsEmpty() || GetSpellId()) { SetRespawnTime(0); if (GetGoType() == GAMEOBJECT_TYPE_NEW_FLAG_DROP) { if (GameObject* go = GetMap()->GetGameObject(GetOwnerGUID())) go->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::InBase, nullptr)); } Delete(); return; } SetLootState(GO_NOT_READY); //burning flags in some battlegrounds, if you find better condition, just add it if (GetGOInfo()->IsDespawnAtAction() || GetGoAnimProgress() > 0) { SendGameObjectDespawn(); //reset flags if (GameObjectOverride const* goOverride = GetGameObjectOverride()) ReplaceAllFlags(GameObjectFlags(goOverride->Flags)); } if (!m_respawnDelayTime) return; if (!m_spawnedByDefault) { m_respawnTime = 0; if (m_spawnId) UpdateObjectVisibilityOnDestroy(); else Delete(); return; } uint32 respawnDelay = m_respawnDelayTime; if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) GetMap()->ApplyDynamicModeRespawnScaling(this, this->m_spawnId, respawnDelay, scalingMode); m_respawnTime = GameTime::GetGameTime() + respawnDelay; // if option not set then object will be saved at grid unload // Otherwise just save respawn time to map object memory SaveRespawnTime(); if (m_respawnCompatibilityMode) UpdateObjectVisibilityOnDestroy(); else AddObjectToRemoveList(); break; } } } GameObjectOverride const* GameObject::GetGameObjectOverride() const { if (m_spawnId) { if (GameObjectOverride const* goOverride = sObjectMgr->GetGameObjectOverride(m_spawnId)) return goOverride; } return m_goTemplateAddon; } void GameObject::Refresh() { // Do not refresh despawned GO from spellcast (GO's from spellcast are destroyed after despawn) if (m_respawnTime > 0 && m_spawnedByDefault) return; if (isSpawned()) GetMap()->AddToMap(this); } void GameObject::AddUniqueUse(Player* player) { AddUse(); m_unique_users.insert(player->GetGUID()); } void GameObject::DespawnOrUnsummon(Milliseconds delay, Seconds forceRespawnTime) { if (delay > 0ms) { if (!m_despawnDelay || m_despawnDelay > delay.count()) { m_despawnDelay = delay.count(); m_despawnRespawnTime = forceRespawnTime; } } else { if (m_goData) { uint32 const respawnDelay = (forceRespawnTime > 0s) ? forceRespawnTime.count() : m_goData->spawntimesecs; SaveRespawnTime(respawnDelay); } Delete(); } } void GameObject::DespawnForPlayer(Player* seer, Seconds respawnTime) { PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[seer->GetGUID()]; perPlayerState.ValidUntil = GameTime::GetSystemTime() + respawnTime; perPlayerState.Despawned = true; seer->UpdateVisibilityOf(this); } void GameObject::Delete() { SetLootState(GO_NOT_READY); RemoveFromOwner(); if (m_goInfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT) { WorldPackets::Battleground::CapturePointRemoved packet(GetGUID()); SendMessageToSet(packet.Write(), true); } SendGameObjectDespawn(); if (m_goInfo->type != GAMEOBJECT_TYPE_TRANSPORT) SetGoState(GO_STATE_READY); if (GameObjectOverride const* goOverride = GetGameObjectOverride()) ReplaceAllFlags(GameObjectFlags(goOverride->Flags)); uint32 poolid = GetGameObjectData() ? GetGameObjectData()->poolId : 0; if (m_respawnCompatibilityMode && poolid) sPoolMgr->UpdatePool(GetMap()->GetPoolData(), poolid, GetSpawnId()); else AddObjectToRemoveList(); } void GameObject::SendGameObjectDespawn() { WorldPackets::GameObject::GameObjectDespawn packet; packet.ObjectGUID = GetGUID(); SendMessageToSet(packet.Write(), true); } Loot* GameObject::GetFishLoot(Player* lootOwner) { uint32 defaultzone = 1; Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING, nullptr); uint32 areaId = GetAreaId(); ItemContext itemContext = ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), lootOwner); while (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId)) { fishLoot->FillLoot(areaId, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_DEFAULT, itemContext); if (!fishLoot->isLooted()) break; areaId = areaEntry->ParentAreaID; } if (fishLoot->isLooted()) fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_DEFAULT, itemContext); return fishLoot; } Loot* GameObject::GetFishLootJunk(Player* lootOwner) { uint32 defaultzone = 1; Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING_JUNK, nullptr); uint32 areaId = GetAreaId(); ItemContext itemContext = ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), lootOwner); while (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId)) { fishLoot->FillLoot(areaId, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH, itemContext); if (!fishLoot->isLooted()) break; areaId = areaEntry->ParentAreaID; } if (fishLoot->isLooted()) fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH, itemContext); return fishLoot; } void GameObject::SaveToDB() { // this should only be used when the gameobject has already been loaded // preferably after adding to map, because mapid may not be valid otherwise GameObjectData const* data = sObjectMgr->GetGameObjectData(m_spawnId); if (!data) { TC_LOG_ERROR("misc", "GameObject::SaveToDB failed, cannot get gameobject data!"); return; } uint32 mapId = GetMapId(); if (TransportBase* transport = GetTransport()) if (transport->GetMapIdForSpawning() >= 0) mapId = transport->GetMapIdForSpawning(); SaveToDB(mapId, data->spawnDifficulties); } void GameObject::SaveToDB(uint32 mapid, std::vector const& spawnDifficulties) { GameObjectTemplate const* goI = GetGOInfo(); if (!goI) return; if (!m_spawnId) m_spawnId = sObjectMgr->GenerateGameObjectSpawnId(); // update in loaded data (changing data only in this place) GameObjectData& data = sObjectMgr->NewOrExistGameObjectData(m_spawnId); if (!data.spawnId) data.spawnId = m_spawnId; ASSERT(data.spawnId == m_spawnId); data.id = GetEntry(); data.mapId = GetMapId(); data.spawnPoint.Relocate(this); data.rotation = m_localRotation; data.spawntimesecs = m_spawnedByDefault ? m_respawnDelayTime : -(int32)m_respawnDelayTime; data.animprogress = GetGoAnimProgress(); data.goState = GetGoState(); data.spawnDifficulties = spawnDifficulties; data.artKit = GetGoArtKit(); if (!data.spawnGroupData) data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); data.phaseId = GetDBPhase() > 0 ? GetDBPhase() : data.phaseId; data.phaseGroup = GetDBPhase() < 0 ? -GetDBPhase() : data.phaseGroup; // Update in DB WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction(); uint8 index = 0; WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); stmt->setUInt64(0, m_spawnId); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT); stmt->setUInt64(index++, m_spawnId); stmt->setUInt32(index++, GetEntry()); stmt->setUInt16(index++, uint16(mapid)); stmt->setString(index++, [&data]() -> std::string { std::ostringstream os; if (!data.spawnDifficulties.empty()) { auto itr = data.spawnDifficulties.begin(); os << int32(*itr++); for (; itr != data.spawnDifficulties.end(); ++itr) os << ',' << int32(*itr); } return std::move(os).str(); }()); stmt->setUInt8(index++, data.phaseUseFlags); stmt->setUInt32(index++, data.phaseId); stmt->setUInt32(index++, data.phaseGroup); stmt->setInt32(index++, data.terrainSwapMap); stmt->setFloat(index++, GetPositionX()); stmt->setFloat(index++, GetPositionY()); stmt->setFloat(index++, GetPositionZ()); stmt->setFloat(index++, GetOrientation()); stmt->setFloat(index++, m_localRotation.x); stmt->setFloat(index++, m_localRotation.y); stmt->setFloat(index++, m_localRotation.z); stmt->setFloat(index++, m_localRotation.w); stmt->setInt32(index++, int32(m_respawnDelayTime)); stmt->setUInt8(index++, GetGoAnimProgress()); stmt->setUInt8(index++, uint8(GetGoState())); stmt->setString(index++, sObjectMgr->GetScriptName(data.scriptId)); if (std::string_view stringId = GetStringId(StringIdType::Spawn); !stringId.empty()) stmt->setString(index++, stringId); else stmt->setNull(index++); trans->Append(stmt); WorldDatabase.CommitTransaction(trans); } bool GameObject::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool) { GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId); if (!data) { TC_LOG_ERROR("sql.sql", "Gameobject (GUID: {}) not found in table `gameobject`, can't load. ", spawnId); return false; } uint32 entry = data->id; //uint32 map_id = data->mapid; // already used before call uint32 animprogress = data->animprogress; GOState go_state = data->goState; uint32 artKit = data->artKit; m_spawnId = spawnId; m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); if (!Create(entry, map, data->spawnPoint, data->rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode, spawnId)) return false; PhasingHandler::InitDbPhaseShift(GetPhaseShift(), data->phaseUseFlags, data->phaseId, data->phaseGroup); PhasingHandler::InitDbVisibleMapId(GetPhaseShift(), data->terrainSwapMap); // Set StateWorldEffectsQuestObjectiveID if there is only one linked objective for this gameobject if (data && data->spawnTrackingQuestObjectives.size() == 1) SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::StateWorldEffectsQuestObjectiveID), data->spawnTrackingQuestObjectives.front()); if (data->spawntimesecs >= 0) { m_spawnedByDefault = true; if (!GetGOInfo()->GetDespawnPossibility() && !GetGOInfo()->IsDespawnAtAction()) { SetFlag(GO_FLAG_NODESPAWN); m_respawnDelayTime = 0; m_respawnTime = 0; } else { m_respawnDelayTime = data->spawntimesecs; m_respawnTime = GetMap()->GetGORespawnTime(m_spawnId); // ready to respawn if (m_respawnTime && m_respawnTime <= GameTime::GetGameTime()) { m_respawnTime = 0; GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId); } } } else { if (!m_respawnCompatibilityMode) { TC_LOG_WARN("sql.sql", "GameObject {} (SpawnID {}) is not spawned by default, but tries to use a non-hack spawn system. This will not work. Defaulting to compatibility mode.", entry, spawnId); m_respawnCompatibilityMode = true; } m_spawnedByDefault = false; m_respawnDelayTime = -data->spawntimesecs; m_respawnTime = 0; } m_goData = data; m_stringIds[AsUnderlyingType(StringIdType::Spawn)] = &data->StringId; if (addToMap && !GetMap()->AddToMap(this)) return false; return true; } /*static*/ bool GameObject::DeleteFromDB(ObjectGuid::LowType spawnId) { GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId); if (!data) return false; CharacterDatabaseTransaction charTrans = CharacterDatabase.BeginTransaction(); sMapMgr->DoForAllMapsWithMapId(data->mapId, [spawnId, charTrans](Map* map) -> void { // despawn all active objects, and remove their respawns std::vector toUnload; for (auto const& pair : Trinity::Containers::MapEqualRange(map->GetGameObjectBySpawnIdStore(), spawnId)) toUnload.push_back(pair.second); for (GameObject* obj : toUnload) map->AddObjectToRemoveList(obj); map->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, spawnId, charTrans); } ); // delete data from memory sObjectMgr->DeleteGameObjectData(spawnId); CharacterDatabase.CommitTransaction(charTrans); WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction(); // ... and the database WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); stmt->setUInt64(0, spawnId); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER); stmt->setUInt8(0, uint8(SPAWN_TYPE_GAMEOBJECT)); stmt->setUInt64(1, spawnId); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT); stmt->setUInt64(0, spawnId); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN); stmt->setUInt64(0, spawnId); stmt->setUInt32(1, LINKED_RESPAWN_GO_TO_GO); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN); stmt->setUInt64(0, spawnId); stmt->setUInt32(1, LINKED_RESPAWN_GO_TO_CREATURE); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN_MASTER); stmt->setUInt64(0, spawnId); stmt->setUInt32(1, LINKED_RESPAWN_GO_TO_GO); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN_MASTER); stmt->setUInt64(0, spawnId); stmt->setUInt32(1, LINKED_RESPAWN_CREATURE_TO_GO); trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT_ADDON); stmt->setUInt64(0, spawnId); trans->Append(stmt); WorldDatabase.CommitTransaction(trans); return true; } /*********************************************************/ /*** QUEST SYSTEM ***/ /*********************************************************/ bool GameObject::hasQuest(uint32 quest_id) const { return sObjectMgr->GetGOQuestRelations(GetEntry()).HasQuest(quest_id); } bool GameObject::hasInvolvedQuest(uint32 quest_id) const { return sObjectMgr->GetGOQuestInvolvedRelations(GetEntry()).HasQuest(quest_id); } bool GameObject::IsTransport() const { // If something is marked as a transport, don't transmit an out of range packet for it. GameObjectTemplate const* gInfo = GetGOInfo(); if (!gInfo) return false; return gInfo->type == GAMEOBJECT_TYPE_TRANSPORT || gInfo->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT; } bool GameObject::IsDestructibleBuilding() const { GameObjectTemplate const* gInfo = GetGOInfo(); if (!gInfo) return false; return gInfo->type == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING; } void GameObject::SaveRespawnTime(uint32 forceDelay) { if (m_goData && (forceDelay || m_respawnTime > GameTime::GetGameTime()) && m_spawnedByDefault) { if (m_respawnCompatibilityMode) { RespawnInfo ri; ri.type = SPAWN_TYPE_GAMEOBJECT; ri.spawnId = m_spawnId; ri.respawnTime = m_respawnTime; GetMap()->SaveRespawnInfoDB(ri); return; } uint32 thisRespawnTime = forceDelay ? GameTime::GetGameTime() + forceDelay : m_respawnTime; GetMap()->SaveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId, GetEntry(), thisRespawnTime, Trinity::ComputeGridCoord(GetPositionX(), GetPositionY()).GetId()); } } bool GameObject::IsNeverVisibleFor(WorldObject const* seer, bool allowServersideObjects) const { if (WorldObject::IsNeverVisibleFor(seer)) return true; if (GetGOInfo()->GetServerOnly() && !allowServersideObjects) return true; if (!GetDisplayId() && GetGOInfo()->IsDisplayMandatory()) return true; if (m_goTypeImpl) return m_goTypeImpl->IsNeverVisibleFor(seer, allowServersideObjects); return false; } bool GameObject::IsAlwaysVisibleFor(WorldObject const* seer) const { if (WorldObject::IsAlwaysVisibleFor(seer)) return true; if (IsTransport() || IsDestructibleBuilding()) return true; if (!seer) return false; // Always seen by owner and friendly units if (!GetOwnerGUID().IsEmpty()) { if (seer->GetGUID() == GetOwnerGUID()) return true; Unit* owner = GetOwner(); if (Unit const* unitSeer = seer->ToUnit()) if (owner && owner->IsFriendlyTo(unitSeer)) return true; } return false; } bool GameObject::IsInvisibleDueToDespawn(WorldObject const* seer) const { if (WorldObject::IsInvisibleDueToDespawn(seer)) return true; // Despawned if (!isSpawned()) return true; if (m_perPlayerState) if (PerPlayerState const* state = Trinity::Containers::MapGetValuePtr(*m_perPlayerState, seer->GetGUID())) if (state->Despawned) return true; return false; } uint8 GameObject::GetLevelForTarget(WorldObject const* target) const { if (Unit* owner = GetOwner()) return owner->GetLevelForTarget(target); if (GetGoType() == GAMEOBJECT_TYPE_TRAP) { if (Player const* player = target->ToPlayer()) if (Optional userLevels = sDB2Manager.GetContentTuningData(GetGOInfo()->ContentTuningId, player->m_playerData->CtrOptions->ConditionalFlags)) return uint8(std::clamp(player->GetLevel(), userLevels->MinLevel, userLevels->MaxLevel)); if (Unit const* targetUnit = target->ToUnit()) return targetUnit->GetLevel(); } return 1; } time_t GameObject::GetRespawnTimeEx() const { time_t now = GameTime::GetGameTime(); if (m_respawnTime > now) return m_respawnTime; else return now; } void GameObject::SetRespawnTime(int32 respawn) { m_respawnTime = respawn > 0 ? GameTime::GetGameTime() + respawn : 0; m_respawnDelayTime = respawn > 0 ? respawn : 0; if (respawn && !m_spawnedByDefault) UpdateObjectVisibility(true); } void GameObject::Respawn() { if (m_spawnedByDefault && m_respawnTime > 0) { m_respawnTime = GameTime::GetGameTime(); GetMap()->Respawn(SPAWN_TYPE_GAMEOBJECT, m_spawnId); } } bool GameObject::HasConditionalInteraction() const { if (GetGOInfo()->GetQuestID()) return true; if (GetGoType() != GAMEOBJECT_TYPE_AURA_GENERATOR && GetGOInfo()->GetConditionID1()) return true; if (sObjectMgr->IsGameObjectForQuests(GetEntry())) return true; return false; } bool GameObject::CanActivateForPlayer(Player const* target) const { if (!MeetsInteractCondition(target)) return false; if (!ActivateToQuest(target)) return false; return true; } bool GameObject::ActivateToQuest(Player const* target) const { if (target->HasQuestForGO(GetEntry())) return true; if (!sObjectMgr->IsGameObjectForQuests(GetEntry())) return true; switch (GetGoType()) { case GAMEOBJECT_TYPE_QUESTGIVER: { if ((target->GetQuestDialogStatus(this) & ~QuestGiverStatusFutureMask) != QuestGiverStatus::None) return true; break; } case GAMEOBJECT_TYPE_CHEST: { // Chests become inactive while not ready to be looted if (getLootState() == GO_NOT_READY) return false; // scan GO chest with loot including quest items if (target->GetQuestStatus(GetGOInfo()->chest.questID) == QUEST_STATUS_INCOMPLETE || LootTemplates_Gameobject.HaveQuestLootForPlayer(GetGOInfo()->chest.chestLoot, target) || LootTemplates_Gameobject.HaveQuestLootForPlayer(GetGOInfo()->chest.chestPersonalLoot, target) || LootTemplates_Gameobject.HaveQuestLootForPlayer(GetGOInfo()->chest.chestPushLoot, target)) { return true; } break; } case GAMEOBJECT_TYPE_GENERIC: { if (target->GetQuestStatus(GetGOInfo()->generic.questID) == QUEST_STATUS_INCOMPLETE) return true; break; } case GAMEOBJECT_TYPE_SPELL_FOCUS: { if (target->GetQuestStatus(GetGOInfo()->spellFocus.questID) == QUEST_STATUS_INCOMPLETE) return true; break; } case GAMEOBJECT_TYPE_GOOBER: { if (target->GetQuestStatus(GetGOInfo()->goober.questID) == QUEST_STATUS_INCOMPLETE) return true; break; } case GAMEOBJECT_TYPE_GATHERING_NODE: { if (LootTemplates_Gameobject.HaveQuestLootForPlayer(GetGOInfo()->gatheringNode.chestLoot, target)) return true; break; } default: break; } return false; } void GameObject::TriggeringLinkedGameObject(uint32 trapEntry, Unit* target) { GameObjectTemplate const* trapInfo = sObjectMgr->GetGameObjectTemplate(trapEntry); if (!trapInfo || trapInfo->type != GAMEOBJECT_TYPE_TRAP) return; SpellInfo const* trapSpell = sSpellMgr->GetSpellInfo(trapInfo->trap.spell, GetMap()->GetDifficultyID()); if (!trapSpell) // checked at load already return; if (GameObject* trapGO = GetLinkedTrap()) trapGO->CastSpell(target, trapSpell->Id); } GameObject* GameObject::LookupFishingHoleAround(float range) { GameObject* ok = nullptr; Trinity::NearestGameObjectFishingHole u_check(*this, range); Trinity::GameObjectSearcher checker(this, ok, u_check); Cell::VisitGridObjects(this, checker, range); return ok; } void GameObject::ResetDoorOrButton() { if (m_lootState == GO_READY || m_lootState == GO_JUST_DEACTIVATED) return; RemoveFlag(GO_FLAG_IN_USE); SetGoState(m_prevGoState); SetLootState(GO_JUST_DEACTIVATED); m_cooldownTime = 0; } void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = false */, Unit* user /*=nullptr*/) { if (m_lootState != GO_READY) return; if (!time_to_restore) time_to_restore = GetGOInfo()->GetAutoCloseTime(); SwitchDoorOrButton(true, alternative); SetLootState(GO_ACTIVATED, user); m_cooldownTime = time_to_restore ? (GameTime::GetGameTimeMS() + time_to_restore) : 0; } void GameObject::ActivateObject(GameObjectActions action, int32 param, WorldObject* spellCaster /*= nullptr*/, uint32 spellId /*= 0*/, int32 effectIndex /*= -1*/) { Unit* unitCaster = spellCaster ? spellCaster->ToUnit() : nullptr; switch (action) { case GameObjectActions::None: TC_LOG_FATAL("spell", "Spell {} has action type NONE in effect {}", spellId, effectIndex); break; case GameObjectActions::AnimateCustom0: case GameObjectActions::AnimateCustom1: case GameObjectActions::AnimateCustom2: case GameObjectActions::AnimateCustom3: SendCustomAnim(uint32(action) - uint32(GameObjectActions::AnimateCustom0)); break; case GameObjectActions::Disturb: // What's the difference with Open? if (unitCaster) Use(unitCaster); break; case GameObjectActions::Unlock: RemoveFlag(GO_FLAG_LOCKED); break; case GameObjectActions::Lock: SetFlag(GO_FLAG_LOCKED); break; case GameObjectActions::Open: if (unitCaster) Use(unitCaster); break; case GameObjectActions::OpenAndUnlock: if (unitCaster) UseDoorOrButton(0, false, unitCaster); RemoveFlag(GO_FLAG_LOCKED); break; case GameObjectActions::Close: ResetDoorOrButton(); break; case GameObjectActions::ToggleOpen: // No use cases, implementation unknown break; case GameObjectActions::Destroy: if (unitCaster) UseDoorOrButton(0, true, unitCaster); break; case GameObjectActions::Rebuild: ResetDoorOrButton(); break; case GameObjectActions::Creation: // No use cases, implementation unknown break; case GameObjectActions::Despawn: DespawnOrUnsummon(); break; case GameObjectActions::MakeInert: SetFlag(GO_FLAG_NOT_SELECTABLE); break; case GameObjectActions::MakeActive: RemoveFlag(GO_FLAG_NOT_SELECTABLE); break; case GameObjectActions::CloseAndLock: ResetDoorOrButton(); SetFlag(GO_FLAG_LOCKED); break; case GameObjectActions::UseArtKit0: case GameObjectActions::UseArtKit1: case GameObjectActions::UseArtKit2: case GameObjectActions::UseArtKit3: case GameObjectActions::UseArtKit4: { GameObjectTemplateAddon const* templateAddon = GetTemplateAddon(); uint32 artKitIndex = action != GameObjectActions::UseArtKit4 ? uint32(action) - uint32(GameObjectActions::UseArtKit0) : 4; uint32 artKitValue = 0; if (templateAddon != nullptr) artKitValue = templateAddon->ArtKits[artKitIndex]; if (artKitValue == 0) TC_LOG_ERROR("sql.sql", "GameObject {} hit by spell {} needs `artkit{}` in `gameobject_template_addon`", GetEntry(), spellId, artKitIndex); else SetGoArtKit(artKitValue); break; } case GameObjectActions::GoTo1stFloor: case GameObjectActions::GoTo2ndFloor: case GameObjectActions::GoTo3rdFloor: case GameObjectActions::GoTo4thFloor: case GameObjectActions::GoTo5thFloor: case GameObjectActions::GoTo6thFloor: case GameObjectActions::GoTo7thFloor: case GameObjectActions::GoTo8thFloor: case GameObjectActions::GoTo9thFloor: case GameObjectActions::GoTo10thFloor: static_assert(int32(GO_STATE_TRANSPORT_ACTIVE) == int32(GameObjectActions::GoTo1stFloor)); if (GetGoType() == GAMEOBJECT_TYPE_TRANSPORT) SetGoState(GOState(action)); else TC_LOG_ERROR("spell", "Spell {} targeted non-transport gameobject for transport only action \"Go to Floor\" {} in effect {}", spellId, int32(action), effectIndex); break; case GameObjectActions::PlayAnimKit: SetAnimKitId(param, false); break; case GameObjectActions::OpenAndPlayAnimKit: if (unitCaster) UseDoorOrButton(0, false, unitCaster); SetAnimKitId(param, false); break; case GameObjectActions::CloseAndPlayAnimKit: ResetDoorOrButton(); SetAnimKitId(param, false); break; case GameObjectActions::PlayOneShotAnimKit: SetAnimKitId(param, true); break; case GameObjectActions::StopAnimKit: SetAnimKitId(0, false); break; case GameObjectActions::OpenAndStopAnimKit: if (unitCaster) UseDoorOrButton(0, false, unitCaster); SetAnimKitId(0, false); break; case GameObjectActions::CloseAndStopAnimKit: ResetDoorOrButton(); SetAnimKitId(0, false); break; case GameObjectActions::PlaySpellVisual: SetSpellVisualId(param, Object::GetGUID(spellCaster)); break; case GameObjectActions::StopSpellVisual: SetSpellVisualId(0); break; default: TC_LOG_ERROR("spell", "Spell {} has unhandled action {} in effect {}", spellId, int32(action), effectIndex); break; } // Apply side effects of type if (m_goTypeImpl) m_goTypeImpl->ActivateObject(action, param, spellCaster, spellId, effectIndex); } void GameObject::SetGoArtKit(uint32 kit) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::ArtKit), kit); GameObjectData* data = const_cast(sObjectMgr->GetGameObjectData(m_spawnId)); if (data) data->artKit = kit; } void GameObject::SetGoArtKit(uint32 artkit, GameObject* go, ObjectGuid::LowType lowguid) { GameObjectData const* data = nullptr; if (go) { go->SetGoArtKit(artkit); data = go->GetGameObjectData(); } else if (lowguid) data = sObjectMgr->GetGameObjectData(lowguid); if (data) const_cast(data)->artKit = artkit; } void GameObject::SwitchDoorOrButton(bool activate, bool alternative /* = false */) { if (activate) SetFlag(GO_FLAG_IN_USE); else RemoveFlag(GO_FLAG_IN_USE); if (GetGoState() == GO_STATE_READY) //if closed -> open SetGoState(alternative ? GO_STATE_DESTROYED : GO_STATE_ACTIVE); else //if open -> close SetGoState(GO_STATE_READY); } void GameObject::Use(Unit* user, bool ignoreCastInProgress /*= false*/) { // by default spell caster is user Unit* spellCaster = user; uint32 spellId = 0; CastSpellExtraArgs spellArgs; if (ignoreCastInProgress) spellArgs.TriggerFlags |= TRIGGERED_IGNORE_CAST_IN_PROGRESS; if (Player* playerUser = user->ToPlayer()) { if (m_goInfo->GetNoDamageImmune() && playerUser->HasUnitFlag(UNIT_FLAG_IMMUNE)) return; if (!m_goInfo->IsUsableMounted()) playerUser->RemoveAurasByType(SPELL_AURA_MOUNTED); playerUser->PlayerTalkClass->ClearMenus(); if (AI()->OnGossipHello(playerUser)) return; } // If cooldown data present in template if (uint32 cooldown = GetGOInfo()->GetCooldown()) { if (m_cooldownTime > GameTime::GetGameTime()) return; m_cooldownTime = GameTime::GetGameTimeMS() + cooldown * IN_MILLISECONDS; } switch (GetGoType()) { case GAMEOBJECT_TYPE_DOOR: //0 case GAMEOBJECT_TYPE_BUTTON: //1 //doors/buttons never really despawn, only reset to default state/flags UseDoorOrButton(0, false, user); return; case GAMEOBJECT_TYPE_QUESTGIVER: //2 { if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); player->PrepareGossipMenu(this, GetGOInfo()->questgiver.gossipID, true); player->SendPreparedGossip(this); return; } case GAMEOBJECT_TYPE_CHEST: //3 { Player* player = user->ToPlayer(); if (!player) return; GameObjectTemplate const* info = GetGOInfo(); if (!m_loot && info->GetLootId()) { if (info->GetLootId()) { Group const* group = player->GetGroup(); bool groupRules = group && info->chest.usegrouplootrules; Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, groupRules ? group : nullptr); m_loot.reset(loot); loot->SetDungeonEncounterId(info->chest.DungeonEncounter); loot->FillLoot(info->GetLootId(), LootTemplates_Gameobject, player, !groupRules, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player)); if (GetLootMode() > 0) if (GameObjectTemplateAddon const* addon = GetTemplateAddon()) loot->generateMoneyLoot(addon->Mingold, addon->Maxgold); } if (info->chest.triggeredEvent) GameEvents::Trigger(info->chest.triggeredEvent, player, this); // triggering linked GO if (uint32 trapEntry = info->chest.linkedTrap) TriggeringLinkedGameObject(trapEntry, player); } else if (!m_personalLoot.count(player->GetGUID())) { if (info->chest.chestPersonalLoot) { GameObjectTemplateAddon const* addon = GetTemplateAddon(); if (info->chest.DungeonEncounter) { std::vector tappers; for (ObjectGuid tapperGuid : GetTapList()) if (Player* tapper = ObjectAccessor::GetPlayer(*this, tapperGuid)) tappers.push_back(tapper); if (tappers.empty()) tappers.push_back(player); m_personalLoot = GenerateDungeonEncounterPersonalLoot(info->chest.DungeonEncounter, info->chest.chestPersonalLoot, LootTemplates_Gameobject, LOOT_CHEST, this, addon ? addon->Mingold : 0, addon ? addon->Maxgold : 0, GetLootMode(), GetMap()->GetMapDifficulty(), tappers); } else { Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr); m_personalLoot[player->GetGUID()].reset(loot); loot->SetDungeonEncounterId(info->chest.DungeonEncounter); loot->FillLoot(info->chest.chestPersonalLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player)); if (GetLootMode() > 0 && addon) loot->generateMoneyLoot(addon->Mingold, addon->Maxgold); } } } if (!m_unique_users.count(player->GetGUID()) && !info->GetLootId()) { if (info->chest.chestPushLoot) { Loot pushLoot(GetMap(), GetGUID(), LOOT_CHEST, nullptr); pushLoot.FillLoot(info->chest.chestPushLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player)); pushLoot.AutoStore(player, NULL_BAG, NULL_SLOT); } if (info->chest.triggeredEvent) GameEvents::Trigger(info->chest.triggeredEvent, player, this); // triggering linked GO if (uint32 trapEntry = info->chest.linkedTrap) TriggeringLinkedGameObject(trapEntry, player); // Cast spell before sending loot if (spellCaster && info->chest.spell) spellCaster->CastSpell(nullptr, info->chest.spell, spellArgs); AddUniqueUse(player); } if (getLootState() != GO_ACTIVATED) SetLootState(GO_ACTIVATED, player); // Send loot if (Loot* loot = GetLootForPlayer(player)) player->SendLoot(*loot); break; } case GAMEOBJECT_TYPE_TRAP: //6 { GameObjectTemplate const* goInfo = GetGOInfo(); if (goInfo->trap.spell) CastSpell(user, goInfo->trap.spell); m_cooldownTime = GameTime::GetGameTimeMS() + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)) * IN_MILLISECONDS; // template or 4 seconds if (goInfo->trap.charges == 1) // Deactivate after trigger SetLootState(GO_JUST_DEACTIVATED); return; } //Sitting: Wooden bench, chairs enzz case GAMEOBJECT_TYPE_CHAIR: //7 { GameObjectTemplate const* info = GetGOInfo(); if (ChairListSlots.empty()) // this is called once at first chair use to make list of available slots { if (info->chair.chairslots > 0) // sometimes chairs in DB have error in fields and we dont know number of slots for (uint32 i = 0; i < info->chair.chairslots; ++i) ChairListSlots[i].Clear(); // Last user of current slot set to 0 (none sit here yet) else ChairListSlots[0].Clear(); // error in DB, make one default slot } // a chair may have n slots. we have to calculate their positions and teleport the player to the nearest one float lowestDist = DEFAULT_VISIBILITY_DISTANCE; uint32 nearest_slot = 0; float x_lowest = GetPositionX(); float y_lowest = GetPositionY(); // the object orientation + 1/2 pi // every slot will be on that straight line float orthogonalOrientation = GetOrientation() + float(M_PI) * 0.5f; // find nearest slot bool found_free_slot = false; for (auto& [slot, sittingUnit] : ChairListSlots) { // the distance between this slot and the center of the go - imagine a 1D space float relativeDistance = (info->size * slot) - (info->size * (info->chair.chairslots - 1) / 2.0f); float x_i = GetPositionX() + relativeDistance * std::cos(orthogonalOrientation); float y_i = GetPositionY() + relativeDistance * std::sin(orthogonalOrientation); if (!sittingUnit.IsEmpty()) { if (Unit* chairUser = ObjectAccessor::GetUnit(*this, sittingUnit)) { if (chairUser->IsSitState() && chairUser->GetStandState() != UNIT_STAND_STATE_SIT && chairUser->GetExactDist2d(x_i, y_i) < 0.1f) continue; // This seat is already occupied by ChairUser. NOTE: Not sure if the ChairUser->GetStandState() != UNIT_STAND_STATE_SIT check is required. sittingUnit.Clear(); // This seat is unoccupied. } else sittingUnit.Clear(); // The seat may of had an occupant, but they're offline. } found_free_slot = true; // calculate the distance between the player and this slot float thisDistance = user->GetDistance2d(x_i, y_i); if (thisDistance <= lowestDist) { nearest_slot = slot; lowestDist = thisDistance; x_lowest = x_i; y_lowest = y_i; } } if (found_free_slot) { auto itr = ChairListSlots.find(nearest_slot); if (itr != ChairListSlots.end()) { itr->second = user->GetGUID(); //this slot in now used by player user->NearTeleportTo(x_lowest, y_lowest, GetPositionZ(), GetOrientation()); user->SetStandState(UnitStandStateType(UNIT_STAND_STATE_SIT_LOW_CHAIR + info->chair.chairheight)); if (info->chair.triggeredEvent) GameEvents::Trigger(info->chair.triggeredEvent, user, this); return; } } return; } case GAMEOBJECT_TYPE_SPELL_FOCUS: //8 // triggering linked GO if (uint32 trapEntry = GetGOInfo()->spellFocus.linkedTrap) TriggeringLinkedGameObject(trapEntry, user); break; //big gun, its a spell/aura case GAMEOBJECT_TYPE_GOOBER: //10 { GameObjectTemplate const* info = GetGOInfo(); Player* player = user->ToPlayer(); if (player) { if (info->goober.pageID) // show page... { WorldPackets::GameObject::PageText data; data.GameObjectGUID = GetGUID(); player->SendDirectMessage(data.Write()); } else if (info->goober.gossipID) { player->PrepareGossipMenu(this, info->goober.gossipID); player->SendPreparedGossip(this); } if (info->goober.eventID) { TC_LOG_DEBUG("maps.script", "Goober ScriptStart id {} for GO entry {} (GUID {}).", info->goober.eventID, GetEntry(), GetSpawnId()); GameEvents::Trigger(info->goober.eventID, player, this); } // possible quest objective for active quests if (info->goober.questID && sObjectMgr->GetQuestTemplate(info->goober.questID)) { //Quest require to be active for GO using if (player->GetQuestStatus(info->goober.questID) != QUEST_STATUS_INCOMPLETE) break; } if (Group* group = player->GetGroup()) { for (GroupReference const& itr : group->GetMembers()) if (itr.GetSource()->IsAtGroupRewardDistance(this)) itr.GetSource()->KillCreditGO(info->entry, GetGUID()); } else player->KillCreditGO(info->entry, GetGUID()); } if (uint32 trapEntry = info->goober.linkedTrap) TriggeringLinkedGameObject(trapEntry, user); if (info->goober.AllowMultiInteract && player) { if (info->IsDespawnAtAction()) DespawnForPlayer(player, Seconds(m_respawnDelayTime)); else SetGoStateFor(GO_STATE_ACTIVE, player); } else { SetFlag(GO_FLAG_IN_USE); SetLootState(GO_ACTIVATED, user); // this appear to be ok, however others exist in addition to this that should have custom (ex: 190510, 188692, 187389) if (info->goober.customAnim) SendCustomAnim(GetGoAnimProgress()); else SetGoState(GO_STATE_ACTIVE); m_cooldownTime = GameTime::GetGameTimeMS() + info->GetAutoCloseTime(); } // cast this spell later if provided spellId = info->goober.spell; if (!info->goober.playerCast) spellCaster = nullptr; break; } case GAMEOBJECT_TYPE_CAMERA: //13 { GameObjectTemplate const* info = GetGOInfo(); if (!info) return; if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); if (info->camera.camera) player->SendCinematicStart(info->camera.camera); if (info->camera.eventID) GameEvents::Trigger(info->camera.eventID, player, this); return; } //fishing bobber case GAMEOBJECT_TYPE_FISHINGNODE: //17 { Player* player = user->ToPlayer(); if (!player) return; if (player->GetGUID() != GetOwnerGUID()) return; switch (getLootState()) { case GO_READY: // ready for loot { SetLootState(GO_ACTIVATED, player); SetGoState(GO_STATE_ACTIVE); ReplaceAllFlags(GO_FLAG_IN_MULTI_USE); SendUpdateToPlayer(player); AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(GetAreaId()); if (!areaEntry) { TC_LOG_ERROR("entities.gameobject", "Gameobject '{}' ({}) spawned in unknown area (x: {} y: {} z: {} map: {})", GetEntry(), GetGUID().ToString(), GetPositionX(), GetPositionY(), GetPositionZ(), GetMapId()); break; } // Update the correct fishing skill according to the area's ContentTuning ContentTuningEntry const* areaContentTuning = DB2Manager::GetContentTuningForArea(areaEntry); if (!areaContentTuning) break; player->UpdateFishingSkill(areaContentTuning->ExpansionID); // Send loot int32 areaFishingLevel = sObjectMgr->GetFishingBaseSkillLevel(areaEntry); uint32 playerFishingSkill = player->GetProfessionSkillForExp(SKILL_FISHING, areaContentTuning->ExpansionID); int32 playerFishingLevel = player->GetSkillValue(playerFishingSkill); int32 roll = irand(1, 100); int32 chance = 100; if (playerFishingLevel < areaFishingLevel) { chance = int32(pow((double)playerFishingLevel / areaFishingLevel, 2) * 100); if (chance < 1) chance = 1; } TC_LOG_DEBUG("misc", "Fishing check (skill {} level: {} area skill level: {} chance {} roll: {}", playerFishingSkill, playerFishingLevel, areaFishingLevel, chance, roll); /// @todo find reasonable value for fishing hole search GameObject* fishingPool = LookupFishingHoleAround(20.0f + CONTACT_DISTANCE); // If fishing skill is high enough, or if fishing on a pool, send correct loot. // Fishing pools have no skill requirement as of patch 3.3.0 (undocumented change). if (chance >= roll || fishingPool) { /// @todo I do not understand this hack. Need some explanation. // prevent removing GO at spell cancel RemoveFromOwner(); SetOwnerGUID(player->GetGUID()); SetSpellId(0); // prevent removing unintended auras at Unit::RemoveGameObject if (fishingPool) { fishingPool->Use(player, ignoreCastInProgress); SetLootState(GO_JUST_DEACTIVATED); } else { m_loot.reset(GetFishLoot(player)); player->SendLoot(*m_loot); } } else // If fishing skill is too low, send junk loot. { m_loot.reset(GetFishLootJunk(player)); player->SendLoot(*m_loot); } break; } case GO_JUST_DEACTIVATED: // nothing to do, will be deleted at next update break; default: { SetLootState(GO_JUST_DEACTIVATED); player->SendDirectMessage(WorldPackets::GameObject::FishNotHooked().Write()); break; } } player->FinishSpell(CURRENT_CHANNELED_SPELL); return; } case GAMEOBJECT_TYPE_RITUAL: //18 { if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); Unit* owner = GetOwner(); GameObjectTemplate const* info = GetGOInfo(); Player* m_ritualOwner = nullptr; if (!m_ritualOwnerGUID.IsEmpty()) m_ritualOwner = ObjectAccessor::FindPlayer(m_ritualOwnerGUID); // ritual owner is set for GO's without owner (not summoned) if (!m_ritualOwner && !owner) { m_ritualOwnerGUID = player->GetGUID(); m_ritualOwner = player; } if (owner) { if (owner->GetTypeId() != TYPEID_PLAYER) return; // accept only use by player from same group as owner, excluding owner itself (unique use already added in spell effect) if (player == owner->ToPlayer() || (info->ritual.castersGrouped && !player->IsInSameRaidWith(owner->ToPlayer()))) return; // expect owner to already be channeling, so if not... if (!owner->GetCurrentSpell(CURRENT_CHANNELED_SPELL)) return; // in case summoning ritual caster is GO creator spellCaster = owner; } else { if (player != m_ritualOwner && (info->ritual.castersGrouped && !player->IsInSameRaidWith(m_ritualOwner))) return; spellCaster = player; } AddUniqueUse(player); if (info->ritual.animSpell) { player->CastSpell(player, info->ritual.animSpell, true); // for this case, summoningRitual.spellId is always triggered spellArgs.TriggerFlags = TRIGGERED_FULL_MASK; } // full amount unique participants including original summoner if (GetUniqueUseCount() == info->ritual.casters) { if (m_ritualOwner) spellCaster = m_ritualOwner; spellId = info->ritual.spell; if (spellId == 62330) // GO store nonexistent spell, replace by expected { // spell have reagent and mana cost but it not expected use its // it triggered spell in fact cast at currently channeled GO spellId = 61993; spellArgs.TriggerFlags = TRIGGERED_FULL_MASK; } // Cast casterTargetSpell at a random GO user // on the current DB there is only one gameobject that uses this (Ritual of Doom) // and its required target number is 1 (outter for loop will run once) if (info->ritual.casterTargetSpell && info->ritual.casterTargetSpell != 1) // No idea why this field is a bool in some cases for (uint32 i = 0; i < info->ritual.casterTargetSpellTargets; i++) // m_unique_users can contain only player GUIDs if (Player* target = ObjectAccessor::GetPlayer(*this, Trinity::Containers::SelectRandomContainerElement(m_unique_users))) spellCaster->CastSpell(target, info->ritual.casterTargetSpell, true); // finish owners spell if (owner) owner->FinishSpell(CURRENT_CHANNELED_SPELL); // can be deleted now, if if (!info->ritual.ritualPersistent) SetLootState(GO_JUST_DEACTIVATED); else { // reset ritual for this GO m_ritualOwnerGUID.Clear(); m_unique_users.clear(); m_usetimes = 0; } } else return; // go to end function to spell casting break; } case GAMEOBJECT_TYPE_SPELLCASTER: //22 { GameObjectTemplate const* info = GetGOInfo(); if (!info) return; if (info->spellCaster.partyOnly) { Unit* caster = GetOwner(); if (!caster || caster->GetTypeId() != TYPEID_PLAYER) return; if (user->GetTypeId() != TYPEID_PLAYER || !user->ToPlayer()->IsInSameRaidWith(caster->ToPlayer())) return; } user->RemoveAurasByType(SPELL_AURA_MOUNTED); spellId = info->spellCaster.spell; AddUse(); break; } case GAMEOBJECT_TYPE_MEETINGSTONE: //23 { GameObjectTemplate const* info = GetGOInfo(); if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); Player* targetPlayer = ObjectAccessor::FindPlayer(player->GetTarget()); // accept only use by player from same raid as caster, except caster itself if (!targetPlayer || targetPlayer == player || !targetPlayer->IsInSameRaidWith(player)) return; //required lvl checks! if (Optional userLevels = sDB2Manager.GetContentTuningData(info->ContentTuningId, player->m_playerData->CtrOptions->ConditionalFlags)) if (player->GetLevel() < userLevels->MaxLevel) return; if (Optional targetLevels = sDB2Manager.GetContentTuningData(info->ContentTuningId, targetPlayer->m_playerData->CtrOptions->ConditionalFlags)) if (targetPlayer->GetLevel() < targetLevels->MaxLevel) return; if (info->entry == 194097) spellId = 61994; // Ritual of Summoning else spellId = 59782; // Summoning Stone Effect break; } case GAMEOBJECT_TYPE_FLAGSTAND: // 24 { if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); if (player->CanUseBattlegroundObject(this)) { if (player->GetVehicle()) return; if (HasFlag(GO_FLAG_IN_USE)) return; if (!MeetsInteractCondition(player)) return; player->RemoveAurasByType(SPELL_AURA_MOD_STEALTH); player->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY); spellId = GetGOInfo()->flagStand.pickupSpell; spellCaster = nullptr; } break; } case GAMEOBJECT_TYPE_FISHINGHOLE: // 25 { if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_FISHINGHOLE, nullptr); loot->FillLoot(GetGOInfo()->GetLootId(), LootTemplates_Gameobject, player, true, false, LOOT_MODE_DEFAULT, ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player)); m_personalLoot[player->GetGUID()].reset(loot); player->SendLoot(*loot); player->UpdateCriteria(CriteriaType::CatchFishInFishingHole, GetGOInfo()->entry); return; } case GAMEOBJECT_TYPE_FLAGDROP: // 26 { if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); if (player->CanUseBattlegroundObject(this)) { if (player->GetVehicle()) return; player->RemoveAurasByType(SPELL_AURA_MOD_STEALTH); player->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY); // BG flag dropped // WS: // 179785 - Silverwing Flag // 179786 - Warsong Flag // EotS: // 184142 - Netherstorm Flag GameObjectTemplate const* info = GetGOInfo(); if (info->flagDrop.eventID) GameEvents::Trigger(info->flagDrop.eventID, player, this); //this cause to call return, all flags must be deleted here!! spellId = 0; Delete(); } break; } case GAMEOBJECT_TYPE_BARBER_CHAIR: //32 { GameObjectTemplate const* info = GetGOInfo(); if (!info) return; if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); WorldPackets::Misc::EnableBarberShop enableBarberShop; enableBarberShop.CustomizationFeatureMask = info->barberChair.CustomizationFeatureMask; player->SendDirectMessage(enableBarberShop.Write()); // fallback, will always work player->TeleportTo(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET); player->SetStandState(UnitStandStateType(UNIT_STAND_STATE_SIT_LOW_CHAIR + info->barberChair.chairheight), info->barberChair.SitAnimKit); return; } case GAMEOBJECT_TYPE_NEW_FLAG: { GameObjectTemplate const* info = GetGOInfo(); if (!info) return; Player* player = user->ToPlayer(); if (!player) return; if (!player->CanUseBattlegroundObject(this)) return; GameObjectType::NewFlag const* newFlag = dynamic_cast(m_goTypeImpl.get()); if (!newFlag) return; if (newFlag->GetState() != FlagState::InBase) return; spellId = info->newflag.pickupSpell; spellCaster = nullptr; break; } case GAMEOBJECT_TYPE_NEW_FLAG_DROP: { GameObjectTemplate const* info = GetGOInfo(); if (!info) return; if (user->GetTypeId() != TYPEID_PLAYER) return; if (!user->IsAlive()) return; if (GameObject* owner = GetMap()->GetGameObject(GetOwnerGUID())) { if (owner->GetGoType() == GAMEOBJECT_TYPE_NEW_FLAG) { GameObjectType::NewFlag const* newFlag = dynamic_cast(owner->m_goTypeImpl.get()); if (!newFlag) return; if (newFlag->GetState() != FlagState::Dropped) return; // friendly with enemy flag means you're taking it bool defenderInteract = !owner->IsFriendlyTo(user); if (defenderInteract && owner->GetGOInfo()->newflag.ReturnonDefenderInteract) { Delete(); owner->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::InBase, user->ToPlayer())); return; } else { // we let the owner cast the spell for now // so that caster guid is set correctly SpellCastResult result = owner->CastSpell(user, owner->GetGOInfo()->newflag.pickupSpell, CastSpellExtraArgs(TRIGGERED_FULL_MASK)); if (result == SPELL_CAST_OK) { Delete(); owner->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::Taken, user->ToPlayer())); return; } } } } Delete(); return; } case GAMEOBJECT_TYPE_CAPTURE_POINT: { Player* player = user->ToPlayer(); if (!player) return; AssaultCapturePoint(player); return; } case GAMEOBJECT_TYPE_ITEM_FORGE: { GameObjectTemplate const* info = GetGOInfo(); if (!info) return; if (user->GetTypeId() != TYPEID_PLAYER) return; Player* player = user->ToPlayer(); if (!MeetsInteractCondition(player)) return; switch (info->itemForge.ForgeType) { case 0: // Artifact Forge case 1: // Relic Forge { Aura const* artifactAura = player->GetAura(ARTIFACTS_ALL_WEAPONS_GENERAL_WEAPON_EQUIPPED_PASSIVE); Item const* item = artifactAura ? player->GetItemByGuid(artifactAura->GetCastItemGUID()) : nullptr; if (!item) { player->SendDirectMessage(WorldPackets::Misc::DisplayGameError(GameError::ERR_MUST_EQUIP_ARTIFACT).Write()); return; } WorldPackets::Artifact::OpenArtifactForge openArtifactForge; openArtifactForge.ArtifactGUID = item->GetGUID(); openArtifactForge.ForgeGUID = GetGUID(); player->SendDirectMessage(openArtifactForge.Write()); break; } case 2: // Heart Forge { Item const* item = player->GetItemByEntry(ITEM_ID_HEART_OF_AZEROTH, ItemSearchLocation::Everywhere); if (!item) return; WorldPackets::GameObject::GameObjectInteraction openHeartForge; openHeartForge.ObjectGUID = GetGUID(); openHeartForge.InteractionType = PlayerInteractionType::AzeriteForge; player->SendDirectMessage(openHeartForge.Write()); break; } default: break; } return; } case GAMEOBJECT_TYPE_UI_LINK: { Player* player = user->ToPlayer(); if (!player) return; WorldPackets::GameObject::GameObjectInteraction gameObjectUILink; gameObjectUILink.ObjectGUID = GetGUID(); switch (GetGOInfo()->UILink.UILinkType) { case 0: gameObjectUILink.InteractionType = PlayerInteractionType::AdventureJournal; break; case 1: gameObjectUILink.InteractionType = PlayerInteractionType::ObliterumForge; break; case 2: gameObjectUILink.InteractionType = PlayerInteractionType::ScrappingMachine; break; case 3: gameObjectUILink.InteractionType = PlayerInteractionType::ItemInteraction; break; default: break; } player->SendDirectMessage(gameObjectUILink.Write()); return; } case GAMEOBJECT_TYPE_GATHERING_NODE: //50 { Player* player = user->ToPlayer(); if (!player) return; GameObjectTemplate const* info = GetGOInfo(); if (!m_personalLoot.count(player->GetGUID())) { if (info->gatheringNode.chestLoot) { Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr); m_personalLoot[player->GetGUID()].reset(loot); loot->FillLoot(info->gatheringNode.chestLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player)); } if (info->gatheringNode.triggeredEvent) GameEvents::Trigger(info->gatheringNode.triggeredEvent, player, this); // triggering linked GO if (uint32 trapEntry = info->gatheringNode.linkedTrap) TriggeringLinkedGameObject(trapEntry, player); if (info->gatheringNode.xpDifficulty && info->gatheringNode.xpDifficulty < 10) if (QuestXPEntry const* questXp = sQuestXPStore.LookupEntry(player->GetLevel())) if (uint32 xp = Quest::RoundXPValue(questXp->Difficulty[info->gatheringNode.xpDifficulty])) player->GiveXP(xp, nullptr); spellId = info->gatheringNode.spell; } if (m_personalLoot.size() >= info->gatheringNode.MaxNumberofLoots) { SetGoState(GO_STATE_ACTIVE); SetDynamicFlag(GO_DYNFLAG_LO_NO_INTERACT); } if (getLootState() != GO_ACTIVATED) { SetLootState(GO_ACTIVATED, player); if (info->gatheringNode.ObjectDespawnDelay) DespawnOrUnsummon(Seconds(info->gatheringNode.ObjectDespawnDelay)); } // Send loot if (Loot* loot = GetLootForPlayer(player)) player->SendLoot(*loot); break; } default: if (GetGoType() >= MAX_GAMEOBJECT_TYPE) TC_LOG_ERROR("misc", "GameObject::Use(): unit ({}, name: {}) tries to use object ({}, name: {}) of unknown type ({})", user->GetGUID().ToString(), user->GetName(), GetGUID().ToString(), GetGOInfo()->name, GetGoType()); break; } if (m_vignette) { if (Player* player = user->ToPlayer()) { if (Quest const* reward = sObjectMgr->GetQuestTemplate(m_vignette->Data->RewardQuestID)) if (!player->GetQuestRewardStatus(m_vignette->Data->RewardQuestID)) player->RewardQuest(reward, LootItemType::Item, 0, this, false); if (m_vignette->Data->VisibleTrackingQuestID) player->SetRewardedQuest(m_vignette->Data->VisibleTrackingQuestID); } // only unregister it from visibility (need to keep vignette for other gameobject users in case its usable by multiple players // to flag their quest completion if (GetGOInfo()->ClearObjectVignetteonOpening()) Vignettes::Remove(*m_vignette, this); } if (!spellId) return; if (!sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID())) { if (user->GetTypeId() != TYPEID_PLAYER || !sOutdoorPvPMgr->HandleCustomSpell(user->ToPlayer(), spellId, this)) TC_LOG_ERROR("misc", "WORLD: unknown spell id {} at use action for gameobject (Entry: {} GoType: {})", spellId, GetEntry(), GetGoType()); else TC_LOG_DEBUG("outdoorpvp", "WORLD: {} non-dbc spell was handled by OutdoorPvP", spellId); return; } if (Player* player = user->ToPlayer()) sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this); if (spellCaster) spellCaster->CastSpell(user, spellId, spellArgs); else { SpellCastResult castResult = CastSpell(user, spellId, spellArgs); if (castResult == SPELL_FAILED_SUCCESS) { switch (GetGoType()) { case GAMEOBJECT_TYPE_NEW_FLAG: HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::Taken, user->ToPlayer())); break; case GAMEOBJECT_TYPE_FLAGSTAND: SetFlag(GO_FLAG_IN_USE); if (ZoneScript* zonescript = GetZoneScript()) zonescript->OnFlagTaken(this, Object::ToPlayer(user)); Delete(); break; default: break; } } } } void GameObject::SendCustomAnim(uint32 anim) { WorldPackets::GameObject::GameObjectCustomAnim customAnim; customAnim.ObjectGUID = GetGUID(); customAnim.CustomAnim = anim; SendMessageToSet(customAnim.Write(), true); } bool GameObject::IsInRange(float x, float y, float z, float radius) const { GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(m_goInfo->displayId); if (!info) return IsWithinDist3d(x, y, z, radius); float sinA = std::sin(GetOrientation()); float cosA = std::cos(GetOrientation()); float dx = x - GetPositionX(); float dy = y - GetPositionY(); float dz = z - GetPositionZ(); float dist = std::sqrt(dx*dx + dy*dy); //! Check if the distance between the 2 objects is 0, can happen if both objects are on the same position. //! The code below this check wont crash if dist is 0 because 0/0 in float operations is valid, and returns infinite if (G3D::fuzzyEq(dist, 0.0f)) return true; float sinB = dx / dist; float cosB = dy / dist; dx = dist * (cosA * cosB + sinA * sinB); dy = dist * (cosA * sinB - sinA * cosB); return dx < info->GeoBoxMax.X + radius && dx > info->GeoBoxMin.X - radius && dy < info->GeoBoxMax.Y + radius && dy > info->GeoBoxMin.Y - radius && dz < info->GeoBoxMax.Z + radius && dz > info->GeoBoxMin.Z - radius; } uint32 GameObject::GetScriptId() const { if (GameObjectData const* gameObjectData = GetGameObjectData()) if (uint32 scriptId = gameObjectData->scriptId) return scriptId; return GetGOInfo()->ScriptId; } void GameObject::InheritStringIds(GameObject const* parent) { // copy references to stringIds from template and spawn m_stringIds = parent->m_stringIds; // then copy script stringId, not just its reference SetScriptStringId(std::string(parent->GetStringId(StringIdType::Script))); } bool GameObject::HasStringId(std::string_view id) const { return std::ranges::any_of(m_stringIds, [id](std::string const* stringId) { return stringId && *stringId == id; }); } void GameObject::SetScriptStringId(std::string id) { if (!id.empty()) { m_scriptStringId.emplace(std::move(id)); m_stringIds[AsUnderlyingType(StringIdType::Script)] = &*m_scriptStringId; } else { m_scriptStringId.reset(); m_stringIds[AsUnderlyingType(StringIdType::Script)] = nullptr; } } SpawnTrackingStateData const* GameObject::GetSpawnTrackingStateDataForPlayer(Player const* player) const { if (!player) return nullptr; if (GameObjectData const* data = GetGameObjectData()) { if (data->spawnTrackingData && !data->spawnTrackingQuestObjectives.empty()) { SpawnTrackingState state = player->GetSpawnTrackingStateByObjectives(data->spawnTrackingData->SpawnTrackingId, data->spawnTrackingQuestObjectives); return &data->spawnTrackingStates[AsUnderlyingType(state)]; } } return nullptr; } // overwrite WorldObject function for proper name localization std::string GameObject::GetNameForLocaleIdx(LocaleConstant locale) const { if (locale != DEFAULT_LOCALE) if (GameObjectLocale const* cl = sObjectMgr->GetGameObjectLocale(GetEntry())) if (cl->Name.size() > locale && !cl->Name[locale].empty()) return cl->Name[locale]; return GetName(); } bool GameObject::HasLabel(int32 gameobjectLabel) const { return advstd::ranges::contains(GetLabels(), gameobjectLabel); } std::span GameObject::GetLabels() const { return sDB2Manager.GetGameObjectLabels(GetEntry()); } void GameObject::UpdatePackedRotation() { static const int32 PACK_YZ = 1 << 20; static const int32 PACK_X = PACK_YZ << 1; static const int32 PACK_YZ_MASK = (PACK_YZ << 1) - 1; static const int32 PACK_X_MASK = (PACK_X << 1) - 1; int8 w_sign = (m_localRotation.w >= 0.f ? 1 : -1); int64 x = int32(m_localRotation.x * PACK_X) * w_sign & PACK_X_MASK; int64 y = int32(m_localRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK; int64 z = int32(m_localRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK; m_packedRotation = z | (y << 21) | (x << 42); } void GameObject::SetLocalRotation(float qx, float qy, float qz, float qw) { G3D::Quat rotation(qx, qy, qz, qw); rotation.unitize(); m_localRotation.x = rotation.x; m_localRotation.y = rotation.y; m_localRotation.z = rotation.z; m_localRotation.w = rotation.w; UpdatePackedRotation(); } void GameObject::SetParentRotation(QuaternionData const& rotation) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::ParentRotation), rotation); } void GameObject::SetLocalRotationAngles(float z_rot, float y_rot, float x_rot) { G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot)); SetLocalRotation(quat.x, quat.y, quat.z, quat.w); } QuaternionData GameObject::GetWorldRotation() const { QuaternionData localRotation = GetLocalRotation(); if (Transport* transport = dynamic_cast(GetTransport())) { QuaternionData worldRotation = transport->GetWorldRotation(); G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w); G3D::Quat localRotationQuat(localRotation.x, localRotation.y, localRotation.z, localRotation.w); G3D::Quat resultRotation = localRotationQuat * worldRotationQuat; return QuaternionData(resultRotation.x, resultRotation.y, resultRotation.z, resultRotation.w); } return localRotation; } void GameObject::ModifyHealth(int32 change, WorldObject* attackerOrHealer /*= nullptr*/, uint32 spellId /*= 0*/) { if (!m_goValue.Building.DestructibleHitpoint || !change) return; // prevent double destructions of the same object if (change < 0 && !m_goValue.Building.Health) return; if (int32(m_goValue.Building.Health) + change <= 0) m_goValue.Building.Health = 0; else if (int32(m_goValue.Building.Health) + change >= int32(m_goValue.Building.DestructibleHitpoint->GetMaxHealth())) m_goValue.Building.Health = m_goValue.Building.DestructibleHitpoint->GetMaxHealth(); else m_goValue.Building.Health += change; // Set the health bar, value = 255 * healthPct; SetGoAnimProgress(m_goValue.Building.Health * 255 / m_goValue.Building.DestructibleHitpoint->GetMaxHealth()); // dealing damage, send packet if (Player* player = attackerOrHealer ? attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself() : nullptr) { WorldPackets::GameObject::DestructibleBuildingDamage packet; packet.Caster = attackerOrHealer->GetGUID(); // todo: this can be a GameObject packet.Target = GetGUID(); packet.Damage = -change; packet.Owner = player->GetGUID(); packet.SpellID = spellId; player->SendDirectMessage(packet.Write()); } if (change < 0 && GetGOInfo()->destructibleBuilding.DamageEvent) GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DamageEvent, attackerOrHealer, this); GameObjectDestructibleState newState = GetDestructibleState(); if (!m_goValue.Building.Health) newState = GO_DESTRUCTIBLE_DESTROYED; else if (m_goValue.Building.Health <= m_goValue.Building.DestructibleHitpoint->DamagedNumHits) newState = GO_DESTRUCTIBLE_DAMAGED; else if (m_goValue.Building.Health == m_goValue.Building.DestructibleHitpoint->GetMaxHealth()) newState = GO_DESTRUCTIBLE_INTACT; if (newState == GetDestructibleState()) return; SetDestructibleState(newState, attackerOrHealer, false); } void GameObject::SetDestructibleState(GameObjectDestructibleState state, WorldObject* attackerOrHealer /*= nullptr*/, bool setHealth /*= false*/) { // the user calling this must know he is already operating on destructible gameobject ASSERT(GetGoType() == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING); switch (state) { case GO_DESTRUCTIBLE_INTACT: RemoveFlag(GO_FLAG_DAMAGED | GO_FLAG_DESTROYED); SetDisplayId(m_goInfo->displayId); if (setHealth && m_goValue.Building.DestructibleHitpoint) { m_goValue.Building.Health = m_goValue.Building.DestructibleHitpoint->GetMaxHealth(); SetGoAnimProgress(255); } EnableCollision(true); break; case GO_DESTRUCTIBLE_DAMAGED: { if (GetGOInfo()->destructibleBuilding.DamagedEvent && attackerOrHealer) GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DamagedEvent, attackerOrHealer, this); AI()->Damaged(attackerOrHealer, m_goInfo->destructibleBuilding.DamagedEvent); RemoveFlag(GO_FLAG_DESTROYED); SetFlag(GO_FLAG_DAMAGED); uint32 modelId = m_goInfo->displayId; if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec)) if (modelData->State1Wmo) modelId = modelData->State1Wmo; SetDisplayId(modelId); if (setHealth && m_goValue.Building.DestructibleHitpoint) { m_goValue.Building.Health = m_goValue.Building.DestructibleHitpoint->DamagedNumHits; uint32 maxHealth = m_goValue.Building.DestructibleHitpoint->GetMaxHealth(); // in this case current health is 0 anyway so just prevent crashing here if (!maxHealth) maxHealth = 1; SetGoAnimProgress(m_goValue.Building.Health * 255 / maxHealth); } break; } case GO_DESTRUCTIBLE_DESTROYED: { if (GetGOInfo()->destructibleBuilding.DestroyedEvent && attackerOrHealer) GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DestroyedEvent, attackerOrHealer, this); AI()->Destroyed(attackerOrHealer, m_goInfo->destructibleBuilding.DestroyedEvent); RemoveFlag(GO_FLAG_DAMAGED); SetFlag(GO_FLAG_DESTROYED); uint32 modelId = m_goInfo->displayId; if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec)) if (modelData->State2Wmo) modelId = modelData->State2Wmo; SetDisplayId(modelId); if (setHealth) { m_goValue.Building.Health = 0; SetGoAnimProgress(0); } EnableCollision(false); break; } case GO_DESTRUCTIBLE_REBUILDING: { if (GetGOInfo()->destructibleBuilding.RebuildingEvent && attackerOrHealer) GameEvents::Trigger(GetGOInfo()->destructibleBuilding.RebuildingEvent, attackerOrHealer, this); RemoveFlag(GO_FLAG_DAMAGED | GO_FLAG_DESTROYED); uint32 modelId = m_goInfo->displayId; if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec)) if (modelData->State3Wmo) modelId = modelData->State3Wmo; SetDisplayId(modelId); // restores to full health if (setHealth && m_goValue.Building.DestructibleHitpoint) { m_goValue.Building.Health = m_goValue.Building.DestructibleHitpoint->GetMaxHealth(); SetGoAnimProgress(255); } EnableCollision(true); break; } } } void GameObject::SetLootState(LootState state, Unit* unit) { m_lootState = state; if (unit) m_lootStateUnitGUID = unit->GetGUID(); else m_lootStateUnitGUID.Clear(); AI()->OnLootStateChanged(state, unit); // Start restock timer if the chest is partially looted or not looted at all if (GetGoType() == GAMEOBJECT_TYPE_CHEST && state == GO_ACTIVATED && GetGOInfo()->chest.chestRestockTime > 0 && m_restockTime == 0 && m_loot && m_loot->IsChanged()) m_restockTime = GameTime::GetGameTime() + GetGOInfo()->chest.chestRestockTime; if (GetGoType() == GAMEOBJECT_TYPE_DOOR) // only set collision for doors on SetGoState return; if (m_model) { bool collision = false; // Use the current go state if ((GetGoState() != GO_STATE_READY && (state == GO_ACTIVATED || state == GO_JUST_DEACTIVATED)) || state == GO_READY) collision = !collision; EnableCollision(collision); } } void GameObject::ClearLoot() { // Unlink loot objects from this GameObject before destroying to avoid accessing freed memory from Loot destructor std::unique_ptr loot(std::move(m_loot)); std::unordered_map> personalLoot(std::move(m_personalLoot)); loot.reset(); personalLoot.clear(); m_unique_users.clear(); m_usetimes = 0; } bool GameObject::IsFullyLooted() const { if (m_loot && !m_loot->isLooted()) return false; for (auto const& [_, loot] : m_personalLoot) if (!loot->isLooted()) return false; return true; } void GameObject::OnLootRelease(Player* looter) { switch (GetGoType()) { case GAMEOBJECT_TYPE_CHEST: { GameObjectTemplate const* goInfo = GetGOInfo(); if (!goInfo->chest.consumable && goInfo->chest.chestPersonalLoot) { DespawnForPlayer(looter, goInfo->chest.chestRestockTime ? Seconds(goInfo->chest.chestRestockTime) : Seconds(m_respawnDelayTime)); // not hiding this object permanently to prevent infinite growth of m_perPlayerState // while also maintaining some sort of cheater protection (not getting rid of entries on logout) } break; } case GAMEOBJECT_TYPE_GATHERING_NODE: { SetGoStateFor(GO_STATE_ACTIVE, looter); UF::ObjectData::Base objMask; UF::GameObjectData::Base goMask; objMask.MarkChanged(&UF::ObjectData::DynamicFlags); UpdateData udata(GetMapId()); BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), looter); WorldPacket packet; udata.BuildPacket(&packet); looter->SendDirectMessage(&packet); break; } default: break; } } void GameObject::SetGoState(GOState state) { GOState oldState = GetGoState(); SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::State), state); if (AI()) AI()->OnStateChanged(state); if (m_goTypeImpl) m_goTypeImpl->OnStateChanged(oldState, state); if (m_model && !IsTransport()) { if (!IsInWorld()) return; // startOpen determines whether we are going to add or remove the LoS on activation bool collision = false; if (state == GO_STATE_READY) collision = !collision; EnableCollision(collision); } } GOState GameObject::GetGoStateFor(ObjectGuid const& viewer) const { if (m_perPlayerState) if (PerPlayerState const* state = Trinity::Containers::MapGetValuePtr(*m_perPlayerState, viewer)) if (state->State) return *state->State; return GetGoState(); } void GameObject::SetGoStateFor(GOState state, Player const* viewer) { PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[viewer->GetGUID()]; perPlayerState.ValidUntil = GameTime::GetSystemTime() + Seconds(m_respawnDelayTime); perPlayerState.State = state; WorldPackets::GameObject::GameObjectSetStateLocal setStateLocal; setStateLocal.ObjectGUID = GetGUID(); setStateLocal.State = state; viewer->SendDirectMessage(setStateLocal.Write()); } void GameObject::SetDisplayId(uint32 displayid) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::DisplayID), displayid); UpdateModel(); } uint8 GameObject::GetNameSetId() const { switch (GetGoType()) { case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec)) { switch (GetDestructibleState()) { case GO_DESTRUCTIBLE_INTACT: return modelData->State0NameSet; case GO_DESTRUCTIBLE_DAMAGED: return modelData->State1NameSet; case GO_DESTRUCTIBLE_DESTROYED: return modelData->State2NameSet; case GO_DESTRUCTIBLE_REBUILDING: return modelData->State3NameSet; default: break; } } break; case GAMEOBJECT_TYPE_GARRISON_BUILDING: case GAMEOBJECT_TYPE_GARRISON_PLOT: case GAMEOBJECT_TYPE_PHASEABLE_MO: return ((*m_gameObjectData->Flags) >> 8) & 0xF; default: break; } return 0; } void GameObject::EnableCollision(bool enable) { if (!m_model) return; /*if (enable && !GetMap()->ContainsGameObjectModel(*m_model)) GetMap()->InsertGameObjectModel(*m_model);*/ m_model->EnableCollision(enable); } void GameObject::UpdateModel() { if (!IsInWorld()) return; if (m_model) if (GetMap()->ContainsGameObjectModel(*m_model)) GetMap()->RemoveGameObjectModel(*m_model); RemoveFlag(GO_FLAG_MAP_OBJECT); delete m_model; m_model = nullptr; CreateModel(); if (m_model) GetMap()->InsertGameObjectModel(*m_model); } bool GameObject::IsLootAllowedFor(Player const* player) const { if (Loot const* loot = GetLootForPlayer(player)) // check only if loot was already generated { if (loot->isLooted()) // nothing to loot or everything looted. return false; if (!loot->HasAllowedLooter(GetGUID()) || (!loot->hasItemForAll() && !loot->hasItemFor(player))) // no loot in chest for this player return false; } if (HasLootRecipient()) return m_tapList.find(player->GetGUID()) != m_tapList.end(); return true; } Loot* GameObject::GetLootForPlayer(Player const* player) const { if (m_personalLoot.empty()) return m_loot.get(); if (Loot* loot = Trinity::Containers::MapGetValuePtr(m_personalLoot, player->GetGUID())) return loot; return nullptr; } GameObject* GameObject::GetLinkedTrap() { return ObjectAccessor::GetGameObject(*this, m_linkedTrap); } void GameObject::BuildValuesCreate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const { m_objectData->WriteCreate(*data, flags, this, target); m_gameObjectData->WriteCreate(*data, flags, this, target); } void GameObject::BuildValuesUpdate(ByteBuffer* data, UF::UpdateFieldFlag flags, Player const* target) const { *data << uint32(m_values.GetChangedObjectTypeMask()); if (m_values.HasChanged(TYPEID_OBJECT)) m_objectData->WriteUpdate(*data, flags, this, target); if (m_values.HasChanged(TYPEID_GAMEOBJECT)) m_gameObjectData->WriteUpdate(*data, flags, this, target); } void GameObject::BuildValuesUpdateForPlayerWithMask(UpdateData* data, UF::ObjectData::Mask const& requestedObjectMask, UF::GameObjectData::Mask const& requestedGameObjectMask, Player const* target) const { UF::UpdateFieldFlag flags = GetUpdateFieldFlagsFor(target); UpdateMask valuesMask; if (requestedObjectMask.IsAnySet()) valuesMask.Set(TYPEID_OBJECT); if (requestedGameObjectMask.IsAnySet()) valuesMask.Set(TYPEID_GAMEOBJECT); ByteBuffer& buffer = PrepareValuesUpdateBuffer(data); std::size_t sizePos = buffer.wpos(); buffer << uint32(0); BuildEntityFragmentsForValuesUpdateForPlayerWithMask(&buffer, flags); buffer << uint32(valuesMask.GetBlock(0)); if (valuesMask[TYPEID_OBJECT]) m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target); if (valuesMask[TYPEID_GAMEOBJECT]) m_gameObjectData->WriteUpdate(buffer, requestedGameObjectMask, true, this, target); buffer.put(sizePos, buffer.wpos() - sizePos - 4); data->AddUpdateBlock(); } void GameObject::ValuesUpdateForPlayerWithMaskSender::operator()(Player const* player) const { UpdateData udata(Owner->GetMapId()); WorldPacket packet; Owner->BuildValuesUpdateForPlayerWithMask(&udata, ObjectMask.GetChangesMask(), GameObjectMask.GetChangesMask(), player); udata.BuildPacket(&packet); player->SendDirectMessage(&packet); } void GameObject::ClearUpdateMask(bool remove) { m_values.ClearChangesMask(&GameObject::m_gameObjectData); Object::ClearUpdateMask(remove); } std::vector const* GameObject::GetPauseTimes() const { if (GameObjectType::Transport const* transport = dynamic_cast(m_goTypeImpl.get())) return transport->GetPauseTimes(); return nullptr; } void GameObject::SetPathProgressForClient(float progress) { m_transportPathProgress = progress; } void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = nullptr*/) const { if (m_goData) { if (ori) m_goData->spawnPoint.GetPosition(x, y, z, *ori); else m_goData->spawnPoint.GetPosition(x, y, z); } else { if (ori) GetPosition(x, y, z, *ori); else GetPosition(x, y, z); } } TransportBase const* GameObject::ToTransportBase() const { switch (GetGoType()) { case GAMEOBJECT_TYPE_TRANSPORT: return static_cast(m_goTypeImpl.get()); case GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT: return static_cast(this); default: break; } return nullptr; } void GameObject::AfterRelocation() { UpdateModelPosition(); UpdatePositionData(); if (m_goTypeImpl) m_goTypeImpl->OnRelocated(); // TODO: on heartbeat if (m_vignette) Vignettes::Update(*m_vignette, this); UpdateObjectVisibility(false); } float GameObject::GetInteractionDistance() const { if (GetGOInfo()->GetInteractRadiusOverride()) return float(GetGOInfo()->GetInteractRadiusOverride()) / 100.0f; switch (GetGoType()) { case GAMEOBJECT_TYPE_AREADAMAGE: return 0.0f; case GAMEOBJECT_TYPE_QUESTGIVER: case GAMEOBJECT_TYPE_TEXT: case GAMEOBJECT_TYPE_FLAGSTAND: case GAMEOBJECT_TYPE_FLAGDROP: case GAMEOBJECT_TYPE_MINI_GAME: return 5.5555553f; case GAMEOBJECT_TYPE_BINDER: return 10.0f; case GAMEOBJECT_TYPE_CHAIR: case GAMEOBJECT_TYPE_BARBER_CHAIR: return 3.0f; case GAMEOBJECT_TYPE_FISHINGNODE: return 100.0f; case GAMEOBJECT_TYPE_FISHINGHOLE: return 20.0f + CONTACT_DISTANCE; // max spell range case GAMEOBJECT_TYPE_CAMERA: case GAMEOBJECT_TYPE_MAP_OBJECT: case GAMEOBJECT_TYPE_DUNGEON_DIFFICULTY: case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: case GAMEOBJECT_TYPE_DOOR: return 5.0f; // Following values are not blizzlike case GAMEOBJECT_TYPE_GUILD_BANK: case GAMEOBJECT_TYPE_MAILBOX: // Successful mailbox interaction is rather critical to the client, failing it will start a minute-long cooldown until the next mail query may be executed. // And since movement info update is not sent with mailbox interaction query, server may find the player outside of interaction range. Thus we increase it. return 10.0f; // 5.0f is blizzlike default: return INTERACTION_DISTANCE; } } void GameObject::UpdateModelPosition() { if (!m_model) return; if (GetMap()->ContainsGameObjectModel(*m_model)) { GetMap()->RemoveGameObjectModel(*m_model); m_model->UpdatePosition(); GetMap()->InsertGameObjectModel(*m_model); } } void GameObject::SetAnimKitId(uint16 animKitId, bool oneshot) { if (_animKitId == animKitId) return; if (animKitId && !sAnimKitStore.LookupEntry(animKitId)) return; if (!oneshot) _animKitId = animKitId; else _animKitId = 0; WorldPackets::GameObject::GameObjectActivateAnimKit activateAnimKit; activateAnimKit.ObjectGUID = GetGUID(); activateAnimKit.AnimKitID = animKitId; activateAnimKit.Maintain = !oneshot; SendMessageToSet(activateAnimKit.Write(), true); } void GameObject::SetVignette(uint32 vignetteId) { if (m_vignette) { if (m_vignette->Data->ID == vignetteId) return; Vignettes::Remove(*m_vignette, this); m_vignette = nullptr; } if (VignetteEntry const* vignette = sVignetteStore.LookupEntry(vignetteId)) m_vignette = Vignettes::Create(vignette, this); } void GameObject::SetSpellVisualId(int32 spellVisualId, ObjectGuid activatorGuid) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::SpellVisualID), spellVisualId); WorldPackets::GameObject::GameObjectPlaySpellVisual packet; packet.ObjectGUID = GetGUID(); packet.ActivatorGUID = activatorGuid; packet.SpellVisualID = spellVisualId; SendMessageToSet(packet.Write(), true); } void GameObject::AssaultCapturePoint(Player* player) { if (!CanInteractWithCapturePoint(player)) return; if (GameObjectAI* ai = AI()) if (ai->OnCapturePointAssaulted(player)) return; // only supported in battlegrounds Battleground* battleground = nullptr; if (BattlegroundMap* map = GetMap()->ToBattlegroundMap()) if (Battleground* bg = map->GetBG()) battleground = bg; if (!battleground) return; // Cancel current timer m_goValue.CapturePoint.AssaultTimer = 0; if (player->GetBGTeam() == HORDE) { if (m_goValue.CapturePoint.LastTeamCapture == TEAM_HORDE) { // defended. capture instantly. m_goValue.CapturePoint.State = WorldPackets::Battleground::BattlegroundCapturePointState::HordeCaptured; battleground->SendBroadcastText(GetGOInfo()->capturePoint.DefendedBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE, player); UpdateCapturePoint(); if (GetGOInfo()->capturePoint.DefendedEventHorde) GameEvents::Trigger(GetGOInfo()->capturePoint.DefendedEventHorde, player, this); return; } switch (m_goValue.CapturePoint.State) { case WorldPackets::Battleground::BattlegroundCapturePointState::Neutral: case WorldPackets::Battleground::BattlegroundCapturePointState::AllianceCaptured: case WorldPackets::Battleground::BattlegroundCapturePointState::ContestedAlliance: m_goValue.CapturePoint.State = WorldPackets::Battleground::BattlegroundCapturePointState::ContestedHorde; battleground->SendBroadcastText(GetGOInfo()->capturePoint.AssaultBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE, player); UpdateCapturePoint(); if (GetGOInfo()->capturePoint.ContestedEventHorde) GameEvents::Trigger(GetGOInfo()->capturePoint.ContestedEventHorde, player, this); m_goValue.CapturePoint.AssaultTimer = GetGOInfo()->capturePoint.CaptureTime; break; default: break; } } else { if (m_goValue.CapturePoint.LastTeamCapture == TEAM_ALLIANCE) { // defended. capture instantly. m_goValue.CapturePoint.State = WorldPackets::Battleground::BattlegroundCapturePointState::AllianceCaptured; battleground->SendBroadcastText(GetGOInfo()->capturePoint.DefendedBroadcastAlliance, CHAT_MSG_BG_SYSTEM_ALLIANCE, player); UpdateCapturePoint(); if (GetGOInfo()->capturePoint.DefendedEventAlliance) GameEvents::Trigger(GetGOInfo()->capturePoint.DefendedEventAlliance, player, this); return; } switch (m_goValue.CapturePoint.State) { case WorldPackets::Battleground::BattlegroundCapturePointState::Neutral: case WorldPackets::Battleground::BattlegroundCapturePointState::HordeCaptured: case WorldPackets::Battleground::BattlegroundCapturePointState::ContestedHorde: m_goValue.CapturePoint.State = WorldPackets::Battleground::BattlegroundCapturePointState::ContestedAlliance; battleground->SendBroadcastText(GetGOInfo()->capturePoint.AssaultBroadcastAlliance, CHAT_MSG_BG_SYSTEM_ALLIANCE, player); UpdateCapturePoint(); if (GetGOInfo()->capturePoint.ContestedEventAlliance) GameEvents::Trigger(GetGOInfo()->capturePoint.ContestedEventAlliance, player, this); m_goValue.CapturePoint.AssaultTimer = GetGOInfo()->capturePoint.CaptureTime; break; default: break; } } } void GameObject::UpdateCapturePoint() { if (GetGoType() != GAMEOBJECT_TYPE_CAPTURE_POINT) return; if (GameObjectAI* ai = AI()) if (ai->OnCapturePointUpdated(m_goValue.CapturePoint.State)) return; uint32 spellVisualId = 0; uint32 customAnim = 0; switch (m_goValue.CapturePoint.State) { case WorldPackets::Battleground::BattlegroundCapturePointState::Neutral: spellVisualId = GetGOInfo()->capturePoint.SpellVisual1; break; case WorldPackets::Battleground::BattlegroundCapturePointState::ContestedHorde: customAnim = 1; spellVisualId = GetGOInfo()->capturePoint.SpellVisual2; break; case WorldPackets::Battleground::BattlegroundCapturePointState::ContestedAlliance: customAnim = 2; spellVisualId = GetGOInfo()->capturePoint.SpellVisual3; break; case WorldPackets::Battleground::BattlegroundCapturePointState::HordeCaptured: customAnim = 3; spellVisualId = GetGOInfo()->capturePoint.SpellVisual4; break; case WorldPackets::Battleground::BattlegroundCapturePointState::AllianceCaptured: customAnim = 4; spellVisualId = GetGOInfo()->capturePoint.SpellVisual5; break; default: break; } if (customAnim != 0) SendCustomAnim(customAnim); SetSpellVisualId(spellVisualId); UpdateDynamicFlagsForNearbyPlayers(); if (BattlegroundMap* map = GetMap()->ToBattlegroundMap()) { if (Battleground* bg = map->GetBG()) { WorldPackets::Battleground::UpdateCapturePoint packet; packet.CapturePointInfo.State = m_goValue.CapturePoint.State; packet.CapturePointInfo.Pos = GetPosition(); packet.CapturePointInfo.Guid = GetGUID(); packet.CapturePointInfo.CaptureTotalDuration = Milliseconds(GetGOInfo()->capturePoint.CaptureTime); packet.CapturePointInfo.CaptureTime = m_goValue.CapturePoint.AssaultTimer; bg->SendPacketToAll(packet.Write()); bg->UpdateWorldState(GetGOInfo()->capturePoint.worldState1, AsUnderlyingType(m_goValue.CapturePoint.State)); } } GetMap()->UpdateSpawnGroupConditions(); } bool GameObject::CanInteractWithCapturePoint(Player const* target) const { if (m_goInfo->type != GAMEOBJECT_TYPE_CAPTURE_POINT) return false; if (m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::Neutral) return true; if (target->GetBGTeam() == HORDE) { return m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::ContestedAlliance || m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::AllianceCaptured; } // For Alliance players return m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::ContestedHorde || m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::HordeCaptured; } FlagState GameObject::GetFlagState() const { if (GetGoType() != GAMEOBJECT_TYPE_NEW_FLAG) return FlagState(0); GameObjectType::NewFlag const* newFlag = dynamic_cast(m_goTypeImpl.get()); if (!newFlag) return FlagState(0); return newFlag->GetState(); } ObjectGuid const& GameObject::GetFlagCarrierGUID() const { if (GetGoType() != GAMEOBJECT_TYPE_NEW_FLAG) return ObjectGuid::Empty; GameObjectType::NewFlag const* newFlag = dynamic_cast(m_goTypeImpl.get()); if (!newFlag) return ObjectGuid::Empty; return newFlag->GetCarrierGUID(); } time_t GameObject::GetFlagTakenFromBaseTime() const { if (GetGoType() != GAMEOBJECT_TYPE_NEW_FLAG) return time_t(0); GameObjectType::NewFlag const* newFlag = dynamic_cast(m_goTypeImpl.get()); if (!newFlag) return time_t(0); return newFlag->GetTakenFromBaseTime(); } GuidUnorderedSet const* GameObject::GetInsidePlayers() const { if (GameObjectType::ControlZone const* controlZone = dynamic_cast(m_goTypeImpl.get())) return controlZone->GetInsidePlayers(); return nullptr; } bool GameObject::MeetsInteractCondition(Player const* user) const { return ConditionMgr::IsPlayerMeetingCondition(user, m_goInfo->GetConditionID1()); } std::unordered_map& GameObject::GetOrCreatePerPlayerStates() { if (!m_perPlayerState) m_perPlayerState = std::make_unique>(); return *m_perPlayerState; } class GameObjectModelOwnerImpl : public GameObjectModelOwnerBase { public: explicit GameObjectModelOwnerImpl(GameObject* owner) : _owner(owner) { } virtual ~GameObjectModelOwnerImpl() = default; bool IsSpawned() const override { return _owner->isSpawned(); } uint32 GetDisplayId() const override { return _owner->GetDisplayId(); } uint8 GetNameSetId() const override { return _owner->GetNameSetId(); } bool IsInPhase(PhaseShift const& phaseShift) const override { return _owner->GetPhaseShift().CanSee(phaseShift); } G3D::Vector3 GetPosition() const override { return G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); } G3D::Quat GetRotation() const override { return G3D::Quat(_owner->GetLocalRotation().x, _owner->GetLocalRotation().y, _owner->GetLocalRotation().z, _owner->GetLocalRotation().w); } float GetScale() const override { return _owner->GetObjectScale(); } void DebugVisualizeCorner(G3D::Vector3 const& corner) const override { _owner->SummonCreature(1, corner.x, corner.y, corner.z, 0, TEMPSUMMON_MANUAL_DESPAWN); } private: GameObject* _owner; }; void GameObject::UpdateDynamicFlagsForNearbyPlayers() { m_values.ModifyValue(&Object::m_objectData).ModifyValue(&UF::ObjectData::DynamicFlags); AddToObjectUpdateIfNeeded(); } void GameObject::HandleCustomTypeCommand(GameObjectTypeBase::CustomCommand const& command) const { if (m_goTypeImpl) command.Execute(*m_goTypeImpl); } TeamId GameObject::GetControllingTeam() const { if (GetGoType() != GAMEOBJECT_TYPE_CONTROL_ZONE) return TEAM_NEUTRAL; GameObjectType::ControlZone const* controlZone = dynamic_cast(m_goTypeImpl.get()); if (!controlZone) return TEAM_NEUTRAL; return controlZone->GetControllingTeam(); } void GameObject::CreateModel() { m_model = GameObjectModel::Create(std::make_unique(this), sWorld->GetDataPath()); if (m_model) { if (m_model->IsMapObject()) SetFlag(GO_FLAG_MAP_OBJECT); if (GetGoType() == GAMEOBJECT_TYPE_DOOR) m_model->DisableLosBlocking(GetGOInfo()->door.NotLOSBlocking); } } std::string GameObject::GetDebugInfo() const { std::stringstream sstr; sstr << WorldObject::GetDebugInfo() << "\n" << "SpawnId: " << GetSpawnId() << " GoState: " << std::to_string(GetGoState()) << " ScriptId: " << GetScriptId() << " AIName: " << GetAIName(); return sstr.str(); } bool GameObject::IsAtInteractDistance(Player const* player, SpellInfo const* spell) const { if (spell || (spell = GetSpellForLock(player))) { float maxRange = spell->GetMaxRange(spell->IsPositive()); if (GetGoType() == GAMEOBJECT_TYPE_SPELL_FOCUS) return maxRange * maxRange >= GetExactDistSq(player); if (sGameObjectDisplayInfoStore.LookupEntry(GetGOInfo()->displayId)) return IsAtInteractDistance(*player, maxRange); } return IsAtInteractDistance(*player, GetInteractionDistance()); } bool GameObject::IsAtInteractDistance(Position const& pos, float radius) const { if (GameObjectDisplayInfoEntry const* displayInfo = sGameObjectDisplayInfoStore.LookupEntry(GetGOInfo()->displayId)) { float scale = GetObjectScale(); float minX = displayInfo->GeoBoxMin.X * scale - radius; float minY = displayInfo->GeoBoxMin.Y * scale - radius; float minZ = displayInfo->GeoBoxMin.Z * scale - radius; float maxX = displayInfo->GeoBoxMax.X * scale + radius; float maxY = displayInfo->GeoBoxMax.Y * scale + radius; float maxZ = displayInfo->GeoBoxMax.Z * scale + radius; QuaternionData worldRotation = GetWorldRotation(); G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w); return G3D::CoordinateFrame { { worldRotationQuat }, { GetPositionX(), GetPositionY(), GetPositionZ() } } .toWorldSpace(G3D::Box { { minX, minY, minZ }, { maxX, maxY, maxZ } }) .contains({ pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ() }); } return GetExactDist(&pos) <= radius; } bool GameObject::IsWithinDistInMap(Player const* player) const { return IsInMap(player) && InSamePhase(player) && IsAtInteractDistance(player); } SpellInfo const* GameObject::GetSpellForLock(Player const* player) const { if (!player) return nullptr; uint32 lockId = GetGOInfo()->GetLockId(); if (!lockId) return nullptr; LockEntry const* lock = sLockStore.LookupEntry(lockId); if (!lock) return nullptr; for (uint8 i = 0; i < MAX_LOCK_CASE; ++i) { if (!lock->Type[i]) continue; if (lock->Type[i] == LOCK_KEY_SPELL) if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(lock->Index[i], GetMap()->GetDifficultyID())) return spell; if (lock->Type[i] != LOCK_KEY_SKILL) break; for (auto&& playerSpell : player->GetSpellMap()) if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(playerSpell.first, GetMap()->GetDifficultyID())) for (auto&& effect : spell->GetEffects()) if (effect.Effect == SPELL_EFFECT_OPEN_LOCK && effect.MiscValue == lock->Index[i]) if (effect.CalcValue(player) >= int32(lock->Skill[i])) return spell; } return nullptr; }