Skip to content

Commit

Permalink
feat(fivem/state): add native to allow entities to reject client dele…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
AvarianKnight committed Mar 19, 2024
1 parent 8b93ef2 commit bff6769
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,8 @@ struct SyncEntityState
bool passedFilter = false;
bool wantsReassign = false;
bool firstOwnerDropped = false;
bool serverCreated = false;
bool rejectClientDeletion = false;

std::list<std::function<void(const fx::ClientSharedPtr& ptr)>> onCreationRPC;

Expand Down Expand Up @@ -893,19 +895,24 @@ struct SyncEntityState
return scriptHash;
}

inline bool IsServerCreated()
{
return serverCreated;
}

inline bool IsOwnedByScript()
{
return GetScriptHash() != 0;
}

inline bool IsOwnedByClientScript()
{
return GetScriptHash() != 0 && creationToken == 0;
return (GetScriptHash() != 0 && creationToken == 0) && !serverCreated;
}

inline bool IsOwnedByServerScript()
{
return GetScriptHash() != 0 && creationToken != 0;
return (GetScriptHash() != 0 && creationToken != 0) || serverCreated;
}

// MAKE SURE YOU HAVE A LOCK BEFORE CALLING THIS
Expand Down Expand Up @@ -1453,6 +1460,12 @@ class ServerGameState : public ServerGameStatePublic, public fx::IAttached<fx::S
void SendArrayData(const fx::ClientSharedPtr& client);

public:
/// <summary>
/// Moves the entity to another player that the entity is relevant to
/// </summary>
/// <param name="entity"></param>
/// <param name="client"></param>
/// <returns>This returns false if there wasn't a client this entity is relevant to, AND the entity wasn't server created</returns>
bool MoveEntityToCandidate(const fx::sync::SyncEntityPtr& entity, const fx::ClientSharedPtr& client);

void SendPacket(int peer, std::string_view data) override;
Expand Down
26 changes: 24 additions & 2 deletions code/components/citizen-server-impl/src/state/ServerGameState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ std::shared_ptr<ConVar<fx::OneSyncState>> g_oneSyncVar;
std::shared_ptr<ConVar<bool>> g_oneSyncPopulation;
std::shared_ptr<ConVar<bool>> g_oneSyncARQ;

// disallow client from deleting server side entities
std::shared_ptr<ConVar<bool>> g_disallowClientDeleteVar;
static bool g_disallowClientDelete;

static std::shared_ptr<ConVar<bool>> g_networkedSoundsEnabledVar;
static bool g_networkedSoundsEnabled;

Expand Down Expand Up @@ -955,7 +959,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->IsServerCreated()) && (entity->type != sync::NetObjEntityType::Player || !entity->GetClient()))
{
FinalizeClone({}, entity, entity->handle, 0, "Regular entity GC");
continue;
Expand Down Expand Up @@ -3077,7 +3081,7 @@ void ServerGameState::ProcessCloneRemove(const fx::ClientSharedPtr& client, rl::

auto entity = GetEntity(0, objectId);

// ack remove no matter if we accept it
// ack remove as the calling client will have removed this entity anyways
ackPacket.Write(3, 3);
ackPacket.Write(13, objectId);
ackPacket.Write(16, uniqifier);
Expand All @@ -3101,6 +3105,21 @@ void ServerGameState::ProcessCloneRemove(const fx::ClientSharedPtr& client, rl::
return;
}

if (entity->rejectClientDeletion)
{
const auto clientData = GetClientDataUnlocked(this, client);
const auto entIdentifier = MakeHandleUniqifierPair(objectId, uniqifier);
if (auto syncData = clientData->syncedEntities.find(entIdentifier); syncData != clientData->syncedEntities.end())
{
GS_LOG("%s: tried to delete server spawned entity setting entity id %d to be recreated for client %d\n", __func__, objectId, client->GetNetId());
// Reset the created state so they will get resent the entity data on the next server tick
syncData->second.hasCreated = false;
syncData->second.hasNAckedCreate = false;
}

return;
}

GS_LOG("%s: queueing remove (%d - %d)\n", __func__, objectId, uniqifier);
RemoveClone(client, objectId, uniqifier);
}
Expand Down Expand Up @@ -3213,6 +3232,8 @@ auto ServerGameState::CreateEntityFromTree(sync::NetObjEntityType type, const st
entity->creationToken = msec().count();
entity->createdAt = msec();
entity->passedFilter = true;
entity->serverCreated = true;
entity->rejectClientDeletion = g_disallowClientDelete;

entity->syncTree = tree;

Expand Down Expand Up @@ -6949,6 +6970,7 @@ static InitFunction initFunction([]()

g_requestControlVar = instance->AddVariable<int>("sv_filterRequestControl", ConVar_None, (int)RequestControlFilterMode::NoFilter, (int*)&g_requestControlFilterState);
g_requestControlSettleVar = instance->AddVariable<int>("sv_filterRequestControlSettleTimer", ConVar_None, 30000, &g_requestControlSettleDelay);
g_disallowClientDeleteVar = instance->AddVariable<bool>("sv_disallowClientDelete", ConVar_None, false, &g_disallowClientDelete);

fx::SetOneSyncGetCallback([]()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,20 @@ static void Init()
{
return int(entity->routingBucket);
}));


fx::ScriptEngine::RegisterNativeHandler("SET_ENTITY_REJECTS_CLIENT_DELETION", makeEntityFunction([](fx::ScriptContext& context, const fx::sync::SyncEntityPtr& entity)
{
if (context.GetArgumentCount() > 1)
{
auto rejectClientDelete = context.GetArgument<bool>(1);

entity->rejectClientDeletion = rejectClientDelete;
}

return true;
}));


fx::ScriptEngine::RegisterNativeHandler("SET_ENTITY_ROUTING_BUCKET", makeEntityFunction([](fx::ScriptContext& context, const fx::sync::SyncEntityPtr& entity)
{
Expand Down
17 changes: 17 additions & 0 deletions ext/native-decls/SetEntityRejectClientDeletion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
ns: CFX
apiset: server
---
## SET_ENTITY_REJECTS_CLIENT_DELETION

```c
void SET_ENTITY_REJECTS_CLIENT_DELETION(Entity entity, bool rejectClientDelete);
```
Sets a flag for the server to reject any client side deletion of this entity.
This flag will default to the value of "sv_disallowClientDelete" when the entity was created
## Parameters
* **entity**: The entity to reject deletion of
* **rejectClientDelete**: A boolean of whether the server should reject client delete, or accept it

0 comments on commit bff6769

Please sign in to comment.