1
0
forked from mirrors/0ad

Savegame / summary screen / replay menu cleanup, refs #1090.

Savegame / replay menu selected game information:
  Show players who won the game (not only defeat).
  Show translated civs instead of the civ code.
  Use a translation for "%(team)s:\n%(playerDescriptions)s".
  Merge nearly duplicate objects passed to formatPlayerInfo.
  Merge deleteGame and deleteGameWithoutConfirmation.

Summary screen:
  Construct the summary screen title in place, not in the session, nor
replay menu.
  Remove g_Players from the replay metadata since all relevant
information is contained in the sim data.
  Split g_GameData into gui and sim.
  Access g_GameData instead of data for consistency.
  Move code from XML to JS.
  Don't use victory music when observers enter the summary screen (just
keep the current track like when exiting the game as a player).
  Rename updateObjectPlayerPosition to initPlayerBoxPositions.

This was SVN commit r18440.
This commit is contained in:
elexis
2016-06-26 03:36:55 +00:00
parent 898ab5229b
commit 8da8d28102
11 changed files with 178 additions and 203 deletions
@@ -213,38 +213,70 @@ function clearChatMessages()
/**
* Returns a formatted string describing the player assignments.
* Including civs, teams, AI settings and player colors
* which are given in data (array of objects per player).
* Needs g_CivData to translate!
*
* @param {object} playerDataArray - As known from gamesetup and simstate.
* @param {(string[]|false)} playerStates - One of "won", "defeated", "active" for each player.
* @returns {string}
*/
function formatPlayerInfo(data)
function formatPlayerInfo(playerDataArray, playerStates)
{
let playerDescriptions = {};
let playerIdx = 0;
for (let playerData of data)
for (let playerData of playerDataArray)
{
if (playerData == null || playerData.Civ == "gaia")
continue;
++playerIdx;
let teamIdx = playerData.Team;
let showDefeated = playerData.state && playerData.state == "defeated";
let isAI = playerData.AI && playerData.AI != "";
let playerState = playerStates && playerStates[playerIdx];
let isActive = !playerState || playerState == "active";
let translated;
if (!isAI && !showDefeated)
translated = translateWithContext("replay", "%(playerName)s (%(civ)s)");
else if (!isAI && showDefeated)
translated = translateWithContext("replay", "%(playerName)s (%(civ)s, defeated)");
else if (isAI && !showDefeated)
translated = translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)");
let playerDescription;
if (isAI)
{
if (isActive)
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s)");
else
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, %(state)s)");
}
else
translated = translateWithContext("replay", "%(playerName)s (%(civ)s, %(AIdifficulty)s %(AIname)s, defeated)");
{
if (isActive)
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(civ)s)");
else
// Translation: Describe a player in a selected game, f.e. in the replay- or savegame menu
playerDescription = translate("%(playerName)s (%(civ)s, %(state)s)");
}
// Sort player descriptions by team
if (!playerDescriptions[teamIdx])
playerDescriptions[teamIdx] = [];
playerDescriptions[teamIdx].push(sprintf(translated, {
"playerName": '[color="' + rgbToGuiColor(playerData.Color) + '"]' + escapeText(playerData.Name) + "[/color]",
"civ": playerData.Civ,
playerDescriptions[teamIdx].push(sprintf(playerDescription, {
"playerName":
'[color="' +
rgbToGuiColor(playerData.Color || g_Settings.PlayerDefaults[playerIdx].Color) +
'"]' + escapeText(playerData.Name) + "[/color]",
"civ":
!playerData.Civ ?
translate("Unknown Civilization") :
g_CivData && g_CivData[playerData.Civ] && g_CivData[playerData.Civ].Name ?
translate(g_CivData[playerData.Civ].Name) :
playerData.Civ,
"state":
playerState == "defeated" ?
translateWithContext("playerstate", "defeated") :
translateWithContext("playerstate", "won"),
"AIname": isAI ? translateAIName(playerData.AI) : "",
"AIdifficulty": isAI ? translateAIDifficulty(playerData.AIDiff) : ""
}));
@@ -258,7 +290,15 @@ function formatPlayerInfo(data)
// If there are teams, merge "Team N:" + playerDescriptions
return teams.map(team => {
let teamCaption = (team == -1) ? translate("No Team") : sprintf(translate("Team %(team)s"), { "team": +team + 1 });
return '[font="sans-bold-14"]' + teamCaption + "[/font]:\n" + playerDescriptions[team].join("\n");
let teamCaption = team == -1 ?
translate("No Team") :
sprintf(translate("Team %(team)s"), { "team": +team + 1 });
// Translation: Describe players of one team in a selected game, f.e. in the replay- or savegame menu or lobby
return sprintf(translate("%(team)s:\n%(playerDescriptions)s"), {
"team": '[font="sans-bold-14"]' + teamCaption + "[/font]",
"playerDescriptions": playerDescriptions[team].join("\n")
});
}).join("\n\n");
}
@@ -109,20 +109,22 @@ function showReplaySummary()
return;
// Load summary screen data from the selected replay directory
var summary = Engine.GetReplayMetadata(g_ReplaysFiltered[selected].directory);
let simData = Engine.GetReplayMetadata(g_ReplaysFiltered[selected].directory);
if (!summary)
if (!simData)
{
messageBox(500, 200, translate("No summary data available."), translate("Error"));
return;
}
// Open summary screen
summary.isReplay = true;
summary.gameResult = translate("Scores at the end of the game.");
summary.replayDirectory = g_ReplaysFiltered[selected].directory;
summary.replaySelectionData = createReplaySelectionData(g_ReplaysFiltered[selected].directory);
Engine.SwitchGuiPage("page_summary.xml", summary);
Engine.SwitchGuiPage("page_summary.xml", {
"sim": simData,
"gui": {
"isReplay": true,
"replayDirectory": g_ReplaysFiltered[selected].directory,
"replaySelectionData": createReplaySelectionData(g_ReplaysFiltered[selected].directory)
}
});
}
/**
@@ -4,7 +4,7 @@
const g_EngineInfo = Engine.GetEngineInfo();
/**
* To show the titles of the selected civs in the replay details.
* Needed for formatPlayerInfo to show the player civs in the details.
*/
const g_CivData = loadCivData();
@@ -190,7 +190,6 @@ function displayReplayList()
filterReplays();
// Create GUI list data
var list = g_ReplaysFiltered.map(replay => {
let works = replay.isCompatible;
return {
@@ -204,7 +203,6 @@ function displayReplayList()
};
});
// Extract arrays
if (list.length)
list = prepareForDropdown(list);
@@ -221,7 +219,6 @@ function displayReplayList()
replaySelection.list = list.directories || [];
replaySelection.list_data = list.directories || [];
// Restore selection
replaySelection.selected = replaySelection.list.findIndex(directory => directory == g_SelectedReplayDirectory);
displayReplayDetails();
@@ -232,8 +229,8 @@ function displayReplayList()
*/
function displayReplayDetails()
{
var selected = Engine.GetGUIObjectByName("replaySelection").selected;
var replaySelected = selected > -1;
let selected = Engine.GetGUIObjectByName("replaySelection").selected;
let replaySelected = selected > -1;
Engine.GetGUIObjectByName("replayInfo").hidden = !replaySelected;
Engine.GetGUIObjectByName("replayInfoEmpty").hidden = replaySelected;
@@ -244,17 +241,27 @@ function displayReplayDetails()
if (!replaySelected)
return;
var replay = g_ReplaysFiltered[selected];
var mapData = getMapDescriptionAndPreview(replay.attribs.settings.mapType, replay.attribs.map);
let replay = g_ReplaysFiltered[selected];
// Update GUI
Engine.GetGUIObjectByName("sgMapName").caption = translate(replay.attribs.settings.Name);
Engine.GetGUIObjectByName("sgMapSize").caption = translateMapSize(replay.attribs.settings.Size);
Engine.GetGUIObjectByName("sgMapType").caption = translateMapType(replay.attribs.settings.mapType);
Engine.GetGUIObjectByName("sgVictory").caption = translateVictoryCondition(replay.attribs.settings.GameType);
Engine.GetGUIObjectByName("sgNbPlayers").caption = replay.attribs.settings.PlayerData.length;
Engine.GetGUIObjectByName("sgPlayersNames").caption = getReplayTeamText(replay);
let metadata = Engine.GetReplayMetadata(replay.directory);
Engine.GetGUIObjectByName("sgPlayersNames").caption =
formatPlayerInfo(
replay.attribs.settings.PlayerData,
Engine.GetGUIObjectByName("showSpoiler").checked &&
metadata &&
metadata.playerStates &&
metadata.playerStates.map(pState => pState.state)
);
let mapData = getMapDescriptionAndPreview(replay.attribs.settings.mapType, replay.attribs.map);
Engine.GetGUIObjectByName("sgMapDescription").caption = mapData.description;
Engine.GetGUIObjectByName("summaryButton").hidden = !Engine.HasReplayMetadata(replay.directory);
setMapPreviewImage("sgMapPreview", mapData.preview);
@@ -331,37 +338,3 @@ function replayHasSameEngineVersion(replay)
{
return replay.attribs.engine_version && replay.attribs.engine_version == g_EngineInfo.engine_version;
}
/**
* Returns a description of the player assignments.
* Including civs, teams, AI settings and player colors.
*
* If the spoiler-checkbox is checked, it also shows defeated players.
*
* @returns {string}
*/
function getReplayTeamText(replay)
{
// Load replay metadata
const metadata = Engine.GetReplayMetadata(replay.directory);
const spoiler = Engine.GetGUIObjectByName("showSpoiler").checked;
let data = [];
let playerIdx = 0;
for (let playerData of replay.attribs.settings.PlayerData)
{
++playerIdx;
data.push({
"Team": playerData.Team,
"Name": playerData.Name,
"Civ": !playerData.Civ ? translate("Unknown Civilization") :
(g_CivData[playerData.Civ] && g_CivData[playerData.Civ].Name ? translate(g_CivData[playerData.Civ].Name) : playerData.Civ),
"Color": playerData.Color ? playerData.Color : g_Settings.PlayerDefaults[playerIdx].Color,
"AI": playerData.AI,
"AIDiff": playerData.AIDiff,
"Defeated": spoiler && metadata && metadata.playerStates && metadata.playerStates[playerIdx].state == "defeated"
});
}
return formatPlayerInfo(data);
}
@@ -1,5 +1,10 @@
var g_SavedGamesMetadata = [];
/**
* Needed for formatPlayerInfo to show the player civs in the details.
*/
const g_CivData = loadCivData();
function init()
{
let gameSelection = Engine.GetGUIObjectByName("gameSelection");
@@ -58,25 +63,10 @@ function selectionChanged()
caption = "[color=\"orange\"]" + caption + "[/color]";
Engine.GetGUIObjectByName("savedMods").caption = caption;
let data = [];
let playerIdx = 0;
for (let playerData of metadata.initAttributes.settings.PlayerData)
{
if (playerData == null || playerData.Civ == "gaia")
continue;
++playerIdx;
data.push({
"Team": playerData.Team,
"Name": playerData.Name,
"Civ": playerData.Civ,
"Color": playerData.Color,
"AI": playerData.AI,
"AIDiff": playerData.AIDiff,
"Defeated": metadata.gui.states && metadata.gui.states[playerIdx] == "defeated"
});
}
Engine.GetGUIObjectByName("savedPlayersNames").caption = formatPlayerInfo(data);
Engine.GetGUIObjectByName("savedPlayersNames").caption = formatPlayerInfo(
metadata.initAttributes.settings.PlayerData,
metadata.gui.states
);
}
function loadGame()
@@ -3,6 +3,7 @@
<objects>
<script file="gui/common/color.js" />
<script file="gui/common/functions_civinfo.js"/>
<script file="gui/common/functions_global_object.js" />
<script file="gui/common/functions_utility.js" />
<script file="gui/common/functions_utility_loadsave.js" />
@@ -30,14 +31,7 @@
<object name="deleteGameButton" type="button" size="33%+20 100%-60 66%-15 100%-32" style="StoneButton" hotkey="session.savedgames.delete">
<translatableAttribute id="caption">Delete</translatableAttribute>
<action on="Press">
if (!this.enabled)
return;
if (Engine.HotkeyIsPressed("session.savedgames.noconfirmation"))
deleteGameWithoutConfirmation();
else
deleteGame();
</action>
<action on="Press">deleteGame();</action>
</object>
<object name="loadGameButton" type="button" style="StoneButton" size="66%-5 100%-60 100%-25 100%-32">
@@ -17,9 +17,7 @@ function init(data)
g_SavedGameData = data && data.savedGameData || {};
let simulationState = Engine.GuiInterfaceCall("GetSimulationState");
g_SavedGameData.timeElapsed = simulationState.timeElapsed;
g_SavedGameData.states = [];
for (let player of simulationState.players)
g_SavedGameData.states.push(player.state);
g_SavedGameData.states = simulationState.players.map(pState => pState.state);
let gameSelection = Engine.GetGUIObjectByName("gameSelection");
Engine.GetGUIObjectByName("deleteGameButton").enabled = false;
@@ -507,58 +507,35 @@ function resignGame(leaveGameAfterResign)
function leaveGame(willRejoin)
{
let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
let mapSettings = Engine.GetMapSettings();
let gameResult;
if (Engine.GetPlayerID() == -1)
{
gameResult = translate("You have left the game.");
global.music.setState(global.music.states.VICTORY);
}
else
{
let playerState = extendedSimState.players[Engine.GetPlayerID()];
if (g_Disconnected)
gameResult = translate("You have been disconnected.");
else if (playerState.state == "won")
gameResult = translate("You have won the battle!");
else if (playerState.state == "defeated")
gameResult = translate("You have been defeated...");
else // "active"
{
global.music.setState(global.music.states.DEFEAT);
if (willRejoin)
gameResult = translate("You have left the game.");
else
{
gameResult = translate("You have abandoned the game.");
resignGame(true);
}
}
}
let summary = {
let simData = {
"timeElapsed" : extendedSimState.timeElapsed,
"playerStates": extendedSimState.players,
"players": g_Players,
"mapSettings": Engine.GetMapSettings(),
"mapSettings": Engine.GetMapSettings()
};
if (!g_IsReplay)
Engine.SaveReplayMetadata(JSON.stringify(summary));
Engine.SaveReplayMetadata(JSON.stringify(simData));
if (!g_HasRejoined)
summary.replayDirectory = Engine.GetCurrentReplayDirectory();
summary.replaySelectionData = g_ReplaySelectionData;
if (!willRejoin &&
simData.playerStates[Engine.GetPlayerID()] &&
simData.playerStates[Engine.GetPlayerID()].state == "active")
resignGame(true);
Engine.EndGame();
if (g_IsController && Engine.HasXmppClient())
Engine.SendUnregisterGame();
summary.gameResult = gameResult;
summary.isReplay = g_IsReplay;
Engine.SwitchGuiPage("page_summary.xml", summary);
Engine.SwitchGuiPage("page_summary.xml", {
"sim": simData,
"gui": {
"assignedPlayer": Engine.GetPlayerID(),
"disconnected": g_Disconnected,
"isReplay": g_IsReplay,
"replayDirectory": !g_HasRejoined && Engine.GetCurrentReplayDirectory(),
"replaySelectionData": g_ReplaySelectionData
}
});
}
// Return some data that we'll use when hotloading this file after changes
@@ -78,7 +78,6 @@ function getTradingTooltip(gain)
if (!gain)
return "";
var playerID = g_ViewedPlayer;
var simState = GetSimState();
var gainString = gain.traderGain;
@@ -317,7 +317,7 @@ function updateGeneralPanelTeams()
Engine.GetGUIObjectByName("playerNameHeading").caption = "";
}
function updateObjectPlayerPosition()
function initPlayerBoxPositions()
{
for (let h = 0; h < g_MaxPlayers; ++h)
{
@@ -82,7 +82,7 @@ function updatePanelData(panelInfo)
let playerBoxesCounts = [ ];
for (let i = 0; i < g_PlayerCount; ++i)
{
let playerState = g_GameData.playerStates[i+1];
let playerState = g_GameData.sim.playerStates[i+1];
if (!playerBoxesCounts[playerState.team+1])
playerBoxesCounts[playerState.team+1] = 1;
@@ -106,9 +106,9 @@ function updatePanelData(panelInfo)
}
let colorString = "color: " +
Math.floor(playerState.color.r * 255) + " " +
Math.floor(playerState.color.g * 255) + " " +
Math.floor(playerState.color.b * 255);
Math.floor(playerState.color.r * 255) + " " +
Math.floor(playerState.color.g * 255) + " " +
Math.floor(playerState.color.b * 255);
let rowPlayerObject = Engine.GetGUIObjectByName(rowPlayer);
rowPlayerObject.hidden = false;
@@ -121,7 +121,7 @@ function updatePanelData(panelInfo)
let playerColorBox = Engine.GetGUIObjectByName(playerColorBoxColumn);
playerColorBox.sprite = colorString + g_PlayerColorBoxAlpha;
Engine.GetGUIObjectByName(playerNameColumn).caption = g_GameData.players[i+1].name;
Engine.GetGUIObjectByName(playerNameColumn).caption = g_GameData.sim.playerStates[i+1].name;
let civIcon = Engine.GetGUIObjectByName(playerCivicBoxColumn);
civIcon.sprite = "stretched:" + g_CivData[playerState.civ].Emblem;
@@ -137,14 +137,42 @@ function updatePanelData(panelInfo)
teamCounterFn(panelInfo.counters);
}
function confirmStartReplay()
{
if (Engine.HasXmppClient())
messageBox(
400, 200,
translate("Are you sure you want to quit the lobby?"),
translate("Confirmation"),
[translate("No"), translate("Yes")],
[null, startReplay]
);
else
startReplay();
}
function continueButton()
{
if (g_GameData.gui.isInGame)
Engine.PopGuiPageCB(0);
else if (g_GameData.gui.isReplay)
Engine.SwitchGuiPage("page_replaymenu.xml", {
"replaySelectionData": g_GameData.gui.replaySelectionData
});
else if (Engine.HasXmppClient())
Engine.SwitchGuiPage("page_lobby.xml");
else
Engine.SwitchGuiPage("page_pregame.xml");
}
function startReplay()
{
if (Engine.HasXmppClient())
Engine.StopXmppClient();
Engine.StartVisualReplay(g_GameData.replayDirectory);
Engine.StartVisualReplay(g_GameData.gui.replayDirectory);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": Engine.GetReplayAttributes(g_GameData.replayDirectory),
"attribs": Engine.GetReplayAttributes(g_GameData.gui.replayDirectory),
"isNetworked": false,
"playerAssignments": {
"local": {
@@ -154,42 +182,56 @@ function startReplay()
},
"savedGUIData": "",
"isReplay": true,
"replaySelectionData": g_GameData.replaySelectionData
"replaySelectionData": g_GameData.gui.replaySelectionData
});
}
function init(data)
{
updateObjectPlayerPosition();
g_GameData = data;
let mapSize = data.mapSettings.Size && g_Settings.MapSizes.find(size => size.Tiles == data.mapSettings.Size);
let mapType = g_Settings.MapTypes.find(mapType => mapType.Name == data.mapSettings.mapType);
let assignedState = g_GameData.sim.playerStates[g_GameData.gui.assignedPlayer || -1];
Engine.GetGUIObjectByName("summaryText").caption =
g_GameData.gui.isReplay ?
translate("Scores at the end of the game.") :
g_GameData.gui.disconnected ?
translate("You have been disconnected.") :
!assignedState ?
translate("You have left the game.") :
assignedState.state == "won" ?
translate("You have won the battle!") :
assignedState.state == "defeated" ?
translate("You have been defeated...") :
translate("You have abandoned the game.");
initPlayerBoxPositions();
Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(
translate("Game time elapsed: %(time)s"), {
"time": timeToString(data.timeElapsed)
"time": timeToString(g_GameData.sim.timeElapsed)
});
Engine.GetGUIObjectByName("summaryText").caption = data.gameResult;
let mapType = g_Settings.MapTypes.find(mapType => mapType.Name == g_GameData.sim.mapSettings.mapType);
let mapSize = g_Settings.MapSizes.find(size => size.Tiles == g_GameData.sim.mapSettings.Size || 0);
Engine.GetGUIObjectByName("mapName").caption = sprintf(
translate("%(mapName)s - %(mapType)s"), {
"mapName": translate(data.mapSettings.Name),
"mapName": translate(g_GameData.sim.mapSettings.Name),
"mapType": mapSize ? mapSize.LongName : (mapType ? mapType.Title : "")
});
Engine.GetGUIObjectByName("replayButton").hidden = g_GameData.isInGame || !g_GameData.replayDirectory;
Engine.GetGUIObjectByName("replayButton").hidden = g_GameData.gui.isInGame || !g_GameData.gui.replayDirectory;
// Panels
g_PlayerCount = data.playerStates.length - 1;
g_PlayerCount = g_GameData.sim.playerStates.length - 1;
if (data.mapSettings.LockTeams)
if (g_GameData.sim.mapSettings.LockTeams)
{
// Count teams
for (let t = 0; t < g_PlayerCount; ++t)
{
let playerTeam = data.playerStates[t+1].team;
let playerTeam = g_GameData.sim.playerStates[t+1].team;
g_Teams[playerTeam] = (g_Teams[playerTeam] || 0) + 1;
}
@@ -203,7 +245,7 @@ function init(data)
if (!g_Teams)
{
for (let p = 0; p < g_PlayerCount; ++p)
data.playerStates[p+1].team = -1;
g_GameData.sim.playerStates[p+1].team = -1;
}
g_WithoutTeam = g_PlayerCount;
@@ -1,11 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
==========================================
- POST-GAME SUMMARY SCREEN -
==========================================
-->
<objects>
<script file="gui/common/functions_global_object.js"/>
<script file="gui/common/functions_civinfo.js"/>
@@ -164,46 +158,12 @@
<object type="button" name="replayButton" style="ModernButtonRed" size="100%-310 100%-48 100%-170 100%-20">
<translatableAttribute id="caption">Replay</translatableAttribute>
<action on="Press"><![CDATA[
if (g_GameData.isInGame)
return;
if (Engine.HasXmppClient())
messageBox(
400, 200,
translate("Are you sure you want to quit the lobby?"),
translate("Confirmation"),
[translate("No"), translate("Yes")],
[null, startReplay]
);
else
startReplay();
]]>
</action>
<action on="Press">confirmStartReplay();</action>
</object>
<object type="button" style="ModernButtonRed" size="100%-160 100%-48 100%-20 100%-20">
<translatableAttribute id="caption">Continue</translatableAttribute>
<action on="Press"><![CDATA[
if (g_GameData.isInGame)
{
Engine.PopGuiPageCB(0);
}
else if (g_GameData.isReplay)
{
Engine.SwitchGuiPage("page_replaymenu.xml", { "replaySelectionData": g_GameData.replaySelectionData });
}
else if (!Engine.HasXmppClient())
{
Engine.SwitchGuiPage("page_pregame.xml");
}
else
{
Engine.LobbySetPlayerPresence("available");
Engine.SwitchGuiPage("page_lobby.xml");
}
]]>
</action>
<action on="Press">continueButton();</action>
</object>
</object>
</objects>