From 4dde88ccc767939ae22f67ed96b89e9fd6168898 Mon Sep 17 00:00:00 2001 From: cocorico8 Date: Sun, 14 Jun 2026 18:24:37 +0200 Subject: [PATCH] server: add unreliable (UDP) event support Add MP.TriggerClientEventUnreliable and MP.TriggerClientEventJsonUnreliable Lua bindings, mirror of the reliable versions using UDP transport ('e' packets instead of 'E'). InternalTriggerClientEvent now takes a Rel flag to select between TCP and UDP delivery. 'E' and 'e' packets both route through the same HandleEvent handler. Client events reach Lua handlers identically regardless of transport reliability. Use case: high-frequency loss-tolerant data where TCP retransmission of stale data is actively harmful. --- include/LuaAPI.h | 2 ++ src/LuaAPI.cpp | 21 +++++++++++++++------ src/TLuaEngine.cpp | 2 ++ src/TServer.cpp | 3 +++ 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/include/LuaAPI.h b/include/LuaAPI.h index 772d35c2..d1d32ac6 100644 --- a/include/LuaAPI.h +++ b/include/LuaAPI.h @@ -32,6 +32,8 @@ namespace MP { std::tuple GetServerVersion(); std::pair TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& Data); std::pair TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data); + std::pair TriggerClientEventUnreliable(int PlayerID, const std::string& EventName, const sol::object& Data); + std::pair TriggerClientEventJsonUnreliable(int PlayerID, const std::string& EventName, const sol::table& Data); inline size_t GetPlayerCount() { return Engine->Server().ClientCount(); } std::pair DropPlayer(int ID, std::optional MaybeReason); std::pair SendChatMessage(int ID, const std::string& Message, const bool& LogChat = true); diff --git a/src/LuaAPI.cpp b/src/LuaAPI.cpp index bb16dda0..0173783c 100644 --- a/src/LuaAPI.cpp +++ b/src/LuaAPI.cpp @@ -136,10 +136,10 @@ TEST_CASE("LuaAPI::MP::GetServerVersion") { CHECK(pa == real.patch); } -static inline std::pair InternalTriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data) { - std::string Packet = "E:" + EventName + ":" + Data; +static inline std::pair InternalTriggerClientEvent(int PlayerID, const std::string& EventName, const std::string& Data, bool Rel) { + std::string Packet = std::string(Rel ? "E:" : "e:") + EventName + ":" + Data; if (PlayerID == -1) { - LuaAPI::MP::Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, true); + LuaAPI::MP::Engine->Network().SendToAll(nullptr, StringToVector(Packet), true, Rel); return { true, "" }; } else { auto MaybeClient = GetClient(LuaAPI::MP::Engine->Server(), PlayerID); @@ -149,7 +149,7 @@ static inline std::pair InternalTriggerClientEvent(int Player return { false, "Player hasn't joined yet" }; } - if (!LuaAPI::MP::Engine->Network().Respond(*c, StringToVector(Packet), true)) { + if (!LuaAPI::MP::Engine->Network().Respond(*c, StringToVector(Packet), Rel)) { beammp_lua_errorf("Respond failed, dropping client {}", PlayerID); LuaAPI::MP::Engine->Network().ClientKick(*c, "Disconnected after failing to receive packets"); return { false, "Respond failed, dropping client" }; @@ -165,7 +165,12 @@ static inline std::pair InternalTriggerClientEvent(int Player std::pair LuaAPI::MP::TriggerClientEvent(int PlayerID, const std::string& EventName, const sol::object& DataObj) { std::string Data = DataObj.as(); - return InternalTriggerClientEvent(PlayerID, EventName, Data); + return InternalTriggerClientEvent(PlayerID, EventName, Data, true); +} + +std::pair LuaAPI::MP::TriggerClientEventUnreliable(int PlayerID, const std::string& EventName, const sol::object& DataObj) { + std::string Data = DataObj.as(); + return InternalTriggerClientEvent(PlayerID, EventName, Data, false); } std::pair LuaAPI::MP::DropPlayer(int ID, std::optional MaybeReason) { @@ -865,5 +870,9 @@ std::string LuaAPI::MP::JsonUnflatten(const std::string& json) { } std::pair LuaAPI::MP::TriggerClientEventJson(int PlayerID, const std::string& EventName, const sol::table& Data) { - return InternalTriggerClientEvent(PlayerID, EventName, JsonEncode(Data)); + return InternalTriggerClientEvent(PlayerID, EventName, JsonEncode(Data), true); } + +std::pair LuaAPI::MP::TriggerClientEventJsonUnreliable(int PlayerID, const std::string& EventName, const sol::table& Data) { + return InternalTriggerClientEvent(PlayerID, EventName, JsonEncode(Data), false); +} \ No newline at end of file diff --git a/src/TLuaEngine.cpp b/src/TLuaEngine.cpp index f92ff3cb..f35dcd87 100644 --- a/src/TLuaEngine.cpp +++ b/src/TLuaEngine.cpp @@ -922,6 +922,8 @@ TLuaEngine::StateThreadData::StateThreadData(const std::string& Name, TLuaStateI }); MPTable.set_function("TriggerClientEvent", &LuaAPI::MP::TriggerClientEvent); MPTable.set_function("TriggerClientEventJson", &LuaAPI::MP::TriggerClientEventJson); + MPTable.set_function("TriggerClientEventUnreliable", &LuaAPI::MP::TriggerClientEventUnreliable); + MPTable.set_function("TriggerClientEventJsonUnreliable", &LuaAPI::MP::TriggerClientEventJsonUnreliable); MPTable.set_function("GetPlayerCount", &LuaAPI::MP::GetPlayerCount); MPTable.set_function("IsPlayerConnected", &LuaAPI::MP::IsPlayerConnected); MPTable.set_function("GetPlayerIDByName", [&](const std::string& Name) -> int { diff --git a/src/TServer.cpp b/src/TServer.cpp index f4b0a452..4a9af07e 100644 --- a/src/TServer.cpp +++ b/src/TServer.cpp @@ -286,6 +286,9 @@ void TServer::GlobalParser(const std::weak_ptr& Client, std::vectorGetName(), LockedClient->GetID()); return; } + // 'E' (reliable) and 'e' (unreliable) both route through HandleEvent + [[fallthrough]]; + case 'e': HandleEvent(*LockedClient, StringPacket); return; case 'N':