diff options
author | Shauren <shauren.trinity@gmail.com> | 2022-10-21 22:11:00 +0200 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2022-10-21 22:11:00 +0200 |
commit | 879c0cccfcb9224f9e15cbed926c57a4e010a070 (patch) | |
tree | bb01bada60e1c943b6981919945b1a91b4da1c11 /src/server | |
parent | 43ac108a527fc593b4d5fa1fb06ff98e843d0b84 (diff) |
Core/GameObjects: Implemented per player gameobject state and visibility for looted non-consumable chests
Diffstat (limited to 'src/server')
15 files changed, 161 insertions, 17 deletions
diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index a30c7d1129f..907c30ecaea 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -1967,9 +1967,9 @@ bool Creature::hasInvolvedQuest(uint32 quest_id) const return true; } -bool Creature::IsInvisibleDueToDespawn() const +bool Creature::IsInvisibleDueToDespawn(WorldObject const* seer) const { - if (Unit::IsInvisibleDueToDespawn()) + if (Unit::IsInvisibleDueToDespawn(seer)) return true; if (IsAlive() || isDying() || m_corpseRemoveTime > GameTime::GetGameTime()) diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index beb74bb7526..1e8c6044a13 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -436,7 +436,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma uint16 m_LootMode; // Bitmask (default: LOOT_MODE_DEFAULT) that determines what loot will be lootable - bool IsInvisibleDueToDespawn() const override; + bool IsInvisibleDueToDespawn(WorldObject const* seer) const override; bool CanAlwaysSee(WorldObject const* obj) const override; private: diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 4dffcf2652e..1e50e56f2a3 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -901,6 +901,44 @@ void GameObject::Update(uint32 diff) if (m_goTypeImpl) m_goTypeImpl->Update(diff); + if (m_perPlayerState) + { + for (auto itr = m_perPlayerState->begin(); itr != m_perPlayerState->end(); ) + { + if (itr->second.ValidUntil > GameTime::GetSystemTime()) + { + ++itr; + continue; + } + + Player* seer = ObjectAccessor::GetPlayer(*this, itr->first); + bool needsStateUpdate = itr->second.State != GetGoState(); + bool despawned = itr->second.Despawned; + + itr = m_perPlayerState->erase(itr); + + if (seer) + { + if (despawned) + { + seer->UpdateVisibilityOf(this); + } + else if (needsStateUpdate) + { + UF::ObjectData::Base objMask; + UF::GameObjectData::Base goMask; + goMask.MarkChanged(&UF::GameObjectData::State); + + UpdateData udata(GetMapId()); + BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), seer); + WorldPacket packet; + udata.BuildPacket(&packet); + seer->SendDirectMessage(&packet); + } + } + } + } + switch (m_lootState) { case GO_NOT_READY: @@ -1795,15 +1833,20 @@ bool GameObject::IsAlwaysVisibleFor(WorldObject const* seer) const return false; } -bool GameObject::IsInvisibleDueToDespawn() const +bool GameObject::IsInvisibleDueToDespawn(WorldObject const* seer) const { - if (WorldObject::IsInvisibleDueToDespawn()) + if (WorldObject::IsInvisibleDueToDespawn(seer)) return true; // Despawned if (!isSpawned()) return true; + if (m_perPlayerState) + if (PerPlayerState const* state = Trinity::Containers::MapGetValuePtr(*m_perPlayerState, seer->GetGUID())) + if (state->Despawned) + return true; + return false; } @@ -3189,6 +3232,29 @@ bool GameObject::IsFullyLooted() const return true; } +void GameObject::OnLootRelease(Player* looter) +{ + switch (GetGoType()) + { + case GAMEOBJECT_TYPE_CHEST: + { + GameObjectTemplate const* goInfo = GetGOInfo(); + if (!goInfo->chest.consumable && goInfo->chest.chestPersonalLoot) + { + PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[looter->GetGUID()]; + perPlayerState.ValidUntil = GameTime::GetSystemTime() + (goInfo->chest.chestRestockTime + ? Seconds(goInfo->chest.chestRestockTime) + : Seconds(m_respawnDelayTime)); // not hiding this object permanently to prevent infinite growth of m_perPlayerState + // while also maintaining some sort of cheater protection (not getting rid of entries on logout) + perPlayerState.Despawned = true; + + looter->UpdateVisibilityOf(this); + } + break; + } + } +} + void GameObject::SetGoState(GOState state) { GOState oldState = GetGoState(); @@ -3213,6 +3279,28 @@ void GameObject::SetGoState(GOState state) } } +GOState GameObject::GetGoStateFor(ObjectGuid const& viewer) const +{ + if (m_perPlayerState) + if (PerPlayerState const* state = Trinity::Containers::MapGetValuePtr(*m_perPlayerState, viewer)) + if (state->State) + return *state->State; + + return GetGoState(); +} + +void GameObject::SetGoStateFor(GOState state, Player const* viewer) +{ + PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[viewer->GetGUID()]; + perPlayerState.ValidUntil = GameTime::GetSystemTime() + Seconds(m_respawnDelayTime); + perPlayerState.State = state; + + WorldPackets::GameObject::GameObjectSetStateLocal setStateLocal; + setStateLocal.ObjectGUID = GetGUID(); + setStateLocal.State = state; + viewer->SendDirectMessage(setStateLocal.Write()); +} + void GameObject::SetDisplayId(uint32 displayid) { SetUpdateFieldValue(m_values.ModifyValue(&GameObject::m_gameObjectData).ModifyValue(&UF::GameObjectData::DisplayID), displayid); @@ -3690,6 +3778,14 @@ bool GameObject::CanInteractWithCapturePoint(Player const* target) const || m_goValue.CapturePoint.State == WorldPackets::Battleground::BattlegroundCapturePointState::HordeCaptured; } +std::unordered_map<ObjectGuid, GameObject::PerPlayerState>& GameObject::GetOrCreatePerPlayerStates() +{ + if (!m_perPlayerState) + m_perPlayerState = std::make_unique<std::unordered_map<ObjectGuid, PerPlayerState>>(); + + return *m_perPlayerState; +} + class GameObjectModelOwnerImpl : public GameObjectModelOwnerBase { public: diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 7d7d0563532..6341d9ecaea 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -237,6 +237,8 @@ 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); + GOState GetGoStateFor(ObjectGuid const& viewer) const; + void SetGoStateFor(GOState state, Player const* viewer); uint32 GetGoArtKit() const { return m_gameObjectData->ArtKit; } void SetGoArtKit(uint32 artkit); uint8 GetGoAnimProgress() const { return m_gameObjectData->PercentHealth; } @@ -261,6 +263,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void RemoveLootMode(uint16 lootMode) { m_LootMode &= ~lootMode; } void ResetLootMode() { m_LootMode = LOOT_MODE_DEFAULT; } bool IsFullyLooted() const; + void OnLootRelease(Player* looter); void AddToSkillupList(ObjectGuid const& PlayerGuidLow) { m_SkillupList.insert(PlayerGuidLow); } bool IsInSkillupList(ObjectGuid const& playerGuid) const @@ -301,7 +304,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> bool IsNeverVisibleFor(WorldObject const* seer) const override; bool IsAlwaysVisibleFor(WorldObject const* seer) const override; - bool IsInvisibleDueToDespawn() const override; + bool IsInvisibleDueToDespawn(WorldObject const* seer) const override; uint8 GetLevelForTarget(WorldObject const* target) const override; @@ -446,5 +449,16 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> bool m_respawnCompatibilityMode; uint16 _animKitId; uint32 _worldEffectID; + + struct PerPlayerState + { + SystemTimePoint ValidUntil = SystemTimePoint::min(); + Optional<GOState> State; + bool Despawned = false; + }; + + std::unique_ptr<std::unordered_map<ObjectGuid, PerPlayerState>> m_perPlayerState; + + std::unordered_map<ObjectGuid, PerPlayerState>& GetOrCreatePerPlayerStates(); }; #endif diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 451ed8cf0dc..8815f3d5416 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1521,7 +1521,7 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo return false; } - if (obj->IsInvisibleDueToDespawn()) + if (obj->IsInvisibleDueToDespawn(this)) return false; if (!CanDetect(obj, ignoreStealth, checkAlert)) diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 550b8e5588b..b17be4c1822 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -750,11 +750,12 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void SetLocationInstanceId(uint32 _instanceId) { m_InstanceId = _instanceId; } virtual bool CanNeverSee(WorldObject const* obj) const; - virtual bool IsNeverVisibleFor(WorldObject const* /*seer*/) const { return !IsInWorld() || IsDestroyedObject(); } - virtual bool IsAlwaysVisibleFor(WorldObject const* /*seer*/) const { return false; } - virtual bool IsInvisibleDueToDespawn() const { return false; } + virtual bool CanAlwaysSee([[maybe_unused]] WorldObject const* /*obj*/) const { return false; } + virtual bool IsNeverVisibleFor([[maybe_unused]] WorldObject const* seer) const { return !IsInWorld() || IsDestroyedObject(); } + virtual bool IsAlwaysVisibleFor([[maybe_unused]] WorldObject const* seer) const { return false; } + virtual bool IsInvisibleDueToDespawn([[maybe_unused]] WorldObject const* seer) const { return false; } //difference from IsAlwaysVisibleFor: 1. after distance check; 2. use owner or charmer as seer - virtual bool IsAlwaysDetectableFor(WorldObject const* /*seer*/) const { return false; } + virtual bool IsAlwaysDetectableFor([[maybe_unused]] WorldObject const* seer) const { return false; } private: Map* m_currMap; // current object's Map location @@ -771,7 +772,6 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation virtual bool _IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D, bool incOwnRadius = true, bool incTargetRadius = true) const; - virtual bool CanAlwaysSee(WorldObject const* /*obj*/) const { return false; } bool CanDetect(WorldObject const* obj, bool ignoreStealth, bool checkAlert = false) const; bool CanDetectInvisibilityOf(WorldObject const* obj) const; bool CanDetectStealthOf(WorldObject const* obj, bool checkAlert = false) const; diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.cpp b/src/server/game/Entities/Object/Updates/UpdateFields.cpp index c9f7d991d67..c38670e91ff 100644 --- a/src/server/game/Entities/Object/Updates/UpdateFields.cpp +++ b/src/server/game/Entities/Object/Updates/UpdateFields.cpp @@ -4270,7 +4270,7 @@ void GameObjectData::WriteCreate(ByteBuffer& data, EnumFlag<UpdateFieldFlag> fie data << float(ParentRotation->z); data << float(ParentRotation->w); data << int32(FactionTemplate); - data << int8(State); + data << int8(ViewerDependentValue<StateTag>::GetValue(this, owner, receiver)); data << int8(TypeID); data << uint8(PercentHealth); data << uint32(ArtKit); @@ -4377,7 +4377,7 @@ void GameObjectData::WriteUpdate(ByteBuffer& data, Mask const& changesMask, bool } if (changesMask[14]) { - data << int8(State); + data << int8(ViewerDependentValue<StateTag>::GetValue(this, owner, receiver)); } if (changesMask[15]) { diff --git a/src/server/game/Entities/Object/Updates/UpdateFields.h b/src/server/game/Entities/Object/Updates/UpdateFields.h index 41f47ffa22f..1d6d1499df8 100644 --- a/src/server/game/Entities/Object/Updates/UpdateFields.h +++ b/src/server/game/Entities/Object/Updates/UpdateFields.h @@ -807,6 +807,7 @@ struct GameObjectData : public IsUpdateFieldStructureTag, public HasChangesMask< UpdateField<QuaternionData, 0, 12> ParentRotation; UpdateField<int32, 0, 13> FactionTemplate; UpdateField<int8, 0, 14> State; + struct StateTag : ViewerDependentValueTag<int8> {}; UpdateField<int8, 0, 15> TypeID; UpdateField<uint8, 0, 16> PercentHealth; UpdateField<uint32, 0, 17> ArtKit; diff --git a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h index dfbf1d554c9..fdcd7a08a15 100644 --- a/src/server/game/Entities/Object/Updates/ViewerDependentValues.h +++ b/src/server/game/Entities/Object/Updates/ViewerDependentValues.h @@ -303,6 +303,18 @@ public: }; template<> +class ViewerDependentValue<UF::GameObjectData::StateTag> +{ +public: + using value_type = UF::GameObjectData::StateTag::value_type; + + static value_type GetValue(UF::GameObjectData const* /*gameObjectData*/, GameObject const* gameObject, Player const* receiver) + { + return gameObject->GetGoStateFor(receiver->GetGUID()); + } +}; + +template<> class ViewerDependentValue<UF::ConversationData::LastLineEndTimeTag> { public: diff --git a/src/server/game/Handlers/LootHandler.cpp b/src/server/game/Handlers/LootHandler.cpp index fdaf064dd74..40d92fc2a22 100644 --- a/src/server/game/Handlers/LootHandler.cpp +++ b/src/server/game/Handlers/LootHandler.cpp @@ -297,6 +297,8 @@ void WorldSession::DoLootRelease(Loot* loot) } else if (go->IsFullyLooted()) go->SetLootState(GO_JUST_DEACTIVATED); + + go->OnLootRelease(player); } else { diff --git a/src/server/game/Server/Packets/GameObjectPackets.cpp b/src/server/game/Server/Packets/GameObjectPackets.cpp index 4e6cc5d7324..fd15e7264cf 100644 --- a/src/server/game/Server/Packets/GameObjectPackets.cpp +++ b/src/server/game/Server/Packets/GameObjectPackets.cpp @@ -87,3 +87,11 @@ WorldPacket const* WorldPackets::GameObject::GameObjectPlaySpellVisual::Write() return &_worldPacket; } + +WorldPacket const* WorldPackets::GameObject::GameObjectSetStateLocal::Write() +{ + _worldPacket << ObjectGUID; + _worldPacket << uint8(State); + + return &_worldPacket; +} diff --git a/src/server/game/Server/Packets/GameObjectPackets.h b/src/server/game/Server/Packets/GameObjectPackets.h index 91d795a15ae..3bd71aba0ab 100644 --- a/src/server/game/Server/Packets/GameObjectPackets.h +++ b/src/server/game/Server/Packets/GameObjectPackets.h @@ -142,6 +142,17 @@ namespace WorldPackets ObjectGuid ActivatorGUID; int32 SpellVisualID = 0; }; + + class GameObjectSetStateLocal final : public ServerPacket + { + public: + GameObjectSetStateLocal() : ServerPacket(SMSG_GAME_OBJECT_SET_STATE_LOCAL, 16 + 1) { } + + WorldPacket const* Write() override; + + ObjectGuid ObjectGUID; + uint8 State = 0; + }; } } #endif // GOPackets_h__ diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp index 3c3226e16e8..75c8222b069 100644 --- a/src/server/game/Server/Protocol/Opcodes.cpp +++ b/src/server/game/Server/Protocol/Opcodes.cpp @@ -1281,7 +1281,7 @@ void OpcodeTable::Initialize() DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_OBJECT_PLAY_SPELL_VISUAL, STATUS_NEVER, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_OBJECT_PLAY_SPELL_VISUAL_KIT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_OBJECT_RESET_STATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); - DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_OBJECT_SET_STATE_LOCAL, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); + DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_OBJECT_SET_STATE_LOCAL, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_OBJECT_UI_LINK, STATUS_NEVER, CONNECTION_TYPE_INSTANCE); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_SPEED_SET, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_TIME_SET, STATUS_UNHANDLED, CONNECTION_TYPE_REALM); diff --git a/src/server/scripts/Maelstrom/Stonecore/boss_slabhide.cpp b/src/server/scripts/Maelstrom/Stonecore/boss_slabhide.cpp index e6d56e6de51..8c0f1c056c3 100644 --- a/src/server/scripts/Maelstrom/Stonecore/boss_slabhide.cpp +++ b/src/server/scripts/Maelstrom/Stonecore/boss_slabhide.cpp @@ -537,7 +537,7 @@ class BehindObjectCheck bool operator()(WorldObject* unit) { for (std::list<GameObject*>::const_iterator itr = objectList.begin(); itr != objectList.end(); ++itr) - if (!(*itr)->IsInvisibleDueToDespawn() && (*itr)->IsInBetween(caster, unit, 1.5f)) + if (!(*itr)->IsInvisibleDueToDespawn(unit) && (*itr)->IsInBetween(caster, unit, 1.5f)) return true; return false; } diff --git a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_forgemaster_garfrost.cpp b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_forgemaster_garfrost.cpp index 36984217ccc..d34a99d67b6 100644 --- a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_forgemaster_garfrost.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_forgemaster_garfrost.cpp @@ -274,7 +274,7 @@ private: { for (std::list<GameObject*>::const_iterator itr = blockList.begin(); itr != blockList.end(); ++itr) { - if (!(*itr)->IsInvisibleDueToDespawn()) + if (!(*itr)->IsInvisibleDueToDespawn(target)) { if ((*itr)->IsInBetween(caster, target, 4.0f)) { |