aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Entities/GameObject
diff options
context:
space:
mode:
authorJeremy <Golrag@users.noreply.github.com>2023-10-03 15:55:24 +0200
committerGitHub <noreply@github.com>2023-10-03 15:55:24 +0200
commitf96f041c3edadfb5f1f09705fe699c2d7a9ed423 (patch)
tree81a846b5cbce1fe493e96ee696f2f5269d7b172e /src/server/game/Entities/GameObject
parent4537b377385c71671665f507edac726716838003 (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.cpp257
-rw-r--r--src/server/game/Entities/GameObject/GameObject.h15
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();