Ticket #1767: chat_addressee_dropdown_v1.patch
File chat_addressee_dropdown_v1.patch, 16.4 KB (added by , 8 years ago) |
---|
-
binaries/data/mods/public/gui/common/network.js
function kickPlayer(username, ban) 85 85 }) 86 86 }); 87 87 } 88 88 89 89 /** 90 * Get a colorized list of usernames sorted by player slot, observers last. 91 * Requires g_PlayerAssignments and colorizePlayernameByGUID. 92 * 93 * @returns {string} 90 * Sort GUIDs of connected users sorted by playerindex, observers last. 91 * Requires g_PlayerAssignments. 94 92 */ 95 function getUsernameList()93 function sortGUIDsByPlayerID() 96 94 { 97 let usernames =Object.keys(g_PlayerAssignments).sort((guidA, guidB) => {95 return Object.keys(g_PlayerAssignments).sort((guidA, guidB) => { 98 96 99 97 let playerIdA = g_PlayerAssignments[guidA].player; 100 98 let playerIdB = g_PlayerAssignments[guidB].player; 101 99 102 // Sort observers last103 100 if (playerIdA == -1) return +1; 104 101 if (playerIdB == -1) return -1; 105 102 106 // Sort players107 103 return playerIdA - playerIdB; 104 }); 105 } 108 106 109 }).map(guid => colorizePlayernameByGUID(guid)); 107 /** 108 * Get a colorized list of usernames sorted by player slot, observers last. 109 * Requires g_PlayerAssignments and colorizePlayernameByGUID. 110 * 111 * @returns {string} 112 */ 113 function getUsernameList() 114 { 115 let usernames = sortGUIDsByPlayerID().map(guid => colorizePlayernameByGUID(guid)); 110 116 111 117 return sprintf(translate("Users: %(users)s"), 112 118 // Translation: This comma is used for separating first to penultimate elements in an enumeration. 113 119 { "users": usernames.join(translate(", ")) }); 114 120 } -
binaries/data/mods/public/gui/session/menu.js
function openChat() 201 201 if (g_Disconnected) 202 202 return; 203 203 204 204 closeOpenDialogs(); 205 205 206 updateTeamCheckbox(false);206 setTeamChat(false); 207 207 208 208 Engine.GetGUIObjectByName("chatInput").focus(); // Grant focus to the input area 209 209 Engine.GetGUIObjectByName("chatDialogPanel").hidden = false; 210 210 } 211 211 … … function closeChat() 215 215 Engine.GetGUIObjectByName("chatInput").blur(); // Remove focus 216 216 Engine.GetGUIObjectByName("chatDialogPanel").hidden = true; 217 217 } 218 218 219 219 /** 220 * Chat is sent via GUID, not playerID. 220 * If the teamchat hotkey was pressed, set allies or observers as addressees, 221 * otherwise send to everyone. 221 222 */ 222 function updateTeamCheckbox(check)223 function setTeamChat(teamChat = false) 223 224 { 224 Engine.GetGUIObjectByName("toggleTeamChatLabel").hidden = g_IsObserver; 225 let toggleTeamChat = Engine.GetGUIObjectByName("toggleTeamChat"); 226 toggleTeamChat.hidden = g_IsObserver; 227 toggleTeamChat.checked = !g_IsObserver && check; 225 let command = teamChat ? (g_IsObserver ? "/observers" : "/allies") : ""; 226 let chatAddressee = Engine.GetGUIObjectByName("chatAddressee"); 227 chatAddressee.selected = chatAddressee.list_data.indexOf(command); 228 228 } 229 229 /** 230 * Opens chat-window or closes it and sends the userinput. 231 */ 230 232 function toggleChatWindow(teamChat) 231 233 { 232 234 if (g_Disconnected) 233 235 return; 234 236 … … function toggleChatWindow(teamChat) 237 239 let hidden = chatWindow.hidden; 238 240 239 241 closeOpenDialogs(); 240 242 241 243 if (hidden) 242 chatInput.focus(); // Grant focus to the input area 244 { 245 setTeamChat(teamChat); 246 chatInput.focus(); 247 } 243 248 else 244 249 { 245 250 if (chatInput.caption.length) 246 251 { 247 252 submitChatInput(); 248 253 return; 249 254 } 250 chatInput.caption = ""; // Clear chat input255 chatInput.caption = ""; 251 256 } 252 257 253 updateTeamCheckbox(teamChat);254 258 chatWindow.hidden = !hidden; 255 259 } 256 260 257 261 function setDiplomacy(data) 258 262 { -
binaries/data/mods/public/gui/session/messages.js
var g_ChatCommands = { 83 83 84 84 var g_ChatAddresseeContext = { 85 85 "/team": translate("Team"), 86 86 "/allies": translate("Ally"), 87 87 "/enemies": translate("Enemy"), 88 "/observers": translate("Observer"), 88 89 "/msg": translate("Private") 89 90 }; 90 91 91 92 /** 92 93 * Returns true if the current player is an addressee, given the chat message type and sender. … … var g_IsChatAddressee = { 98 99 g_Players[Engine.GetPlayerID()].team != -1 && 99 100 g_Players[Engine.GetPlayerID()].team == g_Players[senderID].team, 100 101 101 102 "/allies": senderID => 102 103 g_Players[senderID] && 104 g_Players[Engine.GetPlayerID()] && 103 105 g_Players[senderID].isMutualAlly[Engine.GetPlayerID()], 104 106 105 107 "/enemies": senderID => 106 108 g_Players[senderID] && 109 g_Players[Engine.GetPlayerID()] && 107 110 g_Players[senderID].isEnemy[Engine.GetPlayerID()], 108 111 112 "/observers": senderID => 113 g_IsObserver, 114 109 115 "/msg": (senderID, addresseeGUID) => 110 g_Players[Engine.GetPlayerID()] && 111 g_PlayerAssignments[addresseeGUID] && 112 g_PlayerAssignments[addresseeGUID].name == g_Players[Engine.GetPlayerID()].name 116 addresseeGUID == Engine.GetPlayerGUID() 113 117 }; 114 118 115 119 /** 116 120 * Chatmessage shown on diplomacy change. 117 121 */ … … var g_NotificationsTypes = 186 190 "guid": findGuidForPlayerID(player), 187 191 "player": player 188 192 }); 189 193 190 194 updateDiplomacy(); 195 updateChatAddressees(); 191 196 }, 192 197 "diplomacy": function(notification, player) 193 198 { 194 199 addChatMessage({ 195 200 "type": "diplomacy", … … function handlePlayerAssignmentsMessage( 441 446 } 442 447 443 448 addChatMessage({ "type": "connect", "guid": guid }); 444 449 }); 445 450 451 updateChatAddressees(); 452 446 453 // Update lobby gamestatus 447 454 if (g_IsController && Engine.HasXmppClient()) 448 455 { 449 456 let players = Object.keys(g_PlayerAssignments).map(guid => g_PlayerAssignments[guid].name); 450 457 Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", ")); 451 458 } 452 459 } 453 460 461 function updateChatAddressees() 462 { 463 let addressees = [ 464 { 465 "label": translateWithContext("chat addressee", "Everyone"), 466 "cmd": "" 467 } 468 ]; 469 470 if (!g_IsObserver) 471 { 472 addressees.push({ 473 "label": translateWithContext("chat addressee", "Allies"), 474 "cmd": "/allies" 475 }); 476 addressees.push({ 477 "label": translateWithContext("chat addressee", "Enemies"), 478 "cmd": "/enemies" 479 }); 480 } 481 482 addressees.push({ 483 "label": translateWithContext("chat addressee", "Observers"), 484 "cmd": "/observers" 485 }); 486 487 // Add playernames for private messages 488 for (let guid of sortGUIDsByPlayerID()) 489 { 490 let username = g_PlayerAssignments[guid].name; 491 let playerIndex = g_PlayerAssignments[guid].player; 492 493 if (playerIndex == Engine.GetPlayerID()) 494 continue; 495 496 // Don't provide option for PM from observer to player 497 if (g_IsObserver && !isPlayerObserver(playerIndex)) 498 continue; 499 500 let colorBox = isPlayerObserver(playerIndex) ? "" : '[color="' + rgbToGuiColor(g_Players[playerIndex].color) + '"]■ [/color]'; 501 502 addressees.push({ 503 "cmd": "/msg " + username, 504 "label": colorBox + username 505 }); 506 } 507 508 let chatAddressee = Engine.GetGUIObjectByName("chatAddressee"); 509 chatAddressee.list = addressees.map(adressee => adressee.label); 510 chatAddressee.list_data = addressees.map(adressee => adressee.cmd); 511 chatAddressee.selected = 0; 512 } 513 454 514 /** 455 515 * Send text as chat. Don't look for commands. 456 516 * 457 517 * @param {string} text 458 518 */ … … function submitChatDirectly(text) 471 531 * Loads the text from the GUI window, checks if it is a local command 472 532 * or cheat and executes it. Otherwise sends it as chat. 473 533 */ 474 534 function submitChatInput() 475 535 { 476 let teamChat = Engine.GetGUIObjectByName("toggleTeamChat").checked;477 536 let input = Engine.GetGUIObjectByName("chatInput"); 478 537 let text = input.caption; 479 538 480 539 input.blur(); // Remove focus 481 540 input.caption = ""; // Clear chat input … … function submitChatInput() 488 547 return; 489 548 490 549 if (executeCheat(text)) 491 550 return; 492 551 493 // Observers should only be able to chat with everyone. 494 if (g_IsObserver && text.indexOf("/") == 0 && text.indexOf("/me ") != 0) 495 return; 496 497 if (teamChat && text.indexOf("/team ") != 0) 498 text = "/team " + text; 552 let chatAddressee = Engine.GetGUIObjectByName("chatAddressee"); 553 if (chatAddressee.selected > 0 && (text.indexOf("/") != 0 || text.indexOf("/me ") == 0)) 554 text = chatAddressee.list_data[chatAddressee.selected] + " " + text; 499 555 500 556 submitChatDirectly(text); 501 557 } 502 558 503 559 /** … … function formatChatCommand(msg) 621 677 { 622 678 if (!msg.text) 623 679 return ""; 624 680 625 681 let isMe = msg.text.indexOf("/me ") == 0; 626 if (!isMe && ! checkChatAddressee(msg))682 if (!isMe && !isChatAddressee(msg)) 627 683 return ""; 628 684 629 685 isMe = msg.text.indexOf("/me ") == 0; 630 686 if (isMe) 631 687 msg.text = msg.text.substr("/me ".length); … … function formatChatCommand(msg) 657 713 }); 658 714 } 659 715 660 716 /** 661 717 * Checks if the current user is an addressee of the chatmessage sent by another player. 718 * Sets the context of that message. 719 * Returns true if the message should be displayed. 662 720 * 663 721 * @param {Object} msg 664 722 */ 665 function checkChatAddressee(msg)723 function isChatAddressee(msg) 666 724 { 667 725 if (msg.text[0] != '/') 668 726 return true; 669 727 670 if (Engine.GetPlayerID() == -1) 671 return false; 672 728 // Split addressee command and message-text 673 729 let cmd = msg.text.split(/\s/)[0]; 674 730 msg.text = msg.text.substr(cmd.length + 1); 675 731 676 732 if (cmd == "/ally") 677 733 cmd = "/allies"; 678 734 679 735 if (cmd == "/enemy") 680 736 cmd = "/enemies"; 681 737 682 // GUID for players, ID for bots 738 if (cmd == "/observer") 739 cmd = "/observers"; 740 741 // GUID for players and observers, ID for bots 683 742 let senderID = (g_PlayerAssignments[msg.guid] || msg).player; 743 let isSender = msg.guid ? msg.guid == Engine.GetPlayerGUID() : senderID == Engine.GetPlayerID(); 744 745 // Parse private message 746 let isPM = cmd == "/msg"; 684 747 let addresseeGUID; 685 if (cmd == "/msg") 748 let addresseeIndex; 749 if (isPM) 686 750 { 687 751 addresseeGUID = matchUsername(msg.text); 688 752 let addressee = g_PlayerAssignments[addresseeGUID]; 689 if (!addressee || addressee.player == -1 || senderID == -1) 753 if (!addressee) 754 { 755 if (isSender) 756 warn("Couldn't match username: " + msg.text); 757 return false; 758 } 759 760 // Prohibit PM if addressee and sender are identical 761 if (isSender && addresseeGUID == Engine.GetPlayerGUID()) 690 762 return false; 763 691 764 msg.text = msg.text.substr(addressee.name.length + 1); 765 addresseeIndex = addressee.player; 692 766 } 693 767 694 let isSender = senderID == Engine.GetPlayerID();768 // Set context string 695 769 if (!g_ChatAddresseeContext[cmd]) 696 770 { 697 771 if (isSender) 698 772 warn("Unknown chat command: " + cmd); 699 773 return false; 700 774 } 701 775 msg.context = g_ChatAddresseeContext[cmd]; 702 776 777 // For observers only permit public- and observer-chat and PM to observers 778 if (isPlayerObserver(senderID) && 779 (isPM && !isPlayerObserver(addresseeIndex) || !isPM && cmd != "/observers")) 780 return false; 781 703 782 return isSender || g_IsChatAddressee[cmd](senderID, addresseeGUID); 704 783 } 705 784 706 785 /** 707 786 * Returns the guid of the user with the longest name that is a prefix of the given string. -
binaries/data/mods/public/gui/session/session.js
function selectViewPlayer(playerID) 314 314 315 315 Engine.SetViewedPlayer(g_ViewedPlayer); 316 316 317 317 updateTopPanel(); 318 318 319 updateChatAddressees(); 320 319 321 // Update GUI and clear player-dependent cache 320 322 onSimulationUpdate(); 321 323 322 324 let viewPlayer = Engine.GetGUIObjectByName("viewPlayer"); 323 325 viewPlayer.hidden = !changeView; -
binaries/data/mods/public/gui/session/session.xml
207 207 <object name="chatPanel" size="0 130 100% 100%-240" type="image" ghost="true" z="0" absolute="true"> 208 208 <object name="chatText" size="3 1 100%-1 100%-1" type="text" style="chatPanel" ghost="true"/> 209 209 </object> 210 210 211 211 <!-- Chat window --> 212 <object name="chatDialogPanel" size="50%-180 50%-48 50%+180 50%+36" type="image" hidden="true" sprite="genericPanel"> 213 <object name="chatInput" size="16 12 100%-16 36" type="input" style="ModernInput" max_length="80"> 212 <object name="chatDialogPanel" size="50%-180 50%-66 50%+180 50%+54" type="image" hidden="true" sprite="genericPanel"> 213 214 <!-- Message addressee --> 215 <object size="16 14 50 38" type="text" style="chatPanel"> 216 <translatableAttribute id="caption" context="chat input">To:</translatableAttribute> 217 </object> 218 <object size="75 12 100%-16 36" name="chatAddressee" type="dropdown" style="ModernDropDown" tooltip_style="sessionToolTipBold"> 219 <translatableAttribute id="tooltip" context="chat input">Select chatmessage addresse</translatableAttribute> 220 </object> 221 222 <!-- Message text --> 223 <object size="16 46 50 70" type="text" style="chatPanel"> 224 <translatableAttribute id="caption" context="chat input">Text:</translatableAttribute> 225 </object> 226 <object name="chatInput" size="75 44 100%-16 68" type="input" style="ModernInput" max_length="80"> 227 <translatableAttribute id="tooltip" context="chat input">Type the message to send.</translatableAttribute> 214 228 <action on="Press">submitChatInput();</action> 215 229 <action on="Tab"> 216 230 var players = []; 217 231 for (var player in g_PlayerAssignments) 218 232 players.push(g_PlayerAssignments[player]); 219 233 autoCompleteNick("chatInput", players); 220 234 </action> 221 235 </object> 222 236 237 <!-- Cancel --> 223 238 <object size="16 100%-40 30%+16 100%-12" type="button" style="StoneButton"> 224 239 <translatableAttribute id="caption">Cancel</translatableAttribute> 225 240 <action on="Press">closeChat();</action> 226 241 </object> 227 242 228 <object name="toggleTeamChat" size="30%+22 100%-36 30%+40 100%-6" type="checkbox" style="ModernTickBox"/> 229 <object name="toggleTeamChatLabel" size="30%+40 100%-40 60%+16 100%-12" type="text" style="ModernLeftLabelText"> 230 <translatableAttribute id="caption">Team Only</translatableAttribute> 231 </object> 232 243 <!-- Send --> 233 244 <object size="60%+16 100%-40 100%-16 100%-12" type="button" style="StoneButton"> 234 245 <translatableAttribute id="caption">Send</translatableAttribute> 235 246 <action on="Press">submitChatInput();</action> 236 247 </object> 237 248 </object> -
source/gui/scripting/ScriptFunctions.cpp
void DisconnectNetworkGame(ScriptInterfa 379 379 SAFE_DELETE(g_NetServer); 380 380 SAFE_DELETE(g_NetClient); 381 381 SAFE_DELETE(g_Game); 382 382 } 383 383 384 std::string GetPlayerGUID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate)) 385 { 386 if (!g_NetClient) 387 return ""; 388 389 return g_NetClient->GetGUID(); 390 } 391 384 392 bool KickPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStrW& playerName, bool ban) 385 393 { 386 394 if (!g_NetServer) 387 395 return false; 388 396 … … void GuiScriptingInit(ScriptInterface& s 1014 1022 scriptInterface.RegisterFunction<void, JS::HandleValue, int, &StartGame>("StartGame"); 1015 1023 scriptInterface.RegisterFunction<void, &Script_EndGame>("EndGame"); 1016 1024 scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost"); 1017 1025 scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin"); 1018 1026 scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame"); 1027 scriptInterface.RegisterFunction<std::string, &GetPlayerGUID>("GetPlayerGUID"); 1019 1028 scriptInterface.RegisterFunction<bool, CStrW, bool, &KickPlayer>("KickPlayer"); 1020 1029 scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient"); 1021 1030 scriptInterface.RegisterFunction<void, JS::HandleValue, &SetNetworkGameAttributes>("SetNetworkGameAttributes"); 1022 1031 scriptInterface.RegisterFunction<void, int, std::string, &AssignNetworkPlayer>("AssignNetworkPlayer"); 1023 1032 scriptInterface.RegisterFunction<void, std::string, int, &SetNetworkPlayerStatus>("SetNetworkPlayerStatus"); -
source/network/NetClient.h
public: 87 87 * This must not be called after the connection setup. 88 88 */ 89 89 void SetUserName(const CStrW& username); 90 90 91 91 /** 92 * Returns the GUID of the local client. 93 * Used for distinguishing observers. 94 */ 95 CStr GetGUID() const { return m_GUID; } 96 97 /** 92 98 * Set up a connection to the remote networked server. 93 99 * @param server IP address or host name to connect to 94 100 * @return true on success, false on connection failure 95 101 */ 96 102 bool SetupConnection(const CStr& server);