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 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;