/*
* 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;
}