2021-06-05 10:37:18 -07:00
|
|
|
/**
|
2021-06-12 15:35:30 -07:00
|
|
|
* The number of seconds we monitor to rate limit flares.
|
2021-06-05 10:37:18 -07:00
|
|
|
*/
|
2021-06-12 15:35:30 -07:00
|
|
|
var g_FlareRateLimitScope = 6;
|
2021-06-05 10:37:18 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The maximum allowed number of flares within g_FlareRateLimitScope seconds.
|
|
|
|
|
* This should be a bit larger than the number of flares that can be sent in theory by using the GUI.
|
|
|
|
|
*/
|
|
|
|
|
var g_FlareRateLimitMaximumFlares = 16;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Contains the arrival timestamps the flares of the last g_FlareRateLimitScope seconds.
|
|
|
|
|
*/
|
|
|
|
|
var g_FlareRateLimitLastTimes = [];
|
|
|
|
|
|
2019-10-21 08:00:59 -07:00
|
|
|
/**
|
|
|
|
|
* These handlers are called everytime a client joins or disconnects.
|
|
|
|
|
*/
|
|
|
|
|
var g_PlayerAssignmentsChangeHandlers = new Set();
|
|
|
|
|
|
2019-10-27 05:39:28 -07:00
|
|
|
/**
|
|
|
|
|
* These handlers are called when the ceasefire time has run out.
|
|
|
|
|
*/
|
|
|
|
|
var g_CeasefireEndedHandlers = new Set();
|
|
|
|
|
|
2019-10-30 06:48:27 -07:00
|
|
|
/**
|
|
|
|
|
* These handlers are fired when the match is networked and
|
|
|
|
|
* the current client established the connection, authenticated,
|
|
|
|
|
* finished the loading screen, starts or finished synchronizing after a rejoin.
|
|
|
|
|
* The messages are constructed in NetClient.cpp.
|
|
|
|
|
*/
|
|
|
|
|
var g_NetworkStatusChangeHandlers = new Set();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* These handlers are triggered whenever a client finishes the loading screen.
|
|
|
|
|
*/
|
|
|
|
|
var g_ClientsLoadingHandlers = new Set();
|
|
|
|
|
|
2019-10-30 04:14:55 -07:00
|
|
|
/**
|
|
|
|
|
* These handlers are fired if the server informed the players that the networked game is out of sync.
|
|
|
|
|
*/
|
|
|
|
|
var g_NetworkOutOfSyncHandlers = new Set();
|
|
|
|
|
|
2015-12-13 19:08:53 -08:00
|
|
|
/**
|
2015-12-25 16:42:52 -08:00
|
|
|
* Handle all netmessage types that can occur.
|
2015-12-13 19:08:53 -08:00
|
|
|
*/
|
2015-12-25 16:42:52 -08:00
|
|
|
var g_NetMessageTypes = {
|
2025-12-30 00:57:37 -08:00
|
|
|
"netstatus": msg =>
|
|
|
|
|
{
|
2016-06-16 10:13:02 -07:00
|
|
|
handleNetStatusMessage(msg);
|
|
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"netwarn": msg =>
|
|
|
|
|
{
|
2016-06-16 10:13:02 -07:00
|
|
|
addNetworkWarning(msg);
|
|
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"out-of-sync": msg =>
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const handler of g_NetworkOutOfSyncHandlers)
|
2019-10-30 04:14:55 -07:00
|
|
|
handler(msg);
|
2017-04-30 21:55:56 -07:00
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"players": msg =>
|
|
|
|
|
{
|
2016-06-16 10:13:02 -07:00
|
|
|
handlePlayerAssignmentsMessage(msg);
|
|
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"paused": msg =>
|
|
|
|
|
{
|
Implement session event subscription system and rewrite TopPanel, PlayerViewControl, GameSpeed, Pausing, ObjectivesDialog to use object orientation, refs #5387.
New controller classes: PlayerViewControl, PauseControl,
GameSpeedControl
New viewer classes: ObjectivesDialog, PauseOverlay, FollowPlayer,
TopPanel (BuildLabel, CivIcon, CounterManager, CounterPopulation,
CounterResource refs 7e14a33411/D1113, GameSpeedButton,
ObjectivesDialogButton)
New events: SimulationUpdate, EntitySelectionChange, ViewedPlayerChange,
PreViewedPlayerChangeHandler, PlayerIDChange, PlayersInit,
PlayersFinished, Pause, DiplomacyColorsChange, HotkeyChange, refs #2604
Improves GUI onSimuationUpdate performance without selected entities by
allegedly 30%.
Delete misleading dead code resign command from leaveGame and rename to
endGame. The command is not sent via network (see fa85527baf) nor
processed in simulation, because the Game instance is deleted
immediately thereafter, introduced in fcedcae052, refs a3e1c68b9a,
39ffb0a6bd, 9f796068f8.
Remove explicitResume 0 value from e57c99c6f6 and 8ae67ed15f which
should have been a false if defined, and is equivalent to the default.
Restore fast forwarding option from cd571035bb/D595 for developers
changing the perspective to observer or player following 56308ec1ad.
Add pausing for the delete dialog missing following 7a7ebaa983.
Differential Revision: https://code.wildfiregames.com/D2378
This was SVN commit r23076.
2019-10-17 08:08:56 -07:00
|
|
|
g_PauseControl.setClientPauseState(msg.guid, msg.pause);
|
2016-06-16 10:13:02 -07:00
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"clients-loading": msg =>
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const handler of g_ClientsLoadingHandlers)
|
2019-10-30 06:48:27 -07:00
|
|
|
handler(msg.guids);
|
2017-03-21 11:50:29 -07:00
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"rejoined": msg =>
|
|
|
|
|
{
|
2016-06-16 10:13:02 -07:00
|
|
|
addChatMessage({
|
|
|
|
|
"type": "rejoined",
|
|
|
|
|
"guid": msg.guid
|
|
|
|
|
});
|
|
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"kicked": msg =>
|
|
|
|
|
{
|
2016-06-16 10:13:02 -07:00
|
|
|
addChatMessage({
|
|
|
|
|
"type": "kicked",
|
|
|
|
|
"username": msg.username,
|
|
|
|
|
"banned": msg.banned
|
|
|
|
|
});
|
|
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"chat": msg =>
|
|
|
|
|
{
|
2016-06-16 10:13:02 -07:00
|
|
|
addChatMessage({
|
|
|
|
|
"type": "message",
|
|
|
|
|
"guid": msg.guid,
|
|
|
|
|
"text": msg.text
|
|
|
|
|
});
|
|
|
|
|
},
|
2025-12-30 00:57:37 -08:00
|
|
|
"flare": msg =>
|
|
|
|
|
{
|
2024-12-03 12:24:55 -08:00
|
|
|
handleFlare(msg);
|
|
|
|
|
},
|
2016-06-16 10:13:02 -07:00
|
|
|
"gamesetup": msg => {}, // Needed for autostart
|
|
|
|
|
"start": msg => {}
|
2015-12-25 16:42:52 -08:00
|
|
|
};
|
2014-01-02 08:22:03 -08:00
|
|
|
|
2016-09-03 02:11:02 -07:00
|
|
|
var g_PlayerStateMessages = {
|
|
|
|
|
"won": translate("You have won!"),
|
|
|
|
|
"defeated": translate("You have been defeated!")
|
|
|
|
|
};
|
|
|
|
|
|
2021-06-06 11:29:48 -07:00
|
|
|
var g_LastAttack;
|
|
|
|
|
|
2015-12-13 19:08:53 -08:00
|
|
|
/**
|
|
|
|
|
* Defines how the GUI reacts to notifications that are sent by the simulation.
|
2016-06-25 21:40:50 -07:00
|
|
|
* Don't open new pages (message boxes) here! Otherwise further notifications
|
|
|
|
|
* handled in the same turn can't access the GUI objects anymore.
|
2015-12-13 19:08:53 -08:00
|
|
|
*/
|
2014-06-19 07:37:08 -07:00
|
|
|
var g_NotificationsTypes =
|
2014-01-02 08:22:03 -08:00
|
|
|
{
|
2025-12-30 00:57:37 -08:00
|
|
|
"aichat": function(notification, player)
|
2014-10-14 11:58:41 -07:00
|
|
|
{
|
2025-12-30 00:57:37 -08:00
|
|
|
const message = {
|
|
|
|
|
"type": "message",
|
|
|
|
|
"text": notification.message,
|
|
|
|
|
"guid": findGuidForPlayerID(player) || -1,
|
|
|
|
|
"player": player,
|
|
|
|
|
"translate": true
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (notification.translateParameters)
|
|
|
|
|
{
|
|
|
|
|
message.translateParameters = notification.translateParameters;
|
|
|
|
|
message.parameters = notification.parameters;
|
|
|
|
|
colorizePlayernameParameters(notification.parameters);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addChatMessage(message);
|
|
|
|
|
},
|
|
|
|
|
"defeat": function(notification, player)
|
2017-10-13 03:58:29 -07:00
|
|
|
{
|
2026-03-20 09:06:22 -07:00
|
|
|
handlePlayersFinished(notification.allies, notification.message, false);
|
2025-12-30 00:57:37 -08:00
|
|
|
},
|
|
|
|
|
"won": function(notification, player)
|
|
|
|
|
{
|
2026-03-20 09:06:22 -07:00
|
|
|
handlePlayersFinished(notification.allies, notification.message, true);
|
2025-12-30 00:57:37 -08:00
|
|
|
},
|
|
|
|
|
"diplomacy": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
g_DiplomacyColors.onDiplomacyChange();
|
|
|
|
|
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "diplomacy",
|
|
|
|
|
"sourcePlayer": player,
|
|
|
|
|
"targetPlayer": notification.targetPlayer,
|
|
|
|
|
"status": notification.status
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
"ceasefire-ended": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
for (const handler of g_CeasefireEndedHandlers)
|
|
|
|
|
handler();
|
|
|
|
|
},
|
|
|
|
|
"tutorial": function(notification, player)
|
|
|
|
|
{
|
2026-02-25 05:37:05 -08:00
|
|
|
g_TutorialManager.handleNotification(notification);
|
2025-12-30 00:57:37 -08:00
|
|
|
},
|
|
|
|
|
"tribute": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "tribute",
|
|
|
|
|
"sourcePlayer": notification.donator,
|
|
|
|
|
"targetPlayer": player,
|
|
|
|
|
"amounts": notification.amounts
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
"barter": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "barter",
|
|
|
|
|
"player": player,
|
|
|
|
|
"amountGiven": notification.amountGiven,
|
|
|
|
|
"amountGained": notification.amountGained,
|
|
|
|
|
"resourceGiven": notification.resourceGiven,
|
|
|
|
|
"resourceGained": notification.resourceGained
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
"spy-response": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
g_DiplomacyDialog.onSpyResponse(notification, player);
|
|
|
|
|
|
|
|
|
|
if (notification.entity && g_ViewedPlayer == player && (!g_IsObserver || g_FollowPlayer))
|
|
|
|
|
{
|
|
|
|
|
g_DiplomacyDialog.close();
|
|
|
|
|
setCameraFollow(notification.entity);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"attack": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
if (player != g_ViewedPlayer)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Focus camera on attacks
|
|
|
|
|
if (g_FollowPlayer)
|
|
|
|
|
{
|
|
|
|
|
setCameraFollow(notification.target);
|
|
|
|
|
|
|
|
|
|
g_Selection.reset();
|
|
|
|
|
if (notification.target)
|
|
|
|
|
g_Selection.addList([notification.target]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_LastAttack = { "target": notification.target, "position": notification.position };
|
|
|
|
|
|
|
|
|
|
if (Engine.ConfigDB_GetValue("user", "gui.session.notifications.attack") !== "true")
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "attack",
|
|
|
|
|
"player": player,
|
|
|
|
|
"attacker": notification.attacker,
|
|
|
|
|
"target": notification.target,
|
|
|
|
|
"position": notification.position,
|
|
|
|
|
"targetIsDomesticAnimal": notification.targetIsDomesticAnimal
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
"phase": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "phase",
|
|
|
|
|
"player": player,
|
|
|
|
|
"phaseName": notification.phaseName,
|
|
|
|
|
"phaseState": notification.phaseState
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
"dialog": function(notification, player)
|
|
|
|
|
{
|
|
|
|
|
if (player == Engine.GetPlayerID())
|
|
|
|
|
openDialog(notification.dialogName, notification.data, player);
|
|
|
|
|
},
|
|
|
|
|
"playercommand": function(notification, player)
|
2016-03-14 21:08:07 -07:00
|
|
|
{
|
2026-04-19 06:39:55 -07:00
|
|
|
// For observers, focus the camera on units commanded by the selected player
|
2025-12-30 00:57:37 -08:00
|
|
|
if (!g_FollowPlayer || player != g_ViewedPlayer)
|
|
|
|
|
return;
|
2016-03-14 21:08:07 -07:00
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
const cmd = notification.cmd;
|
2016-03-14 21:08:07 -07:00
|
|
|
|
2026-04-19 06:39:55 -07:00
|
|
|
// Ignore commands executed because of units following rally points
|
|
|
|
|
if (cmd.fromRallyPoint)
|
2025-12-30 00:57:37 -08:00
|
|
|
return;
|
|
|
|
|
|
2026-04-19 06:39:55 -07:00
|
|
|
const entState = cmd.entities && cmd.entities[0] && GetEntityState(cmd.entities[0]);
|
|
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
// Focus the structure to build.
|
|
|
|
|
if (cmd.type == "repair")
|
|
|
|
|
{
|
|
|
|
|
const targetState = GetEntityState(cmd.target);
|
|
|
|
|
if (targetState)
|
|
|
|
|
Engine.CameraMoveTo(targetState.position.x, targetState.position.z);
|
|
|
|
|
}
|
|
|
|
|
else if (cmd.type == "delete-entities" && notification.position)
|
|
|
|
|
Engine.CameraMoveTo(notification.position.x, notification.position.y);
|
|
|
|
|
// Focus commanded entities, but don't lose previous focus when training units
|
|
|
|
|
else if (cmd.type != "train" && cmd.type != "research" && entState)
|
|
|
|
|
setCameraFollow(cmd.entities[0]);
|
|
|
|
|
|
|
|
|
|
if (["walk", "attack-walk", "patrol"].indexOf(cmd.type) != -1)
|
|
|
|
|
DrawTargetMarker(cmd);
|
|
|
|
|
|
|
|
|
|
// Select units affected by that command
|
|
|
|
|
let selection = [];
|
|
|
|
|
if (cmd.entities)
|
|
|
|
|
selection = cmd.entities;
|
|
|
|
|
if (cmd.target)
|
|
|
|
|
selection.push(cmd.target);
|
|
|
|
|
|
|
|
|
|
// Allow gaia in selection when gathering
|
|
|
|
|
g_Selection.reset();
|
|
|
|
|
g_Selection.addList(selection, false, cmd.type == "gather");
|
|
|
|
|
},
|
|
|
|
|
"play-tracks": function(notification, player)
|
2017-12-30 09:42:43 -08:00
|
|
|
{
|
2025-12-30 00:57:37 -08:00
|
|
|
if (notification.lock)
|
|
|
|
|
{
|
|
|
|
|
global.music.storeTracks(notification.tracks.map(track => ({ "Type": "custom", "File": track })));
|
|
|
|
|
global.music.setState(global.music.states.CUSTOM);
|
|
|
|
|
}
|
2017-12-30 09:42:43 -08:00
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
global.music.setLocked(notification.lock);
|
|
|
|
|
}
|
|
|
|
|
};
|
2014-01-02 08:22:03 -08:00
|
|
|
|
2019-10-21 08:00:59 -07:00
|
|
|
function registerPlayerAssignmentsChangeHandler(handler)
|
|
|
|
|
{
|
|
|
|
|
g_PlayerAssignmentsChangeHandlers.add(handler);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-27 05:39:28 -07:00
|
|
|
function registerCeasefireEndedHandler(handler)
|
|
|
|
|
{
|
|
|
|
|
g_CeasefireEndedHandlers.add(handler);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 04:14:55 -07:00
|
|
|
function registerNetworkOutOfSyncHandler(handler)
|
|
|
|
|
{
|
|
|
|
|
g_NetworkOutOfSyncHandlers.add(handler);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 06:48:27 -07:00
|
|
|
function registerNetworkStatusChangeHandler(handler)
|
|
|
|
|
{
|
|
|
|
|
g_NetworkStatusChangeHandlers.add(handler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function registerClientsLoadingHandler(handler)
|
|
|
|
|
{
|
|
|
|
|
g_ClientsLoadingHandlers.add(handler);
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-23 17:06:01 -08:00
|
|
|
function findGuidForPlayerID(playerID)
|
|
|
|
|
{
|
|
|
|
|
return Object.keys(g_PlayerAssignments).find(guid => g_PlayerAssignments[guid].player == playerID);
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-13 19:08:53 -08:00
|
|
|
/**
|
2016-03-10 02:15:23 -08:00
|
|
|
* Processes all pending notifications sent from the GUIInterface simulation component.
|
2015-12-13 19:08:53 -08:00
|
|
|
*/
|
2026-02-24 07:07:05 -08:00
|
|
|
function handleNotifications()
|
2014-06-19 07:37:08 -07:00
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const notification of Engine.GuiInterfaceCall("GetNotifications"))
|
2014-06-19 07:37:08 -07:00
|
|
|
{
|
2016-03-10 02:15:23 -08:00
|
|
|
if (!notification.players || !notification.type || !g_NotificationsTypes[notification.type])
|
2015-05-20 15:09:59 -07:00
|
|
|
{
|
2016-03-10 02:15:23 -08:00
|
|
|
error("Invalid GUI notification: " + uneval(notification));
|
2015-05-20 15:09:59 -07:00
|
|
|
continue;
|
|
|
|
|
}
|
2015-12-22 05:38:38 -08:00
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const player of notification.players)
|
2026-02-24 07:07:05 -08:00
|
|
|
g_NotificationsTypes[notification.type](notification, player);
|
2014-04-20 13:51:48 -07:00
|
|
|
}
|
2013-12-28 05:40:39 -08:00
|
|
|
}
|
|
|
|
|
|
2021-06-06 11:29:48 -07:00
|
|
|
function focusAttack(attack)
|
|
|
|
|
{
|
|
|
|
|
if (!attack)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const entState = attack.target && GetEntityState(attack.target);
|
|
|
|
|
if (entState && hasClass(entState, "Unit"))
|
|
|
|
|
setCameraFollow(attack.target);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
const position = attack.position;
|
|
|
|
|
Engine.SetCameraTarget(position.x, position.y, position.z);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-13 19:08:53 -08:00
|
|
|
/**
|
2016-06-10 06:17:47 -07:00
|
|
|
* Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
|
2015-12-13 19:08:53 -08:00
|
|
|
* Saves the received object to mainlog.html.
|
|
|
|
|
*/
|
2025-05-07 02:24:39 -07:00
|
|
|
async function handleNetMessages()
|
2014-01-02 08:22:03 -08:00
|
|
|
{
|
2016-06-10 06:17:47 -07:00
|
|
|
while (true)
|
|
|
|
|
{
|
2025-05-07 02:24:39 -07:00
|
|
|
const msg = await Engine.PollNetworkClient();
|
2026-04-29 12:44:55 -07:00
|
|
|
if (!msg)
|
|
|
|
|
return;
|
2015-12-23 17:46:06 -08:00
|
|
|
|
2016-06-10 06:17:47 -07:00
|
|
|
log("Net message: " + uneval(msg));
|
|
|
|
|
|
|
|
|
|
if (g_NetMessageTypes[msg.type])
|
|
|
|
|
g_NetMessageTypes[msg.type](msg);
|
|
|
|
|
else
|
2025-04-10 21:24:32 -07:00
|
|
|
error("Unrecognized net message type '" + msg.type + "'");
|
2016-06-10 06:17:47 -07:00
|
|
|
}
|
2014-01-02 08:22:03 -08:00
|
|
|
}
|
|
|
|
|
|
2015-12-23 15:51:18 -08:00
|
|
|
function handleNetStatusMessage(message)
|
|
|
|
|
{
|
|
|
|
|
if (g_Disconnected)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-10-23 07:49:32 -07:00
|
|
|
g_IsNetworkedActive = message.status == "active";
|
|
|
|
|
|
2015-12-23 15:51:18 -08:00
|
|
|
if (message.status == "disconnected")
|
|
|
|
|
{
|
|
|
|
|
g_Disconnected = true;
|
2015-12-28 09:19:29 -08:00
|
|
|
closeOpenDialogs();
|
2015-12-23 15:51:18 -08:00
|
|
|
}
|
|
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const handler of g_NetworkStatusChangeHandlers)
|
2019-10-30 06:48:27 -07:00
|
|
|
handler(message);
|
2017-03-21 11:50:29 -07:00
|
|
|
}
|
|
|
|
|
|
2015-12-23 17:06:01 -08:00
|
|
|
function handlePlayerAssignmentsMessage(message)
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const guid in g_PlayerAssignments)
|
2016-06-03 09:54:00 -07:00
|
|
|
if (!message.newAssignments[guid])
|
|
|
|
|
onClientLeave(guid);
|
2015-12-23 17:06:01 -08:00
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
const joins = Object.keys(message.newAssignments).filter(guid => !g_PlayerAssignments[guid]);
|
2016-05-19 15:10:38 -07:00
|
|
|
|
2016-06-03 09:54:00 -07:00
|
|
|
g_PlayerAssignments = message.newAssignments;
|
2015-12-23 17:06:01 -08:00
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
joins.forEach(guid =>
|
|
|
|
|
{
|
2016-06-03 09:54:00 -07:00
|
|
|
onClientJoin(guid);
|
2015-12-23 17:06:01 -08:00
|
|
|
});
|
|
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const handler of g_PlayerAssignmentsChangeHandlers)
|
2019-10-21 08:00:59 -07:00
|
|
|
handler();
|
|
|
|
|
|
|
|
|
|
// TODO: use subscription instead
|
2017-03-02 16:10:40 -08:00
|
|
|
updateGUIObjects();
|
2015-12-23 17:06:01 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-20 09:06:22 -07:00
|
|
|
function handlePlayersFinished(players, message, won)
|
|
|
|
|
{
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "playerstate",
|
|
|
|
|
"message": message,
|
|
|
|
|
"players": players
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (const handler of g_PlayerFinishedHandlers)
|
|
|
|
|
handler(players, won);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-03 09:54:00 -07:00
|
|
|
function onClientJoin(guid)
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
const playerID = g_PlayerAssignments[guid].player;
|
2016-06-03 09:54:00 -07:00
|
|
|
|
|
|
|
|
if (g_Players[playerID])
|
|
|
|
|
{
|
|
|
|
|
g_Players[playerID].guid = guid;
|
|
|
|
|
g_Players[playerID].name = g_PlayerAssignments[guid].name;
|
|
|
|
|
g_Players[playerID].offline = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "connect",
|
|
|
|
|
"guid": guid
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function onClientLeave(guid)
|
|
|
|
|
{
|
Implement session event subscription system and rewrite TopPanel, PlayerViewControl, GameSpeed, Pausing, ObjectivesDialog to use object orientation, refs #5387.
New controller classes: PlayerViewControl, PauseControl,
GameSpeedControl
New viewer classes: ObjectivesDialog, PauseOverlay, FollowPlayer,
TopPanel (BuildLabel, CivIcon, CounterManager, CounterPopulation,
CounterResource refs 7e14a33411/D1113, GameSpeedButton,
ObjectivesDialogButton)
New events: SimulationUpdate, EntitySelectionChange, ViewedPlayerChange,
PreViewedPlayerChangeHandler, PlayerIDChange, PlayersInit,
PlayersFinished, Pause, DiplomacyColorsChange, HotkeyChange, refs #2604
Improves GUI onSimuationUpdate performance without selected entities by
allegedly 30%.
Delete misleading dead code resign command from leaveGame and rename to
endGame. The command is not sent via network (see fa85527baf) nor
processed in simulation, because the Game instance is deleted
immediately thereafter, introduced in fcedcae052, refs a3e1c68b9a,
39ffb0a6bd, 9f796068f8.
Remove explicitResume 0 value from e57c99c6f6 and 8ae67ed15f which
should have been a false if defined, and is equivalent to the default.
Restore fast forwarding option from cd571035bb/D595 for developers
changing the perspective to observer or player following 56308ec1ad.
Add pausing for the delete dialog missing following 7a7ebaa983.
Differential Revision: https://code.wildfiregames.com/D2378
This was SVN commit r23076.
2019-10-17 08:08:56 -07:00
|
|
|
g_PauseControl.setClientPauseState(guid, false);
|
2016-06-03 09:54:00 -07:00
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const id in g_Players)
|
2016-06-03 09:54:00 -07:00
|
|
|
if (g_Players[id].guid == guid)
|
|
|
|
|
g_Players[id].offline = true;
|
|
|
|
|
|
|
|
|
|
addChatMessage({
|
|
|
|
|
"type": "disconnect",
|
|
|
|
|
"guid": guid
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-29 19:21:17 -07:00
|
|
|
function addChatMessage(msg)
|
2015-12-22 07:29:03 -08:00
|
|
|
{
|
2019-10-06 07:42:12 -07:00
|
|
|
g_Chat.ChatMessageHandler.handleMessage(msg);
|
2015-12-22 07:29:03 -08:00
|
|
|
}
|
|
|
|
|
|
2019-08-12 07:13:04 -07:00
|
|
|
function clearChatMessages()
|
|
|
|
|
{
|
2019-10-06 07:42:12 -07:00
|
|
|
g_Chat.ChatOverlay.clearChatMessages();
|
2015-12-22 08:02:25 -08:00
|
|
|
}
|
|
|
|
|
|
2024-12-03 12:24:55 -08:00
|
|
|
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;
|
|
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
const now = Date.now();
|
2024-12-03 12:24:55 -08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-23 11:06:48 -08:00
|
|
|
/**
|
|
|
|
|
* This function is used for AIs, whose names don't exist in g_PlayerAssignments.
|
|
|
|
|
*/
|
|
|
|
|
function colorizePlayernameByID(playerID)
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
const username = g_Players[playerID] && escapeText(g_Players[playerID].name);
|
2016-02-15 03:39:02 -08:00
|
|
|
return colorizePlayernameHelper(username, playerID);
|
2015-12-23 11:06:48 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function colorizePlayernameByGUID(guid)
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
const username = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].name : "";
|
|
|
|
|
const playerID = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].player : -1;
|
2016-02-15 03:39:02 -08:00
|
|
|
return colorizePlayernameHelper(username, playerID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function colorizePlayernameHelper(username, playerID)
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
const playerColor = playerID > -1 ? g_DiplomacyColors.getPlayerColor(playerID) : "white";
|
2017-12-04 09:29:26 -08:00
|
|
|
return coloredText(username || translate("Unknown Player"), playerColor);
|
2015-12-23 11:06:48 -08:00
|
|
|
}
|
|
|
|
|
|
2017-01-28 12:30:30 -08:00
|
|
|
/**
|
|
|
|
|
* Insert the colorized playername to chat messages sent by the AI and time notifications.
|
|
|
|
|
*/
|
|
|
|
|
function colorizePlayernameParameters(parameters)
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const param in parameters)
|
2017-01-28 12:30:30 -08:00
|
|
|
if (param.startsWith("_player_"))
|
|
|
|
|
parameters[param] = colorizePlayernameByID(parameters[param]);
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-13 19:08:53 -08:00
|
|
|
/**
|
2016-04-17 23:54:04 -07:00
|
|
|
* Custom dialog response handling, usable by trigger maps.
|
2015-12-13 19:08:53 -08:00
|
|
|
*/
|
2014-07-02 07:48:22 -07:00
|
|
|
function sendDialogAnswer(guiObject, dialogName)
|
|
|
|
|
{
|
2017-10-21 10:31:05 -07:00
|
|
|
Engine.GetGUIObjectByName(dialogName + "-dialog").hidden = true;
|
2014-07-02 07:48:22 -07:00
|
|
|
|
|
|
|
|
Engine.PostNetworkCommand({
|
|
|
|
|
"type": "dialog-answer",
|
|
|
|
|
"dialog": dialogName,
|
|
|
|
|
"answer": guiObject.name.split("-").pop(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
resumeGame();
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-13 19:08:53 -08:00
|
|
|
/**
|
2016-04-17 23:54:04 -07:00
|
|
|
* Custom dialog opening, usable by trigger maps.
|
2015-12-13 19:08:53 -08:00
|
|
|
*/
|
2014-07-02 07:48:22 -07:00
|
|
|
function openDialog(dialogName, data, player)
|
|
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
const dialog = Engine.GetGUIObjectByName(dialogName + "-dialog");
|
2014-07-02 07:48:22 -07:00
|
|
|
if (!dialog)
|
|
|
|
|
{
|
2021-03-10 08:50:15 -08:00
|
|
|
warn("messages.js: Unknown dialog with name " + dialogName);
|
2014-07-02 07:48:22 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
dialog.hidden = false;
|
|
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const objName in data)
|
2014-07-02 07:48:22 -07:00
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
const obj = Engine.GetGUIObjectByName(dialogName + "-dialog-" + objName);
|
2014-07-02 07:48:22 -07:00
|
|
|
if (!obj)
|
|
|
|
|
{
|
|
|
|
|
warn("messages.js: Key '" + objName + "' not found in '" + dialogName + "' dialog.");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2015-12-13 19:08:53 -08:00
|
|
|
|
2025-05-10 08:03:15 -07:00
|
|
|
for (const key in data[objName])
|
2014-07-02 07:48:22 -07:00
|
|
|
{
|
2025-05-10 08:03:15 -07:00
|
|
|
const n = data[objName][key];
|
2025-06-26 01:37:24 -07:00
|
|
|
if (typeof n === "object" && n.message)
|
2014-07-02 07:48:22 -07:00
|
|
|
{
|
2015-12-13 19:08:53 -08:00
|
|
|
let message = n.message;
|
2014-07-02 07:48:22 -07:00
|
|
|
if (n.translateMessage)
|
|
|
|
|
message = translate(message);
|
2025-05-10 08:03:15 -07:00
|
|
|
const parameters = n.parameters || {};
|
2014-07-02 07:48:22 -07:00
|
|
|
if (n.translateParameters)
|
|
|
|
|
translateObjectKeys(parameters, n.translateParameters);
|
|
|
|
|
obj[key] = sprintf(message, parameters);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
obj[key] = n;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Implement session event subscription system and rewrite TopPanel, PlayerViewControl, GameSpeed, Pausing, ObjectivesDialog to use object orientation, refs #5387.
New controller classes: PlayerViewControl, PauseControl,
GameSpeedControl
New viewer classes: ObjectivesDialog, PauseOverlay, FollowPlayer,
TopPanel (BuildLabel, CivIcon, CounterManager, CounterPopulation,
CounterResource refs 7e14a33411/D1113, GameSpeedButton,
ObjectivesDialogButton)
New events: SimulationUpdate, EntitySelectionChange, ViewedPlayerChange,
PreViewedPlayerChangeHandler, PlayerIDChange, PlayersInit,
PlayersFinished, Pause, DiplomacyColorsChange, HotkeyChange, refs #2604
Improves GUI onSimuationUpdate performance without selected entities by
allegedly 30%.
Delete misleading dead code resign command from leaveGame and rename to
endGame. The command is not sent via network (see fa85527baf) nor
processed in simulation, because the Game instance is deleted
immediately thereafter, introduced in fcedcae052, refs a3e1c68b9a,
39ffb0a6bd, 9f796068f8.
Remove explicitResume 0 value from e57c99c6f6 and 8ae67ed15f which
should have been a false if defined, and is equivalent to the default.
Restore fast forwarding option from cd571035bb/D595 for developers
changing the perspective to observer or player following 56308ec1ad.
Add pausing for the delete dialog missing following 7a7ebaa983.
Differential Revision: https://code.wildfiregames.com/D2378
This was SVN commit r23076.
2019-10-17 08:08:56 -07:00
|
|
|
g_PauseControl.implicitPause();
|
2014-07-02 07:48:22 -07:00
|
|
|
}
|