3141 lines
111 KiB
C++
3141 lines
111 KiB
C++
/*
|
|
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "GameObject.h"
|
|
#include "ArchaeologyMgr.h"
|
|
#include "Battleground.h"
|
|
#include "CellImpl.h"
|
|
#include "CreatureAISelector.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "DBCStores.h"
|
|
#include "G3DPosition.hpp"
|
|
#include "GameObjectAI.h"
|
|
#include "GameEventSender.h"
|
|
#include "GameObjectModel.h"
|
|
#include "GameTime.h"
|
|
#include "GossipDef.h"
|
|
#include "GridNotifiersImpl.h"
|
|
#include "Group.h"
|
|
#include "GroupMgr.h"
|
|
#include "Log.h"
|
|
#include "LootMgr.h"
|
|
#include "Map.h"
|
|
#include "MapManager.h"
|
|
#include "ObjectAccessor.h"
|
|
#include "ObjectMgr.h"
|
|
#include "OutdoorPvPMgr.h"
|
|
#include "PhasingHandler.h"
|
|
#include "PoolMgr.h"
|
|
#include "QueryPackets.h"
|
|
#include "ScriptMgr.h"
|
|
#include "SpellMgr.h"
|
|
#include "Transport.h"
|
|
#include "UpdateFieldFlags.h"
|
|
#include "World.h"
|
|
#include <G3D/Box.h>
|
|
#include <G3D/CoordinateFrame.h>
|
|
#include <G3D/Quat.h>
|
|
|
|
bool QuaternionData::isUnit() const
|
|
{
|
|
return fabs(x * x + y * y + z * z + w * w - 1.0f) < 1e-5f;
|
|
}
|
|
|
|
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:
|
|
// @todo: chronofy timer trackers for 4.x
|
|
//static constexpr Milliseconds PositionUpdateInterval = 50ms;
|
|
static constexpr uint8 PositionUpdateInterval = 50;
|
|
|
|
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 (!_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.GetUInt32Value(GAMEOBJECT_LEVEL)))
|
|
{
|
|
int32 timeToStop = _owner.GetInt32Value(GAMEOBJECT_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.HasFlag(GAMEOBJECT_DYNAMIC, 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;
|
|
default:
|
|
return 0u;
|
|
}
|
|
}();
|
|
if (eventId)
|
|
GameEvents::Trigger(eventId, &_owner, nullptr);
|
|
|
|
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.HasFlag(GAMEOBJECT_DYNAMIC, 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.GetFloatValue(GAMEOBJECT_PARENTROTATION + 0), _owner.GetFloatValue(GAMEOBJECT_PARENTROTATION + 1),
|
|
_owner.GetFloatValue(GAMEOBJECT_PARENTROTATION + 2), _owner.GetFloatValue(GAMEOBJECT_PARENTROTATION + 3)).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->X, oldRotation->Y, oldRotation->Z, oldRotation->W);
|
|
G3D::Quat next(newRotation->X, newRotation->Y, newRotation->Z, newRotation->W);
|
|
|
|
G3D::Quat rotation = next;
|
|
|
|
if (!(prev == next)) // @todo: use != operator when C++20 has been enabled
|
|
{
|
|
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.SetUInt32Value(GAMEOBJECT_LEVEL, GameTime::GetGameTimeMS() + timeToStop);
|
|
_owner.SetPathProgressForClient(float(_pathProgress) / float(GetTransportPeriod()));
|
|
|
|
if (oldState == GO_STATE_ACTIVE || oldState == newState)
|
|
{
|
|
// initialization
|
|
if (int32(_pathProgress) > stopPathProgress)
|
|
_owner.SetFlag(GAMEOBJECT_DYNAMIC, GO_DYNFLAG_LO_INVERTED_MOVEMENT);
|
|
else
|
|
_owner.RemoveFlag(GAMEOBJECT_DYNAMIC, 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.SetFlag(GAMEOBJECT_DYNAMIC, GO_DYNFLAG_LO_INVERTED_MOVEMENT);
|
|
else
|
|
_owner.RemoveFlag(GAMEOBJECT_DYNAMIC, 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<uint32> 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 %s boarded transport %s.", passenger->GetName().c_str(), _owner.GetName().c_str());
|
|
}
|
|
}
|
|
|
|
TransportBase* RemovePassenger(WorldObject* passenger) override
|
|
{
|
|
if (_passengers.erase(passenger) > 0)
|
|
{
|
|
passenger->SetTransport(nullptr);
|
|
passenger->m_movementInfo.transport.Reset();
|
|
TC_LOG_DEBUG("entities.transport", "Object %s removed from transport %s.", passenger->GetName().c_str(), _owner.GetName().c_str());
|
|
|
|
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<uint32> _stopFrames;
|
|
bool _autoCycleBetweenStopFrames;
|
|
TimeTrackerSmall _positionUpdateTimer;
|
|
std::unordered_set<WorldObject*> _passengers;
|
|
};
|
|
|
|
SetTransportAutoCycleBetweenStopFrames::SetTransportAutoCycleBetweenStopFrames(bool on) : _on(on)
|
|
{
|
|
}
|
|
|
|
void SetTransportAutoCycleBetweenStopFrames::Execute(GameObjectTypeBase& type) const
|
|
{
|
|
if (Transport* transport = dynamic_cast<Transport*>(&type))
|
|
transport->SetAutoCycleBetweenStopFrames(_on);
|
|
}
|
|
}
|
|
|
|
void GameObjectTemplate::InitializeQueryData()
|
|
{
|
|
WorldPacket queryTemp;
|
|
for (uint8 loc = LOCALE_enUS; loc < TOTAL_LOCALES; ++loc)
|
|
{
|
|
queryTemp = BuildQueryData(static_cast<LocaleConstant>(loc));
|
|
QueryData[loc] = queryTemp;
|
|
}
|
|
}
|
|
|
|
WorldPacket GameObjectTemplate::BuildQueryData(LocaleConstant loc) const
|
|
{
|
|
WorldPackets::Query::QueryGameObjectResponse queryTemp;
|
|
|
|
std::string locName = name;
|
|
std::string locIconName = IconName;
|
|
std::string locCastBarCaption = castBarCaption;
|
|
|
|
if (GameObjectLocale const* gameObjectLocale = sObjectMgr->GetGameObjectLocale(entry))
|
|
{
|
|
ObjectMgr::GetLocaleString(gameObjectLocale->Name, loc, locName);
|
|
ObjectMgr::GetLocaleString(gameObjectLocale->CastBarCaption, loc, locCastBarCaption);
|
|
}
|
|
|
|
queryTemp.GameObjectID = entry;
|
|
queryTemp.Allow = true;
|
|
|
|
queryTemp.Stats.Type = type;
|
|
queryTemp.Stats.DisplayID = displayId;
|
|
queryTemp.Stats.Name[0] = locName;
|
|
queryTemp.Stats.IconName = locIconName;
|
|
queryTemp.Stats.CastBarCaption = locCastBarCaption;
|
|
queryTemp.Stats.UnkString = unk1;
|
|
|
|
for (uint8 i = 0; i < MAX_GAMEOBJECT_DATA; ++i)
|
|
queryTemp.Stats.Data[i] = raw.data[i];
|
|
|
|
queryTemp.Stats.Size = size;
|
|
|
|
for (uint32 i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; ++i)
|
|
queryTemp.Stats.QuestItems[i] = 0;
|
|
|
|
if (std::vector<uint32> const* items = sObjectMgr->GetCreatureQuestItemList(entry))
|
|
for (uint32 i = 0; i < MAX_GAMEOBJECT_QUEST_ITEMS; ++i)
|
|
if (i < items->size())
|
|
queryTemp.Stats.QuestItems[i] = (*items)[i];
|
|
|
|
queryTemp.Stats.RequiredLevel = RequiredLevel;
|
|
|
|
queryTemp.Write();
|
|
queryTemp.ShrinkToFit();
|
|
return queryTemp.Move();
|
|
}
|
|
|
|
GameObject::GameObject() : WorldObject(false), MapObject(),
|
|
m_model(nullptr), m_goValue(), m_AI(nullptr), m_respawnCompatibilityMode(false), _triggerJustAppeared(true)
|
|
{
|
|
m_objectType |= TYPEMASK_GAMEOBJECT;
|
|
m_objectTypeId = TYPEID_GAMEOBJECT;
|
|
|
|
m_updateFlag = (UPDATEFLAG_STATIONARY_POSITION | UPDATEFLAG_ROTATION);
|
|
|
|
m_valuesCount = GAMEOBJECT_END;
|
|
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;
|
|
_charges = 0;
|
|
m_spellId = 0;
|
|
m_cooldownTime = 0;
|
|
m_prevGoState = GO_STATE_ACTIVE;
|
|
m_goInfo = nullptr;
|
|
m_goData = nullptr;
|
|
m_goTemplateAddon = nullptr;
|
|
m_packedRotation = 0;
|
|
|
|
m_spawnId = 0;
|
|
|
|
m_lootRecipientGroup = 0;
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
m_lootGenerationTime = 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)
|
|
{
|
|
WorldObject::CleanupsBeforeDelete(finalCleanup);
|
|
|
|
if (m_uint32Values) // field array can be not exist if GameOBject not loaded
|
|
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 (GUID: %u Entry: %u SpellId: %u LinkedGO: %u) that just lost any reference to the owner (%s) GO list",
|
|
GetGUID().GetCounter(), GetGOInfo()->entry, m_spellId, GetGOInfo()->GetLinkedGameObjectEntry(), ownerGUID.ToString().c_str());
|
|
SetOwnerGUID(ObjectGuid::Empty);
|
|
}
|
|
|
|
void GameObject::AddToWorld()
|
|
{
|
|
///- Register the gameobject for guid lookup
|
|
if (!IsInWorld())
|
|
{
|
|
if (m_zoneScript)
|
|
m_zoneScript->OnGameObjectCreate(this);
|
|
|
|
GetMap()->GetObjectsStore().Insert<GameObject>(GetGUID(), 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<GameObject>(GetGUID());
|
|
}
|
|
}
|
|
|
|
bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit /*= 0*/, bool dynamic, ObjectGuid::LowType spawnid)
|
|
{
|
|
ASSERT(map);
|
|
SetMap(map);
|
|
|
|
Relocate(pos);
|
|
m_stationaryPosition.Relocate(pos);
|
|
if (!IsPositionValid())
|
|
{
|
|
TC_LOG_ERROR("misc", "Gameobject (GUID: %u Entry: %u) not created. Suggested coordinates isn't valid (X: %f Y: %f)", guidlow, name_id, pos.GetPositionX(), pos.GetPositionY());
|
|
return false;
|
|
}
|
|
|
|
// Set if this object can handle dynamic spawns
|
|
if (!dynamic)
|
|
SetRespawnCompatibilityMode();
|
|
|
|
UpdatePositionData();
|
|
|
|
SetZoneScript();
|
|
if (m_zoneScript)
|
|
{
|
|
name_id = m_zoneScript->GetGameObjectEntry(guidlow, name_id);
|
|
if (!name_id)
|
|
return false;
|
|
}
|
|
|
|
GameObjectTemplate const* goinfo = sObjectMgr->GetGameObjectTemplate(name_id);
|
|
if (!goinfo)
|
|
{
|
|
TC_LOG_ERROR("sql.sql", "Gameobject (GUID: %u Entry: %u) not created: non-existing entry in `gameobject_template`. Map: %u (X: %f Y: %f Z: %f)", guidlow, name_id, map->GetId(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
|
|
return false;
|
|
}
|
|
|
|
if (goinfo->type == GAMEOBJECT_TYPE_MO_TRANSPORT)
|
|
{
|
|
TC_LOG_ERROR("sql.sql", "Gameobject (GUID: %u Entry: %u) not created: gameobject type GAMEOBJECT_TYPE_MO_TRANSPORT cannot be manually created.", guidlow, name_id);
|
|
return false;
|
|
}
|
|
|
|
HighGuid guidHigh = HighGuid::GameObject;
|
|
|
|
if (goinfo->type == GAMEOBJECT_TYPE_TRANSPORT)
|
|
{
|
|
m_updateFlag |= UPDATEFLAG_TRANSPORT;
|
|
guidHigh = HighGuid::Mo_Transport;
|
|
}
|
|
|
|
Object::_Create(guidlow, goinfo->entry, guidHigh);
|
|
|
|
m_goInfo = goinfo;
|
|
m_goTemplateAddon = sObjectMgr->GetGameObjectTemplateAddon(name_id);
|
|
|
|
if (goinfo->type >= MAX_GAMEOBJECT_TYPE)
|
|
{
|
|
TC_LOG_ERROR("sql.sql", "Gameobject (GUID: %u Entry: %u) not created: non-existing GO type '%u' in `gameobject_template`. It will crash client if created.", guidlow, name_id, 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 (m_goTemplateAddon)
|
|
{
|
|
SetFaction(m_goTemplateAddon->faction);
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, m_goTemplateAddon->flags);
|
|
}
|
|
|
|
SetEntry(goinfo->entry);
|
|
|
|
// set name for logs usage, doesn't affect anything ingame
|
|
SetName(goinfo->name);
|
|
|
|
SetDisplayId(goinfo->displayId);
|
|
|
|
m_model = CreateModel();
|
|
// GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3
|
|
SetGoType(GameobjectTypes(goinfo->type));
|
|
m_prevGoState = go_state;
|
|
SetGoState(go_state);
|
|
SetGoArtKit(artKit);
|
|
SetCharges(goinfo->GetCharges());
|
|
|
|
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.Health = goinfo->building.intactNumHits + goinfo->building.damagedNumHits;
|
|
m_goValue.Building.MaxHealth = m_goValue.Building.Health;
|
|
SetGoAnimProgress(255);
|
|
SetUInt32Value(GAMEOBJECT_PARENTROTATION, m_goInfo->building.destructibleData);
|
|
break;
|
|
case GAMEOBJECT_TYPE_TRANSPORT:
|
|
{
|
|
m_goTypeImpl = std::make_unique<GameObjectType::Transport>(*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:
|
|
SetGoAnimProgress(0);
|
|
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;
|
|
default:
|
|
SetGoAnimProgress(animprogress);
|
|
break;
|
|
}
|
|
|
|
if (gameObjectAddon && gameObjectAddon->InvisibilityValue)
|
|
{
|
|
m_invisibility.AddFlag(gameObjectAddon->invisibilityType);
|
|
m_invisibility.AddValue(gameObjectAddon->invisibilityType, gameObjectAddon->InvisibilityValue);
|
|
}
|
|
|
|
LastUsedScriptID = GetGOInfo()->ScriptId;
|
|
AIM_Initialize();
|
|
|
|
// Initialize loot duplicate count depending on raid difficulty
|
|
if (map->Is25ManRaid())
|
|
loot.maxDuplicates = 3;
|
|
|
|
if (spawnid)
|
|
m_spawnId = spawnid;
|
|
|
|
if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry())
|
|
{
|
|
GameObject* linkedGO = new GameObject();
|
|
if (linkedGO->Create(map->GenerateLowGuid<HighGuid::GameObject>(), linkedEntry, map, pos, rotation, 255, GO_STATE_READY))
|
|
{
|
|
SetLinkedTrap(linkedGO);
|
|
map->AddToMap(linkedGO);
|
|
}
|
|
else
|
|
delete linkedGO;
|
|
}
|
|
|
|
// Check if GameObject is Infinite
|
|
if (m_goInfo->IsInfiniteGameObject())
|
|
SetVisibilityDistanceOverride(VisibilityDistanceType::Infinite);
|
|
|
|
// Check if GameObject is Gigantic
|
|
if (m_goInfo->IsGiganticGameObject())
|
|
SetVisibilityDistanceOverride(VisibilityDistanceType::Gigantic);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GameObject::Update(uint32 diff)
|
|
{
|
|
m_Events.Update(diff);
|
|
|
|
if (AI())
|
|
AI()->UpdateAI(diff);
|
|
else if (!AIM_Initialize())
|
|
TC_LOG_ERROR("misc", "Could not initialize GameObjectAI");
|
|
|
|
if (AI() && _triggerJustAppeared)
|
|
{
|
|
_triggerJustAppeared = false;
|
|
AI()->JustAppeared();
|
|
}
|
|
|
|
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);
|
|
|
|
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)
|
|
{
|
|
SetGoState(GO_STATE_ACTIVE);
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, GO_FLAG_NODESPAWN);
|
|
|
|
UpdateData udata(caster->GetMapId());
|
|
WorldPacket packet;
|
|
BuildValuesUpdateBlockForPlayer(&udata, caster->ToPlayer());
|
|
udata.BuildPacket(&packet);
|
|
caster->ToPlayer()->SendDirectMessage(&packet);
|
|
|
|
SendCustomAnim(GetGoAnimProgress());
|
|
}
|
|
|
|
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;
|
|
AddToObjectUpdateIfNeeded();
|
|
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(HighGuid::GameObject, 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;
|
|
_tappingPlayers.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);
|
|
|
|
WorldPacket data(SMSG_FISH_ESCAPED, 0);
|
|
caster->ToPlayer()->SendDirectMessage(&data);
|
|
}
|
|
// 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<GameObject>(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;
|
|
if (!goInfo->trap.radius)
|
|
{
|
|
// Battleground traps: data2 == 0 && data5 == 3
|
|
if (goInfo->trap.cooldown != 3)
|
|
break;
|
|
|
|
radius = 3.f;
|
|
}
|
|
else
|
|
radius = goInfo->trap.radius / 2.f;
|
|
|
|
// Pointer to appropriate target if found any
|
|
Unit* target = nullptr;
|
|
|
|
/// @todo this hack with search required until GO casting not implemented
|
|
if (GetOwner())
|
|
{
|
|
// Hunter trap: Search units which are unfriendly to the trap's owner
|
|
Trinity::NearestAttackableNoTotemUnitInObjectRangeCheck checker(this, radius);
|
|
Trinity::UnitLastSearcher<Trinity::NearestAttackableNoTotemUnitInObjectRangeCheck> searcher(this, target, checker);
|
|
Cell::VisitAllObjects(this, searcher, radius);
|
|
}
|
|
else
|
|
{
|
|
// Environmental trap: Any player
|
|
Player* player = nullptr;
|
|
Trinity::AnyPlayerInObjectRangeCheck checker(this, radius);
|
|
Trinity::PlayerSearcher<Trinity::AnyPlayerInObjectRangeCheck> searcher(this, player, checker);
|
|
Cell::VisitWorldObjects(this, searcher, radius);
|
|
target = player;
|
|
}
|
|
|
|
if (target)
|
|
SetLootState(GO_ACTIVATED, target);
|
|
|
|
}
|
|
else if (_charges)
|
|
{
|
|
if (m_usetimes >= _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(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
}
|
|
break;
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
if (m_groupLootTimer)
|
|
{
|
|
if (m_groupLootTimer <= diff)
|
|
{
|
|
Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID);
|
|
if (group)
|
|
group->EndRoll(&loot, GetMap());
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
}
|
|
else m_groupLootTimer -= diff;
|
|
}
|
|
|
|
// Gameobject was partially looted and restock time passed, restock all loot now
|
|
if (!GetGOInfo()->chest.consumable && GameTime::GetGameTime() >= m_restockTime)
|
|
{
|
|
m_restockTime = 0;
|
|
m_lootState = GO_READY;
|
|
AddToObjectUpdateIfNeeded();
|
|
}
|
|
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);
|
|
|
|
// Battleground gameobjects have data2 == 0 && data5 == 3
|
|
if (!goInfo->trap.radius && goInfo->trap.cooldown == 3)
|
|
if (Player* player = target->ToPlayer())
|
|
if (Battleground* bg = player->GetBattleground())
|
|
bg->HandleTriggerBuff(GetGUID());
|
|
}
|
|
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;
|
|
}
|
|
|
|
// Only goobers with a lock id and a reset time may reset their go state
|
|
if (GetGOInfo()->GetLockId() || GetGOInfo()->GetAutoCloseTime())
|
|
SetGoState(GO_STATE_READY);
|
|
|
|
//any return here in case battleground traps
|
|
if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
|
|
if (addon->flags & GO_FLAG_NODESPAWN)
|
|
return;
|
|
}
|
|
|
|
loot.clear();
|
|
|
|
// 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);
|
|
AddToObjectUpdateIfNeeded();
|
|
}
|
|
else
|
|
SetLootState(GO_READY);
|
|
UpdateObjectVisibility();
|
|
return;
|
|
}
|
|
else if (GetOwnerGUID() || GetSpellId())
|
|
{
|
|
SetRespawnTime(0);
|
|
Delete();
|
|
return;
|
|
}
|
|
|
|
SetLootState(GO_READY);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 const& delay, Seconds const& forceRespawnTime)
|
|
{
|
|
if (delay > 0ms)
|
|
{
|
|
if (!m_despawnDelay || m_despawnDelay > delay.count())
|
|
{
|
|
m_despawnDelay = delay.count();
|
|
m_despawnRespawnTime = forceRespawnTime;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
uint32 const respawnDelay = (forceRespawnTime > 0s) ? forceRespawnTime.count() : m_respawnDelayTime;
|
|
if (m_goData && respawnDelay)
|
|
SaveRespawnTime(respawnDelay);
|
|
Delete();
|
|
}
|
|
}
|
|
|
|
void GameObject::Delete()
|
|
{
|
|
SetLootState(GO_NOT_READY);
|
|
RemoveFromOwner();
|
|
|
|
SendGameObjectDespawn();
|
|
|
|
if (m_goInfo->type != GAMEOBJECT_TYPE_TRANSPORT)
|
|
SetGoState(GO_STATE_READY);
|
|
|
|
if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, addon->flags);
|
|
|
|
uint32 poolid = GetGameObjectData() ? GetGameObjectData()->poolId : 0;
|
|
if (poolid)
|
|
sPoolMgr->UpdatePool<GameObject>(GetMap()->GetPoolData(), poolid, GetSpawnId());
|
|
else
|
|
AddObjectToRemoveList();
|
|
}
|
|
|
|
void GameObject::SendGameObjectDespawn()
|
|
{
|
|
WorldPacket data(SMSG_GAMEOBJECT_DESPAWN_ANIM, 8);
|
|
data << uint64(GetGUID());
|
|
SendMessageToSet(&data, true);
|
|
}
|
|
|
|
|
|
void GameObject::getFishLoot(Loot* fishloot, Player* loot_owner)
|
|
{
|
|
fishloot->clear();
|
|
|
|
uint32 zone, subzone;
|
|
uint32 defaultzone = 1;
|
|
GetZoneAndAreaId(zone, subzone);
|
|
|
|
// if subzone loot exist use it
|
|
fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, true);
|
|
if (fishloot->empty()) //use this becase if zone or subzone has set LOOT_MODE_JUNK_FISH,Even if no normal drop, fishloot->FillLoot return true. it wrong.
|
|
{
|
|
//subzone no result,use zone loot
|
|
fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true);
|
|
//use zone 1 as default, somewhere fishing got nothing,becase subzone and zone not set, like Off the coast of Storm Peaks.
|
|
if (fishloot->empty())
|
|
fishloot->FillLoot(defaultzone, LootTemplates_Fishing, loot_owner, true, true);
|
|
}
|
|
}
|
|
|
|
void GameObject::getFishLootJunk(Loot* fishloot, Player* loot_owner)
|
|
{
|
|
fishloot->clear();
|
|
|
|
uint32 zone, subzone;
|
|
uint32 defaultzone = 1;
|
|
GetZoneAndAreaId(zone, subzone);
|
|
|
|
// if subzone loot exist use it
|
|
fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
|
|
if (fishloot->empty()) //use this becase if zone or subzone has normal mask drop, then fishloot->FillLoot return true.
|
|
{
|
|
//use zone loot
|
|
fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
|
|
if (fishloot->empty())
|
|
//use zone 1 as default
|
|
fishloot->FillLoot(defaultzone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
|
|
}
|
|
}
|
|
|
|
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->spawnMask);
|
|
}
|
|
|
|
void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask)
|
|
{
|
|
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.spawnMask = spawnMask;
|
|
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->setUInt32(0, m_spawnId);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT);
|
|
stmt->setUInt32(index++, m_spawnId);
|
|
stmt->setUInt32(index++, GetEntry());
|
|
stmt->setUInt16(index++, uint16(mapid));
|
|
stmt->setUInt8(index++, spawnMask);
|
|
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()));
|
|
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: %u) 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(map->GenerateLowGuid<HighGuid::GameObject>(), entry, map, data->spawnPoint, data->rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode))
|
|
return false;
|
|
|
|
PhasingHandler::InitDbPhaseShift(GetPhaseShift(), data->phaseUseFlags, data->phaseId, data->phaseGroup);
|
|
PhasingHandler::InitDbVisibleMapId(GetPhaseShift(), data->terrainSwapMap);
|
|
|
|
if (data->spawntimesecs >= 0)
|
|
{
|
|
m_spawnedByDefault = true;
|
|
|
|
if (!GetGOInfo()->GetDespawnPossibility() && !GetGOInfo()->IsDespawnAtAction())
|
|
{
|
|
SetFlag(GAMEOBJECT_FLAGS, 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 %u (SpawnID %u) 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;
|
|
|
|
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 trans = CharacterDatabase.BeginTransaction();
|
|
|
|
sMapMgr->DoForAllMapsWithMapId(data->mapId,
|
|
[spawnId, trans](Map* map) -> void
|
|
{
|
|
// despawn all active objects, and remove their respawns
|
|
std::vector<GameObject*> 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, trans);
|
|
}
|
|
);
|
|
|
|
WorldDatabaseTransaction trans2 = WorldDatabase.BeginTransaction();
|
|
|
|
// delete data from memory
|
|
sObjectMgr->DeleteGameObjectData(spawnId);
|
|
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT);
|
|
stmt->setUInt32(0, spawnId);
|
|
trans2->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER);
|
|
stmt->setUInt8(0, uint8(SPAWN_TYPE_GAMEOBJECT));
|
|
stmt->setUInt32(1, spawnId);
|
|
trans2->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT);
|
|
stmt->setUInt32(0, spawnId);
|
|
trans2->Append(stmt);
|
|
|
|
WorldDatabase.CommitTransaction(trans2);
|
|
|
|
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_MO_TRANSPORT;
|
|
}
|
|
|
|
// is Dynamic transport = non-stop Transport
|
|
bool GameObject::IsDynTransport() 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_MO_TRANSPORT || gInfo->type == GAMEOBJECT_TYPE_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::IsNeverVisible() const
|
|
{
|
|
if (WorldObject::IsNeverVisible())
|
|
return true;
|
|
|
|
if (GetGOInfo()->GetServerOnly())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::IsAlwaysVisibleFor(WorldObject const* seer) const
|
|
{
|
|
if (WorldObject::IsAlwaysVisibleFor(seer))
|
|
return true;
|
|
|
|
if (!seer)
|
|
return false;
|
|
|
|
// Always seen by owner and friendly units
|
|
if (ObjectGuid guid = GetOwnerGUID())
|
|
{
|
|
if (seer->GetGUID() == guid)
|
|
return true;
|
|
|
|
Unit* owner = GetOwner();
|
|
if (Unit const* unitSeer = seer->ToUnit())
|
|
if (owner && owner->IsFriendlyTo(unitSeer))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GameObject::IsInvisibleDueToDespawn() const
|
|
{
|
|
if (WorldObject::IsInvisibleDueToDespawn())
|
|
return true;
|
|
|
|
// Despawned
|
|
if (!isSpawned())
|
|
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 (GetGOInfo()->trap.Unused != 0)
|
|
return GetGOInfo()->trap.Unused;
|
|
if (const Unit* 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);
|
|
}
|
|
|
|
if (m_respawnCompatibilityMode)
|
|
_triggerJustAppeared = true;
|
|
}
|
|
|
|
bool GameObject::ActivateToQuest(Player* target) const
|
|
{
|
|
if (target->HasQuestForGO(GetEntry()))
|
|
return true;
|
|
|
|
if (!sObjectMgr->IsGameObjectForQuests(GetEntry()))
|
|
return false;
|
|
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_QUESTGIVER:
|
|
{
|
|
GameObject* go = const_cast<GameObject*>(this);
|
|
QuestGiverStatus questStatus = target->GetQuestDialogStatus(go);
|
|
if (questStatus > DIALOG_STATUS_UNAVAILABLE)
|
|
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 (LootTemplates_Gameobject.HaveQuestLootForPlayer(GetGOInfo()->GetLootId(), target))
|
|
{
|
|
if (Battleground const* bg = target->GetBattleground())
|
|
return bg->CanActivateGO(GetEntry(), target->GetTeam());
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_GENERIC:
|
|
{
|
|
if (GetGOInfo()->_generic.questID == -1 || target->GetQuestStatus(GetGOInfo()->_generic.questID) == QUEST_STATUS_INCOMPLETE)
|
|
return true;
|
|
break;
|
|
}
|
|
case GAMEOBJECT_TYPE_GOOBER:
|
|
{
|
|
if (GetGOInfo()->goober.questID == -1 || target->GetQuestStatus(GetGOInfo()->goober.questID) == QUEST_STATUS_INCOMPLETE)
|
|
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);
|
|
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<Trinity::NearestGameObjectFishingHole> 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(GAMEOBJECT_FLAGS, 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::PlayAnimKit(int32 animKit)
|
|
{
|
|
WorldPacket data(SMSG_GAME_OBJECT_ACTIVATE_ANIM_KIT, 4 + 2);
|
|
|
|
ObjectGuid guid = GetGUID();
|
|
data.WriteBit(guid[5]);
|
|
data.WriteBit(guid[1]);
|
|
data.WriteBit(guid[0]);
|
|
data.WriteBit(guid[4]);
|
|
data.WriteBit(guid[7]);
|
|
data.WriteBit(guid[2]);
|
|
data.WriteBit(guid[3]);
|
|
data.WriteBit(guid[6]);
|
|
data.WriteByteSeq(guid[5]);
|
|
data.WriteByteSeq(guid[1]);
|
|
data.WriteByteSeq(guid[0]);
|
|
data.WriteByteSeq(guid[3]);
|
|
data.WriteByteSeq(guid[4]);
|
|
data.WriteByteSeq(guid[6]);
|
|
data.WriteByteSeq(guid[2]);
|
|
data.WriteByteSeq(guid[7]);
|
|
data << int32(animKit);
|
|
|
|
SendMessageToSet(&data, nullptr);
|
|
}
|
|
|
|
void GameObject::SetGoArtKit(uint8 kit)
|
|
{
|
|
SetByteValue(GAMEOBJECT_BYTES_1, 2, kit);
|
|
GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGameObjectData(m_spawnId));
|
|
if (data)
|
|
data->artKit = kit;
|
|
}
|
|
|
|
void GameObject::SetGoArtKit(uint8 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<GameObjectData*>(data)->artKit = artkit;
|
|
}
|
|
|
|
void GameObject::SwitchDoorOrButton(bool activate, bool alternative /* = false */)
|
|
{
|
|
if (activate)
|
|
SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
|
|
else
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
|
|
|
|
if (GetGoState() == GO_STATE_READY) //if closed -> open
|
|
SetGoState(alternative ? GO_STATE_ACTIVE_ALTERNATIVE : GO_STATE_ACTIVE);
|
|
else //if open -> close
|
|
SetGoState(GO_STATE_READY);
|
|
}
|
|
|
|
void GameObject::Use(Unit* user)
|
|
{
|
|
// by default spell caster is user
|
|
Unit* spellCaster = user;
|
|
uint32 spellId = 0;
|
|
bool triggered = false;
|
|
|
|
if (Player* playerUser = user->ToPlayer())
|
|
{
|
|
if (m_goInfo->CannotBeUsedUnderImmunity() && playerUser->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE))
|
|
return;
|
|
|
|
if (!m_goInfo->IsUsableMounted())
|
|
playerUser->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
|
|
|
playerUser->PlayerTalkClass->ClearMenus();
|
|
if (AI()->GossipHello(playerUser))
|
|
return;
|
|
}
|
|
|
|
// If cooldown data present in template
|
|
if (uint32 cooldown = GetGOInfo()->GetCooldown())
|
|
{
|
|
if (GameTime::GetGameTimeMS() < m_cooldownTime)
|
|
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_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 (!info)
|
|
return;
|
|
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
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
|
|
}
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
// 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 (ChairSlotAndUser::iterator itr = ChairListSlots.begin(); itr != ChairListSlots.end(); ++itr)
|
|
{
|
|
// the distance between this slot and the center of the go - imagine a 1D space
|
|
float relativeDistance = (info->size*itr->first)-(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 (itr->second)
|
|
{
|
|
if (Player* ChairUser = ObjectAccessor::GetPlayer(*this, itr->second))
|
|
{
|
|
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.
|
|
else
|
|
itr->second.Clear(); // This seat is unoccupied.
|
|
}
|
|
else
|
|
itr->second.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 = player->GetDistance2d(x_i, y_i);
|
|
|
|
if (thisDistance <= lowestDist)
|
|
{
|
|
nearest_slot = itr->first;
|
|
lowestDist = thisDistance;
|
|
x_lowest = x_i;
|
|
y_lowest = y_i;
|
|
}
|
|
}
|
|
|
|
if (found_free_slot)
|
|
{
|
|
ChairSlotAndUser::iterator itr = ChairListSlots.find(nearest_slot);
|
|
if (itr != ChairListSlots.end())
|
|
{
|
|
itr->second = player->GetGUID(); //this slot in now used by player
|
|
player->TeleportTo(GetMapId(), x_lowest, y_lowest, GetPositionZ(), GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET);
|
|
player->SetStandState(UNIT_STAND_STATE_SIT_LOW_CHAIR + info->chair.chairheight);
|
|
if (info->chair.triggeredEvent)
|
|
GameEvents::Trigger(info->chair.triggeredEvent, player, this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
//big gun, its a spell/aura
|
|
case GAMEOBJECT_TYPE_GOOBER: //10
|
|
{
|
|
GameObjectTemplate const* info = GetGOInfo();
|
|
|
|
if (Player* player = user->ToPlayer())
|
|
{
|
|
if (info->goober.pageID) // show page...
|
|
{
|
|
WorldPacket data(SMSG_GAMEOBJECT_PAGETEXT, 8);
|
|
data << GetGUID();
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
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 %u for GO entry %u (GUID %u).", 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->GetFirstMember(); itr != nullptr; itr = itr->next())
|
|
if (Player* member = itr->GetSource())
|
|
if (member->IsAtGroupRewardDistance(this))
|
|
member->KillCreditGO(info->entry, GetGUID());
|
|
}
|
|
else
|
|
player->KillCreditGO(info->entry, GetGUID());
|
|
}
|
|
|
|
if (uint32 trapEntry = info->goober.linkedTrap)
|
|
TriggeringLinkedGameObject(trapEntry, user);
|
|
|
|
SetFlag(GAMEOBJECT_FLAGS, 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
|
|
{
|
|
uint32 zone, subzone;
|
|
GetZoneAndAreaId(zone, subzone);
|
|
|
|
int32 zone_skill = sObjectMgr->GetFishingBaseSkillLevel(subzone);
|
|
if (!zone_skill)
|
|
zone_skill = sObjectMgr->GetFishingBaseSkillLevel(zone);
|
|
|
|
//provide error, no fishable zone or area should be 0
|
|
if (!zone_skill)
|
|
TC_LOG_ERROR("sql.sql", "Fishable areaId %u are not properly defined in `skill_fishing_base_level`.", subzone);
|
|
|
|
int32 skill = player->GetSkillValue(SKILL_FISHING);
|
|
|
|
int32 chance;
|
|
if (skill < zone_skill)
|
|
{
|
|
chance = int32(pow((double)skill/zone_skill, 2) * 100);
|
|
if (chance < 1)
|
|
chance = 1;
|
|
}
|
|
else
|
|
chance = 100;
|
|
|
|
int32 roll = irand(1, 100);
|
|
|
|
TC_LOG_DEBUG("misc", "Fishing check (skill: %i zone min skill: %i chance %i roll: %i", skill, zone_skill, chance, roll);
|
|
|
|
player->UpdateFishingSkill();
|
|
|
|
/// @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);
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
}
|
|
else
|
|
player->SendLoot(GetGUID(), LOOT_FISHING);
|
|
}
|
|
else // If fishing skill is too low, send junk loot.
|
|
player->SendLoot(GetGUID(), LOOT_FISHING_JUNK);
|
|
break;
|
|
}
|
|
case GO_JUST_DEACTIVATED: // nothing to do, will be deleted at next update
|
|
break;
|
|
default:
|
|
{
|
|
SetLootState(GO_JUST_DEACTIVATED);
|
|
|
|
WorldPacket data(SMSG_FISH_NOT_HOOKED, 0);
|
|
player->SendDirectMessage(&data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
player->FinishSpell(CURRENT_CHANNELED_SPELL);
|
|
return;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_SUMMONING_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)
|
|
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->summoningRitual.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->summoningRitual.castersGrouped && !player->IsInSameRaidWith(m_ritualOwner)))
|
|
return;
|
|
|
|
spellCaster = player;
|
|
}
|
|
|
|
AddUniqueUse(player);
|
|
|
|
if (info->summoningRitual.animSpell)
|
|
{
|
|
player->CastSpell(player, info->summoningRitual.animSpell, true);
|
|
|
|
// for this case, summoningRitual.spellId is always triggered
|
|
triggered = true;
|
|
}
|
|
|
|
// full amount unique participants including original summoner
|
|
if (GetUniqueUseCount() == info->summoningRitual.casters)
|
|
{
|
|
if (m_ritualOwner)
|
|
spellCaster = m_ritualOwner;
|
|
|
|
spellId = info->summoningRitual.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;
|
|
triggered = true;
|
|
}
|
|
|
|
// 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->summoningRitual.casterTargetSpell && info->summoningRitual.casterTargetSpell != 1) // No idea why this field is a bool in some cases
|
|
for (uint32 i = 0; i < info->summoningRitual.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->summoningRitual.casterTargetSpell, true);
|
|
|
|
// finish owners spell
|
|
if (owner)
|
|
owner->FinishSpell(CURRENT_CHANNELED_SPELL);
|
|
|
|
// can be deleted now, if
|
|
if (!info->summoningRitual.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
|
|
{
|
|
if (m_goInfo->spellcaster.partyOnly)
|
|
{
|
|
Unit const* caster = GetOwner();
|
|
if (!caster || !caster->IsPlayer() || !user->IsPlayer() || !user->ToPlayer()->IsInSameRaidWith(caster->ToPlayer()))
|
|
return;
|
|
}
|
|
|
|
if (!m_goInfo->spellcaster.allowMounted)
|
|
user->RemoveAurasByType(SPELL_AURA_MOUNTED);
|
|
|
|
spellId = m_goInfo->spellcaster.spell;
|
|
if (!sSpellMgr->GetSpellInfo(spellId))
|
|
{
|
|
TC_LOG_ERROR("misc", "SpellCaster gameobject (Entry: %u) attempted to cast a non-existant spell (ID: %u).", GetEntry(), spellId);
|
|
return;
|
|
}
|
|
|
|
if (Player* player = user->ToPlayer())
|
|
sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this);
|
|
|
|
// Only count successful spell casts as uses
|
|
WorldObject* caster = this;
|
|
if (m_goInfo->spellcaster.playerCast != 0 && user->IsPlayer())
|
|
caster = user;
|
|
|
|
if (caster->CastSpell(user, spellId) == SPELL_CAST_OK)
|
|
AddUse();
|
|
return;
|
|
}
|
|
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!
|
|
uint8 level = player->getLevel();
|
|
if (level < info->meetingstone.minLevel)
|
|
return;
|
|
level = targetPlayer->getLevel();
|
|
if (level < info->meetingstone.minLevel)
|
|
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))
|
|
{
|
|
// in battleground check
|
|
Battleground* bg = player->GetBattleground();
|
|
if (!bg)
|
|
return;
|
|
|
|
if (player->GetVehicle())
|
|
return;
|
|
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
|
|
player->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY);
|
|
// BG flag click
|
|
// AB:
|
|
// 15001
|
|
// 15002
|
|
// 15003
|
|
// 15004
|
|
// 15005
|
|
bg->EventPlayerClickedOnFlag(player, this);
|
|
return; //we don;t need to delete flag ... it is despawned!
|
|
}
|
|
break;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_FISHINGHOLE: // 25
|
|
{
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
player->SendLoot(GetGUID(), LOOT_FISHINGHOLE);
|
|
player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_FISH_IN_GAMEOBJECT, GetGOInfo()->entry);
|
|
return;
|
|
}
|
|
|
|
case GAMEOBJECT_TYPE_FLAGDROP: // 26
|
|
{
|
|
if (user->GetTypeId() != TYPEID_PLAYER)
|
|
return;
|
|
|
|
Player* player = user->ToPlayer();
|
|
|
|
if (player->CanUseBattlegroundObject(this))
|
|
{
|
|
// in battleground check
|
|
Battleground* bg = player->GetBattleground();
|
|
if (!bg)
|
|
return;
|
|
|
|
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)
|
|
{
|
|
switch (info->entry)
|
|
{
|
|
case 179785: // Silverwing Flag
|
|
case 179786: // Warsong Flag
|
|
if (bg->GetTypeID(true) == BATTLEGROUND_WS)
|
|
bg->EventPlayerClickedOnFlag(player, this);
|
|
break;
|
|
case 184142: // Netherstorm Flag
|
|
if (bg->GetTypeID(true) == BATTLEGROUND_EY)
|
|
bg->EventPlayerClickedOnFlag(player, this);
|
|
break;
|
|
}
|
|
|
|
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();
|
|
|
|
// 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);
|
|
|
|
WorldPacket data(SMSG_ENABLE_BARBER_SHOP, 0);
|
|
player->SendDirectMessage(&data);
|
|
|
|
player->SetStandState(UNIT_STAND_STATE_SIT_LOW_CHAIR+info->barberChair.chairheight);
|
|
return;
|
|
}
|
|
default:
|
|
if (GetGoType() >= MAX_GAMEOBJECT_TYPE)
|
|
TC_LOG_ERROR("misc", "GameObject::Use(): unit (type: %u, guid: %u, name: %s) tries to use object (guid: %u, entry: %u, name: %s) of unknown type (%u)",
|
|
user->GetTypeId(), user->GetGUID().GetCounter(), user->GetName().c_str(), GetGUID().GetCounter(), GetEntry(), GetGOInfo()->name.c_str(), GetGoType());
|
|
break;
|
|
}
|
|
|
|
if (!spellId)
|
|
return;
|
|
|
|
if (!sSpellMgr->GetSpellInfo(spellId))
|
|
{
|
|
if (user->GetTypeId() != TYPEID_PLAYER || !sOutdoorPvPMgr->HandleCustomSpell(user->ToPlayer(), spellId, this))
|
|
TC_LOG_ERROR("misc", "WORLD: unknown spell id %u at use action for gameobject (Entry: %u GoType: %u)", spellId, GetEntry(), GetGoType());
|
|
else
|
|
TC_LOG_DEBUG("outdoorpvp", "WORLD: %u 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, triggered);
|
|
else
|
|
CastSpell(user, spellId);
|
|
}
|
|
|
|
void GameObject::SendCustomAnim(uint32 anim)
|
|
{
|
|
WorldPacket data(SMSG_GAMEOBJECT_CUSTOM_ANIM, 8+4);
|
|
data << GetGUID();
|
|
data << uint32(anim);
|
|
SendMessageToSet(&data, 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;
|
|
}
|
|
|
|
// overwrite WorldObject function for proper name localization
|
|
std::string const & GameObject::GetNameForLocaleIdx(LocaleConstant loc_idx) const
|
|
{
|
|
if (loc_idx != DEFAULT_LOCALE)
|
|
{
|
|
uint8 uloc_idx = uint8(loc_idx);
|
|
if (GameObjectLocale const* cl = sObjectMgr->GetGameObjectLocale(GetEntry()))
|
|
if (cl->Name.size() > uloc_idx && !cl->Name[uloc_idx].empty())
|
|
return cl->Name[uloc_idx];
|
|
}
|
|
|
|
return GetName();
|
|
}
|
|
|
|
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)
|
|
{
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 0, rotation.x);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 1, rotation.y);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 2, rotation.z);
|
|
SetFloatValue(GAMEOBJECT_PARENTROTATION + 3, rotation.w);
|
|
}
|
|
|
|
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<Transport*>(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.MaxHealth || !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.MaxHealth))
|
|
m_goValue.Building.Health = m_goValue.Building.MaxHealth;
|
|
else
|
|
m_goValue.Building.Health += change;
|
|
|
|
// Set the health bar, value = 255 * healthPct;
|
|
SetGoAnimProgress(m_goValue.Building.Health * 255 / m_goValue.Building.MaxHealth);
|
|
|
|
// dealing damage, send packet
|
|
if (Player* player = attackerOrHealer ? attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself() : nullptr)
|
|
{
|
|
WorldPacket data(SMSG_DESTRUCTIBLE_BUILDING_DAMAGE, 8 + 8 + 8 + 4 + 4);
|
|
data << GetPackGUID();
|
|
data << attackerOrHealer->GetPackGUID();
|
|
data << player->GetPackGUID();
|
|
data << uint32(-change); // change < 0 triggers SPELL_BUILDING_HEAL combat log event
|
|
// change >= 0 triggers SPELL_BUILDING_DAMAGE event
|
|
data << uint32(spellId);
|
|
player->SendDirectMessage(&data);
|
|
}
|
|
|
|
if (change < 0 && GetGOInfo()->building.damagedEvent)
|
|
GameEvents::Trigger(GetGOInfo()->building.damagedEvent, attackerOrHealer, this);
|
|
|
|
|
|
GameObjectDestructibleState newState = GetDestructibleState();
|
|
|
|
if (!m_goValue.Building.Health)
|
|
newState = GO_DESTRUCTIBLE_DESTROYED;
|
|
else if (m_goValue.Building.Health <= GetGOInfo()->building.damagedNumHits)
|
|
newState = GO_DESTRUCTIBLE_DAMAGED;
|
|
else if (m_goValue.Building.Health == m_goValue.Building.MaxHealth)
|
|
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(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED | GO_FLAG_DESTROYED);
|
|
SetDisplayId(m_goInfo->displayId);
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = m_goValue.Building.MaxHealth;
|
|
SetGoAnimProgress(255);
|
|
}
|
|
EnableCollision(true);
|
|
break;
|
|
case GO_DESTRUCTIBLE_DAMAGED:
|
|
{
|
|
if (GetGOInfo()->building.damagedEvent)
|
|
GameEvents::Trigger(GetGOInfo()->building.damagedEvent, attackerOrHealer, this);
|
|
AI()->Damaged(attackerOrHealer, m_goInfo->building.damageEvent);
|
|
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DESTROYED);
|
|
SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED);
|
|
|
|
uint32 modelId = m_goInfo->displayId;
|
|
if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->building.destructibleData))
|
|
if (modelData->State0WMO)
|
|
modelId = modelData->State0WMO;
|
|
SetDisplayId(modelId);
|
|
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = m_goInfo->building.damagedNumHits;
|
|
uint32 maxHealth = m_goValue.Building.MaxHealth;
|
|
// 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()->building.destroyedEvent)
|
|
GameEvents::Trigger(GetGOInfo()->building.destroyedEvent, attackerOrHealer, this);
|
|
AI()->Destroyed(attackerOrHealer, m_goInfo->building.destroyedEvent);
|
|
|
|
if (attackerOrHealer && attackerOrHealer->GetTypeId() == TYPEID_PLAYER)
|
|
if (Battleground* bg = attackerOrHealer->ToPlayer()->GetBattleground())
|
|
bg->DestroyGate(attackerOrHealer->ToPlayer(), this);
|
|
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED);
|
|
SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_DESTROYED);
|
|
|
|
uint32 modelId = m_goInfo->displayId;
|
|
if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->building.destructibleData))
|
|
if (modelData->State1WMO)
|
|
modelId = modelData->State1WMO;
|
|
SetDisplayId(modelId);
|
|
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = 0;
|
|
SetGoAnimProgress(0);
|
|
}
|
|
EnableCollision(false);
|
|
break;
|
|
}
|
|
case GO_DESTRUCTIBLE_REBUILDING:
|
|
{
|
|
if (GetGOInfo()->building.rebuildingEvent)
|
|
GameEvents::Trigger(GetGOInfo()->building.rebuildingEvent, attackerOrHealer, this);
|
|
RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED | GO_FLAG_DESTROYED);
|
|
|
|
uint32 modelId = m_goInfo->displayId;
|
|
if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->building.destructibleData))
|
|
if (modelData->State2WMO)
|
|
modelId = modelData->State2WMO;
|
|
SetDisplayId(modelId);
|
|
|
|
// restores to full health
|
|
if (setHealth)
|
|
{
|
|
m_goValue.Building.Health = m_goValue.Building.MaxHealth;
|
|
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_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::SetLootGenerationTime()
|
|
{
|
|
m_lootGenerationTime = GameTime::GetGameTime();
|
|
}
|
|
|
|
void GameObject::SetGoState(GOState state)
|
|
{
|
|
GOState oldState = GetGoState();
|
|
SetByteValue(GAMEOBJECT_BYTES_1, 0, 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);
|
|
}
|
|
}
|
|
|
|
void GameObject::SetDisplayId(uint32 displayid)
|
|
{
|
|
SetUInt32Value(GAMEOBJECT_DISPLAYID, displayid);
|
|
UpdateModel();
|
|
}
|
|
|
|
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);
|
|
delete m_model;
|
|
m_model = CreateModel();
|
|
if (m_model)
|
|
GetMap()->InsertGameObjectModel(*m_model);
|
|
}
|
|
|
|
Player* GameObject::GetLootRecipient() const
|
|
{
|
|
if (!m_lootRecipient)
|
|
return nullptr;
|
|
return ObjectAccessor::FindConnectedPlayer(m_lootRecipient);
|
|
}
|
|
|
|
Group* GameObject::GetLootRecipientGroup() const
|
|
{
|
|
if (!m_lootRecipientGroup)
|
|
return nullptr;
|
|
return sGroupMgr->GetGroupByGUID(m_lootRecipientGroup);
|
|
}
|
|
|
|
void GameObject::SetLootRecipient(Unit* unit, Group* group)
|
|
{
|
|
// set the player whose group should receive the right
|
|
// to loot the creature after it dies
|
|
// should be set to nullptr after the loot disappears
|
|
|
|
if (!unit)
|
|
{
|
|
m_lootRecipient.Clear();
|
|
m_lootRecipientGroup = group ? group->GetLowGUID() : 0;
|
|
return;
|
|
}
|
|
|
|
if (unit->GetTypeId() != TYPEID_PLAYER && !unit->IsVehicle())
|
|
return;
|
|
|
|
Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
if (!player) // normal creature, no player involved
|
|
return;
|
|
|
|
m_lootRecipient = player->GetGUID();
|
|
|
|
// either get the group from the passed parameter or from unit's one
|
|
if (group)
|
|
m_lootRecipientGroup = group->GetLowGUID();
|
|
else if (Group* unitGroup = player->GetGroup())
|
|
m_lootRecipientGroup = unitGroup->GetLowGUID();
|
|
}
|
|
|
|
bool GameObject::IsLootAllowedFor(Player const* player) const
|
|
{
|
|
if (!m_lootRecipient && !m_lootRecipientGroup)
|
|
return true;
|
|
|
|
if (player->GetGUID() == m_lootRecipient)
|
|
return true;
|
|
|
|
Group const* playerGroup = player->GetGroup();
|
|
if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient
|
|
return false; // if go doesnt have group bound it means it was solo killed by someone else
|
|
|
|
return true;
|
|
}
|
|
|
|
GameObject* GameObject::GetLinkedTrap()
|
|
{
|
|
return ObjectAccessor::GetGameObject(*this, m_linkedTrap);
|
|
}
|
|
|
|
void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const
|
|
{
|
|
if (!target)
|
|
return;
|
|
|
|
bool forcedFlags = GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.usegrouplootrules && HasLootRecipient();
|
|
bool targetIsGM = target->IsGameMaster();
|
|
|
|
ByteBuffer fieldBuffer;
|
|
|
|
UpdateMaskPacketBuilder updateMask(m_valuesCount);
|
|
|
|
uint32* flags = GameObjectUpdateFieldFlags;
|
|
uint32 visibleFlag = UF_FLAG_PUBLIC;
|
|
if (GetOwnerGUID() == target->GetGUID())
|
|
visibleFlag |= UF_FLAG_OWNER;
|
|
|
|
for (uint16 index = 0; index < m_valuesCount; ++index)
|
|
{
|
|
if (_fieldNotifyFlags & flags[index] ||
|
|
((updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(index) : m_uint32Values[index]) && (flags[index] & visibleFlag)) ||
|
|
(index == GAMEOBJECT_FLAGS && forcedFlags))
|
|
{
|
|
updateMask.SetBit(index);
|
|
|
|
if (index == GAMEOBJECT_DYNAMIC)
|
|
{
|
|
uint32 dynamicFlags = m_uint32Values[GAMEOBJECT_DYNAMIC];
|
|
|
|
uint16 dynFlags = 0;
|
|
uint16 pathProgress = 0xFFFF;
|
|
switch (GetGoType())
|
|
{
|
|
case GAMEOBJECT_TYPE_QUESTGIVER:
|
|
if (ActivateToQuest(target))
|
|
dynFlags |= GO_DYNFLAG_LO_ACTIVATE;
|
|
break;
|
|
case GAMEOBJECT_TYPE_CHEST:
|
|
case GAMEOBJECT_TYPE_GOOBER:
|
|
if (ActivateToQuest(target))
|
|
dynFlags |= GO_DYNFLAG_LO_ACTIVATE | GO_DYNFLAG_LO_SPARKLE;
|
|
else if (targetIsGM)
|
|
dynFlags |= GO_DYNFLAG_LO_ACTIVATE;
|
|
break;
|
|
case GAMEOBJECT_TYPE_GENERIC:
|
|
if (ActivateToQuest(target))
|
|
dynFlags |= GO_DYNFLAG_LO_SPARKLE;
|
|
break;
|
|
case GAMEOBJECT_TYPE_TRANSPORT:
|
|
case GAMEOBJECT_TYPE_MO_TRANSPORT:
|
|
{
|
|
dynFlags = dynamicFlags & 0xFFFF;
|
|
pathProgress = dynamicFlags >> 16;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
fieldBuffer << ((uint32(pathProgress) << 16) | uint32(dynFlags));
|
|
}
|
|
else if (index == GAMEOBJECT_FLAGS)
|
|
{
|
|
uint32 goFlags = m_uint32Values[GAMEOBJECT_FLAGS];
|
|
if (GetGoType() == GAMEOBJECT_TYPE_CHEST)
|
|
if (GetGOInfo()->chest.usegrouplootrules && !IsLootAllowedFor(target))
|
|
goFlags |= GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE;
|
|
|
|
fieldBuffer << goFlags;
|
|
}
|
|
else
|
|
fieldBuffer << m_uint32Values[index]; // other cases
|
|
}
|
|
}
|
|
|
|
updateMask.AppendToPacket(data);
|
|
data->append(fieldBuffer);
|
|
}
|
|
|
|
std::vector<uint32> const* GameObject::GetPauseTimes() const
|
|
{
|
|
if (GameObjectType::Transport const* transport = dynamic_cast<GameObjectType::Transport const*>(m_goTypeImpl.get()))
|
|
return transport->GetPauseTimes();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void GameObject::SetPathProgressForClient(float progress)
|
|
{
|
|
bool marked = _changesMask.GetBit(GAMEOBJECT_DYNAMIC);
|
|
|
|
uint32 dynamicFlags = GetUInt32Value(GAMEOBJECT_DYNAMIC);
|
|
dynamicFlags &= 0xFFFF; // remove high bits
|
|
dynamicFlags |= uint32(progress * 65535.0f) << 16;
|
|
UpdateUInt32Value(GAMEOBJECT_DYNAMIC, dynamicFlags);
|
|
|
|
if (!marked)
|
|
_changesMask.UnsetBit(GAMEOBJECT_DYNAMIC);
|
|
}
|
|
|
|
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<GameObjectType::Transport const*>(m_goTypeImpl.get());
|
|
case GAMEOBJECT_TYPE_MO_TRANSPORT:
|
|
return static_cast<Transport const*>(this);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void GameObject::AfterRelocation()
|
|
{
|
|
UpdateModelPosition();
|
|
UpdatePositionData();
|
|
if (m_goTypeImpl)
|
|
m_goTypeImpl->OnRelocated();
|
|
|
|
UpdateObjectVisibility(false);
|
|
}
|
|
|
|
float GameObject::GetInteractionDistance() const
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
class GameObjectModelOwnerImpl : public GameObjectModelOwnerBase
|
|
{
|
|
public:
|
|
explicit GameObjectModelOwnerImpl(GameObject* owner) : _owner(owner) { }
|
|
|
|
virtual bool IsSpawned() const override { return _owner->isSpawned(); }
|
|
virtual uint32 GetDisplayId() const override { return _owner->GetDisplayId(); }
|
|
virtual bool IsInPhase(PhaseShift const& phaseShift) const override { return _owner->GetPhaseShift().CanSee(phaseShift); }
|
|
virtual 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); }
|
|
virtual float GetScale() const override { return _owner->GetObjectScale(); }
|
|
virtual void DebugVisualizeCorner(G3D::Vector3 const& corner) const override { const_cast<GameObject*>(_owner)->SummonCreature(1, corner.x, corner.y, corner.z, 0, TEMPSUMMON_MANUAL_DESPAWN); }
|
|
|
|
private:
|
|
GameObject* _owner;
|
|
};
|
|
|
|
void GameObject::HandleCustomTypeCommand(GameObjectTypeBase::CustomCommand const& command) const
|
|
{
|
|
if (m_goTypeImpl)
|
|
command.Execute(*m_goTypeImpl);
|
|
}
|
|
|
|
GameObjectModel* GameObject::CreateModel()
|
|
{
|
|
return GameObjectModel::Create(Trinity::make_unique<GameObjectModelOwnerImpl>(this), sWorld->GetDataPath());
|
|
}
|
|
|
|
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) && IsInPhase(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]))
|
|
return spell;
|
|
|
|
if (lock->Type[i] != LOCK_KEY_SKILL)
|
|
break;
|
|
|
|
for (auto&& playerSpell : player->GetSpellMap())
|
|
if (SpellInfo const* spell = sSpellMgr->GetSpellInfo(playerSpell.first))
|
|
for (auto&& effect : spell->Effects)
|
|
if (effect.Effect == SPELL_EFFECT_OPEN_LOCK && ((uint32) effect.MiscValue) == lock->Index[i])
|
|
if (effect.CalcValue(player) >= int32(lock->Skill[i]))
|
|
return spell;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|