diff --git a/dGame/Entity.cpp b/dGame/Entity.cpp index 35e2cfdc..f4ad52f3 100644 --- a/dGame/Entity.cpp +++ b/dGame/Entity.cpp @@ -504,7 +504,7 @@ void Entity::Initialize() { auto& systemAddress = m_Character->GetParentUser() ? m_Character->GetParentUser()->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS; AddComponent(characterID, m_Character, systemAddress)->LoadFromXml(m_Character->GetXMLDoc()); - AddComponent(characterID); + AddComponent(characterID)->LoadFromXml(m_Character->GetXMLDoc()); } const auto inventoryID = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY); diff --git a/dGame/EntityManager.cpp b/dGame/EntityManager.cpp index 12be2e06..92258afc 100644 --- a/dGame/EntityManager.cpp +++ b/dGame/EntityManager.cpp @@ -361,16 +361,24 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr LOG("Attempted to construct null entity"); return; } + // Don't construct GM invisible entities unless it's for the GM themselves + // GMs can see other GMs if they are the same or lower level + GameMessages::GetGMInvis getGMInvisMsg; + getGMInvisMsg.Send(entity->GetObjectID()); + if (getGMInvisMsg.bGMInvis && sysAddr != entity->GetSystemAddress()) { + auto* toUser = UserManager::Instance()->GetUser(sysAddr); + if (!toUser) return; + auto* constructedUser = UserManager::Instance()->GetUser(entity->GetSystemAddress()); + if (!constructedUser) return; + if (toUser->GetMaxGMLevel() < constructedUser->GetMaxGMLevel()) return; + } if (entity->GetNetworkId() == 0) { uint16_t networkId; - if (!m_LostNetworkIds.empty()) { networkId = m_LostNetworkIds.top(); m_LostNetworkIds.pop(); - } else { - networkId = ++m_NetworkIdCounter; - } + } else networkId = ++m_NetworkIdCounter; entity->SetNetworkId(networkId); } @@ -379,10 +387,8 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr if (std::find(m_EntitiesToGhost.begin(), m_EntitiesToGhost.end(), entity) == m_EntitiesToGhost.end()) { m_EntitiesToGhost.push_back(entity); } - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) { CheckGhosting(entity); - return; } } @@ -413,14 +419,9 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr Game::server->Send(stream, sysAddr, false); } - if (entity->IsPlayer()) { - if (entity->GetGMLevel() > eGameMasterLevel::CIVILIAN) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, sysAddr); - } - } } -void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) { +void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) { //ZoneControl is special: ConstructEntity(m_ZoneControlEntity, sysAddr); @@ -488,11 +489,7 @@ void EntityManager::QueueGhostUpdate(LWOOBJID playerID) { void EntityManager::UpdateGhosting() { for (const auto playerID : m_PlayersToUpdateGhosting) { auto* player = PlayerManager::GetPlayer(playerID); - - if (player == nullptr) { - continue; - } - + if (!player) continue; UpdateGhosting(player); } @@ -519,6 +516,7 @@ void EntityManager::UpdateGhosting(Entity* player) { const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint); + auto ghostingDistanceMax = m_GhostDistanceMaxSquared; auto ghostingDistanceMin = m_GhostDistanceMinSqaured; @@ -555,35 +553,25 @@ void EntityManager::UpdateGhosting(Entity* player) { } void EntityManager::CheckGhosting(Entity* entity) { - if (entity == nullptr) { - return; - } + if (!entity) return; const auto& referencePoint = entity->GetPosition(); - for (auto* player : PlayerManager::GetAllPlayers()) { auto* ghostComponent = player->GetComponent(); if (!ghostComponent) continue; const auto& entityPoint = ghostComponent->GetGhostReferencePoint(); - const auto id = entity->GetObjectID(); - const auto observed = ghostComponent->IsObserved(id); - const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint); if (observed && distance > m_GhostDistanceMaxSquared) { ghostComponent->GhostEntity(id); - DestructEntity(entity, player->GetSystemAddress()); - entity->SetObservers(entity->GetObservers() - 1); } else if (!observed && m_GhostDistanceMinSqaured > distance) { ghostComponent->ObserveEntity(id); - ConstructEntity(entity, player->GetSystemAddress()); - entity->SetObservers(entity->GetObservers() + 1); } } diff --git a/dGame/dComponents/GhostComponent.cpp b/dGame/dComponents/GhostComponent.cpp index d86de72b..96283de3 100644 --- a/dGame/dComponents/GhostComponent.cpp +++ b/dGame/dComponents/GhostComponent.cpp @@ -1,4 +1,9 @@ #include "GhostComponent.h" +#include "PlayerManager.h" +#include "Character.h" +#include "ControllablePhysicsComponent.h" +#include "UserManager.h" +#include "User.h" #include "Amf3.h" #include "GameMessages.h" @@ -7,7 +12,9 @@ GhostComponent::GhostComponent(Entity* parent, const int32_t componentID) : Comp m_GhostReferencePoint = NiPoint3Constant::ZERO; m_GhostOverridePoint = NiPoint3Constant::ZERO; m_GhostOverride = false; - + + RegisterMsg(this, &GhostComponent::OnToggleGMInvis); + RegisterMsg(this, &GhostComponent::OnGetGMInvis); RegisterMsg(this, &GhostComponent::MsgGetObjectReportInfo); } @@ -22,6 +29,25 @@ GhostComponent::~GhostComponent() { } } +void GhostComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { + auto* objElement = doc.FirstChildElement("obj"); + if (!objElement) return; + auto* ghstElement = objElement->FirstChildElement("ghst"); + if (!ghstElement) return; + m_IsGMInvisible = ghstElement->BoolAttribute("i"); +} + +void GhostComponent::UpdateXml(tinyxml2::XMLDocument& doc) { + auto* objElement = doc.FirstChildElement("obj"); + if (!objElement) return; + auto* ghstElement = objElement->FirstChildElement("ghst"); + if (ghstElement) objElement->DeleteChild(ghstElement); + // Only save if GM invisible + if (!m_IsGMInvisible) return; + ghstElement = objElement->InsertNewChildElement("ghst"); + if (ghstElement) ghstElement->SetAttribute("i", m_IsGMInvisible); +} + void GhostComponent::SetGhostReferencePoint(const NiPoint3& value) { m_GhostReferencePoint = value; } @@ -61,6 +87,40 @@ void GhostComponent::GhostEntity(LWOOBJID id) { m_ObservedEntities.erase(id); } +bool GhostComponent::OnToggleGMInvis(GameMessages::GameMsg& msg) { + auto& gmInvisMsg = static_cast(msg); + gmInvisMsg.bStateOut = !m_IsGMInvisible; + m_IsGMInvisible = !m_IsGMInvisible; + LOG_DEBUG("GM Invisibility toggled to: %s", m_IsGMInvisible ? "true" : "false"); + gmInvisMsg.Send(UNASSIGNED_SYSTEM_ADDRESS); + auto* thisUser = UserManager::Instance()->GetUser(m_Parent->GetSystemAddress()); + for (const auto& player : PlayerManager::GetAllPlayers()) { + if (!player || player->GetObjectID() == m_Parent->GetObjectID()) continue; + auto* toUser = UserManager::Instance()->GetUser(player->GetSystemAddress()); + if (m_IsGMInvisible) { + if (toUser->GetMaxGMLevel() < thisUser->GetMaxGMLevel()) { + Game::entityManager->DestructEntity(m_Parent, player->GetSystemAddress()); + } + } else { + if (toUser->GetMaxGMLevel() >= thisUser->GetMaxGMLevel()) { + Game::entityManager->ConstructEntity(m_Parent, player->GetSystemAddress()); + auto* controllableComp = m_Parent->GetComponent(); + controllableComp->SetDirtyPosition(true); + } + } + } + Game::entityManager->SerializeEntity(m_Parent); + + return true; +} + +bool GhostComponent::OnGetGMInvis(GameMessages::GameMsg& msg) { + LOG_DEBUG("GM Invisibility requested: %s", m_IsGMInvisible ? "true" : "false"); + auto& gmInvisMsg = static_cast(msg); + gmInvisMsg.bGMInvis = m_IsGMInvisible; + return gmInvisMsg.bGMInvis; +} + bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) { auto& reportMsg = static_cast(msg); auto& cmptType = reportMsg.info->PushDebug("Ghost"); diff --git a/dGame/dComponents/GhostComponent.h b/dGame/dComponents/GhostComponent.h index 75ed3c9d..01443c1b 100644 --- a/dGame/dComponents/GhostComponent.h +++ b/dGame/dComponents/GhostComponent.h @@ -7,11 +7,17 @@ class NiPoint3; +namespace tinyxml2 { + class XMLDocument; +} + class GhostComponent final : public Component { public: static inline const eReplicaComponentType ComponentType = eReplicaComponentType::GHOST; GhostComponent(Entity* parent, const int32_t componentID); ~GhostComponent() override; + void LoadFromXml(const tinyxml2::XMLDocument& doc) override; + void UpdateXml(tinyxml2::XMLDocument& doc) override; void SetGhostOverride(bool value) { m_GhostOverride = value; }; @@ -39,9 +45,14 @@ public: void GhostEntity(const LWOOBJID id); + bool OnToggleGMInvis(GameMessages::GameMsg& msg); + + bool OnGetGMInvis(GameMessages::GameMsg& msg); + bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg); private: + NiPoint3 m_GhostReferencePoint; NiPoint3 m_GhostOverridePoint; @@ -51,6 +62,9 @@ private: std::unordered_set m_LimboConstructions; bool m_GhostOverride; + + bool m_IsGMInvisible; + }; #endif //!__GHOSTCOMPONENT__H__ diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 522f4741..118baa5d 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1847,18 +1847,6 @@ void GameMessages::SendNotifyClientFailedPrecondition(LWOOBJID objectId, const S SEND_PACKET; } -void GameMessages::SendToggleGMInvis(LWOOBJID objectId, bool enabled, const SystemAddress& sysAddr) { - CBITSTREAM; - CMSGHEADER; - - bitStream.Write(objectId); - bitStream.Write(MessageType::Game::TOGGLE_GM_INVIS); - bitStream.Write(enabled); // does not matter? - - if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) SEND_PACKET_BROADCAST; - SEND_PACKET; -} - void GameMessages::SendSetName(LWOOBJID objectID, std::u16string name, const SystemAddress& sysAddr) { CBITSTREAM; CMSGHEADER; @@ -6449,4 +6437,8 @@ namespace GameMessages { stream.Write(lootID); stream.Write(lootOwnerID); } + + void ToggleGMInvis::Serialize(RakNet::BitStream& stream) const { + stream.Write(bStateOut); + } } diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h index 9daae4b5..3ae9ffce 100644 --- a/dGame/dGameMessages/GameMessages.h +++ b/dGame/dGameMessages/GameMessages.h @@ -243,8 +243,6 @@ namespace GameMessages { bool cancelOnLogout = false, bool cancelOnRemoveBuff = true, bool cancelOnUi = false, bool cancelOnUnequip = false, bool cancelOnZone = false, bool addedByTeammate = false, bool applyOnTeammates = false, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); - void SendToggleGMInvis(LWOOBJID objectId, bool enabled, const SystemAddress& sysAddr); - void SendSetName(LWOOBJID objectID, std::u16string name, const SystemAddress& sysAddr); // Property messages @@ -938,6 +936,20 @@ namespace GameMessages { LWOOBJID lootOwnerID{}; }; + struct ToggleGMInvis : public GameMsg { + ToggleGMInvis() : GameMsg(MessageType::Game::TOGGLE_GM_INVIS) {} + + void Serialize(RakNet::BitStream& stream) const override; + bool bStateOut{ false }; + + }; + + struct GetGMInvis : public GameMsg { + GetGMInvis() : GameMsg(MessageType::Game::GET_GM_INVIS) {} + + bool bGMInvis{ false }; + }; + struct ChildRemoved : public GameMsg { ChildRemoved() : GameMsg(MessageType::Game::CHILD_REMOVED) {} diff --git a/dGame/dUtilities/SlashCommandHandler.cpp b/dGame/dUtilities/SlashCommandHandler.cpp index 65ee97c6..6c2d96ee 100644 --- a/dGame/dUtilities/SlashCommandHandler.cpp +++ b/dGame/dUtilities/SlashCommandHandler.cpp @@ -893,10 +893,10 @@ void SlashCommandHandler::Startup() { Command GmInvisCommand{ .help = "Toggles invisibility for the character", - .info = "Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8", + .info = "Toggles invisibility for the character, making them invisible to other players and lower GM levels", .aliases = { "gminvis" }, .handle = GMGreaterThanZeroCommands::GmInvis, - .requiredLevel = eGameMasterLevel::DEVELOPER + .requiredLevel = eGameMasterLevel::FORUM_MODERATOR }; RegisterCommand(GmInvisCommand); diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index 0fd86512..721ea828 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -89,7 +89,8 @@ namespace DEVGMCommands { GameMessages::SendChatModeUpdate(entity->GetObjectID(), eGameMasterLevel::CIVILIAN); entity->SetGMLevel(eGameMasterLevel::CIVILIAN); - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::ToggleGMInvis msg; + msg.Send(entity->GetObjectID()); GameMessages::SendSlashCommandFeedbackText(entity, u"Your game master level has been changed, you may not be able to use all commands."); } @@ -183,7 +184,6 @@ namespace DEVGMCommands { Game::entityManager->ConstructEntity(entity); ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(lowerName) + u" set to " + (GeneralUtils::to_u16string(minifigItemId))); - GameMessages::SendToggleGMInvis(entity->GetObjectID(), false, UNASSIGNED_SYSTEM_ADDRESS); // need to retoggle because it gets reenabled on creation of new character } void PlayAnimation(Entity* entity, const SystemAddress& sysAddr, const std::string args) { diff --git a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp index 7d017cf0..7575b9cc 100644 --- a/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMGreaterThanZeroCommands.cpp @@ -275,7 +275,8 @@ namespace GMGreaterThanZeroCommands { } void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args) { - GameMessages::SendToggleGMInvis(entity->GetObjectID(), true, UNASSIGNED_SYSTEM_ADDRESS); + GameMessages::ToggleGMInvis msg; + msg.Send(entity->GetObjectID()); } void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args) {