aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Entities/GameObject
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2022-05-25 22:14:32 +0200
committerShauren <shauren.trinity@gmail.com>2022-05-25 22:14:32 +0200
commit630b60eb0dcd3d9ce41582664ab822b049365431 (patch)
treeb5d60fe41ca49846524ddbc7f9409194b76e7849 /src/server/game/Entities/GameObject
parent3031fbb63bfb2df2aa041bd9c04c5c42b0c07c05 (diff)
Core/GameObjects: Transport (type 11) improvements
* Fully synchronize serverside animation progress with client * Implemented updating passenger positions on elevators * Removed visibility hack for elevators that always forced CreateObject packet to be sent to client
Diffstat (limited to 'src/server/game/Entities/GameObject')
-rw-r--r--src/server/game/Entities/GameObject/GameObject.cpp560
-rw-r--r--src/server/game/Entities/GameObject/GameObject.h63
-rw-r--r--src/server/game/Entities/GameObject/GameObjectData.h1
3 files changed, 497 insertions, 127 deletions
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp
index 16fd48efa0f..0122d0560b0 100644
--- a/src/server/game/Entities/GameObject/GameObject.cpp
+++ b/src/server/game/Entities/GameObject/GameObject.cpp
@@ -25,6 +25,7 @@
#include "CreatureAISelector.h"
#include "DatabaseEnv.h"
#include "DB2Stores.h"
+#include "G3DPosition.hpp"
#include "GameEventSender.h"
#include "GameObjectAI.h"
#include "GameObjectModel.h"
@@ -116,6 +117,371 @@ QuaternionData QuaternionData::fromEulerAnglesZYX(float Z, float Y, float X)
return QuaternionData(quat.x, quat.y, quat.z, quat.w);
}
+GameObjectTypeBase::CustomCommand::~CustomCommand() = default;
+
+namespace GameObjectType
+{
+//11 GAMEOBJECT_TYPE_TRANSPORT
+class Transport : public GameObjectTypeBase, public TransportBase
+{
+public:
+ static constexpr Milliseconds PositionUpdateInterval = 50ms;
+
+ explicit Transport(GameObject& owner) : GameObjectTypeBase(owner), _animationInfo(sTransportMgr->GetTransportAnimInfo(owner.GetGOInfo()->entry)),
+ _pathProgress(GameTime::GetGameTimeMS() % GetTransportPeriod()), _stateChangeTime(GameTime::GetGameTimeMS()), _stateChangeProgress(_pathProgress),
+ _autoCycleBetweenStopFrames(false)
+ {
+ GameObjectTemplate const* goInfo = _owner.GetGOInfo();
+ if (goInfo->transport.Timeto2ndfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto2ndfloor);
+ if (goInfo->transport.Timeto3rdfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto3rdfloor);
+ if (goInfo->transport.Timeto4thfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto4thfloor);
+ if (goInfo->transport.Timeto5thfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto5thfloor);
+ if (goInfo->transport.Timeto6thfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto6thfloor);
+ if (goInfo->transport.Timeto7thfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto7thfloor);
+ if (goInfo->transport.Timeto8thfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto8thfloor);
+ if (goInfo->transport.Timeto9thfloor > 0)
+ {
+ _stopFrames.push_back(goInfo->transport.Timeto9thfloor);
+ if (goInfo->transport.Timeto10thfloor > 0)
+ _stopFrames.push_back(goInfo->transport.Timeto10thfloor);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ _positionUpdateTimer.Reset(PositionUpdateInterval);
+ }
+
+ void Update(uint32 diff) override
+ {
+ if (!_animationInfo)
+ return;
+
+ _positionUpdateTimer.Update(diff);
+ if (!_positionUpdateTimer.Passed())
+ return;
+
+ _positionUpdateTimer.Reset(PositionUpdateInterval);
+
+ uint32 now = GameTime::GetGameTimeMS();
+ uint32 period = GetTransportPeriod();
+ uint32 newProgress = 0;
+ if (_stopFrames.empty())
+ newProgress = now % period;
+ else
+ {
+ int32 stopTargetTime = 0;
+ if (_owner.GetGoState() == GO_STATE_TRANSPORT_ACTIVE)
+ stopTargetTime = 0;
+ else
+ stopTargetTime = _stopFrames[_owner.GetGoState() - GO_STATE_TRANSPORT_STOPPED];
+
+ if (now < uint32(*_owner.m_gameObjectData->Level))
+ {
+ int32 timeToStop = _owner.m_gameObjectData->Level - _stateChangeTime;
+ float stopSourcePathPct = float(_stateChangeProgress) / float(period);
+ float stopTargetPathPct = float(stopTargetTime) / float(period);
+ float timeSinceStopProgressPct = float(now - _stateChangeTime) / float(timeToStop);
+
+ float progressPct;
+ if (!_owner.HasDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT))
+ {
+ if (_owner.GetGoState() == GO_STATE_TRANSPORT_ACTIVE)
+ stopTargetPathPct = 1.0f;
+
+ float pathPctBetweenStops = stopTargetPathPct - stopSourcePathPct;
+ if (pathPctBetweenStops < 0.0f)
+ pathPctBetweenStops += 1.0f;
+
+ progressPct = pathPctBetweenStops * timeSinceStopProgressPct + stopSourcePathPct;
+ if (progressPct > 1.0f)
+ progressPct = progressPct - 1.0f;
+ }
+ else
+ {
+ float pathPctBetweenStops = stopSourcePathPct - stopTargetPathPct;
+ if (pathPctBetweenStops < 0.0f)
+ pathPctBetweenStops += 1.0f;
+
+ progressPct = stopSourcePathPct - pathPctBetweenStops * timeSinceStopProgressPct;
+ if (progressPct < 0.0f)
+ progressPct += 1.0f;
+ }
+
+ newProgress = uint32(float(period) * progressPct) % period;
+ }
+ else
+ newProgress = stopTargetTime;
+
+ if (int32(newProgress) == stopTargetTime && newProgress != _pathProgress)
+ {
+ uint32 eventId = [&]()
+ {
+ switch (_owner.GetGoState() - GO_STATE_TRANSPORT_ACTIVE)
+ {
+ case 0:
+ return _owner.GetGOInfo()->transport.Reached1stfloor;
+ case 1:
+ return _owner.GetGOInfo()->transport.Reached2ndfloor;
+ case 2:
+ return _owner.GetGOInfo()->transport.Reached3rdfloor;
+ case 3:
+ return _owner.GetGOInfo()->transport.Reached4thfloor;
+ case 4:
+ return _owner.GetGOInfo()->transport.Reached5thfloor;
+ case 5:
+ return _owner.GetGOInfo()->transport.Reached6thfloor;
+ case 6:
+ return _owner.GetGOInfo()->transport.Reached7thfloor;
+ case 7:
+ return _owner.GetGOInfo()->transport.Reached8thfloor;
+ case 8:
+ return _owner.GetGOInfo()->transport.Reached9thfloor;
+ case 9:
+ return _owner.GetGOInfo()->transport.Reached10thfloor;
+ default:
+ return 0u;
+ }
+ }();
+ if (eventId)
+ GameEvents::Trigger(eventId, &_owner, 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 == _stopFrames.size())
+ newState = GOState(currentState - 1);
+ else if (_owner.HasDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT))
+ newState = GOState(currentState - 1);
+ else
+ newState = GOState(currentState + 1);
+
+ _owner.SetGoState(newState);
+ }
+ }
+ }
+
+ if (_pathProgress == newProgress)
+ return;
+
+ _pathProgress = newProgress;
+
+ TransportAnimationEntry const* oldAnimation = _animationInfo->GetPrevAnimNode(newProgress);
+ TransportAnimationEntry const* newAnimation = _animationInfo->GetNextAnimNode(newProgress);
+ if (oldAnimation && newAnimation)
+ {
+ G3D::Matrix3 pathRotation = G3D::Quat(_owner.m_gameObjectData->ParentRotation->x, _owner.m_gameObjectData->ParentRotation->y,
+ _owner.m_gameObjectData->ParentRotation->z, _owner.m_gameObjectData->ParentRotation->w).toRotationMatrix();
+
+ G3D::Vector3 prev(oldAnimation->Pos.X, oldAnimation->Pos.Y, oldAnimation->Pos.Z);
+ G3D::Vector3 next(newAnimation->Pos.X, newAnimation->Pos.Y, newAnimation->Pos.Z);
+
+ float animProgress = float(newProgress - oldAnimation->TimeIndex) / float(newAnimation->TimeIndex - oldAnimation->TimeIndex);
+
+ G3D::Vector3 dst = prev.lerp(next, animProgress) * pathRotation;
+
+ dst += PositionToVector3(&_owner.GetStationaryPosition());
+
+ _owner.GetMap()->GameObjectRelocation(&_owner, dst.x, dst.y, dst.z, _owner.GetOrientation());
+ }
+
+ TransportRotationEntry const* oldRotation = _animationInfo->GetPrevAnimRotation(newProgress);
+ TransportRotationEntry const* newRotation = _animationInfo->GetNextAnimRotation(newProgress);
+ if (oldRotation && newRotation)
+ {
+ G3D::Quat prev(oldRotation->Rot[0], oldRotation->Rot[1], oldRotation->Rot[2], oldRotation->Rot[3]);
+ G3D::Quat next(newRotation->Rot[0], newRotation->Rot[1], newRotation->Rot[2], newRotation->Rot[3]);
+
+ float animProgress = float(newProgress - oldRotation->TimeIndex) / float(newRotation->TimeIndex - oldRotation->TimeIndex);
+
+ G3D::Quat rotation = prev.slerp(next, animProgress);
+
+ _owner.SetLocalRotation(rotation.x, rotation.y, rotation.z, rotation.w);
+ _owner.UpdateModelPosition();
+ }
+
+ // update progress marker for client
+ _owner.SetPathProgressForClient(float(_pathProgress) / float(period));
+ }
+
+ void OnStateChanged(GOState oldState, GOState newState) override
+ {
+ ASSERT(newState >= GO_STATE_TRANSPORT_ACTIVE);
+
+ // transports without stop frames just keep animating in state 24
+ if (_stopFrames.empty())
+ {
+ if (newState != GO_STATE_TRANSPORT_ACTIVE)
+ _owner.SetGoState(GO_STATE_TRANSPORT_ACTIVE);
+ return;
+ }
+
+ int32 stopPathProgress = 0;
+
+ if (newState != GO_STATE_TRANSPORT_ACTIVE)
+ {
+ ASSERT(newState < GOState(GO_STATE_TRANSPORT_STOPPED + MAX_GO_STATE_TRANSPORT_STOP_FRAMES));
+ uint32 stopFrame = newState - GO_STATE_TRANSPORT_STOPPED;
+ ASSERT(stopFrame < _stopFrames.size());
+ stopPathProgress = _stopFrames[stopFrame];
+ }
+
+ _stateChangeTime = GameTime::GetGameTimeMS();
+ _stateChangeProgress = _pathProgress;
+ uint32 timeToStop = std::abs(int32(_pathProgress) - stopPathProgress);
+ _owner.SetLevel(GameTime::GetGameTimeMS() + timeToStop);
+ _owner.SetPathProgressForClient(float(_pathProgress) / float(GetTransportPeriod()));
+
+ if (oldState == GO_STATE_ACTIVE || oldState == newState)
+ {
+ // initialization
+ if (int32(_pathProgress) > stopPathProgress)
+ _owner.SetDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT);
+ else
+ _owner.RemoveDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT);
+
+ return;
+ }
+
+ int32 pauseTimesCount = _stopFrames.size();
+ int32 newToOldStateDelta = newState - oldState;
+ if (newToOldStateDelta < 0)
+ newToOldStateDelta += pauseTimesCount + 1;
+
+ int32 oldToNewStateDelta = oldState - newState;
+ if (oldToNewStateDelta < 0)
+ oldToNewStateDelta += pauseTimesCount + 1;
+
+ if (oldToNewStateDelta < newToOldStateDelta)
+ _owner.SetDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT);
+ else
+ _owner.RemoveDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT);
+ }
+
+ void OnRelocated() override
+ {
+ UpdatePassengerPositions();
+ }
+
+ void UpdatePassengerPositions()
+ {
+ for (WorldObject* passenger : _passengers)
+ {
+ float x, y, z, o;
+ passenger->m_movementInfo.transport.pos.GetPosition(x, y, z, o);
+ CalculatePassengerPosition(x, y, z, &o);
+ UpdatePassengerPosition(_owner.GetMap(), passenger, x, y, z, o, true);
+ }
+ }
+
+ uint32 GetTransportPeriod() const
+ {
+ if (_animationInfo)
+ return _animationInfo->TotalTime;
+
+ return 1;
+ }
+
+ std::vector<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;
+ TimeTracker _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);
+}
+}
+
GameObject::GameObject() : WorldObject(false), MapObject(),
m_model(nullptr), m_goValue(), m_AI(nullptr), m_respawnCompatibilityMode(false), _animKitId(0), _worldEffectID(0)
{
@@ -154,10 +520,6 @@ GameObject::~GameObject()
{
delete m_AI;
delete m_model;
- if (m_goInfo && m_goInfo->type == GAMEOBJECT_TYPE_TRANSPORT)
- delete m_goValue.Transport.StopFrames;
- //if (m_uint32Values) // field array can be not exist if GameOBject not loaded
- // CleanupsBeforeDelete();
}
void GameObject::AIM_Destroy()
@@ -388,37 +750,14 @@ bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionD
}
case GAMEOBJECT_TYPE_TRANSPORT:
{
- m_goValue.Transport.AnimationInfo = sTransportMgr->GetTransportAnimInfo(goInfo->entry);
- m_goValue.Transport.PathProgress = getMSTime();
- if (m_goValue.Transport.AnimationInfo)
- m_goValue.Transport.PathProgress -= m_goValue.Transport.PathProgress % GetTransportPeriod(); // align to period
- m_goValue.Transport.CurrentSeg = 0;
- m_goValue.Transport.StateUpdateTimer = 0;
- m_goValue.Transport.StopFrames = new std::vector<uint32>();
- if (goInfo->transport.Timeto2ndfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto2ndfloor);
- if (goInfo->transport.Timeto3rdfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto3rdfloor);
- if (goInfo->transport.Timeto4thfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto4thfloor);
- if (goInfo->transport.Timeto5thfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto5thfloor);
- if (goInfo->transport.Timeto6thfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto6thfloor);
- if (goInfo->transport.Timeto7thfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto7thfloor);
- if (goInfo->transport.Timeto8thfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto8thfloor);
- if (goInfo->transport.Timeto9thfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto9thfloor);
- if (goInfo->transport.Timeto10thfloor > 0)
- m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto10thfloor);
+ m_goTypeImpl = std::make_unique<GameObjectType::Transport>(*this);
if (goInfo->transport.startOpen)
- SetTransportState(GO_STATE_TRANSPORT_STOPPED, goInfo->transport.startOpen - 1);
+ SetGoState(GO_STATE_TRANSPORT_STOPPED);
else
- SetTransportState(GO_STATE_TRANSPORT_ACTIVE);
+ SetGoState(GO_STATE_TRANSPORT_ACTIVE);
SetGoAnimProgress(animProgress);
+ setActive(true);
break;
}
case GAMEOBJECT_TYPE_FISHINGNODE:
@@ -555,6 +894,9 @@ void GameObject::Update(uint32 diff)
}
}
+ if (m_goTypeImpl)
+ m_goTypeImpl->Update(diff);
+
switch (m_lootState)
{
case GO_NOT_READY:
@@ -576,53 +918,6 @@ void GameObject::Update(uint32 diff)
SetLootState(GO_READY);
break;
}
- case GAMEOBJECT_TYPE_TRANSPORT:
- {
- if (!m_goValue.Transport.AnimationInfo)
- break;
-
- if (GetGoState() == GO_STATE_TRANSPORT_ACTIVE)
- {
- m_goValue.Transport.PathProgress += diff;
- /* TODO: Fix movement in unloaded grid - currently GO will just disappear
- uint32 timer = m_goValue.Transport.PathProgress % GetTransportPeriod();
- TransportAnimationEntry const* node = m_goValue.Transport.AnimationInfo->GetAnimNode(timer);
- if (node && m_goValue.Transport.CurrentSeg != node->TimeSeg)
- {
- m_goValue.Transport.CurrentSeg = node->TimeSeg;
-
- G3D::Quat rotation;
- if (TransportRotationEntry const* rot = m_goValue.Transport.AnimationInfo->GetAnimRotation(timer))
- rotation = G3D::Quat(rot->X, rot->Y, rot->Z, rot->W);
-
- G3D::Vector3 pos = rotation.toRotationMatrix()
- * G3D::Matrix3::fromEulerAnglesZYX(GetOrientation(), 0.0f, 0.0f)
- * G3D::Vector3(node->X, node->Y, node->Z);
-
- pos += G3D::Vector3(GetStationaryX(), GetStationaryY(), GetStationaryZ());
-
- G3D::Vector3 src(GetPositionX(), GetPositionY(), GetPositionZ());
-
- TC_LOG_DEBUG("misc", "Src: %s Dest: %s", src.toString().c_str(), pos.toString().c_str());
-
- GetMap()->GameObjectRelocation(this, pos.x, pos.y, pos.z, GetOrientation());
- }
- */
-
- if (!m_goValue.Transport.StopFrames->empty())
- {
- uint32 visualStateBefore = (m_goValue.Transport.StateUpdateTimer / 20000) & 1;
- m_goValue.Transport.StateUpdateTimer += diff;
- uint32 visualStateAfter = (m_goValue.Transport.StateUpdateTimer / 20000) & 1;
- if (visualStateBefore != visualStateAfter)
- {
- ForceUpdateFieldChange(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::Level));
- ForceUpdateFieldChange(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::State));
- }
- }
- }
- break;
- }
case GAMEOBJECT_TYPE_FISHINGNODE:
{
// fishing code (bobber ready)
@@ -1164,7 +1459,12 @@ void GameObject::SaveToDB()
return;
}
- SaveToDB(GetMapId(), data->spawnDifficulties);
+ uint32 mapId = GetMapId();
+ if (TransportBase* transport = GetTransport())
+ if (transport->GetMapIdForSpawning() >= 0)
+ mapId = transport->GetMapIdForSpawning();
+
+ SaveToDB(mapId, data->spawnDifficulties);
}
void GameObject::SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDifficulties)
@@ -1412,7 +1712,7 @@ bool GameObject::IsDynTransport() const
if (!gInfo)
return false;
- return gInfo->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT || (gInfo->type == GAMEOBJECT_TYPE_TRANSPORT && m_goValue.Transport.StopFrames->empty());
+ return gInfo->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT || gInfo->type == GAMEOBJECT_TYPE_TRANSPORT;
}
bool GameObject::IsDestructibleBuilding() const
@@ -1734,8 +2034,9 @@ void GameObject::ActivateObject(GameObjectActions action, int32 param, WorldObje
case GameObjectActions::GoTo8thFloor:
case GameObjectActions::GoTo9thFloor:
case GameObjectActions::GoTo10thFloor:
+ static_assert(int32(GO_STATE_TRANSPORT_ACTIVE) == int32(GameObjectActions::GoTo1stFloor));
if (GetGoType() == GAMEOBJECT_TYPE_TRANSPORT)
- SetTransportState(GO_STATE_TRANSPORT_STOPPED, uint32(action) - uint32(GameObjectActions::GoTo1stFloor));
+ SetGoState(GOState(action));
else
TC_LOG_ERROR("spell", "Spell %d targeted non-transport gameobject for transport only action \"Go to Floor\" %d in effect %d", spellId, int32(action), effectIndex);
break;
@@ -2589,7 +2890,7 @@ void GameObject::SetLocalRotationAngles(float z_rot, float y_rot, float x_rot)
QuaternionData GameObject::GetWorldRotation() const
{
QuaternionData localRotation = GetLocalRotation();
- if (Transport* transport = GetTransport())
+ if (Transport* transport = dynamic_cast<Transport*>(GetTransport()))
{
QuaternionData worldRotation = transport->GetWorldRotation();
@@ -2781,9 +3082,14 @@ void GameObject::SetLootGenerationTime()
void GameObject::SetGoState(GOState state)
{
+ GOState oldState = GetGoState();
SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::State), state);
if (AI())
AI()->OnStateChanged(state);
+
+ if (m_goTypeImpl)
+ m_goTypeImpl->OnStateChanged(oldState, state);
+
if (m_model && !IsTransport())
{
if (!IsInWorld())
@@ -2798,39 +3104,6 @@ void GameObject::SetGoState(GOState state)
}
}
-uint32 GameObject::GetTransportPeriod() const
-{
- ASSERT(GetGOInfo()->type == GAMEOBJECT_TYPE_TRANSPORT);
- if (m_goValue.Transport.AnimationInfo)
- return m_goValue.Transport.AnimationInfo->TotalTime;
-
- return 0;
-}
-
-void GameObject::SetTransportState(GOState state, uint32 stopFrame /*= 0*/)
-{
- if (GetGoState() == state)
- return;
-
- ASSERT(GetGOInfo()->type == GAMEOBJECT_TYPE_TRANSPORT);
- ASSERT(state >= GO_STATE_TRANSPORT_ACTIVE);
- if (state == GO_STATE_TRANSPORT_ACTIVE)
- {
- m_goValue.Transport.StateUpdateTimer = 0;
- m_goValue.Transport.PathProgress = getMSTime();
- if (GetGoState() >= GO_STATE_TRANSPORT_STOPPED)
- m_goValue.Transport.PathProgress += m_goValue.Transport.StopFrames->at(GetGoState() - GO_STATE_TRANSPORT_STOPPED);
- SetGoState(GO_STATE_TRANSPORT_ACTIVE);
- }
- else
- {
- ASSERT(state < GOState(GO_STATE_TRANSPORT_STOPPED + MAX_GO_STATE_TRANSPORT_STOP_FRAMES));
- ASSERT(stopFrame < m_goValue.Transport.StopFrames->size());
- m_goValue.Transport.PathProgress = getMSTime() + m_goValue.Transport.StopFrames->at(stopFrame);
- SetGoState(GOState(GO_STATE_TRANSPORT_STOPPED + stopFrame));
- }
-}
-
void GameObject::SetDisplayId(uint32 displayid)
{
SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::DisplayID), displayid);
@@ -3029,6 +3302,32 @@ void GameObject::ClearUpdateMask(bool remove)
Object::ClearUpdateMask(remove);
}
+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)
+{
+ DoWithSuppressingObjectUpdates([&]()
+ {
+ UF::ObjectData::Base dynflagMask;
+ dynflagMask.MarkChanged(&UF::ObjectData::DynamicFlags);
+ bool marked = (m_objectData->GetChangesMask() & dynflagMask.GetChangesMask()).IsAnySet();
+
+ uint32 dynamicFlags = GetDynamicFlags();
+ dynamicFlags &= 0xFFFF; // remove high bits
+ dynamicFlags |= uint32(progress * 65535.0f) << 16;
+ ReplaceAllDynamicFlags(dynamicFlags);
+
+ if (!marked)
+ const_cast<UF::ObjectData&>(*m_objectData).ClearChanged(&UF::ObjectData::DynamicFlags);
+ });
+}
+
void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = nullptr*/) const
{
if (m_goData)
@@ -3047,6 +3346,31 @@ void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /*
}
}
+TransportBase const* GameObject::ToTransportBase() const
+{
+ switch (GetGoType())
+ {
+ case GAMEOBJECT_TYPE_TRANSPORT:
+ return static_cast<GameObjectType::Transport const*>(m_goTypeImpl.get());
+ case GAMEOBJECT_TYPE_MAP_OBJ_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())
@@ -3315,6 +3639,12 @@ void GameObject::UpdateDynamicFlagsForNearbyPlayers() const
Cell::VisitWorldObjects(this, deliverer, GetVisibilityRange());
}
+void GameObject::HandleCustomTypeCommand(GameObjectTypeBase::CustomCommand const& command) const
+{
+ if (m_goTypeImpl)
+ command.Execute(*m_goTypeImpl);
+}
+
void GameObject::CreateModel()
{
m_model = GameObjectModel::Create(std::make_unique<GameObjectModelOwnerImpl>(this), sWorld->GetDataPath());
diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h
index e47df17d3e0..e345f9a147d 100644
--- a/src/server/game/Entities/GameObject/GameObject.h
+++ b/src/server/game/Entities/GameObject/GameObject.h
@@ -25,11 +25,13 @@
#include "MapObject.h"
#include "SharedDefines.h"
+class GameObject;
class GameObjectAI;
class GameObjectModel;
class Group;
class OPvPCapturePoint;
class Transport;
+class TransportBase;
class Unit;
struct TransportAnimation;
enum TriggerCastFlags : uint32;
@@ -42,17 +44,44 @@ namespace WorldPackets
}
}
-union GameObjectValue
+// Base class for GameObject type specific implementations
+class GameObjectTypeBase
{
- //11 GAMEOBJECT_TYPE_TRANSPORT
- struct
+public:
+ class TC_GAME_API CustomCommand
{
- uint32 PathProgress;
- TransportAnimation const* AnimationInfo;
- uint32 CurrentSeg;
- std::vector<uint32>* StopFrames;
- uint32 StateUpdateTimer;
- } Transport;
+ public:
+ virtual ~CustomCommand();
+ virtual void Execute(GameObjectTypeBase& type) const = 0;
+ };
+
+ explicit GameObjectTypeBase(GameObject& owner) : _owner(owner) { }
+ virtual ~GameObjectTypeBase() = default;
+
+ virtual void Update([[maybe_unused]] uint32 diff) { }
+ virtual void OnStateChanged([[maybe_unused]] GOState oldState, [[maybe_unused]] GOState newState) { }
+ virtual void OnRelocated() { }
+
+protected:
+ GameObject& _owner;
+};
+
+namespace GameObjectType
+{
+class TC_GAME_API SetTransportAutoCycleBetweenStopFrames : public GameObjectTypeBase::CustomCommand
+{
+public:
+ explicit SetTransportAutoCycleBetweenStopFrames(bool on);
+
+ void Execute(GameObjectTypeBase& type) const override;
+
+private:
+ bool _on;
+};
+}
+
+union GameObjectValue
+{
//25 GAMEOBJECT_TYPE_FISHINGHOLE
struct
{
@@ -209,14 +238,15 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
void SetGoType(GameobjectTypes type) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::TypeID), type); }
GOState GetGoState() const { return GOState(*m_gameObjectData->State); }
void SetGoState(GOState state);
- virtual uint32 GetTransportPeriod() const;
- void SetTransportState(GOState state, uint32 stopFrame = 0);
uint32 GetGoArtKit() const { return m_gameObjectData->ArtKit; }
void SetGoArtKit(uint32 artkit);
uint8 GetGoAnimProgress() const { return m_gameObjectData->PercentHealth; }
void SetGoAnimProgress(uint8 animprogress) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::PercentHealth), animprogress); }
static void SetGoArtKit(uint32 artkit, GameObject* go, ObjectGuid::LowType lowguid = UI64LIT(0));
+ std::vector<uint32> const* GetPauseTimes() const;
+ void SetPathProgressForClient(float progress);
+
void EnableCollision(bool enable);
void Use(Unit* user);
@@ -313,6 +343,9 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
GameObjectModel* m_model;
void GetRespawnPosition(float &x, float &y, float &z, float* ori = nullptr) const;
+ TransportBase* ToTransportBase() { return const_cast<TransportBase*>(const_cast<GameObject const*>(this)->ToTransportBase()); }
+ TransportBase const* ToTransportBase() const;
+
Transport* ToTransport() { if (GetGOInfo()->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT) return reinterpret_cast<Transport*>(this); else return nullptr; }
Transport const* ToTransport() const { if (GetGOInfo()->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT) return reinterpret_cast<Transport const*>(this); else return nullptr; }
@@ -320,8 +353,11 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
float GetStationaryY() const override { return m_stationaryPosition.GetPositionY(); }
float GetStationaryZ() const override { return m_stationaryPosition.GetPositionZ(); }
float GetStationaryO() const override { return m_stationaryPosition.GetOrientation(); }
+ Position const& GetStationaryPosition() const { return m_stationaryPosition; }
void RelocateStationaryPosition(float x, float y, float z, float o) { m_stationaryPosition.Relocate(x, y, z, o); }
+ void AfterRelocation();
+
float GetInteractionDistance() const;
void UpdateModelPosition();
@@ -352,6 +388,8 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
void UpdateDynamicFlagsForNearbyPlayers() const;
+ void HandleCustomTypeCommand(GameObjectTypeBase::CustomCommand const& command) const;
+
UF::UpdateField<UF::GameObjectData, 0, TYPEID_GAMEOBJECT> m_gameObjectData;
protected:
@@ -383,7 +421,8 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject>
GameObjectTemplate const* m_goInfo;
GameObjectTemplateAddon const* m_goTemplateAddon;
GameObjectData const* m_goData;
- GameObjectValue m_goValue;
+ std::unique_ptr<GameObjectTypeBase> m_goTypeImpl;
+ GameObjectValue m_goValue; // TODO: replace with m_goTypeImpl
int64 m_packedRotation;
QuaternionData m_localRotation;
diff --git a/src/server/game/Entities/GameObject/GameObjectData.h b/src/server/game/Entities/GameObject/GameObjectData.h
index 9e7650115e4..46f87f8c9f4 100644
--- a/src/server/game/Entities/GameObject/GameObjectData.h
+++ b/src/server/game/Entities/GameObject/GameObjectData.h
@@ -1017,6 +1017,7 @@ struct GameObjectTemplate
case GAMEOBJECT_TYPE_TRAP: return trap.GiganticAOI != 0;
case GAMEOBJECT_TYPE_SPELL_FOCUS: return spellFocus.GiganticAOI != 0;
case GAMEOBJECT_TYPE_GOOBER: return goober.GiganticAOI != 0;
+ case GAMEOBJECT_TYPE_TRANSPORT: return true;
case GAMEOBJECT_TYPE_SPELLCASTER: return spellCaster.GiganticAOI != 0;
case GAMEOBJECT_TYPE_FLAGSTAND: return flagStand.GiganticAOI != 0;
case GAMEOBJECT_TYPE_FLAGDROP: return flagDrop.GiganticAOI != 0;