Add chat-addressee dropdown, refs #1767.

Add observer-only chat and allow private messages from observer to
observer.
Prevent defeated players from using the team-chat, fixes #3441.

This was SVN commit r17771.
This commit is contained in:
elexis 2016-02-18 01:09:56 +00:00
parent 365cbf5b64
commit f8b20d181d
7 changed files with 167 additions and 50 deletions

View File

@ -86,6 +86,24 @@ function kickPlayer(username, ban)
});
}
/**
* Sort GUIDs of connected users sorted by playerindex, observers last.
* Requires g_PlayerAssignments.
*/
function sortGUIDsByPlayerID()
{
return Object.keys(g_PlayerAssignments).sort((guidA, guidB) => {
let playerIdA = g_PlayerAssignments[guidA].player;
let playerIdB = g_PlayerAssignments[guidB].player;
if (playerIdA == -1) return +1;
if (playerIdB == -1) return -1;
return playerIdA - playerIdB;
});
}
/**
* Get a colorized list of usernames sorted by player slot, observers last.
* Requires g_PlayerAssignments and colorizePlayernameByGUID.
@ -94,19 +112,7 @@ function kickPlayer(username, ban)
*/
function getUsernameList()
{
let usernames = Object.keys(g_PlayerAssignments).sort((guidA, guidB) => {
let playerIdA = g_PlayerAssignments[guidA].player;
let playerIdB = g_PlayerAssignments[guidB].player;
// Sort observers last
if (playerIdA == -1) return +1;
if (playerIdB == -1) return -1;
// Sort players
return playerIdA - playerIdB;
}).map(guid => colorizePlayernameByGUID(guid));
let usernames = sortGUIDsByPlayerID().map(guid => colorizePlayernameByGUID(guid));
return sprintf(translate("Users: %(users)s"),
// Translation: This comma is used for separating first to penultimate elements in an enumeration.

View File

@ -203,7 +203,7 @@ function openChat()
closeOpenDialogs();
updateTeamCheckbox(false);
setTeamChat(false);
Engine.GetGUIObjectByName("chatInput").focus(); // Grant focus to the input area
Engine.GetGUIObjectByName("chatDialogPanel").hidden = false;
@ -217,16 +217,18 @@ function closeChat()
}
/**
* Chat is sent via GUID, not playerID.
* If the teamchat hotkey was pressed, set allies or observers as addressees,
* otherwise send to everyone.
*/
function updateTeamCheckbox(check)
function setTeamChat(teamChat = false)
{
Engine.GetGUIObjectByName("toggleTeamChatLabel").hidden = g_IsObserver;
let toggleTeamChat = Engine.GetGUIObjectByName("toggleTeamChat");
toggleTeamChat.hidden = g_IsObserver;
toggleTeamChat.checked = !g_IsObserver && check;
let command = teamChat ? (g_IsObserver ? "/observers" : "/allies") : "";
let chatAddressee = Engine.GetGUIObjectByName("chatAddressee");
chatAddressee.selected = chatAddressee.list_data.indexOf(command);
}
/**
* Opens chat-window or closes it and sends the userinput.
*/
function toggleChatWindow(teamChat)
{
if (g_Disconnected)
@ -239,7 +241,10 @@ function toggleChatWindow(teamChat)
closeOpenDialogs();
if (hidden)
chatInput.focus(); // Grant focus to the input area
{
setTeamChat(teamChat);
chatInput.focus();
}
else
{
if (chatInput.caption.length)
@ -247,10 +252,9 @@ function toggleChatWindow(teamChat)
submitChatInput();
return;
}
chatInput.caption = ""; // Clear chat input
chatInput.caption = "";
}
updateTeamCheckbox(teamChat);
chatWindow.hidden = !hidden;
}

View File

@ -85,6 +85,7 @@ var g_ChatAddresseeContext = {
"/team": translate("Team"),
"/allies": translate("Ally"),
"/enemies": translate("Enemy"),
"/observers": translate("Observer"),
"/msg": translate("Private")
};
@ -100,16 +101,19 @@ var g_IsChatAddressee = {
"/allies": senderID =>
g_Players[senderID] &&
g_Players[Engine.GetPlayerID()] &&
g_Players[senderID].isMutualAlly[Engine.GetPlayerID()],
"/enemies": senderID =>
g_Players[senderID] &&
g_Players[Engine.GetPlayerID()] &&
g_Players[senderID].isEnemy[Engine.GetPlayerID()],
"/observers": senderID =>
g_IsObserver,
"/msg": (senderID, addresseeGUID) =>
g_Players[Engine.GetPlayerID()] &&
g_PlayerAssignments[addresseeGUID] &&
g_PlayerAssignments[addresseeGUID].name == g_Players[Engine.GetPlayerID()].name
addresseeGUID == Engine.GetPlayerGUID()
};
/**
@ -188,6 +192,7 @@ var g_NotificationsTypes =
});
updateDiplomacy();
updateChatAddressees();
},
"diplomacy": function(notification, player)
{
@ -443,6 +448,8 @@ function handlePlayerAssignmentsMessage(message)
addChatMessage({ "type": "connect", "guid": guid });
});
updateChatAddressees();
// Update lobby gamestatus
if (g_IsController && Engine.HasXmppClient())
{
@ -451,6 +458,59 @@ function handlePlayerAssignmentsMessage(message)
}
}
function updateChatAddressees()
{
let addressees = [
{
"label": translateWithContext("chat addressee", "Everyone"),
"cmd": ""
}
];
if (!g_IsObserver)
{
addressees.push({
"label": translateWithContext("chat addressee", "Allies"),
"cmd": "/allies"
});
addressees.push({
"label": translateWithContext("chat addressee", "Enemies"),
"cmd": "/enemies"
});
}
addressees.push({
"label": translateWithContext("chat addressee", "Observers"),
"cmd": "/observers"
});
// Add playernames for private messages
for (let guid of sortGUIDsByPlayerID())
{
let username = g_PlayerAssignments[guid].name;
let playerIndex = g_PlayerAssignments[guid].player;
if (playerIndex == Engine.GetPlayerID())
continue;
// Don't provide option for PM from observer to player
if (g_IsObserver && !isPlayerObserver(playerIndex))
continue;
let colorBox = isPlayerObserver(playerIndex) ? "" : '[color="' + rgbToGuiColor(g_Players[playerIndex].color) + '"]■ [/color]';
addressees.push({
"cmd": "/msg " + username,
"label": colorBox + username
});
}
let chatAddressee = Engine.GetGUIObjectByName("chatAddressee");
chatAddressee.list = addressees.map(adressee => adressee.label);
chatAddressee.list_data = addressees.map(adressee => adressee.cmd);
chatAddressee.selected = 0;
}
/**
* Send text as chat. Don't look for commands.
*
@ -473,7 +533,6 @@ function submitChatDirectly(text)
*/
function submitChatInput()
{
let teamChat = Engine.GetGUIObjectByName("toggleTeamChat").checked;
let input = Engine.GetGUIObjectByName("chatInput");
let text = input.caption;
@ -490,12 +549,9 @@ function submitChatInput()
if (executeCheat(text))
return;
// Observers should only be able to chat with everyone.
if (g_IsObserver && text.indexOf("/") == 0 && text.indexOf("/me ") != 0)
return;
if (teamChat && text.indexOf("/team ") != 0)
text = "/team " + text;
let chatAddressee = Engine.GetGUIObjectByName("chatAddressee");
if (chatAddressee.selected > 0 && (text.indexOf("/") != 0 || text.indexOf("/me ") == 0))
text = chatAddressee.list_data[chatAddressee.selected] + " " + text;
submitChatDirectly(text);
}
@ -623,7 +679,7 @@ function formatChatCommand(msg)
return "";
let isMe = msg.text.indexOf("/me ") == 0;
if (!isMe && !checkChatAddressee(msg))
if (!isMe && !isChatAddressee(msg))
return "";
isMe = msg.text.indexOf("/me ") == 0;
@ -659,17 +715,17 @@ function formatChatCommand(msg)
/**
* Checks if the current user is an addressee of the chatmessage sent by another player.
* Sets the context of that message.
* Returns true if the message should be displayed.
*
* @param {Object} msg
*/
function checkChatAddressee(msg)
function isChatAddressee(msg)
{
if (msg.text[0] != '/')
return true;
if (Engine.GetPlayerID() == -1)
return false;
// Split addressee command and message-text
let cmd = msg.text.split(/\s/)[0];
msg.text = msg.text.substr(cmd.length + 1);
@ -679,19 +735,37 @@ function checkChatAddressee(msg)
if (cmd == "/enemy")
cmd = "/enemies";
// GUID for players, ID for bots
if (cmd == "/observer")
cmd = "/observers";
// GUID for players and observers, ID for bots
let senderID = (g_PlayerAssignments[msg.guid] || msg).player;
let isSender = msg.guid ? msg.guid == Engine.GetPlayerGUID() : senderID == Engine.GetPlayerID();
// Parse private message
let isPM = cmd == "/msg";
let addresseeGUID;
if (cmd == "/msg")
let addresseeIndex;
if (isPM)
{
addresseeGUID = matchUsername(msg.text);
let addressee = g_PlayerAssignments[addresseeGUID];
if (!addressee || addressee.player == -1 || senderID == -1)
if (!addressee)
{
if (isSender)
warn("Couldn't match username: " + msg.text);
return false;
}
// Prohibit PM if addressee and sender are identical
if (isSender && addresseeGUID == Engine.GetPlayerGUID())
return false;
msg.text = msg.text.substr(addressee.name.length + 1);
addresseeIndex = addressee.player;
}
let isSender = senderID == Engine.GetPlayerID();
// Set context string
if (!g_ChatAddresseeContext[cmd])
{
if (isSender)
@ -700,6 +774,11 @@ function checkChatAddressee(msg)
}
msg.context = g_ChatAddresseeContext[cmd];
// For observers only permit public- and observer-chat and PM to observers
if (isPlayerObserver(senderID) &&
(isPM && !isPlayerObserver(addresseeIndex) || !isPM && cmd != "/observers"))
return false;
return isSender || g_IsChatAddressee[cmd](senderID, addresseeGUID);
}

View File

@ -316,6 +316,8 @@ function selectViewPlayer(playerID)
updateTopPanel();
updateChatAddressees();
// Update GUI and clear player-dependent cache
onSimulationUpdate();

View File

@ -209,8 +209,22 @@
</object>
<!-- Chat window -->
<object name="chatDialogPanel" size="50%-180 50%-48 50%+180 50%+36" type="image" hidden="true" sprite="genericPanel">
<object name="chatInput" size="16 12 100%-16 36" type="input" style="ModernInput" max_length="80">
<object name="chatDialogPanel" size="50%-180 50%-66 50%+180 50%+54" type="image" hidden="true" sprite="genericPanel">
<!-- Message addressee -->
<object size="16 14 50 38" type="text" style="chatPanel">
<translatableAttribute id="caption" context="chat input">To:</translatableAttribute>
</object>
<object size="75 12 100%-16 36" name="chatAddressee" type="dropdown" style="ModernDropDown" tooltip_style="sessionToolTipBold">
<translatableAttribute id="tooltip" context="chat input">Select chatmessage addresse</translatableAttribute>
</object>
<!-- Message text -->
<object size="16 46 50 70" type="text" style="chatPanel">
<translatableAttribute id="caption" context="chat input">Text:</translatableAttribute>
</object>
<object name="chatInput" size="75 44 100%-16 68" type="input" style="ModernInput" max_length="80">
<translatableAttribute id="tooltip" context="chat input">Type the message to send.</translatableAttribute>
<action on="Press">submitChatInput();</action>
<action on="Tab">
var players = [];
@ -220,16 +234,13 @@
</action>
</object>
<!-- Cancel -->
<object size="16 100%-40 30%+16 100%-12" type="button" style="StoneButton">
<translatableAttribute id="caption">Cancel</translatableAttribute>
<action on="Press">closeChat();</action>
</object>
<object name="toggleTeamChat" size="30%+22 100%-36 30%+40 100%-6" type="checkbox" style="ModernTickBox"/>
<object name="toggleTeamChatLabel" size="30%+40 100%-40 60%+16 100%-12" type="text" style="ModernLeftLabelText">
<translatableAttribute id="caption">Team Only</translatableAttribute>
</object>
<!-- Send -->
<object size="60%+16 100%-40 100%-16 100%-12" type="button" style="StoneButton">
<translatableAttribute id="caption">Send</translatableAttribute>
<action on="Press">submitChatInput();</action>

View File

@ -381,6 +381,14 @@ void DisconnectNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
SAFE_DELETE(g_Game);
}
std::string GetPlayerGUID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (!g_NetClient)
return "";
return g_NetClient->GetGUID();
}
bool KickPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const CStrW& playerName, bool ban)
{
if (!g_NetServer)
@ -1016,6 +1024,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
scriptInterface.RegisterFunction<void, std::wstring, &StartNetworkHost>("StartNetworkHost");
scriptInterface.RegisterFunction<void, std::wstring, std::string, &StartNetworkJoin>("StartNetworkJoin");
scriptInterface.RegisterFunction<void, &DisconnectNetworkGame>("DisconnectNetworkGame");
scriptInterface.RegisterFunction<std::string, &GetPlayerGUID>("GetPlayerGUID");
scriptInterface.RegisterFunction<bool, CStrW, bool, &KickPlayer>("KickPlayer");
scriptInterface.RegisterFunction<JS::Value, &PollNetworkClient>("PollNetworkClient");
scriptInterface.RegisterFunction<void, JS::HandleValue, &SetNetworkGameAttributes>("SetNetworkGameAttributes");

View File

@ -88,6 +88,12 @@ public:
*/
void SetUserName(const CStrW& username);
/**
* Returns the GUID of the local client.
* Used for distinguishing observers.
*/
CStr GetGUID() const { return m_GUID; }
/**
* Set up a connection to the remote networked server.
* @param server IP address or host name to connect to