Index: ps/trunk/binaries/data/mods/public/gui/common/functions_global_object.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/functions_global_object.js (revision 14771) +++ ps/trunk/binaries/data/mods/public/gui/common/functions_global_object.js (revision 14772) @@ -1,67 +1,74 @@ /* DESCRIPTION : Contains global GUI functions, which will later be accessible from every GUI script/file. NOTES : So far, only the message box-related functions are implemented. */ // ******************************************* // messageBox // ******************************************* // @params: int mbWidth, int mbHeight, string mbMessage, string mbTitle, int mbMode, arr mbButtonCaptions, function mbBtnCode, var mbCallbackArgs // @return: void // @desc: Displays a new modal message box. // ******************************************* // We want to pass callback functions for the different buttons in a convenient way. // Because passing functions accross compartment boundaries is a pain, we just store them here together with some optional arguments. // The messageBox page will return the code of the pressed button and the according function will be called. var g_messageBoxBtnFunctions = []; var g_messageBoxCallbackArgs = []; var g_messageBoxCallbackFunction = function(btnCode) { if (btnCode !== undefined && g_messageBoxBtnFunctions[btnCode]) { - if (g_messageBoxCallbackArgs[btnCode]) - g_messageBoxBtnFunctions[btnCode](g_messageBoxCallbackArgs[btnCode]); + // Cache the variables to make it possible to call a messageBox from a callback function. + var callbackFunction = g_messageBoxBtnFunctions[btnCode]; + var callbackArgs = g_messageBoxCallbackArgs[btnCode] + g_messageBoxBtnFunctions = []; + g_messageBoxCallbackArgs = []; + + if (callbackArgs !== undefined) + callbackFunction(callbackArgs); else - g_messageBoxBtnFunctions[btnCode](); + callbackFunction(); + return; } g_messageBoxBtnFunctions = []; g_messageBoxCallbackArgs = []; } function messageBox (mbWidth, mbHeight, mbMessage, mbTitle, mbMode, mbButtonCaptions, mbBtnCode, mbCallbackArgs) { if (g_messageBoxBtnFunctions && g_messageBoxBtnFunctions.length != 0) { warn("A messagebox was called when a previous callback function is still set, aborting!"); return; } g_messageBoxBtnFunctions = mbBtnCode; if (mbCallbackArgs) g_messageBoxCallbackArgs = mbCallbackArgs; var initData = { width: mbWidth, height: mbHeight, message: mbMessage, title: mbTitle, mode: mbMode, buttonCaptions: mbButtonCaptions, } if (mbBtnCode) initData.callback = "g_messageBoxCallbackFunction"; Engine.PushGuiPage("page_msgbox.xml", initData); } // ==================================================================== function updateFPS() { Engine.GetGUIObjectByName("fpsCounter").caption = "FPS: " + Engine.GetFPS(); } Index: ps/trunk/binaries/data/mods/public/gui/session/menu.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 14771) +++ ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 14772) @@ -1,598 +1,618 @@ const PAUSE = "Pause"; const RESUME = "Resume"; /* * MENU POSITION CONSTANTS */ // Menu / panel border size const MARGIN = 4; // Includes the main menu button const NUM_BUTTONS = 8; // Regular menu buttons const BUTTON_HEIGHT = 32; // The position where the bottom of the menu will end up (currently 228) const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN; // Menu starting position: bottom const MENU_BOTTOM = 0; // Menu starting position: top const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION; // Menu starting position: overall const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM; // Number of pixels per millisecond to move const MENU_SPEED = 1.2; // Trade menu: available resources and step for probability changes const RESOURCES = ["food", "wood", "stone", "metal"]; const STEP = 5; var isMenuOpen = false; var menu; var isDiplomacyOpen = false; var isTradeOpen = false; // Redefined every time someone makes a tribute (so we can save some data in a closure). Called in input.js handleInputBeforeGui. var flushTributing = function() {}; // Ignore size defined in XML and set the actual menu size here function initMenuPosition() { menu = Engine.GetGUIObjectByName("menu"); menu.size = INITIAL_MENU_POSITION; } // ============================================================================= // Overall Menu // ============================================================================= // // Slide menu function updateMenuPosition(dt) { if (isMenuOpen) { var maxOffset = END_MENU_POSITION - menu.size.bottom; if (maxOffset > 0) { var offset = Math.min(MENU_SPEED * dt, maxOffset); var size = menu.size; size.top += offset; size.bottom += offset; menu.size = size; } } else { var maxOffset = menu.size.top - MENU_TOP; if (maxOffset > 0) { var offset = Math.min(MENU_SPEED * dt, maxOffset); var size = menu.size; size.top -= offset; size.bottom -= offset; menu.size = size; } } } // Opens the menu by revealing the screen which contains the menu function openMenu() { isMenuOpen = true; } // Closes the menu and resets position function closeMenu() { isMenuOpen = false; } function toggleMenu() { if (isMenuOpen == true) closeMenu(); else openMenu(); } // Menu buttons // ============================================================================= function settingsMenuButton() { closeMenu(); closeOpenDialogs(); openSettings(); } function chatMenuButton() { closeMenu(); closeOpenDialogs(); openChat(); } function diplomacyMenuButton() { closeMenu(); closeOpenDialogs(); openDiplomacy(); } function pauseMenuButton() { togglePause(); } function resignMenuButton() { closeMenu(); closeOpenDialogs(); pauseGame(); var btCaptions = ["Yes", "No"]; var btCode = [resignGame, resumeGame]; messageBox(400, 200, "Are you sure you want to resign?", "Confirmation", 0, btCaptions, btCode); } function exitMenuButton() { closeMenu(); closeOpenDialogs(); pauseGame(); - var btCaptions = ["Yes", "No"]; - var btCode = [leaveGame, resumeGame]; - messageBox(400, 200, "Are you sure you want to quit?", "Confirmation", 0, btCaptions, btCode); + if (g_IsNetworked && g_IsController) + { + var btCode = [leaveGame, resumeGame]; + var message = "Are you sure you want to quit? Leaving will disconnect all other players."; + } + else if (g_IsNetworked && !g_GameEnded) + { + var btCode = [networkReturnQuestion, resumeGame]; + var message = "Are you sure you want to quit?"; + } + else + { + var btCode = [leaveGame, resumeGame]; + var message = "Are you sure you want to quit?"; + } + messageBox(400, 200, message, "Confirmation", 0, ["Yes", "No"], btCode); } +function networkReturnQuestion() +{ + var btCaptions = ["I resign", "I will return"]; + var btCode = [leaveGame, leaveGame]; + var btArgs = [false, true]; + messageBox(400, 200, "Do you want to resign or will you return soon?", "Confirmation", 0, btCaptions, btCode, btArgs); +} function openDeleteDialog(selection) { closeMenu(); closeOpenDialogs(); var deleteSelectedEntities = function (selectionArg) { Engine.PostNetworkCommand({"type": "delete-entities", "entities": selectionArg}); }; var btCaptions = ["Yes", "No"]; var btCode = [deleteSelectedEntities, resumeGame]; messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode, [selection, null]); } // Menu functions // ============================================================================= function openSave() { closeMenu(); closeOpenDialogs(); pauseGame(); var savedGameData = getSavedGameData(); Engine.PushGuiPage("page_savegame.xml", {"savedGameData": savedGameData, "callback": "resumeGame"}); } function openSettings() { Engine.GetGUIObjectByName("settingsDialogPanel").hidden = false; pauseGame(); } function closeSettings(resume) { Engine.GetGUIObjectByName("settingsDialogPanel").hidden = true; if (resume) resumeGame(); } function openChat() { Engine.GetGUIObjectByName("chatInput").focus(); // Grant focus to the input area Engine.GetGUIObjectByName("chatDialogPanel").hidden = false; } function closeChat() { Engine.GetGUIObjectByName("chatInput").caption = ""; // Clear chat input Engine.GetGUIObjectByName("chatInput").blur(); // Remove focus Engine.GetGUIObjectByName("chatDialogPanel").hidden = true; } function toggleChatWindow(teamChat) { closeSettings(); var chatWindow = Engine.GetGUIObjectByName("chatDialogPanel"); var chatInput = Engine.GetGUIObjectByName("chatInput"); if (chatWindow.hidden) chatInput.focus(); // Grant focus to the input area else { if (chatInput.caption.length) { submitChatInput(); return; } chatInput.caption = ""; // Clear chat input } Engine.GetGUIObjectByName("toggleTeamChat").checked = teamChat; chatWindow.hidden = !chatWindow.hidden; } function setDiplomacy(data) { Engine.PostNetworkCommand({"type": "diplomacy", "to": data.to, "player": data.player}); } function tributeResource(data) { Engine.PostNetworkCommand({"type": "tribute", "player": data.player, "amounts": data.amounts}); } function openDiplomacy() { if (isTradeOpen) closeTrade(); isDiplomacyOpen = true; var we = Engine.GetPlayerID(); var players = getPlayerData(g_PlayerAssignments); // Get offset for one line var onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size; var rowsize = onesize.bottom - onesize.top; // We don't include gaia for (var i = 1; i < players.length; i++) { // Apply offset var row = Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]"); var size = row.size; size.top = rowsize*(i-1); size.bottom = rowsize*i; row.size = size; // Set background colour var playerColor = players[i].color.r+" "+players[i].color.g+" "+players[i].color.b; row.sprite = "colour: "+playerColor + " 32"; Engine.GetGUIObjectByName("diplomacyPlayerName["+(i-1)+"]").caption = "[color=\"" + playerColor + "\"]" + players[i].name + "[/color]"; Engine.GetGUIObjectByName("diplomacyPlayerCiv["+(i-1)+"]").caption = g_CivData[players[i].civ].Name; Engine.GetGUIObjectByName("diplomacyPlayerTeam["+(i-1)+"]").caption = (players[i].team < 0) ? "None" : players[i].team+1; if (i != we) Engine.GetGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = (players[i].isAlly[we] ? "Ally" : (players[i].isNeutral[we] ? "Neutral" : "Enemy")); // Don't display the options for ourself, or if we or the other player aren't active anymore if (i == we || players[we].state != "active" || players[i].state != "active") { // Hide the unused/unselectable options for each (var a in ["TributeFood", "TributeWood", "TributeStone", "TributeMetal", "Ally", "Neutral", "Enemy"]) Engine.GetGUIObjectByName("diplomacyPlayer"+a+"["+(i-1)+"]").hidden = true; continue; } // Tribute for each (var resource in ["food", "wood", "stone", "metal"]) { var button = Engine.GetGUIObjectByName("diplomacyPlayerTribute"+toTitleCase(resource)+"["+(i-1)+"]"); button.onpress = (function(player, resource, button){ // Implement something like how unit batch training works. Shift+click to send 500, shift+click+click to send 1000, etc. // Also see input.js (searching for "INPUT_MASSTRIBUTING" should get all the relevant parts). var multiplier = 1; return function() { var isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute"); if (isBatchTrainPressed) { inputState = INPUT_MASSTRIBUTING; multiplier += multiplier == 1 ? 4 : 5; } var amounts = { "food": (resource == "food" ? 100 : 0) * multiplier, "wood": (resource == "wood" ? 100 : 0) * multiplier, "stone": (resource == "stone" ? 100 : 0) * multiplier, "metal": (resource == "metal" ? 100 : 0) * multiplier, }; button.tooltip = formatTributeTooltip(players[player], resource, amounts[resource]); // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some // evil global variable hackery. flushTributing = function() { tributeResource({"player": player, "amounts": amounts}); multiplier = 1; button.tooltip = formatTributeTooltip(players[player], resource, 100); }; if (!isBatchTrainPressed) flushTributing(); }; })(i, resource, button); button.hidden = false; button.tooltip = formatTributeTooltip(players[i], resource, 100); } // Skip our own teams on teams locked if (players[we].teamsLocked && players[we].team != -1 && players[we].team == players[i].team) continue; // Diplomacy settings // Set up the buttons for each (var setting in ["ally", "neutral", "enemy"]) { var button = Engine.GetGUIObjectByName("diplomacyPlayer"+toTitleCase(setting)+"["+(i-1)+"]"); if (setting == "ally") { if (players[we].isAlly[i]) button.caption = "x"; else button.caption = ""; } else if (setting == "neutral") { if (players[we].isNeutral[i]) button.caption = "x"; else button.caption = ""; } else // "enemy" { if (players[we].isEnemy[i]) button.caption = "x"; else button.caption = ""; } button.onpress = (function(e){ return function() { setDiplomacy(e) } })({"player": i, "to": setting}); button.hidden = false; } } Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false; } function closeDiplomacy() { isDiplomacyOpen = false; Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = true; } function toggleDiplomacy() { if (isDiplomacyOpen) closeDiplomacy(); else openDiplomacy(); }; function openTrade() { if (isDiplomacyOpen) closeDiplomacy(); isTradeOpen = true; var updateButtons = function() { for (var res in button) { button[res].label.caption = proba[res] + "%"; if (res == selec) { button[res].res.enabled = false; button[res].sel.hidden = false; button[res].up.hidden = true; button[res].dn.hidden = true; } else { button[res].res.enabled = true; button[res].sel.hidden = true; button[res].up.hidden = (proba[res] == 100 || proba[selec] == 0); button[res].dn.hidden = (proba[res] == 0 || proba[selec] == 100); } } } var proba = Engine.GuiInterfaceCall("GetTradingGoods"); var button = {}; var selec = RESOURCES[0]; for (var i = 0; i < RESOURCES.length; ++i) { var buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]"); if (i > 0) { var size = Engine.GetGUIObjectByName("tradeResource["+(i-1)+"]").size; var width = size.right - size.left; size.left += width; size.right += width; Engine.GetGUIObjectByName("tradeResource["+i+"]").size = size; } var resource = RESOURCES[i]; proba[resource] = (proba[resource] ? proba[resource] : 0); var buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]"); var icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]"); icon.sprite = "stretched:session/icons/resources/" + resource + ".png"; var label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]"); var buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]"); var buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]"); var iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]"); button[resource] = { "res": buttonResource, "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel }; buttonResource.onpress = (function(resource){ return function() { if (selec == resource) return; selec = resource; updateButtons(); } })(resource); buttonUp.onpress = (function(resource){ return function() { proba[resource] += Math.min(STEP, proba[selec]); proba[selec] -= Math.min(STEP, proba[selec]); Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba}); updateButtons(); } })(resource); buttonDn.onpress = (function(resource){ return function() { proba[selec] += Math.min(STEP, proba[resource]); proba[resource] -= Math.min(STEP, proba[resource]); Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba}); updateButtons(); } })(resource); } updateButtons(); var traderNumber = Engine.GuiInterfaceCall("GetTraderNumber"); var caption = ""; var comma = ""; if (traderNumber.landTrader.total == 0) caption += "0"; else { if (traderNumber.landTrader.trading > 0) { caption += traderNumber.landTrader.trading + " trading" comma = ", "; } if (traderNumber.landTrader.garrisoned > 0) { caption += comma + traderNumber.landTrader.garrisoned + " garrisoned inside ships"; comma = ", "; } var inactive = traderNumber.landTrader.total - traderNumber.landTrader.trading - traderNumber.landTrader.garrisoned; if (inactive > 0) caption += comma + "[color=\"orange\"]" + inactive + " inactive[/color]"; } Engine.GetGUIObjectByName("landTraders").caption = caption; caption = ""; comma = ""; if (traderNumber.shipTrader.total == 0) caption += "0"; else { if (traderNumber.shipTrader.trading > 0) { caption += traderNumber.shipTrader.trading + " trading" comma = ", "; } var inactive = traderNumber.shipTrader.total - traderNumber.shipTrader.trading; if (inactive > 0) caption += comma + "[color=\"orange\"]" + inactive + " inactive[/color]"; } Engine.GetGUIObjectByName("shipTraders").caption = caption; Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false; } function closeTrade() { isTradeOpen = false; Engine.GetGUIObjectByName("tradeDialogPanel").hidden = true; } function toggleTrade() { if (isTradeOpen) closeTrade(); else openTrade(); }; function toggleGameSpeed() { var gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); gameSpeed.hidden = !gameSpeed.hidden; } /** * Pause the game in single player mode. */ function pauseGame() { if (g_IsNetworked) return; Engine.GetGUIObjectByName("pauseButtonText").caption = RESUME; Engine.GetGUIObjectByName("pauseOverlay").hidden = false; Engine.SetPaused(true); } function resumeGame() { Engine.GetGUIObjectByName("pauseButtonText").caption = PAUSE; Engine.GetGUIObjectByName("pauseOverlay").hidden = true; Engine.SetPaused(false); } function togglePause() { closeMenu(); closeOpenDialogs(); var pauseOverlay = Engine.GetGUIObjectByName("pauseOverlay"); if (pauseOverlay.hidden) { Engine.GetGUIObjectByName("pauseButtonText").caption = RESUME; Engine.SetPaused(true); } else { Engine.SetPaused(false); Engine.GetGUIObjectByName("pauseButtonText").caption = PAUSE; } pauseOverlay.hidden = !pauseOverlay.hidden; } function openManual() { closeMenu(); closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_manual.xml", {"page": "intro", "callback": "resumeGame"}); } function toggleDeveloperOverlay() { if (Engine.HasXmppClient() && Engine.IsRankedGame()) return; var devCommands = Engine.GetGUIObjectByName("devCommands"); var text = devCommands.hidden ? "opened." : "closed."; submitChatDirectly("The Developer Overlay was " + text); // Update the options dialog Engine.GetGUIObjectByName("developerOverlayCheckbox").checked = devCommands.hidden; devCommands.hidden = !devCommands.hidden; } function closeOpenDialogs() { closeMenu(); closeChat(); closeDiplomacy(); closeTrade(); closeSettings(false); } function formatTributeTooltip(player, resource, amount) { var playerColor = player.color.r + " " + player.color.g + " " + player.color.b; return "Tribute " + amount + " " + resource + " to [color=\"" + playerColor + "\"]" + player.name + "[/color]. Shift-click to tribute " + (amount < 500 ? 500 : amount + 500) + "."; } Index: ps/trunk/binaries/data/mods/public/gui/session/session.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14771) +++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14772) @@ -1,1026 +1,1036 @@ // Network Mode var g_IsNetworked = false; // Is this user in control of game settings (i.e. is a network server, or offline player) var g_IsController; // Match ID for tracking var g_MatchID; // Cache the basic player data (name, civ, color) var g_Players = []; // Cache the useful civ data var g_CivData = {}; var g_GameSpeeds = {}; var g_CurrentSpeed; var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } }; // Cache dev-mode settings that are frequently or widely used var g_DevSettings = { controlAll: false }; // Whether status bars should be shown for all of the player's units. var g_ShowAllStatusBars = false; // Indicate when one of the current player's training queues is blocked // (this is used to support population counter blinking) var g_IsTrainingBlocked = false; // Cache simulation state (updated on every simulation update) var g_SimState; // Cache EntityStates var g_EntityStates = {}; // {id:entState} // Whether the player has lost/won and reached the end of their game var g_GameEnded = false; var g_Disconnected = false; // Lost connection to server // Holds player states from the last tick var g_CachedLastStates = ""; // Colors to flash when pop limit reached const DEFAULT_POPULATION_COLOR = "white"; const POPULATION_ALERT_COLOR = "orange"; // List of additional entities to highlight var g_ShowGuarding = false; var g_ShowGuarded = false; var g_AdditionalHighlight = []; // for saving the hitpoins of the hero (is there a better way to do that?) // Should be possible with AttackDetection but might be an overkill because it would have to loop // always through the list of all ongoing attacks... var g_previousHeroHitPoints = undefined; function GetSimState() { if (!g_SimState) g_SimState = Engine.GuiInterfaceCall("GetSimulationState"); return g_SimState; } function GetEntityState(entId) { if (!(entId in g_EntityStates)) { var entState = Engine.GuiInterfaceCall("GetEntityState", entId); if (entState) entState.extended = false; g_EntityStates[entId] = entState; } return g_EntityStates[entId]; } function GetExtendedEntityState(entId) { if (entId in g_EntityStates) var entState = g_EntityStates[entId]; else var entState = Engine.GuiInterfaceCall("GetEntityState", entId); if (!entState || entState.extended) return entState; var extension = Engine.GuiInterfaceCall("GetExtendedEntityState", entId); for (var prop in extension) entState[prop] = extension[prop]; entState.extended = true; g_EntityStates[entId] = entState; return entState; } // Cache TemplateData var g_TemplateData = {}; // {id:template} function GetTemplateData(templateName) { if (!(templateName in g_TemplateData)) { var template = Engine.GuiInterfaceCall("GetTemplateData", templateName); g_TemplateData[templateName] = template; } return g_TemplateData[templateName]; } // Cache TechnologyData var g_TechnologyData = {}; // {id:template} function GetTechnologyData(technologyName) { if (!(technologyName in g_TechnologyData)) { var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName); g_TechnologyData[technologyName] = template; } return g_TechnologyData[technologyName]; } // Init function init(initData, hotloadData) { if (initData) { g_IsNetworked = initData.isNetworked; // Set network mode g_IsController = initData.isController; // Set controller mode g_PlayerAssignments = initData.playerAssignments; g_MatchID = initData.attribs.matchID; // Cache the player data // (This may be updated at runtime by handleNetMessage) g_Players = getPlayerData(g_PlayerAssignments); if (initData.savedGUIData) restoreSavedGameData(initData.savedGUIData); Engine.GetGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked; } else // Needed for autostart loading option { g_Players = getPlayerData(null); } // Cache civ data g_CivData = loadCivData(); g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia" }; g_GameSpeeds = initGameSpeeds(); g_CurrentSpeed = Engine.GetSimRate(); var gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); gameSpeed.list = g_GameSpeeds.names; gameSpeed.list_data = g_GameSpeeds.speeds; var idx = g_GameSpeeds.speeds.indexOf(g_CurrentSpeed); gameSpeed.selected = idx != -1 ? idx : g_GameSpeeds["default"]; gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); } Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[Engine.GetPlayerID()].civ].Emblem; Engine.GetGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[Engine.GetPlayerID()].civ].Name; initMenuPosition(); // set initial position // Populate player selection dropdown var playerNames = []; var playerIDs = []; for (var player in g_Players) { playerNames.push(g_Players[player].name); playerIDs.push(player); } var viewPlayerDropdown = Engine.GetGUIObjectByName("viewPlayer"); viewPlayerDropdown.list = playerNames; viewPlayerDropdown.list_data = playerIDs; viewPlayerDropdown.selected = Engine.GetPlayerID(); // If in Atlas editor, disable the exit button if (Engine.IsAtlasRunning()) Engine.GetGUIObjectByName("menuExitButton").enabled = false; if (hotloadData) { g_Selection.selected = hotloadData.selection; } else { // Starting for the first time: var civMusic = g_CivData[g_Players[Engine.GetPlayerID()].civ].Music; initMusic(); global.music.storeTracks(civMusic); global.music.setState(global.music.states.PEACE); playRandomAmbient("temperate"); } if (Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") === "true") Engine.GetGUIObjectByName("timeElapsedCounter").hidden = false; onSimulationUpdate(); // Report the performance after 5 seconds (when we're still near // the initial camera view) and a minute (when the profiler will // have settled down if framerates as very low), to give some // extremely rough indications of performance setTimeout(function() { reportPerformance(5); }, 5000); setTimeout(function() { reportPerformance(60); }, 60000); } function selectViewPlayer(playerID) { Engine.SetPlayerID(playerID); if (playerID != 0) { Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem; Engine.GetGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name; } } function reportPerformance(time) { var settings = Engine.GetMapSettings(); var data = { time: time, map: settings.Name, seed: settings.Seed, // only defined for random maps size: settings.Size, // only defined for random maps profiler: Engine.GetProfilerState() }; Engine.SubmitUserReport("profile", 3, JSON.stringify(data)); } -function resignGame() +/** + * Resign a player. + * @param leaveGameAfterResign If player is quitting after resignation. + */ +function resignGame(leaveGameAfterResign) { var simState = GetSimState(); // Players can't resign if they've already won or lost. if (simState.players[Engine.GetPlayerID()].state != "active" || g_Disconnected) return; // Tell other players that we have given up and been defeated Engine.PostNetworkCommand({ "type": "defeat-player", "playerId": Engine.GetPlayerID() }); global.music.setState(global.music.states.DEFEAT); - resumeGame(); + + // Resume the game if not resigning. + if (!leaveGameAfterResign) + resumeGame(); } -function leaveGame() +/** + * Leave the game + * @param willRejoin If player is going to be rejoining a networked game. + */ +function leaveGame(willRejoin) { var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); var playerState = extendedSimState.players[Engine.GetPlayerID()]; var mapSettings = Engine.GetMapSettings(); var gameResult; if (g_Disconnected) { gameResult = "You have been disconnected." } else if (playerState.state == "won") { gameResult = "You have won the battle!"; } else if (playerState.state == "defeated") { gameResult = "You have been defeated..."; } else // "active" { - gameResult = "You have abandoned the game."; - - // Tell other players that we have given up and been defeated - Engine.PostNetworkCommand({ - "type": "defeat-player", - "playerId": Engine.GetPlayerID() - }); - global.music.setState(global.music.states.DEFEAT); + if (willRejoin) + gameResult = "You have left the game."; + else + { + gameResult = "You have abandoned the game."; + resignGame(true); + } } stopAmbient(); Engine.EndGame(); if (g_IsController && Engine.HasXmppClient()) Engine.SendUnregisterGame(); Engine.SwitchGuiPage("page_summary.xml", { "gameResult" : gameResult, "timeElapsed" : extendedSimState.timeElapsed, "playerStates": extendedSimState.players, "players": g_Players, "mapSettings": mapSettings }); } // Return some data that we'll use when hotloading this file after changes function getHotloadData() { return { selection: g_Selection.selected }; } // Return some data that will be stored in saved game files function getSavedGameData() { var data = {}; data.playerAssignments = g_PlayerAssignments; data.groups = g_Groups.groups; // TODO: any other gui state? return data; } function restoreSavedGameData(data) { // Clear selection when loading a game g_Selection.reset(); // Restore control groups for (var groupNumber in data.groups) { g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups; g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents; } updateGroups(); } var lastTickTime = new Date; var lastXmppClientPoll = Date.now(); /** * Called every frame. */ function onTick() { var now = new Date; var tickLength = new Date - lastTickTime; lastTickTime = now; checkPlayerState(); while (true) { var message = Engine.PollNetworkClient(); if (!message) break; handleNetMessage(message); } updateCursorAndTooltip(); // If the selection changed, we need to regenerate the sim display (the display depends on both the // simulation state and the current selection). if (g_Selection.dirty) { g_Selection.dirty = false; onSimulationUpdate(); // Display rally points for selected buildings Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() }); } // Run timers updateTimers(); // Animate menu updateMenuPosition(tickLength); // When training is blocked, flash population (alternates colour every 500msec) if (g_IsTrainingBlocked && (Date.now() % 1000) < 500) Engine.GetGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR; else Engine.GetGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR; // Clear renamed entities list Engine.GuiInterfaceCall("ClearRenamedEntities"); // If the lobby is running, wake it up every 10 seconds so we stay connected. if (Engine.HasXmppClient() && (Date.now() - lastXmppClientPoll) > 10000) { Engine.RecvXmppClient(); lastXmppClientPoll = Date.now(); } } function checkPlayerState() { // Once the game ends, we're done here. if (g_GameEnded) return; // Send a game report for each player in this game. var m_simState = GetSimState(); var playerState = m_simState.players[Engine.GetPlayerID()]; var tempStates = ""; for each (var player in m_simState.players) {tempStates += player.state + ",";} if (g_CachedLastStates != tempStates) { g_CachedLastStates = tempStates; reportGame(Engine.GuiInterfaceCall("GetExtendedSimulationState")); } // If the local player hasn't finished playing, we return here to avoid the victory/defeat messages. if (playerState.state == "active") return; // We can't resign once the game is over. Engine.GetGUIObjectByName("menuResignButton").enabled = false; // Make sure nothing is open to avoid stacking. closeMenu(); closeOpenDialogs(); // Make sure this doesn't run again. g_GameEnded = true; if (Engine.IsAtlasRunning()) { // If we're in Atlas, we can't leave the game var btCaptions = ["OK"]; var btCode = [null]; var message = "Press OK to continue"; } else { var btCaptions = ["Yes", "No"]; var btCode = [leaveGame, null]; var message = "Do you want to quit?"; } if (playerState.state == "defeated") { global.music.setState(global.music.states.DEFEAT); messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode); } else if (playerState.state == "won") { global.music.setState(global.music.states.VICTORY); // TODO: Reveal map directly instead of this silly proxy. if (!Engine.GetGUIObjectByName("devCommandsRevealMap").checked) Engine.GetGUIObjectByName("devCommandsRevealMap").checked = true; messageBox(400, 200, message, "VICTORIOUS!", 0, btCaptions, btCode); } } function changeGameSpeed(speed) { // For non-networked games only if (!g_IsNetworked) { Engine.SetSimRate(speed); g_CurrentSpeed = speed; } } /** * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation * update (see session.xml), or from onTick when the selection has changed. */ function onSimulationUpdate() { g_EntityStates = {}; g_TemplateData = {}; g_TechnologyData = {}; g_SimState = Engine.GuiInterfaceCall("GetSimulationState"); // If we're called during init when the game is first loading, there will be no simulation yet, so do nothing if (!g_SimState) return; handleNotifications(); if (g_ShowAllStatusBars) recalculateStatusBarDisplay(); if (g_ShowGuarding || g_ShowGuarded) updateAdditionalHighlight(); updateHero(); updateGroups(); updateDebug(); updatePlayerDisplay(); updateSelectionDetails(); updateResearchDisplay(); updateBuildingPlacementPreview(); updateTimeElapsedCounter(); updateTimeNotifications(); // Update music state on basis of battle state. var battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID()); if (battleState) global.music.setState(global.music.states[battleState]); } /** * updates a status bar on the GUI * nameOfBar: name of the bar * points: points to show * maxPoints: max points * direction: gets less from (right to left) 0; (top to bottom) 1; (left to right) 2; (bottom to top) 3; */ function updateGUIStatusBar(nameOfBar, points, maxPoints, direction) { // check, if optional direction parameter is valid. if (!direction || !(direction >= 0 && direction < 4)) direction = 0; // get the bar and update it var statusBar = Engine.GetGUIObjectByName(nameOfBar); if (!statusBar) return; var healthSize = statusBar.size; var value = 100*Math.max(0, Math.min(1, points / maxPoints)); // inverse bar if(direction == 2 || direction == 3) value = 100 - value; if(direction == 0) healthSize.rright = value; else if(direction == 1) healthSize.rbottom = value; else if(direction == 2) healthSize.rleft = value; else if(direction == 3) healthSize.rtop = value; // update bar statusBar.size = healthSize; } function updateHero() { var simState = GetSimState(); var playerState = simState.players[Engine.GetPlayerID()]; var unitHeroPanel = Engine.GetGUIObjectByName("unitHeroPanel"); var heroButton = Engine.GetGUIObjectByName("unitHeroButton"); if (!playerState || playerState.heroes.length <= 0) { g_previousHeroHitPoints = undefined; unitHeroPanel.hidden = true; return; } var heroImage = Engine.GetGUIObjectByName("unitHeroImage"); var heroState = GetExtendedEntityState(playerState.heroes[0]); var template = GetTemplateData(heroState.template); heroImage.sprite = "stretched:session/portraits/" + template.icon; var hero = playerState.heroes[0]; heroButton.onpress = function() { if (!Engine.HotkeyIsPressed("selection.add")) g_Selection.reset(); g_Selection.addList([hero]); }; heroButton.ondoublepress = function() { selectAndMoveTo(getEntityOrHolder(hero)); }; unitHeroPanel.hidden = false; // Setup tooltip var tooltip = "[font=\"serif-bold-16\"]" + template.name.specific + "[/font]"; tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + heroState.hitpoints + "/" + heroState.maxHitpoints; tooltip += "\n[font=\"serif-bold-13\"]" + (heroState.attack ? heroState.attack.type + " " : "") + "Attack:[/font] " + damageTypeDetails(heroState.attack); // Show max attack range if ranged attack, also convert to tiles (4m per tile) if (heroState.attack && heroState.attack.type == "Ranged") tooltip += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(heroState.attack.maxRange/4); tooltip += "\n[font=\"serif-bold-13\"]Armor:[/font] " + damageTypeDetails(heroState.armour); tooltip += "\n" + template.tooltip; heroButton.tooltip = tooltip; // update heros health bar updateGUIStatusBar("heroHealthBar", heroState.hitpoints, heroState.maxHitpoints); // define the hit points if not defined if (!g_previousHeroHitPoints) g_previousHeroHitPoints = heroState.hitpoints; // check, if the health of the hero changed since the last update if (heroState.hitpoints < g_previousHeroHitPoints) { g_previousHeroHitPoints = heroState.hitpoints; // trigger the animation startColorFade("heroHitOverlay", 100, 0, colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit); return; } } function updateGroups() { var guiName = "Group"; g_Groups.update(); for (var i = 0; i < 10; i++) { var button = Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]"); var label = Engine.GetGUIObjectByName("unit"+guiName+"Label["+i+"]").caption = i; if (g_Groups.groups[i].getTotalCount() == 0) button.hidden = true; else button.hidden = false; button.onpress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); } })(i); button.ondoublepress = (function(i) { return function() { performGroup("snap", i); } })(i); button.onpressright = (function(i) { return function() { performGroup("breakUp", i); } })(i); } var numButtons = i; var rowLength = 1; var numRows = Math.ceil(numButtons / rowLength); var buttonSideLength = Engine.GetGUIObjectByName("unit"+guiName+"Button[0]").size.bottom; var buttonSpacer = buttonSideLength+1; for (var i = 0; i < numRows; i++) layoutButtonRow(i, guiName, buttonSideLength, buttonSpacer, rowLength*i, rowLength*(i+1) ); } function updateDebug() { var simState = GetSimState(); var debug = Engine.GetGUIObjectByName("debug"); if (Engine.GetGUIObjectByName("devDisplayState").checked) { debug.hidden = false; } else { debug.hidden = true; return; } var conciseSimState = deepcopy(simState); conciseSimState.players = "<<>>"; var text = "simulation: " + uneval(conciseSimState); var selection = g_Selection.toList(); if (selection.length) { var entState = GetExtendedEntityState(selection[0]); if (entState) { var template = GetTemplateData(entState.template); text += "\n\nentity: {\n"; for (var k in entState) text += " "+k+":"+uneval(entState[k])+"\n"; text += "}\n\ntemplate: " + uneval(template); } } debug.caption = text; } function updatePlayerDisplay() { var simState = GetSimState(); var playerState = simState.players[Engine.GetPlayerID()]; if (!playerState) return; Engine.GetGUIObjectByName("resourceFood").caption = Math.floor(playerState.resourceCounts.food); Engine.GetGUIObjectByName("resourceWood").caption = Math.floor(playerState.resourceCounts.wood); Engine.GetGUIObjectByName("resourceStone").caption = Math.floor(playerState.resourceCounts.stone); Engine.GetGUIObjectByName("resourceMetal").caption = Math.floor(playerState.resourceCounts.metal); Engine.GetGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit; g_IsTrainingBlocked = playerState.trainingBlocked; } function selectAndMoveTo(ent) { var entState = GetEntityState(ent); if (!entState || !entState.position) return; g_Selection.reset(); g_Selection.addList([ent]); var position = entState.position; Engine.CameraMoveTo(position.x, position.z); } function updateResearchDisplay() { var researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", Engine.GetPlayerID()); if (!researchStarted) return; // Set up initial positioning. var buttonSideLength = Engine.GetGUIObjectByName("researchStartedButton[0]").size.right; for (var i = 0; i < 10; ++i) { var button = Engine.GetGUIObjectByName("researchStartedButton[" + i + "]"); var size = button.size; size.top = (4 + buttonSideLength) * i; size.bottom = size.top + buttonSideLength; button.size = size; } var numButtons = 0; for (var tech in researchStarted) { // Show at most 10 in-progress techs. if (numButtons >= 10) break; var template = GetTechnologyData(tech); var button = Engine.GetGUIObjectByName("researchStartedButton[" + numButtons + "]"); button.hidden = false; button.tooltip = getEntityNames(template); button.onpress = (function(e) { return function() { selectAndMoveTo(e) } })(researchStarted[tech].researcher); var icon = "stretched:session/portraits/" + template.icon; Engine.GetGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon; // Scale the progress indicator. var size = Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size; // Buttons are assumed to be square, so left/right offsets can be used for top/bottom. size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left)); Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size; ++numButtons; } // Hide unused buttons. for (var i = numButtons; i < 10; ++i) Engine.GetGUIObjectByName("researchStartedButton[" + i + "]").hidden = true; } function updateTimeElapsedCounter() { var simState = GetSimState(); var speed = g_CurrentSpeed != 1.0 ? " (" + g_CurrentSpeed + "x)" : ""; var timeElapsedCounter = Engine.GetGUIObjectByName("timeElapsedCounter"); timeElapsedCounter.caption = timeToString(simState.timeElapsed) + speed; } // Toggles the display of status bars for all of the player's entities. function recalculateStatusBarDisplay() { if (g_ShowAllStatusBars) var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID()); else { var selected = g_Selection.toList(); for each (var ent in g_Selection.highlighted) selected.push(ent); // Remove selected entities from the 'all entities' array, to avoid disabling their status bars. var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter( function(idx) { return (selected.indexOf(idx) == -1); } ); } Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars }); } // Update the additional list of entities to be highlighted. function updateAdditionalHighlight() { var entsAdd = []; // list of entities units to be highlighted var entsRemove = []; var highlighted = g_Selection.toList(); for each (var ent in g_Selection.highlighted) highlighted.push(ent); if (g_ShowGuarding) { // flag the guarding entities to add in this additional highlight for each (var sel in g_Selection.selected) { var state = GetEntityState(sel); if (!state.guard || !state.guard.entities.length) continue; for each (var ent in state.guard.entities) if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1) entsAdd.push(ent); } } if (g_ShowGuarded) { // flag the guarded entities to add in this additional highlight for each (var sel in g_Selection.selected) { var state = GetEntityState(sel); if (!state.unitAI || !state.unitAI.isGuarding) continue; var ent = state.unitAI.isGuarding; if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1) entsAdd.push(ent); } } // flag the entities to remove (from the previously added) from this additional highlight for each (var ent in g_AdditionalHighlight) if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1) entsRemove.push(ent); _setHighlight(entsAdd , HIGHLIGHTED_ALPHA, true ); _setHighlight(entsRemove, 0 , false); g_AdditionalHighlight = entsAdd; } // Temporarily adding this here const AMBIENT_TEMPERATE = "temperate"; var currentAmbient; function playRandomAmbient(type) { switch (type) { case AMBIENT_TEMPERATE: // Seem to need the underscore at the end of "temperate" to avoid crash // (Might be caused by trying to randomly load day_temperate.xml) // currentAmbient = newRandomSound("ambient", "temperate_", "dayscape"); const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg"; Engine.PlayAmbientSound( AMBIENT, true ); break; default: Engine.Console_Write("Unrecognized ambient type: " + type); break; } } // Temporarily adding this here function stopAmbient() { if (currentAmbient) { currentAmbient.free(); currentAmbient = null; } } // Send a report on the game status to the lobby function reportGame(extendedSimState) { if (!Engine.HasXmppClient()) return; // units var unitsClasses = [ "total", "Infantry", "Worker", "Female", "Cavalry", "Champion", "Hero", "Ship" ]; var unitsCountersTypes = [ "unitsTrained", "unitsLost", "enemyUnitsKilled" ]; // buildings var buildingsClasses = [ "total", "CivCentre", "House", "Economic", "Outpost", "Military", "Fortress", "Wonder" ]; var buildingsCountersTypes = [ "buildingsConstructed", "buildingsLost", "enemyBuildingsDestroyed" ]; // resources var resourcesTypes = [ "wood", "food", "stone", "metal" ]; var resourcesCounterTypes = [ "resourcesGathered", "resourcesUsed", "resourcesSold", "resourcesBought" ]; var playerStatistics = { }; // Unit Stats for each (var unitCounterType in unitsCountersTypes) { if (!playerStatistics[unitCounterType]) playerStatistics[unitCounterType] = { }; for each (var unitsClass in unitsClasses) playerStatistics[unitCounterType][unitsClass] = ""; } playerStatistics.unitsLostValue = ""; playerStatistics.unitsKilledValue = ""; // Building stats for each (var buildingCounterType in buildingsCountersTypes) { if (!playerStatistics[buildingCounterType]) playerStatistics[buildingCounterType] = { }; for each (var buildingsClass in buildingsClasses) playerStatistics[buildingCounterType][buildingsClass] = ""; } playerStatistics.buildingsLostValue = ""; playerStatistics.enemyBuildingsDestroyedValue = ""; // Resources for each (var resourcesCounterType in resourcesCounterTypes) { if (!playerStatistics[resourcesCounterType]) playerStatistics[resourcesCounterType] = { }; for each (var resourcesType in resourcesTypes) playerStatistics[resourcesCounterType][resourcesType] = ""; } playerStatistics.resourcesGathered.vegetarianFood = ""; playerStatistics.tradeIncome = ""; // Tribute playerStatistics.tributesSent = ""; playerStatistics.tributesReceived = ""; // Total playerStatistics.economyScore = ""; playerStatistics.militaryScore = ""; playerStatistics.totalScore = ""; // Various playerStatistics.treasuresCollected = ""; playerStatistics.feminisation = ""; playerStatistics.percentMapExplored = ""; var mapName = Engine.GetMapSettings().Name; var playerStates = ""; var playerCivs = ""; var teams = ""; var teamsLocked = true; // Serialize the statistics for each player into a comma-separated list. for each (var player in extendedSimState.players) { playerStates += player.state + ","; playerCivs += player.civ + ","; teams += player.team + ","; teamsLocked = teamsLocked && player.teamsLocked; for each (var resourcesCounterType in resourcesCounterTypes) for each (var resourcesType in resourcesTypes) playerStatistics[resourcesCounterType][resourcesType] += player.statistics[resourcesCounterType][resourcesType] + ","; playerStatistics.resourcesGathered.vegetarianFood += player.statistics.resourcesGathered.vegetarianFood + ","; for each (var unitCounterType in unitsCountersTypes) for each (var unitsClass in unitsClasses) playerStatistics[unitCounterType][unitsClass] += player.statistics[unitCounterType][unitsClass] + ","; for each (var buildingCounterType in buildingsCountersTypes) for each (var buildingsClass in buildingsClasses) playerStatistics[buildingCounterType][buildingsClass] += player.statistics[buildingCounterType][buildingsClass] + ","; var total = 0; for each (var res in player.statistics.resourcesGathered) total += res; playerStatistics.economyScore += total + ","; playerStatistics.militaryScore += Math.round((player.statistics.enemyUnitsKilledValue + player.statistics.enemyBuildingsDestroyedValue) / 10) + ","; playerStatistics.totalScore += (total + Math.round((player.statistics.enemyUnitsKilledValue + player.statistics.enemyBuildingsDestroyedValue) / 10)) + ","; playerStatistics.tradeIncome += player.statistics.tradeIncome + ","; playerStatistics.tributesSent += player.statistics.tributesSent + ","; playerStatistics.tributesReceived += player.statistics.tributesReceived + ","; playerStatistics.percentMapExplored += player.statistics.percentMapExplored + ","; playerStatistics.treasuresCollected += player.statistics.treasuresCollected + ","; } // Send the report with serialized data var reportObject = { }; reportObject.timeElapsed = extendedSimState.timeElapsed; reportObject.playerStates = playerStates; reportObject.playerID = Engine.GetPlayerID(); reportObject.matchID = g_MatchID; reportObject.civs = playerCivs; reportObject.teams = teams; reportObject.teamsLocked = String(teamsLocked); reportObject.mapName = mapName; reportObject.economyScore = playerStatistics.economyScore; reportObject.militaryScore = playerStatistics.militaryScore; reportObject.totalScore = playerStatistics.totalScore; for each (var rct in resourcesCounterTypes) { for each (var rt in resourcesTypes) reportObject[rt+rct.substr(9)] = playerStatistics[rct][rt]; // eg. rt = food rct.substr = Gathered rct = resourcesGathered } reportObject.vegetarianFoodGathered = playerStatistics.resourcesGathered.vegetarianFood; for each (var type in unitsClasses) { // eg. type = Infantry (type.substr(0,1)).toLowerCase()+type.substr(1) = infantry reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"UnitsTrained"] = playerStatistics.unitsTrained[type]; reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"UnitsLost"] = playerStatistics.unitsLost[type]; reportObject["enemy"+type+"UnitsKilled"] = playerStatistics.enemyUnitsKilled[type]; } for each (var type in buildingsClasses) { reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"BuildingsConstructed"] = playerStatistics.buildingsConstructed[type]; reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"BuildingsLost"] = playerStatistics.buildingsLost[type]; reportObject["enemy"+type+"BuildingsDestroyed"] = playerStatistics.enemyBuildingsDestroyed[type]; } reportObject.tributesSent = playerStatistics.tributesSent; reportObject.tributesReceived = playerStatistics.tributesReceived; reportObject.percentMapExplored = playerStatistics.percentMapExplored; reportObject.treasuresCollected = playerStatistics.treasuresCollected; reportObject.tradeIncome = playerStatistics.tradeIncome; Engine.SendGameReport(reportObject); } Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 14771) +++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 14772) @@ -1,1362 +1,1361 @@