forked from 0ad/0ad
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:
parent
365cbf5b64
commit
f8b20d181d
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -316,6 +316,8 @@ function selectViewPlayer(playerID)
|
||||
|
||||
updateTopPanel();
|
||||
|
||||
updateChatAddressees();
|
||||
|
||||
// Update GUI and clear player-dependent cache
|
||||
onSimulationUpdate();
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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");
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user