#pragma once #include #include #include #include #include #include #include "NiPoint3.h" #include "NiQuaternion.h" #include "LDFFormat.h" #include "eKillType.h" #include "Observable.h" namespace Loot { class Info; }; namespace tinyxml2 { class XMLDocument; }; class Player; class EntityInfo; class User; class Spawner; class ScriptComponent; class dpEntity; class EntityTimer; class Component; class Item; class Character; class EntityCallbackTimer; class PositionUpdate; enum class eTriggerEventType; enum class eGameMasterLevel : uint8_t; enum class eReplicaComponentType : uint32_t; enum class eReplicaPacketType : uint8_t; enum class eCinematicEvent : uint32_t; namespace CppScripts { class Script; }; /** * An entity in the world. Has multiple components. */ class Entity { public: explicit Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser = nullptr, Entity* parentEntity = nullptr); ~Entity(); void Initialize(); bool operator==(const Entity& other) const; bool operator!=(const Entity& other) const; /** * Getters */ const LWOOBJID& GetObjectID() const { return m_ObjectID; } const LOT GetLOT() const { return m_TemplateID; } Character* GetCharacter() const { return m_Character; } eGameMasterLevel GetGMLevel() const { return m_GMLevel; } uint8_t GetCollectibleID() const; Entity* GetParentEntity() const { return m_ParentEntity; } std::vector& GetGroups() { return m_Groups; }; Spawner* GetSpawner() const { return m_Spawner; } LWOOBJID GetSpawnerID() const { return m_SpawnerID; } const std::vector& GetSettings() const { return m_Settings; } const std::vector& GetNetworkSettings() const { return m_NetworkSettings; } bool GetIsDead() const; bool GetPlayerReadyForUpdates() const { return m_PlayerIsReadyForUpdates; } bool GetIsGhostingCandidate() const; void SetIsGhostingCandidate(bool value) { m_IsGhostingCandidate = value; }; int8_t GetObservers() const; uint16_t GetNetworkId() const; Entity* GetOwner() const; const NiPoint3& GetDefaultPosition() const; const NiQuaternion& GetDefaultRotation() const; float GetDefaultScale() const; const NiPoint3& GetPosition() const; const NiQuaternion& GetRotation() const; const SystemAddress& GetSystemAddress() const; // Returns the collision group for this entity. // Because the collision group is stored on a base component, this will look for a physics component // then return the collision group from that. int32_t GetCollisionGroup() const; /** * Setters */ void SetCharacter(Character* value) { m_Character = value; } void SetGMLevel(eGameMasterLevel value); void SetOwnerOverride(LWOOBJID value); void SetPlayerReadyForUpdates() { m_PlayerIsReadyForUpdates = true; } void SetObservers(int8_t value); void SetNetworkId(uint16_t id); void SetPosition(const NiPoint3& position); void SetRotation(const NiQuaternion& rotation); void SetRespawnPos(const NiPoint3& position); void SetRespawnRot(const NiQuaternion& rotation); /** * Component management */ Component* GetComponent(eReplicaComponentType componentID) const; template T* GetComponent() const; template bool TryGetComponent(eReplicaComponentType componentId, T*& component) const; bool HasComponent(eReplicaComponentType componentId) const; void AddComponent(eReplicaComponentType componentId, Component* component); // This is expceted to never return nullptr, an assert checks this. CppScripts::Script* const GetScript(); void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName); void Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName); void SetProximityRadius(float proxRadius, std::string name); void SetProximityRadius(dpEntity* entity, std::string name); void AddChild(Entity* child); void RemoveChild(Entity* child); void RemoveParent(); // Adds a timer to start next frame with the given name and time. void AddTimer(std::string name, float time); void AddCallbackTimer(float time, std::function callback); bool HasTimer(const std::string& name); void CancelCallbackTimers(); void CancelAllTimers(); void CancelTimer(const std::string& name); void AddToGroup(const std::string& group); bool IsPlayer() const; std::unordered_map& GetComponents() { return m_Components; } // TODO: Remove void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType); void UpdateXMLDoc(tinyxml2::XMLDocument& doc); void Update(float deltaTime); // Events void OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxName, const std::string& status); void OnCollisionPhantom(LWOOBJID otherEntity); void OnCollisionLeavePhantom(LWOOBJID otherEntity); void OnFireEventServerSide(Entity* sender, std::string args, int32_t param1 = -1, int32_t param2 = -1, int32_t param3 = -1); void OnActivityStateChangeRequest(const LWOOBJID senderID, const int32_t value1, const int32_t value2, const std::u16string& stringValue); void OnCinematicUpdate(Entity* self, Entity* sender, eCinematicEvent event, const std::u16string& pathName, float_t pathTime, float_t totalTime, int32_t waypoint); void NotifyObject(Entity* sender, const std::string& name, int32_t param1 = 0, int32_t param2 = 0); void OnEmoteReceived(int32_t emote, Entity* target); void OnUse(Entity* originator); void OnHitOrHealResult(Entity* attacker, int32_t damage); void OnHit(Entity* attacker); void OnZonePropertyEditBegin(); void OnZonePropertyEditEnd(); void OnZonePropertyModelEquipped(); void OnZonePropertyModelPlaced(Entity* player); void OnZonePropertyModelPickedUp(Entity* player); void OnZonePropertyModelRemoved(Entity* player); void OnZonePropertyModelRemovedWhileEquipped(Entity* player); void OnZonePropertyModelRotated(Entity* player); void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData); void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier); void RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled); void Smash(const LWOOBJID source = LWOOBJID_EMPTY, const eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u""); void Kill(Entity* murderer = nullptr, const eKillType killType = eKillType::SILENT); void AddQuickBuildCompleteCallback(const std::function& callback) const; void AddCollisionPhantomCallback(const std::function& callback); void AddDieCallback(const std::function& callback); void Resurrect(); void AddLootItem(const Loot::Info& info); void PickupItem(const LWOOBJID& objectID); bool CanPickupCoins(uint64_t count); void RegisterCoinDrop(uint64_t count); void ScheduleKillAfterUpdate(Entity* murderer = nullptr); void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr); void ScheduleDestructionAfterUpdate() { m_ShouldDestroyAfterUpdate = true; } const NiPoint3& GetRespawnPosition() const; const NiQuaternion& GetRespawnRotation() const; void Sleep(); void Wake(); bool IsSleeping() const; /* * Utility */ /** * Retroactively corrects the model vault size due to incorrect initialization in a previous patch. * */ void RetroactiveVaultSize(); bool GetBoolean(const std::u16string& name) const; int32_t GetI32(const std::u16string& name) const; int64_t GetI64(const std::u16string& name) const; void SetBoolean(const std::u16string& name, bool value); void SetI32(const std::u16string& name, int32_t value); void SetI64(const std::u16string& name, int64_t value); bool HasVar(const std::u16string& name) const; template const T& GetVar(const std::u16string& name) const; template void SetVar(const std::u16string& name, T value); void SendNetworkVar(const std::string& data, const SystemAddress& sysAddr); template void SetNetworkVar(const std::u16string& name, T value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); template void SetNetworkVar(const std::u16string& name, std::vector value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS); template T GetNetworkVar(const std::u16string& name); /** * Get the LDF value and cast it as T. */ template T GetVarAs(const std::u16string& name) const; template ComponentType* AddComponent(VaArgs... args); /** * Get the LDF data. */ LDFBaseData* GetVarData(const std::u16string& name) const; /** * Get the LDF value and convert it to a string. */ std::string GetVarAsString(const std::u16string& name) const; /* * Collision */ std::vector GetTargetsInPhantom(); Entity* GetScheduledKiller() { return m_ScheduleKiller; } void ProcessPositionUpdate(PositionUpdate& update); // Scale will only be communicated to the client when the construction packet is sent void SetScale(const float scale) { m_Scale = scale; }; /** * @brief The observable for player entity position updates. */ static Observable OnPlayerPositionUpdate; protected: LWOOBJID m_ObjectID; LOT m_TemplateID; std::vector m_Settings; std::vector m_NetworkSettings; NiPoint3 m_DefaultPosition; NiQuaternion m_DefaultRotation; float m_Scale; Spawner* m_Spawner; LWOOBJID m_SpawnerID; bool m_HasSpawnerNodeID; uint32_t m_SpawnerNodeID; Character* m_Character; Entity* m_ParentEntity; //For spawners and the like std::vector m_ChildEntities; eGameMasterLevel m_GMLevel; uint16_t m_CollectibleID; std::vector m_Groups; uint16_t m_NetworkID; std::vector> m_DieCallbacks; std::vector> m_PhantomCollisionCallbacks; std::unordered_map m_Components; std::vector m_Timers; std::vector m_PendingTimers; std::vector m_CallbackTimers; std::vector m_PendingCallbackTimers; bool m_ShouldDestroyAfterUpdate = false; LWOOBJID m_OwnerOverride; Entity* m_ScheduleKiller; bool m_PlayerIsReadyForUpdates = false; bool m_IsGhostingCandidate = false; int8_t m_Observers = 0; bool m_IsParentChildDirty = true; /* * Collision */ std::vector m_TargetsInPhantom; }; /** * Template definitions. */ template bool Entity::TryGetComponent(const eReplicaComponentType componentId, T*& component) const { const auto& index = m_Components.find(componentId); if (index == m_Components.end()) { component = nullptr; return false; } component = dynamic_cast(index->second); return true; } template T* Entity::GetComponent() const { return dynamic_cast(GetComponent(T::ComponentType)); } template const T& Entity::GetVar(const std::u16string& name) const { auto* data = GetVarData(name); if (data == nullptr) { return LDFData::Default; } auto* typed = dynamic_cast*>(data); if (typed == nullptr) { return LDFData::Default; } return typed->GetValue(); } template T Entity::GetVarAs(const std::u16string& name) const { const auto data = GetVarAsString(name); return GeneralUtils::TryParse(data).value_or(LDFData::Default); } template void Entity::SetVar(const std::u16string& name, T value) { auto* data = GetVarData(name); if (data == nullptr) { auto* data = new LDFData(name, value); m_Settings.push_back(data); return; } auto* typed = dynamic_cast*>(data); if (typed == nullptr) { return; } typed->SetValue(value); } template void Entity::SetNetworkVar(const std::u16string& name, T value, const SystemAddress& sysAddr) { LDFData* newData = nullptr; for (auto* data : m_NetworkSettings) { if (data->GetKey() != name) continue; newData = dynamic_cast*>(data); if (newData != nullptr) { newData->SetValue(value); } else { // If we're changing types m_NetworkSettings.erase( std::remove(m_NetworkSettings.begin(), m_NetworkSettings.end(), data), m_NetworkSettings.end() ); delete data; } break; } if (newData == nullptr) { newData = new LDFData(name, value); } m_NetworkSettings.push_back(newData); SendNetworkVar(newData->GetString(true), sysAddr); } template void Entity::SetNetworkVar(const std::u16string& name, std::vector values, const SystemAddress& sysAddr) { std::stringstream updates; auto index = 1; for (const auto& value : values) { LDFData* newData = nullptr; const auto& indexedName = name + u"." + GeneralUtils::to_u16string(index); for (auto* data : m_NetworkSettings) { if (data->GetKey() != indexedName) continue; newData = dynamic_cast*>(data); newData->SetValue(value); break; } if (newData == nullptr) { newData = new LDFData(indexedName, value); } m_NetworkSettings.push_back(newData); if (index == values.size()) { updates << newData->GetString(true); } else { updates << newData->GetString(true) << "\n"; } index++; } SendNetworkVar(updates.str(), sysAddr); } template T Entity::GetNetworkVar(const std::u16string& name) { for (auto* data : m_NetworkSettings) { if (data == nullptr || data->GetKey() != name) continue; auto* typed = dynamic_cast*>(data); if (typed == nullptr) continue; return typed->GetValue(); } return LDFData::Default; } /** * @brief Adds a component of type ComponentType to this entity and forwards the arguments to the constructor. * * @tparam ComponentType The component class type to add. Must derive from Component. * @tparam VaArgs The argument types to forward to the constructor. * @param args The arguments to forward to the constructor. The first argument passed to the ComponentType constructor will be this entity. * @return ComponentType* The added component. Will never return null. */ template inline ComponentType* Entity::AddComponent(VaArgs... args) { static_assert(std::is_base_of_v, "ComponentType must be a Component"); // Get the component if it already exists, or default construct a nullptr auto*& componentToReturn = m_Components[ComponentType::ComponentType]; // If it doesn't exist, create it and forward the arguments to the constructor if (!componentToReturn) { componentToReturn = new ComponentType(this, std::forward(args)...); } else { // In this case the block is already allocated and ready for use // so we use a placement new to construct the component again as was requested by the caller. // Placement new means we already have memory allocated for the object, so this just calls its constructor again. // This is useful for when we want to create a new object in the same memory location as an old one. componentToReturn->~Component(); new(componentToReturn) ComponentType(this, std::forward(args)...); } // Finally return the created or already existing component. // Because of the assert above, this should always be a ComponentType* but I need a way to guarantee the map cannot be modifed outside this function // To allow a static cast here instead of a dynamic one. return dynamic_cast(componentToReturn); }