Ticket #3241: t3241_kick_v6.2.patch
File t3241_kick_v6.2.patch, 40.1 KB (added by , 9 years ago) |
---|
-
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 */ 7 function getDisconnectReason(reason) 2 8 { 3 // Must be kept in sync with source/network/NetHost.h 4 switch (id) 9 switch (reason) 5 10 { 6 11 case 0: return translate("Unknown reason"); 7 12 case 1: return translate("Unexpected shutdown"); 8 13 case 2: return translate("Incorrect network protocol version"); 9 14 case 3: return translate("Game is loading, please try later"); 10 15 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 }); 12 19 } 13 20 } 14 21 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 */ 15 27 function reportDisconnect(reason) 16 28 { 17 var reasontext = getDisconnectReason(reason);18 19 29 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) }), 21 32 translate("Disconnected"), 2); 22 33 } 34 35 /** 36 * Get usernames sorted by player slot, observers last. 37 * Requires g_PlayerAssignments. 38 * 39 * @returns {string[]} the usernames 40 */ 41 function 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 */ 60 function 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
518 518 519 519 case "chat": 520 520 addChatMessage({ "type": "message", "guid": message.guid, "text": message.text }); 521 521 break; 522 522 523 case "kicked": 524 addChatMessage({ "type": message.ban ? "banned" : "kicked", "username": message.username }); 525 break; 526 523 527 // Singular client to host message 524 528 case "ready": 525 529 g_ReadyChanged -= 1; 526 530 if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1) 527 531 addChatMessage({ "type": "ready", "guid": message.guid, "ready": +message.status == 1 }); … … 1706 1710 1707 1711 // Remove AI from this player slot 1708 1712 g_GameAttributes.settings.PlayerData[newSlot].AI = ""; 1709 1713 } 1710 1714 1715 // Called when pressing enter or clicking the send button. May call network commands. 1711 1716 function submitChatInput() 1712 1717 { 1713 1718 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); 1720 1730 } 1721 1731 1732 // Displays a given chat message. 1722 1733 function addChatMessage(msg) 1723 1734 { 1724 1735 var username = ""; 1725 1736 if (msg.username) 1726 1737 username = escapeText(msg.username); … … 1750 1761 var formatted; 1751 1762 switch (msg.type) 1752 1763 { 1753 1764 case "connect": 1754 1765 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]'; 1756 1767 break; 1757 1768 1758 1769 case "disconnect": 1759 1770 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]'; 1761 1784 break; 1762 1785 1763 1786 case "message": 1764 1787 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 }); 1767 1790 break; 1768 1791 1769 1792 case "ready": 1770 1793 var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font]' 1771 1794 if (msg.ready) 1772 formatted = ' ' + sprintf(translate("* %(username)s is ready!"), { username: formattedUsername });1795 formatted = ' ' + sprintf(translate("* %(username)s is ready!"), { "username": formattedUsername }); 1773 1796 else 1774 formatted = ' ' + sprintf(translate("* %(username)s is not ready."), { username: formattedUsername });1797 formatted = ' ' + sprintf(translate("* %(username)s is not ready."), { "username": formattedUsername }); 1775 1798 break; 1776 1799 1777 1800 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]'; 1779 1802 break; 1780 1803 1781 1804 default: 1782 error(sprintf("Invalid chat message '%(message)s'", { message: uneval(msg) }));1805 error(sprintf("Invalid chat message '%(message)s'", { "message": uneval(msg) })); 1783 1806 return; 1784 1807 } 1785 1808 1786 1809 g_ChatMessages.push(formatted); 1787 1810 -
binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml
2 2 3 3 <objects> 4 4 5 5 <script file="gui/common/network.js"/> 6 6 <script file="gui/common/functions_global_object.js"/> 7 <script file="gui/common/functions_utility.js"/> 7 8 <script file="gui/gamesetup/gamesetup_mp.js"/> 8 9 9 10 <!-- Add a translucent black background to fade out the menu page --> 10 11 <object type="image" sprite="ModernFade"/> 11 12 … … 46 47 </object> 47 48 48 49 <object hotkey="confirm" type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed"> 49 50 <translatableAttribute id="caption">Continue</translatableAttribute> 50 51 <action on="Press"> 51 var joinPlayerName = Engine.GetGUIObjectByName("joinPlayerName").caption;52 var joinPlayerName = sanitizePlayerName(Engine.GetGUIObjectByName("joinPlayerName").caption, true, true); 52 53 var joinServer = Engine.GetGUIObjectByName("joinServer").caption; 53 54 if (startJoin(joinPlayerName, joinServer)) 54 55 switchSetupPage("pageJoin", "pageConnecting"); 55 56 </action> 56 57 </object> … … 88 89 </object> 89 90 90 91 <object type="button" size="50%+5 100%-45 100%-18 100%-17" style="ModernButtonRed"> 91 92 <translatableAttribute id="caption">Continue</translatableAttribute> 92 93 <action on="Press"> 93 var hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;94 var hostPlayerName = sanitizePlayerName(Engine.GetGUIObjectByName("hostPlayerName").caption, true, true); 94 95 var hostServerName = Engine.GetGUIObjectByName("hostServerName").caption; 95 96 if (startHost(hostPlayerName, hostServerName)) 96 97 switchSetupPage("pageHost", "pageConnecting"); 97 98 </action> 98 99 </object> -
binaries/data/mods/public/gui/session/messages.js
7 7 // Notification Data 8 8 const NOTIFICATION_TIMEOUT = 10000; 9 9 const MAX_NUM_NOTIFICATION_LINES = 3; 10 10 var notifications = []; 11 11 var notificationsTimers = []; 12 var cheats = getCheatsData();12 var g_Cheats = getCheatsData(); 13 13 14 // Loads available cheats 14 15 function getCheatsData() 15 16 { 16 17 var cheats = {}; 17 18 var cheatFileList = getJSONFileList("simulation/data/cheats/"); 18 for each (var fileName incheatFileList)19 for (let fileName of cheatFileList) 19 20 { 20 var currentCheat = Engine.ReadJSONFile("simulation/data/cheats/"+fileName+".json");21 let currentCheat = Engine.ReadJSONFile("simulation/data/cheats/" + fileName + ".json"); 21 22 if (!currentCheat) 22 23 continue; 23 24 if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1) 24 25 warn("Cheat name '" + currentCheat.Name + "' is already present"); 25 26 else 26 27 cheats[currentCheat.Name] = currentCheat.Data; 27 28 } 28 29 return cheats; 29 30 } 30 31 32 // Returns true if a cheat was executed. 33 function 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 31 75 var g_NotificationsTypes = 32 76 { 33 77 "chat": function(notification, player) 34 78 { 35 79 var message = { … … 204 248 escapeText(g_Players[player].name), 205 249 color.r + " " + color.g + " " + color.b, 206 250 ]; 207 251 } 208 252 209 // Messages210 253 function handleNetMessage(message) 211 254 { 212 log(sprintf(translate("Net message: %(message)s"), { message: uneval(message) }));255 log(sprintf(translate("Net message: %(message)s"), { "message": uneval(message) })); 213 256 214 257 switch (message.type) 215 258 { 216 259 case "netstatus": 217 260 // If we lost connection, further netstatus messages are useless … … 241 284 obj.caption = translate("Connection to the server has been authenticated."); 242 285 obj.hidden = false; 243 286 break; 244 287 case "disconnected": 245 288 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."); 247 292 obj.hidden = false; 248 293 break; 249 294 default: 250 295 error("Unrecognised netstatus type '" + message.status + "'"); 251 296 break; … … 296 341 case "aichat": 297 342 addChatMessage({ "type": "message", "guid": message.guid, "text": message.text, "translate": true }); 298 343 break; 299 344 300 345 case "rejoined": 301 addChatMessage({ "type": "rejoined", "guid": message.guid });346 addChatMessage({ "type": "rejoined", "guid": message.guid }); 302 347 break; 303 348 349 case "kicked": 350 addChatMessage({ "type": message.ban ? "banned" : "kicked", "username": message.username }); 351 break; 352 304 353 // To prevent errors, ignore these message types that occur during autostart 305 354 case "gamesetup": 306 355 case "start": 307 356 break; 308 357 309 358 default: 310 359 error("Unrecognised net message type '" + message.type + "'"); 311 360 } 312 361 } 313 362 363 // Sends or displays the given text as chat. 314 364 function submitChatDirectly(text) 315 365 { 316 366 if (text.length) 317 367 { 318 368 if (g_IsNetworked) … … 320 370 else 321 371 addChatMessage({ "type": "message", "guid": "local", "text": text }); 322 372 } 323 373 } 324 374 375 // Called when pressing enter or clicking the send button. May call cheats and commands. 325 376 function submitChatInput() 326 377 { 378 // Get user input 327 379 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(); 339 381 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(); 378 386 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; 384 389 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; 392 392 393 input.blur(); // Remove focus 393 if (executeNetworkCommand(user_input)) 394 return; 394 395 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); 396 405 } 397 406 398 function addChatMessage(msg, playerAssignments) 407 // Displays a given chat message. 408 function addChatMessage(msg) 399 409 { 400 // Default to global assignments, but allow overriding for when reporting401 // new players joining402 if (!playerAssignments)403 playerAssignments = g_PlayerAssignments;404 405 410 var playerColor, username; 406 411 407 412 // No context by default. May be set by parseChatCommands(). 408 413 msg.context = ""; 409 414 410 if ("guid" in msg && playerAssignments[msg.guid])415 if ("guid" in msg && g_PlayerAssignments[msg.guid]) 411 416 { 412 var n = playerAssignments[msg.guid].player;417 var n = g_PlayerAssignments[msg.guid].player; 413 418 // Observers have an ID of -1 which is not a valid index. 414 419 if (n < 0) 415 420 n = 0; 416 421 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); 418 423 419 424 // Parse in-line commands in regular messages. 420 425 if (msg.type == "message") 421 parseChatCommands(msg , playerAssignments);426 parseChatCommands(msg); 422 427 } 423 428 else if (msg.type == "defeat" && msg.player) 424 429 { 425 430 [username, playerColor] = getUsernameAndColor(msg.player); 426 431 } 427 432 else if (msg.type == "message") 428 433 { 429 434 [username, playerColor] = getUsernameAndColor(msg.player); 430 parseChatCommands(msg , playerAssignments);435 parseChatCommands(msg); 431 436 } 432 437 else 433 438 { 434 439 playerColor = "255 255 255"; 435 440 username = translate("Unknown player"); … … 438 443 var formatted; 439 444 440 445 switch (msg.type) 441 446 { 442 447 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]" }); 444 449 break; 445 450 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]" }); 447 452 break; 448 453 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 }); 450 464 break; 451 465 case "defeat": 452 466 // In singleplayer, the local player is "You". "You has" is incorrect. 453 467 if (!g_IsNetworked && msg.player == Engine.GetPlayerID()) 454 468 formatted = translate("You have been defeated."); 455 469 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]" }); 457 471 break; 458 472 case "diplomacy": 459 473 var message; 460 474 if (msg.player == Engine.GetPlayerID()) 461 475 { … … 598 612 chatTimers.shift(); 599 613 chatMessages.shift(); 600 614 Engine.GetGUIObjectByName("chatText").caption = chatMessages.join("\n"); 601 615 } 602 616 603 // Parses c hat messages for commands.604 function parseChatCommands(msg , playerAssignments)617 // Parses commands that are specific to the chat. 618 function parseChatCommands(msg) 605 619 { 606 620 // Only interested in messages that start with '/'. 607 621 if (!msg.text || msg.text[0] != '/') 608 622 return; 609 623 610 624 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; 613 627 else 614 628 sender = msg.player; 615 629 616 630 // TODO: It would be nice to display multiple different contexts. 617 631 // It should be made clear that only players matching the union of those receive the message. … … 666 680 case "/msg": 667 681 var trimmed = msg.text.substr(split[0].length + 1); 668 682 var matched = ""; 669 683 670 684 // 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) 672 686 if (trimmed.indexOf(player.name + " ") == 0 && player.name.length > matched.length) 673 687 matched = player.name; 674 688 675 689 // If the local player's name was the longest one matched, show the message. 676 690 var playerName = g_Players[Engine.GetPlayerID()].name; … … 694 708 if (!msg.text.length) 695 709 msg.hide = true; 696 710 697 711 // Attempt to parse more commands if the current command allows it. 698 712 if (recurse) 699 parseChatCommands(msg , playerAssignments);713 parseChatCommands(msg); 700 714 } 701 715 702 716 function sendDialogAnswer(guiObject, dialogName) 703 717 { 704 718 Engine.GetGUIObjectByName(dialogName+"-dialog").hidden = true; -
binaries/data/mods/public/gui/session/session.xml
5 5 <script file="gui/common/colorFades.js"/> 6 6 <script file="gui/common/functions_civinfo.js"/> 7 7 <script file="gui/common/functions_global_object.js"/> 8 8 <script file="gui/common/functions_utility.js"/> 9 9 <script file="gui/common/l10n.js"/> 10 <script file="gui/common/network.js"/> 10 11 <script file="gui/common/music.js"/> 11 12 <script file="gui/common/timer.js"/> 12 13 <script file="gui/common/tooltips.js"/> 13 14 <!-- load all scripts in this directory --> 14 15 <script directory="gui/session/"/> -
source/gui/scripting/ScriptFunctions.cpp
345 345 SAFE_DELETE(g_NetServer); 346 346 SAFE_DELETE(g_NetClient); 347 347 SAFE_DELETE(g_Game); 348 348 } 349 349 350 void KickPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStrW playerName, bool ban) 351 { 352 if (g_NetServer) 353 g_NetServer->KickPlayer(playerName, ban); 354 } 355 350 356 JS::Value PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate) 351 357 { 352 358 if (!g_NetClient) 353 359 return JS::UndefinedValue(); 354 360 … … 954 960 scriptInterface.RegisterFunction<void, JS::HandleValue, int, &StartGame>("StartGame"); 955 961 scriptInterface.RegisterFunction<void, &Script_EndGame>("EndGame"); 956 962 scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost"); 957 963 scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin"); 958 964 scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame"); 965 scriptInterface.RegisterFunction<void, CStrW, bool, &KickPlayer>("KickPlayer"); 959 966 scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient"); 960 967 scriptInterface.RegisterFunction<void, JS::HandleValue, &SetNetworkGameAttributes>("SetNetworkGameAttributes"); 961 968 scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer"); 962 969 scriptInterface.RegisterFunction<void, std::string, int, &SetNetworkPlayerStatus>("SetNetworkPlayerStatus"); 963 970 scriptInterface.RegisterFunction<void, &ClearAllPlayerReady>("ClearAllPlayerReady"); -
source/network/NetClient.cpp
91 91 92 92 AddTransition(NCS_PREGAME, (uint)NMT_CHAT, NCS_PREGAME, (void*)&OnChat, context); 93 93 AddTransition(NCS_PREGAME, (uint)NMT_READY, NCS_PREGAME, (void*)&OnReady, context); 94 94 AddTransition(NCS_PREGAME, (uint)NMT_GAME_SETUP, NCS_PREGAME, (void*)&OnGameSetup, context); 95 95 AddTransition(NCS_PREGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_PREGAME, (void*)&OnPlayerAssignment, context); 96 AddTransition(NCS_PREGAME, (uint)NMT_KICKED, NCS_PREGAME, (void*)&OnKicked, context); 96 97 AddTransition(NCS_PREGAME, (uint)NMT_GAME_START, NCS_LOADING, (void*)&OnGameStart, context); 97 98 AddTransition(NCS_PREGAME, (uint)NMT_JOIN_SYNC_START, NCS_JOIN_SYNCING, (void*)&OnJoinSyncStart, context); 98 99 99 100 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_CHAT, NCS_JOIN_SYNCING, (void*)&OnChat, context); 100 101 AddTransition(NCS_JOIN_SYNCING, (uint)NMT_GAME_SETUP, NCS_JOIN_SYNCING, (void*)&OnGameSetup, context); … … 108 109 AddTransition(NCS_LOADING, (uint)NMT_GAME_SETUP, NCS_LOADING, (void*)&OnGameSetup, context); 109 110 AddTransition(NCS_LOADING, (uint)NMT_PLAYER_ASSIGNMENT, NCS_LOADING, (void*)&OnPlayerAssignment, context); 110 111 AddTransition(NCS_LOADING, (uint)NMT_LOADED_GAME, NCS_INGAME, (void*)&OnLoadedGame, context); 111 112 112 113 AddTransition(NCS_INGAME, (uint)NMT_REJOINED, NCS_INGAME, (void*)&OnRejoined, context); 114 AddTransition(NCS_INGAME, (uint)NMT_KICKED, NCS_INGAME, (void*)&OnKicked, context); 113 115 AddTransition(NCS_INGAME, (uint)NMT_CHAT, NCS_INGAME, (void*)&OnChat, context); 114 116 AddTransition(NCS_INGAME, (uint)NMT_GAME_SETUP, NCS_INGAME, (void*)&OnGameSetup, context); 115 117 AddTransition(NCS_INGAME, (uint)NMT_PLAYER_ASSIGNMENT, NCS_INGAME, (void*)&OnPlayerAssignment, context); 116 118 AddTransition(NCS_INGAME, (uint)NMT_SIMULATION_COMMAND, NCS_INGAME, (void*)&OnInGame, context); 117 119 AddTransition(NCS_INGAME, (uint)NMT_SYNC_ERROR, NCS_INGAME, (void*)&OnInGame, context); … … 605 607 client->PushGuiMessage(msg); 606 608 607 609 return true; 608 610 } 609 611 612 bool 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 610 629 bool CNetClient::OnLoadedGame(void* context, CFsmEvent* event) 611 630 { 612 631 ENSURE(event->GetType() == (uint)NMT_LOADED_GAME); 613 632 614 633 CNetClient* client = (CNetClient*)context; -
source/network/NetClient.h
199 199 static bool OnInGame(void* context, CFsmEvent* event); 200 200 static bool OnGameStart(void* context, CFsmEvent* event); 201 201 static bool OnJoinSyncStart(void* context, CFsmEvent* event); 202 202 static bool OnJoinSyncEndCommandBatch(void* context, CFsmEvent* event); 203 203 static bool OnRejoined(void* context, CFsmEvent* event); 204 static bool OnKicked(void* context, CFsmEvent* event); 204 205 static bool OnLoadedGame(void* context, CFsmEvent* event); 205 206 206 207 /** 207 208 * Take ownership of a session object, and use it for all network communication. 208 209 */ -
source/network/NetHost.h
61 61 { 62 62 NDR_UNKNOWN = 0, 63 63 NDR_UNEXPECTED_SHUTDOWN, 64 64 NDR_INCORRECT_PROTOCOL_VERSION, 65 65 NDR_SERVER_LOADING, 66 NDR_SERVER_ALREADY_IN_GAME 66 NDR_SERVER_ALREADY_IN_GAME, 67 NDR_KICKED, 68 NDR_BANNED 67 69 }; 68 70 69 71 class CNetHost 70 72 { 71 73 public: -
source/network/NetMessage.cpp
133 133 134 134 case NMT_REJOINED: 135 135 pNewMessage = new CRejoinedMessage; 136 136 break; 137 137 138 case NMT_KICKED: 139 pNewMessage = new CKickedMessage; 140 break; 141 138 142 case NMT_LOADED_GAME: 139 143 pNewMessage = new CLoadedGameMessage; 140 144 break; 141 145 142 146 case NMT_SERVER_HANDSHAKE: -
source/network/NetMessages.h
26 26 #include "ps/CStr.h" 27 27 #include "scriptinterface/ScriptVal.h" 28 28 29 29 #define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?' 30 30 #define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!' 31 #define PS_PROTOCOL_VERSION 0x0101000 7// Arbitrary protocol31 #define PS_PROTOCOL_VERSION 0x01010008 // Arbitrary protocol 32 32 #define PS_DEFAULT_PORT 0x5073 // 'P', 's' 33 33 34 34 // Defines the list of message types. The order of the list must not change. 35 35 // The message types having a negative value are used internally and not sent 36 36 // over the network. The message types used for network communication have … … 56 56 NMT_FILE_TRANSFER_ACK, 57 57 58 58 NMT_JOIN_SYNC_START, 59 59 60 60 NMT_REJOINED, 61 NMT_KICKED, 61 62 62 63 NMT_LOADED_GAME, 63 64 NMT_GAME_START, 64 65 NMT_END_COMMAND_BATCH, 65 66 NMT_SYNC_CHECK, // OOS-detection hash checking … … 159 160 160 161 START_NMT_CLASS_(Rejoined, NMT_REJOINED) 161 162 NMT_FIELD(CStr8, m_GUID) 162 163 END_NMT_CLASS() 163 164 165 START_NMT_CLASS_(Kicked, NMT_KICKED) 166 NMT_FIELD(CStrW, m_Name) 167 NMT_FIELD_INT(m_Ban, bool, 1) 168 END_NMT_CLASS() 169 164 170 START_NMT_CLASS_(LoadedGame, NMT_LOADED_GAME) 165 171 NMT_FIELD_INT(m_CurrentTurn, u32, 4) 166 172 END_NMT_CLASS() 167 173 168 174 START_NMT_CLASS_(GameStart, NMT_GAME_START) -
source/network/NetServer.cpp
119 119 120 120 CNetServerWorker::CNetServerWorker(int autostartPlayers) : 121 121 m_AutostartPlayers(autostartPlayers), 122 122 m_Shutdown(false), 123 123 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) 125 125 { 126 126 m_State = SERVER_STATE_UNCONNECTED; 127 127 128 128 m_ServerTurnManager = NULL; 129 129 … … 604 604 session->SetFirstState(NSS_HANDSHAKE); 605 605 } 606 606 607 607 bool CNetServerWorker::HandleConnect(CNetServerSession* session) 608 608 { 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 609 619 CSrvHandshakeMessage handshake; 610 620 handshake.m_Magic = PS_PROTOCOL_MAGIC; 611 621 handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION; 612 622 handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION; 613 623 return session->SendMessage(&handshake); … … 615 625 616 626 void CNetServerWorker::OnUserJoin(CNetServerSession* session) 617 627 { 618 628 AddPlayer(session->GetGUID(), session->GetUserName()); 619 629 630 if (m_HostGUID.empty()) 631 m_HostGUID = session->GetGUID(); 632 620 633 CGameSetupMessage gameSetupMessage(GetScriptInterface()); 621 634 gameSetupMessage.m_Data = m_GameAttributes.get(); 622 635 session->SendMessage(&gameSetupMessage); 623 636 624 637 CPlayerAssignmentMessage assignMessage; … … 630 643 { 631 644 RemovePlayer(session->GetGUID()); 632 645 633 646 if (m_ServerTurnManager && session->GetCurrState() != NSS_JOIN_SYNCING) 634 647 m_ServerTurnManager->UninitialiseClient(session->GetHostID()); // TODO: only for non-observers 635 636 // TODO: ought to switch the player controlled by that client637 // back to AI control, or something?638 648 } 639 649 640 650 void CNetServerWorker::AddPlayer(const CStr& guid, const CStrW& name) 641 651 { 642 652 // Find all player IDs in active use; we mustn't give them to a second player (excluding the unassigned ID: -1) … … 707 717 it->second.m_Status = 0; 708 718 709 719 SendPlayerAssignments(); 710 720 } 711 721 722 void 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 754 CStr 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 712 762 void CNetServerWorker::AssignPlayer(int playerID, const CStr& guid) 713 763 { 714 764 // Remove anyone who's already assigned to this player 715 765 for (PlayerAssignmentMap::iterator it = m_PlayerAssignments.begin(); it != m_PlayerAssignments.end(); ++it) 716 766 { … … 764 814 ENSURE(event->GetType() == (uint)NMT_CLIENT_HANDSHAKE); 765 815 766 816 CNetServerSession* session = (CNetServerSession*)context; 767 817 CNetServerWorker& server = session->GetServer(); 768 818 819 // Check protocol version 769 820 CCliHandshakeMessage* message = (CCliHandshakeMessage*)event->GetParamRef(); 770 821 if (message->m_ProtocolVersion != PS_PROTOCOL_VERSION) 771 822 { 772 823 session->Disconnect(NDR_INCORRECT_PROTOCOL_VERSION); 773 824 return false; 774 825 } 775 826 827 // Send handshake response 776 828 CSrvHandshakeResponseMessage handshakeResponse; 777 829 handshakeResponse.m_UseProtocolVersion = PS_PROTOCOL_VERSION; 778 830 handshakeResponse.m_Message = server.m_WelcomeMessage; 779 831 handshakeResponse.m_Flags = 0; 780 832 session->SendMessage(&handshakeResponse); … … 798 850 } 799 851 800 852 CAuthenticateMessage* message = (CAuthenticateMessage*)event->GetParamRef(); 801 853 CStrW username = server.DeduplicatePlayerName(SanitisePlayerName(message->m_Name)); 802 854 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 803 862 // Optionally allow observers to join after the game has started 804 863 bool observerLateJoin = false; 805 864 ScriptInterface& scriptInterface = server.GetScriptInterface(); 806 865 JSContext* cx = scriptInterface.GetContext(); 807 866 JSAutoRequest rq(cx); … … 1147 1206 bool CNetServer::SetupConnection() 1148 1207 { 1149 1208 return m_Worker->SetupConnection(); 1150 1209 } 1151 1210 1211 void CNetServer::KickPlayer(const CStrW& playerName, const bool ban) 1212 { 1213 CScopeLock lock(m_Worker->m_WorkerMutex); 1214 m_Worker->KickPlayer(playerName, ban); 1215 } 1216 1152 1217 void CNetServer::AssignPlayer(int playerID, const CStr& guid) 1153 1218 { 1154 1219 CScopeLock lock(m_Worker->m_WorkerMutex); 1155 1220 m_Worker->m_AssignPlayerQueue.emplace_back(playerID, guid); 1156 1221 } -
source/network/NetServer.h
120 120 * The given GUID will be (re)assigned to the given player ID. 121 121 * Any player currently using that ID will be unassigned. 122 122 * The changes will be asynchronously propagated to all clients. 123 123 */ 124 124 void AssignPlayer(int playerID, const CStr& guid); 125 125 126 126 /** 127 127 * Call from the GUI to update the player readiness. 128 128 * The changes will be asynchronously propagated to all clients. 129 129 */ 130 130 void SetPlayerReady(const CStr& guid, int ready); … … 132 132 /** 133 133 * Call from the GUI to set the all player readiness to 0. 134 134 * The changes will be asynchronously propagated to all clients. 135 135 */ 136 136 void ClearAllPlayerReady(); 137 137 138 /** 139 * Disconnects a player from the gamesetup / session. 140 */ 141 void KickPlayer(const CStrW& playerName, const bool ban); 142 138 143 /** 139 144 * Call from the GUI to asynchronously notify all clients that they should start loading the game. 140 145 */ 141 146 void StartGame(); 142 147 … … 181 186 * Send a message to the given network peer. 182 187 */ 183 188 bool SendMessage(ENetPeer* peer, const CNetMessage* message); 184 189 185 190 /** 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 /** 186 196 * Send a message to all clients who have completed the full connection process 187 197 * (i.e. are in the pre-game or in-game states). 188 198 */ 189 199 bool Broadcast(const CNetMessage* message); 190 200 201 /** 202 * Returns the IP address of the given connected player. 203 */ 204 CStr GetPlayerIPAddress(const CStrW& playerName); 205 191 206 private: 192 207 friend class CNetServer; 193 208 friend class CNetFileReceiveTask_ServerRejoin; 194 209 195 210 CNetServerWorker(int autostartPlayers); … … 243 258 */ 244 259 void SetTurnLength(u32 msecs); 245 260 246 261 void AddPlayer(const CStr& guid, const CStrW& name); 247 262 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(); 249 265 void SendPlayerAssignments(); 250 266 void ClearAllPlayerReady(); 251 267 252 268 void SetupSession(CNetServerSession* session); 253 269 bool HandleConnect(CNetServerSession* session); … … 297 313 NetServerState m_State; 298 314 299 315 CStrW m_ServerName; 300 316 CStrW m_WelcomeMessage; 301 317 318 std::vector<CStr> m_BannedIPs; 319 std::vector<CStrW> m_BannedPlayers; 320 302 321 u32 m_NextHostID; 303 322 304 323 CNetServerTurnManager* m_ServerTurnManager; 305 324 325 CStr m_HostGUID; 326 306 327 /** 307 328 * A copy of all simulation commands received so far, indexed by 308 329 * turn number, to simplify support for rejoining etc. 309 330 * TODO: verify this doesn't use too much RAM. 310 331 */ -
source/network/NetSession.cpp
1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 5 * it under the terms of the GNU General Public License as published by 6 6 * the Free Software Foundation, either version 2 of the License, or … … 173 173 CNetServerSession::CNetServerSession(CNetServerWorker& server, ENetPeer* peer) : 174 174 m_Server(server), m_FileTransferer(this), m_Peer(peer) 175 175 { 176 176 } 177 177 178 CStr 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 178 185 void CNetServerSession::Disconnect(u32 reason) 179 186 { 180 187 Update((uint)NMT_CONNECTION_LOST, NULL); 181 188 182 189 enet_peer_disconnect(m_Peer, reason); -
source/network/NetSession.h
1 /* Copyright (C) 201 1Wildfire Games.1 /* Copyright (C) 2015 Wildfire Games. 2 2 * This file is part of 0 A.D. 3 3 * 4 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 5 * it under the terms of the GNU General Public License as published by 6 6 * the Free Software Foundation, either version 2 of the License, or … … 121 121 122 122 u32 GetHostID() const { return m_HostID; } 123 123 void SetHostID(u32 id) { m_HostID = id; } 124 124 125 125 /** 126 * Returns the IP address of the client. 127 */ 128 CStr GetIPAddress(); 129 130 /** 126 131 * Sends a disconnection notification to the client, 127 132 * and sends a NMT_CONNECTION_LOST message to the session FSM. 128 133 * The server will receive a disconnection notification after a while. 129 134 * The server will not receive any further messages sent via this session. 130 135 */