diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js index db58cf0a86..a54b786e51 100644 --- a/binaries/data/mods/public/gui/session/input.js +++ b/binaries/data/mods/public/gui/session/input.js @@ -1271,14 +1271,9 @@ function triggerFlareAction(position) g_LastFlareTime = now; - const guid = Engine.GetPlayerGUID(); - displayFlare(position, guid); - Engine.PlayUISound(g_FlareSound, false); - Engine.PostNetworkCommand({ - "type": "map-flare", - "position": position, - "guid": guid - }); + renderAndPlayFlare(position, Engine.GetPlayerGUID()); + if (Engine.HasNetClient()) + Engine.SendNetworkFlare(position); } function handleUnitAction(position, action) diff --git a/binaries/data/mods/public/gui/session/messages.js b/binaries/data/mods/public/gui/session/messages.js index 7f688c5347..5e508d4698 100644 --- a/binaries/data/mods/public/gui/session/messages.js +++ b/binaries/data/mods/public/gui/session/messages.js @@ -96,6 +96,9 @@ var g_NetMessageTypes = { "text": msg.text }); }, + "flare": msg => { + handleFlare(msg); + }, "gamesetup": msg => {}, // Needed for autostart "start": msg => {} }; @@ -287,29 +290,6 @@ var g_NotificationsTypes = } global.music.setLocked(notification.lock); - }, - "map-flare": function(notification, player) - { - const shouldSeeFlare = g_IsObserver || g_Players[player]?.isMutualAlly[Engine.GetPlayerID()]; - - // Don't display for the player that did the flare because they will see it immediately. - if (!shouldSeeFlare || notification.guid == Engine.GetPlayerGUID()) - return; - - let now = Date.now(); - if (g_FlareRateLimitLastTimes.length) - { - g_FlareRateLimitLastTimes = g_FlareRateLimitLastTimes.filter(t => now - t < g_FlareRateLimitScope * 1000); - if (g_FlareRateLimitLastTimes.length >= g_FlareRateLimitMaximumFlares) - { - warn("Received too many flares. Dropping a flare request by '" + g_Players[player].name + "'."); - return; - } - } - g_FlareRateLimitLastTimes.push(now); - - displayFlare(notification.position, notification.guid); - Engine.PlayUISound(g_FlareSound, false); } }; @@ -524,6 +504,30 @@ function clearChatMessages() g_Chat.ChatOverlay.clearChatMessages(); } +function handleFlare(data) +{ + const playerID = g_PlayerAssignments[data.guid].player; + const shouldSeeFlare = g_IsObserver || g_Players[playerID]?.isMutualAlly[Engine.GetPlayerID()]; + + // Don't display for the player that sent the flare because they will see it immediately. + if (!shouldSeeFlare || data.guid == Engine.GetPlayerGUID()) + return; + + let now = Date.now(); + if (g_FlareRateLimitLastTimes.length) + { + g_FlareRateLimitLastTimes = g_FlareRateLimitLastTimes.filter(t => now - t < g_FlareRateLimitScope * 1000); + if (g_FlareRateLimitLastTimes.length >= g_FlareRateLimitMaximumFlares) + { + warn("Received too many flares. Dropping a flare request by '" + g_PlayerAssignments[data.guid].name + "'."); + return; + } + } + g_FlareRateLimitLastTimes.push(now); + + renderAndPlayFlare(data.position, data.guid); +} + /** * This function is used for AIs, whose names don't exist in g_PlayerAssignments. */ diff --git a/binaries/data/mods/public/gui/session/unit_actions.js b/binaries/data/mods/public/gui/session/unit_actions.js index 62673e1198..ca4880f9b5 100644 --- a/binaries/data/mods/public/gui/session/unit_actions.js +++ b/binaries/data/mods/public/gui/session/unit_actions.js @@ -1937,7 +1937,7 @@ function DrawTargetMarker(position) }); } -function displayFlare(position, playerGUID) +function renderAndPlayFlare(position, playerGUID) { const playerID = g_PlayerAssignments[playerGUID].player; Engine.GuiInterfaceCall("AddTargetMarker", { @@ -1948,6 +1948,7 @@ function displayFlare(position, playerGUID) "owner": playerID != -1 ? playerID : 0 }); g_MiniMapPanel.flare(position, playerID); + Engine.PlayUISound(g_FlareSound, false); addChatMessage({ "type": "flare", "guid": playerGUID, diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index 8e301bb145..8bd77366fd 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -4,13 +4,6 @@ var g_DebugCommands = false; function ProcessCommand(player, cmd) { - if (player == -1) - { - if (g_ObserverCommands[cmd.type]) - g_ObserverCommands[cmd.type](player, cmd, {}); - return; - } - let cmpPlayer = QueryPlayerIDInterface(player); if (!cmpPlayer) return; @@ -882,17 +875,6 @@ var g_Commands = { } }, - "map-flare": function(player, cmd, data) - { - let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); - cmpGuiInterface.PushNotification({ - "type": "map-flare", - "players": [player], - "guid": cmd.guid, - "position": cmd.position - }); - }, - "autoqueue-on": function(player, cmd, data) { for (let ent of data.entities) @@ -915,10 +897,6 @@ var g_Commands = { }; -var g_ObserverCommands = { - "map-flare": g_Commands["map-flare"] -}; - /** * Sends a GUI notification about unit(s) that failed to ungarrison. */ diff --git a/source/network/NetClient.cpp b/source/network/NetClient.cpp index 6c9da4adf8..ef88a5906f 100644 --- a/source/network/NetClient.cpp +++ b/source/network/NetClient.cpp @@ -116,6 +116,7 @@ CNetClient::CNetClient(CGame* game) : AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, &OnGameSetup, this); AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, &OnPlayerAssignment, this); AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, &OnInGame, this); + AddTransition(NCS_INGAME, (uint)NMT_FLARE, NCS_INGAME, &OnFlare, this); AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, &OnInGame, this); AddTransition(NCS_INGAME, (uint)NMT_END_COMMAND_BATCH, NCS_INGAME, &OnInGame, this); @@ -489,6 +490,15 @@ void CNetClient::SendStartGameMessage(const CStr& initAttribs) SendMessage(&gameStart); } +void CNetClient::SendFlareMessage(const CStr& positionX, const CStr& positionY, const CStr& positionZ) +{ + CFlareMessage flare; + flare.m_PositionX = positionX; + flare.m_PositionY = positionY; + flare.m_PositionZ = positionZ; + SendMessage(&flare); +} + void CNetClient::SendRejoinedMessage() { CRejoinedMessage rejoinedMessage; @@ -963,3 +973,28 @@ bool CNetClient::OnInGame(CNetClient* client, CFsmEvent* event) return true; } + +bool CNetClient::OnFlare(CNetClient* client, CFsmEvent* event) +{ + ENSURE(event->GetType() == static_cast(NMT_FLARE)); + + CFlareMessage* message = static_cast(event->GetParamRef()); + + const ScriptInterface& scriptInterface = client->m_Game->GetSimulation2()->GetScriptInterface(); + ScriptRequest rq(scriptInterface); + JS::RootedValue position(rq.cx); + Script::CreateObject( + rq, &position, + // The coordinates are transmitted as strings (because because direct (de)serialisation of floating point numbers is not supported). + "x", message->m_PositionX.ToDouble(), + "y", message->m_PositionY.ToDouble(), + "z", message->m_PositionZ.ToDouble() + ); + + client->PushGuiMessage( + "type", "flare", + "guid", message->m_GUID, + "position", position); + + return true; +} diff --git a/source/network/NetClient.h b/source/network/NetClient.h index 41671d9b24..f17646cec4 100644 --- a/source/network/NetClient.h +++ b/source/network/NetClient.h @@ -18,6 +18,7 @@ #ifndef NETCLIENT_H #define NETCLIENT_H + #include "network/FSM.h" #include "network/NetFileTransfer.h" #include "network/NetHost.h" @@ -236,6 +237,11 @@ public: void SendStartGameMessage(const CStr& initAttribs); + /** + * Call when the client (player or observer) has sent a flare. + */ + void SendFlareMessage(const CStr& positionX, const CStr& positionY, const CStr& positionZ); + /** * Call when the client has rejoined a running match and finished * the loading screen. @@ -279,6 +285,7 @@ private: static bool OnGameStart(CNetClient* client, CFsmEvent* event); static bool OnJoinSyncStart(CNetClient* client, CFsmEvent* event); static bool OnJoinSyncEndCommandBatch(CNetClient* client, CFsmEvent* event); + static bool OnFlare(CNetClient* client, CFsmEvent* event); static bool OnRejoined(CNetClient* client, CFsmEvent* event); static bool OnKicked(CNetClient* client, CFsmEvent* event); static bool OnClientTimeout(CNetClient* client, CFsmEvent* event); diff --git a/source/network/NetMessage.cpp b/source/network/NetMessage.cpp index 9515d939de..2dabfacf8e 100644 --- a/source/network/NetMessage.cpp +++ b/source/network/NetMessage.cpp @@ -207,6 +207,10 @@ CNetMessage* CNetMessageFactory::CreateMessage(const void* pData, pNewMessage = new CSimulationMessage(scriptInterface); break; + case NMT_FLARE: + pNewMessage = new CFlareMessage; + break; + case NMT_CLEAR_ALL_READY: pNewMessage = new CClearAllReadyMessage; break; diff --git a/source/network/NetMessages.h b/source/network/NetMessages.h index f2259d0a7e..1f67256f83 100644 --- a/source/network/NetMessages.h +++ b/source/network/NetMessages.h @@ -80,7 +80,8 @@ enum NetMessageType NMT_SYNC_CHECK, // OOS-detection hash checking NMT_SYNC_ERROR, // OOS-detection error - NMT_SIMULATION_COMMAND + NMT_SIMULATION_COMMAND, + NMT_FLARE }; // Authentication result codes @@ -177,6 +178,13 @@ START_NMT_CLASS_(JoinSyncStart, NMT_JOIN_SYNC_START) NMT_FIELD(CStr, m_InitAttributes) END_NMT_CLASS() +START_NMT_CLASS_(Flare, NMT_FLARE) + NMT_FIELD(CStr, m_GUID) // ignored when client->server, valid when server->client + NMT_FIELD(CStr, m_PositionX) + NMT_FIELD(CStr, m_PositionY) + NMT_FIELD(CStr, m_PositionZ) +END_NMT_CLASS() + START_NMT_CLASS_(Rejoined, NMT_REJOINED) NMT_FIELD(CStr, m_GUID) END_NMT_CLASS() diff --git a/source/network/NetServer.cpp b/source/network/NetServer.cpp index d4ab15c40b..a4c6f01a11 100644 --- a/source/network/NetServer.cpp +++ b/source/network/NetServer.cpp @@ -673,6 +673,7 @@ void CNetServerWorker::SetupSession(CNetServerSession* session) session->AddTransition(NSS_INGAME, (uint)NMT_CONNECTION_LOST, NSS_UNCONNECTED, &OnDisconnect, session); session->AddTransition(NSS_INGAME, (uint)NMT_CHAT, NSS_INGAME, &OnChat, session); session->AddTransition(NSS_INGAME, (uint)NMT_SIMULATION_COMMAND, NSS_INGAME, &OnSimulationCommand, session); + session->AddTransition(NSS_INGAME, (uint)NMT_FLARE, NSS_INGAME, &OnFlare, session); session->AddTransition(NSS_INGAME, (uint)NMT_SYNC_CHECK, NSS_INGAME, &OnSyncCheck, session); session->AddTransition(NSS_INGAME, (uint)NMT_END_COMMAND_BATCH, NSS_INGAME, &OnEndCommandBatch, session); @@ -1194,6 +1195,18 @@ bool CNetServerWorker::OnSimulationCommand(CNetServerSession* session, CFsmEvent return true; } +bool CNetServerWorker::OnFlare(CNetServerSession* session, CFsmEvent* event) +{ + ENSURE(event->GetType() == (uint)NMT_FLARE); + + CNetServerWorker& server = session->GetServer(); + CFlareMessage* message = (CFlareMessage*)event->GetParamRef(); + message->m_GUID = session->GetGUID(); + server.Broadcast(message, { NSS_INGAME }); + + return true; +} + bool CNetServerWorker::OnSyncCheck(CNetServerSession* session, CFsmEvent* event) { ENSURE(event->GetType() == (uint)NMT_SYNC_CHECK); diff --git a/source/network/NetServer.h b/source/network/NetServer.h index d571ab8444..0e8f616021 100644 --- a/source/network/NetServer.h +++ b/source/network/NetServer.h @@ -297,6 +297,7 @@ private: static bool OnClientHandshake(CNetServerSession* session, CFsmEvent* event); static bool OnAuthenticate(CNetServerSession* session, CFsmEvent* event); static bool OnSimulationCommand(CNetServerSession* session, CFsmEvent* event); + static bool OnFlare(CNetServerSession* session, CFsmEvent* event); static bool OnSyncCheck(CNetServerSession* session, CFsmEvent* event); static bool OnEndCommandBatch(CNetServerSession* session, CFsmEvent* event); static bool OnChat(CNetServerSession* session, CFsmEvent* event); diff --git a/source/network/scripting/JSInterface_Network.cpp b/source/network/scripting/JSInterface_Network.cpp index f3ee514cdb..ffc897d87a 100644 --- a/source/network/scripting/JSInterface_Network.cpp +++ b/source/network/scripting/JSInterface_Network.cpp @@ -274,6 +274,29 @@ void SetTurnLength(int length) LOGERROR("Only network host can change turn length"); } +void SendNetworkFlare(JS::HandleValue position) +{ + ENSURE(g_NetClient); + + ScriptRequest rq(g_NetClient->GetScriptInterface()); + ENSURE(position.isObject()); + JS::RootedObject positionObj(rq.cx, &position.toObject()); + JS::RootedValue positionX(rq.cx); + JS::RootedValue positionY(rq.cx); + JS::RootedValue positionZ(rq.cx); + ENSURE(JS_GetProperty(rq.cx, positionObj, "x", &positionX)); + ENSURE(JS_GetProperty(rq.cx, positionObj, "y", &positionY)); + ENSURE(JS_GetProperty(rq.cx, positionObj, "z", &positionZ)); + + // (TODO?): Converting the doubles into strings here is a workaround because direct (de)serialisation of floating point numbers is not supported. + // It causes somewhat awkward message handling, but the resulting efficiency losses are negligible. + g_NetClient->SendFlareMessage( + CStr::FromDouble(positionX.toNumber()), + CStr::FromDouble(positionY.toNumber()), + CStr::FromDouble(positionZ.toNumber()) + ); +} + void RegisterScriptFunctions(const ScriptRequest& rq) { ScriptFunction::Register<&GetDefaultPort>(rq, "GetDefaultPort"); @@ -294,5 +317,6 @@ void RegisterScriptFunctions(const ScriptRequest& rq) ScriptFunction::Register<&ClearAllPlayerReady>(rq, "ClearAllPlayerReady"); ScriptFunction::Register<&StartNetworkGame>(rq, "StartNetworkGame"); ScriptFunction::Register<&SetTurnLength>(rq, "SetTurnLength"); + ScriptFunction::Register<&SendNetworkFlare>(rq, "SendNetworkFlare"); } }