mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Up to now `eslint-plugin-brace-rules` was used to enforce a common brace style for JavaScript code. This plugin was however updated the last time over 9 years ago and will be incompatible with ESLint v10, as that [removes `context.getSourceCode()`][1], the plugin relies on. To keep the eslint config working with ESLint v10, this replaces `eslint-plugin-brace-rules` with the [`@stylistic/brace-style`][2] rule from `@stylistic/eslint-plugin`, a package we already use. While `@stylistic/brace-style` doesn't offer an option to format braces in exactly the same way as before, the "allman" style seems to be the one closest to the existing code. [1]: https://eslint.org/blog/2025/11/eslint-v10.0.0-alpha.0-released/#removed-deprecated-rule-context-members [2]: https://eslint.style/rules/brace-style
313 lines
8.8 KiB
JavaScript
313 lines
8.8 KiB
JavaScript
/**
|
|
* Number of milliseconds to display network warnings.
|
|
*/
|
|
var g_NetworkWarningTimeout = 3000;
|
|
|
|
/**
|
|
* Currently displayed network warnings. At most one message per user.
|
|
*/
|
|
var g_NetworkWarnings = {};
|
|
|
|
/**
|
|
* Message-types to be displayed.
|
|
*/
|
|
var g_NetworkWarningTexts = {
|
|
|
|
"server-timeout": (msg, username) =>
|
|
sprintf(translate("Losing connection to server (%(seconds)ss)"), {
|
|
"seconds": Math.ceil(msg.lastReceivedTime / 1000)
|
|
}),
|
|
|
|
"client-timeout": (msg, username) =>
|
|
sprintf(translate("%(player)s losing connection (%(seconds)ss)"), {
|
|
"player": username,
|
|
"seconds": Math.ceil(msg.lastReceivedTime / 1000)
|
|
}),
|
|
|
|
"server-latency": (msg, username) =>
|
|
sprintf(translate("Bad connection to server (%(milliseconds)sms)"), {
|
|
"milliseconds": msg.meanRTT
|
|
}),
|
|
|
|
"client-latency": (msg, username) =>
|
|
sprintf(translate("Bad connection to %(player)s (%(milliseconds)sms)"), {
|
|
"player": username,
|
|
"milliseconds": msg.meanRTT
|
|
})
|
|
};
|
|
|
|
var g_NetworkCommands = {
|
|
"/kick": argument => kickPlayer(argument, false),
|
|
"/ban": argument => kickPlayer(argument, true),
|
|
"/kickspecs": argument => kickObservers(false),
|
|
"/banspecs": argument => kickObservers(true),
|
|
"/list": argument => addChatMessage({ "type": "clientlist" }),
|
|
"/clear": argument => clearChatMessages()
|
|
};
|
|
|
|
var g_ValidPorts = { "min": 1024, "max": 65535 };
|
|
// TODO: This check should be performed exclusively on the C++ side, currently this is sort of redundant.
|
|
function getValidPort(port)
|
|
{
|
|
if (isNaN(+port) || +port < g_ValidPorts.min || +port > g_ValidPorts.max)
|
|
return Engine.GetDefaultPort();
|
|
|
|
return +port;
|
|
}
|
|
|
|
/**
|
|
* Must be kept in sync with source/network/NetHost.h
|
|
*/
|
|
function getDisconnectReason(id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case 0: return translate("An unknown error occurred.");
|
|
case 1: return translate("The connection request has timed out.");
|
|
case 2: return translate("The host has ended the game.");
|
|
case 3: return translate("Incorrect network protocol version.");
|
|
case 4: return translate("Game is loading, please try again later.");
|
|
case 5: return translate("Game has already started, no observers allowed.");
|
|
case 6: return translate("You have been kicked.");
|
|
case 7: return translate("You have been banned.");
|
|
case 8: return translate("Player name in use. If you were disconnected, retry in few seconds.");
|
|
case 9: return translate("Server full.");
|
|
case 10: return translate("Secure lobby authentication failed. Join via lobby.");
|
|
case 11: return translate("Error: Server failed to allocate a unique client identifier.");
|
|
case 12: return translate("Error: Client commands were ready for an unexpected game turn.");
|
|
case 13: return translate("Error: Client simulated an unexpected game turn.");
|
|
case 14: return translate("Password is invalid.");
|
|
case 15: return translate("Could not find an unused port for the enet STUN client.");
|
|
case 16: return translate("Could not find the STUN endpoint.");
|
|
case 17: return translate("Different game engine versions or different mods loaded.");
|
|
|
|
default:
|
|
warn("Unknown disconnect-reason ID received: " + id);
|
|
return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id });
|
|
}
|
|
}
|
|
|
|
function getRequestTimeOutMessage()
|
|
{
|
|
return (Engine.HasXmppClient() ? "" : translate("Please ensure that you entered the correct server name or IP address, and port number.\n\n")) +
|
|
translate("If you haven't yet, try to connect again and to different hosts." +
|
|
"\nIf the issue persists or occurs regularly, visit the official FAQ for detailed guidance and troubleshooting steps.");
|
|
}
|
|
|
|
function getMismatchMessage(mismatchType, clientMismatchInfo, serverMismatchInfo)
|
|
{
|
|
switch (mismatchType)
|
|
{
|
|
case "engine": return sprintf(translate("Different engine versions detected client version: %(clientMismatch)s server version: %(serverMismatch)s"), {
|
|
"clientMismatch": clientMismatchInfo,
|
|
"serverMismatch": serverMismatchInfo
|
|
});
|
|
case "mod": return sprintf(translate("Different mods enabled, or mods enabled in a different order. Client mod: '%(clientMismatch)s' Server mod: '%(serverMismatch)s'"), {
|
|
"clientMismatch": clientMismatchInfo,
|
|
"serverMismatch": serverMismatchInfo
|
|
});
|
|
default: return sprintf(translate("Unrecognized handshake mismatch type: %(mismatchType)s"), {
|
|
"mismatchType": mismatchType
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show the disconnect reason in a message box.
|
|
*
|
|
* @param {Object} message
|
|
* @param {boolean} wasConnected
|
|
*/
|
|
function reportDisconnect(message, wasConnected)
|
|
{
|
|
if (message.reason === 0)
|
|
reportConnectionRequestTimeOut();
|
|
else if (message.reason == 16)
|
|
reportMismatchingSoftwareVersions(message.mismatch_type, message.client_mismatch, message.server_mismatch);
|
|
else
|
|
messageBox(
|
|
400, 200,
|
|
(wasConnected ?
|
|
translate("Lost connection to the server.") :
|
|
translate("Failed to connect to the server.")
|
|
) + "\n\n" + getDisconnectReason(message.reason),
|
|
translate("Disconnected")
|
|
);
|
|
}
|
|
|
|
async function reportConnectionRequestTimeOut()
|
|
{
|
|
const buttonIndex = await messageBox(
|
|
600, 230,
|
|
translate("Failed to connect to the server.") + " " + getDisconnectReason(1) + "\n\n" + getRequestTimeOutMessage(),
|
|
translate("Connection Error"),
|
|
[translate("Close"), translate("Open FAQ")]
|
|
);
|
|
if (buttonIndex === 1)
|
|
openURL("https://gitea.wildfiregames.com/0ad/0ad/wiki/FAQ#what-shall-i-do-when-joining-multiplayer-matches-fails-with-an-error-message");
|
|
|
|
return;
|
|
}
|
|
|
|
function reportMismatchingSoftwareVersions(mismatchType, clientMismatch, serverMismatch)
|
|
{
|
|
messageBox(
|
|
400, 200,
|
|
translate("Failed to connect to the server.") +
|
|
"\n\n" + getMismatchMessage(mismatchType, clientMismatch, serverMismatch),
|
|
translate("Disconnected")
|
|
);
|
|
}
|
|
|
|
function kickError()
|
|
{
|
|
addChatMessage({
|
|
"type": "system",
|
|
"text": translate("Only the host can kick clients!")
|
|
});
|
|
}
|
|
|
|
function kickPlayer(username, ban)
|
|
{
|
|
if (!g_IsController)
|
|
{
|
|
kickError();
|
|
return;
|
|
}
|
|
if (!g_IsNetworked)
|
|
{
|
|
addChatMessage({
|
|
"type": "system",
|
|
"text": translate("Offline game! Can not kick or ban.")
|
|
});
|
|
return;
|
|
}
|
|
Engine.KickPlayer(username, ban);
|
|
}
|
|
|
|
function kickObservers(ban)
|
|
{
|
|
if (!g_IsController)
|
|
{
|
|
kickError();
|
|
return;
|
|
}
|
|
if (!g_IsNetworked)
|
|
{
|
|
addChatMessage({
|
|
"type": "system",
|
|
"text": translate("Offline game! No spectators to kick or ban.")
|
|
});
|
|
return;
|
|
}
|
|
for (const guid in g_PlayerAssignments)
|
|
if (g_PlayerAssignments[guid].player == -1)
|
|
Engine.KickPlayer(g_PlayerAssignments[guid].name, ban);
|
|
}
|
|
|
|
/**
|
|
* Sort GUIDs of connected users sorted by playerindex, observers last.
|
|
* Requires g_PlayerAssignments.
|
|
*/
|
|
function sortGUIDsByPlayerID()
|
|
{
|
|
return Object.keys(g_PlayerAssignments).sort((guidA, guidB) =>
|
|
{
|
|
|
|
const playerIdA = g_PlayerAssignments[guidA].player;
|
|
const playerIdB = g_PlayerAssignments[guidB].player;
|
|
|
|
if (playerIdA == -1) return +1;
|
|
if (playerIdB == -1) return -1;
|
|
|
|
return playerIdA - playerIdB;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get a colorized list of usernames sorted by player slot, observers last.
|
|
* Requires g_PlayerAssignments and colorizePlayernameByGUID.
|
|
*
|
|
* @returns {string}
|
|
*/
|
|
function getUsernameList()
|
|
{
|
|
const usernames = sortGUIDsByPlayerID().map(guid => colorizePlayernameByGUID(guid));
|
|
|
|
// Translation: Number of currently connected players/observers and their names
|
|
return sprintf(translate("Users (%(num)s): %(users)s"), {
|
|
"users": usernames.join(translate(", ")),
|
|
"num": usernames.length
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Execute a command locally. Requires addChatMessage.
|
|
*
|
|
* @param {string} input
|
|
* @returns {boolean} whether a command was executed
|
|
*/
|
|
function executeNetworkCommand(input)
|
|
{
|
|
if (input.indexOf("/") != 0)
|
|
return false;
|
|
|
|
const command = input.split(" ", 1)[0];
|
|
const argument = input.substr(command.length + 1);
|
|
|
|
if (g_NetworkCommands[command])
|
|
g_NetworkCommands[command](argument);
|
|
|
|
return !!g_NetworkCommands[command];
|
|
}
|
|
|
|
/**
|
|
* Remember this warning for a few seconds.
|
|
* Overwrite previous warnings for this user.
|
|
*
|
|
* @param msg - GUI message sent by NetServer or NetClient
|
|
*/
|
|
function addNetworkWarning(msg)
|
|
{
|
|
if (!g_NetworkWarningTexts[msg.warntype])
|
|
{
|
|
warn("Unknown network warning type received: " + uneval(msg));
|
|
return;
|
|
}
|
|
|
|
if (Engine.ConfigDB_GetValue("user", "overlay.netwarnings") != "true")
|
|
return;
|
|
|
|
g_NetworkWarnings[msg.guid || "server"] = {
|
|
"added": Date.now(),
|
|
"msg": msg
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Colorizes and concatenates all network warnings.
|
|
* Returns text and textWidth.
|
|
*/
|
|
function getNetworkWarnings()
|
|
{
|
|
// Remove outdated messages
|
|
for (const guid in g_NetworkWarnings)
|
|
if (Date.now() > g_NetworkWarnings[guid].added + g_NetworkWarningTimeout ||
|
|
guid != "server" && !g_PlayerAssignments[guid])
|
|
delete g_NetworkWarnings[guid];
|
|
|
|
// Show local messages first
|
|
const guids = Object.keys(g_NetworkWarnings).sort(guid => guid != "server");
|
|
|
|
const messages = [];
|
|
|
|
for (const guid of guids)
|
|
{
|
|
const msg = g_NetworkWarnings[guid].msg;
|
|
|
|
// Add formatted text
|
|
messages.push(g_NetworkWarningTexts[msg.warntype](msg, colorizePlayernameByGUID(guid)));
|
|
}
|
|
|
|
return messages;
|
|
}
|