Core/GameObjects: Implemented per player gameobject state and visibility for looted non-consumable chests
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
{
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user