diff options
| author | Jeremy <Golrag@users.noreply.github.com> | 2023-10-03 15:55:24 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-10-03 15:55:24 +0200 |
| commit | f96f041c3edadfb5f1f09705fe699c2d7a9ed423 (patch) | |
| tree | 81a846b5cbce1fe493e96ee696f2f5269d7b172e /src/server/game/Entities/GameObject | |
| parent | 4537b377385c71671665f507edac726716838003 (diff) | |
Core/GameObject: Implement ControlZone gameobject type (#29320)
Diffstat (limited to 'src/server/game/Entities/GameObject')
| -rw-r--r-- | src/server/game/Entities/GameObject/GameObject.cpp | 257 | ||||
| -rw-r--r-- | src/server/game/Entities/GameObject/GameObject.h | 15 |
2 files changed, 271 insertions, 1 deletions
diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 04ffbf5dcf8..1fd5ce84570 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -49,6 +49,7 @@ #include "PhasingHandler.h" #include "PoolMgr.h" #include "QueryPackets.h" +#include "Util.h" #include "SpellAuras.h" #include "SpellMgr.h" #include "Transport.h" @@ -507,7 +508,7 @@ void SetTransportAutoCycleBetweenStopFrames::Execute(GameObjectTypeBase& type) c class NewFlag : public GameObjectTypeBase { public: - explicit NewFlag(GameObject& owner) : GameObjectTypeBase(owner), _state(FlagState::InBase), _respawnTime(0), _takenFromBaseTime(0){ } + explicit NewFlag(GameObject& owner) : GameObjectTypeBase(owner), _state(FlagState::InBase), _respawnTime(0), _takenFromBaseTime(0) { } void SetState(FlagState newState, Player* player) { @@ -569,6 +570,244 @@ void SetNewFlagState::Execute(GameObjectTypeBase& type) const if (NewFlag* newFlag = dynamic_cast<NewFlag*>(&type)) newFlag->SetState(_state, _player); } + +class ControlZone : public GameObjectTypeBase +{ +public: + explicit ControlZone(GameObject& owner) : GameObjectTypeBase(owner), _value(static_cast<float>(owner.GetGOInfo()->controlZone.startingValue)) + { + if (owner.GetMap()->Instanceable()) + _heartbeatRate = 1s; + else if (owner.GetGOInfo()->controlZone.FrequentHeartbeat) + _heartbeatRate = 2500ms; + else + _heartbeatRate = 5s; + + _heartbeatTracker.Reset(_heartbeatRate); + _previousTeamId = GetControllingTeam(); + _contestedTriggered = false; + } + + void Update(uint32 diff) override + { + if (_owner.HasFlag(GO_FLAG_NOT_SELECTABLE)) + return; + + _heartbeatTracker.Update(diff); + if (_heartbeatTracker.Passed()) + { + _heartbeatTracker.Reset(_heartbeatRate); + HandleHeartbeat(); + } + } + + TeamId GetControllingTeam() const + { + if (_value < GetMaxHordeValue()) + return TEAM_HORDE; + + if (_value > GetMinAllianceValue()) + return TEAM_ALLIANCE; + + return TEAM_NEUTRAL; + } + + GuidUnorderedSet const* GetInsidePlayers() const { return &_insidePlayers; } + + void ActivateObject(GameObjectActions action, int32 /*param*/, WorldObject* /*spellCaster*/, uint32 /*spellId*/, int32 /*effectIndex*/) override + { + switch (action) + { + case GameObjectActions::MakeInert: + for (ObjectGuid const& guid : _insidePlayers) + if (Player* player = ObjectAccessor::GetPlayer(_owner, guid)) + player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0); + + _insidePlayers.clear(); + break; + default: + break; + } + } + + void SetValue(float value) + { + _value = RoundToInterval<float>(value, 0.0f, 100.0f); + } + + void HandleHeartbeat() + { + // update player list inside control zone + std::vector<Player*> targetList; + SearchTargets(targetList); + + TeamId oldControllingTeam = GetControllingTeam(); + float pointsGained = CalculatePointsPerSecond(targetList) * _heartbeatRate.count() / 1000.0f; + if (pointsGained == 0) + return; + + int32 oldRoundedValue = static_cast<int32>(_value); + SetValue(_value + pointsGained); + int32 roundedValue = static_cast<int32>(_value); + if (oldRoundedValue == roundedValue) + return; + + TeamId newControllingTeam = GetControllingTeam(); + TeamId assaultingTeam = pointsGained > 0 ? TEAM_ALLIANCE : TEAM_HORDE; + + if (oldControllingTeam != newControllingTeam) + _contestedTriggered = false; + + if (oldControllingTeam != TEAM_ALLIANCE && newControllingTeam == TEAM_ALLIANCE) + TriggerEvent(_owner.GetGOInfo()->controlZone.ProgressEventAlliance); + else if (oldControllingTeam != TEAM_HORDE && newControllingTeam == TEAM_HORDE) + TriggerEvent(_owner.GetGOInfo()->controlZone.ProgressEventHorde); + else if (oldControllingTeam == TEAM_HORDE && newControllingTeam == TEAM_NEUTRAL) + TriggerEvent(_owner.GetGOInfo()->controlZone.NeutralEventHorde); + else if (oldControllingTeam == TEAM_ALLIANCE && newControllingTeam == TEAM_NEUTRAL) + TriggerEvent(_owner.GetGOInfo()->controlZone.NeutralEventAlliance); + + if (roundedValue == 100 && newControllingTeam == TEAM_ALLIANCE && assaultingTeam == TEAM_ALLIANCE) + TriggerEvent(_owner.GetGOInfo()->controlZone.CaptureEventAlliance); + else if (roundedValue == 0 && newControllingTeam == TEAM_HORDE && assaultingTeam == TEAM_HORDE) + TriggerEvent(_owner.GetGOInfo()->controlZone.CaptureEventHorde); + + if (oldRoundedValue == 100 && assaultingTeam == TEAM_HORDE && !_contestedTriggered) + { + TriggerEvent(_owner.GetGOInfo()->controlZone.ContestedEventHorde); + _contestedTriggered = true; + } + else if (oldRoundedValue == 0 && assaultingTeam == TEAM_ALLIANCE && !_contestedTriggered) + { + TriggerEvent(_owner.GetGOInfo()->controlZone.ContestedEventAlliance); + _contestedTriggered = true; + } + + for (Player* player : targetList) + player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, roundedValue); + } + + void SearchTargets(std::vector<Player*>& targetList) + { + Trinity::AnyUnitInObjectRangeCheck check(&_owner, _owner.GetGOInfo()->controlZone.radius, true); + Trinity::PlayerListSearcher<Trinity::AnyUnitInObjectRangeCheck> searcher(&_owner, targetList, check); + Cell::VisitWorldObjects(&_owner, searcher, _owner.GetGOInfo()->controlZone.radius); + HandleUnitEnterExit(targetList); + } + + float CalculatePointsPerSecond(std::vector<Player*> const& targetList) + { + int32 delta = 0; + + for (Player* player : targetList) + { + if (!player->IsOutdoorPvPActive()) + continue; + + if (player->GetTeamId() == TEAM_HORDE) + delta--; + else + delta++; + } + + uint32 minTime = _owner.GetGOInfo()->controlZone.minTime; + uint32 maxTime = _owner.GetGOInfo()->controlZone.maxTime; + uint32 minSuperiority = _owner.GetGOInfo()->controlZone.minSuperiority; + uint32 maxSuperiority = _owner.GetGOInfo()->controlZone.maxSuperiority; + + if (static_cast<uint32>(std::abs(delta)) < minSuperiority) + return 0; + + float slope = (static_cast<float>(minTime) - maxTime) / (maxSuperiority - minSuperiority); + float intercept = maxTime - slope * minSuperiority; + float timeNeeded = slope * std::abs(delta) + intercept; + float percentageIncrease = 100.0f / timeNeeded; + + if (delta < 0) + percentageIncrease *= -1; + + return percentageIncrease; + } + + void HandleUnitEnterExit(std::vector<Player*> const& newTargetList) + { + GuidUnorderedSet exitPlayers(std::move(_insidePlayers)); + + std::vector<Player*> enteringPlayers; + + for (Player* unit : newTargetList) + { + if (exitPlayers.erase(unit->GetGUID()) == 0) // erase(key_type) returns number of elements erased + enteringPlayers.push_back(unit); + + _insidePlayers.insert(unit->GetGUID()); + } + + for (Player* player : enteringPlayers) + { + player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 1); + player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, static_cast<int32>(_value)); + player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate3, _owner.GetGOInfo()->controlZone.neutralPercent); + } + + for (ObjectGuid const& exitPlayerGuid : exitPlayers) + { + if (Player* player = ObjectAccessor::GetPlayer(_owner, exitPlayerGuid)) + { + player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0); + } + } + } + + float GetMaxHordeValue() const + { + // ex: if neutralPercent is 40; then 0 - 30 is Horde Controlled + return 50.0f - _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f; + } + + float GetMinAllianceValue() const + { + // ex: if neutralPercent is 40; then 70 - 100 is Alliance Controlled + return 50.0f + _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f; + } + + void TriggerEvent(uint32 eventId) const + { + if (eventId <= 0) + return; + + GameEvents::Trigger(eventId, &_owner, nullptr); + } + + uint32 GetStartingValue() const + { + return _owner.GetGOInfo()->controlZone.startingValue; + } + +private: + Milliseconds _heartbeatRate; + TimeTracker _heartbeatTracker; + GuidUnorderedSet _insidePlayers; + TeamId _previousTeamId; + float _value; + bool _contestedTriggered; +}; + +SetControlZoneValue::SetControlZoneValue(Optional<uint32> value /*= { }*/) : _value(value) +{ +} + +void SetControlZoneValue::Execute(GameObjectTypeBase& type) const +{ + if (ControlZone* controlZone = dynamic_cast<ControlZone*>(&type)) + { + uint32 value = controlZone->GetStartingValue(); + if (_value.has_value()) + value = *_value; + + controlZone->SetValue(value); + } +} } GameObject::GameObject() : WorldObject(false), MapObject(), @@ -863,6 +1102,10 @@ bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionD m_invisibility.AddValue(INVISIBILITY_TRAP, 300); } break; + case GAMEOBJECT_TYPE_CONTROL_ZONE: + m_goTypeImpl = std::make_unique<GameObjectType::ControlZone>(*this); + setActive(true); + break; case GAMEOBJECT_TYPE_NEW_FLAG: m_goTypeImpl = std::make_unique<GameObjectType::NewFlag>(*this); if (map->Instanceable()) @@ -2216,6 +2459,10 @@ void GameObject::ActivateObject(GameObjectActions action, int32 param, WorldObje TC_LOG_ERROR("spell", "Spell {} has unhandled action {} in effect {}", spellId, int32(action), effectIndex); break; } + + // Apply side effects of type + if (m_goTypeImpl) + m_goTypeImpl->ActivateObject(action, param, spellCaster, spellId, effectIndex); } void GameObject::SetGoArtKit(uint32 kit) @@ -4118,6 +4365,14 @@ time_t GameObject::GetFlagTakenFromBaseTime() const return newFlag->GetTakenFromBaseTime(); } +GuidUnorderedSet const* GameObject::GetInsidePlayers() const +{ + if (GameObjectType::ControlZone const* controlZone = dynamic_cast<GameObjectType::ControlZone const*>(m_goTypeImpl.get())) + return controlZone->GetInsidePlayers(); + + return nullptr; +} + bool GameObject::MeetsInteractCondition(Player const* user) const { if (!m_goInfo->GetConditionID1()) diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index bb6282e8fd0..bf54c9cf2a6 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -71,6 +71,7 @@ public: virtual void OnStateChanged([[maybe_unused]] GOState oldState, [[maybe_unused]] GOState newState) { } virtual void OnRelocated() { } virtual bool IsNeverVisibleFor([[maybe_unused]] WorldObject const* seer, [[maybe_unused]] bool allowServersideObjects) const { return false; } + virtual void ActivateObject([[maybe_unused]] GameObjectActions action, [[maybe_unused]] int32 param, [[maybe_unused]] WorldObject* spellCaster = nullptr, [[maybe_unused]] uint32 spellId = 0, [[maybe_unused]] int32 effectIndex = -1) { } protected: GameObject& _owner; @@ -100,6 +101,18 @@ private: FlagState _state; Player* _player; }; + +class TC_GAME_API SetControlZoneValue : public GameObjectTypeBase::CustomCommand +{ +public: + explicit SetControlZoneValue(Optional<uint32> value = { }); + + void Execute(GameObjectTypeBase& type) const override; + +private: + Optional<uint32> _value; +}; + } union GameObjectValue @@ -413,6 +426,8 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> ObjectGuid const& GetFlagCarrierGUID() const; time_t GetFlagTakenFromBaseTime() const; + GuidUnorderedSet const* GetInsidePlayers() const; + bool MeetsInteractCondition(Player const* user) const; void AIM_Destroy(); |
