From fa8ee391860e1d8db4908f6073b522357a26aa48 Mon Sep 17 00:00:00 2001 From: Federico Romero Date: Mon, 22 Jun 2026 21:43:03 -0300 Subject: [PATCH 1/2] Fix warpPedIntoVehicle right after createVehicle Warping a player into a vehicle created moments earlier (like in onResourceStart) used to send the warp before clients even knew the vehicle existed, so they just ignored it. The server kept thinking the player was in the vehicle, the client never agreed, and that mismatch is what caused the endless "NETWORK TROUBLE" freeze. Now it waits until clients actually know about the vehicle before sending the warp, instead of dropping it. --- Server/mods/deathmatch/logic/CResource.cpp | 7 +++++ Server/mods/deathmatch/logic/CResource.h | 16 ++++++++++++ .../logic/CStaticFunctionDefinitions.cpp | 26 ++++++++++++++----- .../logic/CStaticFunctionDefinitions.h | 2 +- .../deathmatch/logic/luadefs/CLuaPedDefs.cpp | 10 +++++-- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/Server/mods/deathmatch/logic/CResource.cpp b/Server/mods/deathmatch/logic/CResource.cpp index 496aafb121e..d63119ad9f9 100644 --- a/Server/mods/deathmatch/logic/CResource.cpp +++ b/Server/mods/deathmatch/logic/CResource.cpp @@ -1069,6 +1069,13 @@ bool CResource::Start(std::list* pDependents, bool bManualStart, con SendNoClientCacheScripts(); m_bClientSync = true; + // Run anything that got held back during onResourceStart because it depended on clients already knowing + // about elements we've just finished broadcasting above (see RunOrDeferUntilClientSynced) + std::vector> pendingCallbacks = std::move(m_PendingClientSyncCallbacks); + m_PendingClientSyncCallbacks.clear(); + for (const auto& callback : pendingCallbacks) + callback(); + // Add us to the running resources list m_StartedResources.push_back(this); diff --git a/Server/mods/deathmatch/logic/CResource.h b/Server/mods/deathmatch/logic/CResource.h index aff0364111e..98bb3c7e152 100644 --- a/Server/mods/deathmatch/logic/CResource.h +++ b/Server/mods/deathmatch/logic/CResource.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -228,6 +229,19 @@ class CResource : public EHS bool IsClientSynced() const noexcept { return m_bClientSync; } + // Runs the callback now if our elements have already reached the clients, otherwise holds onto it and runs it + // right after they do (see Start()). Used for things that need to tell clients about an element created moments + // earlier in onResourceStart, which wouldn't make sense to the client yet (e.g. warpPedIntoVehicle on a vehicle + // created in the same event) - dropping it outright would leave the server and clients permanently disagreeing + // about that element's state instead. + void RunOrDeferUntilClientSynced(std::function callback) + { + if (m_bClientSync) + callback(); + else + m_PendingClientSyncCallbacks.push_back(std::move(callback)); + } + const SString& GetName() const noexcept { return m_strResourceName; } CLuaMain* GetVirtualMachine() { return m_pVM; } @@ -372,6 +386,8 @@ class CResource : public EHS EResourceState m_eState = EResourceState::None; bool m_bClientSync = false; + std::vector> m_PendingClientSyncCallbacks; + unsigned short m_usNetID = -1; uint m_uiScriptID = -1; diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp index 7bcd5afe321..f6e597d93e5 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.cpp @@ -4293,7 +4293,7 @@ bool CStaticFunctionDefinitions::SetPedWeaponSlot(CElement* pElement, unsigned c return false; } -bool CStaticFunctionDefinitions::WarpPedIntoVehicle(CPed* pPed, CVehicle* pVehicle, unsigned int uiSeat) +bool CStaticFunctionDefinitions::WarpPedIntoVehicle(CPed* pPed, CVehicle* pVehicle, unsigned int uiSeat, CResource* pCallingResource) { assert(pPed); assert(pVehicle); @@ -4347,12 +4347,24 @@ bool CStaticFunctionDefinitions::WarpPedIntoVehicle(CPed* pPed, CVehicle* pVehic if (uiSeat == 0 && g_pGame->IsWorldSpecialPropertyEnabled(WorldSpecialProperty::VEHICLE_ENGINE_AUTOSTART)) pVehicle->SetEngineOn(true); - // Tell all the players - CBitStream BitStream; - BitStream.pBitStream->Write(pVehicle->GetID()); - BitStream.pBitStream->Write(static_cast(uiSeat)); - BitStream.pBitStream->Write(pPed->GenerateSyncTimeContext()); - m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pPed, WARP_PED_INTO_VEHICLE, *BitStream.pBitStream)); + // Tell all the players. If the calling resource's elements haven't reached the clients yet + // (e.g. called from onResourceStart on a vehicle created in the same event), hold off until + // they have instead of just dropping it - the vehicle itself isn't synced to clients yet + // either, and an RPC referencing an unknown element there would leave the server and clients + // permanently disagreeing about whether this ped is in a vehicle. + auto sendWarpRpc = [pPed, pVehicle, uiSeat]() + { + CBitStream BitStream; + BitStream.pBitStream->Write(pVehicle->GetID()); + BitStream.pBitStream->Write(static_cast(uiSeat)); + BitStream.pBitStream->Write(pPed->GenerateSyncTimeContext()); + m_pPlayerManager->BroadcastOnlyJoined(CElementRPCPacket(pPed, WARP_PED_INTO_VEHICLE, *BitStream.pBitStream)); + }; + + if (pCallingResource) + pCallingResource->RunOrDeferUntilClientSynced(sendWarpRpc); + else + sendWarpRpc(); // Call the player->vehicle event CLuaArguments PlayerVehicleArguments; diff --git a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h index 911710b06b4..4578adb20ec 100644 --- a/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h +++ b/Server/mods/deathmatch/logic/CStaticFunctionDefinitions.h @@ -213,7 +213,7 @@ class CStaticFunctionDefinitions static bool SetPedGravity(CElement* pElement, float fGravity); static bool SetPedChoking(CElement* pElement, bool bChoking); static bool SetPedWeaponSlot(CElement* pElement, unsigned char ucWeaponSlot); - static bool WarpPedIntoVehicle(CPed* pPed, CVehicle* pVehicle, unsigned int uiSeat = 0); + static bool WarpPedIntoVehicle(CPed* pPed, CVehicle* pVehicle, unsigned int uiSeat = 0, CResource* pCallingResource = nullptr); static bool RemovePedFromVehicle(CElement* pElement); static bool SetPedDoingGangDriveby(CElement* pElement, bool bGangDriveby); static bool SetPedAnimation(CElement* pElement, const SString& blockName, const SString& animName, int iTime, int iBlend, bool bLoop, bool bUpdatePosition, diff --git a/Server/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp b/Server/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp index 135e5a4e9a8..e6cb57189b3 100644 --- a/Server/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp +++ b/Server/mods/deathmatch/logic/luadefs/CLuaPedDefs.cpp @@ -1360,7 +1360,10 @@ int CLuaPedDefs::WarpPedIntoVehicle(lua_State* luaVM) { LogWarningIfPlayerHasNotJoinedYet(luaVM, pPed); - if (CStaticFunctionDefinitions::WarpPedIntoVehicle(pPed, pVehicle, uiSeat)) + CLuaMain* pLuaMain = g_pGame->GetLuaManager()->GetVirtualMachine(luaVM); + CResource* pResource = pLuaMain ? pLuaMain->GetResource() : nullptr; + + if (CStaticFunctionDefinitions::WarpPedIntoVehicle(pPed, pVehicle, uiSeat, pResource)) { lua_pushboolean(luaVM, true); return 1; @@ -1394,7 +1397,10 @@ int CLuaPedDefs::OOP_WarpPedIntoVehicle(lua_State* luaVM) { LogWarningIfPlayerHasNotJoinedYet(luaVM, pPed); - if (CStaticFunctionDefinitions::WarpPedIntoVehicle(pPed, pVehicle, uiSeat)) + CLuaMain* pLuaMain = g_pGame->GetLuaManager()->GetVirtualMachine(luaVM); + CResource* pResource = pLuaMain ? pLuaMain->GetResource() : nullptr; + + if (CStaticFunctionDefinitions::WarpPedIntoVehicle(pPed, pVehicle, uiSeat, pResource)) { lua_pushboolean(luaVM, true); return 1; From cdcf86941c25a281d7d69edf08c9db8c9817a023 Mon Sep 17 00:00:00 2001 From: Federico Romero Date: Mon, 22 Jun 2026 21:43:18 -0300 Subject: [PATCH 2/2] Keep replying to a player while they're entering a vehicle The server stops answering a player's position updates the instant they're marked as being in a vehicle, since it expects vehicle updates instead from then on. Usually that's a split-second gap, but it was enough to occasionally trigger the client's network trouble warning. --- Server/mods/deathmatch/logic/CGame.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Server/mods/deathmatch/logic/CGame.cpp b/Server/mods/deathmatch/logic/CGame.cpp index bdccfaea790..94eb759f744 100644 --- a/Server/mods/deathmatch/logic/CGame.cpp +++ b/Server/mods/deathmatch/logic/CGame.cpp @@ -2389,7 +2389,14 @@ void CGame::Packet_PlayerPuresync(CPlayerPuresyncPacket& Packet) { // Allow it if he's exiting if (pPlayer->GetVehicleAction() != CPed::VEHICLEACTION_EXITING) + { + // Still acknowledge so the client's network-trouble watchdog doesn't trip while it catches up + // to the new vehicle-occupied state (e.g. just after warpPedIntoVehicle, before it starts + // sending vehicle puresync packets instead of these on-foot ones) + if ((pPlayer->GetPuresyncCount() % 4) == 0) + pPlayer->Send(CReturnSyncPacket(pPlayer)); return; + } } // Send a returnsync packet to the player that sent it