Unique network transmission handling of flares

This patch addresses issues concerning a1796ed71f:

Allow for a more elegant implementation of observer flares.
And still display flares even if the sender is lagging behind:
Split off flares from simulation commands.
Remove the new, problematic 'observer commands' entirely.
Provide an engine function 'SendNetworkFlare' to the JS interface.
-> which sets off the (pretty ordinary) transmission process.
Add a new type of net messages exclusively for flares
-> contains the flare's position and its sender's GUID.
This commit is contained in:
Vantha
2024-12-03 21:24:55 +01:00
committed by phosit
parent 75205699d0
commit 960bd5eace
11 changed files with 125 additions and 55 deletions
@@ -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)
@@ -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.
*/
@@ -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,
@@ -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.
*/
+35
View File
@@ -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<uint>(NMT_FLARE));
CFlareMessage* message = static_cast<CFlareMessage*>(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;
}
+7
View File
@@ -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);
+4
View File
@@ -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;
+9 -1
View File
@@ -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()
+13
View File
@@ -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);
+1
View File
@@ -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);
@@ -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");
}
}