Core/GameObjects: Implemented per player gameobject state and visibility for looted non-consumable chests

This commit is contained in:
Shauren
2022-10-21 22:11:00 +02:00
parent 43ac108a52
commit 879c0cccfc
15 changed files with 161 additions and 17 deletions
@@ -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())
+1 -1
View File
@@ -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:
@@ -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:
@@ -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
+1 -1
View File
@@ -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))
+5 -5
View File
@@ -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;
@@ -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])
{
@@ -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;
@@ -302,6 +302,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>
{
+2
View File
@@ -297,6 +297,8 @@ void WorldSession::DoLootRelease(Loot* loot)
}
else if (go->IsFullyLooted())
go->SetLootState(GO_JUST_DEACTIVATED);
go->OnLootRelease(player);
}
else
{
@@ -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;
}
@@ -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__
+1 -1
View File
@@ -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);
@@ -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;
}
@@ -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))
{