1
0
forked from mirrors/0ad

Victory and defeat reason strings.

Clarify the victory / defeat reason by using a custom string for each
defeat and victory reason.
Group chat notifications instead of posting one for each player.
This also slightly improves lobby performance upon win.

Differential Revision: https://code.wildfiregames.com/D762
Fixes #4382
Based on patch by: Angen
This was SVN commit r19955.
This commit is contained in:
elexis
2017-08-08 11:32:00 +00:00
parent eff4e12656
commit d2d43ab46f
17 changed files with 189 additions and 69 deletions
@@ -25,6 +25,7 @@
{"nick": "aBothe", "name": "Alexander Bothe"},
{"nick": "alpha123", "name": "Peter P. Cannici"},
{"nick": "andy5995", "name": "Andy Alt"},
{"nick": "Angen"},
{"nick": "ArnH", "name": "Arno Hemelhof"},
{"nick": "Aurium", "name": "Aurélio Heckert"},
{"nick": "badmadblacksad", "name": "Martin F"},
@@ -126,8 +126,7 @@ var g_FormatChatMessage = {
),
"clientlist": msg => getUsernameList(),
"message": msg => formatChatCommand(msg),
"defeat": msg => formatDefeatMessage(msg),
"won": msg => formatWinMessage(msg),
"defeat-victory": msg => formatDefeatVictoryMessage(msg.message, msg.players),
"diplomacy": msg => formatDiplomacyMessage(msg),
"tribute": msg => formatTributeMessage(msg),
"barter": msg => formatBarterMessage(msg),
@@ -315,24 +314,11 @@ var g_NotificationsTypes =
},
"defeat": function(notification, player)
{
addChatMessage({
"type": "defeat",
"guid": findGuidForPlayerID(player),
"player": player,
"resign": !!notification.resign
});
playerFinished(player, false);
sendLobbyPlayerlistUpdate();
playersFinished(notification.allies, notification.message, false);
},
"won": function(notification, player)
{
addChatMessage({
"type": "won",
"guid": findGuidForPlayerID(player),
"player": player
});
playerFinished(player, true);
sendLobbyPlayerlistUpdate();
playersFinished(notification.allies, notification.message, true);
},
"diplomacy": function(notification, player)
{
@@ -966,20 +952,20 @@ function colorizePlayernameParameters(parameters)
parameters[param] = colorizePlayernameByID(parameters[param]);
}
function formatDefeatMessage(msg)
function formatDefeatVictoryMessage(message, players)
{
return sprintf(
msg.resign ?
translate("%(player)s has resigned.") :
translate("%(player)s has been defeated."),
{ "player": colorizePlayernameByID(msg.player) }
);
}
if (!message.pluralMessage)
return sprintf(translate(message), {
"player": colorizePlayernameByID(players[0])
});
function formatWinMessage(msg)
{
return sprintf(translate("%(player)s has won."), {
"player": colorizePlayernameByID(msg.player)
let mPlayers = players.map(playerID => colorizePlayernameByID(playerID));
let lastPlayer = mPlayers.pop();
return sprintf(translatePlural(message.message, message.pluralMessage, message.pluralCount), {
// Translation: This comma is used for separating first to penultimate elements in an enumeration.
"players": mPlayers.join(translate(", ")),
"lastPlayer": lastPlayer
});
}
@@ -546,23 +546,35 @@ function controlsPlayer(playerID)
}
/**
* Called when a player has won or was defeated.
* Called when one or more players have won or were defeated.
*
* @param {array} - IDs of the players who have won or were defeated.
* @param {object} - a plural string stating the victory reason.
* @param {boolean} - whether these players have won or lost.
*/
function playerFinished(player, won)
function playersFinished(players, victoryString, won)
{
if (player == Engine.GetPlayerID())
addChatMessage({
"type": "defeat-victory",
"message": victoryString,
"players": players
});
if (players.indexOf(Engine.GetPlayerID()) != -1)
reportGame();
sendLobbyPlayerlistUpdate();
updatePlayerData();
updateChatAddressees();
if (player != g_ViewedPlayer)
if (players.indexOf(g_ViewedPlayer) == -1)
return;
// Select "observer" item on loss. On win enable observermode without changing perspective
Engine.GetGUIObjectByName("viewPlayer").selected = won ? g_ViewedPlayer + 1 : 0;
if (player != Engine.GetPlayerID() || Engine.IsAtlasRunning())
if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning())
return;
global.music.setState(
@@ -657,9 +669,7 @@ function resignGame(leaveGameAfterResign)
return;
Engine.PostNetworkCommand({
"type": "defeat-player",
"playerId": Engine.GetPlayerID(),
"resign": true
"type": "resign"
});
if (!leaveGameAfterResign)
@@ -345,7 +345,10 @@ Trigger.prototype.OnOwnershipChanged = function(data)
if (data.entity == this.playerCivicCenter[data.from])
{
this.playerCivicCenter[data.from] = undefined;
TriggerHelper.DefeatPlayer(data.from);
TriggerHelper.DefeatPlayer(
data.from,
markForTranslation("%(player)s has been defeated (lost civic center)."));
}
else if (data.entity == this.treasureFemale[data.from])
{
@@ -96,7 +96,16 @@ Trigger.prototype.BattleMessage = function()
Trigger.prototype.Victory = function(playerID)
{
TriggerHelper.SetPlayerWon(playerID);
TriggerHelper.SetPlayerWon(
playerID,
n => markForPluralTranslation(
"%(lastPlayer)s has won (treasure collected).",
"%(players)s and %(lastPlayer)s have won (treasure collected).",
n),
n => markForPluralTranslation(
"%(lastPlayer)s has been defeated (treasure collected).",
"%(players)s and %(lastPlayer)s have been defeated (treasure collected).",
n));
};
var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
@@ -145,8 +145,23 @@ Trigger.prototype.StartCaptureTheRelicCountdown = function(playerAndAllies)
"translateMessage": true
}, captureTheRelicDuration);
this.relicsVictoryTimer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_EndGameManager,
"MarkPlayerAsWon", captureTheRelicDuration, playerAndAllies[0]);
this.relicsVictoryTimer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger,
"CaptureTheRelicVictorySetWinner", captureTheRelicDuration, playerAndAllies[0]);
};
Trigger.prototype.CaptureTheRelicVictorySetWinner = function(playerID)
{
let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
cmpEndGameManager.MarkPlayerAsWon(
playerID,
n => markForPluralTranslation(
"%(lastPlayer)s has won (Capture the Relic).",
"%(players)s and %(lastPlayer)s have won (Capture the Relic).",
n),
n => markForPluralTranslation(
"%(lastPlayer)s has been defeated (Capture the Relic).",
"%(players)s and %(lastPlayer)s have been defeated (Capture the Relic).",
n));
};
{
@@ -1,6 +1,7 @@
{
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.conquestClassFilter = "ConquestCritical";
cmpTrigger.conquestDefeatReason = markForTranslation("%(player)s has been defeated (lost all workers and structures).");
let data = { "enabled": true };
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "ConquestHandlerOwnerShipChanged", data);
@@ -29,7 +29,7 @@ Trigger.prototype.ConquestHandlerOwnerShipChanged = function(msg)
{
let cmpPlayer = QueryPlayerIDInterface(msg.from);
if (cmpPlayer)
cmpPlayer.SetState("defeated");
cmpPlayer.SetState("defeated", this.conquestDefeatReason);
}
}
};
@@ -1,6 +1,7 @@
{
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.conquestClassFilter = "Structure";
cmpTrigger.conquestDefeatReason = markForTranslation("%(player)s has been defeated (lost all structures).");
let data = { "enabled": true };
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "ConquestHandlerOwnerShipChanged", data);
@@ -1,6 +1,7 @@
{
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.conquestClassFilter = "Unit";
cmpTrigger.conquestDefeatReason = markForTranslation("%(player)s has been defeated (lost all workers).");
let data = { "enabled": true };
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "ConquestHandlerOwnerShipChanged", data);
@@ -1,7 +1,9 @@
Trigger.prototype.CheckRegicideDefeat = function(data)
{
if (data.entity == this.regicideHeroes[data.from])
TriggerHelper.DefeatPlayer(data.from);
TriggerHelper.DefeatPlayer(
data.from,
markForTranslation("%(player)s has been defeated (lost hero)."));
};
Trigger.prototype.InitRegicideGame = function(msg)
@@ -116,22 +116,34 @@ TriggerHelper.GetResourceType = function(entity)
/**
* The given player will win the game.
* If it's not a last man standing game, then allies will win too.
* If it's not a last man standing game, then allies will win too and others will be defeated.
*
* @param {number} playerID - The player who will win.
* @param {function} victoryReason - Function that maps from number to plural string, for example
* n => markForPluralTranslation(
* "%(lastPlayer)s has won (game mode).",
* "%(players)s and %(lastPlayer)s have won (game mode).",
* n));
* It's a function since we don't know in advance how many players will have won.
*/
TriggerHelper.SetPlayerWon = function(playerID)
TriggerHelper.SetPlayerWon = function(playerID, victoryReason, defeatReason)
{
let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
cmpEndGameManager.MarkPlayerAsWon(playerID);
cmpEndGameManager.MarkPlayerAsWon(playerID, victoryReason, defeatReason);
};
/**
* Defeats a player
* Defeats a single player.
*
* @param {number} - ID of that player.
* @param {string} - String to be shown in chat, for example
* markForTranslation("%(player)s has been defeated (objective).")
*/
TriggerHelper.DefeatPlayer = function(playerID)
TriggerHelper.DefeatPlayer = function(playerID, defeatReason)
{
let cmpPlayer = QueryPlayerIDInterface(playerID);
if (cmpPlayer)
cmpPlayer.SetState("defeated");
cmpPlayer.SetState("defeated", defeatReason);
};
/**
@@ -56,8 +56,8 @@ Trigger.prototype.CheckWonderVictory = function(data)
"translateMessage": true,
}, wonderDuration);
timer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_EndGameManager,
"MarkPlayerAsWon", wonderDuration, data.to);
timer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger,
"WonderVictorySetWinner", wonderDuration, data.to);
this.wonderVictoryTimers[ent] = timer;
this.wonderVictoryMessages[ent] = messages;
@@ -76,6 +76,21 @@ Trigger.prototype.DeleteWonderVictoryMessages = function(data)
}
};
Trigger.prototype.WonderVictorySetWinner = function(playerID)
{
let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
cmpEndGameManager.MarkPlayerAsWon(
playerID,
n => markForPluralTranslation(
"%(lastPlayer)s has won (wonder victory).",
"%(players)s and %(lastPlayer)s have won (wonder victory).",
n),
n => markForPluralTranslation(
"%(lastPlayer)s has been defeated (wonder victory).",
"%(players)s and %(lastPlayer)s have been defeated (wonder victory).",
n));
};
{
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CheckWonderVictory", { "enabled": true });
@@ -48,13 +48,28 @@ EndGameManager.prototype.SetGameType = function(newGameType, newSettings = {})
Engine.BroadcastMessage(MT_GameTypeChanged, {});
};
EndGameManager.prototype.MarkPlayerAsWon = function(playerID)
/**
* Sets the given player (and the allies if allied victory is enabled) as a winner.
*
* @param {number} playerID - The player that should win.
* @param {function} victoryReason - Function that maps from number to plural string, for example
* n => markForPluralTranslation(
* "%(lastPlayer)s has won (game mode).",
* "%(players)s and %(lastPlayer)s have won (game mode).",
* n));
*/
EndGameManager.prototype.MarkPlayerAsWon = function(playerID, victoryString, defeatString)
{
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
let numPlayers = cmpPlayerManager.GetNumPlayers();
this.skipAlliedVictoryCheck = true;
let winningPlayers = [];
let defeatedPlayers = [];
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
// Group win/defeat messages
for (let won of [false, true])
for (let i = 1; i < numPlayers; ++i)
@@ -63,9 +78,36 @@ EndGameManager.prototype.MarkPlayerAsWon = function(playerID)
let hasWon = playerID == i || this.alliedVictory && cmpPlayer.IsMutualAlly(playerID);
if (hasWon == won)
cmpPlayer.SetState(won ? "won" : "defeated");
{
if (won)
{
cmpPlayer.SetState("won", undefined);
winningPlayers.push(i);
}
else
{
cmpPlayer.SetState("defeated", undefined);
defeatedPlayers.push(i);
}
}
}
if (winningPlayers.length)
cmpGUIInterface.PushNotification({
"type": "won",
"players": [winningPlayers[0]],
"allies" : winningPlayers,
"message": victoryString(winningPlayers.length)
});
if (defeatedPlayers.length)
cmpGUIInterface.PushNotification({
"type": "defeat",
"players": [defeatedPlayers[0]],
"allies" : defeatedPlayers,
"message": defeatString(defeatedPlayers.length)
});
this.skipAlliedVictoryCheck = false;
};
@@ -106,12 +148,24 @@ EndGameManager.prototype.AlliedVictoryCheck = function()
}
if (this.alliedVictory || allies.length == 1)
{
for (let playerID of allies)
{
let cmpPlayer = QueryPlayerIDInterface(playerID);
if (cmpPlayer)
cmpPlayer.SetState("won");
cmpPlayer.SetState("won", undefined);
}
cmpGuiInterface.PushNotification({
"type": "won",
"players": [allies[0]],
"allies" : allies,
"message": markForPluralTranslation(
"%(lastPlayer)s has won (last player alive).",
"%(players)s and %(lastPlayer)s have won (last players alive).",
allies.length)
});
}
else
this.lastManStandingMessage = cmpGuiInterface.AddTimeNotification({
"message": markForTranslation("Last remaining player wins."),
@@ -395,7 +395,13 @@ Player.prototype.GetState = function()
return this.state;
};
Player.prototype.SetState = function(newState, resign)
/**
* @param {string} newState - Either "defeated" or "won".
* @param {string|undefined} message - A string to be shown in chat, for example
* markForTranslation("%(player)s has been defeated (failed objective).").
* If it is undefined, the caller MUST send that GUI notification manually.
*/
Player.prototype.SetState = function(newState, message)
{
if (this.state != "active")
return;
@@ -438,17 +444,21 @@ Player.prototype.SetState = function(newState, resign)
Engine.BroadcastMessage(won ? MT_PlayerWon : MT_PlayerDefeated, { "playerId": this.playerID });
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
if (won)
cmpGUIInterface.PushNotification({
"type": "won",
"players": [this.playerID]
});
else
cmpGUIInterface.PushNotification({
"type": "defeat",
"players": [this.playerID],
"resign": resign
});
if (message)
if (won)
cmpGUIInterface.PushNotification({
"type": "won",
"players": [this.playerID],
"allies": [this.playerID],
"message": message
});
else
cmpGUIInterface.PushNotification({
"type": "defeat",
"players": [this.playerID],
"allies": [this.playerID],
"message": message
});
};
Player.prototype.GetTeam = function()
@@ -50,7 +50,7 @@ function Cheat(input)
case "defeatplayer":
cmpPlayer = QueryPlayerIDInterface(input.parameter);
if (cmpPlayer)
cmpPlayer.SetState("defeated");
cmpPlayer.SetState("defeated", markForTranslation("%(player)s has been defeated (cheat)."));
return;
case "createunits":
var cmpProductionQueue = input.selected.length && Engine.QueryInterface(input.selected[0], IID_ProductionQueue);
@@ -437,11 +437,11 @@ var g_Commands = {
}
},
"defeat-player": function(player, cmd, data)
"resign": function(player, cmd, data)
{
let cmpPlayer = QueryPlayerIDInterface(player);
if (cmpPlayer)
cmpPlayer.SetState("defeated", !!cmd.resign);
cmpPlayer.SetState("defeated", markForTranslation("%(player)s has resigned."));
},
"garrison": function(player, cmd, data)