From 7a7a6c99e3bc249003ea8b1f62f1e2f00fe837f6 Mon Sep 17 00:00:00 2001 From: Dillon Skaggs Date: Wed, 21 Aug 2024 00:22:10 -0500 Subject: [PATCH] feat(server/state): add `[GET/SET]_ENTITY_ORPHAN_MODE` - this allows the end user to be able to define how they want an entity to be deleted instead of automatically cleaning up every entity on entity GC - this is mainly useful for allowing the server to keep specific client entities (like vehicles) from being automatically deleted - this allows for the same persistence as server created entities but without the draw backs. - also allows for specific entities (like objects) to be marked for deletion whenever the client leaves the server --- .../include/state/ServerGameState.h | 8 ++ .../src/state/ServerGameState.cpp | 14 +++- .../src/state/ServerGameState_Scripting.cpp | 82 +++++++++++++++++++ ext/native-decls/GetEntityOrphanMode.md | 15 ++++ ext/native-decls/SetEntityOrphanMode.md | 32 ++++++++ 5 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 ext/native-decls/GetEntityOrphanMode.md create mode 100644 ext/native-decls/SetEntityOrphanMode.md diff --git a/code/components/citizen-server-impl/include/state/ServerGameState.h b/code/components/citizen-server-impl/include/state/ServerGameState.h index 12620b355b..0c8b6ac4a5 100644 --- a/code/components/citizen-server-impl/include/state/ServerGameState.h +++ b/code/components/citizen-server-impl/include/state/ServerGameState.h @@ -778,6 +778,13 @@ struct SyncTreeBase virtual bool IsEntityVisible(bool* visible) = 0; }; +enum EntityOrphanMode : uint8_t +{ + DeleteWhenNotRelevant = 0, + DeleteOnOwnerDisconnect = 1, + KeepEntity = 2, +}; + struct SyncEntityState { using TData = std::variant; @@ -822,6 +829,7 @@ struct SyncEntityState bool passedFilter = false; bool wantsReassign = false; bool firstOwnerDropped = false; + EntityOrphanMode orphanMode = EntityOrphanMode::DeleteWhenNotRelevant; std::list> onCreationRPC; diff --git a/code/components/citizen-server-impl/src/state/ServerGameState.cpp b/code/components/citizen-server-impl/src/state/ServerGameState.cpp index 463bac2468..507e0a13f7 100644 --- a/code/components/citizen-server-impl/src/state/ServerGameState.cpp +++ b/code/components/citizen-server-impl/src/state/ServerGameState.cpp @@ -945,7 +945,7 @@ void ServerGameState::Tick(fx::ServerInstanceBase* instance) continue; } // it's a client-owned entity, let's check for a few things - else if (entity->IsOwnedByClientScript()) + else if (entity->IsOwnedByClientScript() && entity->orphanMode != sync::KeepEntity) { // is the original owner offline? if (entity->firstOwnerDropped) @@ -956,7 +956,7 @@ void ServerGameState::Tick(fx::ServerInstanceBase* instance) } } // it's a script-less entity, we can collect it. - else if (!entity->IsOwnedByScript() && (entity->type != sync::NetObjEntityType::Player || !entity->GetClient())) + else if (!entity->IsOwnedByScript() && (entity->type != sync::NetObjEntityType::Player || !entity->GetClient()) && entity->orphanMode != sync::KeepEntity) { FinalizeClone({}, entity, entity->handle, 0, "Regular entity GC"); continue; @@ -2886,6 +2886,13 @@ void ServerGameState::HandleClientDrop(const fx::ClientSharedPtr& client, uint16 if (firstOwner && firstOwner->GetNetId() == client->GetNetId()) { entity->firstOwnerDropped = true; + + if (entity->orphanMode == sync::DeleteOnOwnerDisconnect) + { + entity->deleting = true; + // we don't need to hit the cleanup logic below, this will automatically be done on next server tick + continue; + } } if (hasSlotId) @@ -2909,7 +2916,8 @@ void ServerGameState::HandleClientDrop(const fx::ClientSharedPtr& client, uint16 { ReassignEntity(entity->handle, firstOwner); } - else + // we don't want to add these to the list to remove if they're set to be kept when orphaned + else if (entity->orphanMode != sync::KeepEntity) { toErase.insert(entity->handle); } diff --git a/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp b/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp index 0a5655b0fd..80ae8c9b94 100644 --- a/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp +++ b/code/components/citizen-server-impl/src/state/ServerGameState_Scripting.cpp @@ -95,6 +95,41 @@ static void Init() context.SetResult(true); }); + +#if _DEBUG + fx::ScriptEngine::RegisterNativeHandler("IS_ENTITY_RELEVANT", [](fx::ScriptContext& context) + { + // get the current resource manager + auto resourceManager = fx::ResourceManager::GetCurrent(); + + // get the owning server instance + auto instance = resourceManager->GetComponent()->Get(); + + // get the server's game state + auto gameState = instance->GetComponent(); + + // parse the client ID + auto id = context.GetArgument(0); + + if (!id) + { + context.SetResult(false); + return; + } + + auto entity = gameState->GetEntity(id); + + if (!entity || entity->finalizing || entity->deleting) + { + context.SetResult(false); + return; + } + + std::lock_guard l(entity->guidMutex); + + context.SetResult(entity->relevantTo.any()); + }); +#endif fx::ScriptEngine::RegisterNativeHandler("NETWORK_GET_ENTITY_FROM_NETWORK_ID", [](fx::ScriptContext& context) { @@ -152,6 +187,53 @@ static void Init() return retval; })); + + fx::ScriptEngine::RegisterNativeHandler("SET_ENTITY_ORPHAN_MODE", [](fx::ScriptContext& context) + { + // get the current resource manager + auto resourceManager = fx::ResourceManager::GetCurrent(); + + // get the owning server instance + auto instance = resourceManager->GetComponent()->Get(); + + // get the server's game state + auto gameState = instance->GetComponent(); + + // parse the client ID + auto id = context.GetArgument(0); + + if (!id) + { + return; + } + + auto entity = gameState->GetEntity(id); + + if (!entity) + { + throw std::runtime_error(va("Tried to access invalid entity: %d", id)); + } + + auto orphanMode = context.GetArgument(1); + + if (orphanMode < 0 || orphanMode > fx::sync::KeepEntity) + { + throw std::runtime_error(va("Tried to set entities (%d) orphan mode to an invalid orphan mode: %d", id, orphanMode)); + } + + entity->orphanMode = static_cast(orphanMode); + + // if they set the orphan mode to `DeleteOnOwnerDisconnect` and the entity already doesn't have an owner then treat this as a `DELETE_ENTITY` call + if (entity->orphanMode == fx::sync::DeleteOnOwnerDisconnect && entity->firstOwnerDropped && !entity->GetFirstOwner()) + { + entity->deleting = true; + } + }); + + fx::ScriptEngine::RegisterNativeHandler("GET_ENTITY_ORPHAN_MODE", makeEntityFunction([](fx::ScriptContext& context, const fx::sync::SyncEntityPtr& entity) + { + return entity->orphanMode; + })); fx::ScriptEngine::RegisterNativeHandler("GET_ENTITY_COORDS", makeEntityFunction([](fx::ScriptContext& context, const fx::sync::SyncEntityPtr& entity) { diff --git a/ext/native-decls/GetEntityOrphanMode.md b/ext/native-decls/GetEntityOrphanMode.md new file mode 100644 index 0000000000..793d63a741 --- /dev/null +++ b/ext/native-decls/GetEntityOrphanMode.md @@ -0,0 +1,15 @@ +--- +ns: CFX +apiset: server +--- +## GET_ENTITY_ORPHAN_MODE + +```c +int GET_ENTITY_ORPHAN_MODE(Entity entity); +``` + +## Parameters +* **entity**: The entity handle to override the distance culling radius. + +## Return value +Returns the entities current orphan mode, refer to enum in [SET_ENTITY_ORPHAN_MODE](#_0x489E9162) diff --git a/ext/native-decls/SetEntityOrphanMode.md b/ext/native-decls/SetEntityOrphanMode.md new file mode 100644 index 0000000000..81eb45f501 --- /dev/null +++ b/ext/native-decls/SetEntityOrphanMode.md @@ -0,0 +1,32 @@ +--- +ns: CFX +apiset: server +--- +## SET_ENTITY_ORPHAN_MODE + +```c +void SET_ENTITY_ORPHAN_MODE(Entity entity, int orphanMode); +``` + +```c +enum EntityOrphanMode { + // Default, this will delete the entity when it isn't relevant to any players + // NOTE: this *doesn't* mean when they're no longer in scope + DeleteWhenNotRelevant = 0, + // The entity will be deleted whenever its original owner disconnects + // NOTE: if this is set when the entities original owner has already left it will be + // marked for deletion (similar to just calling DELETE_ENTITY) + DeleteOnOwnerDisconnect = 1, + // The entity will never be deleted by the server when it does relevancy checks + // you should only use this on entities that need to be relatively persistent + KeepEntity = 2 +} +``` + +Sets what happens when the entity is orphaned and no longer has its original owner. + +**NOTE**: This native doesn't guarantee the persistence of the entity. + +## Parameters +* **entity**: The entity handle to override the distance culling radius. +* **orphanMode**: The mode that the server should use for determining if an entity should be removed.