Ticket #3241: t3241_kick_v6.2.patch

File t3241_kick_v6.2.patch, 40.1 KB (added by elexis, 9 years ago)

Rebased and added JSDoc comments.

  • binaries/data/mods/public/gui/common/network.js

     
    1 function getDisconnectReason(id)
     1/**
     2 * Get a human readable version of the given disconnect reason.
     3 *
     4 * @param {number} reason - as defined in the enum in source/network/NetHost.h
     5 * @returns {string} human readable version of the reason
     6 */
     7function getDisconnectReason(reason)
    28{
    3     // Must be kept in sync with source/network/NetHost.h
    4     switch (id)
     9    switch (reason)
    510    {
    611    case 0: return translate("Unknown reason");
    712    case 1: return translate("Unexpected shutdown");
    813    case 2: return translate("Incorrect network protocol version");
    914    case 3: return translate("Game is loading, please try later");
    1015    case 4: return translate("Game has already started, no observers allowed");
    11     default: return sprintf(translate("\\[Invalid value %(id)s]"), { "id": id });
     16    case 5: return translate("You have been kicked from the match");
     17    case 6: return translate("You have been banned from the match");
     18    default: return sprintf(translate("\\[Invalid value %(id)s]"), { "id": reason });
    1219    }
    1320}
    1421
     22/**
     23 * Shows a message box stating the disconnect reason.
     24 *
     25 * @param {number} reason - as defined in the enum in source/network/NetHost.h
     26 */
    1527function reportDisconnect(reason)
    1628{
    17     var reasontext = getDisconnectReason(reason);
    18 
    1929    messageBox(400, 200,
    20         translate("Lost connection to the server.") + "\n\n" + sprintf(translate("Reason: %(reason)s."), { reason: reasontext }),
     30        translate("Lost connection to the server.") + "\n\n" +
     31            sprintf(translate("Reason: %(reason)s."), { "reason": getDisconnectReason(reason) }),
    2132        translate("Disconnected"), 2);
    2233}
     34
     35/**
     36 * Get usernames sorted by player slot, observers last.
     37 * Requires g_PlayerAssignments.
     38 *
     39 * @returns {string[]} the usernames
     40 */
     41function getUsernameList()
     42{
     43    // First sort, then lookup name
     44    return Object.keys(g_PlayerAssignments).sort((guidA, guidB) =>
     45    {
     46        let slotA = g_PlayerAssignments[guidA].player;
     47        let slotB = g_PlayerAssignments[guidB].player;
     48        if (slotA == -1) return 1;
     49        if (slotB == -1) return -1;
     50        return slotA - slotB;
     51    }).map(guid => g_PlayerAssignments[guid].name);
     52}
     53
     54/**
     55 * Execute a commands locally. Requires addChatMessage.
     56 *
     57 * @param {string} input - What the user typed
     58 * @returns {Boolean} true if the command was executed
     59 */
     60function executeNetworkCommand(input)
     61{
     62    // Must start with slash
     63    if (input.indexOf("/") != 0)
     64        return false;
     65
     66    // Split command and argument
     67    var command = input.split(" ", 1)[0];
     68    var argument = input.substr(command.length).trim();
     69
     70    // Execute command
     71    switch (command)
     72    {
     73    case "/list":
     74        addChatMessage({ "type": "clientlist", "guid": "local" });
     75        return true;
     76
     77    case "/kick":
     78        Engine.KickPlayer(argument, false);
     79        return true;
     80
     81    case "/ban":
     82        Engine.KickPlayer(argument, true);
     83        return true;
     84
     85    default:
     86        return false;
     87    }
     88}
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

     
    518518
    519519    case "chat":
    520520        addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
    521521        break;
    522522
     523    case "kicked":
     524        addChatMessage({ "type": message.ban ? "banned" : "kicked", "username": message.username });
     525        break;
     526
    523527    // Singular client to host message
    524528    case "ready":
    525529        g_ReadyChanged -= 1;
    526530        if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1)
    527531            addChatMessage({ "type": "ready", "guid": message.guid, "ready": +message.status == 1 });
     
    17061710
    17071711    // Remove AI from this player slot
    17081712    g_GameAttributes.settings.PlayerData[newSlot].AI = "";
    17091713}
    17101714
     1715// Called when pressing enter or clicking the send button. May call network commands.
    17111716function submitChatInput()
    17121717{
    17131718    var input = Engine.GetGUIObjectByName("chatInput");
    1714     var text = input.caption;
    1715     if (text.length)
    1716     {
    1717         Engine.SendNetworkChat(text);
    1718         input.caption = "";
    1719     }
     1719    var text = input.caption.trim();
     1720
     1721    if (!text.length)
     1722        return;
     1723
     1724    input.caption = "";
     1725
     1726    if (executeNetworkCommand(text))
     1727        return;
     1728
     1729    Engine.SendNetworkChat(text);
    17201730}
    17211731
     1732// Displays a given chat message.
    17221733function addChatMessage(msg)
    17231734{
    17241735    var username = "";
    17251736    if (msg.username)
    17261737        username = escapeText(msg.username);
     
    17501761    var formatted;
    17511762    switch (msg.type)
    17521763    {
    17531764    case "connect":
    17541765        var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
    1755         formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: sprintf(translate("%(username)s has joined"), { username: formattedUsername }) }) + '[/font]';
     1766        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { "message": sprintf(translate("%(username)s has joined"), { "username": formattedUsername }) }) + '[/font]';
    17561767        break;
    17571768
    17581769    case "disconnect":
    17591770        var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
    1760         formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: sprintf(translate("%(username)s has left"), { username: formattedUsername }) }) + '[/font]';
     1771        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { "message": sprintf(translate("%(username)s has left"), { "username": formattedUsername }) }) + '[/font]';
     1772        break;
     1773
     1774    case "clientlist":
     1775        formatted = sprintf(translate("Users: %(users)s"), { "users": getUsernameList().join(",") });
     1776        break;
     1777
     1778    case "kicked":
     1779        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { "message": sprintf(translate("%(username)s has been kicked."), { "username": username }) }) + '[/font]';
     1780        break;
     1781
     1782    case "banned":
     1783        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { "message": sprintf(translate("%(username)s has been banned."), { "username": username }) }) + '[/font]';
    17611784        break;
    17621785
    17631786    case "message":
    17641787        var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
    1765         var formattedUsernamePrefix = '[font="sans-bold-13"]' + sprintf(translate("<%(username)s>"), { username: formattedUsername }) + '[/font]'
    1766         formatted = sprintf(translate("%(username)s %(message)s"), { username: formattedUsernamePrefix, message: message });
     1788        var formattedUsernamePrefix = '[font="sans-bold-13"]' + sprintf(translate("<%(username)s>"), { "username": formattedUsername }) + '[/font]'
     1789        formatted = sprintf(translate("%(username)s %(message)s"), { "username": formattedUsernamePrefix, "message": message });
    17671790        break;
    17681791
    17691792    case "ready":
    17701793        var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font]'
    17711794        if (msg.ready)
    1772             formatted = ' ' + sprintf(translate("* %(username)s is ready!"), { username: formattedUsername });
     1795            formatted = ' ' + sprintf(translate("* %(username)s is ready!"), { "username": formattedUsername });
    17731796        else
    1774             formatted = ' ' + sprintf(translate("* %(username)s is not ready."), { username: formattedUsername });
     1797            formatted = ' ' + sprintf(translate("* %(username)s is not ready."), { "username": formattedUsername });
    17751798        break;
    17761799
    17771800    case "settings":
    1778         formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: translate('Game settings have been changed') }) + '[/font]';
     1801        formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { "message": translate('Game settings have been changed') }) + '[/font]';
    17791802        break;
    17801803
    17811804    default:
    1782         error(sprintf("Invalid chat message '%(message)s'", { message: uneval(msg) }));
     1805        error(sprintf("Invalid chat message '%(message)s'", { "message": uneval(msg) }));
    17831806        return;
    17841807    }
    17851808
    17861809    g_ChatMessages.push(formatted);
    17871810
  • binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml

     
    22
    33<objects>
    44
    55    <script file="gui/common/network.js"/>
    66    <script file="gui/common/functions_global_object.js"/>
     7    <script file="gui/common/functions_utility.js"/>
    78    <script file="gui/gamesetup/gamesetup_mp.js"/>
    89
    910    <!-- Add a translucent black background to fade out the menu page -->
    1011    <object type="image" sprite="ModernFade"/>
    1112
     
    4647            </object>
    4748
    4849            <object hotkey="confirm" type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed">
    4950                <translatableAttribute id="caption">Continue</translatableAttribute>
    5051                <action on="Press">
    51                     var joinPlayerName = Engine.GetGUIObjectByName("joinPlayerName").caption;
     52                    var joinPlayerName = sanitizePlayerName(Engine.GetGUIObjectByName("joinPlayerName").caption, true, true);
    5253                    var joinServer = Engine.GetGUIObjectByName("joinServer").caption;
    5354                    if (startJoin(joinPlayerName, joinServer))
    5455                        switchSetupPage("pageJoin", "pageConnecting");
    5556                </action>
    5657            </object>
     
    8889            </object>
    8990
    9091            <object type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed">
    9192                <translatableAttribute id="caption">Continue</translatableAttribute>
    9293                <action on="Press">
    93                     var hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;
     94                    var hostPlayerName = sanitizePlayerName(Engine.GetGUIObjectByName("hostPlayerName").caption, true, true);
    9495                    var hostServerName = Engine.GetGUIObjectByName("hostServerName").caption;
    9596                    if (startHost(hostPlayerName, hostServerName))
    9697                        switchSetupPage("pageHost", "pageConnecting");
    9798                </action>
    9899            </object>
  • binaries/data/mods/public/gui/session/messages.js

     
    77// Notification Data
    88const NOTIFICATION_TIMEOUT = 10000;
    99const MAX_NUM_NOTIFICATION_LINES = 3;
    1010var notifications = [];
    1111var notificationsTimers = [];
    12 var cheats = getCheatsData();
     12var g_Cheats = getCheatsData();
    1313
     14// Loads available cheats
    1415function getCheatsData()
    1516{
    1617    var cheats = {};
    1718    var cheatFileList = getJSONFileList("simulation/data/cheats/");
    18     for each (var fileName in cheatFileList)
     19    for (let fileName of cheatFileList)
    1920    {
    20         var currentCheat = Engine.ReadJSONFile("simulation/data/cheats/"+fileName+".json");
     21        let currentCheat = Engine.ReadJSONFile("simulation/data/cheats/" + fileName + ".json");
    2122        if (!currentCheat)
    2223            continue;
    2324        if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1)
    2425            warn("Cheat name '" + currentCheat.Name + "' is already present");
    2526        else
    2627            cheats[currentCheat.Name] = currentCheat.Data;
    2728    }
    2829    return cheats;
    2930}
    3031
     32// Returns true if a cheat was executed.
     33function executeCheat(text)
     34{
     35    // Cheats only if allowed
     36    if (g_IsObserver || !g_Players[Engine.GetPlayerID()].cheatsEnabled)
     37        return false;
     38
     39    // Find the cheat code that is a prefix of the user input
     40    var cheatCode = Object.keys(g_Cheats).find(cheatCode => text.indexOf(cheatCode) == 0);
     41
     42    // Cancel if not found
     43    if (!cheatCode)
     44        return false;
     45
     46    // Parse parameter from user input
     47    var parameter = text.substr(cheatCode.length);
     48
     49    // Load cheat object
     50    var cheat = g_Cheats[cheatCode];
     51
     52    // Parse parameter as number
     53    if (cheat.isNumeric)
     54        parameter = +parameter;
     55
     56    // Use default value if the user didn't specify the parameter properly
     57    if (cheat.DefaultParameter && (isNaN(parameter) || parameter < 0))
     58        parameter = cheat.DefaultParameter;
     59
     60    // Execute Cheat
     61    Engine.PostNetworkCommand(
     62    {
     63        "type": "cheat",
     64        "action": cheat.Action,
     65        "text": cheat.Type,
     66        "templates": cheat.Templates,
     67        "parameter": parameter,
     68        "player": Engine.GetPlayerID(),
     69        "selected": g_Selection.toList()
     70    });
     71
     72    return true;
     73}
     74
    3175var g_NotificationsTypes =
    3276{
    3377    "chat": function(notification, player)
    3478    {
    3579        var message = {
     
    204248        escapeText(g_Players[player].name),
    205249        color.r + " " + color.g + " " + color.b,
    206250    ];
    207251}
    208252
    209 // Messages
    210253function handleNetMessage(message)
    211254{
    212     log(sprintf(translate("Net message: %(message)s"), { message: uneval(message) }));
     255    log(sprintf(translate("Net message: %(message)s"), { "message": uneval(message) }));
    213256
    214257    switch (message.type)
    215258    {
    216259    case "netstatus":
    217260        // If we lost connection, further netstatus messages are useless
     
    241284            obj.caption = translate("Connection to the server has been authenticated.");
    242285            obj.hidden = false;
    243286            break;
    244287        case "disconnected":
    245288            g_Disconnected = true;
    246             obj.caption = translate("Connection to the server has been lost.") + "\n\n" + translate("The game has ended.");
     289            obj.caption = translate("Connection to the server has been lost.") + "\n" +
     290                sprintf(translate("Reason: %(reason)s."), { "reason": getDisconnectReason(message.reason) }) + "\n" +
     291                translate("The game has ended.");
    247292            obj.hidden = false;
    248293            break;
    249294        default:
    250295            error("Unrecognised netstatus type '" + message.status + "'");
    251296            break;
     
    296341    case "aichat":
    297342        addChatMessage({ "type": "message", "guid": message.guid, "text": message.text, "translate": true });
    298343        break;
    299344
    300345    case "rejoined":
    301         addChatMessage({ "type": "rejoined", "guid": message.guid});
     346        addChatMessage({ "type": "rejoined", "guid": message.guid });
    302347        break;
    303        
     348
     349    case "kicked":
     350        addChatMessage({ "type": message.ban ? "banned" : "kicked", "username": message.username });
     351        break;
     352
    304353    // To prevent errors, ignore these message types that occur during autostart
    305354    case "gamesetup":
    306355    case "start":
    307356        break;
    308357
    309358    default:
    310359        error("Unrecognised net message type '" + message.type + "'");
    311360    }
    312361}
    313362
     363// Sends or displays the given text as chat.
    314364function submitChatDirectly(text)
    315365{
    316366    if (text.length)
    317367    {
    318368        if (g_IsNetworked)
     
    320370        else
    321371            addChatMessage({ "type": "message", "guid": "local", "text": text });
    322372    }
    323373}
    324374
     375// Called when pressing enter or clicking the send button. May call cheats and commands.
    325376function submitChatInput()
    326377{
     378    // Get user input
    327379    var input = Engine.GetGUIObjectByName("chatInput");
    328     var text = input.caption;
    329     var isCheat = false;
    330     if (text.length)
    331     {
    332         if (!g_IsObserver && g_Players[Engine.GetPlayerID()].cheatsEnabled)
    333         {
    334             for each (var cheat in Object.keys(cheats))
    335             {
    336                 // Line must start with the cheat.
    337                 if (text.indexOf(cheat) !== 0)
    338                     continue;
     380    var user_input = input.caption.trim();
    339381
    340                 // test for additional parameter which is the rest of the string after the cheat
    341                 var parameter = "";
    342                 if (cheats[cheat].DefaultParameter !== undefined)
    343                 {
    344                     var par = text.substr(cheat.length);
    345                     par = par.replace(/^\W+/, '').replace(/\W+$/, ''); // remove whitespaces at start and end
    346 
    347                     // check, if the isNumeric flag is set
    348                     if (cheats[cheat].isNumeric)
    349                     {
    350                         // Match the first word in the substring.
    351                         var match = par.match(/\S+/);
    352                         if (match && match[0])
    353                             par = Math.floor(match[0]);
    354                         // check, if valid number could be parsed
    355                         if (par <= 0 || isNaN(par))
    356                             par = "";
    357                     }
    358 
    359                     // replace default parameter, if not empty or number
    360                     if (par.length > 0 || parseFloat(par) === par)
    361                         parameter = par;
    362                     else
    363                         parameter = cheats[cheat].DefaultParameter;
    364                 }
    365 
    366                 Engine.PostNetworkCommand({
    367                     "type": "cheat",
    368                     "action": cheats[cheat].Action,
    369                     "parameter": parameter,
    370                     "text": cheats[cheat].Type,
    371                     "selected": g_Selection.toList(),
    372                     "templates": cheats[cheat].Templates,
    373                     "player": Engine.GetPlayerID()});
    374                 isCheat = true;
    375                 break;
    376             }
    377         }
     382    // Clear chat input, remove focus and close window
     383    input.caption = "";
     384    input.blur();
     385    toggleChatWindow();
    378386
    379         // Observers should only send messages to "/all"
    380         if (!isCheat && (!g_IsObserver || text.indexOf("/") == -1 || text.indexOf("/all ") == 0))
    381         {
    382             if (Engine.GetGUIObjectByName("toggleTeamChat").checked)
    383                 text = "/team " + text;
     387    if (!user_input.length)
     388        return;
    384389
    385             if (g_IsNetworked)
    386                 Engine.SendNetworkChat(text);
    387             else
    388                 addChatMessage({ "type": "message", "guid": "local", "text": text });
    389         }
    390         input.caption = ""; // Clear chat input
    391     }
     390    if (executeCheat(user_input))
     391        return;
    392392
    393     input.blur(); // Remove focus
     393    if (executeNetworkCommand(user_input))
     394        return;
    394395
    395     toggleChatWindow();
     396    // Prohibit observers from sending private messages
     397    if (g_IsObserver && user_input.indexOf("/all") != 0)
     398        return;
     399
     400    // Optionally tag as team chat
     401    if (Engine.GetGUIObjectByName("toggleTeamChat").checked)
     402        user_input = "/team " + user_input;
     403
     404    submitChatDirectly(user_input);
    396405}
    397406
    398 function addChatMessage(msg, playerAssignments)
     407// Displays a given chat message.
     408function addChatMessage(msg)
    399409{
    400     // Default to global assignments, but allow overriding for when reporting
    401     // new players joining
    402     if (!playerAssignments)
    403         playerAssignments = g_PlayerAssignments;
    404 
    405410    var playerColor, username;
    406411
    407412    // No context by default. May be set by parseChatCommands().
    408413    msg.context = "";
    409414
    410     if ("guid" in msg && playerAssignments[msg.guid])
     415    if ("guid" in msg && g_PlayerAssignments[msg.guid])
    411416    {
    412         var n = playerAssignments[msg.guid].player;
     417        var n = g_PlayerAssignments[msg.guid].player;
    413418        // Observers have an ID of -1 which is not a valid index.
    414419        if (n < 0)
    415420            n = 0;
    416421        playerColor = g_Players[n].color.r + " " + g_Players[n].color.g + " " + g_Players[n].color.b;
    417         username = escapeText(playerAssignments[msg.guid].name);
     422        username = escapeText(g_PlayerAssignments[msg.guid].name);
    418423
    419424        // Parse in-line commands in regular messages.
    420425        if (msg.type == "message")
    421             parseChatCommands(msg, playerAssignments);
     426            parseChatCommands(msg);
    422427    }
    423428    else if (msg.type == "defeat" && msg.player)
    424429    {
    425430        [username, playerColor] = getUsernameAndColor(msg.player);
    426431    }
    427432    else if (msg.type == "message")
    428433    {
    429434        [username, playerColor] = getUsernameAndColor(msg.player);
    430         parseChatCommands(msg, playerAssignments);
     435        parseChatCommands(msg);
    431436    }
    432437    else
    433438    {
    434439        playerColor = "255 255 255";
    435440        username = translate("Unknown player");
     
    438443    var formatted;
    439444
    440445    switch (msg.type)
    441446    {
    442447    case "connect":
    443         formatted = sprintf(translate("%(player)s is starting to rejoin the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     448        formatted = sprintf(translate("%(player)s is starting to rejoin the game."), { "player": "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    444449        break;
    445450    case "disconnect":
    446         formatted = sprintf(translate("%(player)s has left the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     451        formatted = sprintf(translate("%(player)s has left the game."), { "player": "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    447452        break;
    448453    case "rejoined":
    449         formatted = sprintf(translate("%(player)s has rejoined the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     454        formatted = sprintf(translate("%(player)s has rejoined the game."), { "player": "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     455        break;
     456    case "clientlist":
     457        formatted = sprintf(translate("Users: %(users)s"), { "users": getUsernameList().join(",") });
     458        break;
     459    case "kicked":
     460        formatted = sprintf(translate("%(username)s has been kicked."), { "username": msg.username });
     461        break;
     462    case "banned":
     463        formatted = sprintf(translate("%(username)s has been banned."), { "username": msg.username });
    450464        break;
    451465    case "defeat":
    452466        // In singleplayer, the local player is "You". "You has" is incorrect.
    453467        if (!g_IsNetworked && msg.player == Engine.GetPlayerID())
    454468            formatted = translate("You have been defeated.");
    455469        else
    456             formatted = sprintf(translate("%(player)s has been defeated."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
     470            formatted = sprintf(translate("%(player)s has been defeated."), { "player": "[color=\"" + playerColor + "\"]" + username + "[/color]" });
    457471        break;
    458472    case "diplomacy":
    459473        var message;
    460474        if (msg.player == Engine.GetPlayerID())
    461475        {
     
    598612    chatTimers.shift();
    599613    chatMessages.shift();
    600614    Engine.GetGUIObjectByName("chatText").caption = chatMessages.join("\n");
    601615}
    602616
    603 // Parses chat messages for commands.
    604 function parseChatCommands(msg, playerAssignments)
     617// Parses commands that are specific to the chat.
     618function parseChatCommands(msg)
    605619{
    606620    // Only interested in messages that start with '/'.
    607621    if (!msg.text || msg.text[0] != '/')
    608622        return;
    609623
    610624    var sender;
    611     if (playerAssignments[msg.guid])
    612         sender = playerAssignments[msg.guid].player;
     625    if (g_PlayerAssignments[msg.guid])
     626        sender = g_PlayerAssignments[msg.guid].player;
    613627    else
    614628        sender = msg.player;
    615629
    616630    // TODO: It would be nice to display multiple different contexts.
    617631    // It should be made clear that only players matching the union of those receive the message.
     
    666680    case "/msg":
    667681        var trimmed = msg.text.substr(split[0].length + 1);
    668682        var matched = "";
    669683
    670684        // Reject names which don't match or are a superset of the intended name.
    671         for each (var player in playerAssignments)
     685        for (let player of g_PlayerAssignments)
    672686            if (trimmed.indexOf(player.name + " ") == 0 && player.name.length > matched.length)
    673687                matched = player.name;
    674688
    675689        // If the local player's name was the longest one matched, show the message.
    676690        var playerName = g_Players[Engine.GetPlayerID()].name;
     
    694708    if (!msg.text.length)
    695709        msg.hide = true;
    696710
    697711    // Attempt to parse more commands if the current command allows it.
    698712    if (recurse)
    699         parseChatCommands(msg, playerAssignments);
     713        parseChatCommands(msg);
    700714}
    701715
    702716function sendDialogAnswer(guiObject, dialogName)
    703717{
    704718    Engine.GetGUIObjectByName(dialogName+"-dialog").hidden = true;
  • binaries/data/mods/public/gui/session/session.xml

     
    55<script file="gui/common/colorFades.js"/>
    66<script file="gui/common/functions_civinfo.js"/>
    77<script file="gui/common/functions_global_object.js"/>
    88<script file="gui/common/functions_utility.js"/>
    99<script file="gui/common/l10n.js"/>
     10<script file="gui/common/network.js"/>
    1011<script file="gui/common/music.js"/>
    1112<script file="gui/common/timer.js"/>
    1213<script file="gui/common/tooltips.js"/>
    1314<!-- load all scripts in this directory -->
    1415<script directory="gui/session/"/>
  • source/gui/scripting/ScriptFunctions.cpp

     
    345345    SAFE_DELETE(g_NetServer);
    346346    SAFE_DELETE(g_NetClient);
    347347    SAFE_DELETE(g_Game);
    348348}
    349349
     350void KickPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW playerName, bool ban)
     351{
     352    if (g_NetServer)
     353        g_NetServer->KickPlayer(playerName, ban);
     354}
     355
    350356JS::Value PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate)
    351357{
    352358    if (!g_NetClient)
    353359        return JS::UndefinedValue();
    354360
     
    954960    scriptInterface.RegisterFunction<void, JS::HandleValue, int, &StartGame>("StartGame");
    955961    scriptInterface.RegisterFunction<void, &Script_EndGame>("EndGame");
    956962    scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost");
    957963    scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin");
    958964    scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame");
     965    scriptInterface.RegisterFunction<void, CStrW, bool, &KickPlayer>("KickPlayer");
    959966    scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient");
    960967    scriptInterface.RegisterFunction<void, JS::HandleValue, &SetNetworkGameAttributes>("SetNetworkGameAttributes");
    961968    scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer");
    962969    scriptInterface.RegisterFunction<void, std::string, int, &SetNetworkPlayerStatus>("SetNetworkPlayerStatus");
    963970    scriptInterface.RegisterFunction<void, &ClearAllPlayerReady>("ClearAllPlayerReady");
  • source/network/NetClient.cpp

     
    9191
    9292    AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context);
    9393    AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context);
    9494    AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context);
    9595    AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context);
     96    AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context);
    9697    AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context);
    9798    AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context);
    9899
    99100    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context);
    100101    AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context);
     
    108109    AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context);
    109110    AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context);
    110111    AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context);
    111112
    112113    AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context);
     114    AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context);
    113115    AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context);
    114116    AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context);
    115117    AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context);
    116118    AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context);
    117119    AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context);
     
    605607    client->PushGuiMessage(msg);
    606608
    607609    return true;
    608610}
    609611
     612bool CNetClient::OnKicked(void *context, CFsmEvent* event)
     613{
     614    ENSURE(event->GetType() == (uint)NMT_KICKED);
     615
     616    CNetClient* client = (CNetClient*)context;
     617    JSContext* cx = client->GetScriptInterface().GetContext();
     618
     619    CKickedMessage* message = (CKickedMessage*)event->GetParamRef();
     620    JS::RootedValue msg(cx);
     621    client->GetScriptInterface().Eval("({ 'type': 'kicked' })", &msg);
     622    client->GetScriptInterface().SetProperty(msg, "username", message->m_Name, false);
     623    client->GetScriptInterface().SetProperty(msg, "ban", message->m_Ban, false);
     624    client->PushGuiMessage(msg);
     625
     626    return true;
     627}
     628
    610629bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event)
    611630{
    612631    ENSURE(event->GetType() == (uint)NMT_LOADED_GAME);
    613632
    614633    CNetClient* client = (CNetClient*)context;
  • source/network/NetClient.h

     
    199199    static bool OnInGame(void* context, CFsmEvent* event);
    200200    static bool OnGameStart(void* context, CFsmEvent* event);
    201201    static bool OnJoinSyncStart(void* context, CFsmEvent* event);
    202202    static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event);
    203203    static bool OnRejoined(void* context, CFsmEvent* event);
     204    static bool OnKicked(void* context, CFsmEvent* event);
    204205    static bool OnLoadedGame(void* context, CFsmEvent* event);
    205206
    206207    /**
    207208     * Take ownership of a session object, and use it for all network communication.
    208209     */
  • source/network/NetHost.h

     
    6161{
    6262    NDR_UNKNOWN = 0,
    6363    NDR_UNEXPECTED_SHUTDOWN,
    6464    NDR_INCORRECT_PROTOCOL_VERSION,
    6565    NDR_SERVER_LOADING,
    66     NDR_SERVER_ALREADY_IN_GAME
     66    NDR_SERVER_ALREADY_IN_GAME,
     67    NDR_KICKED,
     68    NDR_BANNED
    6769};
    6870
    6971class CNetHost
    7072{
    7173public:
  • source/network/NetMessage.cpp

     
    133133
    134134    case NMT_REJOINED:
    135135        pNewMessage = new CRejoinedMessage;
    136136        break;
    137137
     138    case NMT_KICKED:
     139        pNewMessage = new CKickedMessage;
     140        break;
     141
    138142    case NMT_LOADED_GAME:
    139143        pNewMessage = new CLoadedGameMessage;
    140144        break;
    141145
    142146    case NMT_SERVER_HANDSHAKE:
  • source/network/NetMessages.h

     
    2626#include "ps/CStr.h"
    2727#include "scriptinterface/ScriptVal.h"
    2828
    2929#define PS_PROTOCOL_MAGIC               0x5073013f      // 'P', 's', 0x01, '?'
    3030#define PS_PROTOCOL_MAGIC_RESPONSE      0x50630121      // 'P', 'c', 0x01, '!'
    31 #define PS_PROTOCOL_VERSION             0x01010007      // Arbitrary protocol
     31#define PS_PROTOCOL_VERSION             0x01010008      // Arbitrary protocol
    3232#define PS_DEFAULT_PORT                 0x5073          // 'P', 's'
    3333
    3434// Defines the list of message types. The order of the list must not change.
    3535// The message types having a negative value are used internally and not sent
    3636// over the network. The message types used for network communication have
     
    5656    NMT_FILE_TRANSFER_ACK,
    5757
    5858    NMT_JOIN_SYNC_START,
    5959
    6060    NMT_REJOINED,
     61    NMT_KICKED,
    6162
    6263    NMT_LOADED_GAME,
    6364    NMT_GAME_START,
    6465    NMT_END_COMMAND_BATCH,
    6566    NMT_SYNC_CHECK, // OOS-detection hash checking
     
    159160
    160161START_NMT_CLASS_(Rejoined, NMT_REJOINED)
    161162    NMT_FIELD(CStr8, m_GUID)
    162163END_NMT_CLASS()
    163164
     165START_NMT_CLASS_(Kicked, NMT_KICKED)
     166    NMT_FIELD(CStrW, m_Name)
     167    NMT_FIELD_INT(m_Ban, bool, 1)
     168END_NMT_CLASS()
     169
    164170START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME)
    165171    NMT_FIELD_INT(m_CurrentTurn, u32, 4)
    166172END_NMT_CLASS()
    167173
    168174START_NMT_CLASS_(GameStart, NMT_GAME_START)
  • source/network/NetServer.cpp

     
    119119
    120120CNetServerWorker::CNetServerWorker(int autostartPlayers) :
    121121    m_AutostartPlayers(autostartPlayers),
    122122    m_Shutdown(false),
    123123    m_ScriptInterface(NULL),
    124     m_NextHostID(1), m_Host(NULL), m_Stats(NULL)
     124    m_NextHostID(1), m_Host(NULL), m_HostGUID(), m_Stats(NULL)
    125125{
    126126    m_State = SERVER_STATE_UNCONNECTED;
    127127
    128128    m_ServerTurnManager = NULL;
    129129
     
    604604    session->SetFirstState(NSS_HANDSHAKE);
    605605}
    606606
    607607bool CNetServerWorker::HandleConnect(CNetServerSession* session)
    608608{
     609    CNetServerWorker& server = session->GetServer();
     610
     611    // Disconnect banned IPs
     612    if (std::find(server.m_BannedIPs.begin(), server.m_BannedIPs.end(), session->GetIPAddress()) != server.m_BannedIPs.end())
     613    {
     614        session->Disconnect(NDR_BANNED);
     615        return false;
     616    }
     617
     618    // Send handshake challenge
    609619    CSrvHandshakeMessage handshake;
    610620    handshake.m_Magic = PS_PROTOCOL_MAGIC;
    611621    handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
    612622    handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
    613623    return session->SendMessage(&handshake);
     
    615625
    616626void CNetServerWorker::OnUserJoin(CNetServerSession* session)
    617627{
    618628    AddPlayer(session->GetGUID(), session->GetUserName());
    619629
     630    if (m_HostGUID.empty())
     631        m_HostGUID = session->GetGUID();
     632
    620633    CGameSetupMessage gameSetupMessage(GetScriptInterface());
    621634    gameSetupMessage.m_Data = m_GameAttributes.get();
    622635    session->SendMessage(&gameSetupMessage);
    623636
    624637    CPlayerAssignmentMessage assignMessage;
     
    630643{
    631644    RemovePlayer(session->GetGUID());
    632645
    633646    if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING)
    634647        m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers
    635 
    636     // TODO: ought to switch the player controlled by that client
    637     // back to AI control, or something?
    638648}
    639649
    640650void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name)
    641651{
    642652    // Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1)
     
    707717        it->second.m_Status = 0;
    708718
    709719    SendPlayerAssignments();
    710720}
    711721
     722void CNetServerWorker::KickPlayer(const CStrW& playerName, const bool ban)
     723{
     724    // Find the user with that name
     725    std::vector<CNetServerSession*>::iterator it = std::find_if(m_Sessions.begin(), m_Sessions.end(),
     726        [&](CNetServerSession* session) { return session->GetUserName() == playerName; });
     727
     728    // and return if no one or the host has that name
     729    if (it == m_Sessions.end() || (*it)->GetGUID() == m_HostGUID)
     730        return;
     731
     732    if (ban)
     733    {
     734        // Remember name
     735        if (std::find(m_BannedPlayers.begin(), m_BannedPlayers.end(), playerName) == m_BannedPlayers.end())
     736            m_BannedPlayers.push_back(playerName);
     737
     738        // Remember IP address
     739        CStr ipAddress = GetPlayerIPAddress(playerName);
     740        if (std::find(m_BannedIPs.begin(), m_BannedIPs.end(), ipAddress) == m_BannedIPs.end())
     741            m_BannedIPs.push_back(ipAddress);
     742    }
     743
     744    // Disconnect that user
     745    (*it)->Disconnect(ban ? NDR_BANNED : NDR_KICKED);
     746
     747    // Send message notifying other clients
     748    CKickedMessage kickedMessage;
     749    kickedMessage.m_Name = playerName;
     750    kickedMessage.m_Ban = ban;
     751    Broadcast(&kickedMessage);
     752}
     753
     754CStr CNetServerWorker::GetPlayerIPAddress(const CStrW& playerName)
     755{
     756    for (CNetServerSession* session : m_Sessions)
     757        if (session->GetUserName() == playerName)
     758            return session->GetIPAddress();
     759    return "(error)";
     760}
     761
    712762void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid)
    713763{
    714764    // Remove anyone who's already assigned to this player
    715765    for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it)
    716766    {
     
    764814    ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE);
    765815
    766816    CNetServerSession* session = (CNetServerSession*)context;
    767817    CNetServerWorker& server = session->GetServer();
    768818
     819    // Check protocol version
    769820    CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef();
    770821    if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION)
    771822    {
    772823        session->Disconnect(NDR_INCORRECT_PROTOCOL_VERSION);
    773824        return false;
    774825    }
    775826
     827    // Send handshake response
    776828    CSrvHandshakeResponseMessage handshakeResponse;
    777829    handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION;
    778830    handshakeResponse.m_Message = server.m_WelcomeMessage;
    779831    handshakeResponse.m_Flags = 0;
    780832    session->SendMessage(&handshakeResponse);
     
    798850    }
    799851
    800852    CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef();
    801853    CStrW username = server.DeduplicatePlayerName(SanitisePlayerName(message->m_Name));
    802854
     855    // Disconnect banned usernames
     856    if (std::find(server.m_BannedPlayers.begin(), server.m_BannedPlayers.end(), username) != server.m_BannedPlayers.end())
     857    {
     858        session->Disconnect(NDR_BANNED);
     859        return true;
     860    }
     861
    803862    // Optionally allow observers to join after the game has started
    804863    bool observerLateJoin = false;
    805864    ScriptInterface& scriptInterface = server.GetScriptInterface();
    806865    JSContext* cx = scriptInterface.GetContext();
    807866    JSAutoRequest rq(cx);
     
    11471206bool CNetServer::SetupConnection()
    11481207{
    11491208    return m_Worker->SetupConnection();
    11501209}
    11511210
     1211void CNetServer::KickPlayer(const CStrW& playerName, const bool ban)
     1212{
     1213    CScopeLock lock(m_Worker->m_WorkerMutex);
     1214    m_Worker->KickPlayer(playerName, ban);
     1215}
     1216
    11521217void CNetServer::AssignPlayer(int playerID, const CStr& guid)
    11531218{
    11541219    CScopeLock lock(m_Worker->m_WorkerMutex);
    11551220    m_Worker->m_AssignPlayerQueue.emplace_back(playerID, guid);
    11561221}
  • source/network/NetServer.h

     
    120120     * The given GUID will be (re)assigned to the given player ID.
    121121     * Any player currently using that ID will be unassigned.
    122122     * The changes will be asynchronously propagated to all clients.
    123123     */
    124124    void AssignPlayer(int playerID, const CStr& guid);
    125    
     125
    126126    /**
    127127     * Call from the GUI to update the player readiness.
    128128     * The changes will be asynchronously propagated to all clients.
    129129     */
    130130    void SetPlayerReady(const CStr& guid, int ready);
     
    132132    /**
    133133     * Call from the GUI to set the all player readiness to 0.
    134134     * The changes will be asynchronously propagated to all clients.
    135135     */
    136136    void ClearAllPlayerReady();
    137    
     137
     138    /**
     139     * Disconnects a player from the gamesetup / session.
     140     */
     141    void KickPlayer(const CStrW& playerName, const bool ban);
     142
    138143    /**
    139144     * Call from the GUI to asynchronously notify all clients that they should start loading the game.
    140145     */
    141146    void StartGame();
    142147
     
    181186     * Send a message to the given network peer.
    182187     */
    183188    bool SendMessage(ENetPeer* peer, const CNetMessage* message);
    184189
    185190    /**
     191     * Disconnected a player from the match / gamesetup and optionally prevents him/her from rejoining.
     192     */
     193    void KickPlayer(const CStrW& playerName, const bool ban);
     194
     195    /**
    186196     * Send a message to all clients who have completed the full connection process
    187197     * (i.e. are in the pre-game or in-game states).
    188198     */
    189199    bool Broadcast(const CNetMessage* message);
    190200
     201    /**
     202     * Returns the IP address of the given connected player.
     203     */
     204    CStr GetPlayerIPAddress(const CStrW& playerName);
     205
    191206private:
    192207    friend class CNetServer;
    193208    friend class CNetFileReceiveTask_ServerRejoin;
    194209
    195210    CNetServerWorker(int autostartPlayers);
     
    243258     */
    244259    void SetTurnLength(u32 msecs);
    245260
    246261    void AddPlayer(const CStr& guid, const CStrW& name);
    247262    void RemovePlayer(const CStr& guid);
    248     void SetPlayerReady(const CStr& guid, const int ready);
     263    void SetPlayerReady(const CStr& guid, const int ready);
     264    CStr GetHostGUID();
    249265    void SendPlayerAssignments();
    250266    void ClearAllPlayerReady();
    251267
    252268    void SetupSession(CNetServerSession* session);
    253269    bool HandleConnect(CNetServerSession* session);
     
    297313    NetServerState m_State;
    298314
    299315    CStrW m_ServerName;
    300316    CStrW m_WelcomeMessage;
    301317
     318    std::vector<CStr> m_BannedIPs;
     319    std::vector<CStrW> m_BannedPlayers;
     320
    302321    u32 m_NextHostID;
    303322
    304323    CNetServerTurnManager* m_ServerTurnManager;
    305324
     325    CStr m_HostGUID;
     326
    306327    /**
    307328     * A copy of all simulation commands received so far, indexed by
    308329     * turn number, to simplify support for rejoining etc.
    309330     * TODO: verify this doesn't use too much RAM.
    310331     */
  • source/network/NetSession.cpp

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    173173CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) :
    174174    m_Server(server), m_FileTransferer(this), m_Peer(peer)
    175175{
    176176}
    177177
     178CStr CNetServerSession::GetIPAddress()
     179{
     180    char ipAddress[256] = "(error)";
     181    enet_address_get_host_ip(&(m_Peer->address), ipAddress, ARRAY_SIZE(ipAddress));
     182    return CStr(ipAddress);
     183}
     184
    178185void CNetServerSession::Disconnect(u32 reason)
    179186{
    180187    Update((uint)NMT_CONNECTION_LOST, NULL);
    181188
    182189    enet_peer_disconnect(m_Peer, reason);
  • source/network/NetSession.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2015 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    55 * it under the terms of the GNU General Public License as published by
    66 * the Free Software Foundation, either version 2 of the License, or
     
    121121
    122122    u32 GetHostID() const { return m_HostID; }
    123123    void SetHostID(u32 id) { m_HostID = id; }
    124124
    125125    /**
     126     * Returns the IP address of the client.
     127     */
     128    CStr GetIPAddress();
     129
     130    /**
    126131     * Sends a disconnection notification to the client,
    127132     * and sends a NMT_CONNECTION_LOST message to the session FSM.
    128133     * The server will receive a disconnection notification after a while.
    129134     * The server will not receive any further messages sent via this session.
    130135     */