Index: ps/trunk/binaries/data/mods/public/gui/lobby/prelobby.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/prelobby.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/prelobby.xml (revision 14954)
@@ -1,98 +1,98 @@
Index: ps/trunk/binaries/data/mods/public/gui/locale/locale.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/locale/locale.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/locale/locale.xml (revision 14954)
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Language
+
+
+ Language:
+
+
+ languageSelectionChanged();
+
+
+ Locale:
+
+
+
+
+ Accept
+ applySelectedLocale();
+
+
+
+ Advanced
+ openAdvancedMenu();
+
+
+
+ Cancel
+ cancelSetup();
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/locale/locale.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.xml (revision 14954)
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+ onTick();
+
+
+ Language
+
+
+ Language:
+
+
+
+
+ Country:
+
+
+
+
+
+ Script:
+
+
+ Optional four-letter script code part following the language code (as listed in ISO 15924)
+
+
+
+ Variant (unused):
+
+
+ Not implemented yet.
+
+
+
+ Keywords (unused):
+
+
+ Not implemented yet.
+
+
+
+ Resulting locale:
+
+
+
+
+ Dictionary files used:
+
+
+
+
+ Accept
+ applySelectedLocale();
+
+
+
+ Auto detect
+ autoDetectLocale();
+
+
+
+ Cancel
+ cancelSetup();
+
+
+
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/manual/manual.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/manual/manual.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/manual/manual.xml (revision 14954)
@@ -1,30 +1,33 @@
- Manual
+
+ Manual
+
-
- Online Manual
+
+
+ Online Manual
+ ]]>
- Close
+ Close
Index: ps/trunk/binaries/data/mods/public/gui/msgbox/msgbox.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/msgbox/msgbox.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/msgbox/msgbox.xml (revision 14954)
@@ -1,150 +1,49 @@
-
+
Engine.PopGuiPage();
Index: ps/trunk/binaries/data/mods/public/gui/page_locale.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/page_locale.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/page_locale.xml (revision 14954)
@@ -0,0 +1,16 @@
+
+
+
+ common/modern/styles.xml
+ common/modern/sprites.xml
+ common/modern/setup.xml
+ common/setup.xml
+ common/styles.xml
+ common/sprite1.xml
+ common/common_sprites.xml
+ common/common_styles.xml
+
+ locale/locale.xml
+
+ common/global.xml
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/page_locale.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 14954)
@@ -1,558 +1,572 @@
onTick();
tooltipText
[font="serif-bold-16"]Help improve 0 A.D.![/font]
-
-You can automatically send us anonymous feedback that will help us fix bugs, and improve performance and compatibility.
+ >
+
+ [font="serif-bold-16"]
+ Help improve 0 A.D.!
+ [/font]\n\n
+ You can automatically send us anonymous feedback that will help us fix bugs, and improve performance and compatibility.
+
- Enable feedback
+ Enable feedbackEnableUserReport(true);
- Technical details
+ Technical detailsEngine.PushGuiPage("page_manual.xml", { "page": "userreport" });[font="serif-bold-16"]Thank you for helping improve 0 A.D.![/font]
-
-Anonymous feedback is currently enabled.
-Status: $status.
-
+ >
+
+ [font="serif-bold-16"]
+ Thank you for helping improve 0 A.D.!
+ [/font]\n\n
+ Anonymous feedback is currently enabled.
+ \n
+ Status: $status.
+
+
- Disable feedback
+ Disable feedbackEnableUserReport(false);
- Technical details
+ Technical detailsEngine.PushGuiPage("page_manual.xml", { "page": "userreport" });
closeMenu();
- Matches
+ Matches
+ Click here to start a new single player game.
Engine.SwitchGuiPage("page_gamesetup.xml", { type: "offline" });
- Campaigns
+ Campaigns
+ Relive history through historical military campaigns. [NOT YET IMPLEMENTED]
closeMenu();
- Load Game
+ Load Game
+ Click here to load a saved game.
closeMenu();
Engine.PushGuiPage("page_loadgame.xml", { type: "offline" });
- Join Game
+ Join Game
+ Joining an existing multiplayer game.
closeMenu();
// Open Multiplayer connection window with join option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "join" });
- Host Game
+ Host Game
+ Host a multiplayer game.\n\nRequires UDP port 20595 to be open.
closeMenu();
// Open Multiplayer connection window with host option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "host" });
- Game Lobby
+ Game Lobby
+ Launch the multiplayer lobby.
closeMenu();
// Open Multiplayer game lobby.
Engine.PushGuiPage("page_prelobby.xml", []);
if (!Engine.StartXmppClient)
{
this.enabled = false;
- this.tooltip = "Launch the multiplayer lobby. [DISABLED BY BUILD]";
+ this.tooltip = getLobbyDisabledByBuild();
}
- Options
+ Options
+ Adjust game settings.
closeMenu();
-
- Scenario Editor
+ Language
+ Choose the language of the game.
- closeMenu();
+
+
+ Scenario Editor
+ Open the Atlas Scenario Editor in a new window. You can run this more reliably by starting the game with the command-line argument "-editor".
+
+ pressedScenarioEditorButton();
+
+
- Learn To Play
+ Learn To Play
+ The 0 A.D. Game Manual
closeMenu();
- Single Player
+ Single Player
+ Challenge the computer player to a single player match.
closeMenu();
openMenu("submenuSinglePlayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
- Multiplayer
+ Multiplayer
+ Fight against one or more human players in a multiplayer game.
closeMenu();
openMenu("submenuMultiplayer", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
- Tools Options
+ Tools & Options
+ Game options and scenario design tools.
closeMenu();
- openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 2);
+ openMenu("submenuToolsAndOptions", (this.parent.size.top+this.size.top), (this.size.bottom-this.size.top), 3);
- History
+ History
+ Learn about the many civilizations featured in 0 A.D.
closeMenu();
- Exit
-
- closeMenu();
-
-
+ Exit
+ Exit Game
+ exitGamePressed();
-[font="serif-bold-16"]Alpha XV: Osiris[/font]
-
-WARNING: This is an early development version of the game. Many features have not been added yet.
-
-Get involved at: play0ad.com
+
+
+ [font="serif-bold-16"]
+ Alpha XV: Osiris
+ [/font]\n\n
+ WARNING: This is an early development version of the game. Many features have not been added yet.
+ \n\n
+ Get involved at: play0ad.com
+
- Website
+ Website
+ Click to open play0ad.com in your web browser.
- Chat
+ Chat
+ Click to open the 0 A.D. IRC chat in your browser. (#0ad on webchat.quakenet.org)
- Report a Bug
+ Report a Bug
+ Click to visit 0 A.D. Trac to report a bug, crash, or errorWILDFIRE GAMES
+ >
+ WILDFIRE GAMES
+
-
+
+ this.caption = getBuildString();
+
Index: ps/trunk/binaries/data/mods/public/gui/savedgames/save.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/savedgames/save.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/savedgames/save.js (revision 14954)
@@ -1,108 +1,108 @@
var g_Descriptions;
var savedGameData;
function selectDescription()
{
var gameSelection = Engine.GetGUIObjectByName("gameSelection");
if (gameSelection.selected != -1)
{
Engine.GetGUIObjectByName("deleteGameButton").enabled = true;
var gameID = gameSelection.list_data[gameSelection.selected];
Engine.GetGUIObjectByName("saveGameDesc").caption = g_Descriptions[gameID];
}
}
function init(data)
{
if (data)
{
if (data.savedGameData)
savedGameData = data.savedGameData;
}
var gameSelection = Engine.GetGUIObjectByName("gameSelection");
Engine.GetGUIObjectByName("deleteGameButton").enabled = false;
var savedGames = Engine.GetSavedGames();
if (savedGames.length == 0)
{
- gameSelection.list = [ "No saved games found" ];
+ gameSelection.list = [translate("No saved games found")];
gameSelection.selected = -1;
return;
}
savedGames.sort(sortDecreasingDate);
var gameListIDs = [ game.id for each (game in savedGames) ];
var gameListLabels = [ generateLabel(game.metadata) for each (game in savedGames) ];
g_Descriptions = {};
[ g_Descriptions[game.id] = (game.metadata.description ? game.metadata.description : "") for each (game in savedGames) ];
gameSelection.list = gameListLabels;
gameSelection.list_data = gameListIDs;
gameSelection.selected = -1;
}
function saveGame()
{
var gameSelection = Engine.GetGUIObjectByName("gameSelection");
var gameLabel = gameSelection.list[gameSelection.selected];
var gameID = gameSelection.list_data[gameSelection.selected];
var desc = Engine.GetGUIObjectByName("saveGameDesc").caption;
var name = gameID ? gameID : "savegame";
if (gameSelection.selected != -1)
{
// Ask for confirmation
- var btCaptions = ["Yes", "No"];
+ var btCaptions = [translate("Yes"), translate("No")];
var btCode = [function(){ reallySaveGame(name, desc, false); }, null];
- messageBox(500, 200, "\""+gameLabel+"\"\nSaved game will be permanently overwritten, are you sure?", "OVERWRITE SAVE", 0, btCaptions, btCode);
+ messageBox(500, 200, sprintf(translate("\"%(label)s\""), { label: gameLabel }) + "\n" + translate("Saved game will be permanently overwritten, are you sure?"), translate("OVERWRITE SAVE"), 0, btCaptions, btCode);
}
else
reallySaveGame(name, desc, true);
}
function reallySaveGame(name, desc, nameIsPrefix)
{
if (nameIsPrefix)
Engine.SaveGamePrefix(name, desc, savedGameData);
else
Engine.SaveGame(name, desc, savedGameData);
closeSave();
}
function closeSave()
{
Engine.PopGuiPageCB(0);
}
function deleteGame()
{
var gameSelection = Engine.GetGUIObjectByName("gameSelection");
var gameLabel = gameSelection.list[gameSelection.selected];
var gameID = gameSelection.list_data[gameSelection.selected];
// Ask for confirmation
- var btCaptions = ["Yes", "No"];
+ var btCaptions = [translate("Yes"), translate("No")];
var btCode = [function(){ reallyDeleteGame(gameID); }, null];
- messageBox(500, 200, "\""+gameLabel+"\"\nSaved game will be permanently deleted, are you sure?", "DELETE", 0, btCaptions, btCode);
+ messageBox(500, 200, sprintf(translate("\"%(label)s\""), { label: gameLabel }) + "\n" + translate("Saved game will be permanently deleted, are you sure?"), translate("DELETE"), 0, btCaptions, btCode);
}
function reallyDeleteGame(gameID)
{
if (!Engine.DeleteSavedGame(gameID))
- error("Could not delete saved game '"+gameID+"'");
+ error(sprintf("Could not delete saved game '%(id)s'", { id: gameID }));
// Run init again to refresh saved game list
init();
}
// HACK: Engine.SaveGame* expects this function to be defined on the current page.
// That's why we have to pass the data to this page even if we don't need it.
function getSavedGameData()
{
return savedGameData;
}
Index: ps/trunk/binaries/data/mods/public/gui/session/menu.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 14954)
@@ -1,618 +1,708 @@
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 btCaptions = [translate("Yes"), translate("No")];
var btCode = [resignGame, resumeGame];
- messageBox(400, 200, "Are you sure you want to resign?", "Confirmation", 0, btCaptions, btCode);
+ messageBox(400, 200, translate("Are you sure you want to resign?"), translate("Confirmation"), 0, btCaptions, btCode);
}
function exitMenuButton()
{
closeMenu();
closeOpenDialogs();
pauseGame();
if (g_IsNetworked && g_IsController)
{
var btCode = [leaveGame, resumeGame];
- var message = "Are you sure you want to quit? Leaving will disconnect all other players.";
+ var message = translate("Are you sure you want to quit? Leaving will disconnect all other players.");
}
else if (g_IsNetworked && !g_GameEnded && !g_IsObserver)
{
var btCode = [networkReturnQuestion, resumeGame];
- var message = "Are you sure you want to quit?";
+ var message = translate("Are you sure you want to quit?");
}
else
{
var btCode = [leaveGame, resumeGame];
- var message = "Are you sure you want to quit?";
+ var message = translate("Are you sure you want to quit?");
}
- messageBox(400, 200, message, "Confirmation", 0, ["Yes", "No"], btCode);
+ messageBox(400, 200, message, translate("Confirmation"), 0, [translate("Yes"), translate("No")], btCode);
}
function networkReturnQuestion()
{
- var btCaptions = ["I resign", "I will return"];
+ var btCaptions = [translate("I resign"), translate("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);
+ messageBox(400, 200, translate("Do you want to resign or will you return soon?"), translate("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 btCaptions = [translate("Yes"), translate("No")];
var btCode = [deleteSelectedEntities, resumeGame];
- messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode, [selection, null]);
+ messageBox(400, 200, translate("Destroy everything currently selected?"), translate("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;
+ Engine.GetGUIObjectByName("diplomacyPlayerTeam["+(i-1)+"]").caption = (players[i].team < 0) ? translateWithContext("team", "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"));
+ Engine.GetGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = (players[i].isAlly[we] ? translate("Ally") : (players[i].isNeutral[we] ? translate("Neutral") : translate("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";
+ button.caption = translate("x");
else
button.caption = "";
}
else if (setting == "neutral")
{
if (players[we].isNeutral[i])
- button.caption = "x";
+ button.caption = translate("x");
else
button.caption = "";
}
else // "enemy"
{
if (players[we].isEnemy[i])
- button.caption = "x";
+ button.caption = translate("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";
+ caption = translate("There are no land traders.");
else
{
+ var inactive = traderNumber.landTrader.total - traderNumber.landTrader.trading - traderNumber.landTrader.garrisoned;
+ var inactiveString = "";
+ if (inactive > 0)
+ inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfLandTraders)s inactive", "%(numberOfLandTraders)s inactive", inactive), { numberOfLandTraders: inactive }) + "[/color]";
+
if (traderNumber.landTrader.trading > 0)
{
- caption += traderNumber.landTrader.trading + " trading"
- comma = ", ";
+ var openingTradingString = sprintf(translatePlural("There is %(numberTrading)s land trader trading", "There are %(numberTrading)s land traders trading", traderNumber.landTrader.trading), { numberTrading: traderNumber.landTrader.trading });
+ if (traderNumber.landTrader.garrisoned > 0)
+ {
+ var garrisonedString = sprintf(translatePlural("%(numberGarrisoned)s garrisoned on a trading merchant ship", "%(numberGarrisoned)s garrisoned on a trading merchant ship", traderNumber.landTrader.garrisoned), { numberGarrisoned: traderNumber.landTrader.garrisoned });
+ if (inactive > 0)
+ {
+ caption = sprintf(translate("%(openingTradingString)s, %(garrisonedString)s, and %(inactiveString)s."), {
+ openingTradingString: openingTradingString,
+ garrisonedString: garrisonedString,
+ inactiveString: inactiveString
+ });
+ }
+ else
+ {
+ caption = sprintf(translate("%(openingTradingString)s, and %(garrisonedString)s."), {
+ openingTradingString: openingTradingString,
+ garrisonedString: garrisonedString
+ });
+ }
+ }
+ else
+ {
+ if (inactive > 0)
+ {
+ caption = sprintf(translate("%(openingTradingString)s, and %(inactiveString)s."), {
+ openingTradingString: openingTradingString,
+ inactiveString: inactiveString
+ });
+ }
+ else
+ {
+ caption = sprintf(translate("%(openingTradingString)s."), {
+ openingTradingString: openingTradingString,
+ });
+ }
+ }
}
- if (traderNumber.landTrader.garrisoned > 0)
+ else
{
- caption += comma + traderNumber.landTrader.garrisoned + " garrisoned inside ships";
- comma = ", ";
+ if (traderNumber.landTrader.garrisoned > 0)
+ {
+ var openingGarrisonedString = sprintf(translatePlural("There is %(numberGarrisoned)s land trader garrisoned on a trading merchant ship", "There are %(numberGarrisoned)s land traders garrisoned on a trading merchant ship", traderNumber.landTrader.garrisoned), { numberGarrisoned: traderNumber.landTrader.garrisoned });
+ if (inactive > 0)
+ {
+ caption = sprintf(translate("%(openingGarrisonedString)s, and %(inactiveString)s."), {
+ openingGarrisonedString: openingGarrisonedString,
+ inactiveString: inactiveString
+ });
+ }
+ else
+ {
+ caption = sprintf(translate("%(openingGarrisonedString)s."), {
+ openingGarrisonedString: openingGarrisonedString
+ });
+ }
+ }
+ else
+ {
+ if (inactive > 0)
+ {
+ inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfLandTraders)s land trader inactive", "%(numberOfLandTraders)s land traders inactive", inactive), { numberOfLandTraders: inactive }) + "[/color]";
+ caption = sprintf(translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), {
+ inactiveString: inactiveString
+ });
+ }
+ // The “else” here is already handled by “if (traderNumber.landTrader.total == 0)” above.
+ }
}
- 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";
+ caption = translate("There are no merchant ships.");
else
{
+ var inactive = traderNumber.shipTrader.total - traderNumber.shipTrader.trading;
+ var inactiveString = "";
+ if (inactive > 0)
+ inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfShipTraders)s inactive", "%(numberOfShipTraders)s inactive", inactive), { numberOfShipTraders: inactive }) + "[/color]";
+
if (traderNumber.shipTrader.trading > 0)
{
- caption += traderNumber.shipTrader.trading + " trading"
- comma = ", ";
+ var openingTradingString = sprintf(translatePlural("There is %(numberTrading)s merchant ship trading", "There are %(numberTrading)s merchant ships trading", traderNumber.shipTrader.trading), { numberTrading: traderNumber.shipTrader.trading });
+ if (inactive > 0)
+ {
+ caption = sprintf(translate("%(openingTradingString)s, and %(inactiveString)s."), {
+ openingTradingString: openingTradingString,
+ inactiveString: inactiveString
+ });
+ }
+ else
+ {
+ caption = sprintf(translate("%(openingTradingString)s."), {
+ openingTradingString: openingTradingString,
+ });
+ }
+ }
+ else
+ {
+ if (inactive > 0)
+ {
+ inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfShipTraders)s merchant ship inactive", "%(numberOfShipTraders)s merchant ships inactive", inactive), { numberOfShipTraders: inactive }) + "[/color]";
+ caption = sprintf(translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), {
+ inactiveString: inactiveString
+ });
+ }
+ // The “else” here is already handled by “if (traderNumber.shipTrader.total == 0)” above.
}
- 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);
+ if (devCommands.hidden)
+ submitChatDirectly(translate("The Developer Overlay was opened."));
+ else
+ submitChatDirectly(translate("The Developer Overlay was closed."));
// 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) + ".";
+ return sprintf(translate("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s. Shift-click to tribute %(greaterAmount)s."), {
+ resourceAmount: amount,
+ resourceType: resource,
+ playerName: "[color=\"" + playerColor + "\"]" + player.name + "[/color]",
+ greaterAmount: (amount < 500 ? 500 : amount + 500)
+ });
}
Index: ps/trunk/binaries/data/mods/public/globalscripts/l10n.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/l10n.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/globalscripts/l10n.js (revision 14954)
@@ -0,0 +1,154 @@
+var g_translations = {};
+var g_pluralTranslations = {};
+var g_translationsWithContext = {};
+var g_pluralTranslationsWithContext = {};
+
+// Translates the specified English message into the current language.
+//
+// This function relies on the g_translations cache when possible. You should use this function instead of
+// Engine.Translate() whenever you can to minimize the number of C++ calls and string conversions involved.
+function translate(message)
+{
+ var translation = g_translations[message];
+ if (!translation)
+ return g_translations[message] = Engine.Translate(message);
+ return translation;
+}
+
+
+// Translates the specified English message into the current language for the specified number.
+//
+// This function relies on the g_pluralTranslations cache when possible. You should use this function instead of
+// Engine.TranslatePlural() whenever you can to minimize the number of C++ calls and string conversions involved.
+function translatePlural(singularMessage, pluralMessage, number)
+{
+ var translation = g_pluralTranslations[singularMessage];
+ if (!translation)
+ g_pluralTranslations[singularMessage] = {};
+
+ var pluralTranslation = g_pluralTranslations[singularMessage][number];
+ if (!pluralTranslation)
+ return g_pluralTranslations[singularMessage][number] = Engine.TranslatePlural(singularMessage, pluralMessage, number);
+
+ return pluralTranslation;
+}
+
+
+// Translates the specified English message into the current language for the specified context.
+//
+// This function relies on the g_translationsWithContext cache when possible. You should use this function instead of
+// Engine.TranslateWithContext() whenever you can to minimize the number of C++ calls and string conversions involved.
+function translateWithContext(context, message)
+{
+ var translationContext = g_translationsWithContext[context];
+ if (!translationContext)
+ g_translationsWithContext[context] = {}
+
+ var translationWithContext = g_translationsWithContext[context][message];
+ if (!translationWithContext)
+ return g_translationsWithContext[context][message] = Engine.TranslateWithContext(context, message);
+
+ return translationWithContext;
+}
+
+
+// Translates the specified English message into the current language for the specified context and number.
+//
+// This function relies on the g_pluralTranslationsWithContext cache when possible. You should use this function instead of
+// Engine.TranslatePluralWithContext() whenever you can to minimize the number of C++ calls and string conversions involved.
+function translatePluralWithContext(context, singularMessage, pluralMessage, number)
+{
+ var translationContext = g_pluralTranslationsWithContext[context];
+ if (!translationContext)
+ g_pluralTranslationsWithContext[context] = {};
+
+ var translationWithContext = g_pluralTranslationsWithContext[context][singularMessage];
+ if (!translationWithContext)
+ g_pluralTranslationsWithContext[context][singularMessage] = {};
+
+ var pluralTranslationWithContext = g_pluralTranslationsWithContext[context][singularMessage][number];
+ if (!pluralTranslationWithContext)
+ return g_pluralTranslationsWithContext[context][singularMessage][number] = Engine.TranslatePluralWithContext(context, singularMessage, pluralMessage, number);
+
+ return pluralTranslationWithContext;
+}
+
+/**
+ * Translates any string value in the specified JavaScript object
+ * that is associated with a key included in the specified keys array.
+ *
+ * it accepts an object in the form of
+ *
+ * {
+ * translatedString1: "my first message",
+ * unTranslatedString1: "some English string",
+ * ignoredObject: {
+ * translatedString2: "my second message",
+ * unTranslatedString2: "some English string"
+ * },
+ * translatedObject1: {
+ * message: "my third singular message",
+ * context: "message context",
+ * },
+ * translatedObject2: {
+ * list: ["list", "of", "strings"],
+ * context: "message context",
+ * },
+ * }
+ *
+ * Together with a keys list to translate the strings and objects
+ * ["translatedString1", "translatedString2", "translatedObject1",
+ * "translatedObject2"]
+ *
+ * The result will be (f.e. in Dutch)
+ * {
+ * translatedString1: "mijn eerste bericht",
+ * unTranslatedString1: "some English string",
+ * ignoredObject: {
+ * translatedString2: "mijn tweede bericht",
+ * unTranslatedString2: "some English string"
+ * },
+ * translatedObject1: "mijn derde bericht",
+ * translatedObject2: "lijst, van, teksten",
+ * }
+ *
+ * So you see that the keys array can also contain lower-level keys,
+ * And that you can include objects in the keys array to translate
+ * them with a context, or to join a list of translations.
+ */
+function translateObjectKeys(object, keys) {
+ for (var property in object)
+ {
+ if (keys.indexOf(property) > -1)
+ {
+ if (typeof object[property] == "string")
+ object[property] = translate(object[property]);
+ else if (object[property] instanceof Object)
+ {
+ // the translation function
+ var trans = translate;
+ if (object[property].context)
+ trans = function(msg) { return translateWithContext(object[property].context, msg);};
+
+ if (object[property].message)
+ object[property] = trans(object[property].message);
+ else if (object[property].list)
+ {
+ var translatedList = object[property].list.map(trans);
+ object[property] = translatedList.join(translateWithContext("enumeration", ", "));
+ }
+ }
+ }
+ else if (object[property] instanceof Object)
+ translateObjectKeys(object[property], keys);
+ }
+}
+
+function markForTranslation(message) {
+ return message;
+}
+
+function markForTranslationWithContext(context, message) {
+ return message;
+}
+
Property changes on: ps/trunk/binaries/data/mods/public/globalscripts/l10n.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/globalscripts/sprintf.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/sprintf.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/globalscripts/sprintf.js (revision 14954)
@@ -0,0 +1,183 @@
+/**
+sprintf() for JavaScript 0.7-beta1
+http://www.diveintojavascript.com/projects/javascript-sprintf
+
+Copyright (c) Alexandru Marasteanu
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of sprintf() for JavaScript nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL Alexandru Marasteanu BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Changelog:
+2010.09.06 - 0.7-beta1
+ - features: vsprintf, support for named placeholders
+ - enhancements: format cache, reduced global namespace pollution
+
+2010.05.22 - 0.6:
+ - reverted to 0.4 and fixed the bug regarding the sign of the number 0
+ Note:
+ Thanks to Raphael Pigulla (http://www.n3rd.org/)
+ who warned me about a bug in 0.5, I discovered that the last update was
+ a regress. I appologize for that.
+
+2010.05.09 - 0.5:
+ - bug fix: 0 is now preceeded with a + sign
+ - bug fix: the sign was not at the right position on padded results (Kamal Abdali)
+ - switched from GPL to BSD license
+
+2007.10.21 - 0.4:
+ - unit test and patch (David Baird)
+
+2007.09.17 - 0.3:
+ - bug fix: no longer throws exception on empty paramenters (Hans Pufal)
+
+2007.09.11 - 0.2:
+ - feature: added argument swapping
+
+2007.04.03 - 0.1:
+ - initial release
+**/
+
+var sprintf = (function() {
+ function get_type(variable) {
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
+ }
+ function str_repeat(input, multiplier) {
+ for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
+ return output.join('');
+ }
+
+ var str_format = function() {
+ if (!str_format.cache.hasOwnProperty(arguments[0])) {
+ str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
+ }
+ return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
+ };
+
+ str_format.format = function(parse_tree, argv) {
+ var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
+ for (i = 0; i < tree_length; i++) {
+ node_type = get_type(parse_tree[i]);
+ if (node_type === 'string') {
+ output.push(parse_tree[i]);
+ }
+ else if (node_type === 'array') {
+ match = parse_tree[i]; // convenience purposes only
+ if (match[2]) { // keyword argument
+ arg = argv[cursor];
+ for (k = 0; k < match[2].length; k++) {
+ if (!arg.hasOwnProperty(match[2][k])) {
+ throw(sprintf(Engine.Translate('[sprintf] property "%s" does not exist'), match[2][k]));
+ }
+ arg = arg[match[2][k]];
+ }
+ }
+ else if (match[1]) { // positional argument (explicit)
+ arg = argv[match[1]];
+ }
+ else { // positional argument (implicit)
+ arg = argv[cursor++];
+ }
+
+ if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
+ throw(sprintf(Engine.Translate('[sprintf] expecting number but found %s'), get_type(arg)));
+ }
+ switch (match[8]) {
+ case 'b': arg = arg.toString(2); break;
+ case 'c': arg = String.fromCharCode(arg); break;
+ case 'd': arg = parseInt(arg, 10); break;
+ case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
+ case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
+ case 'o': arg = arg.toString(8); break;
+ case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
+ case 'u': arg = Math.abs(arg); break;
+ case 'x': arg = arg.toString(16); break;
+ case 'X': arg = arg.toString(16).toUpperCase(); break;
+ }
+ arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
+ pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
+ pad_length = match[6] - String(arg).length;
+ pad = match[6] ? str_repeat(pad_character, pad_length) : '';
+ output.push(match[5] ? arg + pad : pad + arg);
+ }
+ }
+ return output.join('');
+ };
+
+ str_format.cache = {};
+
+ str_format.parse = function(fmt) {
+ var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
+ while (_fmt) {
+ if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
+ parse_tree.push(match[0]);
+ }
+ else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
+ parse_tree.push('%');
+ }
+ else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
+ if (match[2]) {
+ arg_names |= 1;
+ var field_list = [], replacement_field = match[2], field_match = [];
+ if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
+ field_list.push(field_match[1]);
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
+ if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
+ field_list.push(field_match[1]);
+ }
+ else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
+ field_list.push(field_match[1]);
+ }
+ else {
+ throw(Engine.Translate('[sprintf] huh?'));
+ }
+ }
+ }
+ else {
+ throw(Engine.Translate('[sprintf] huh?'));
+ }
+ match[2] = field_list;
+ }
+ else {
+ arg_names |= 2;
+ }
+ if (arg_names === 3) {
+ throw(Engine.Translate('[sprintf] mixing positional and named placeholders is not (yet) supported'));
+ }
+ parse_tree.push(match);
+ }
+ else {
+ throw(sprintf(Engine.Translate('[sprintf] No placeholder found in the ‘%(formatString)s’ format string. Maybe you used an incorrect syntax for your placeholder?'), { formatString: _fmt } ));
+ }
+ _fmt = _fmt.substring(match[0].length);
+ }
+ return parse_tree;
+ };
+
+ return str_format;
+})();
+
+var vsprintf = function(fmt, argv) {
+ argv.unshift(fmt);
+ return sprintf.apply(null, argv);
+};
Property changes on: ps/trunk/binaries/data/mods/public/globalscripts/sprintf.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/aiconfig/aiconfig.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/aiconfig/aiconfig.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/aiconfig/aiconfig.js (revision 14954)
@@ -1,51 +1,53 @@
var g_AIs; // [ {"id": ..., "data": {"name": ..., "description": ..., ...} }, ... ]
var g_PlayerSlot;
function init(settings)
{
g_PlayerSlot = settings.playerSlot;
+
+ translateObjectKeys(settings.ais, ["name", "description"]);
g_AIs = [
- {id: "", data: {name: "None", description: "AI will be disabled for this player."}}
+ {id: "", data: {name: translateWithContext("ai", "None"), description: translate("AI will be disabled for this player.")}}
].concat(settings.ais);
var aiSelection = Engine.GetGUIObjectByName("aiSelection");
- aiSelection.list = [ ai.data.name for each (ai in g_AIs) ];
+ aiSelection.list = [ translate(ai.data.name) for each (ai in g_AIs) ];
var selected = 0;
for (var i = 0; i < g_AIs.length; ++i)
{
if (g_AIs[i].id == settings.id)
{
selected = i;
break;
}
}
aiSelection.selected = selected;
var aiDiff = Engine.GetGUIObjectByName("aiDifficulty");
- aiDiff.list = [ "Sandbox", "Easy", "Medium", "Hard", "Very Hard" ];
+ aiDiff.list = [translate("Sandbox"), translate("Easy"), translate("Medium"), translate("Hard"), translate("Very Hard")];
aiDiff.selected = settings.difficulty;
}
function selectAI(idx)
{
var id = g_AIs[idx].id;
var name = g_AIs[idx].data.name;
var description = g_AIs[idx].data.description;
Engine.GetGUIObjectByName("aiDescription").caption = description;
}
function returnAI()
{
var aiSelection = Engine.GetGUIObjectByName("aiSelection");
var idx = aiSelection.selected;
var id = g_AIs[idx].id;
var name = g_AIs[idx].data.name;
var difficulty = Engine.GetGUIObjectByName("aiDifficulty").selected;
// Pop the page before calling the callback, so the callback runs
// in the parent GUI page's context
Engine.PopGuiPageCB({"id": id, "name": name, "difficulty" : difficulty, "playerSlot" : g_PlayerSlot });
}
Index: ps/trunk/binaries/data/mods/public/gui/aiconfig/aiconfig.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/aiconfig/aiconfig.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/aiconfig/aiconfig.xml (revision 14954)
@@ -1,49 +1,51 @@
- AI Configuration
+
+ AI Configuration
+
- AI Player
+ AI PlayerselectAI(this.selected);
- AI Difficulty
+ AI Difficulty
- OK
+ OKreturnAI();
- Cancel
+ CancelEngine.PopGuiPage();
Index: ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.js (revision 14954)
@@ -1,156 +1,160 @@
var g_CivData = {};
var TEXTCOLOR = "white"
function init(settings)
{
// Initialize civ list
initCivNameList();
// TODO: Separate control for factions?
}
// Sort by culture, then by code equals culture and then by name ignoring case
function sortByCultureAndName(a, b)
{
if (a.culture < b.culture)
return -1;
if (a.culture > b.culture)
return 1;
// Same culture
// First code == culture
if (a.code == a.culture)
return -1;
if (b.code == b.culture)
return 1;
// Then alphabetically by name (ignoring case)
return sortNameIgnoreCase(a, b);
}
// Initialize the dropdown containing all the available civs
function initCivNameList()
{
// Cache map data
g_CivData = loadCivData();
var civList = [ { "name": civ.Name, "culture": civ.Culture, "code": civ.Code } for each (civ in g_CivData) ];
// Alphabetically sort the list, ignoring case
civList.sort(sortByCultureAndName);
// Indent sub-factions
var civListNames = [ ((civ.code == civ.culture)?"":" ")+civ.name for each (civ in civList) ];
var civListCodes = [ civ.code for each (civ in civList) ];
// Set civ control
var civSelection = Engine.GetGUIObjectByName("civSelection");
civSelection.list = civListNames;
civSelection.list_data = civListCodes;
civSelection.selected = 0;
}
// Function to make first char of string big
function bigFirstLetter(str, size)
{
return '[font="serif-bold-'+(size+6)+'"]' + str[0] + '[/font]' + '[font="serif-bold-'+size+'"]' + str.substring(1) + '[/font]';
}
// Heading font - bold and mixed caps
function heading(string, size)
{
var textArray = string.split(" ");
for(var i = 0; i < textArray.length; ++i)
{
var word = textArray[i];
var wordCaps = word.toUpperCase();
// Check if word is capitalized, if so assume it needs a big first letter
// Check if toLowerCase changes the character to avoid false positives from special signs
if (word.length && word[0].toLowerCase() != word[0])
textArray[i] = bigFirstLetter(wordCaps, size);
else
textArray[i] = '[font="serif-bold-'+size+'"]' + wordCaps + '[/font]'; // TODO: Would not be necessary if we could do nested tags
}
return textArray.join(" ");
}
// Called when user selects civ from dropdown
function selectCiv(code)
{
+ var escapeChars = function(str)
+ {
+ return str.replace(/\[/g, "[").replace(/\]/g, "]").replace(/"/g, """);
+ };
+
var civInfo = g_CivData[code];
if(!civInfo)
- error("Error loading civ data for \""+code+"\"");
+ error(sprintf("Error loading civ data for \"%(code)s\"", { code: code }));
// Update civ gameplay display
- Engine.GetGUIObjectByName("civGameplayHeading").caption = heading(civInfo.Name+" Gameplay", 16);
-
+ Engine.GetGUIObjectByName("civGameplayHeading").caption = heading(sprintf(translate("%(civilization)s Gameplay"), { civilization: civInfo.Name }), 16);
// Bonuses
- var bonusCaption = heading("Civilization Bonus"+(civInfo.CivBonuses.length == 1 ? "" : "es"), 12) + '\n';
+ var bonusCaption = heading(translatePlural("Civilization Bonus", "Civilization Bonuses", civInfo.CivBonuses.length), 12) + '\n';
for(var i = 0; i < civInfo.CivBonuses.length; ++i)
{
bonusCaption += '[color="' + TEXTCOLOR + '"][font="serif-bold-14"]' + civInfo.CivBonuses[i].Name + '[/font] [icon="iconInfo" tooltip="'
- + civInfo.CivBonuses[i].History + '" tooltip_style="civInfoTooltip"]\n ' + civInfo.CivBonuses[i].Description + '\n[/color]';
+ + escapeChars(civInfo.CivBonuses[i].History) + '" tooltip_style="civInfoTooltip"]\n ' + civInfo.CivBonuses[i].Description + '\n[/color]';
}
- bonusCaption += heading("Team Bonus"+(civInfo.TeamBonuses.length == 1 ? "" : "es"), 12) + '\n';
+ bonusCaption += heading(translatePlural("Team Bonus", "Team Bonuses", civInfo.TeamBonuses.length), 12) + '\n';
for(var i = 0; i < civInfo.TeamBonuses.length; ++i)
{
bonusCaption += '[color="' + TEXTCOLOR + '"][font="serif-bold-14"]' + civInfo.TeamBonuses[i].Name + '[/font] [icon="iconInfo" tooltip="'
- + civInfo.TeamBonuses[i].History + '" tooltip_style="civInfoTooltip"]\n ' + civInfo.TeamBonuses[i].Description + '\n[/color]';
+ + escapeChars(civInfo.TeamBonuses[i].History) + '" tooltip_style="civInfoTooltip"]\n ' + civInfo.TeamBonuses[i].Description + '\n[/color]';
}
Engine.GetGUIObjectByName("civBonuses").caption = bonusCaption;
// Special techs / buildings
- var techCaption = heading("Special Technologies", 12) + '\n';
+ var techCaption = heading(translate("Special Technologies"), 12) + '\n';
for(var i = 0; i < civInfo.Factions.length; ++i)
{
var faction = civInfo.Factions[i];
for(var j = 0; j < faction.Technologies.length; ++j)
{
techCaption += '[color="' + TEXTCOLOR + '"][font="serif-bold-14"]' + faction.Technologies[j].Name + '[/font] [icon="iconInfo" tooltip="'
- + faction.Technologies[j].History + '" tooltip_style="civInfoTooltip"]\n ' + faction.Technologies[j].Description + '\n[/color]';
+ + escapeChars(faction.Technologies[j].History) + '" tooltip_style="civInfoTooltip"]\n ' + faction.Technologies[j].Description + '\n[/color]';
}
}
- techCaption += heading("Special Building"+(civInfo.Structures.length == 1 ? "" : "s"), 12) + '\n';
+ techCaption += heading(translatePlural("Special Building", "Special Buildings", civInfo.Structures.length), 12) + '\n';
for(var i = 0; i < civInfo.Structures.length; ++i)
{
techCaption += '[color="' + TEXTCOLOR + '"][font="serif-bold-14"]' + civInfo.Structures[i].Name + '[/font][/color] [icon="iconInfo" tooltip="'
- + civInfo.Structures[i].History + '" tooltip_style="civInfoTooltip"]\n';
+ + escapeChars(civInfo.Structures[i].History) + '" tooltip_style="civInfoTooltip"]\n';
}
Engine.GetGUIObjectByName("civTechs").caption = techCaption;
// Heroes
- var heroCaption = heading("Heroes", 12) + '\n';
+ var heroCaption = heading(translate("Heroes"), 12) + '\n';
for(var i = 0; i < civInfo.Factions.length; ++i)
{
var faction = civInfo.Factions[i];
for(var j = 0; j < faction.Heroes.length; ++j)
{
heroCaption += '[color="' + TEXTCOLOR + '"][font="serif-bold-14"]' + faction.Heroes[j].Name + '[/font][/color] [icon="iconInfo" tooltip="'
- + faction.Heroes[j].History + '" tooltip_style="civInfoTooltip"]\n';
+ + escapeChars(faction.Heroes[j].History) + '" tooltip_style="civInfoTooltip"]\n';
}
heroCaption += '\n';
}
Engine.GetGUIObjectByName("civHeroes").caption = heroCaption;
// Update civ history display
- Engine.GetGUIObjectByName("civHistoryHeading").caption = heading("History of the " + civInfo.Name, 16);
+ Engine.GetGUIObjectByName("civHistoryHeading").caption = heading(sprintf(translate("History of the %(civilization)s"), { civilization: civInfo.Name }), 16);
Engine.GetGUIObjectByName("civHistoryText").caption = civInfo.History;
}
Index: ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.xml (revision 14954)
@@ -1,125 +1,128 @@
-
+
- Civilizations
+
+ Civilizations
+
- Civilization Selection
+ size="50%-420 10 50%-96 48">
+ Civilization SelectionselectCiv(this.list_data[this.selected]);Close
+ >
+ Close
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_civinfo.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_civinfo.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_civinfo.js (revision 14954)
@@ -1,23 +1,24 @@
/*
DESCRIPTION : Functions related to reading civ info
NOTES :
*/
// ====================================================================
function loadCivData()
{ // Load all JSON files containing civ data
var civData = {};
var civFiles = Engine.BuildDirEntList("civs/", "*.json", false);
for each (var filename in civFiles)
{ // Parse data if valid file
var data = parseJSONData(filename);
+ translateObjectKeys(data, ["Name", "Description", "History", "Special"]);
civData[data.Code] = data;
}
return civData;
}
-// ====================================================================
\ No newline at end of file
+// ====================================================================
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 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_global_object.js (revision 14954)
@@ -1,74 +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])
{
// 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
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();
+ Engine.GetGUIObjectByName("fpsCounter").caption = sprintf(translate("FPS: %(fps)s"), { fps: Engine.GetFPS() });
}
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 14954)
@@ -1,313 +1,317 @@
/*
DESCRIPTION : Generic utility functions.
NOTES :
*/
// ====================================================================
function getRandom(randomMin, randomMax)
{
// Returns a random whole number in a min..max range.
// NOTE: There should probably be an engine function for this,
// since we'd need to keep track of random seeds for replays.
var randomNum = randomMin + (randomMax-randomMin)*Math.random(); // num is random, from A to B
return Math.round(randomNum);
}
// ====================================================================
// Get list of XML files in pathname with recursion, excepting those starting with _
function getXMLFileList(pathname)
{
var files = Engine.BuildDirEntList(pathname, "*.xml", true);
var result = [];
// Get only subpath from filename and discard extension
for (var i = 0; i < files.length; ++i)
{
var file = files[i];
file = file.substring(pathname.length, file.length-4);
// Split path into directories so we can check for beginning _ character
var tokens = file.split("/");
if (tokens[tokens.length-1][0] != "_")
result.push(file);
}
return result;
}
// ====================================================================
// Get list of JSON files in pathname
function getJSONFileList(pathname)
{
var files = Engine.BuildDirEntList(pathname, "*.json", false);
// Remove the path and extension from each name, since we just want the filename
files = [ n.substring(pathname.length, n.length-5) for each (n in files) ];
return files;
}
// ====================================================================
// Parse JSON data
function parseJSONData(pathname)
{
var data = {};
var rawData = Engine.ReadFile(pathname);
if (!rawData)
{
- error("Failed to read file: "+pathname);
+ error(sprintf("Failed to read file: %(path)s", { path: pathname }));
}
else
{
try
{ // Catch nasty errors from JSON parsing
// TODO: Need more info from the parser on why it failed: line number, position, etc!
data = JSON.parse(rawData);
if (!data)
- error("Failed to parse JSON data in: "+pathname+" (check for valid JSON data)");
-
-
+ error(sprintf("Failed to parse JSON data in: %(path)s (check for valid JSON data)", { path: pathname }));
}
catch(err)
{
- error(err.toString()+": parsing JSON data in "+pathname);
+ error(sprintf("%(error)s: parsing JSON data in %(path)s", { error: err.toString(), path: pathname }));
}
}
return data;
}
// ====================================================================
// A sorting function for arrays of objects with 'name' properties, ignoring case
function sortNameIgnoreCase(x, y)
{
var lowerX = x.name.toLowerCase();
var lowerY = y.name.toLowerCase();
if (lowerX < lowerY)
return -1;
else if (lowerX > lowerY)
return 1;
else
return 0;
}
// ====================================================================
// Escape text tags and whitespace, so users can't use special formatting in their chats
// Limit string length to 256 characters
function escapeText(text)
{
if (!text)
return text;
var out = text.replace(/[\[\]]+/g,"");
out = out.replace(/\s+/g, " ");
return out.substr(0, 255);
}
// ====================================================================
function toTitleCase (string)
{
if (!string)
return string;
// Returns the title-case version of a given string.
string = string.toString();
string = string[0].toUpperCase() + string.substring(1).toLowerCase();
return string;
}
// ====================================================================
// Parse and return JSON data from file in simulation/data/*
// returns valid object or undefined on error
function parseJSONFromDataFile(filename)
{
var path = "simulation/data/"+filename;
var rawData = Engine.ReadFile(path);
if (!rawData)
- error("Failed to read file: "+path);
+ error(sprintf("Failed to read file: %(path)s", { path: path }));
try
{
// Catch nasty errors from JSON parsing
// TODO: Need more info from the parser on why it failed: line number, position, etc!
var data = JSON.parse(rawData);
return data;
}
catch(err)
{
- error(err.toString()+": parsing JSON data in "+path);
+ error(sprintf("%(error)s: parsing JSON data in %(path)s", { error: err.toString(), path: path }));
}
return undefined;
}
// ====================================================================
// Load default player data, for when it's not otherwise specified
function initPlayerDefaults()
{
var defaults = [];
var data = parseJSONFromDataFile("player_defaults.json");
if (!data || !data.PlayerData)
error("Failed to parse player defaults in player_defaults.json (check for valid JSON data)");
else
+ {
+ translateObjectKeys(data.PlayerData, ["Name"])
defaults = data.PlayerData;
+ }
return defaults;
}
// ====================================================================
// Load map size data
function initMapSizes()
{
var sizes = {
"shortNames":[],
"names":[],
"tiles": [],
"default": 0
};
var data = parseJSONFromDataFile("map_sizes.json");
if (!data || !data.Sizes)
error("Failed to parse map sizes in map_sizes.json (check for valid JSON data)");
else
{
+ translateObjectKeys(data, ["Name", "LongName"]);
for (var i = 0; i < data.Sizes.length; ++i)
{
sizes.shortNames.push(data.Sizes[i].Name);
sizes.names.push(data.Sizes[i].LongName);
sizes.tiles.push(data.Sizes[i].Tiles);
if (data.Sizes[i].Default)
sizes["default"] = i;
}
}
return sizes;
}
// ====================================================================
// Load game speed data
function initGameSpeeds()
{
var gameSpeeds = {
"names": [],
"speeds": [],
"default": 0
};
var data = parseJSONFromDataFile("game_speeds.json");
if (!data || !data.Speeds)
error("Failed to parse game speeds in game_speeds.json (check for valid JSON data)");
else
{
+ translateObjectKeys(data, ["Name"]);
for (var i = 0; i < data.Speeds.length; ++i)
{
gameSpeeds.names.push(data.Speeds[i].Name);
gameSpeeds.speeds.push(data.Speeds[i].Speed);
if (data.Speeds[i].Default)
gameSpeeds["default"] = i;
}
}
return gameSpeeds;
}
// ====================================================================
// Convert integer color values to string (for use in GUI objects)
function iColorToString(color)
{
var string = "0 0 0";
if (color && ("r" in color) && ("g" in color) && ("b" in color))
string = color.r + " " + color.g + " " + color.b;
return string;
}
// ====================================================================
/**
- * Convert time in milliseconds to hh:mm:ss string representation.
+ * Convert time in milliseconds to [hh:]mm:ss string representation.
* @param time Time period in milliseconds (integer)
* @return String representing time period
*/
function timeToString(time)
{
- var hours = Math.floor(time / 1000 / 60 / 60);
- var minutes = Math.floor(time / 1000 / 60) % 60;
- var seconds = Math.floor(time / 1000) % 60;
- return hours + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds);
+ if (time < 1000 * 60 * 60)
+ var format = translate("mm:ss");
+ else
+ var format = translate("HH:mm:ss");
+ return Engine.FormatMillisecondsIntoDateString(time, format);
}
// ====================================================================
function removeDupes(array)
{
for (var i = 0; i < array.length; i++)
{
if (array.indexOf(array[i]) < i)
{
array.splice(i, 1);
i--;
}
}
}
// ====================================================================
// "Inside-out" implementation of Fisher-Yates shuffle
function shuffleArray(source)
{
if (!source.length)
return [];
var result = [source[0]];
for (var i = 1; i < source.length; ++i)
{
var j = Math.floor(Math.random() * i);
result[i] = result[j];
result[j] = source[i];
}
return result;
}
// ====================================================================
// Filter out conflicting characters and limit the length of a given name.
// @param name Name to be filtered.
// @param stripUnicode Whether or not to remove unicode characters.
// @param stripSpaces Whether or not to remove whitespace.
function sanitizePlayerName(name, stripUnicode, stripSpaces)
{
// We delete the '[', ']' characters (GUI tags) and delete the ',' characters (player name separators) by default.
var sanitizedName = name.replace(/[\[\],]/g, "");
// Optionally strip unicode
if (stripUnicode)
sanitizedName = sanitizedName.replace(/[^\x20-\x7f]/g, "");
// Optionally strip whitespace
if (stripSpaces)
sanitizedName = sanitizedName.replace(/\s/g, "");
// Limit the length to 20 characters
return sanitizedName.substr(0,20);
}
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility_error.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_utility_error.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility_error.js (revision 14954)
@@ -1,29 +1,29 @@
/*
DESCRIPTION : Error-handling utility functions.
NOTES :
*/
// ====================================================================
function cancelOnError(msg)
{
// Delete game objects
Engine.EndGame();
// Return to pregame
Engine.SwitchGuiPage("page_pregame.xml");
// Display error dialog if message given
if (msg)
{
Engine.PushGuiPage("page_msgbox.xml", {
width: 500,
height: 200,
message: '[font="serif-bold-18"]' + msg + '[/font]',
- title: "Loading Aborted",
+ title: translate("Loading Aborted"),
mode: 2
});
}
// Reset cursor
Engine.SetCursor("arrow-default");
}
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility_list.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_utility_list.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility_list.js (revision 14954)
@@ -1,164 +1,165 @@
/*
DESCRIPTION : Functions to manipulate objects with a 'list' property
(used to handle the items in list, dropdown, etc.)
NOTES : To ensure the selection is properly updated, it is important to
use these functions and not manually access the list.
*/
// ====================================================================
// Remove the item at the given index (pos) from the given list object (objectName).
function removeItem (objectName, pos)
{
if (Engine.GetGUIObjectByName (objectName) == null)
- Engine.Console_Write ("removeItem(): " + objectName + " not found.");
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "removeItem()", object: objectName }));
var list = Engine.GetGUIObjectByName (objectName).list;
var selected = Engine.GetGUIObjectByName (objectName).selected;
list.splice(pos, 1);
Engine.GetGUIObjectByName (objectName).list = list;
// It's important that we update the selection *after*
// we've committed the changes to the list.
// Update the selected so the same element remains selected.
if (selected == pos)
{
Engine.GetGUIObjectByName (objectName).selected = -1;
}
else
if (selected > pos)
{
Engine.GetGUIObjectByName (objectName).selected = selected - 1;
}
}
// ====================================================================
// Add the item at the given index (pos) to the given list object (objectName) with the given value (value).
function addItem (objectName, pos, value)
{
if (Engine.GetGUIObjectByName (objectName) == null)
- Engine.Console_Write ("addItem(): " + objectName + " not found.");
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "addItem()", object: objectName }));
var list = Engine.GetGUIObjectByName (objectName).list;
var selected = Engine.GetGUIObjectByName (objectName).selected;
list.splice (pos, 0, value);
Engine.GetGUIObjectByName (objectName).list = list;
// It's important that we update the selection *after*
// we've committed the changes to the list.
// Update the selected so the same element remains selected.
if (selected >= pos)
{
Engine.GetGUIObjectByName (objectName).selected = selected + 1;
}
}
// ====================================================================
// Adds an element to the end of the list
function pushItem (objectName, value)
{
if (Engine.GetGUIObjectByName (objectName) == null)
- Engine.Console_Write ("pushItem(): " + objectName + " not found.");
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "pushItem()", object: objectName }));
var list = Engine.GetGUIObjectByName (objectName).list;
list.push (value);
Engine.GetGUIObjectByName (objectName).list = list;
// Point to the new item.
Engine.GetGUIObjectByName(objectName).selected = getNumItems(objectName)-1;
}
// ====================================================================
// Removes the last element
function popItem (objectName)
{
if (Engine.GetGUIObjectByName (objectName) == null)
- Engine.Console_Write ("popItem(): " + objectName + " not found.");
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "popItem()", object: objectName }));
var selected = Engine.GetGUIObjectByName (objectName).selected;
removeItem(objectName, getNumItems(objectName)-1);
if (selected == getNumItems(objectName)-1)
{
Engine.GetGUIObjectByName(objectName).selected = -1;
}
}
// ====================================================================
// Retrieves the number of elements in the list
function getNumItems (objectName)
{
if (Engine.GetGUIObjectByName (objectName) == null)
- Engine.Console_Write ("getNumItems(): " + objectName + " not found.");
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "getNumItems()", object: objectName }));
var list = Engine.GetGUIObjectByName(objectName).list;
return list.length;
}
// ====================================================================
// Retrieves the value of the item at 'pos'
function getItemValue (objectName, pos)
{
if (Engine.GetGUIObjectByName (objectName) == null)
- Engine.Console_Write ("getItemValue(): " + objectName + " not found.");
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "getItemValue()", object: objectName }));
var list = Engine.GetGUIObjectByName(objectName).list;
return list[pos];
}
// ====================================================================
// Retrieves the value of the currently selected item
function getCurrItemValue (objectName)
{
if (Engine.GetGUIObjectByName (objectName) == null)
- Engine.Console_Write ("getCurrItemValue(): " + objectName + " not found.");
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "getCurrItemValue()", object: objectName }));
if (Engine.GetGUIObjectByName(objectName).selected == -1)
return "";
var list = Engine.GetGUIObjectByName(objectName).list;
return list[Engine.GetGUIObjectByName(objectName).selected];
}
// ====================================================================
// Sets current item to a given string (which must be one of those
// already in the list).
function setCurrItemValue (objectName, string)
{
- if (Engine.GetGUIObjectByName(objectName) == null) {
- Engine.Console_Write ("setCurrItemValue(): " + objectName + " not found.");
+ if (Engine.GetGUIObjectByName(objectName) == null)
+ {
+ Engine.Console_Write (sprintf(translate("%(functionName)s: %(object)s not found."), { functionName: "setCurrItemValue()", object: objectName }));
return -1;
}
if (Engine.GetGUIObjectByName(objectName).selected == -1)
return -1; // Return -1 if nothing selected.
var list = Engine.GetGUIObjectByName(objectName).list;
// Seek through list.
for (var ctr = 0; ctr < list.length; ctr++)
{
// If we have found the string in the list,
if (list[ctr] == string)
{
// Point selected to this item.
Engine.GetGUIObjectByName(objectName).selected = ctr;
return ctr; // Return position of item.
}
}
// Return -2 if failed to find value in list.
- Engine.Console_Write ("Requested string '" + string + "' not found in " + objectName + "'s list.");
+ Engine.Console_Write (sprintf(translate("Requested string '%(string)s' not found in %(object)s's list."), { string: string, object: objectName }));
return -2;
}
// ====================================================================
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility_loadsave.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_utility_loadsave.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility_loadsave.js (revision 14954)
@@ -1,55 +1,47 @@
function sortDecreasingDate(a, b)
{
return b.metadata.time - a.metadata.time;
}
-function twoDigits(n)
-{
- return n < 10 ? "0" + n : n;
-}
-
function generateLabel(metadata, engineInfo)
{
- var t = new Date(metadata.time*1000);
- var date = t.getFullYear()+"-"+twoDigits(1+t.getMonth())+"-"+twoDigits(t.getDate());
- var time = twoDigits(t.getHours())+":"+twoDigits(t.getMinutes())+":"+twoDigits(t.getSeconds());
- var label = "["+date+" "+time+"] ";
-
+ var dateTimeString = Engine.FormatMillisecondsIntoDateString(metadata.time*1000, translate("yyyy-MM-dd HH:mm:ss"));
+ var dateString = sprintf(translate("[%(date)s]"), { date: dateTimeString });
if (engineInfo)
{
if (!hasSameVersion(metadata, engineInfo))
- label = "[color=\"red\"]" + label + "[/color]";
+ dateString = "[color=\"red\"]" + dateString + "[/color]";
else if (!hasSameMods(metadata, engineInfo))
- label = "[color=\"orange\"]" + label + "[/color]";
+ dateString = "[color=\"orange\"]" + dateString + "[/color]";
}
-
- label += metadata.initAttributes.map.replace("maps/","")
- + (metadata.description ? " - "+metadata.description : "");
- return label;
+ if (metadata.description)
+ return sprintf(translate("%(dateString)s %(map)s - %(description)s"), { dateString: dateString, map: metadata.initAttributes.map, description: metadata.description });
+ else
+ return sprintf(translate("%(dateString)s %(map)s"), { dateString: dateString, map: metadata.initAttributes.map });
}
/**
* Check the version compatibility between the saved game to be loaded and the engine
*/
function hasSameVersion(metadata, engineInfo)
{
return (metadata.version_major == engineInfo.version_major);
}
/**
* Check the mod compatibility between the saved game to be loaded and the engine
*/
function hasSameMods(metadata, engineInfo)
{
if (!metadata.mods) // only here for backwards compatibility with previous saved games
var gameMods = [];
else
var gameMods = metadata.mods;
if (gameMods.length != engineInfo.mods.length)
return false;
for (var i = 0; i < gameMods.length; ++i)
if (gameMods[i] != engineInfo.mods[i])
return false;
return true;
}
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility_music.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_utility_music.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility_music.js (revision 14954)
@@ -1,171 +1,171 @@
/*
DESCRIPTION : Audio functions (eg "pick a random sound from a list", "build a playlist") go here.
NOTES :
*/
// ====================================================================
// Quick run-down of the basic audio commands:
// Save the specified audio file to handle "s".
// s = new Sound( "audio/music/menu_track.ogg" );
// Play the sound stored at handle "s" one time (it'll be automatically freed when the playback ends):
// s.play();
// Play the sound stored at handle "s" continuously:
// s.loop();
// Close "s" and free it from memory (use in conjunction with loop()):
// s.free();
// Adjust the gain (volume) of a sound (floating point range between 0 (silent) and 1 (max volume)).
// s.setGain(value);
// ====================================================================
function newRandomSound(soundType, soundSubType, soundPrePath)
{
// Return a random audio file by category, to be assigned to a handle.
var randomSoundPath;
switch (soundType)
{
case "music":
randomSoundPath = "audio/music/"
break;
case "ambient":
randomSoundPath = "audio/ambient/" + soundPrePath + "/";
break;
case "effect":
randomSoundPath = soundPrePath + "/";
break;
default:
break;
}
// Get names of sounds (attack, command, select, hit, pain).
// or
// Get names of "peace", "menu" (theme) and "battle" tracks.
var soundArray = Engine.BuildDirEntList(randomSoundPath, "*" + soundSubType + "*", false);
if (soundArray.length == 0)
{
- Engine.Console_Write ("Failed to find sounds matching '*"+soundSubType+"*'");
+ Engine.Console_Write (sprintf("Failed to find sounds matching '*%(subtype)s*'", { soundSubType: subtype }));
return undefined;
}
// Get a random number within the sound's range.
var randomSound = getRandom (0, soundArray.length-1);
// Set name of track.
var randomFileName = soundArray[randomSound];
// Build path to random audio file.
randomSoundPath = randomFileName;
//Engine.Console_Write("Playing " + randomSoundPath + " ...");
switch (soundType)
{
case "music":
return new MusicSound(randomSoundPath);
break;
case "ambient":
return new AmbientSound(randomSoundPath);
break;
case "effect":
- Engine.Console_Write("am loading effect '*"+randomSoundPath+"*'");
+ Engine.Console_Write(sprintf("am loading effect '*%(path)s*'", { path: randomSoundPath }));
break;
default:
break;
}
return new Sound(randomSoundPath);
}
// ====================================================================
function fadeOut (soundHandle, fadeDuration)
{
// Adjust the gain of a sound until it is zero.
// The sound is automatically freed when finished fading.
soundHandle.fade(-1, 0, fadeDuration)
return true;
}
// ====================================================================
function fadeIn (soundHandle, finalGain, fadeDuration)
{
// Adjust the gain of a sound from zero up to the given value.
soundHandle.fade(0, finalGain, fadeDuration)
return true;
}
// ====================================================================
function crossFade (outHandle, inHandle, fadeDuration)
{
// Accepts two sound handles. Over the given duration,
// fades out the first while fading in the second.
// Note that it plays the in and frees the out while it's at it.
if (outHandle)
fadeOut(outHandle, fadeDuration);
if (inHandle)
{
inHandle.play();
fadeIn(inHandle, Engine.ConfigDB_GetValue("user", "sound.mastergain"), fadeDuration);
}
return true;
}
// ====================================================================
//const AMBIENT_SOUND = "audio/ambient/dayscape/day_temperate_gen_03.ogg";
//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");
// if (currentAmbient)
// {
// currentAmbient.loop();
// currentAmbient.setGain(0.8);
// }
// break;
//
// default:
// Engine.Console_Write("Unrecognized ambient type: " + type);
// break;
// }
//}
//
//function stopAmbient()
//{
// if (currentAmbient)
// {
// currentAmbient.fade(-1, 0.0, 5.0);
// currentAmbient = null;
// }
//}
//const BUTTON_SOUND = "audio/interface/ui/ui_button_longclick.ogg";
//function playButtonSound()
//{
// var buttonSound = new Sound(BUTTON_SOUND);
// buttonSound.play();
//}
Index: ps/trunk/binaries/data/mods/public/gui/common/music.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/music.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/music.js (revision 14954)
@@ -1,171 +1,171 @@
function initMusic()
{
// Probably will need to put this in a place where it won't get
// reinitialized after every match. Otherwise, it will not remember
// the current track
// Might need to use pregame for that sort of setup and move all the
// menu stuff to a main_menu page
if (!global.music)
global.music = new Music();
}
// =============================================================================
// Music class for handling music states (requires onTick)
// =============================================================================
function Music()
{
this.reference = this;
this.RELATIVE_MUSIC_PATH = "audio/music/";
this.MUSIC = {
PEACE: "peace",
BATTLE: "battle",
VICTORY: "victory",
DEFEAT: "defeat"
};
this.resetTracks();
this.states = {
OFF : 0,
MENU : 1,
PEACE : 2,
BATTLE : 3,
VICTORY :4,
DEFEAT : 5
};
this.musicGain = 0.3;
this.currentState = 0;
this.oldState = 0;
// timer for delay between tracks
this.timer = [];
this.time = Date.now();
}
Music.prototype.resetTracks = function()
{
this.tracks = {
MENU: ["Honor_Bound.ogg"],
PEACE: [],
BATTLE: ["Taiko_1.ogg", "Taiko_2.ogg"],
VICTORY : ["You_are_Victorious!.ogg"],
DEFEAT : ["Dried_Tears.ogg"]
};
};
// "reference" refers to this instance of Music (needed if called from the timer)
Music.prototype.setState = function(state)
{
this.reference.currentState = state;
this.updateState();
};
Music.prototype.updateState = function()
{
if (this.currentState != this.oldState)
{
this.oldState = this.currentState;
switch (this.currentState)
{
case this.states.OFF:
Engine.StopMusic();
break;
case this.states.MENU:
this.switchMusic(this.getRandomTrack(this.tracks.MENU), 0.0, true);
break;
case this.states.PEACE:
this.startPlayList(this.tracks.PEACE, 3.0, true);
break;
case this.states.BATTLE:
this.startPlayList(this.tracks.BATTLE, 2.0, true);
break;
case this.states.VICTORY:
this.startPlayList(this.tracks.VICTORY, 2.0, true);
break;
case this.states.DEFEAT:
this.startPlayList(this.tracks.DEFEAT, 2.0, true);
break;
default:
- warn("Music.updateState(): Unknown music state: " + this.currentState);
+ warn(sprintf("%(functionName)s: Unknown music state: %(state)s", { functionName: "Music.updateState()", state: this.currentState }));
break;
}
}
};
Music.prototype.storeTracks = function(civMusic)
{
this.resetTracks();
for each (var music in civMusic)
{
var type = undefined;
for (var i in this.MUSIC)
{
if (music.Type == this.MUSIC[i])
{
type = i;
break;
}
}
if (type === undefined)
{
- warn("Music.storeTracks(): Unrecognized music type: " + music.Type);
+ warn(sprintf("%(functionName)s: Unrecognized music type: %(musicType)s", { functionName: "Music.storeTracks()", musicType: music.Type }));
continue;
}
this.tracks[type].push(music.File);
}
};
Music.prototype.getRandomTrack = function(tracks)
{
return tracks[getRandom(0, tracks.length-1)];
};
Music.prototype.startPlayList = function(tracks, fadeInPeriod, isLooping)
{
Engine.ClearPlaylist();
for (var i in tracks)
{
Engine.AddPlaylistItem( this.RELATIVE_MUSIC_PATH + tracks[i] );
}
Engine.StartPlaylist(isLooping);
};
Music.prototype.switchMusic = function(track, fadeInPeriod, isLooping)
{
Engine.ClearPlaylist();
Engine.AddPlaylistItem( this.RELATIVE_MUSIC_PATH + track );
Engine.StartPlaylist(isLooping);
};
Music.prototype.isPlaying = function()
{
return Engine.MusicPlaying();
};
Music.prototype.start = function()
{
Engine.StartMusic();
this.setState(this.states.PEACE);
};
Music.prototype.stop = function()
{
this.setState(this.states.OFF);
};
Index: ps/trunk/binaries/data/mods/public/gui/common/network.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/network.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/network.js (revision 14954)
@@ -1,21 +1,21 @@
function getDisconnectReason(id)
{
// Must be kept in sync with source/network/NetHost.h
switch (id)
{
- case 0: return "Unknown reason";
- case 1: return "Unexpected shutdown";
- case 2: return "Incorrect network protocol version";
- case 3: return "Game has already started";
- default: return "[Invalid value "+id+"]";
+ case 0: return translate("Unknown reason");
+ case 1: return translate("Unexpected shutdown");
+ case 2: return translate("Incorrect network protocol version");
+ case 3: return translate("Game has already started");
+ default: return sprintf(translate("[Invalid value %(id)s]"), { id: id });
}
}
function reportDisconnect(reason)
{
var reasontext = getDisconnectReason(reason);
messageBox(400, 200,
- "Lost connection to the server.\n\nReason: " + reasontext + ".",
- "Disconnected", 2);
+ translate("Lost connection to the server.") + "\n\n" + sprintf(translate("Reason: %(reason)s."), { reason: reasontext }),
+ translate("Disconnected"), 2);
}
Index: ps/trunk/binaries/data/mods/public/gui/common/timer.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/timer.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/common/timer.js (revision 14954)
@@ -1,69 +1,69 @@
var g_TimerID = 0;
var g_Timers = {};
var g_Time = Date.now();
/**
* Set a timeout to call func() after 'delay' msecs.
* func: function to call
* delay: delay in ms
* Returns an id that can be passed to clearTimeout.
*/
function setTimeout(func, delay)
{
var id = ++g_TimerID;
g_Timers[id] = [g_Time + delay, func];
return id;
}
/**
* deletes a timer
* id: of the timer
*/
function clearTimeout(id)
{
delete g_Timers[id];
}
/**
* alters an function call
* id: of the timer
* func: function to call
*/
function setNewTimerFunction(id, func)
{
if (id in g_Timers)
g_Timers[id][1] = func;
}
/**
* If you want to use timers, then you must call this function regularly
* (e.g. in a Tick handler)
*/
function updateTimers()
{
g_Time = Date.now();
// Collect the timers that need to run
// (We do this in two stages to avoid deleting from the timer list while
// we're in the middle of iterating through it)
var run = [];
for (var id in g_Timers)
{
if (g_Timers[id][0] <= g_Time)
run.push(id);
}
for each (var id in run)
{
var t = g_Timers[id];
if (!t)
continue; // an earlier timer might have cancelled this one, so skip it
try {
t[1]();
} catch (e) {
var stack = e.stack.trimRight().replace(/^/mg, ' '); // indent the stack trace
- error("Error in timer: "+e+"\n"+stack+"\n");
+ error(sprintf("Error in timer: %(error)s", { error: e })+"\n"+stack+"\n");
}
delete g_Timers[id];
}
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 14954)
@@ -1,1637 +1,1661 @@
////////////////////////////////////////////////////////////////////////////////////////////////
// Constants
const DEFAULT_NETWORKED_MAP = "Acropolis 01";
const DEFAULT_OFFLINE_MAP = "Acropolis 01";
// TODO: Move these somewhere like simulation\data\game_types.json, Atlas needs them too
-const VICTORY_TEXT = ["Conquest", "Wonder", "None"];
+const VICTORY_TEXT = [translate("Conquest"), translate("Wonder"), translateWithContext("victory", "None")];
const VICTORY_DATA = ["conquest", "wonder", "endless"];
const VICTORY_DEFAULTIDX = 0;
-const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", "Unlimited"];
+const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", translate("Unlimited")];
const POPULATION_CAP_DATA = [50, 100, 150, 200, 250, 300, 10000];
const POPULATION_CAP_DEFAULTIDX = 5;
-const STARTING_RESOURCES = ["Very Low", "Low", "Medium", "High", "Very High", "Deathmatch"];
+const STARTING_RESOURCES = [translate("Very Low"), translate("Low"), translate("Medium"), translate("High"), translate("Very High"), translate("Deathmatch")];
const STARTING_RESOURCES_DATA = [100, 300, 500, 1000, 3000, 50000];
const STARTING_RESOURCES_DEFAULTIDX = 1;
// Max number of players for any map
const MAX_PLAYERS = 8;
////////////////////////////////////////////////////////////////////////////////////////////////
// Is this is a networked game, or offline
var g_IsNetworked;
// Is this user in control of game settings (i.e. is a network server, or offline player)
var g_IsController;
//Server name, if user is a server, connected to the multiplayer lobby
var g_ServerName;
// Are we currently updating the GUI in response to network messages instead of user input
// (and therefore shouldn't send further messages to the network)
var g_IsInGuiUpdate;
var g_PlayerAssignments = {};
// Default game setup attributes
var g_DefaultPlayerData = [];
var g_GameAttributes = {
settings: {}
};
var g_GameSpeeds = {};
var g_MapSizes = {};
var g_AIs = [];
var g_ChatMessages = [];
// Data caches
var g_MapData = {};
var g_CivData = {};
var g_MapFilters = [];
// Warn about the AI's nonexistent naval map support.
-var g_NavalWarning = "\n\n[font=\"serif-bold-12\"][color=\"orange\"]Warning:[/color][/font] \
-The AI does not support naval maps and may cause severe performance issues. \
-Naval maps are recommended to be played with human opponents only.";
+var g_NavalWarning = "\n\n" + sprintf(
+ translate("%(warning)s The AI does not support naval maps and may cause severe performance issues. Naval maps are recommended to be played with human opponents only."),
+ { warning: "[font=\"serif-bold-12\"][color=\"orange\"]" + translate("Warning:") + "[/color][/font]" }
+);
// To prevent the display locking up while we load the map metadata,
// we'll start with a 'loading' message and switch to the main screen in the
// tick handler
var g_LoadingState = 0; // 0 = not started, 1 = loading, 2 = loaded
////////////////////////////////////////////////////////////////////////////////////////////////
function init(attribs)
{
switch (attribs.type)
{
case "offline":
g_IsNetworked = false;
g_IsController = true;
break;
case "server":
g_IsNetworked = true;
g_IsController = true;
break;
case "client":
g_IsNetworked = true;
g_IsController = false;
break;
default:
- error("Unexpected 'type' in gamesetup init: "+attribs.type);
+ error(sprintf("Unexpected 'type' in gamesetup init: %(unexpectedType)s", { unexpectedType: attribs.type }));
}
if (attribs.serverName)
g_ServerName = attribs.serverName;
// Init the Cancel Button caption and tooltip
var cancelButton = Engine.GetGUIObjectByName("cancelGame");
if(!Engine.HasXmppClient())
{
- cancelButton.tooltip = "Return to the main menu."
+ cancelButton.tooltip = translate("Return to the main menu.");
}
else
{
- cancelButton.tooltip = "Return to the lobby."
+ cancelButton.tooltip = translate("Return to the lobby.");
}
}
// Called after the map data is loaded and cached
function initMain()
{
// Load AI list and hide deprecated AIs
g_AIs = Engine.GetAIs();
// Sort AIs by displayed name
g_AIs.sort(function (a, b) {
return a.data.name < b.data.name ? -1 : b.data.name < a.data.name ? +1 : 0;
});
// Get default player data - remove gaia
g_DefaultPlayerData = initPlayerDefaults();
g_DefaultPlayerData.shift();
for (var i = 0; i < g_DefaultPlayerData.length; i++)
g_DefaultPlayerData[i].Civ = "random";
g_GameSpeeds = initGameSpeeds();
g_MapSizes = initMapSizes();
// Init civs
initCivNameList();
// Init map types
var mapTypes = Engine.GetGUIObjectByName("mapTypeSelection");
- mapTypes.list = ["Skirmish","Random","Scenario"];
+ mapTypes.list = [translateWithContext("map", "Skirmish"), translateWithContext("map", "Random"), translate("Scenario")];
mapTypes.list_data = ["skirmish","random","scenario"];
// Setup map filters - will appear in order they are added
- addFilter("Default", function(settings) { return settings && (settings.Keywords === undefined || !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"])); });
- addFilter("Naval Maps", function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["naval"]); });
- addFilter("Demo Maps", function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["demo"]); });
- addFilter("All Maps", function(settings) { return true; });
+ addFilter("default", translate("Default"), function(settings) { return settings && (settings.Keywords === undefined || !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"])); });
+ addFilter("naval", translate("Naval Maps"), function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["naval"]); });
+ addFilter("demo", translate("Demo Maps"), function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["demo"]); });
+ addFilter("all", translate("All Maps"), function(settings) { return true; });
// Populate map filters dropdown
var mapFilters = Engine.GetGUIObjectByName("mapFilterSelection");
- mapFilters.list = getFilters();
- g_GameAttributes.mapFilter = "Default";
+ mapFilters.list = getFilterNames();
+ mapFilters.list_data = getFilterIds();
+ g_GameAttributes.mapFilter = "default";
// Setup controls for host only
if (g_IsController)
{
mapTypes.selected = 0;
mapFilters.selected = 0;
// Create a unique ID for this match, to be used for identifying the same game reports
// for the lobby.
g_GameAttributes.matchID = Engine.GetMatchID();
initMapNameList();
var numPlayersSelection = Engine.GetGUIObjectByName("numPlayersSelection");
var players = [];
for (var i = 1; i <= MAX_PLAYERS; ++i)
players.push(i);
numPlayersSelection.list = players;
numPlayersSelection.list_data = players;
numPlayersSelection.selected = MAX_PLAYERS - 1;
var gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
gameSpeed.hidden = false;
Engine.GetGUIObjectByName("gameSpeedText").hidden = true;
gameSpeed.list = g_GameSpeeds.names;
gameSpeed.list_data = g_GameSpeeds.speeds;
gameSpeed.onSelectionChange = function()
{
// Update attributes so other players can see change
if (this.selected != -1)
g_GameAttributes.gameSpeed = g_GameSpeeds.speeds[this.selected];
if (!g_IsInGuiUpdate)
updateGameAttributes();
}
gameSpeed.selected = g_GameSpeeds["default"];
var populationCaps = Engine.GetGUIObjectByName("populationCap");
populationCaps.list = POPULATION_CAP;
populationCaps.list_data = POPULATION_CAP_DATA;
populationCaps.selected = POPULATION_CAP_DEFAULTIDX;
populationCaps.onSelectionChange = function()
{
if (this.selected != -1)
g_GameAttributes.settings.PopulationCap = POPULATION_CAP_DATA[this.selected];
if (!g_IsInGuiUpdate)
updateGameAttributes();
}
var startingResourcesL = Engine.GetGUIObjectByName("startingResources");
startingResourcesL.list = STARTING_RESOURCES;
startingResourcesL.list_data = STARTING_RESOURCES_DATA;
startingResourcesL.selected = STARTING_RESOURCES_DEFAULTIDX;
startingResourcesL.onSelectionChange = function()
{
if (this.selected != -1)
g_GameAttributes.settings.StartingResources = STARTING_RESOURCES_DATA[this.selected];
if (!g_IsInGuiUpdate)
updateGameAttributes();
}
var victoryConditions = Engine.GetGUIObjectByName("victoryCondition");
victoryConditions.list = VICTORY_TEXT;
victoryConditions.list_data = VICTORY_DATA;
victoryConditions.onSelectionChange = function()
{ // Update attributes so other players can see change
if (this.selected != -1)
g_GameAttributes.settings.GameType = VICTORY_DATA[this.selected];
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
victoryConditions.selected = VICTORY_DEFAULTIDX;
var mapSize = Engine.GetGUIObjectByName("mapSize");
mapSize.list = g_MapSizes.names;
mapSize.list_data = g_MapSizes.tiles;
mapSize.onSelectionChange = function()
{
// Update attributes so other players can see change
if (this.selected != -1)
g_GameAttributes.settings.Size = g_MapSizes.tiles[this.selected];
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
mapSize.selected = 0;
Engine.GetGUIObjectByName("revealMap").onPress = function()
{
// Update attributes so other players can see change
g_GameAttributes.settings.RevealMap = this.checked;
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
Engine.GetGUIObjectByName("exploreMap").onPress = function()
{ // Update attributes so other players can see change
g_GameAttributes.settings.ExploreMap = this.checked;
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
Engine.GetGUIObjectByName("lockTeams").onPress = function()
{
// Update attributes so other players can see change
g_GameAttributes.settings.LockTeams = this.checked;
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
Engine.GetGUIObjectByName("enableCheats").onPress = function()
{
// Update attributes so other players can see change
g_GameAttributes.settings.CheatsEnabled = this.checked;
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
Engine.GetGUIObjectByName("enableRating").onPress = function()
{
// Update attributes so other players can see change
g_GameAttributes.settings.RatingEnabled = this.checked;
Engine.SetRankedGame(this.checked);
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
}
else
{
// If we're a network client, disable all the map controls
// TODO: make them look visually disabled so it's obvious why they don't work
Engine.GetGUIObjectByName("mapTypeSelection").hidden = true;
Engine.GetGUIObjectByName("mapTypeText").hidden = false;
Engine.GetGUIObjectByName("mapFilterSelection").hidden = true;
Engine.GetGUIObjectByName("mapFilterText").hidden = false;
Engine.GetGUIObjectByName("mapSelectionText").hidden = false;
Engine.GetGUIObjectByName("mapSelection").hidden = true;
Engine.GetGUIObjectByName("victoryConditionText").hidden = false;
Engine.GetGUIObjectByName("victoryCondition").hidden = true;
Engine.GetGUIObjectByName("gameSpeedText").hidden = false;
Engine.GetGUIObjectByName("gameSpeed").hidden = true;
// Disable player and game options controls
// TODO: Shouldn't players be able to choose their own assignment?
for (var i = 0; i < MAX_PLAYERS; ++i)
{
Engine.GetGUIObjectByName("playerAssignment["+i+"]").enabled = false;
Engine.GetGUIObjectByName("playerCiv["+i+"]").hidden = true;
Engine.GetGUIObjectByName("playerTeam["+i+"]").hidden = true;
}
Engine.GetGUIObjectByName("numPlayersSelection").hidden = true;
}
// Set up multiplayer/singleplayer bits:
if (!g_IsNetworked)
{
Engine.GetGUIObjectByName("chatPanel").hidden = true;
Engine.GetGUIObjectByName("enableCheats").checked = true;
g_GameAttributes.settings.CheatsEnabled = true;
}
else
{
Engine.GetGUIObjectByName("optionCheats").hidden = false;
Engine.GetGUIObjectByName("enableCheats").checked = false;
g_GameAttributes.settings.CheatsEnabled = false;
// Setup ranked option if we are connected to the lobby.
if (Engine.HasXmppClient())
{
Engine.GetGUIObjectByName("optionRating").hidden = false;
Engine.GetGUIObjectByName("enableRating").checked = Engine.IsRankedGame();
g_GameAttributes.settings.RatingEnabled = true;
}
if (g_IsController)
{
Engine.GetGUIObjectByName("enableCheatsText").hidden = true;
Engine.GetGUIObjectByName("enableCheats").hidden = false;
if (Engine.HasXmppClient())
{
Engine.GetGUIObjectByName("enableRatingText").hidden = true;
Engine.GetGUIObjectByName("enableRating").hidden = false;
}
}
}
// Settings for all possible player slots
var boxSpacing = 32;
for (var i = 0; i < MAX_PLAYERS; ++i)
{
// Space player boxes
var box = Engine.GetGUIObjectByName("playerBox["+i+"]");
var boxSize = box.size;
var h = boxSize.bottom - boxSize.top;
boxSize.top = i * boxSpacing;
boxSize.bottom = i * boxSpacing + h;
box.size = boxSize;
// Populate team dropdowns
var team = Engine.GetGUIObjectByName("playerTeam["+i+"]");
- team.list = ["None", "1", "2", "3", "4"];
+ team.list = [translateWithContext("team", "None"), "1", "2", "3", "4"];
team.list_data = [-1, 0, 1, 2, 3];
team.selected = 0;
let playerSlot = i; // declare for inner function use
team.onSelectionChange = function()
{ // Update team
if (this.selected != -1)
g_GameAttributes.settings.PlayerData[playerSlot].Team = this.selected - 1;
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
// Set events
var civ = Engine.GetGUIObjectByName("playerCiv["+i+"]");
civ.onSelectionChange = function()
{ // Update civ
if ((this.selected != -1)&&(g_GameAttributes.mapType !== "scenario"))
g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected];
if (!g_IsInGuiUpdate)
updateGameAttributes();
};
}
if (g_IsNetworked)
{
// For multiplayer, focus the chat input box by default
Engine.GetGUIObjectByName("chatInput").focus();
}
else
{
// For single-player, focus the map list by default,
// to allow easy keyboard selection of maps
Engine.GetGUIObjectByName("mapSelection").focus();
}
}
function handleNetMessage(message)
{
log("Net message: "+uneval(message));
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "disconnected":
cancelSetup();
if (Engine.HasXmppClient())
Engine.SwitchGuiPage("page_lobby.xml");
else
Engine.SwitchGuiPage("page_pregame.xml");
reportDisconnect(message.reason);
break;
default:
error("Unrecognised netstatus type "+message.status);
break;
}
break;
case "gamesetup":
if (message.data) // (the host gets undefined data on first connect, so skip that)
g_GameAttributes = message.data;
onGameAttributesChange();
break;
case "players":
// Find and report all joinings/leavings
for (var host in message.hosts)
if (! g_PlayerAssignments[host])
addChatMessage({ "type": "connect", "username": message.hosts[host].name });
for (var host in g_PlayerAssignments)
if (! message.hosts[host])
addChatMessage({ "type": "disconnect", "guid": host });
// Update the player list
g_PlayerAssignments = message.hosts;
updatePlayerList();
if (g_IsController)
sendRegisterGameStanza();
break;
case "start":
if (g_IsController && Engine.HasXmppClient())
{
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ]
Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", "));
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments,
"isController": g_IsController
});
break;
case "chat":
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
break;
default:
error("Unrecognised net message type "+message.type);
}
}
// Get display name from map data
function getMapDisplayName(map)
{
var mapData = loadMapData(map);
if (!mapData || !mapData.settings || !mapData.settings.Name)
{ // Give some msg that map format is unsupported
log("Map data missing in scenario '"+map+"' - likely unsupported format");
return map;
}
return mapData.settings.Name;
}
// Get display name from map data
function getMapPreview(map)
{
var mapData = loadMapData(map);
if (!mapData || !mapData.settings || !mapData.settings.Preview)
{ // Give some msg that map format is unsupported
return "nopreview.png";
}
return mapData.settings.Preview;
}
// Get a setting if it exists or return default
function getSetting(settings, defaults, property)
{
if (settings && (property in settings))
return settings[property];
// Use defaults
if (defaults && (property in defaults))
return defaults[property];
return undefined;
}
// Initialize the dropdowns containing all the available civs
function initCivNameList()
{
// Cache civ data
g_CivData = loadCivData();
// Extract name/code, and skip civs that are explicitly disabled
// (intended for unusable incomplete civs)
var civList = [
{ "name": civ.Name, "code": civ.Code }
for each (civ in g_CivData)
if (civ.SelectableInGameSetup !== false)
];
// Alphabetically sort the list, ignoring case
civList.sort(sortNameIgnoreCase);
var civListNames = [ civ.name for each (civ in civList) ];
var civListCodes = [ civ.code for each (civ in civList) ];
- // Add random civ to beginning of list
- civListNames.unshift("[color=\"orange\"]Random");
- civListCodes.unshift("random");
+ // Add random civ to beginning of list
+ civListNames.unshift("[color=\"orange\"]" + translateWithContext("civilization", "Random"));
+ civListCodes.unshift("random");
// Update the dropdowns
for (var i = 0; i < MAX_PLAYERS; ++i)
{
var civ = Engine.GetGUIObjectByName("playerCiv["+i+"]");
civ.list = civListNames;
civ.list_data = civListCodes;
civ.selected = 0;
}
}
// Initialise the list control containing all the available maps
function initMapNameList()
{
// Get a list of map filenames
// TODO: Should verify these are valid maps before adding to list
var mapSelectionBox = Engine.GetGUIObjectByName("mapSelection")
var mapFiles;
switch (g_GameAttributes.mapType)
{
case "scenario":
case "skirmish":
mapFiles = getXMLFileList(g_GameAttributes.mapPath);
break;
case "random":
mapFiles = getJSONFileList(g_GameAttributes.mapPath);
break;
default:
- error("initMapNameList: Unexpected map type '"+g_GameAttributes.mapType+"'");
+ error(sprintf("initMapNameList: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return;
}
// Apply map filter, if any defined
var mapList = [];
for (var i = 0; i < mapFiles.length; ++i)
{
var file = g_GameAttributes.mapPath + mapFiles[i];
var mapData = loadMapData(file);
if (g_GameAttributes.mapFilter && mapData && testFilter(g_GameAttributes.mapFilter, mapData.settings))
mapList.push({ "name": getMapDisplayName(file), "file": file });
}
// Alphabetically sort the list, ignoring case
mapList.sort(sortNameIgnoreCase);
if (g_GameAttributes.mapType == "random")
- mapList.unshift({ "name": "[color=\"orange\"]Random[/color]", "file": "random" });
+ mapList.unshift({ "name": "[color=\"orange\"]" + translateWithContext("map", "Random") + "[/color]", "file": "random" });
var mapListNames = [ map.name for each (map in mapList) ];
var mapListFiles = [ map.file for each (map in mapList) ];
// Select the default map
var selected = mapListFiles.indexOf(g_GameAttributes.map);
// Default to the first element if list is not empty and we can't find the one we searched for
if (selected == -1 && mapList.length)
{
selected = 0;
}
// Update the list control
mapSelectionBox.list = mapListNames;
mapSelectionBox.list_data = mapListFiles;
mapSelectionBox.selected = selected;
}
function loadMapData(name)
{
if (!name)
return undefined;
if (!g_MapData[name])
{
switch (g_GameAttributes.mapType)
{
case "scenario":
case "skirmish":
g_MapData[name] = Engine.LoadMapSettings(name);
+ translateObjectKeys(g_MapData[name], ["Name", "Description"]);
break;
case "random":
if (name == "random")
- g_MapData[name] = {settings : {"Name" : "Random", "Description" : "Randomly selects a map from the list"}};
+ g_MapData[name] = { settings: { "Name": translateWithContext("map", "Random"), "Description": translate("Randomly selects a map from the list") } };
else
+ {
g_MapData[name] = parseJSONData(name+".json");
+ translateObjectKeys(g_MapData[name], ["Name", "Description"]);
+ }
break;
default:
- error("loadMapData: Unexpected map type '"+g_GameAttributes.mapType+"'");
+ error(sprintf("loadMapData: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return undefined;
}
}
return g_MapData[name];
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GUI event handlers
function cancelSetup()
{
Engine.DisconnectNetworkGame();
if (Engine.HasXmppClient())
{
// Set player presence
Engine.LobbySetPlayerPresence("available");
// Unregister the game
if (g_IsController)
Engine.SendUnregisterGame();
}
}
var lastXmppClientPoll = Date.now();
function onTick()
{
// First tick happens before first render, so don't load yet
if (g_LoadingState == 0)
{
g_LoadingState++;
}
else if (g_LoadingState == 1)
{
Engine.GetGUIObjectByName("loadingWindow").hidden = true;
Engine.GetGUIObjectByName("setupWindow").hidden = false;
initMain();
g_LoadingState++;
}
else if (g_LoadingState == 2)
{
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
}
// 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();
}
}
// Called when user selects number of players
function selectNumPlayers(num)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change number of players
if (g_IsNetworked && !g_IsController)
return;
// Only meaningful for random maps
if (g_GameAttributes.mapType != "random")
return;
// Update player data
var pData = g_GameAttributes.settings.PlayerData;
if (pData && num < pData.length)
{
// Remove extra player data
g_GameAttributes.settings.PlayerData = pData.slice(0, num);
}
else
{
// Add player data from defaults
for (var i = pData.length; i < num; ++i)
g_GameAttributes.settings.PlayerData.push(g_DefaultPlayerData[i]);
}
// Some players may have lost their assigned slot
for (var guid in g_PlayerAssignments)
{
var player = g_PlayerAssignments[guid].player;
if (player > num)
{
if (g_IsNetworked)
Engine.AssignNetworkPlayer(player, "");
else
- g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
+ g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1} };
}
}
updateGameAttributes();
}
// Called when the user selects a map type from the list
function selectMapType(type)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change map type
if (g_IsNetworked && !g_IsController)
return;
// Reset game attributes
g_GameAttributes.map = "";
g_GameAttributes.mapType = type;
// Clear old map data
g_MapData = {};
// Select correct path
switch (g_GameAttributes.mapType)
{
case "scenario":
// Set a default map
// TODO: This should be remembered from the last session
g_GameAttributes.mapPath = "maps/scenarios/";
g_GameAttributes.map = g_GameAttributes.mapPath + (g_IsNetworked ? DEFAULT_NETWORKED_MAP : DEFAULT_OFFLINE_MAP);
break;
case "skirmish":
g_GameAttributes.mapPath = "maps/skirmishes/";
g_GameAttributes.settings = {
PlayerData: g_DefaultPlayerData.slice(0, 4),
Seed: Math.floor(Math.random() * 65536),
CheatsEnabled: g_GameAttributes.settings.CheatsEnabled
};
break;
case "random":
g_GameAttributes.mapPath = "maps/random/";
g_GameAttributes.settings = {
PlayerData: g_DefaultPlayerData.slice(0, 4),
Seed: Math.floor(Math.random() * 65536),
CheatsEnabled: g_GameAttributes.settings.CheatsEnabled
};
break;
default:
- error("selectMapType: Unexpected map type '"+g_GameAttributes.mapType+"'");
+ error(sprintf("selectMapType: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return;
}
initMapNameList();
updateGameAttributes();
}
-function selectMapFilter(filterName)
+function selectMapFilter(id)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change map filter
if (g_IsNetworked && !g_IsController)
return;
- g_GameAttributes.mapFilter = filterName;
+ g_GameAttributes.mapFilter = id;
initMapNameList();
updateGameAttributes();
}
// Called when the user selects a map from the list
function selectMap(name)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change map
if (g_IsNetworked && !g_IsController)
return;
// Return if we have no map
if (!name)
return;
var mapData = loadMapData(name);
var mapSettings = (mapData && mapData.settings ? deepcopy(mapData.settings) : {});
// Copy any new settings
g_GameAttributes.map = name;
g_GameAttributes.script = mapSettings.Script;
- if (mapData !== "Random")
+ if (g_GameAttributes.map !== "random")
for (var prop in mapSettings)
g_GameAttributes.settings[prop] = mapSettings[prop];
// Use default AI if the map doesn't specify any explicitly
for (var i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i)
{
if (!('AI' in g_GameAttributes.settings.PlayerData[i]))
g_GameAttributes.settings.PlayerData[i].AI = g_DefaultPlayerData[i].AI;
if (!('AIDiff' in g_GameAttributes.settings.PlayerData[i]))
g_GameAttributes.settings.PlayerData[i].AIDiff = g_DefaultPlayerData[i].AIDiff;
}
// Reset player assignments on map change
if (!g_IsNetworked)
{ // Slot 1
- g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
+ g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1} };
}
else
{
var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : g_GameAttributes.settings.PlayerData.length);
for (var guid in g_PlayerAssignments)
{ // Unassign extra players
var player = g_PlayerAssignments[guid].player;
if (player <= MAX_PLAYERS && player > numPlayers)
Engine.AssignNetworkPlayer(player, "");
}
}
updateGameAttributes();
}
function launchGame()
{
if (g_IsNetworked && !g_IsController)
{
error("Only host can start game");
return;
}
// Check that we have a map
if (!g_GameAttributes.map)
return;
if (g_GameAttributes.map == "random")
selectMap(Engine.GetGUIObjectByName("mapSelection").list_data[Math.floor(Math.random() *
(Engine.GetGUIObjectByName("mapSelection").list.length - 1)) + 1]);
g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
var numPlayers = g_GameAttributes.settings.PlayerData.length;
// Assign random civilizations to players with that choice
// (this is synchronized because we're the host)
var cultures = [];
for each (var civ in g_CivData)
if (civ.Culture !== undefined && cultures.indexOf(civ.Culture) < 0 && civ.SelectableInGameSetup !== false)
cultures.push(civ.Culture);
var allcivs = new Array(cultures.length);
for (var i = 0; i < allcivs.length; ++i)
allcivs[i] = [];
for each (var civ in g_CivData)
if (civ.Culture !== undefined && civ.SelectableInGameSetup !== false)
allcivs[cultures.indexOf(civ.Culture)].push(civ.Code);
const romanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
for (var i = 0; i < numPlayers; ++i)
{
var civs = allcivs[Math.floor(Math.random()*allcivs.length)];
if (!g_GameAttributes.settings.PlayerData[i].Civ || g_GameAttributes.settings.PlayerData[i].Civ == "random")
g_GameAttributes.settings.PlayerData[i].Civ = civs[Math.floor(Math.random()*civs.length)];
// Setting names for AI players. Check if the player is AI and the match is not a scenario
if (g_GameAttributes.mapType !== "scenario" && g_GameAttributes.settings.PlayerData[i].AI)
{
// Get the civ specific names
if (g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames !== undefined)
var civAINames = shuffleArray(g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames);
else
var civAINames = [g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].Name];
// Choose the name
var usedName = 0;
if (i < civAINames.length)
var chosenName = civAINames[i];
else
var chosenName = civAINames[Math.floor(Math.random() * civAINames.length)];
for (var j = 0; j < numPlayers; ++j)
if (g_GameAttributes.settings.PlayerData[j].Name && g_GameAttributes.settings.PlayerData[j].Name.indexOf(chosenName) !== -1)
usedName++;
// Assign civ specific names to AI players
+ chosenName = translate(chosenName);
if (usedName)
- g_GameAttributes.settings.PlayerData[i].Name = chosenName + " " + romanNumbers[usedName+1];
+ g_GameAttributes.settings.PlayerData[i].Name = sprintf(translate("%(playerName)s %(romanNumber)s"), { playerName: chosenName, romanNumber: romanNumbers[usedName+1]});
else
g_GameAttributes.settings.PlayerData[i].Name = chosenName;
}
}
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
Engine.StartNetworkGame();
}
else
{
// Find the player ID which the user has been assigned to
var numPlayers = g_GameAttributes.settings.PlayerData.length;
var playerID = -1;
for (var i = 0; i < numPlayers; ++i)
{
var assignBox = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
if (assignBox.list_data[assignBox.selected] == "local")
playerID = i+1;
}
// Remove extra player data
g_GameAttributes.settings.PlayerData = g_GameAttributes.settings.PlayerData.slice(0, numPlayers);
Engine.StartGame(g_GameAttributes, playerID);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments
});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
function onGameAttributesChange()
{
g_IsInGuiUpdate = true;
// Don't set any attributes here, just show the changes in GUI
var mapName = g_GameAttributes.map || "";
var mapSettings = g_GameAttributes.settings;
var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : MAX_PLAYERS);
// Update some controls for clients
if (!g_IsController)
{
- Engine.GetGUIObjectByName("mapFilterText").caption = g_GameAttributes.mapFilter;
+ var mapFilderSelection = Engine.GetGUIObjectByName("mapFilterText");
+ var mapFilterId = mapFilderSelection.list_data.indexOf(g_GameAttributes.mapFilter);
+ Engine.GetGUIObjectByName("mapFilterText").caption = mapFilderSelection.list[mapFilterId];
var mapTypeSelection = Engine.GetGUIObjectByName("mapTypeSelection");
var idx = mapTypeSelection.list_data.indexOf(g_GameAttributes.mapType);
Engine.GetGUIObjectByName("mapTypeText").caption = mapTypeSelection.list[idx];
var mapSelectionBox = Engine.GetGUIObjectByName("mapSelection");
mapSelectionBox.selected = mapSelectionBox.list_data.indexOf(mapName);
Engine.GetGUIObjectByName("mapSelectionText").caption = getMapDisplayName(mapName);
var populationCapBox = Engine.GetGUIObjectByName("populationCap");
populationCapBox.selected = populationCapBox.list_data.indexOf(mapSettings.PopulationCap);
var startingResourcesBox = Engine.GetGUIObjectByName("startingResources");
startingResourcesBox.selected = startingResourcesBox.list_data.indexOf(mapSettings.StartingResources);
initMapNameList();
}
// Controls common to all map types
var numPlayersSelection = Engine.GetGUIObjectByName("numPlayersSelection");
var revealMap = Engine.GetGUIObjectByName("revealMap");
var exploreMap = Engine.GetGUIObjectByName("exploreMap");
var victoryCondition = Engine.GetGUIObjectByName("victoryCondition");
var lockTeams = Engine.GetGUIObjectByName("lockTeams");
var mapSize = Engine.GetGUIObjectByName("mapSize");
var enableCheats = Engine.GetGUIObjectByName("enableCheats");
var enableRating = Engine.GetGUIObjectByName("enableRating");
var populationCap = Engine.GetGUIObjectByName("populationCap");
var startingResources = Engine.GetGUIObjectByName("startingResources");
var numPlayersText= Engine.GetGUIObjectByName("numPlayersText");
var mapSizeDesc = Engine.GetGUIObjectByName("mapSizeDesc");
var mapSizeText = Engine.GetGUIObjectByName("mapSizeText");
var revealMapText = Engine.GetGUIObjectByName("revealMapText");
var exploreMapText = Engine.GetGUIObjectByName("exploreMapText");
var victoryConditionText = Engine.GetGUIObjectByName("victoryConditionText");
var lockTeamsText = Engine.GetGUIObjectByName("lockTeamsText");
var enableCheatsText = Engine.GetGUIObjectByName("enableCheatsText");
var enableRatingText = Engine.GetGUIObjectByName("enableRatingText");
var populationCapText = Engine.GetGUIObjectByName("populationCapText");
var startingResourcesText = Engine.GetGUIObjectByName("startingResourcesText");
var gameSpeedText = Engine.GetGUIObjectByName("gameSpeedText");
var sizeIdx = (mapSettings.Size !== undefined && g_MapSizes.tiles.indexOf(mapSettings.Size) != -1 ? g_MapSizes.tiles.indexOf(mapSettings.Size) : g_MapSizes["default"]);
var speedIdx = (g_GameAttributes.gameSpeed !== undefined && g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) != -1) ? g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) : g_GameSpeeds["default"];
var victoryIdx = (mapSettings.GameType !== undefined && VICTORY_DATA.indexOf(mapSettings.GameType) != -1 ? VICTORY_DATA.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
enableCheats.checked = (g_GameAttributes.settings.CheatsEnabled === undefined || !g_GameAttributes.settings.CheatsEnabled ? false : true)
enableCheatsText.caption = (enableCheats.checked ? "Yes" : "No");
if (g_GameAttributes.settings.RatingEnabled !== undefined)
{
enableRating.checked = g_GameAttributes.settings.RatingEnabled;
Engine.SetRankedGame(enableRating.checked);
enableRatingText.caption = (enableRating.checked ? "Yes" : "No");
}
else
enableRatingText.caption = "Unknown";
gameSpeedText.caption = g_GameSpeeds.names[speedIdx];
populationCap.selected = (mapSettings.PopulationCap !== undefined && POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);
populationCapText.caption = POPULATION_CAP[populationCap.selected];
startingResources.selected = (mapSettings.StartingResources !== undefined && STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) != -1 ? STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) : STARTING_RESOURCES_DEFAULTIDX);
startingResourcesText.caption = STARTING_RESOURCES[startingResources.selected];
// Update map preview
Engine.GetGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);
// Handle map type specific logic
switch (g_GameAttributes.mapType)
{
case "random":
- mapSizeDesc.hidden = false;
+ mapSizeDesc.hidden = false;
if (g_IsController)
{
//Host
numPlayersSelection.selected = numPlayers - 1;
numPlayersSelection.hidden = false;
mapSize.hidden = false;
revealMap.hidden = false;
exploreMap.hidden = false;
victoryCondition.hidden = false;
lockTeams.hidden = false;
populationCap.hidden = false;
startingResources.hidden = false;
numPlayersText.hidden = true;
mapSizeText.hidden = true;
revealMapText.hidden = true;
exploreMapText.hidden = true;
victoryConditionText.hidden = true;
lockTeamsText.hidden = true;
populationCapText.hidden = true;
startingResourcesText.hidden = true;
-
- mapSizeText.caption = "Map size:";
+
+ mapSizeText.caption = translate("Map size:");
mapSize.selected = sizeIdx;
- revealMapText.caption = "Reveal map:";
- exploreMapText.caption = "Explore map:";
+ revealMapText.caption = translate("Reveal map:");
+ exploreMapText.caption = translate("Explored map:");
revealMap.checked = (mapSettings.RevealMap ? true : false);
exploreMap.checked = (mapSettings.ExploreMap ? true : false);
- victoryConditionText.caption = "Victory condition:";
+ victoryConditionText.caption = translate("Victory condition:");
victoryCondition.selected = victoryIdx;
- lockTeamsText.caption = "Teams locked:";
+ lockTeamsText.caption = translate("Teams locked:");
lockTeams.checked = (mapSettings.LockTeams ? true : false);
}
else
{
// Client
numPlayersText.hidden = false;
mapSizeText.hidden = false;
revealMapText.hidden = false;
exploreMapText.hidden = false;
victoryConditionText.hidden = false;
lockTeamsText.hidden = false;
populationCap.hidden = true;
populationCapText.hidden = false;
startingResources.hidden = true;
startingResourcesText.hidden = false;
numPlayersText.caption = numPlayers;
mapSizeText.caption = g_MapSizes.names[sizeIdx];
- revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
- exploreMapText.caption = (mapSettings.ExloreMap ? "Yes" : "No");
+ revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
+ exploreMapText.caption = (mapSettings.ExporeMap ? translate("Yes") : translate("No"));
victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
- lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
+ lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
}
break;
case "skirmish":
- mapSizeText.caption = "Default";
+ mapSizeText.caption = translate("Default");
numPlayersText.caption = numPlayers;
numPlayersSelection.hidden = true;
mapSize.hidden = true;
mapSizeText.hidden = true;
mapSizeDesc.hidden = true;
if (g_IsController)
{
//Host
revealMap.hidden = false;
exploreMap.hidden = false;
victoryCondition.hidden = false;
lockTeams.hidden = false;
populationCap.hidden = false;
startingResources.hidden = false;
numPlayersText.hidden = false;
revealMapText.hidden = true;
exploreMapText.hidden = true;
victoryConditionText.hidden = true;
lockTeamsText.hidden = true;
populationCapText.hidden = true;
startingResourcesText.hidden = true;
- revealMapText.caption = "Reveal map:";
- exploreMapText.caption = "Explore map:";
+ revealMapText.caption = translate("Reveal map:");
+ exploreMapText.caption = translate("Explore map:");
revealMap.checked = (mapSettings.RevealMap ? true : false);
exploreMap.checked = (mapSettings.ExploreMap ? true : false);
- victoryConditionText.caption = "Victory condition:";
+ victoryConditionText.caption = translate("Victory condition:");
victoryCondition.selected = victoryIdx;
- lockTeamsText.caption = "Teams locked:";
+ lockTeamsText.caption = translate("Teams locked:");
lockTeams.checked = (mapSettings.LockTeams ? true : false);
}
else
{
// Client
numPlayersText.hidden = false;
revealMapText.hidden = false;
exploreMapText.hidden = false;
victoryConditionText.hidden = false;
lockTeamsText.hidden = false;
populationCap.hidden = true;
populationCapText.hidden = false;
startingResources.hidden = true;
startingResourcesText.hidden = false;
- revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
- exploreMapText.caption = (mapSettings.ExploreMap ? "Yes" : "No");
+ revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
+ exploreMapText.caption = (mapSettings.ExploreMap ? translate("Yes") : translate("No"));
victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
- lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
+ lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
}
break;
case "scenario":
// For scenario just reflect settings for the current map
numPlayersSelection.hidden = true;
mapSize.hidden = true;
revealMap.hidden = true;
exploreMap.hidden = true;
victoryCondition.hidden = true;
lockTeams.hidden = true;
numPlayersText.hidden = false;
mapSizeText.hidden = true;
mapSizeDesc.hidden = true;
revealMapText.hidden = false;
exploreMapText.hidden = false;
victoryConditionText.hidden = false;
lockTeamsText.hidden = false;
populationCap.hidden = true;
populationCapText.hidden = false;
startingResources.hidden = true;
startingResourcesText.hidden = false;
numPlayersText.caption = numPlayers;
- mapSizeText.caption = "Default";
- revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
- exploreMapText.caption = (mapSettings.ExploreMap ? "Yes" : "No");
+ mapSizeText.caption = translate("Default");
+ revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
+ exploreMapText.caption = (mapSettings.ExploreMap ? translate("Yes") : translate("No"));
victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
- lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
+ lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
Engine.GetGUIObjectByName("populationCap").selected = POPULATION_CAP_DEFAULTIDX;
break;
default:
- error("onGameAttributesChange: Unexpected map type '"+g_GameAttributes.mapType+"'");
+ error(sprintf("onGameAttributesChange: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return;
}
// Display map name
Engine.GetGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);
// Load the description from the map file, if there is one
- var description = mapSettings.Description || "Sorry, no description available.";
+ var description = mapSettings.Description || translate("Sorry, no description available.");
- if (g_GameAttributes.mapFilter == "Naval Maps")
+ if (g_GameAttributes.mapFilter == "naval")
description += g_NavalWarning;
// Describe the number of players
- var playerString = numPlayers + " " + (numPlayers == 1 ? "player" : "players") + ". ";
+ var playerString = sprintf(translatePlural("%(number)s player. %(description)s", "%(number)s players. %(description)s", numPlayers), { number: numPlayers, description: description });
for (var i = 0; i < MAX_PLAYERS; ++i)
{
// Show only needed player slots
Engine.GetGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers);
// Show player data or defaults as necessary
if (i >= numPlayers)
continue;
var pName = Engine.GetGUIObjectByName("playerName["+i+"]");
var pCiv = Engine.GetGUIObjectByName("playerCiv["+i+"]");
var pCivText = Engine.GetGUIObjectByName("playerCivText["+i+"]");
var pTeam = Engine.GetGUIObjectByName("playerTeam["+i+"]");
var pTeamText = Engine.GetGUIObjectByName("playerTeamText["+i+"]");
var pColor = Engine.GetGUIObjectByName("playerColour["+i+"]");
// Player data / defaults
var pData = mapSettings.PlayerData ? mapSettings.PlayerData[i] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[i] : {};
// Common to all game types
var color = iColorToString(getSetting(pData, pDefs, "Colour"));
pColor.sprite = "colour:"+color+" 100";
pName.caption = getSetting(pData, pDefs, "Name");
var team = getSetting(pData, pDefs, "Team");
var civ = getSetting(pData, pDefs, "Civ");
// For clients or scenarios, hide some player dropdowns
if (!g_IsController || g_GameAttributes.mapType == "scenario")
{
pCivText.hidden = false;
pCiv.hidden = true;
pTeamText.hidden = false;
pTeam.hidden = true;
// Set text values
if (civ == "random")
- pCivText.caption = "[color=\"orange\"]Random";
+ pCivText.caption = "[color=\"orange\"]" + translateWithContext("civilization", "Random");
else
pCivText.caption = g_CivData[civ].Name;
pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
}
else if (g_GameAttributes.mapType != "scenario")
{
pCivText.hidden = true;
pCiv.hidden = false;
pTeamText.hidden = true;
pTeam.hidden = false;
// Set dropdown values
pCiv.selected = (civ ? pCiv.list_data.indexOf(civ) : 0);
pTeam.selected = (team !== undefined && team >= 0) ? team+1 : 0;
}
}
- Engine.GetGUIObjectByName("mapInfoDescription").caption = playerString + description;
+ Engine.GetGUIObjectByName("mapInfoDescription").caption = playerString;
g_IsInGuiUpdate = false;
// Game attributes include AI settings, so update the player list
updatePlayerList();
}
function updateGameAttributes()
{
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
if (g_IsController && g_LoadingState >= 2)
sendRegisterGameStanza();
}
else
{
onGameAttributesChange();
}
}
function AIConfigCallback(ai)
{
g_GameAttributes.settings.PlayerData[ai.playerSlot].AI = ai.id;
g_GameAttributes.settings.PlayerData[ai.playerSlot].AIDiff = ai.difficulty;
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
else
updatePlayerList();
}
function updatePlayerList()
{
g_IsInGuiUpdate = true;
var hostNameList = [];
var hostGuidList = [];
var assignments = [];
var aiAssignments = {};
var noAssignment;
var assignedCount = 0;
for (var guid in g_PlayerAssignments)
{
var name = g_PlayerAssignments[guid].name;
var hostID = hostNameList.length;
var player = g_PlayerAssignments[guid].player;
hostNameList.push(name);
hostGuidList.push(guid);
assignments[player] = hostID;
if (player != -1)
assignedCount++;
}
// Only enable start button if we have enough assigned players
if (g_IsController)
Engine.GetGUIObjectByName("startGame").enabled = (assignedCount > 0);
for each (var ai in g_AIs)
{
if (ai.data.hidden)
{
// If the map uses a hidden AI then don't hide it
var usedByMap = false;
for (var i = 0; i < MAX_PLAYERS; ++i)
if (i < g_GameAttributes.settings.PlayerData.length &&
g_GameAttributes.settings.PlayerData[i].AI == ai.id)
{
usedByMap = true;
break;
}
if (!usedByMap)
continue;
}
// Give AI a different color so it stands out
aiAssignments[ai.id] = hostNameList.length;
- hostNameList.push("[color=\"70 150 70 255\"]AI: " + ai.data.name);
+ hostNameList.push("[color=\"70 150 70 255\"]" + sprintf(translate("AI: %(ai)s"), { ai: translate(ai.data.name) }));
hostGuidList.push("ai:" + ai.id);
}
noAssignment = hostNameList.length;
- hostNameList.push("[color=\"140 140 140 255\"]Unassigned");
+ hostNameList.push("[color=\"140 140 140 255\"]" + translate("Unassigned"));
hostGuidList.push("");
for (var i = 0; i < MAX_PLAYERS; ++i)
{
let playerSlot = i;
let playerID = i+1; // we don't show Gaia, so first slot is ID 1
var selection = assignments[playerID];
var configButton = Engine.GetGUIObjectByName("playerConfig["+i+"]");
configButton.hidden = true;
// Look for valid player slots
if (playerSlot >= g_GameAttributes.settings.PlayerData.length)
continue;
// If no human is assigned, look for an AI instead
if (selection === undefined)
{
var aiId = g_GameAttributes.settings.PlayerData[playerSlot].AI;
if (aiId)
{
// Check for a valid AI
if (aiId in aiAssignments)
selection = aiAssignments[aiId];
else
- warn("AI \""+aiId+"\" not present. Defaulting to unassigned.");
+ warn(sprintf("AI \"%(id)s\" not present. Defaulting to unassigned.", { id: aiId }));
}
if (!selection)
selection = noAssignment;
// Since no human is assigned, show the AI config button
if (g_IsController)
{
configButton.hidden = false;
configButton.onpress = function()
{
Engine.PushGuiPage("page_aiconfig.xml", {
ais: g_AIs,
id: g_GameAttributes.settings.PlayerData[playerSlot].AI,
difficulty: g_GameAttributes.settings.PlayerData[playerSlot].AIDiff,
callback: "AIConfigCallback",
playerSlot: playerSlot // required by the callback function
});
};
}
}
// There was a human, so make sure we don't have any AI left
// over in their slot, if we're in charge of the attributes
else if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
{
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
}
var assignBox = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
assignBox.list = hostNameList;
assignBox.list_data = hostGuidList;
if (assignBox.selected != selection)
assignBox.selected = selection;
if (g_IsNetworked && g_IsController)
{
assignBox.onselectionchange = function ()
{
if (!g_IsInGuiUpdate)
{
var guid = hostGuidList[this.selected];
if (guid == "")
{
// Unassign any host from this player slot
Engine.AssignNetworkPlayer(playerID, "");
// Remove AI from this player slot
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
}
else if (guid.substr(0, 3) == "ai:")
{
// Unassign any host from this player slot
Engine.AssignNetworkPlayer(playerID, "");
// Set the AI for this player slot
g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
}
else
swapPlayers(guid, playerSlot);
Engine.SetNetworkGameAttributes(g_GameAttributes);
}
};
}
else if (!g_IsNetworked)
{
assignBox.onselectionchange = function ()
{
if (!g_IsInGuiUpdate)
{
var guid = hostGuidList[this.selected];
if (guid == "")
{
// Remove AI from this player slot
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
}
else if (guid.substr(0, 3) == "ai:")
{
// Set the AI for this player slot
g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
}
else
swapPlayers(guid, playerSlot);
updatePlayerList();
}
};
}
}
g_IsInGuiUpdate = false;
}
function swapPlayers(guid, newSlot)
{
// Player slots are indexed from 0 as Gaia is omitted.
var newPlayerID = newSlot + 1;
var playerID = g_PlayerAssignments[guid].player;
// Attempt to swap the player or AI occupying the target slot,
// if any, into the slot this player is currently in.
if (playerID != -1)
{
for (var i in g_PlayerAssignments)
{
// Move the player in the destination slot into the current slot.
if (g_PlayerAssignments[i].player == newPlayerID)
{
if (g_IsNetworked)
Engine.AssignNetworkPlayer(playerID, i);
else
g_PlayerAssignments[i].player = playerID;
break;
}
}
// Transfer the AI from the target slot to the current slot.
g_GameAttributes.settings.PlayerData[playerID - 1].AI = g_GameAttributes.settings.PlayerData[newSlot].AI;
}
if (g_IsNetworked)
Engine.AssignNetworkPlayer(newPlayerID, guid);
else
g_PlayerAssignments[guid].player = newPlayerID;
// Remove AI from this player slot
g_GameAttributes.settings.PlayerData[newSlot].AI = "";
}
function submitChatInput()
{
var input = Engine.GetGUIObjectByName("chatInput");
var text = input.caption;
if (text.length)
{
Engine.SendNetworkChat(text);
input.caption = "";
}
}
function addChatMessage(msg)
{
var username = escapeText(msg.username || g_PlayerAssignments[msg.guid].name);
var message = escapeText(msg.text);
// TODO: Maybe host should have distinct font/color?
var color = "white";
if (g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != -1)
{ // Valid player who has been assigned - get player colour
var player = g_PlayerAssignments[msg.guid].player - 1;
var mapName = g_GameAttributes.map;
var mapData = loadMapData(mapName);
var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
var pData = mapSettings.PlayerData ? mapSettings.PlayerData[player] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[player] : {};
color = iColorToString(getSetting(pData, pDefs, "Colour"));
}
var formatted;
switch (msg.type)
{
case "connect":
- formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has joined[/color]';
+ var formattedUsername = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
+ formatted = '[color="gold"]' + sprintf(translate("%(username)s has joined"), { username: formattedUsername });
break;
case "disconnect":
- formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has left[/color]';
+ var formattedUsername = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font][color="gold"]'
+ formatted = '[color="gold"]' + sprintf(translate("%(username)s has left"), { username: formattedUsername });
break;
case "message":
- formatted = '[font="serif-bold-13"]<[color="'+ color +'"]' + username + '[/color]>[/font] ' + message;
+ var formattedUsername = '[color="'+ color +'"]' + username + '[/color]'
+ var formattedUsernamePrefix = '[font="serif-bold-13"]' + sprintf(translate("<%(username)s>"), { username: formattedUsername }) + '[/font]'
+ formatted = sprintf(translate("%(username)s %(message)s"), { username: formattedUsernamePrefix, message: message });
break;
default:
- error("Invalid chat message '" + uneval(msg) + "'");
+ error(sprintf("Invalid chat message '%(message)s'", { message: uneval(msg) }));
return;
}
g_ChatMessages.push(formatted);
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
function toggleMoreOptions()
{
Engine.GetGUIObjectByName("moreOptionsFade").hidden = !Engine.GetGUIObjectByName("moreOptionsFade").hidden;
Engine.GetGUIObjectByName("moreOptions").hidden = !Engine.GetGUIObjectByName("moreOptions").hidden;
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Basic map filters API
// Add a new map list filter
-function addFilter(name, filterFunc)
+function addFilter(id, name, filterFunc)
{
if (filterFunc instanceof Object)
{ // Basic validity test
var newFilter = {};
+ newFilter.id = id;
newFilter.name = name;
newFilter.filter = filterFunc;
g_MapFilters.push(newFilter);
}
else
{
- error("Invalid map filter: "+name);
+ error(sprintf("Invalid map filter: %(name)s", { name: name }));
}
}
+// Get array of map filter IDs
+function getFilterIds()
+{
+ var filters = [];
+ for (var i = 0; i < g_MapFilters.length; ++i)
+ filters.push(g_MapFilters[i].id);
+
+ return filters;
+}
+
// Get array of map filter names
-function getFilters()
+function getFilterNames()
{
var filters = [];
for (var i = 0; i < g_MapFilters.length; ++i)
filters.push(g_MapFilters[i].name);
return filters;
}
// Test map filter on given map settings object
-function testFilter(name, mapSettings)
+function testFilter(id, mapSettings)
{
for (var i = 0; i < g_MapFilters.length; ++i)
- if (g_MapFilters[i].name == name)
+ if (g_MapFilters[i].id == id)
return g_MapFilters[i].filter(mapSettings);
- error("Invalid map filter: "+name);
+ error(sprintf("Invalid map filter: %(id)s", { id: id }));
return false;
}
// Test an array of keywords against a match array using AND logic
function keywordTestAND(keywords, matches)
{
if (!keywords || !matches)
return false;
for (var m = 0; m < matches.length; ++m)
if (keywords.indexOf(matches[m]) == -1)
return false;
return true;
}
// Test an array of keywords against a match array using OR logic
function keywordTestOR(keywords, matches)
{
if (!keywords || !matches)
return false;
for (var m = 0; m < matches.length; ++m)
if (keywords.indexOf(matches[m]) != -1)
return true;
return false;
}
function sendRegisterGameStanza()
{
if (!Engine.HasXmppClient())
return;
var selectedMapSize = Engine.GetGUIObjectByName("mapSize").selected;
var selectedVictoryCondition = Engine.GetGUIObjectByName("victoryCondition").selected;
// Map sizes only apply to random maps.
if (g_GameAttributes.mapType == "random")
var mapSize = Engine.GetGUIObjectByName("mapSize").list[selectedMapSize];
else
var mapSize = "Default";
var victoryCondition = Engine.GetGUIObjectByName("victoryCondition").list[selectedVictoryCondition];
var numberOfPlayers = Object.keys(g_PlayerAssignments).length;
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ].join(", ");
var nbp = numberOfPlayers ? numberOfPlayers : 1;
var tnbp = g_GameAttributes.settings.PlayerData.length;
var gameData = {
"name":g_ServerName,
"mapName":g_GameAttributes.map,
"niceMapName":getMapDisplayName(g_GameAttributes.map),
"mapSize":mapSize,
"mapType":g_GameAttributes.mapType,
"victoryCondition":victoryCondition,
"nbp":nbp,
"tnbp":tnbp,
"players":players
};
Engine.SendRegisterGame(gameData);
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 14954)
@@ -1,312 +1,342 @@
- Match Setup
+ Match Setup
- Loading
+ Loading
- Loading map data. Please wait...
+ Loading map data. Please wait...
onTick();
- Number of players:
+
+ Number of players:
+
+ tooltip_style="onscreenToolTip">
+ Select number of players.selectNumPlayers(this.list_data[this.selected]);
- Player Name
- Player Placement
- Civilization
+
+ Player Name
+
+
+ Player Placement
+
+
+ Civilization
+
+ View civilization info
- Team
+
+ Team
+
-
+
+ Select player.
+ Settings
-
+ >
+ Settings
+ Configure AI settings.
+
+
+ Select player's civilization.
+
-
+
+ Select player's team.
+
- Match Type:
- Map Filter:
- Select Map:
- Map Size:
+
+ Match Type:
+
+
+ Map Filter:
+
+
+ Select Map:
+
+
+ Map Size:
+
+ tooltip_style="onscreenToolTip">
+ Select a map type.selectMapType(this.list_data[this.selected]);
- selectMapFilter(this.list[this.selected]);
+ tooltip_style="onscreenToolTip">
+ Select a map filter.
+ selectMapFilter(this.list_data[this.selected]);
+ tooltip_style="onscreenToolTip">
+ Select a map to play on.selectMap(this.list_data[this.selected]);
-
-
-
+
+
+ Select map size. (Larger sizes may reduce performance.)
+
+
submitChatInput();
- Send
+ SendsubmitChatInput();[Tooltip text]
+ >
+ [Tooltip text]
+
- Start game!
+ Start game!
+ Start a new game with the current settings.launchGame();
+ Back
- More Options
+ More Options
+ See more game optionstoggleMoreOptions();More OptionsGame Speed:Victory Condition:Population Cap:Starting Resources:Reveal Map:Explore Map:Teams Locked:Cheats:Rated Game:
- OK
+ OK
+ Close more game options windowtoggleMoreOptions();
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js (revision 14954)
@@ -1,237 +1,242 @@
var g_IsConnecting = false;
var g_GameType; // "server" or "client"
var g_ServerName = "";
var g_IsRejoining = false;
var g_GameAttributes; // used when rejoining
var g_PlayerAssignments; // used when rejoining
var g_userRating; // player rating
function init(attribs)
{
g_userRating = attribs.rating;
switch (attribs.multiplayerGameType)
{
case "join":
if(Engine.HasXmppClient())
{
if (startJoin(attribs.name, attribs.ip))
switchSetupPage("pageJoin", "pageConnecting");
}
else
{
Engine.GetGUIObjectByName("pageJoin").hidden = false;
Engine.GetGUIObjectByName("pageHost").hidden = true;
}
break;
case "host":
Engine.GetGUIObjectByName("pageJoin").hidden = true;
Engine.GetGUIObjectByName("pageHost").hidden = false;
if(Engine.HasXmppClient())
{
Engine.GetGUIObjectByName("hostServerNameWrapper").hidden = false;
Engine.GetGUIObjectByName("hostPlayerName").caption = attribs.name;
- Engine.GetGUIObjectByName("hostServerName").caption = attribs.name + "'s game";
+ Engine.GetGUIObjectByName("hostServerName").caption = sprintf(translate("%(name)s's game"), { name: attribs.name });
}
else
Engine.GetGUIObjectByName("hostPlayerNameWrapper").hidden = false;
break;
default:
- error("Unrecognised multiplayer game type : " + attribs.multiplayerGameType);
+ error(sprintf("Unrecognised multiplayer game type: %(gameType)s", { gameType: multiplayerGameType }));
break;
}
}
function cancelSetup()
{
if (g_IsConnecting)
Engine.DisconnectNetworkGame();
// Set player lobby presence
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("available");
Engine.PopGuiPage();
}
function startConnectionStatus(type)
{
g_GameType = type;
g_IsConnecting = true;
g_IsRejoining = false;
- Engine.GetGUIObjectByName("connectionStatus").caption = "Connecting to server...";
+ Engine.GetGUIObjectByName("connectionStatus").caption = translate("Connecting to server...");
}
function onTick()
{
if (!g_IsConnecting)
return;
pollAndHandleNetworkClient();
}
function pollAndHandleNetworkClient()
{
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
- log("Net message: "+uneval(message));
+ log(sprintf(translate("Net message: %(message)s"), { message: uneval(message) }));
// If we're rejoining an active game, we don't want to actually display
// the game setup screen, so perform similar processing to gamesetup.js
// in this screen
if (g_IsRejoining)
{
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "disconnected":
cancelSetup();
reportDisconnect(message.reason);
return;
default:
- error("Unrecognised netstatus type "+message.status);
+ error(sprintf("Unrecognised netstatus type %(netType)s", { netType: message.status }));
break;
}
break;
case "gamesetup":
g_GameAttributes = message.data;
break;
case "players":
g_PlayerAssignments = message.hosts;
break;
case "start":
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : true,
"playerAssignments": g_PlayerAssignments
});
break;
case "chat":
// Ignore, since we have nowhere to display chat messages
break;
default:
- error("Unrecognised net message type "+message.type);
+ error(sprintf("Unrecognised net message type %(messageType)s", { messageType: message.type }));
}
}
else
{
// Not rejoining - just trying to connect to server
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "connected":
- Engine.GetGUIObjectByName("connectionStatus").caption = "Registering with server...";
+ Engine.GetGUIObjectByName("connectionStatus").caption = translate("Registering with server...");
break;
case "authenticated":
if (message.rejoining)
{
- Engine.GetGUIObjectByName("connectionStatus").caption = "Game has already started - rejoining...";
+ Engine.GetGUIObjectByName("connectionStatus").caption = translate("Game has already started, rejoining...");
g_IsRejoining = true;
return; // we'll process the game setup messages in the next tick
}
else
{
Engine.SwitchGuiPage("page_gamesetup.xml", { "type": g_GameType, "serverName": g_ServerName });
return; // don't process any more messages - leave them for the game GUI loop
}
case "disconnected":
cancelSetup();
reportDisconnect(message.reason);
return;
default:
- error("Unrecognised netstatus type "+message.status);
+ error(sprintf("Unrecognised netstatus type %(netType)s", { netType: message.status }));
break;
}
break;
default:
- error("Unrecognised net message type "+message.type);
+ error(sprintf("Unrecognised net message type %(messageType)s", { messageType: message.type }));
break;
}
}
}
}
function switchSetupPage(oldpage, newpage)
{
Engine.GetGUIObjectByName(oldpage).hidden = true;
Engine.GetGUIObjectByName(newpage).hidden = false;
}
function startHost(playername, servername)
{
// Disallow identically named games in the multiplayer lobby
if (Engine.HasXmppClient())
{
for each (var g in Engine.GetGameList())
{
if (g.name === servername)
{
- Engine.GetGUIObjectByName("hostFeedback").caption = "Game name already in use.";
+ Engine.GetGUIObjectByName("hostFeedback").caption = translate("Game name already in use.");
return false;
}
}
}
try
{
if(Engine.HasXmppClient())
Engine.StartNetworkHost(playername + " (" + g_userRating + ")");
else
Engine.StartNetworkHost(playername);
}
catch (e)
{
cancelSetup();
messageBox(400, 200,
- "Cannot host game: " + e.message + ".",
+ sprintf("Cannot host game: %(message)s.", { message: e.message }),
"Error", 2);
return false;
}
startConnectionStatus("server");
g_ServerName = servername;
// Set player lobby presence
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
return true;
}
function startJoin(playername, ip)
{
try
{
if(Engine.HasXmppClient())
Engine.StartNetworkJoin(playername + " (" + g_userRating + ")", ip);
else
Engine.StartNetworkJoin(playername, ip);
}
catch (e)
{
cancelSetup();
messageBox(400, 200,
- "Cannot join game: " + e.message + ".",
+ sprintf("Cannot join game: %(message)s.", { message: e.message }),
"Error", 2);
return false;
}
startConnectionStatus("client");
// Set player lobby presence
if (Engine.HasXmppClient())
Engine.LobbySetPlayerPresence("playing");
return true;
}
+
+function getDefaultGameName()
+{
+ return sprintf(translate("%(playername)s's game"), { playername: Engine.ConfigDB_GetValue("user", "playername")});
+}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.xml (revision 14954)
@@ -1,121 +1,121 @@
onTick();
- Multiplayer
+ Multiplayer
- Joining an existing game.
+ Joining an existing game.
- Player name:
+ Player name:
this.caption = Engine.ConfigDB_GetValue("user", "playername");
- Server Hostname or IP:
+ Server Hostname or IP:
this.caption = Engine.ConfigDB_GetValue("user", "multiplayerserver")
3 100%-33 103 100%-3
- Continue
+ Continue
var joinPlayerName = Engine.GetGUIObjectByName("joinPlayerName").caption;
var joinServer = Engine.GetGUIObjectByName("joinServer").caption;
Engine.ConfigDB_CreateValue("user", "playername", joinPlayerName);
Engine.ConfigDB_CreateValue("user", "multiplayerserver", joinServer);
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
if (startJoin(joinPlayerName, joinServer))
{
switchSetupPage("pageJoin", "pageConnecting");
}
- Set up your server to host.
+ Set up your server to host.
- Player name:
+ Player name:
this.caption = Engine.ConfigDB_GetValue("user", "playername");
- Server name:
+ Server name:
- this.caption = Engine.ConfigDB_GetValue("user", "playername") + "'s game";
+ this.caption = getDefaultGameName();
- Continue
+ Continue
var hostPlayerName = Engine.GetGUIObjectByName("hostPlayerName").caption;
Engine.ConfigDB_CreateValue("user", "playername", hostPlayerName);
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
if (startHost(hostPlayerName, Engine.GetGUIObjectByName("hostServerName").caption))
switchSetupPage("pageHost", "pageConnecting");
- Cancel
+ CancelcancelSetup();
- [Connection status]
+ [Connection status]
Index: ps/trunk/binaries/data/mods/public/gui/loading/loading.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/loading/loading.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/loading/loading.js (revision 14954)
@@ -1,116 +1,116 @@
var g_Data;
const END_PIECE_WIDTH = 16;
function init(data)
{
g_Data = data;
// Set to "hourglass" cursor.
Engine.SetCursor("cursor-wait");
// Get tip image and corresponding tip text
var tipTextLoadingArray = Engine.BuildDirEntList("gui/text/tips/", "*.txt", false);
if (tipTextLoadingArray.length > 0)
{
// Set tip text
var tipTextFilePath = tipTextLoadingArray[getRandom (0, tipTextLoadingArray.length-1)];
- var tipText = Engine.ReadFile(tipTextFilePath);
+ var tipText = Engine.TranslateLines(Engine.ReadFile(tipTextFilePath));
if (tipText)
{
var index = tipText.indexOf("\n");
var tipTextTitle = tipText.substring(0, index);
var tipTextMessage = tipText.substring(index);
Engine.GetGUIObjectByName("tipTitle").caption = tipTextTitle? tipTextTitle : "";
Engine.GetGUIObjectByName("tipText").caption = tipTextMessage? tipTextMessage : "";
}
// Set tip image
var fileName = tipTextFilePath.substring(tipTextFilePath.lastIndexOf("/")+1).replace(".txt", ".png");
var tipImageFilePath = "loading/tips/" + fileName;
var sprite = "stretched:" + tipImageFilePath;
Engine.GetGUIObjectByName("tipImage").sprite = sprite? sprite : "";
}
else
{
error("Failed to find any matching tips for the loading screen.")
}
// janwas: main loop now sets progress / description, but that won't
// happen until the first timeslice completes, so set initial values.
var loadingMapName = Engine.GetGUIObjectByName ("loadingMapName");
if (data)
{
var mapName = data.attribs.settings.Name;
switch (data.attribs.mapType)
{
case "skirmish":
case "scenario":
- loadingMapName.caption = "Loading \"" + mapName + "\"";
+ loadingMapName.caption = sprintf(translate("Loading \"%(map)s\""), {map: mapName});
break;
case "random":
- loadingMapName.caption = "Generating \"" + mapName + "\"";
+ loadingMapName.caption = sprintf(translate("Generating \"%(map)s\""), {map: mapName});
break;
default:
- error("Unknown map type: " + data.attribs.mapType);
+ error(sprintf("Unknown map type: %(mapType)s", { mapType: data.attribs.mapType }));
}
}
Engine.GetGUIObjectByName("progressText").caption = "";
Engine.GetGUIObjectByName("progressbar").caption = 0;
// Pick a random quote of the day (each line is a separate tip).
var quoteArray = Engine.ReadFileLines("gui/text/quotes.txt");
- Engine.GetGUIObjectByName("quoteText").caption = quoteArray[getRandom(0, quoteArray.length-1)];
+ Engine.GetGUIObjectByName("quoteText").caption = translate(quoteArray[getRandom(0, quoteArray.length-1)]);
}
// ====================================================================
function displayProgress()
{
// Make the progessbar finish a little early so that the user can actually see it finish
if (g_Progress < 100)
{
// Show 100 when it is really 99
var progress = g_Progress + 1;
Engine.GetGUIObjectByName("progressbar").caption = progress; // display current progress
Engine.GetGUIObjectByName("progressText").caption = progress + "%";
// Displays detailed loading info rather than a percent
// Engine.GetGUIObjectByName("progressText").caption = g_LoadDescription; // display current progess details
// Keep curved right edge of progress bar in sync with the rest of the progress bar
var middle = Engine.GetGUIObjectByName("progressbar");
var rightSide = Engine.GetGUIObjectByName("progressbar_right");
var middleLength = (middle.size.right - middle.size.left) - (END_PIECE_WIDTH / 2);
var increment = Math.round(progress * middleLength / 100);
var size = rightSide.size;
size.left = increment;
size.right = increment + END_PIECE_WIDTH;
rightSide.size = size;
}
}
// ====================================================================
function reallyStartGame()
{
// Stop the music
// if (global.curr_music)
// global.curr_music.fade(-1, 0.0, 5.0); // fade to 0 over 5 seconds
// This is a reserved function name that is executed by the engine when it is ready
// to start the game (i.e. loading progress has reached 100%).
// Switch GUI from loading screen to game session.
Engine.SwitchGuiPage("page_session.xml", g_Data);
// Restore default cursor.
Engine.SetCursor("arrow-default");
}
Index: ps/trunk/binaries/data/mods/public/gui/loading/loading.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/loading/loading.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/loading/loading.xml (revision 14954)
@@ -1,51 +1,53 @@
displayProgress();
`
- Quote of the Day:
+
+ Quote of the Day:
+
Index: ps/trunk/binaries/data/mods/public/gui/lobby/lobby.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/lobby.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/lobby.js (revision 14954)
@@ -1,982 +1,1009 @@
var g_ChatMessages = [];
var g_Name = "unknown";
var g_GameList = {};
var g_specialKey = Math.random();
// This object looks like {"name":[numMessagesSinceReset, lastReset, timeBlocked]} when in use.
var g_spamMonitor = {};
var g_timestamp = Engine.ConfigDB_GetValue("user", "lobby.chattimestamp") == "true";
var g_mapSizes = {};
var g_userRating = "UNR"; // Rating of user, defaults to Unrated
var g_modPrefix = "@";
// Block spammers for 30 seconds.
var SPAM_BLOCK_LENGTH = 30;
////////////////////////////////////////////////////////////////////////////////////////////////
function init(attribs)
{
// Play menu music
initMusic();
global.music.setState(global.music.states.MENU);
g_Name = Engine.LobbyGetNick();
g_mapSizes = initMapSizes();
- g_mapSizes.shortNames.splice(0, 0, "Any");
+ g_mapSizes.shortNames.splice(0, 0, translateWithContext("map size", "Any"));
g_mapSizes.tiles.splice(0, 0, "");
var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter");
mapSizeFilter.list = g_mapSizes.shortNames;
mapSizeFilter.list_data = g_mapSizes.tiles;
var playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter");
- playersNumberFilter.list = ["Any",2,3,4,5,6,7,8];
+ playersNumberFilter.list = [translateWithContext("player number", "Any"),2,3,4,5,6,7,8];
playersNumberFilter.list_data = ["",2,3,4,5,6,7,8];
var mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter");
- mapTypeFilter.list = ["Any", "Skirmish", "Random", "Scenario"];
+ mapTypeFilter.list = [translateWithContext("map", "Any"), translateWithContext("map", "Skirmish"), translateWithContext("map", "Random"), translate("Scenario")];
mapTypeFilter.list_data = ["", "skirmish", "random", "scenario"];
Engine.LobbySetPlayerPresence("available");
Engine.SendGetGameList();
Engine.SendGetBoardList();
Engine.SendGetRatingList();
updatePlayerList();
updateSubject(Engine.LobbyGetRoomSubject());
resetFilters();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Xmpp client connection management
////////////////////////////////////////////////////////////////////////////////////////////////
function lobbyStop()
{
Engine.StopXmppClient();
}
function lobbyConnect()
{
Engine.ConnectXmppClient();
}
function lobbyDisconnect()
{
Engine.DisconnectXmppClient();
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Update functions
////////////////////////////////////////////////////////////////////////////////////////////////
function resetFilters()
{
// Reset states of gui objects
Engine.GetGUIObjectByName("mapSizeFilter").selected = 0
Engine.GetGUIObjectByName("playersNumberFilter").selected = 0;
Engine.GetGUIObjectByName("mapTypeFilter").selected = 0;
Engine.GetGUIObjectByName("showFullFilter").checked = true;
// Update the list of games
updateGameList();
// Update info box about the game currently selected
updateGameSelection();
}
function applyFilters()
{
// Update the list of games
updateGameList();
// Update info box about the game currently selected
updateGameSelection();
}
/**
* Filter a game based on the status of the filter dropdowns.
*
* @param game Game to be tested.
* @return True if game should not be displayed.
*/
function filterGame(game)
{
var mapSizeFilter = Engine.GetGUIObjectByName("mapSizeFilter");
var playersNumberFilter = Engine.GetGUIObjectByName("playersNumberFilter");
var mapTypeFilter = Engine.GetGUIObjectByName("mapTypeFilter");
var showFullFilter = Engine.GetGUIObjectByName("showFullFilter");
// We assume index 0 means display all for any given filter.
if (mapSizeFilter.selected != 0 && game.mapSize != mapSizeFilter.list_data[mapSizeFilter.selected])
return true;
if (playersNumberFilter.selected != 0 && game.tnbp != playersNumberFilter.list_data[playersNumberFilter.selected])
return true;
if (mapTypeFilter.selected != 0 && game.mapType != mapTypeFilter.list_data[mapTypeFilter.selected])
return true;
if (!showFullFilter.checked && game.tnbp <= game.nbp)
return true;
return false;
}
/**
* Update the subject GUI object.
*
* @param newSubject New room subject.
*/
function updateSubject(newSubject)
{
var subject = Engine.GetGUIObjectByName("subject");
var subjectBox = Engine.GetGUIObjectByName("subjectBox");
var logo = Engine.GetGUIObjectByName("logo");
// Load new subject and un-escape newlines.
subject.caption = newSubject.replace("\\n", "\n", "g");
// If the subject is only whitespace, hide it and reposition the logo.
if (subject.caption.match(/^([\s\t\r\n]*)$/g))
{
subjectBox.hidden = true;
logo.size = "50%-110 50%-50 50%+110 50%+50";
}
else
{
subjectBox.hidden = false;
logo.size = "50%-110 40 50%+110 140";
}
}
/**
* Do a full update of the player listing, including ratings from C++.
*
* @return Array containing the player, presence, nickname, and rating listings.
*/
function updatePlayerList()
{
var playersBox = Engine.GetGUIObjectByName("playersBox");
[playerList, presenceList, nickList, ratingList] = [[],[],[],[]];
var cleanPlayerList = Engine.GetPlayerList();
// Sort the player list, ignoring case.
cleanPlayerList.sort(function(a,b)
{
var aName = a.name.toLowerCase();
var bName = b.name.toLowerCase();
return ((aName > bName) ? 1 : (bName > aName) ? -1 : 0);
} );
for (var i = 0; i < cleanPlayerList.length; i++)
{
// Identify current user's rating.
if (cleanPlayerList[i].name == g_Name && cleanPlayerList[i].rating)
g_userRating = cleanPlayerList[i].rating;
// Add a "-" for unrated players.
if (!cleanPlayerList[i].rating)
cleanPlayerList[i].rating = "-";
// Colorize.
var [name, status, rating] = formatPlayerListEntry(cleanPlayerList[i].name, cleanPlayerList[i].presence, cleanPlayerList[i].rating, cleanPlayerList[i].role);
// Push to lists.
playerList.push(name);
presenceList.push(status);
nickList.push(cleanPlayerList[i].name);
var ratingSpaces = " ";
for (var index = 0; index < 4 - Math.ceil(Math.log(cleanPlayerList[i].rating) / Math.LN10); index++)
ratingSpaces += " ";
ratingList.push(String(ratingSpaces + rating));
}
playersBox.list_name = playerList;
playersBox.list_status = presenceList;
playersBox.list_rating = ratingList;
playersBox.list = nickList;
if (playersBox.selected >= playersBox.list.length)
playersBox.selected = -1;
return [playerList, presenceList, nickList, ratingList];
}
/**
* Update the leaderboard from data cached in C++.
*/
function updateLeaderboard()
{
// Get list from C++
var boardList = Engine.GetBoardList();
// Get GUI leaderboard object
var leaderboard = Engine.GetGUIObjectByName("leaderboardBox");
// Sort list in acending order by rating
boardList.sort(function(a, b) b.rating - a.rating);
var list = [];
var list_name = [];
var list_rank = [];
var list_rating = [];
// Push changes
for (var i = 0; i < boardList.length; i++)
{
list_name.push(boardList[i].name);
list_rating.push(boardList[i].rating);
list_rank.push(i+1);
list.push(boardList[i].name);
}
leaderboard.list_name = list_name;
leaderboard.list_rating = list_rating;
leaderboard.list_rank = list_rank;
leaderboard.list = list;
if (leaderboard.selected >= leaderboard.list.length)
leaderboard.selected = -1;
}
/**
* Update the game listing from data cached in C++.
*/
function updateGameList()
{
var gamesBox = Engine.GetGUIObjectByName("gamesBox");
var gameList = Engine.GetGameList();
// Store the game whole game list data so that we can access it later
// to update the game info panel.
g_GameList = gameList;
// Sort the list of games to that games 'waiting' are displayed at the top
g_GameList.sort(function (a,b) {
return a.state == 'waiting' ? -1 : b.state == 'waiting' ? +1 : 0;
});
var list_name = [];
var list_ip = [];
var list_mapName = [];
var list_mapSize = [];
var list_mapType = [];
var list_nPlayers = [];
var list = [];
var list_data = [];
var c = 0;
for each (var g in gameList)
{
if(!filterGame(g))
{
// Highlight games 'waiting' for this player, otherwise display as green
var name = (g.state != 'waiting') ? '[color="0 125 0"]' + g.name + '[/color]' : '[color="orange"]' + g.name + '[/color]';
list_name.push(name);
list_ip.push(g.ip);
list_mapName.push(g.niceMapName);
list_mapSize.push(g.mapSize.split("(")[0]);
list_mapType.push(toTitleCase(g.mapType));
list_nPlayers.push(g.nbp + "/" +g.tnbp);
list.push(g.name);
list_data.push(c);
}
c++;
}
gamesBox.list_name = list_name;
//gamesBox.list_ip = list_ip;
gamesBox.list_mapName = list_mapName;
gamesBox.list_mapSize = list_mapSize;
gamesBox.list_mapType = list_mapType;
gamesBox.list_nPlayers = list_nPlayers;
gamesBox.list = list;
gamesBox.list_data = list_data;
if (gamesBox.selected >= gamesBox.list_name.length)
gamesBox.selected = -1;
// Update info box about the game currently selected
updateGameSelection();
}
/**
* Colorize and format the entries in the player list.
*
* @param nickname Name of player.
* @param presence Presence of player.
* @param rating Rating of player.
* @return Colorized versions of name, status, and rating.
*/
function formatPlayerListEntry(nickname, presence, rating, role)
{
// Set colors based on player status
- var color, status;
+ var color;
+ var status;
switch (presence)
{
case "playing":
color = "125 0 0";
- status = "Busy";
+ status = translate("Busy");
break;
case "gone":
case "away":
color = "229 76 13";
- status = "Away";
+ status = translate("Away");
break;
case "available":
color = "0 125 0";
- status = "Online";
+ status = translate("Online");
break;
case "offline":
color = "0 0 0";
- status = "Offline";
+ status = translate("Offline");
break;
default:
- warn("Unknown presence '" + presence + "'");
+ warn(sprintf("Unknown presence '%(presence)s'", { presence: presence }));
color = "178 178 178";
- status = "Unknown";
+ status = translateWithContext("lobby presence", "Unknown");
break;
}
// Center the unrated symbol.
if (rating == "-")
rating = " -";
var formattedStatus = '[color="' + color + '"]' + status + "[/color]";
var formattedRating = '[color="' + color + '"]' + rating + "[/color]";
if (role == "moderator")
nickname = g_modPrefix + nickname;
var formattedName = colorPlayerName(nickname);
// Give moderators special formatting.
if (role == "moderator")
formattedName = formattedName; //TODO
+
// Push this player's name and status onto the list
return [formattedName, formattedStatus, formattedRating];
}
/**
* Populate the game info area with information on the current game selection.
*/
function updateGameSelection()
{
var selected = Engine.GetGUIObjectByName("gamesBox").selected;
// If a game is not selected, hide the game information area.
if (selected == -1)
{
Engine.GetGUIObjectByName("gameInfo").hidden = true;
Engine.GetGUIObjectByName("joinGameButton").hidden = true;
Engine.GetGUIObjectByName("gameInfoEmpty").hidden = false;
return;
}
var mapData;
var g = Engine.GetGUIObjectByName("gamesBox").list_data[selected];
// Load map data
if (g_GameList[g].mapType == "random" && g_GameList[g].mapName == "random")
- mapData = {"settings": {"Description": "A randomly selected map."}};
+ mapData = {"settings": {"Description": translate("A randomly selected map.")}};
else if (g_GameList[g].mapType == "random" && Engine.FileExists(g_GameList[g].mapName + ".json"))
mapData = parseJSONData(g_GameList[g].mapName + ".json");
else if (Engine.FileExists(g_GameList[g].mapName + ".xml"))
mapData = Engine.LoadMapSettings(g_GameList[g].mapName + ".xml");
else
// Warn the player if we can't find the map.
- warn("Map '" + g_GameList[g].mapName + "' not found locally.");
+ warn(sprintf("Map '%(mapName)s' not found locally.", { mapName: g_GameList[g].mapName }));
// Show the game info panel and join button.
Engine.GetGUIObjectByName("gameInfo").hidden = false;
Engine.GetGUIObjectByName("joinGameButton").hidden = false;
Engine.GetGUIObjectByName("gameInfoEmpty").hidden = true;
// Display the map name, number of players, the names of the players, the map size and the map type.
Engine.GetGUIObjectByName("sgMapName").caption = g_GameList[g].niceMapName;
Engine.GetGUIObjectByName("sgNbPlayers").caption = g_GameList[g].nbp + "/" + g_GameList[g].tnbp;
Engine.GetGUIObjectByName("sgPlayersNames").caption = g_GameList[g].players;
Engine.GetGUIObjectByName("sgMapSize").caption = g_GameList[g].mapSize.split("(")[0];
Engine.GetGUIObjectByName("sgMapType").caption = toTitleCase(g_GameList[g].mapType);
// Display map description if it exists, otherwise display a placeholder.
if (mapData && mapData.settings.Description)
var mapDescription = mapData.settings.Description;
else
- var mapDescription = "Sorry, no description available.";
+ var mapDescription = translate("Sorry, no description available.");
// Display map preview if it exists, otherwise display a placeholder.
if (mapData && mapData.settings.Preview)
var mapPreview = mapData.settings.Preview;
else
var mapPreview = "nopreview.png";
Engine.GetGUIObjectByName("sgMapDescription").caption = mapDescription;
Engine.GetGUIObjectByName("sgMapPreview").sprite = "cropped:(0.7812,0.5859)session/icons/mappreview/" + mapPreview;
}
/**
* Start the joining process on the currectly selected game.
*/
function joinSelectedGame()
{
var gamesBox = Engine.GetGUIObjectByName("gamesBox");
if (gamesBox.selected >= 0)
{
var g = gamesBox.list_data[gamesBox.selected];
var sname = g_Name;
var sip = g_GameList[g].ip;
// TODO: What about valid host names?
// Check if it looks like an ip address
if (sip.split('.').length != 4)
{
- addChatMessage({ "from": "system", "text": "This game's address '" + sip + "' does not appear to be valid." });
+ addChatMessage({ "from": "system", "text": sprintf(translate("This game's address '%(ip)s' does not appear to be valid."), { ip: sip }) });
return;
}
// Open Multiplayer connection window with join option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "join", name: sname, ip: sip, rating: g_userRating });
}
}
/**
* Start the hosting process.
*/
function hostGame()
{
// Open Multiplayer connection window with host option.
Engine.PushGuiPage("page_gamesetup_mp.xml", { multiplayerGameType: "host", name: g_Name, rating: g_userRating });
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Utils
////////////////////////////////////////////////////////////////////////////////////////////////
-/**
- * Add a leading zero to single-digit numbers.
- */
-function twoDigits(n)
-{
- return n < 10 ? "0" + n : n;
-}
-
function stripColorCodes(input)
{
return input.replace(/\[(\w+)[^w]*?](.*?)\[\/\1]/g, '$2');
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GUI event handlers
////////////////////////////////////////////////////////////////////////////////////////////////
function onTick()
{
// Wake up XmppClient
Engine.RecvXmppClient();
updateTimers();
checkSpamMonitor();
// Receive messages
while (true)
{
var message = Engine.LobbyGuiPollMessage();
// Clean Message
if (!message)
break;
var text = escapeText(message.text);
switch (message.type)
{
case "mucmessage": // For room messages
var from = escapeText(message.from);
addChatMessage({ "from": from, "text": text });
break;
case "message": // For private messages
var from = escapeText(message.from);
addChatMessage({ "from": from, "text": text });
break;
case "muc":
var nick = message.text;
var presence = Engine.LobbyGetPlayerPresence(nick);
var playersBox = Engine.GetGUIObjectByName("playersBox");
var playerList = playersBox.list_name;
var presenceList = playersBox.list_status;
var nickList = playersBox.list;
var ratingList = playersBox.list_rating;
var nickIndex = nickList.indexOf(nick);
switch(message.level)
{
case "join":
if (nick == g_Name)
{
// We just joined, we need to get the full player list
[playerList, presenceList, nickList, ratingList] = updatePlayerList();
break;
}
var [name, status, rating] = formatPlayerListEntry(nick, presence, "-");
playerList.push(name);
presenceList.push(status);
nickList.push(nick);
ratingList.push(String(rating));
Engine.SendGetRatingList();
- addChatMessage({ "text": "/special " + nick + " has joined.", "key": g_specialKey });
+ addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has joined."), { nick: nick }), "key": g_specialKey });
break;
case "leave":
if (nickIndex == -1) // Left, but not present (TODO: warn about this?)
break;
playerList.splice(nickIndex, 1);
presenceList.splice(nickIndex, 1);
nickList.splice(nickIndex, 1);
ratingList.splice(nickIndex, 1);
- addChatMessage({ "text": "/special " + nick + " has left.", "key": g_specialKey });
+ addChatMessage({ "text": "/special " + sprintf(translate("%(nick)s has left."), { nick: nick }), "key": g_specialKey });
break;
case "nick":
if (nickIndex == -1) // This shouldn't ever happen
break;
if (!isValidNick(message.data))
{
- addChatMessage({ "from": "system", "text": "Invalid nickname: " + message.data });
+ addChatMessage({ "from": "system", "text": sprintf(translate("Invalid nickname: %(nick)s"), { nick: message.data })});
break;
}
var [name, status, rating] = formatPlayerListEntry(message.data, presence, stripColorCodes(ratingList[nickIndex])); // TODO: actually we don't want to change the presence here, so use what was used before
playerList[nickIndex] = name;
// presence stays the same
nickList[nickIndex] = message.data;
- addChatMessage({ "text": "/special " + nick + " is now known as " + message.data + ".", "key": g_specialKey });
+ addChatMessage({ "text": "/special " + sprintf(translate("%(oldnick)s is now known as %(newnick)s."), { oldnick: nick, newnick: message.data }), "key": g_specialKey });
Engine.SendGetRatingList();
break;
case "presence":
if (nickIndex == -1) // This shouldn't ever happen
break;
var [name, status, rating] = formatPlayerListEntry(nick, presence, stripColorCodes(ratingList[nickIndex]));
presenceList[nickIndex] = status;
playerList[nickIndex] = name;
ratingList[nickIndex] = rating;
break;
case "subject":
updateSubject(message.text);
break;
default:
- warn("Unknown message.level '" + message.level + "'");
+ warn(sprintf("Unknown message.level '%(msglvl)s'", { msglvl: message.level }));
break;
}
// Push new data to GUI
playersBox.list_name = playerList;
playersBox.list_status = presenceList;
playersBox.list_rating = ratingList;
playersBox.list = nickList;
if (playersBox.selected >= playersBox.list.length)
playersBox.selected = -1;
break;
case "system":
switch (message.level)
{
case "standard":
addChatMessage({ "from": "system", "text": text, "color": "150 0 0" });
if (message.text == "disconnected")
{
// Clear the list of games and the list of players
updateGameList();
updateLeaderboard();
updatePlayerList();
// Disable the 'host' button
Engine.GetGUIObjectByName("hostButton").enabled = false;
}
else if (message.text == "connected")
{
Engine.GetGUIObjectByName("hostButton").enabled = true;
}
break;
case "error":
addChatMessage({ "from": "system", "text": text, "color": "150 0 0" });
break;
case "internal":
switch (message.text)
{
case "gamelist updated":
updateGameList();
break;
case "boardlist updated":
updateLeaderboard();
break;
case "ratinglist updated":
updatePlayerList();
break;
}
break
}
break;
default:
- error("Unrecognised message type "+message.type);
+ error(sprintf("Unrecognised message type %(msgtype)s", { msgtype: message.type }));
}
}
}
/* Messages */
function submitChatInput()
{
var input = Engine.GetGUIObjectByName("chatInput");
var text = escapeText(input.caption);
if (text.length)
{
if (!handleSpecialCommand(text) && !isSpam(text, g_Name))
Engine.LobbySendMessage(text);
input.caption = "";
}
}
function completeNick()
{
var input = Engine.GetGUIObjectByName("chatInput");
var text = escapeText(input.caption);
if (text.length)
{
var matched = false;
for each (var playerObj in Engine.GetPlayerList())
{
var player = playerObj.name;
var breaks = text.match(/(\s+)/g) || [];
text.split(/\s+/g).reduceRight(function (wordsSoFar, word, index)
{
if (matched)
return null;
var matchCandidate = word + (breaks[index - 1] || "") + wordsSoFar;
if (player.toUpperCase().indexOf(matchCandidate.toUpperCase().trim()) == 0)
{
input.caption = text.replace(matchCandidate.trim(), player);
matched = true;
}
return matchCandidate;
}, "");
if (matched)
break;
}
}
}
function isValidNick(nick)
{
var prohibitedNicks = ["system"];
return prohibitedNicks.indexOf(nick) == -1;
}
/**
* Handle all '/' commands.
*
* @param text Text to be checked for commands.
* @return true if more text processing is needed, false otherwise.
*/
function handleSpecialCommand(text)
{
if (text[0] != '/')
return false;
var [cmd, nick] = ircSplit(text);
switch (cmd)
{
case "away":
Engine.LobbySetPlayerPresence("away");
break;
case "back":
Engine.LobbySetPlayerPresence("available");
break;
case "kick": // TODO: Split reason from nick and pass it too, for now just support "/kick nick"
// also allow quoting nicks (and/or prevent users from changing it here, but that doesn't help if the spammer uses a different client)
Engine.LobbyKick(nick, "");
break;
case "ban": // TODO: Split reason from nick and pass it too, for now just support "/ban nick"
Engine.LobbyBan(nick, "");
break;
case "quit":
lobbyStop();
Engine.SwitchGuiPage("page_pregame.xml");
break;
case "say":
case "me":
return false;
default:
- addChatMessage({ "from":"system", "text":"We're sorry, the '" + cmd + "' command is not supported."});
+ addChatMessage({ "from":"system", "text": sprintf(translate("We're sorry, the '%(cmd)s' command is not supported."), { cmd: cmd})});
}
return true;
}
/**
* Process and, if appropriate, display a formatted message.
*
* @param msg The message to be processed.
*/
function addChatMessage(msg)
{
// Some calls of this function will leave some msg parameters empty. Text is required though.
if (msg.from)
{
// Display the moderator symbol in the chatbox.
var playerRole = Engine.LobbyGetPlayerRole(msg.from);
if (playerRole == "moderator")
msg.from = g_modPrefix + msg.from;
}
else
msg.from = null;
if (!msg.color)
msg.color = null;
if (!msg.key)
msg.key = null;
// Highlight local user's nick
if (msg.text.indexOf(g_Name) != -1 && g_Name != msg.from)
msg.text = msg.text.replace(new RegExp('\\b' + '\\' + g_Name + '\\b', "g"), colorPlayerName(g_Name));
// Run spam test
updateSpamMonitor(msg.from);
if (isSpam(msg.text, msg.from))
return;
// Format Text
var formatted = ircFormat(msg.text, msg.from, msg.color, msg.key);
// If there is text, add it to the chat box.
if (formatted)
{
g_ChatMessages.push(formatted);
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
}
function ircSplit(string)
{
var idx = string.indexOf(' ');
if (idx != -1)
return [string.substr(1,idx-1), string.substr(idx+1)];
return [string.substr(1), ""];
}
/**
* Format text in an IRC-like way.
*
* @param text Body of the message.
* @param from Sender of the message.
* @param color Optional color of sender.
* @param key Key to verify join/leave messages with. TODO: Remove this, it only provides synthetic security.
* @return Formatted text.
*/
function ircFormat(text, from, color, key)
{
// Generate and apply color to uncolored names,
if (!color && from)
var coloredFrom = colorPlayerName(from);
else if (color && from)
var coloredFrom = '[color="' + color + '"]' + from + "[/color]";
- // Time for optional time header
- var time = new Date(Date.now());
-
- // Build time header if enabled
- if (g_timestamp)
- var formatted = '[font="serif-bold-13"]\x5B' + twoDigits(time.getHours() % 12) + ":" + twoDigits(time.getMinutes()) + '\x5D[/font] '
- else
- var formatted = "";
-
// Handle commands allowed past handleSpecialCommand.
if (text[0] == '/')
{
var [command, message] = ircSplit(text);
switch (command)
{
case "me":
- return formatted + '[font="serif-bold-13"]* ' + coloredFrom + '[/font] ' + message;
+ // Translation: IRC message prefix when the sender uses the /me command.
+ var senderString = '[font="serif-bold-13"]' + sprintf(translate("* %(sender)s"), { sender: coloredFrom }) + '[/font]';
+ // Translation: IRC message issued using the ‘/me’ command.
+ var formattedMessage = sprintf(translate("%(sender)s %(action)s"), { sender: senderString, action: message });
+ break;
case "say":
- return formatted + '[font="serif-bold-13"]<' + coloredFrom + '>[/font] ' + message;
+ // Translation: IRC message prefix.
+ var senderString = '[font="serif-bold-13"]' + sprintf(translate("<%(sender)s>"), { sender: coloredFrom }) + '[/font]';
+ // Translation: IRC message.
+ var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { sender: senderString, message: message });
+ break
case "special":
if (key === g_specialKey)
- return formatted + '[font="serif-bold-13"] == ' + message + '[/font]';
+ // Translation: IRC system message.
+ var formattedMessage = '[font="serif-bold-13"]' + sprintf(translate("== %(message)s"), { message: message }) + '[/font]';
+ else
+ {
+ // Translation: IRC message prefix.
+ var senderString = '[font="serif-bold-13"]' + sprintf(translate("<%(sender)s>"), { sender: coloredFrom }) + '[/font]';
+ // Translation: IRC message.
+ var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { sender: senderString, message: message });
+ }
break;
default:
// This should never happen.
- return "";
+ var formattedMessage = "";
}
}
- return formatted + '[font="serif-bold-13"]<' + coloredFrom + '>[/font] ' + text;
+ else
+ {
+ // Translation: IRC message prefix.
+ var senderString = '[font="serif-bold-13"]' + sprintf(translate("<%(sender)s>"), { sender: coloredFrom }) + '[/font]';
+ // Translation: IRC message.
+ var formattedMessage = sprintf(translate("%(sender)s %(message)s"), { sender: senderString, message: text });
+ }
+
+ // Build time header if enabled
+ if (g_timestamp)
+ {
+ // Time for optional time header
+ var time = new Date(Date.now());
+
+ // Translation: Time as shown in the multiplayer lobby (when you enable it in the options page).
+ // For a list of symbols that you can use, see:
+ // https://sites.google.com/site/icuprojectuserguide/formatparse/datetime?pli=1#TOC-Date-Field-Symbol-Table
+ var timeString = Engine.FormatMillisecondsIntoDateString(time.getTime(), translate("HH:mm"));
+
+ // Translation: Time prefix as shown in the multiplayer lobby (when you enable it in the options page).
+ var timePrefixString = '[font="serif-bold-13"]' + sprintf(translate("[%(time)s]"), { time: timeString }) + '[/font]';
+
+ // Translation: IRC message format when there is a time prefix.
+ return sprintf(translate("%(time)s %(message)s"), { time: timePrefixString, message: formattedMessage });
+ }
+ else
+ return formattedMessage;
}
/**
* Update the spam monitor.
*
* @param from User to update.
*/
function updateSpamMonitor(from)
{
// Integer time in seconds.
var time = Math.floor(Date.now() / 1000);
// Update or initialize the user in the spam monitor.
if (g_spamMonitor[from])
g_spamMonitor[from][0]++;
else
g_spamMonitor[from] = [1, time, 0];
}
/**
* Check if a message is spam.
*
* @param text Body of message.
* @param from Sender of message.
* @return True if message should be blocked.
*/
function isSpam(text, from)
{
// Integer time in seconds.
var time = Math.floor(Date.now() / 1000);
// Initialize if not already in the database.
if (!g_spamMonitor[from])
g_spamMonitor[from] = [1, time, 0];
// Block blank lines.
if (text == " ")
return true;
// Block users who are still within their spam block period.
else if (g_spamMonitor[from][2] + SPAM_BLOCK_LENGTH >= time)
return true;
// Block users who exceed the rate of 1 message per second for five seconds and are not already blocked. TODO: Make this smarter and block profanity.
else if (g_spamMonitor[from][0] == 6)
{
g_spamMonitor[from][2] = time;
if (from == g_Name)
- addChatMessage({ "from": "system", "text": "Please do not spam. You have been blocked for thirty seconds." });
+ addChatMessage({ "from": "system", "text": translate("Please do not spam. You have been blocked for thirty seconds.") });
return true;
}
// Return false if everything is clear.
else
return false;
}
/**
* Reset timer used to measure message send speed.
*/
function checkSpamMonitor()
{
// Integer time in seconds.
var time = Math.floor(Date.now() / 1000);
// Clear message count every 5 seconds.
for each (var stats in g_spamMonitor)
{
if (stats[1] + 5 <= time)
{
stats[1] = time;
stats[0] = 0;
}
}
}
/* Utilities */
// Generate a (mostly) unique color for this player based on their name.
// See http://stackoverflow.com/questions/3426404/create-a-hexadecimal-colour-based-on-a-string-with-jquery-javascript
function getPlayerColor(playername)
{
// Generate a probably-unique hash for the player name and use that to create a color.
var hash = 0;
for (var i = 0; i < playername.length; i++)
hash = playername.charCodeAt(i) + ((hash << 5) - hash);
// First create the color in RGB then HSL, clamp the lightness so it's not too dark to read, and then convert back to RGB to display.
// The reason for this roundabout method is this algorithm can generate values from 0 to 255 for RGB but only 0 to 100 for HSL; this gives
// us much more variety if we generate in RGB. Unfortunately, enforcing that RGB values are a certain lightness is very difficult, so
// we convert to HSL to do the computation. Since our GUI code only displays RGB colors, we have to convert back.
var [h, s, l] = rgbToHsl(hash >> 24 & 0xFF, hash >> 16 & 0xFF, hash >> 8 & 0xFF);
return hslToRgb(h, s, Math.max(0.4, l)).join(" ");
}
function repeatString(times, string) {
return Array(times + 1).join(string);
}
// Some names are special and should always appear in certain colors.
var fixedColors = { "system": repeatString(7, "255.0.0."), "@WFGbot": repeatString(7, "255.24.24."),
"pyrogenesis": repeatString(2, "97.0.0.") + repeatString(2, "124.0.0.") + "138.0.0." +
repeatString(2, "174.0.0.") + repeatString(2, "229.40.0.") + repeatString(2, "243.125.15.") };
function colorPlayerName(playername)
{
var color = fixedColors[playername];
if (color) {
color = color.split(".");
return ('[color="' + playername.split("").map(function (c, i) color.slice(i * 3, i * 3 + 3).join(" ") + '"]' + c + '[/color][color="')
.join("") + '"]').slice(0, -10);
}
return '[color="' + getPlayerColor(playername.replace(g_modPrefix, "")) + '"]' + playername + '[/color]';
}
// Ensure `value` is between 0 and 1.
function clampColorValue(value)
{
return Math.abs(1 - Math.abs(value - 1));
}
// See http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
function rgbToHsl(r, g, b)
{
r /= 255;
g /= 255;
b /= 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b);
var h, s, l = (max + min) / 2;
if (max == min)
h = s = 0; // achromatic
else
{
var d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max)
{
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
function hslToRgb(h, s, l)
{
function hue2rgb(p, q, t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
[h, s, l] = [h, s, l].map(clampColorValue);
var r, g, b;
if (s == 0)
r = g = b = l; // achromatic
else {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [r, g, b].map(function (n) Math.round(n * 255));
}
(function () {
function hexToRgb(hex) {
return parseInt(hex.slice(0, 2), 16) + "." + parseInt(hex.slice(2, 4), 16) + "." + parseInt(hex.slice(4, 6), 16) + ".";
}
function r(times, hex) {
return repeatString(times, hexToRgb(hex));
}
fixedColors["Twilight_Sparkle"] = r(2, "d19fe3") + r(2, "b689c8") + r(2, "a76bc2") +
r(4, "263773") + r(2, "131f46") + r(2, "662d8a") + r(2, "ed438a");
fixedColors["Applejack"] = r(3, "ffc261") + r(3, "efb05d") + r(3, "f26f31");
fixedColors["Rarity"] = r(1, "ebeff1") + r(1, "dee3e4") + r(1, "bec2c3") +
r(1, "83509f") + r(1, "4b2568") + r(1, "4917d6");
fixedColors["Rainbow_Dash"] = r(2, "ee4144") + r(1, "f37033") + r(1, "fdf6af") +
r(1, "62bc4d") + r(1, "1e98d3") + r(2, "672f89") + r(1, "9edbf9") +
r(1, "88c4eb") + r(1, "77b0e0") + r(1, "1e98d3");
fixedColors["Pinkie_Pie"] = r(2, "f3b6cf") + r(2, "ec9dc4") + r(4, "eb81b4") +
r(1, "ed458b") + r(1, "be1d77");
fixedColors["Fluttershy"] = r(2, "fdf6af") + r(2, "fee78f") + r(2, "ead463") +
r(2, "f3b6cf") + r(2, "eb81b4");
fixedColors["Sweetie_Belle"] = r(2, "efedee") + r(3, "e2dee3") + r(3, "cfc8d1") +
r(2, "b28dc0") + r(2, "f6b8d2") + r(1, "795b8a");
fixedColors["Apple_Bloom"] = r(2, "f4f49b") + r(2, "e7e793") + r(2, "dac582") +
r(2, "f46091") + r(2, "f8415f") + r(1, "c52451");
fixedColors["Scootaloo"] = r(2, "fbba64") + r(2, "f2ab56") + r(2, "f37003") +
r(2, "bf5d95") + r(1, "bf1f79");
fixedColors["Luna"] = r(1, "7ca7fa") + r(1, "5d6fc1") + r(1, "656cb9") + r(1, "393993");
fixedColors["Celestia"] = r(1, "fdfafc") + r(1, "f7eaf2") + r(1, "d99ec5") +
r(1, "00aec5") + r(1, "f7c6dc") + r(1, "98d9ef") + r(1, "ced7ed") + r(1, "fed17b");
})();
Index: ps/trunk/binaries/data/mods/public/gui/lobby/lobby.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/lobby.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/lobby.xml (revision 14954)
@@ -1,192 +1,224 @@
- Multiplayer Lobby
+ Multiplayer Lobby
onTick();
-
-
-
+
+ Status
+
+
+ Name
+
+
+ Rating
+
- Leaderboard
+ LeaderboardEngine.GetGUIObjectByName("leaderboard").hidden = false;Engine.GetGUIObjectByName("leaderboardFade").hidden = false;
- Map Type:
+
+ Map Type:
+
- Map Size:
+
+ Map Size:
+
- Players:
+
+ Players:
+
- Join Game
+ Join Game
joinSelectedGame();
- Host Game
+ Host Game
hostGame();
- Main Menu
+ Main Menu
lobbyStop();
Engine.SwitchGuiPage("page_pregame.xml");
updateGameSelection();
-
+
+ Name
+
-
-
-
-
+
+ Map Name
+
+
+ Map Size
+
+
+ Map Type
+
+
+ Players
+ applyFilters();applyFilters();applyFilters();
- Show full games
+
+ Show full games
+ applyFilters();submitChatInput();completeNick();
- Leaderboard
+
+ Leaderboard
+
-
-
-
+
+ Rank
+
+
+ Rating
+
+
+ Name
+
- Back
+ BackEngine.GetGUIObjectByName("leaderboard").hidden = true;Engine.GetGUIObjectByName("leaderboardFade").hidden = true;
- Update
+ UpdateEngine.SendGetBoardList();
Index: ps/trunk/binaries/data/mods/public/gui/lobby/prelobby.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/lobby/prelobby.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/lobby/prelobby.js (revision 14954)
@@ -1,185 +1,186 @@
var g_LobbyIsConnecting = false;
var g_EncrytedPassword = "";
var g_PasswordInputIsHidden = false;
function init()
{
g_EncrytedPassword = Engine.ConfigDB_GetValue("user", "lobby.password");
}
function lobbyStop()
{
Engine.GetGUIObjectByName("feedback").caption = "";
if (g_LobbyIsConnecting == false)
return;
g_LobbyIsConnecting = false;
Engine.StopXmppClient();
}
function lobbyStart()
{
if (g_LobbyIsConnecting)
return;
if (Engine.HasXmppClient())
Engine.StopXmppClient();
var username = Engine.GetGUIObjectByName("connectUsername").caption;
var password = Engine.GetGUIObjectByName("connectPassword").caption;
var feedback = Engine.GetGUIObjectByName("feedback");
var room = Engine.ConfigDB_GetValue("user", "lobby.room");
var history = Number(Engine.ConfigDB_GetValue("user", "lobby.history"));
- feedback.caption = "Connecting....";
+ feedback.caption = translate("Connecting...");
// If they enter a different password, re-encrypt.
if (password != g_EncrytedPassword.substring(0, 10))
g_EncrytedPassword = Engine.EncryptPassword(password, username);
// We just use username as nick for simplicity.
Engine.StartXmppClient(username, g_EncrytedPassword, room, username, history);
g_LobbyIsConnecting = true;
Engine.ConnectXmppClient();
}
function lobbyStartRegister()
{
if (g_LobbyIsConnecting != false)
return;
if (Engine.HasXmppClient())
Engine.StopXmppClient();
var account = Engine.GetGUIObjectByName("connectUsername").caption;
var password = Engine.GetGUIObjectByName("connectPassword").caption;
var passwordAgain = Engine.GetGUIObjectByName("registerPasswordAgain").caption;
var feedback = Engine.GetGUIObjectByName("feedback");
// Check the passwords match.
if (password != passwordAgain)
{
- feedback.caption = "Passwords do not match";
+ feedback.caption = translate("Passwords do not match");
Engine.GetGUIObjectByName("connectPassword").caption = "";
Engine.GetGUIObjectByName("registerPasswordAgain").caption = "";
switchRegister();
return;
}
- feedback.caption = "Registering...";
+ feedback.caption = translate("Registering...");
g_EncrytedPassword = Engine.EncryptPassword(password, account);
Engine.StartRegisterXmppClient(account, g_EncrytedPassword);
g_LobbyIsConnecting = true;
Engine.ConnectXmppClient();
}
function switchRegister()
{
if (Engine.GetGUIObjectByName("pageRegister").hidden)
{
lobbyStop();
Engine.GetGUIObjectByName("pageRegister").hidden = false;
Engine.GetGUIObjectByName("pageConnect").hidden = true;
Engine.GetGUIObjectByName("connect").enabled = false;
}
else
{
Engine.GetGUIObjectByName("pageRegister").hidden = true;
Engine.GetGUIObjectByName("pageConnect").hidden = false;
Engine.GetGUIObjectByName("connect").enabled = true;
}
}
function onTick()
{
//
var username = Engine.GetGUIObjectByName("connectUsername").caption;
var password = Engine.GetGUIObjectByName("connectPassword").caption;
var passwordAgain = Engine.GetGUIObjectByName("registerPasswordAgain").caption;
var feedback = Engine.GetGUIObjectByName("feedback");
var pageRegisterHidden = Engine.GetGUIObjectByName("pageRegister").hidden;
var connectButton = Engine.GetGUIObjectByName("connect");
var registerButton = Engine.GetGUIObjectByName("register");
var sanitizedName = sanitizePlayerName(username, true, true)
// If there aren't a username and password entered, we can't start registration or connection.
if (!username || !password)
{
connectButton.enabled = false;
registerButton.enabled = false;
if (!username && !password)
- feedback.caption = "Please enter existing login or desired registration credentials.";
+ feedback.caption = translate("Please enter existing login or desired registration credentials.");
}
// Check they are using a valid account name.
else if (username != sanitizedName)
{
- feedback.caption = "Usernames can't contain [, ], unicode, whitespace, or commas.";
+ feedback.caption = translate("Usernames can't contain [, ], unicode, whitespace, or commas.");
connectButton.enabled = false;
registerButton.enabled = false;
}
// Allow them to connect/begin registation if there aren't any problems.
else if (pageRegisterHidden)
{
- if (feedback.caption == "Usernames can't contain [, ], unicode, whitespace, or commas." ||
- feedback.caption == "Please enter existing login or desired registration credentials.")
+ // TODO Do this without comparing the caption
+ if (feedback.caption == translate("Usernames can't contain [, ], unicode, whitespace, or commas.") ||
+ feedback.caption == translate("Please enter existing login or desired registration credentials."))
feedback.caption = "";
connectButton.enabled = true;
registerButton.enabled = true;
}
// If the password hasn't been entered again, we can't complete registation.
if (!pageRegisterHidden && !passwordAgain)
registerButton.enabled = false;
else if (!pageRegisterHidden)
registerButton.enabled = true;
if (!g_LobbyIsConnecting)
// The Xmpp Client has not been created
return;
// The XmppClient has been created, we are waiting
// to be connected or to receive an error.
//Wake up XmppClient
Engine.RecvXmppClient();
//Receive messages
while (true)
{
var message = Engine.LobbyGuiPollMessage();
if (!message)
break;
if (message.type == "muc" && message.level == "join")
{
// We are connected, switch to the lobby page
Engine.PopGuiPage();
// Use username as nick.
var nick = sanitizePlayerName(username, true, true);
// Switch to lobby
Engine.SwitchGuiPage("page_lobby.xml");
// Store nick, login, and password
Engine.ConfigDB_CreateValue("user", "playername", nick);
Engine.ConfigDB_CreateValue("user", "lobby.login", username);
// We only store the encrypted password, so make sure to re-encrypt it if changed before saving.
if (password != g_EncrytedPassword.substring(0, 10))
g_EncrytedPassword = Engine.EncryptPassword(password, username);
Engine.ConfigDB_CreateValue("user", "lobby.password", g_EncrytedPassword);
Engine.ConfigDB_WriteFile("user", "config/user.cfg");
return;
}
else if (message.type == "system" && message.text == "registered")
{
// Great, we are registered. Switch to the connection window.
feedback.caption = toTitleCase(message.text);
Engine.StopXmppClient();
g_LobbyIsConnecting = false;
switchRegister();
}
else if(message.type == "system" && (message.level == "error" || message.text == "disconnected"))
{
feedback.caption = toTitleCase(message.text);
Engine.StopXmppClient();
g_LobbyIsConnecting = false;
}
}
}
Index: ps/trunk/binaries/data/mods/public/gui/locale/locale.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/locale/locale.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/locale/locale.js (revision 14954)
@@ -0,0 +1,65 @@
+function init()
+{
+ var languageList = Engine.GetGUIObjectByName("languageList");
+ languageList.list = Engine.GetSupportedLocaleDisplayNames();
+ languageList.list_data = Engine.GetSupportedLocaleBaseNames();
+
+ var currentLocale = Engine.GetCurrentLocale();
+ var currentLocaleBaseName = Engine.GetLocaleBaseName(currentLocale);
+ var currentLocaleLanguage = Engine.GetLocaleLanguage(currentLocale);
+ if (languageList.list_data.indexOf(currentLocaleBaseName) != -1)
+ languageList.selected = languageList.list_data.indexOf(currentLocaleBaseName);
+ else if (languageList.list_data.indexOf(currentLocaleLanguage) != -1)
+ languageList.selected = languageList.list_data.indexOf(currentLocaleLanguage);
+
+ var localeText = Engine.GetGUIObjectByName("localeText");
+ localeText.caption = currentLocale;
+}
+
+function cancelSetup()
+{
+ Engine.PopGuiPage();
+}
+
+function applySelectedLocale()
+{
+ var localeText = Engine.GetGUIObjectByName("localeText");
+ if(!Engine.SaveLocale(localeText.caption))
+ {
+ warn("Selected locale could not be saved in the configuration!");
+ return;
+ }
+ Engine.ReevaluateCurrentLocaleAndReload();
+ Engine.SwitchGuiPage("page_pregame.xml");
+}
+
+function languageSelectionChanged()
+{
+ var languageList = Engine.GetGUIObjectByName("languageList");
+ var locale = languageList.list_data[languageList.selected];
+ if(!Engine.ValidateLocale(locale))
+ warn("Selected locale is not valid! This is not expected, please report the issue.");
+ var localeText = Engine.GetGUIObjectByName("localeText");
+ localeText.caption = locale;
+}
+
+function openAdvancedMenu()
+{
+ var localeText = Engine.GetGUIObjectByName("localeText");
+ Engine.PushGuiPage("page_locale_advanced.xml", { "callback": "applyFromAdvancedMenu", "locale": localeText.caption } );
+}
+
+function applyFromAdvancedMenu(locale)
+{
+ var languageList = Engine.GetGUIObjectByName("languageList");
+
+ var localeBaseName = Engine.GetLocaleBaseName(locale);
+ var localeLanguage = Engine.GetLocaleLanguage(locale);
+ if (languageList.list_data.indexOf(localeBaseName) != -1)
+ languageList.selected = languageList.list_data.indexOf(localeBaseName);
+ else if (languageList.list_data.indexOf(localeLanguage) != -1)
+ languageList.selected = languageList.list_data.indexOf(localeLanguage);
+
+ var localeText = Engine.GetGUIObjectByName("localeText");
+ localeText.caption = locale;
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/locale/locale.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.js (revision 14954)
@@ -0,0 +1,115 @@
+function init(initData)
+{
+ var languageList = Engine.GetGUIObjectByName("languageList");
+ var countryList = Engine.GetGUIObjectByName("countryList");
+ var resultingLocaleText = Engine.GetGUIObjectByName("resultingLocale");
+ var scriptInput = Engine.GetGUIObjectByName("scriptInput");
+
+ // get languageList data. Only list languages for which we have a dictionary.
+ var languageListData = [];
+ var languageListTmp = Engine.GetSupportedLocaleBaseNames();
+ var currentLocaleLanguage = Engine.GetLocaleLanguage(initData.locale);
+ for (var i=0; i");
+ dictionaryFile.caption = "";
+ }
+}
+
+function autoDetectLocale()
+{
+ var languageList = Engine.GetGUIObjectByName("languageList");
+ var countryList = Engine.GetGUIObjectByName("countryList");
+ var scriptInput = Engine.GetGUIObjectByName("scriptInput");
+ var variantInput = Engine.GetGUIObjectByName("variantInput");
+ var dictionaryFile = Engine.GetGUIObjectByName("dictionaryFile");
+
+ variantInput.caption = "";
+ dictionaryFile.caption = "";
+ var locale = Engine.GetDictionaryLocale("");
+
+ languageList.selected = languageList.list_data.indexOf(Engine.GetLocaleLanguage(locale));
+ countryList.selected = countryList.selected = countryList.list_data.indexOf(Engine.GetLocaleCountry(locale));
+ scriptInput.caption = Engine.GetLocaleScript(locale);
+}
+
+function applySelectedLocale()
+{
+ var resultingLocaleText = Engine.GetGUIObjectByName("resultingLocale");
+ Engine.PopGuiPageCB(resultingLocaleText.caption);
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/locale/locale_advanced.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/manual/manual.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/manual/manual.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/manual/manual.js (revision 14954)
@@ -1,16 +1,16 @@
var hasCallback = false;
function init(data)
{
- Engine.GetGUIObjectByName("mainText").caption = Engine.ReadFile("gui/manual/" + data.page + ".txt");
+ Engine.GetGUIObjectByName("mainText").caption = Engine.TranslateLines(Engine.ReadFile("gui/manual/" + data.page + ".txt"));
if (data.callback)
hasCallback = true;
}
function closeManual()
{
if (hasCallback)
Engine.PopGuiPageCB();
else
Engine.PopGuiPage();
}
Index: ps/trunk/binaries/data/mods/public/gui/msgbox/msgbox.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/msgbox/msgbox.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/msgbox/msgbox.js (revision 14954)
@@ -0,0 +1,100 @@
+function init(data)
+{
+ var mbMainObj = Engine.GetGUIObjectByName("mbMain");
+ var mbTitleObj = Engine.GetGUIObjectByName("mbTitleBar");
+ var mbTextObj = Engine.GetGUIObjectByName("mbText");
+
+ var mbButton1Obj = Engine.GetGUIObjectByName("mbButton1");
+ var mbButton2Obj = Engine.GetGUIObjectByName("mbButton2");
+ var mbButton3Obj = Engine.GetGUIObjectByName("mbButton3");
+
+ // Calculate size
+ var mbLRDiff = data.width / 2; // Message box left/right difference from 50% of screen
+ var mbUDDiff = data.height / 2; // Message box up/down difference from 50% of screen
+
+ var mbSizeString = "50%-" + mbLRDiff + " 50%-" + mbUDDiff + " 50%+" + mbLRDiff + " 50%+" + mbUDDiff;
+
+ mbMainObj.size = mbSizeString;
+
+ // Texts
+ mbTitleObj.caption = data.title;
+ mbTextObj.caption = data.message;
+
+ if (data.font)
+ mbTextObj.font = data.font;
+
+ // Message box modes
+ // There is a number of standard modes, and if none of these is used (mbMode == 0), the button captions will be
+ // taken from the array mbButtonCaptions; there currently is a maximum of three buttons.
+ switch (data.mode)
+ {
+ case 1:
+ // Simple Yes/No question box
+ data.buttonCaptions = [translate("Yes"), translate("No")];
+ break;
+ case 2:
+ // Okay-only box
+ data.buttonCaptions = [translate("OK")];
+ break;
+ case 3:
+ // Retry/Abort/Ignore box (will we ever need this?!)
+ data.buttonCaptions = [translate("Retry"), translate("Ignore"), translate("Abort")];
+ default:
+ break;
+ }
+
+ // Buttons
+ var codes = data.buttonCode;
+ if (data.buttonCaptions.length >= 1)
+ {
+ mbButton1Obj.caption = data.buttonCaptions[0];
+ mbButton1Obj.onPress = function ()
+ {
+ if (data.callback)
+ Engine.PopGuiPageCB(0);
+ else
+ Engine.PopGuiPage();
+ };
+ mbButton1Obj.hidden = false;
+ }
+ if (data.buttonCaptions.length >= 2)
+ {
+ mbButton2Obj.caption = data.buttonCaptions[1];
+ mbButton2Obj.onPress = function ()
+ {
+ if (data.callback)
+ Engine.PopGuiPageCB(1);
+ else
+ Engine.PopGuiPage();
+ };
+ mbButton2Obj.hidden = false;
+ }
+ if (data.buttonCaptions.length >= 3)
+ {
+ mbButton3Obj.caption = data.buttonCaptions[2];
+ mbButton3Obj.onPress = function ()
+ {
+ if (data.callback)
+ Engine.PopGuiPageCB(2);
+ else
+ Engine.PopGuiPage();
+ };
+ mbButton3Obj.hidden = false;
+ }
+
+ switch (data.buttonCaptions.length)
+ {
+ case 1:
+ mbButton1Obj.size = "50%-64 100%-76 50%+64 100%-48";
+ break;
+ case 2:
+ mbButton1Obj.size = "50%-144 100%-76 50%-16 100%-48";
+ mbButton2Obj.size = "50%+16 100%-76 50%+144 100%-48";
+ break;
+ case 3:
+ mbButton1Obj.size = "10% 100%-76 30% 100%-48";
+ mbButton2Obj.size = "40% 100%-76 60% 100%-48";
+ mbButton3Obj.size = "70% 100%-76 90% 100%-48";
+ break;
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/gui/msgbox/msgbox.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/options/options.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/options/options.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/options/options.xml (revision 14954)
@@ -1,67 +1,77 @@
-
+
- Game Options
+
+ Game Options
+
- General
+
+ General
+
- Graphics Settings
+
+ Graphics Settings
+
- Sound Settings
+
+ Sound Settings
+
- Lobby Settings
+
+ Lobby Settings
+
- Save
+ SaveEngine.ConfigDB_WriteFile("user", "config/user.cfg");Engine.PopGuiPage();
- Cancel
+ CancelEngine.PopGuiPage();
Index: ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.js (revision 14954)
@@ -1,332 +1,366 @@
var userReportEnabledText; // contains the original version with "$status" placeholder
var currentSubmenuType; // contains submenu type
const MARGIN = 4; // menu border size
const background = "hellenes1"; // Background type. Currently: 'hellenes1', 'persians1'.
var g_ShowSplashScreens;
function init(initData, hotloadData)
{
initMusic();
// Play main menu music
global.music.setState(global.music.states.MENU);
userReportEnabledText = Engine.GetGUIObjectByName("userReportEnabledText").caption;
// initialize currentSubmenuType with placeholder to avoid null when switching
currentSubmenuType = "submenuSinglePlayer";
EnableUserReport(Engine.IsUserReportEnabled());
// Only show splash screen(s) once at startup, but not again after hotloading
g_ShowSplashScreens = hotloadData ? hotloadData.showSplashScreens : initData && initData.isStartup;
}
function getHotloadData()
{
return { "showSplashScreens": g_ShowSplashScreens };
}
var t0 = new Date;
function scrollBackgrounds(background)
{
if (background == "hellenes1")
{
var layer1 = Engine.GetGUIObjectByName("backgroundHele1-1");
var layer2 = Engine.GetGUIObjectByName("backgroundHele1-2");
var layer3 = Engine.GetGUIObjectByName("backgroundHele1-3");
layer1.hidden = false;
layer2.hidden = false;
layer3.hidden = false;
var screen = layer1.parent.getComputedSize();
var h = screen.bottom - screen.top; // height of screen
var w = h*16/9; // width of background image
// Offset the layers by oscillating amounts
var t = (t0 - new Date) / 700;
var speed = 1/20;
var off1 = 0.02 * w * (1+Math.cos(t*speed));
var off2 = 0.12 * w * (1+Math.cos(t*speed)) - h*6/9;
var off3 = 0.16 * w * (1+Math.cos(t*speed));
var left = screen.right - w * (1 + Math.ceil(screen.right / w));
layer1.size = new GUISize(left + off1, screen.top, screen.right + off1, screen.bottom);
layer2.size = new GUISize(screen.right/2 - h + off2, screen.top, screen.right/2 + h + off2, screen.bottom);
layer3.size = new GUISize(screen.right - h + off3, screen.top, screen.right + off3, screen.bottom);
}
if (background == "persians1")
{
var layer1 = Engine.GetGUIObjectByName("backgroundPers1-1");
var layer2 = Engine.GetGUIObjectByName("backgroundPers1-2");
var layer3 = Engine.GetGUIObjectByName("backgroundPers1-3");
var layer4 = Engine.GetGUIObjectByName("backgroundPers1-4");
layer1.hidden = false;
layer2.hidden = false;
layer3.hidden = false;
layer4.hidden = false;
var screen = layer1.parent.getComputedSize();
var h = screen.bottom - screen.top; // height of screen
var screenWidth = screen.right - screen.left;
var w = h*16/9;
var t = (t0 - new Date) / 1000;
var speed = 1/20;
var off1 = 0.01 * w * (Math.cos(t*speed));
var off2 = 0.03 * w * (Math.cos(t*speed));
var off3 = 0.07 * w * (1+Math.cos(t*speed)) + 0.5 * screenWidth - h*1.1;
var off4 = 0.16 * w * (1+Math.cos(t*speed)) - h*6/9;
var left = screen.right - w * (1 + Math.ceil(screen.right / w)) - 0.5 * screenWidth + h;
layer1.size = new GUISize(left + off1, screen.top, screen.right + off1 + h, screen.bottom);
layer2.size = new GUISize(left + off2, screen.top, screen.right + off2 + h, screen.bottom);
layer3.size = new GUISize(screen.left + off3, screen.top, screen.left + 2 * h + off3, screen.bottom);
layer4.size = new GUISize(screen.left + off4, screen.top, screen.left + 2 * h + off4, screen.bottom);
}
}
function submitUserReportMessage()
{
var input = Engine.GetGUIObjectByName("userReportMessageInput");
var msg = input.caption;
if (msg.length)
Engine.SubmitUserReport("message", 1, msg);
input.caption = "";
}
function formatUserReportStatus(status)
{
var d = status.split(/:/, 3);
if (d[0] == "disabled")
- return "disabled";
+ return translate("disabled");
if (d[0] == "connecting")
- return "connecting to server";
+ return translate("connecting to server");
if (d[0] == "sending")
{
var done = d[1];
- return "uploading (" + Math.floor(100*done) + "%)";
+ return sprintf(translate("uploading (%f%%)"), Math.floor(100*done));
}
if (d[0] == "completed")
{
var httpCode = d[1];
if (httpCode == 200)
- return "upload succeeded";
+ return translate("upload succeeded");
else
- return "upload failed (" + httpCode + ")";
+ return sprintf(translate("upload failed (%(errorCode)s)"), { errorCode: httpCode });
}
if (d[0] == "failed")
{
var errCode = d[1];
var errMessage = d[2];
- return "upload failed (" + errMessage + ")";
+ return sprintf(translate("upload failed (%(errorMessage)s)"), { errorMessage: errMessage });
}
- return "unknown";
+ return translate("unknown");
}
var lastTickTime = new Date;
function onTick()
{
var now = new Date;
var tickLength = new Date - lastTickTime;
lastTickTime = now;
// Animate backgrounds
scrollBackgrounds(background);
// Animate submenu
updateMenuPosition(tickLength);
if (Engine.IsUserReportEnabled())
{
Engine.GetGUIObjectByName("userReportEnabledText").caption =
userReportEnabledText.replace(/\$status/,
formatUserReportStatus(Engine.GetUserReportStatus()));
}
// Show splash screens here, so we don't interfere with main menu hotloading
if (g_ShowSplashScreens)
{
g_ShowSplashScreens = false;
if (Engine.ConfigDB_GetValue("user", "splashscreenenable") !== "false")
Engine.PushGuiPage("page_splashscreen.xml", { "page": "splashscreen", callback : "SplashScreenClosedCallback" } );
else
ShowRenderPathMessage();
-
}
}
function ShowRenderPathMessage()
{
// Warn about removing fixed render path
if (Engine.Renderer_GetRenderPath() == "fixed")
messageBox(
600,
300,
- "[font=\"serif-bold-16\"][color=\"200 20 20\"]Warning:[/color] You appear to be using non-shader (fixed function) graphics. This option will be removed in a future 0 A.D. release, to allow for more advanced graphics features. We advise upgrading your graphics card to a more recent, shader-compatible model.\n\nPlease press \"Read More\" for more information or \"Ok\" to continue.",
- "WARNING!",
+ "[font=\"serif-bold-16\"]" +
+ sprintf(translate("%(startWarning)sWarning:%(endWarning)s You appear to be using non-shader (fixed function) graphics. This option will be removed in a future 0 A.D. release, to allow for more advanced graphics features. We advise upgrading your graphics card to a more recent, shader-compatible model."), { startWarning: "[color=\"200 20 20\"]", endWarning: "[/color]"}) +
+ "\n\n" +
+ // Translation: This is the second paragraph of a warning. The
+ // warning explains that the user is using “non-shader“ graphics,
+ // and that in the future this will not be supported by the game, so
+ // the user will need a better graphics card.
+ translate("Please press \"Read More\" for more information or \"OK\" to continue."),
+ translate("WARNING!"),
0,
- ["Ok", "Read More"],
+ [translate("OK"), translate("Read More")],
[ null, function() { Engine.OpenURL("http://www.wildfiregames.com/forum/index.php?showtopic=16734"); } ]
);
}
function SplashScreenClosedCallback()
{
ShowRenderPathMessage();
}
function EnableUserReport(Enabled)
{
Engine.GetGUIObjectByName("userReportDisabled").hidden = Enabled;
Engine.GetGUIObjectByName("userReportEnabled").hidden = !Enabled;
Engine.SetUserReportEnabled(Enabled);
}
/*
* MENU FUNCTIONS
*/
// Temporarily adding this here
//const BUTTON_SOUND = "audio/interface/ui/ui_button_longclick.ogg";
//function playButtonSound()
//{
// var buttonSound = new Sound(BUTTON_SOUND);
// buttonSound.play();
//}
// Slide menu
function updateMenuPosition(dt)
{
var submenu = Engine.GetGUIObjectByName("submenu");
if (submenu.hidden == false)
{
// Number of pixels per millisecond to move
const SPEED = 1.2;
var maxOffset = Engine.GetGUIObjectByName("mainMenu").size.right - submenu.size.left;
if (maxOffset > 0)
{
var offset = Math.min(SPEED * dt, maxOffset);
var size = submenu.size;
size.left += offset;
size.right += offset;
submenu.size = size;
}
}
}
// Opens the menu by revealing the screen which contains the menu
function openMenu(newSubmenu, position, buttonHeight, numButtons)
{
// switch to new submenu type
currentSubmenuType = newSubmenu;
Engine.GetGUIObjectByName(currentSubmenuType).hidden = false;
// set position of new submenu
var submenu = Engine.GetGUIObjectByName("submenu");
var top = position - MARGIN;
var bottom = position + ((buttonHeight + MARGIN) * numButtons);
submenu.size = submenu.size.left + " " + top + " " + submenu.size.right + " " + bottom;
// Blend in right border of main menu into the left border of the submenu
blendSubmenuIntoMain(top, bottom);
// Reveal submenu
Engine.GetGUIObjectByName("submenu").hidden = false;
}
// Closes the menu and resets position
function closeMenu()
{
// playButtonSound();
// remove old submenu type
Engine.GetGUIObjectByName(currentSubmenuType).hidden = true;
// hide submenu and reset position
var submenu = Engine.GetGUIObjectByName("submenu");
submenu.hidden = true;
submenu.size = Engine.GetGUIObjectByName("mainMenu").size;
// reset main menu panel right border
Engine.GetGUIObjectByName("MainMenuPanelRightBorderTop").size = "100%-2 0 100% 100%";
}
// Sizes right border on main menu panel to match the submenu
function blendSubmenuIntoMain(topPosition, bottomPosition)
{
var topSprite = Engine.GetGUIObjectByName("MainMenuPanelRightBorderTop");
topSprite.size = "100%-2 0 100% " + (topPosition + MARGIN);
var bottomSprite = Engine.GetGUIObjectByName("MainMenuPanelRightBorderBottom");
bottomSprite.size = "100%-2 " + (bottomPosition) + " 100% 100%";
}
+function getBuildString()
+{
+ return sprintf(translate("Build: %(buildDate)s (%(revision)s)"), { buildDate: Engine.GetBuildTimestamp(0), revision: Engine.GetBuildTimestamp(2) });
+}
+
/*
* FUNCTIONS BELOW DO NOT WORK YET
*/
//// Switch to a given options tab window.
//function openOptionsTab(tabName)
//{
// // Hide the other tabs.
// for (var i = 1; i <= 3; i++)
// {
// switch (i)
// {
// case 1:
// var tmpName = "pgOptionsAudio";
// break;
// case 2:
// var tmpName = "pgOptionsVideo";
// break;
// case 3:
// var tmpName = "pgOptionsGame";
// break;
// default:
// break;
// }
//
// if (tmpName != tabName)
// {
// Engine.GetGUIObjectByName (tmpName + "Window").hidden = true;
// Engine.GetGUIObjectByName (tmpName + "Button").enabled = true;
// }
// }
//
// // Make given tab visible.
// Engine.GetGUIObjectByName (tabName + "Window").hidden = false;
// Engine.GetGUIObjectByName (tabName + "Button").enabled = false;
//}
//
//// Move the credits up the screen.
//function updateCredits()
//{
// // If there are still credit lines to remove, remove them.
// if (getNumItems("pgCredits") > 0)
// removeItem ("pgCredits", 0);
// else
// {
// // When we've run out of credit,
//
// // Stop the increment timer if it's still active.
// cancelInterval();
//
// // Close the credits screen and return.
// closeMainMenuSubWindow ("pgCredits");
// guiUnHide ("pg");
// }
//}
+
+function exitGamePressed()
+{
+ closeMenu();
+ var btCaptions = [translate("Yes"), translate("No")];
+ var btCode = [Engine.Exit, null];
+ messageBox(400, 200, translate("Are you sure you want to quit 0 A.D.?"), translate("Confirmation"), 0, btCaptions, btCode);
+}
+
+function pressedScenarioEditorButton()
+{
+ closeMenu();
+ // Start Atlas
+ if (Engine.AtlasIsAvailable())
+ Engine.RestartInAtlas();
+ else
+ messageBox(400, 200, translate("The scenario editor is not available or failed to load."), translate("Error"), 2);
+}
+
+function getLobbyDisabledByBuild()
+{
+ return translate("Launch the multiplayer lobby. [DISABLED BY BUILD]");
+}
Index: ps/trunk/binaries/data/mods/public/gui/savedgames/load.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/savedgames/load.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/savedgames/load.xml (revision 14954)
@@ -1,41 +1,41 @@
- Load Game
+ Load Game
- Load
+ LoadloadGame();
- Delete
+ DeletedeleteGame();
- Cancel
+ CancelEngine.PopGuiPage();
Index: ps/trunk/binaries/data/mods/public/gui/session/input.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/input.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/input.js (revision 14954)
@@ -1,2247 +1,2276 @@
const SDL_BUTTON_LEFT = 1;
const SDL_BUTTON_MIDDLE = 2;
const SDL_BUTTON_RIGHT = 3;
const SDLK_LEFTBRACKET = 91;
const SDLK_RIGHTBRACKET = 93;
const SDLK_RSHIFT = 303;
const SDLK_LSHIFT = 304;
const SDLK_RCTRL = 305;
const SDLK_LCTRL = 306;
const SDLK_RALT = 307;
const SDLK_LALT = 308;
// TODO: these constants should be defined somewhere else instead, in
// case any other code wants to use them too
const ACTION_NONE = 0;
const ACTION_GARRISON = 1;
const ACTION_REPAIR = 2;
const ACTION_GUARD = 3;
var preSelectedAction = ACTION_NONE;
const INPUT_NORMAL = 0;
const INPUT_SELECTING = 1;
const INPUT_BANDBOXING = 2;
const INPUT_BUILDING_PLACEMENT = 3;
const INPUT_BUILDING_CLICK = 4;
const INPUT_BUILDING_DRAG = 5;
const INPUT_BATCHTRAINING = 6;
const INPUT_PRESELECTEDACTION = 7;
const INPUT_BUILDING_WALL_CLICK = 8;
const INPUT_BUILDING_WALL_PATHING = 9;
const INPUT_MASSTRIBUTING = 10;
var inputState = INPUT_NORMAL;
var placementSupport = new PlacementSupport();
var mouseX = 0;
var mouseY = 0;
var mouseIsOverObject = false;
// Distance to search for a selatable entity in. Bigger numbers are slower.
var SELECTION_SEARCH_RADIUS = 100;
// Number of pixels the mouse can move before the action is considered a drag
var maxDragDelta = 4;
// Time in milliseconds in which a double click is recognized
const doubleClickTime = 500;
var doubleClickTimer = 0;
var doubleClicked = false;
// Store the previously clicked entity - ensure a double/triple click happens on the same entity
var prevClickedEntity = 0;
// Same double-click behaviour for hotkey presses
const doublePressTime = 500;
var doublePressTimer = 0;
var prevHotkey = 0;
function updateCursorAndTooltip()
{
var cursorSet = false;
var tooltipSet = false;
var informationTooltip = Engine.GetGUIObjectByName("informationTooltip");
if (!mouseIsOverObject)
{
var action = determineAction(mouseX, mouseY);
if (inputState == INPUT_NORMAL || inputState == INPUT_PRESELECTEDACTION)
{
if (action)
{
if (action.cursor)
{
Engine.SetCursor(action.cursor);
cursorSet = true;
}
if (action.tooltip)
{
tooltipSet = true;
informationTooltip.caption = action.tooltip;
informationTooltip.hidden = false;
}
}
}
}
if (!cursorSet)
Engine.SetCursor("arrow-default");
if (!tooltipSet)
informationTooltip.hidden = true;
var placementTooltip = Engine.GetGUIObjectByName("placementTooltip");
if (placementSupport.tooltipMessage)
{
if (placementSupport.tooltipError)
placementTooltip.sprite = "BackgroundErrorTooltip";
else
placementTooltip.sprite = "BackgroundInformationTooltip";
placementTooltip.caption = placementSupport.tooltipMessage;
placementTooltip.hidden = false;
}
else
{
placementTooltip.caption = "";
placementTooltip.hidden = true;
}
}
function updateBuildingPlacementPreview()
{
// The preview should be recomputed every turn, so that it responds to obstructions/fog/etc moving underneath it, or
// in the case of the wall previews, in response to new tower foundations getting constructed for it to snap to.
// See onSimulationUpdate in session.js.
if (placementSupport.mode === "building")
{
if (placementSupport.template && placementSupport.position)
{
var result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
"template": placementSupport.template,
"x": placementSupport.position.x,
"z": placementSupport.position.z,
"angle": placementSupport.angle,
"actorSeed": placementSupport.actorSeed
});
// Show placement info tooltip if invalid position
placementSupport.tooltipError = !result.success;
- placementSupport.tooltipMessage = result.success ? "" : result.message;
+ placementSupport.tooltipMessage = "";
if (!result.success)
+ {
+ if (result.message && result.parameters)
+ {
+ // translate the message parameters
+ translateObjectKeys(result.parameters, Object.keys(result.parameters), true)
+ placementSupport.tooltipMessage = sprintf(translate(result.message), result.parameters);
+ }
return false;
+ }
if (placementSupport.attack)
{
// building can be placed here, and has an attack
// show the range advantage in the tooltip
var cmd = {x: placementSupport.position.x,
z: placementSupport.position.z,
range: placementSupport.attack.maxRange,
elevationBonus: placementSupport.attack.elevationBonus,
};
var averageRange = Engine.GuiInterfaceCall("GetAverageRangeForBuildings",cmd);
- placementSupport.tooltipMessage = "Basic range: "+Math.round(cmd.range/4)+"\nAverage bonus range: "+Math.round((averageRange - cmd.range)/4);
+ placementSupport.tooltipMessage = sprintf(translate("Basic range: %(range)s"), { range: Math.round(cmd.range/4) }) + "\n" + sprintf(translate("Average bonus range: %(range)s"), { range: Math.round((averageRange - cmd.range)/4) });
}
return true;
}
}
else if (placementSupport.mode === "wall")
{
if (placementSupport.wallSet && placementSupport.position)
{
// Fetch an updated list of snapping candidate entities
placementSupport.wallSnapEntities = Engine.PickSimilarFriendlyEntities(
placementSupport.wallSet.templates.tower,
placementSupport.wallSnapEntitiesIncludeOffscreen,
true, // require exact template match
true // include foundations
);
return Engine.GuiInterfaceCall("SetWallPlacementPreview", {
"wallSet": placementSupport.wallSet,
"start": placementSupport.position,
"end": placementSupport.wallEndPosition,
"snapEntities": placementSupport.wallSnapEntities, // snapping entities (towers) for starting a wall segment
});
}
}
return false;
}
function findGatherType(gatherer, supply)
{
if (!gatherer || !supply)
return undefined;
if (gatherer[supply.type.generic+"."+supply.type.specific])
return supply.type.specific;
if (gatherer[supply.type.generic])
return supply.type.generic;
return undefined;
}
function getActionInfo(action, target)
{
var simState = GetSimState();
var selection = g_Selection.toList();
// If the selection doesn't exist, no action
var entState = GetEntityState(selection[0]);
if (!entState)
return {"possible": false};
if (!target)
{
if (action == "set-rallypoint")
{
var cursor = "";
var data = {command: "walk"};
if (Engine.HotkeyIsPressed("session.attackmove"))
{
data = {command: "attack-walk"};
cursor = "action-attack-move";
}
return {"possible": true, "data": data, "cursor": cursor};
}
else if (action == "move" || action == "attack-move")
return {"possible": true};
else if (action == "remove-guard")
return {"possible": true};
else
return {"possible": false};
}
if (action == "unset-rallypoint" && selection.indexOf(target) != -1)
{
var haveNonEmptyRallyPoints = selection.some(function(ent) {
var entState = GetEntityState(ent);
return entState && entState.rallyPoint && entState.rallyPoint.position;
});
if (haveNonEmptyRallyPoints)
return {"possible": true};
else
return {"possible": false};
}
// Look at the first targeted entity
// (TODO: maybe we eventually want to look at more, and be more context-sensitive?
// e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse)
var targetState = GetExtendedEntityState(target);
var gaiaOwned = (targetState.player == 0);
// Look to see what type of command units going to the rally point should use
if (action == "set-rallypoint")
{
// We assume that all entities are owned by the same player (given par entState of selection[0]).
var playerState = simState.players[entState.player];
var playerOwned = (targetState.player == entState.player);
var allyOwned = playerState.isAlly[targetState.player];
var mutualAllyOwned = playerState.isMutualAlly[targetState.player];
var enemyOwned = playerState.isEnemy[targetState.player];
var tooltip;
// default to walking there (or attack-walking if hotkey pressed)
var data = {command: "walk"};
var cursor = "";
if (Engine.HotkeyIsPressed("session.attackmove"))
{
data = {command: "attack-walk"};
cursor = "action-attack-move";
}
if (targetState.garrisonHolder && (playerOwned || mutualAllyOwned))
{
data.command = "garrison";
data.target = target;
cursor = "action-garrison";
- tooltip = "Current garrison: " + targetState.garrisonHolder.garrisonedEntitiesCount
- + "/" + targetState.garrisonHolder.capacity;
+ tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
+ garrisoned: targetState.garrisonHolder.garrisonedEntitiesCount,
+ capacity: targetState.garrisonHolder.capacity
+ });
if (targetState.garrisonHolder.garrisonedEntitiesCount >= targetState.garrisonHolder.capacity)
tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
}
else if (targetState.resourceSupply)
{
var resourceType = targetState.resourceSupply.type;
if (resourceType.generic == "treasure")
cursor = "action-gather-" + resourceType.generic;
else
cursor = "action-gather-" + resourceType.specific;
data.command = "gather";
data.resourceType = resourceType;
data.resourceTemplate = targetState.template;
}
else if (targetState.foundation && entState.buildEntities)
{
data.command = "build";
data.target = target;
cursor = "action-build";
}
else if (hasClass(entState, "Market") && hasClass(targetState, "Market") && entState.id != targetState.id &&
(!hasClass(entState, "NavalMarket") || hasClass(targetState, "NavalMarket")) && !enemyOwned)
{
// Find a trader (if any) that this building can produce.
var trader;
if (entState.production && entState.production.entities.length)
for (var i = 0; i < entState.production.entities.length; ++i)
if ((trader = GetTemplateData(entState.production.entities[i]).trader))
break;
var traderData = { "firstMarket": entState.id, "secondMarket": targetState.id, "template": trader };
var gain = Engine.GuiInterfaceCall("GetTradingRouteGain", traderData);
if (gain && gain.traderGain)
{
data.command = "trade";
data.target = traderData.secondMarket;
data.source = traderData.firstMarket;
cursor = "action-setup-trade-route";
- tooltip = "Right-click to establish a default route for new traders.";
+ tooltip = translate("Right-click to establish a default route for new traders.");
if (trader)
- tooltip += "\nGain: " + getTradingTooltip(gain);
+ tooltip += "\n" + sprintf(translate("Gain: %(gain)s"), { gain: getTradingTooltip(gain) });
else // Foundation or cannot produce traders
- tooltip += "\nExpected gain: " + getTradingTooltip(gain);
+ tooltip += "\n" + sprintf(translate("Expected gain: %(gain)s"), { gain: getTradingTooltip(gain) });
}
}
else if (targetState.needsRepair && allyOwned)
{
data.command = "repair";
data.target = target;
cursor = "action-repair";
}
// Don't allow the rally point to be set on any of the currently selected entities (used for unset)
// except if the autorallypoint hotkey is pressed and the target can produce entities
if (!Engine.HotkeyIsPressed("session.autorallypoint") || !targetState.production || !targetState.production.entities.length)
{
for (var i = 0; i < selection.length; i++)
if (target === selection[i])
return {"possible": false};
}
return {"possible": true, "data": data, "position": targetState.position, "cursor": cursor, "tooltip": tooltip};
}
// Check if the target entity is a resource, dropsite, foundation, or enemy unit.
// Check if any entities in the selection can gather the requested resource,
// can return to the dropsite, can build the foundation, or can attack the enemy
for each (var entityID in selection)
{
var entState = GetExtendedEntityState(entityID);
if (!entState)
continue;
var playerState = simState.players[entState.player];
var playerOwned = (targetState.player == entState.player);
var allyOwned = playerState.isAlly[targetState.player];
var mutualAllyOwned = playerState.isMutualAlly[targetState.player];
var neutralOwned = playerState.isNeutral[targetState.player];
var enemyOwned = playerState.isEnemy[targetState.player];
// Find the resource type we're carrying, if any
var carriedType = undefined;
if (entState.resourceCarrying && entState.resourceCarrying.length)
carriedType = entState.resourceCarrying[0].type;
switch (action)
{
case "garrison":
if (hasClass(entState, "Unit") && targetState.garrisonHolder && (playerOwned || mutualAllyOwned))
{
- var tooltip = "Current garrison: " + targetState.garrisonHolder.garrisonedEntitiesCount
- + "/" + targetState.garrisonHolder.capacity;
+ var tooltip = sprintf(translate("Current garrison: %(garrisoned)s/%(capacity)s"), {
+ garrisoned: targetState.garrisonHolder.garrisonedEntitiesCount,
+ capacity: targetState.garrisonHolder.capacity
+ });
var extraCount = 0;
if (entState.garrisonHolder)
extraCount += entState.garrisonHolder.garrisonedEntitiesCount;
if (targetState.garrisonHolder.garrisonedEntitiesCount + extraCount >= targetState.garrisonHolder.capacity)
tooltip = "[color=\"orange\"]" + tooltip + "[/color]";
var allowedClasses = targetState.garrisonHolder.allowedClasses;
for each (var unitClass in entState.identity.classes)
{
if (allowedClasses.indexOf(unitClass) != -1)
return {"possible": true, "tooltip": tooltip};
}
}
break;
case "setup-trade-route":
// If ground or sea trade possible
if (!targetState.foundation && ((entState.trader && hasClass(entState, "Organic") && (playerOwned || allyOwned) && hasClass(targetState, "Market")) ||
(entState.trader && hasClass(entState, "Ship") && (playerOwned || allyOwned) && hasClass(targetState, "NavalMarket"))))
{
var tradingData = {"trader": entState.id, "target": target};
var tradingDetails = Engine.GuiInterfaceCall("GetTradingDetails", tradingData);
var tooltip;
if (tradingDetails === null)
return {"possible": false};
switch (tradingDetails.type)
{
case "is first":
- tooltip = "Origin trade market.";
+ tooltip = translate("Origin trade market.");
if (tradingDetails.hasBothMarkets)
- tooltip += "\nGain: " + getTradingTooltip(tradingDetails.gain);
+ tooltip += "\n" + sprintf(translate("Gain: %(gain)s"), {
+ gain: getTradingTooltip(tradingDetails.gain)
+ });
else
- tooltip += "\nRight-click on another market to set it as a destination trade market."
+ tooltip += "\n" + translate("Right-click on another market to set it as a destination trade market.")
break;
case "is second":
- tooltip = "Destination trade market.\nGain: " + getTradingTooltip(tradingDetails.gain);
+ tooltip = translate("Destination trade market.") + "\n" + sprintf(translate("Gain: %(gain)s"), {
+ gain: getTradingTooltip(tradingDetails.gain)
+ });
break;
case "set first":
- tooltip = "Right-click to set as origin trade market";
+ tooltip = translate("Right-click to set as origin trade market");
break;
case "set second":
if (tradingDetails.gain.traderGain == 0) // markets too close
return {"possible": false};
- tooltip = "Right-click to set as destination trade market.\nGain: " + getTradingTooltip(tradingDetails.gain);
+ tooltip = translate("Right-click to set as destination trade market.") + "\n" + sprintf(translate("Gain: %(gain)s"), {
+ gain: getTradingTooltip(tradingDetails.gain)
+ });
break;
}
return {"possible": true, "tooltip": tooltip};
}
break;
case "heal":
// The check if the target is unhealable is done by targetState.needsHeal
if (entState.healer && hasClass(targetState, "Unit") && targetState.needsHeal && (playerOwned || allyOwned))
{
// Healers can't heal themselves.
if (entState.id == targetState.id)
return {"possible": false};
var unhealableClasses = entState.healer.unhealableClasses;
for each (var unitClass in targetState.identity.classes)
{
if (unhealableClasses.indexOf(unitClass) != -1)
return {"possible": false};
}
var healableClasses = entState.healer.healableClasses;
for each (var unitClass in targetState.identity.classes)
{
if (healableClasses.indexOf(unitClass) != -1)
return {"possible": true};
}
}
break;
case "gather":
if (targetState.resourceSupply)
{
var resource = findGatherType(entState.resourceGatherRates, targetState.resourceSupply);
if (resource)
return {"possible": true, "cursor": "action-gather-" + resource};
}
break;
case "returnresource":
if (targetState.resourceDropsite && playerOwned && carriedType && targetState.resourceDropsite.types.indexOf(carriedType) != -1)
return {"possible": true, "cursor": "action-return-" + carriedType};
break;
case "build":
if (targetState.foundation && entState.buildEntities && playerOwned)
return {"possible": true};
break;
case "repair":
if (entState.buildEntities && targetState.needsRepair && allyOwned)
return {"possible": true};
break;
case "attack":
if (entState.attack && targetState.hitpoints && (enemyOwned || neutralOwned))
return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": target})};
break;
case "guard":
if (targetState.guard && (playerOwned || mutualAllyOwned))
return {"possible": (entState.unitAI && entState.unitAI.canGuard && !(targetState.unitAI && targetState.unitAI.isGuarding))};
break;
}
}
if (action == "move" || action == "attack-move")
return {"possible": true};
else
return {"possible": false};
}
/**
* Determine the context-sensitive action that should be performed when the mouse is at (x,y)
*/
function determineAction(x, y, fromMinimap)
{
var selection = g_Selection.toList();
// No action if there's no selection
if (!selection.length)
{
preSelectedAction = ACTION_NONE;
return undefined;
}
// If the selection doesn't exist, no action
var entState = GetEntityState(selection[0]);
if (!entState)
return undefined;
// If the selection isn't friendly units, no action
var playerID = Engine.GetPlayerID();
var allOwnedByPlayer = selection.every(function(ent) {
var entState = GetEntityState(ent);
return entState && entState.player == playerID;
});
if (!g_DevSettings.controlAll && !allOwnedByPlayer)
return undefined;
// Work out whether at least part of the selection have UnitAI
var haveUnitAI = selection.some(function(ent) {
var entState = GetEntityState(ent);
return entState && entState.unitAI;
});
// Work out whether at least part the selection have rally points
// while none have UnitAI
var haveRallyPoints = !haveUnitAI && selection.some(function(ent) {
var entState = GetEntityState(ent);
return entState && entState.rallyPoint;
});
var targets = [];
var target = undefined;
if (!fromMinimap)
targets = Engine.PickEntitiesAtPoint(x, y, SELECTION_SEARCH_RADIUS);
if (targets.length)
target = targets[0];
var actionInfo = undefined;
if (preSelectedAction != ACTION_NONE)
{
switch (preSelectedAction)
{
case ACTION_GARRISON:
if ((actionInfo = getActionInfo("garrison", target)).possible)
return {"type": "garrison", "cursor": "action-garrison", "tooltip": actionInfo.tooltip, "target": target};
else
return {"type": "none", "cursor": "action-garrison-disabled", "target": undefined};
break;
case ACTION_REPAIR:
if (getActionInfo("repair", target).possible)
return {"type": "repair", "cursor": "action-repair", "target": target};
else
return {"type": "none", "cursor": "action-repair-disabled", "target": undefined};
break;
case ACTION_GUARD:
if (getActionInfo("guard", target).possible)
return {"type": "guard", "cursor": "action-guard", "target": target};
else
return {"type": "none", "cursor": "action-guard-disabled", "target": undefined};
break;
}
}
else if (Engine.HotkeyIsPressed("session.attack") && getActionInfo("attack", target).possible)
{
return {"type": "attack", "cursor": "action-attack", "target": target};
}
else if (Engine.HotkeyIsPressed("session.garrison") && (actionInfo = getActionInfo("garrison", target)).possible)
{
return {"type": "garrison", "cursor": "action-garrison", "tooltip": actionInfo.tooltip, "target": target};
}
else if (haveUnitAI && Engine.HotkeyIsPressed("session.attackmove") && getActionInfo("attack-move", target).possible)
{
return {"type": "attack-move", "cursor": "action-attack-move"};
}
else if (Engine.HotkeyIsPressed("session.guard") && getActionInfo("guard", target).possible)
{
return {"type": "guard", "cursor": "action-guard", "target": target};
}
else if (Engine.HotkeyIsPressed("session.guard") && getActionInfo("remove-guard", target).possible)
{
var isGuarding = selection.some(function(ent) {
var entState = GetEntityState(ent);
return entState && entState.unitAI && entState.unitAI.isGuarding;
});
if (isGuarding)
return {"type": "remove-guard", "cursor": "action-remove-guard"};
}
else
{
if ((actionInfo = getActionInfo("setup-trade-route", target)).possible)
return {"type": "setup-trade-route", "cursor": "action-setup-trade-route", "tooltip": actionInfo.tooltip, "target": target};
else if ((actionInfo = getActionInfo("gather", target)).possible)
return {"type": "gather", "cursor": actionInfo.cursor, "target": target};
else if ((actionInfo = getActionInfo("returnresource", target)).possible)
return {"type": "returnresource", "cursor": actionInfo.cursor, "target": target};
else if (getActionInfo("build", target).possible)
return {"type": "build", "cursor": "action-build", "target": target};
else if (getActionInfo("repair", target).possible)
return {"type": "build", "cursor": "action-repair", "target": target};
else if (haveRallyPoints && (actionInfo = getActionInfo("set-rallypoint", target)).possible)
return {"type": "set-rallypoint", "cursor": actionInfo.cursor, "data": actionInfo.data, "tooltip": actionInfo.tooltip, "position": actionInfo.position};
else if (getActionInfo("heal", target).possible)
return {"type": "heal", "cursor": "action-heal", "target": target};
else if (getActionInfo("attack", target).possible)
return {"type": "attack", "cursor": "action-attack", "target": target};
else if (haveRallyPoints && getActionInfo("unset-rallypoint", target).possible)
return {"type": "unset-rallypoint", "cursor": "action-unset-rally"};
else if (haveUnitAI && getActionInfo("move", target).possible)
return {"type": "move"};
}
return {"type": "none", "cursor": "", "target": target};
}
var dragStart; // used for remembering mouse coordinates at start of drag operations
function tryPlaceBuilding(queued)
{
if (placementSupport.mode !== "building")
{
- error("[tryPlaceBuilding] Called while in '"+placementSupport.mode+"' placement mode instead of 'building'");
+ error(sprintf("[%(functionName)s] Called while in '%(mode)s' placement mode instead of 'building'", {
+ functionName: "tryPlaceBuilding",
+ mode: placementSupport.mode
+ }));
return false;
}
// Use the preview to check it's a valid build location
if (!updateBuildingPlacementPreview())
{
// invalid location - don't build it
// TODO: play a sound?
return false;
}
var selection = g_Selection.toList();
// Start the construction
Engine.PostNetworkCommand({
"type": "construct",
"template": placementSupport.template,
"x": placementSupport.position.x,
"z": placementSupport.position.z,
"angle": placementSupport.angle,
"actorSeed": placementSupport.actorSeed,
"entities": selection,
"autorepair": true,
"autocontinue": true,
"queued": queued
});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] });
if (!queued)
placementSupport.Reset();
else
placementSupport.RandomizeActorSeed();
return true;
}
function tryPlaceWall(queued)
{
if (placementSupport.mode !== "wall")
{
- error("[tryPlaceWall] Called while in '" + placementSupport.mode + "' placement mode; expected 'wall' mode");
+ error(sprintf("[%(functionName)s] Called while in '%(mode)s' placement mode; expected 'wall' mode", {
+ functionName: "tryPlaceWall",
+ mode: placementSupport.mode
+ }));
return false;
}
var wallPlacementInfo = updateBuildingPlacementPreview(); // entities making up the wall (wall segments, towers, ...)
if (!(wallPlacementInfo === false || typeof(wallPlacementInfo) === "object"))
{
- error("[tryPlaceWall] Unexpected return value from updateBuildingPlacementPreview: '" + uneval(placementInfo) + "'; expected either 'false' or 'object'");
+ error(sprintf("[%(functionName)s] Unexpected return value from %(function2Name)s: '%(value)s'; expected either 'false' or 'object'", {
+ functionName: "tryPlaceWall",
+ function2Name: "updateBuildingPlacementPreview",
+ value: uneval(placementInfo)
+ }));
return false;
}
if (!wallPlacementInfo)
return false;
var selection = g_Selection.toList();
var cmd = {
"type": "construct-wall",
"autorepair": true,
"autocontinue": true,
"queued": queued,
"entities": selection,
"wallSet": placementSupport.wallSet,
"pieces": wallPlacementInfo.pieces,
"startSnappedEntity": wallPlacementInfo.startSnappedEnt,
"endSnappedEntity": wallPlacementInfo.endSnappedEnt,
};
// make sure that there's at least one non-tower entity getting built, to prevent silly edge cases where the start and end
// point are too close together for the algorithm to place a wall segment inbetween, and only the towers are being previewed
// (this is somewhat non-ideal and hardcode-ish)
var hasWallSegment = false;
for (var k in cmd.pieces)
{
if (cmd.pieces[k].template != cmd.wallSet.templates.tower) // TODO: hardcode-ish :(
{
hasWallSegment = true;
break;
}
}
if (hasWallSegment)
{
Engine.PostNetworkCommand(cmd);
Engine.GuiInterfaceCall("PlaySound", {"name": "order_repair", "entity": selection[0] });
}
return true;
}
// Limits bandboxed selections to certain types of entities based on priority
function getPreferredEntities(ents)
{
var entStateList = [];
var preferredEnts = [];
// Check if there are units in the selection and get a list of entity states
for each (var ent in ents)
{
var entState = GetEntityState(ent);
if (!entState)
continue;
if (hasClass(entState, "Unit"))
preferredEnts.push(ent);
entStateList.push(entState);
}
// If there are no units, check if there are defensive entities in the selection
if (!preferredEnts.length)
for (var i = 0; i < ents.length; i++)
if (hasClass(entStateList[i], "Defensive"))
preferredEnts.push(ents[i]);
return preferredEnts;
}
// Removes any support units from the passed list of entities
function getMilitaryEntities(ents)
{
var militaryEnts = [];
for each (var ent in ents)
{
var entState = GetEntityState(ent);
if (!hasClass(entState, "Support"))
militaryEnts.push(ent);
}
return militaryEnts;
}
function handleInputBeforeGui(ev, hoveredObject)
{
// Capture mouse position so we can use it for displaying cursors,
// and key states
switch (ev.type)
{
case "mousebuttonup":
case "mousebuttondown":
case "mousemotion":
mouseX = ev.x;
mouseY = ev.y;
break;
}
// Remember whether the mouse is over a GUI object or not
mouseIsOverObject = (hoveredObject != null);
// Close the menu when interacting with the game world
if (!mouseIsOverObject && (ev.type =="mousebuttonup" || ev.type == "mousebuttondown")
&& (ev.button == SDL_BUTTON_LEFT || ev.button == SDL_BUTTON_RIGHT))
closeMenu();
// State-machine processing:
//
// (This is for states which should override the normal GUI processing - events will
// be processed here before being passed on, and propagation will stop if this function
// returns true)
//
// TODO: it'd probably be nice to have a better state-machine system, with guaranteed
// entry/exit functions, since this is a bit broken now
switch (inputState)
{
case INPUT_BANDBOXING:
switch (ev.type)
{
case "mousemotion":
var x0 = dragStart[0];
var y0 = dragStart[1];
var x1 = ev.x;
var y1 = ev.y;
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
var bandbox = Engine.GetGUIObjectByName("bandbox");
bandbox.size = [x0, y0, x1, y1].join(" ");
bandbox.hidden = false;
// TODO: Should we handle "control all units" here as well?
var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
g_Selection.setHighlightList(ents);
return false;
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
var x0 = dragStart[0];
var y0 = dragStart[1];
var x1 = ev.x;
var y1 = ev.y;
if (x0 > x1) { var t = x0; x0 = x1; x1 = t; }
if (y0 > y1) { var t = y0; y0 = y1; y1 = t; }
var bandbox = Engine.GetGUIObjectByName("bandbox");
bandbox.hidden = true;
// Get list of entities limited to preferred entities
// TODO: Should we handle "control all units" here as well?
var ents = Engine.PickFriendlyEntitiesInRect(x0, y0, x1, y1, Engine.GetPlayerID());
var preferredEntities = getPreferredEntities(ents)
if (preferredEntities.length)
{
ents = preferredEntities;
if (Engine.HotkeyIsPressed("selection.milonly"))
{
var militaryEntities = getMilitaryEntities(ents);
if (militaryEntities.length)
ents = militaryEntities;
}
}
// Remove the bandbox hover highlighting
g_Selection.setHighlightList([]);
// Update the list of selected units
if (Engine.HotkeyIsPressed("selection.add"))
{
g_Selection.addList(ents);
}
else if (Engine.HotkeyIsPressed("selection.remove"))
{
g_Selection.removeList(ents);
}
else
{
g_Selection.reset();
g_Selection.addList(ents);
}
inputState = INPUT_NORMAL;
return true;
}
else if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel selection
var bandbox = Engine.GetGUIObjectByName("bandbox");
bandbox.hidden = true;
g_Selection.setHighlightList([]);
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
case INPUT_BUILDING_CLICK:
switch (ev.type)
{
case "mousemotion":
// If the mouse moved far enough from the original click location,
// then switch to drag-orientation mode
var dragDeltaX = ev.x - dragStart[0];
var dragDeltaY = ev.y - dragStart[1];
var maxDragDelta = 16;
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
{
inputState = INPUT_BUILDING_DRAG;
return false;
}
break;
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
// If shift is down, let the player continue placing another of the same building
var queued = Engine.HotkeyIsPressed("session.queue");
if (tryPlaceBuilding(queued))
{
if (queued)
inputState = INPUT_BUILDING_PLACEMENT;
else
inputState = INPUT_NORMAL;
}
else
{
inputState = INPUT_BUILDING_PLACEMENT;
}
return true;
}
break;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel building
placementSupport.Reset();
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
case INPUT_BUILDING_WALL_CLICK:
// User is mid-click in choosing a starting point for building a wall. The build process can still be cancelled at this point
// by right-clicking; releasing the left mouse button will 'register' the starting point and commence endpoint choosing mode.
switch (ev.type)
{
case "mousebuttonup":
if (ev.button === SDL_BUTTON_LEFT)
{
inputState = INPUT_BUILDING_WALL_PATHING;
return true;
}
break;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel building
placementSupport.Reset();
updateBuildingPlacementPreview();
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
case INPUT_BUILDING_WALL_PATHING:
// User has chosen a starting point for constructing the wall, and is now looking to set the endpoint.
// Right-clicking cancels wall building mode, left-clicking sets the endpoint and builds the wall and returns to
// normal input mode. Optionally, shift + left-clicking does not return to normal input, and instead allows the
// user to continue building walls.
switch (ev.type)
{
case "mousemotion":
placementSupport.wallEndPosition = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
// Update the building placement preview, and by extension, the list of snapping candidate entities for both (!)
// the ending point and the starting point to snap to.
//
// TODO: Note that here, we need to fetch all similar entities, including any offscreen ones, to support the case
// where the snap entity for the starting point has moved offscreen, or has been deleted/destroyed, or was a
// foundation and has been replaced with a completed entity since the user first chose it. Fetching all towers on
// the entire map instead of only the current screen might get expensive fast since walls all have a ton of towers
// in them. Might be useful to query only for entities within a certain range around the starting point and ending
// points.
placementSupport.wallSnapEntitiesIncludeOffscreen = true;
var result = updateBuildingPlacementPreview(); // includes an update of the snap entity candidates
if (result && result.cost)
{
placementSupport.tooltipMessage = getEntityCostTooltip(result);
var neededResources = Engine.GuiInterfaceCall("GetNeededResources", result.cost);
if (neededResources)
placementSupport.tooltipMessage += getNeededResourcesTooltip(neededResources);
}
break;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_LEFT)
{
var queued = Engine.HotkeyIsPressed("session.queue");
if (tryPlaceWall(queued))
{
if (queued)
{
// continue building, just set a new starting position where we left off
placementSupport.position = placementSupport.wallEndPosition;
placementSupport.wallEndPosition = undefined;
inputState = INPUT_BUILDING_WALL_CLICK;
}
else
{
placementSupport.Reset();
inputState = INPUT_NORMAL;
}
}
else
{
- placementSupport.tooltipMessage = "Cannot build wall here!";
+ placementSupport.tooltipMessage = translate("Cannot build wall here!");
}
updateBuildingPlacementPreview();
return true;
}
else if (ev.button == SDL_BUTTON_RIGHT)
{
// reset to normal input mode
placementSupport.Reset();
updateBuildingPlacementPreview();
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
case INPUT_BUILDING_DRAG:
switch (ev.type)
{
case "mousemotion":
var dragDeltaX = ev.x - dragStart[0];
var dragDeltaY = ev.y - dragStart[1];
var maxDragDelta = 16;
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
{
// Rotate in the direction of the mouse
var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
placementSupport.angle = Math.atan2(target.x - placementSupport.position.x, target.z - placementSupport.position.z);
}
else
{
// If the mouse is near the center, snap back to the default orientation
placementSupport.SetDefaultAngle();
}
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
"template": placementSupport.template,
"x": placementSupport.position.x,
"z": placementSupport.position.z
});
if (snapData)
{
placementSupport.angle = snapData.angle;
placementSupport.position.x = snapData.x;
placementSupport.position.z = snapData.z;
}
updateBuildingPlacementPreview();
break;
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
// If shift is down, let the player continue placing another of the same building
var queued = Engine.HotkeyIsPressed("session.queue");
if (tryPlaceBuilding(queued))
{
if (queued)
inputState = INPUT_BUILDING_PLACEMENT;
else
inputState = INPUT_NORMAL;
}
else
{
inputState = INPUT_BUILDING_PLACEMENT;
}
return true;
}
break;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel building
placementSupport.Reset();
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
case INPUT_MASSTRIBUTING:
if (ev.type == "hotkeyup" && ev.hotkey == "session.masstribute")
{
flushTributing();
inputState = INPUT_NORMAL;
}
break;
case INPUT_BATCHTRAINING:
if (ev.type == "hotkeyup" && ev.hotkey == "session.batchtrain")
{
flushTrainingBatch();
inputState = INPUT_NORMAL;
}
break;
}
return false;
}
function handleInputAfterGui(ev)
{
if (ev.hotkey === undefined)
ev.hotkey = null;
// Handle the time-warp testing features, restricted to single-player
if (!g_IsNetworked && Engine.GetGUIObjectByName("devTimeWarp").checked)
{
if (ev.type == "hotkeydown" && ev.hotkey == "timewarp.fastforward")
Engine.SetSimRate(20.0);
else if (ev.type == "hotkeyup" && ev.hotkey == "timewarp.fastforward")
Engine.SetSimRate(1.0);
else if (ev.type == "hotkeyup" && ev.hotkey == "timewarp.rewind")
Engine.RewindTimeWarp();
}
if (ev.hotkey == "session.showstatusbars")
{
g_ShowAllStatusBars = (ev.type == "hotkeydown");
recalculateStatusBarDisplay();
}
if (ev.hotkey == "session.highlightguarding")
{
g_ShowGuarding = (ev.type == "hotkeydown");
updateAdditionalHighlight();
}
if (ev.hotkey == "session.highlightguarded")
{
g_ShowGuarded = (ev.type == "hotkeydown");
updateAdditionalHighlight();
}
// State-machine processing:
switch (inputState)
{
case INPUT_NORMAL:
switch (ev.type)
{
case "mousemotion":
// Highlight the first hovered entity (if any)
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
if (ents.length)
g_Selection.setHighlightList([ents[0]]);
else
g_Selection.setHighlightList([]);
return false;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_LEFT)
{
dragStart = [ ev.x, ev.y ];
inputState = INPUT_SELECTING;
return true;
}
else if (ev.button == SDL_BUTTON_RIGHT)
{
var action = determineAction(ev.x, ev.y);
if (!action)
break;
return doAction(action, ev);
}
break;
case "hotkeydown":
if (ev.hotkey.indexOf("selection.group.") == 0)
{
var now = new Date();
if ((now.getTime() - doublePressTimer < doublePressTime) && (ev.hotkey == prevHotkey))
{
if (ev.hotkey.indexOf("selection.group.select.") == 0)
{
var sptr = ev.hotkey.split(".");
performGroup("snap", sptr[3]);
}
}
else
{
var sptr = ev.hotkey.split(".");
performGroup(sptr[2], sptr[3]);
doublePressTimer = now.getTime();
prevHotkey = ev.hotkey;
}
}
break;
}
break;
case INPUT_PRESELECTEDACTION:
switch (ev.type)
{
case "mousemotion":
// Highlight the first hovered entity (if any)
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
if (ents.length)
g_Selection.setHighlightList([ents[0]]);
else
g_Selection.setHighlightList([]);
return false;
case "mousebuttondown":
if (ev.button == SDL_BUTTON_LEFT && preSelectedAction != ACTION_NONE)
{
var action = determineAction(ev.x, ev.y);
if (!action)
break;
preSelectedAction = ACTION_NONE;
inputState = INPUT_NORMAL;
return doAction(action, ev);
}
else if (ev.button == SDL_BUTTON_RIGHT && preSelectedAction != ACTION_NONE)
{
preSelectedAction = ACTION_NONE;
inputState = INPUT_NORMAL;
break;
}
// else
default:
// Slight hack: If selection is empty, reset the input state
if (g_Selection.toList().length == 0)
{
preSelectedAction = ACTION_NONE;
inputState = INPUT_NORMAL;
break;
}
}
break;
case INPUT_SELECTING:
switch (ev.type)
{
case "mousemotion":
// If the mouse moved further than a limit, switch to bandbox mode
var dragDeltaX = ev.x - dragStart[0];
var dragDeltaY = ev.y - dragStart[1];
if (Math.abs(dragDeltaX) >= maxDragDelta || Math.abs(dragDeltaY) >= maxDragDelta)
{
inputState = INPUT_BANDBOXING;
return false;
}
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
g_Selection.setHighlightList(ents);
return false;
case "mousebuttonup":
if (ev.button == SDL_BUTTON_LEFT)
{
var ents = Engine.PickEntitiesAtPoint(ev.x, ev.y, SELECTION_SEARCH_RADIUS);
if (!ents.length)
{
if (!Engine.HotkeyIsPressed("selection.add") && !Engine.HotkeyIsPressed("selection.remove"))
{
g_Selection.reset();
resetIdleUnit();
}
inputState = INPUT_NORMAL;
return true;
}
var selectedEntity = ents[0];
var now = new Date();
// If camera following and we select different unit, stop
if (Engine.GetFollowedEntity() != selectedEntity)
{
Engine.CameraFollow(0);
}
if ((now.getTime() - doubleClickTimer < doubleClickTime) && (selectedEntity == prevClickedEntity))
{
// Double click or triple click has occurred
var showOffscreen = Engine.HotkeyIsPressed("selection.offscreen");
var matchRank = true;
var templateToMatch;
// Check for double click or triple click
if (!doubleClicked)
{
// If double click hasn't already occurred, this is a double click.
// Select similar units regardless of rank
templateToMatch = GetEntityState(selectedEntity).identity.selectionGroupName;
if (templateToMatch)
{
matchRank = false;
}
else
{ // No selection group name defined, so fall back to exact match
templateToMatch = GetEntityState(selectedEntity).template;
}
doubleClicked = true;
// Reset the timer so the user has an extra period 'doubleClickTimer' to do a triple-click
doubleClickTimer = now.getTime();
}
else
{
// Double click has already occurred, so this is a triple click.
// Select units matching exact template name (same rank)
templateToMatch = GetEntityState(selectedEntity).template;
}
// TODO: Should we handle "control all units" here as well?
ents = Engine.PickSimilarFriendlyEntities(templateToMatch, showOffscreen, matchRank, false);
}
else
{
// It's single click right now but it may become double or triple click
doubleClicked = false;
doubleClickTimer = now.getTime();
prevClickedEntity = selectedEntity;
// We only want to include the first picked unit in the selection
ents = [ents[0]];
}
// Update the list of selected units
if (Engine.HotkeyIsPressed("selection.add"))
{
g_Selection.addList(ents);
}
else if (Engine.HotkeyIsPressed("selection.remove"))
{
g_Selection.removeList(ents);
}
else
{
g_Selection.reset();
g_Selection.addList(ents);
}
inputState = INPUT_NORMAL;
return true;
}
break;
}
break;
case INPUT_BUILDING_PLACEMENT:
switch (ev.type)
{
case "mousemotion":
placementSupport.position = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
if (placementSupport.mode === "wall")
{
// Including only the on-screen towers in the next snap candidate list is sufficient here, since the user is
// still selecting a starting point (which must necessarily be on-screen). (The update of the snap entities
// itself happens in the call to updateBuildingPlacementPreview below).
placementSupport.wallSnapEntitiesIncludeOffscreen = false;
}
else
{
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
"template": placementSupport.template,
"x": placementSupport.position.x,
"z": placementSupport.position.z,
});
if (snapData)
{
placementSupport.angle = snapData.angle;
placementSupport.position.x = snapData.x;
placementSupport.position.z = snapData.z;
}
}
updateBuildingPlacementPreview(); // includes an update of the snap entity candidates
return false; // continue processing mouse motion
case "mousebuttondown":
if (ev.button == SDL_BUTTON_LEFT)
{
if (placementSupport.mode === "wall")
{
var validPlacement = updateBuildingPlacementPreview();
if (validPlacement !== false)
{
inputState = INPUT_BUILDING_WALL_CLICK;
}
}
else
{
placementSupport.position = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
dragStart = [ ev.x, ev.y ];
inputState = INPUT_BUILDING_CLICK;
}
return true;
}
else if (ev.button == SDL_BUTTON_RIGHT)
{
// Cancel building
placementSupport.Reset();
inputState = INPUT_NORMAL;
return true;
}
break;
case "hotkeydown":
var rotation_step = Math.PI / 12; // 24 clicks make a full rotation
switch (ev.hotkey)
{
case "session.rotate.cw":
placementSupport.angle += rotation_step;
updateBuildingPlacementPreview();
break;
case "session.rotate.ccw":
placementSupport.angle -= rotation_step;
updateBuildingPlacementPreview();
break;
}
break;
}
break;
}
return false;
}
function doAction(action, ev)
{
var selection = g_Selection.toList();
// If shift is down, add the order to the unit's order queue instead
// of running it immediately
var queued = Engine.HotkeyIsPressed("session.queue");
switch (action.type)
{
case "move":
var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
return true;
case "attack-move":
var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
return true;
case "attack":
Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
return true;
case "heal":
Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] });
return true;
case "build": // (same command as repair)
case "repair":
Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] });
return true;
case "gather":
Engine.PostNetworkCommand({"type": "gather", "entities": selection, "target": action.target, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
return true;
case "returnresource":
Engine.PostNetworkCommand({"type": "returnresource", "entities": selection, "target": action.target, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
return true;
case "setup-trade-route":
Engine.PostNetworkCommand({"type": "setup-trade-route", "entities": selection, "target": action.target, "source": undefined, "route": undefined, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_trade", "entity": selection[0] });
return true;
case "garrison":
Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_garrison", "entity": selection[0] });
return true;
case "guard":
Engine.PostNetworkCommand({"type": "guard", "entities": selection, "target": action.target, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_guard", "entity": selection[0] });
return true;
case "remove-guard":
Engine.PostNetworkCommand({"type": "remove-guard", "entities": selection, "target": action.target, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_guard", "entity": selection[0] });
return true;
case "set-rallypoint":
var pos = undefined;
// if there is a position set in the action then use this so that when setting a
// rally point on an entity it is centered on that entity
if (action.position)
{
pos = action.position;
}
else
{
pos = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
}
Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": pos.x, "z": pos.z, "data": action.data, "queued": queued});
// Display rally point at the new coordinates, to avoid display lag
Engine.GuiInterfaceCall("DisplayRallyPoint", {
"entities": selection,
"x": pos.x,
"z": pos.z,
"queued": queued
});
return true;
case "unset-rallypoint":
var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
Engine.PostNetworkCommand({"type": "unset-rallypoint", "entities": selection});
// Remove displayed rally point
Engine.GuiInterfaceCall("DisplayRallyPoint", {
"entities": []
});
return true;
case "none":
return true;
default:
error("Invalid action.type "+action.type);
return false;
}
}
function handleMinimapEvent(target)
{
// Partly duplicated from handleInputAfterGui(), but with the input being
// world coordinates instead of screen coordinates.
if (inputState == INPUT_NORMAL)
{
var fromMinimap = true;
var action = determineAction(undefined, undefined, fromMinimap);
if (!action)
return false;
var selection = g_Selection.toList();
var queued = Engine.HotkeyIsPressed("session.queue");
switch (action.type)
{
case "move":
Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
return true;
case "attack-move":
Engine.PostNetworkCommand({"type": "attack-walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
return true;
case "set-rallypoint":
Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
// Display rally point at the new coordinates, to avoid display lag
Engine.GuiInterfaceCall("DisplayRallyPoint", {
"entities": selection,
"x": target.x,
"z": target.z,
"queued": queued
});
return true;
default:
error("Invalid action.type "+action.type);
}
}
return false;
}
// Called by GUI when user clicks construction button
// @param buildTemplate Template name of the entity the user wants to build
function startBuildingPlacement(buildTemplate, playerState)
{
if(getEntityLimitAndCount(playerState, buildTemplate)[2] == 0)
return;
// TODO: we should clear any highlight selection rings here. If the mouse was over an entity before going onto the GUI
// to start building a structure, then the highlight selection rings are kept during the construction of the building.
// Gives the impression that somehow the hovered-over entity has something to do with the building you're constructing.
placementSupport.Reset();
// find out if we're building a wall, and change the entity appropriately if so
var templateData = GetTemplateData(buildTemplate);
if (templateData.wallSet)
{
placementSupport.mode = "wall";
placementSupport.wallSet = templateData.wallSet;
inputState = INPUT_BUILDING_PLACEMENT;
}
else
{
placementSupport.mode = "building";
placementSupport.template = buildTemplate;
inputState = INPUT_BUILDING_PLACEMENT;
}
if (templateData.attack &&
templateData.attack.Ranged &&
templateData.attack.Ranged.maxRange)
{
// add attack information to display a good tooltip
placementSupport.attack = templateData.attack.Ranged;
}
}
// Called by GUI when user changes required trading goods
function selectRequiredGoods(data)
{
Engine.PostNetworkCommand({"type": "select-required-goods", "entities": data.entities, "requiredGoods": data.requiredGoods});
}
// Called by GUI when user clicks exchange resources button
function exchangeResources(command)
{
Engine.PostNetworkCommand({"type": "barter", "sell": command.sell, "buy": command.buy, "amount": command.amount});
}
// Camera jumping: when the user presses a hotkey the current camera location is marked.
// When they press another hotkey the camera jumps back to that position. If the camera is already roughly at that location,
// jump back to where it was previously.
var jumpCameraPositions = [], jumpCameraLast;
function jumpCamera(index)
{
var position = jumpCameraPositions[index], distanceThreshold = Engine.ConfigDB_GetValue("user", "camerajump.threshold");
if (position)
{
if (jumpCameraLast &&
Math.abs(Engine.CameraGetX() - position.x) < distanceThreshold &&
Math.abs(Engine.CameraGetZ() - position.z) < distanceThreshold)
Engine.CameraMoveTo(jumpCameraLast.x, jumpCameraLast.z);
else
{
jumpCameraLast = {x: Engine.CameraGetX(), z: Engine.CameraGetZ()};
Engine.CameraMoveTo(position.x, position.z);
}
}
}
function setJumpCamera(index)
{
jumpCameraPositions[index] = {x: Engine.CameraGetX(), z: Engine.CameraGetZ()};
}
// Batch training:
// When the user shift-clicks, we set these variables and switch to INPUT_BATCHTRAINING
// When the user releases shift, or clicks on a different training button, we create the batched units
var batchTrainingEntities;
var batchTrainingType;
var batchTrainingCount;
var batchTrainingEntityAllowedCount;
const batchIncrementSize = 5;
function flushTrainingBatch()
{
var appropriateBuildings = getBuildingsWhichCanTrainEntity(batchTrainingEntities, batchTrainingType);
// If training limits don't allow us to train batchTrainingCount in each appropriate building
if (batchTrainingEntityAllowedCount !== undefined &&
batchTrainingEntityAllowedCount < batchTrainingCount * appropriateBuildings.length)
{
// Train as many full batches as we can
var buildingsCountToTrainFullBatch = Math.floor(batchTrainingEntityAllowedCount / batchTrainingCount);
var buildingsToTrainFullBatch = appropriateBuildings.slice(0, buildingsCountToTrainFullBatch);
Engine.PostNetworkCommand({"type": "train", "entities": buildingsToTrainFullBatch,
"template": batchTrainingType, "count": batchTrainingCount});
// Train remainer in one more building
var remainderToTrain = batchTrainingEntityAllowedCount % batchTrainingCount;
Engine.PostNetworkCommand({"type": "train",
"entities": [ appropriateBuildings[buildingsCountToTrainFullBatch] ],
"template": batchTrainingType, "count": remainderToTrain});
}
else
{
Engine.PostNetworkCommand({"type": "train", "entities": appropriateBuildings,
"template": batchTrainingType, "count": batchTrainingCount});
}
}
function getBuildingsWhichCanTrainEntity(entitiesToCheck, trainEntType)
{
return entitiesToCheck.filter(function(entity) {
var state = GetEntityState(entity);
var canTrain = state && state.production && state.production.entities.length &&
state.production.entities.indexOf(trainEntType) != -1;
return canTrain;
});
}
function getEntityLimitAndCount(playerState, entType)
{
var template = GetTemplateData(entType);
var entCategory = null;
if (template.trainingRestrictions)
entCategory = template.trainingRestrictions.category;
else if (template.buildRestrictions)
entCategory = template.buildRestrictions.category;
var entLimit = undefined;
var entCount = undefined;
var entLimitChangers = undefined;
var canBeAddedCount = undefined;
if (entCategory && playerState.entityLimits[entCategory] != null)
{
entLimit = playerState.entityLimits[entCategory];
entCount = playerState.entityCounts[entCategory];
entLimitChangers = playerState.entityLimitChangers[entCategory];
canBeAddedCount = Math.max(entLimit - entCount, 0);
}
return [entLimit, entCount, canBeAddedCount, entLimitChangers];
}
// Add the unit shown at position to the training queue for all entities in the selection
function addTrainingByPosition(position)
{
var simState = GetSimState();
var playerState = simState.players[Engine.GetPlayerID()];
var selection = g_Selection.toList();
if (!selection.length)
return;
var trainableEnts = getAllTrainableEntitiesFromSelection();
// Check if the position is valid
if (!trainableEnts.length || trainableEnts.length <= position)
return;
var entToTrain = trainableEnts[position];
addTrainingToQueue(selection, entToTrain, playerState);
return;
}
// Called by GUI when user clicks training button
function addTrainingToQueue(selection, trainEntType, playerState)
{
// Create list of buildings which can train trainEntType
var appropriateBuildings = getBuildingsWhichCanTrainEntity(selection, trainEntType);
// Check trainEntType entity limit and count
var [trainEntLimit, trainEntCount, canBeTrainedCount] = getEntityLimitAndCount(playerState, trainEntType)
// Batch training possible if we can train at least 2 units
var batchTrainingPossible = canBeTrainedCount == undefined || canBeTrainedCount > 1;
var decrement = Engine.HotkeyIsPressed("selection.remove");
if (!decrement)
var template = GetTemplateData(trainEntType);
if (Engine.HotkeyIsPressed("session.batchtrain") && batchTrainingPossible)
{
if (inputState == INPUT_BATCHTRAINING)
{
// Check if we are training in the same building(s) as the last batch
var sameEnts = false;
if (batchTrainingEntities.length == selection.length)
{
// NOTE: We just check if the arrays are the same and if the order is the same
// If the order changed, we have a new selection and we should create a new batch.
for (var i = 0; i < batchTrainingEntities.length; ++i)
{
if (!(sameEnts = batchTrainingEntities[i] == selection[i]))
break;
}
}
// If we're already creating a batch of this unit (in the same building(s)), then just extend it
// (if training limits allow)
if (sameEnts && batchTrainingType == trainEntType)
{
if (decrement)
{
batchTrainingCount -= batchIncrementSize;
if (batchTrainingCount <= 0)
inputState = INPUT_NORMAL;
}
else if (canBeTrainedCount == undefined ||
canBeTrainedCount > batchTrainingCount * appropriateBuildings.length)
{
if (Engine.GuiInterfaceCall("GetNeededResources", multiplyEntityCosts(
template, batchTrainingCount + batchIncrementSize)))
return;
batchTrainingCount += batchIncrementSize;
}
batchTrainingEntityAllowedCount = canBeTrainedCount;
return;
}
// Otherwise start a new one
else if (!decrement)
{
flushTrainingBatch();
// fall through to create the new batch
}
}
// Don't start a new batch if decrementing or unable to afford it.
if (decrement || Engine.GuiInterfaceCall("GetNeededResources",
multiplyEntityCosts(template, batchIncrementSize)))
return;
inputState = INPUT_BATCHTRAINING;
batchTrainingEntities = selection;
batchTrainingType = trainEntType;
batchTrainingEntityAllowedCount = canBeTrainedCount;
batchTrainingCount = batchIncrementSize;
}
else
{
// Non-batched - just create a single entity in each building
// (but no more than entity limit allows)
var buildingsForTraining = appropriateBuildings;
if (trainEntLimit)
buildingsForTraining = buildingsForTraining.slice(0, canBeTrainedCount);
Engine.PostNetworkCommand({"type": "train", "template": trainEntType,
"count": 1, "entities": buildingsForTraining});
}
}
// Called by GUI when user clicks research button
function addResearchToQueue(entity, researchType)
{
Engine.PostNetworkCommand({"type": "research", "entity": entity, "template": researchType});
}
// Returns the number of units that will be present in a batch if the user clicks
// the training button with shift down
function getTrainingBatchStatus(playerState, entity, trainEntType, selection)
{
var appropriateBuildings = [entity];
if (selection && selection.indexOf(entity) != -1)
appropriateBuildings = getBuildingsWhichCanTrainEntity(selection, trainEntType);
var nextBatchTrainingCount = 0;
var currentBatchTrainingCount = 0;
if (inputState == INPUT_BATCHTRAINING && batchTrainingEntities.indexOf(entity) != -1 &&
batchTrainingType == trainEntType)
{
nextBatchTrainingCount = batchTrainingCount;
currentBatchTrainingCount = batchTrainingCount;
var canBeTrainedCount = batchTrainingEntityAllowedCount;
}
else
{
var [trainEntLimit, trainEntCount, canBeTrainedCount] =
getEntityLimitAndCount(playerState, trainEntType);
var batchSize = Math.min(canBeTrainedCount, batchIncrementSize);
}
// We need to calculate count after the next increment if it's possible
if (canBeTrainedCount == undefined ||
canBeTrainedCount > nextBatchTrainingCount * appropriateBuildings.length)
nextBatchTrainingCount += batchIncrementSize;
// If training limits don't allow us to train batchTrainingCount in each appropriate building
// train as many full batches as we can and remainer in one more building.
var buildingsCountToTrainFullBatch = appropriateBuildings.length;
var remainderToTrain = 0;
if (canBeTrainedCount !== undefined &&
canBeTrainedCount < nextBatchTrainingCount * appropriateBuildings.length)
{
buildingsCountToTrainFullBatch = Math.floor(canBeTrainedCount / nextBatchTrainingCount);
remainderToTrain = canBeTrainedCount % nextBatchTrainingCount;
}
return [buildingsCountToTrainFullBatch, nextBatchTrainingCount, remainderToTrain, currentBatchTrainingCount];
}
// Called by GUI when user clicks production queue item
function removeFromProductionQueue(entity, id)
{
Engine.PostNetworkCommand({"type": "stop-production", "entity": entity, "id": id});
}
// Called by unit selection buttons
function changePrimarySelectionGroup(templateName, deselectGroup)
{
if (Engine.HotkeyIsPressed("session.deselectgroup") || deselectGroup)
g_Selection.makePrimarySelection(templateName, true);
else
g_Selection.makePrimarySelection(templateName, false);
}
// Performs the specified command (delete, town bell, repair, etc.)
function performCommand(entity, commandName)
{
if (entity)
{
var entState = GetExtendedEntityState(entity);
var playerID = Engine.GetPlayerID();
if (entState.player == playerID || g_DevSettings.controlAll)
{
switch (commandName)
{
case "delete":
var selection = g_Selection.toList();
if (selection.length > 0)
if (!entState.resourceSupply || !entState.resourceSupply.killBeforeGather)
openDeleteDialog(selection);
break;
case "stop":
var selection = g_Selection.toList();
if (selection.length > 0)
stopUnits(selection);
break;
case "garrison":
inputState = INPUT_PRESELECTEDACTION;
preSelectedAction = ACTION_GARRISON;
break;
case "repair":
inputState = INPUT_PRESELECTEDACTION;
preSelectedAction = ACTION_REPAIR;
break;
case "add-guard":
inputState = INPUT_PRESELECTEDACTION;
preSelectedAction = ACTION_GUARD;
break;
case "remove-guard":
removeGuard();
break;
case "unload-all":
unloadAll();
break;
case "focus-rally":
// if the selected building has a rally point set, move the camera to it; otherwise, move to the building itself
// (since that's where units will spawn without a rally point)
var focusTarget = null;
if (entState.rallyPoint && entState.rallyPoint.position)
{
focusTarget = entState.rallyPoint.position;
}
else
{
if (entState.position)
focusTarget = entState.position;
}
if (focusTarget !== null)
Engine.CameraMoveTo(focusTarget.x, focusTarget.z);
break;
case "back-to-work":
backToWork();
break;
case "increase-alert-level":
increaseAlertLevel();
break;
case "alert-end":
endOfAlert();
break;
case "select-trading-goods":
toggleTrade();
break;
default:
break;
}
}
}
}
// Performs the specified formation
function performFormation(entity, formationTemplate)
{
if (entity)
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "formation",
"entities": selection,
"name": formationTemplate
});
}
}
// Performs the specified group
function performGroup(action, groupId)
{
switch (action)
{
case "snap":
case "select":
case "add":
var toSelect = [];
g_Groups.update();
for (var ent in g_Groups.groups[groupId].ents)
toSelect.push(+ent);
if (action != "add")
g_Selection.reset();
g_Selection.addList(toSelect);
if (action == "snap" && toSelect.length)
Engine.CameraFollow(toSelect[0]);
break;
case "save":
case "breakUp":
g_Groups.groups[groupId].reset();
if (action == "save")
g_Groups.addEntities(groupId, g_Selection.toList());
updateGroups();
break;
}
}
// Performs the specified stance
function performStance(entity, stanceName)
{
if (entity)
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "stance",
"entities": selection,
"name": stanceName
});
}
}
// Lock / Unlock the gate
function lockGate(lock)
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "lock-gate",
"entities": selection,
"lock": lock,
});
}
// Pack / unpack unit(s)
function packUnit(pack)
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "pack",
"entities": selection,
"pack": pack,
"queued": false
});
}
// Cancel un/packing unit(s)
function cancelPackUnit(pack)
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "cancel-pack",
"entities": selection,
"pack": pack,
"queued": false
});
}
// Transform a wall to a gate
function transformWallToGate(template)
{
var selection = g_Selection.toList();
Engine.PostNetworkCommand({
"type": "wall-to-gate",
"entities": selection.filter( function(e) { return getWallGateTemplate(e) == template } ),
"template": template,
});
}
// Gets the gate form (if any) of a given long wall piece
function getWallGateTemplate(entity)
{
// TODO: find the gate template name in a better way
var entState = GetEntityState(entity);
var index;
if (entState && !entState.foundation && hasClass(entState, "LongWall") && (index = entState.template.indexOf("long")) >= 0)
return entState.template.substr(0, index) + "gate";
return undefined;
}
// Set the camera to follow the given unit
function setCameraFollow(entity)
{
// Follow the given entity if it's a unit
if (entity)
{
var entState = GetEntityState(entity);
if (entState && hasClass(entState, "Unit"))
{
Engine.CameraFollow(entity);
return;
}
}
// Otherwise stop following
Engine.CameraFollow(0);
}
var lastIdleUnit = 0;
var currIdleClass = 0;
var lastIdleType = undefined;
function resetIdleUnit()
{
lastIdleUnit = 0;
currIdleClass = 0;
lastIdleType = undefined;
}
function findIdleUnit(classes)
{
var append = Engine.HotkeyIsPressed("selection.add");
var selectall = Engine.HotkeyIsPressed("selection.offscreen");
// Reset the last idle unit, etc., if the selection type has changed.
var type = classes.join();
if (selectall || type != lastIdleType)
resetIdleUnit();
lastIdleType = type;
// If selectall is true, there is no limit and it's necessary to iterate
// over all of the classes, resetting only when the first match is found.
var matched = false;
for (var i = 0; i < classes.length; ++i)
{
var data = {
"idleClass": classes[currIdleClass],
"prevUnit": lastIdleUnit,
"limit": 1,
"excludeUnits": []
};
if (append)
data.excludeUnits = g_Selection.toList();
if (selectall)
data = { idleClass: classes[currIdleClass] };
// Check if we have new valid entity
var idleUnits = Engine.GuiInterfaceCall("FindIdleUnits", data);
if (idleUnits.length && idleUnits[0] != lastIdleUnit)
{
lastIdleUnit = idleUnits[0];
if (!append && (!selectall || selectall && !matched))
g_Selection.reset()
if (selectall)
g_Selection.addList(idleUnits);
else
{
g_Selection.addList([lastIdleUnit]);
var position = GetEntityState(lastIdleUnit).position;
if (position)
Engine.CameraMoveTo(position.x, position.z);
return;
}
matched = true;
}
lastIdleUnit = 0;
currIdleClass = (currIdleClass + 1) % classes.length;
}
// TODO: display a message or play a sound to indicate no more idle units, or something
// Reset for next cycle
resetIdleUnit();
}
function stopUnits(entities)
{
Engine.PostNetworkCommand({ "type": "stop", "entities": entities, "queued": false });
}
function unload(garrisonHolder, entities)
{
if (Engine.HotkeyIsPressed("session.unloadtype"))
Engine.PostNetworkCommand({"type": "unload", "entities": entities, "garrisonHolder": garrisonHolder});
else
Engine.PostNetworkCommand({"type": "unload", "entities": [entities[0]], "garrisonHolder": garrisonHolder});
}
function unloadTemplate(template)
{
// Filter out all entities that aren't garrisonable.
var garrisonHolders = g_Selection.toList().filter(function(e) {
var state = GetEntityState(e);
if (state && state.garrisonHolder)
return true;
return false;
});
Engine.PostNetworkCommand({
"type": "unload-template",
"all": Engine.HotkeyIsPressed("session.unloadtype"),
"template": template,
"garrisonHolders": garrisonHolders
});
}
function unloadAll()
{
// Filter out all entities that aren't garrisonable.
var garrisonHolders = g_Selection.toList().filter(function(e) {
var state = GetEntityState(e);
if (state && state.garrisonHolder)
return true;
return false;
});
Engine.PostNetworkCommand({"type": "unload-all", "garrisonHolders": garrisonHolders});
}
function backToWork()
{
// Filter out all entities that can't go back to work.
var workers = g_Selection.toList().filter(function(e) {
var state = GetEntityState(e);
return (state && state.unitAI && state.unitAI.hasWorkOrders);
});
Engine.PostNetworkCommand({"type": "back-to-work", "entities": workers});
}
function removeGuard()
{
// Filter out all entities that are currently guarding/escorting.
var entities = g_Selection.toList().filter(function(e) {
var state = GetEntityState(e);
return (state && state.unitAI && state.unitAI.isGuarding);
});
Engine.PostNetworkCommand({"type": "remove-guard", "entities": entities});
}
function increaseAlertLevel()
{
var entities = g_Selection.toList().filter(function(e) {
var state = GetEntityState(e);
return (state && state.alertRaiser && state.alertRaiser.canIncreaseLevel);
});
Engine.PostNetworkCommand({"type": "increase-alert-level", "entities": entities});
}
function endOfAlert()
{
var entities = g_Selection.toList().filter(function(e) {
var state = GetEntityState(e);
return (state && state.alertRaiser && state.alertRaiser.hasRaisedAlert);
});
Engine.PostNetworkCommand({"type": "alert-end", "entities": entities});
}
function clearSelection()
{
if(inputState==INPUT_BUILDING_PLACEMENT || inputState==INPUT_BUILDING_WALL_PATHING)
{
inputState = INPUT_NORMAL;
placementSupport.Reset();
}
else
g_Selection.reset();
preSelectedAction = ACTION_NONE;
}
+
Index: ps/trunk/binaries/data/mods/public/gui/options/options.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 14954)
@@ -1,197 +1,197 @@
/**
* This array holds the data to populate the general section with.
* Data is in the form [Title, Tooltip, {ActionType:Action}, InputType].
*/
var options = {
"generalSetting":
[
- ["Windowed Mode", "Start 0 A.D. in windowed mode", {"config":"windowed"}, "boolean"],
- ["Background Pause", "Pause single player games when window loses focus", {"config":"pauseonfocusloss"}, "boolean"],
+ [translate("Windowed Mode"), translate("Start 0 A.D. in windowed mode"), {"config":"windowed"}, "boolean"],
+ [translate("Background Pause"), translate("Pause single player games when window loses focus"), {"config":"pauseonfocusloss"}, "boolean"],
],
"graphicsSetting":
[
- ["Prefer GLSL", "Use OpenGL 2.0 shaders (recommended)", {"renderer":"PreferGLSL"}, "boolean"],
- ["Shadows", "Enable shadows", {"renderer":"Shadows"}, "boolean"],
- ["Particles", "Enable particles", {"renderer":"Particles"}, "boolean"],
- ["Show Sky", "Render Sky", {"renderer":"ShowSky"}, "boolean"],
- ["Unit Silhouettes", "Show outlines of units behind buildings", {"renderer":"Silhouettes"}, "boolean"],
- ["Shadow Flitering", "Smooth shadows", {"renderer":"ShadowPCF"}, "boolean"],
- ["HQ Waviness", "Use real normals for ocean-wave rendering, instead of applying them as a flat texture", {"renderer":"WaterNormal"}, "boolean"],
- ["Real Water Depth", "Use actual water depth in rendering calculations", {"renderer":"WaterRealDepth"}, "boolean"],
- ["Water Reflections", "Allow water to reflect a mirror image", {"renderer":"WaterReflection"}, "boolean"],
- ["Water Refraction", "Use a real water refraction map and not transparency", {"renderer":"WaterRefraction"}, "boolean"],
- ["Shore Foam", "Show foam on water near shore depending on water waviness", {"renderer":"WaterFoam"}, "boolean"],
- ["Shore Waves", "Show breaking waves on water near shore (Requires HQ Waviness)", {"renderer":"WaterCoastalWaves"}, "boolean"],
- ["Water Shadows", "Cast shadows on water", {"renderer":"WaterShadow"}, "boolean"],
+ [translate("Prefer GLSL"), translate("Use OpenGL 2.0 shaders (recommended)"), {"renderer":"PreferGLSL"}, "boolean"],
+ [translate("Shadows"), translate("Enable shadows"), {"renderer":"Shadows"}, "boolean"],
+ [translate("Particles"), translate("Enable particles"), {"renderer":"Particles"}, "boolean"],
+ [translate("Show Sky"), translate("Render Sky"), {"renderer":"ShowSky"}, "boolean"],
+ [translate("Unit Silhouettes"), translate("Show outlines of units behind buildings"), {"renderer":"Silhouettes"}, "boolean"],
+ [translate("Shadow Filtering"), translate("Smooth shadows"), {"renderer":"ShadowPCF"}, "boolean"],
+ [translate("HQ Waviness"), translate("Use real normals for ocean-wave rendering, instead of applying them as a flat texture"), {"renderer":"WaterNormal"}, "boolean"],
+ [translate("Real Water Depth"), translate("Use actual water depth in rendering calculations"), {"renderer":"WaterRealDepth"}, "boolean"],
+ [translate("Water Reflections"), translate("Allow water to reflect a mirror image"), {"renderer":"WaterReflection"}, "boolean"],
+ [translate("Water Refraction"), translate("Use a real water refraction map and not transparency"), {"renderer":"WaterRefraction"}, "boolean"],
+ [translate("Shore Foam"), translate("Show foam on water near shore depending on water waviness"), {"renderer":"WaterFoam"}, "boolean"],
+ [translate("Shore Waves"), translate("Show breaking waves on water near shore (Requires HQ Waviness)"), {"renderer":"WaterCoastalWaves"}, "boolean"],
+ [translate("Water Shadows"), translate("Cast shadows on water"), {"renderer":"WaterShadow"}, "boolean"],
],
"soundSetting":
[
- ["Master Gain", "Master audio gain", {"config":"sound.mastergain", "function":"Engine.SetMasterGain(Number(this.caption));"}, "number"],
- ["Music Gain", "In game music gain", {"config":"sound.musicgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
- ["Ambient Gain", "In game ambient sound gain", {"config":"sound.ambientgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
- ["Action Gain", "In game unit action sound gain", {"config":"sound.actiongain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
- ["UI Gain", "UI sound gain", {"config":"sound.uigain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
+ [translate("Master Gain"), translate("Master audio gain"), {"config":"sound.mastergain", "function":"Engine.SetMasterGain(Number(this.caption));"}, "number"],
+ [translate("Music Gain"), translate("In game music gain"), {"config":"sound.musicgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
+ [translate("Ambient Gain"), translate("In game ambient sound gain"), {"config":"sound.ambientgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
+ [translate("Action Gain"), translate("In game unit action sound gain"), {"config":"sound.actiongain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
+ [translate("UI Gain"), translate("UI sound gain"), {"config":"sound.uigain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
],
"lobbySetting":
[
- ["Chat Backlog", "Number of backlogged messages to load when joining the lobby", {"config":"lobby.history"}, "number"],
- ["Chat Timestamp", "Show time that messages are posted in the lobby chat", {"config":"lobby.chattimestamp"}, "boolean"],
+ [translate("Chat Backlog"), translate("Number of backlogged messages to load when joining the lobby"), {"config":"lobby.history"}, "number"],
+ [translate("Chat Timestamp"), translate("Show time that messages are posted in the lobby chat"), {"config":"lobby.chattimestamp"}, "boolean"],
],
};
function init()
{
// WARNING: We assume a strict formatting of the XML and do minimal checking.
for each (var prefix in Object.keys(options))
{
var lastSize;
for (var i = 0; i < options[prefix].length; i++)
{
var body = Engine.GetGUIObjectByName(prefix + "[" + i + "]");
var label = Engine.GetGUIObjectByName(prefix + "Label[" + i + "]");
// Setup control.
setupControl(options[prefix][i], i, prefix);
// Setup label.
label.caption = options[prefix][i][0];
label.tooltip = options[prefix][i][1];
// Move each element to the correct place.
if (i > 0)
{
var newSize = new GUISize();
newSize.left = lastSize.left;
newSize.rright = lastSize.rright;
newSize.top = lastSize.bottom;
newSize.bottom = newSize.top + 25;
body.size = newSize;
lastSize = newSize;
}
else
{
lastSize = body.size;
}
// Show element.
body.hidden = false;
}
}
}
/**
* Setup the apropriate control for a given option.
*
* @param option Structure containing the data to setup an option.
* @param prefix Prefix to use when accessing control, for example "generalSetting" when the tickbox name is generalSettingTickbox[i].
*/
function setupControl(option, i, prefix)
{
switch (option[3])
{
case "boolean":
var control = Engine.GetGUIObjectByName(prefix + "Tickbox[" + i + "]");
var checked;
var onPress = function(){};
// Different option action load and save differently, so this switch is needed.
for each (var action in Object.keys(option[2]))
{
switch (action)
{
case "config":
// Load initial value if not yet loaded.
if (!checked || typeof checked != boolean)
checked = Engine.ConfigDB_GetValue("user", option[2][action]) === "true" ? true : false;
// Hacky macro to create the callback.
var callback = function(key)
{
return function()
{
Engine.ConfigDB_CreateValue("user", key, String(this.checked));
};
}(option[2][action]);
// Merge the new callback with any existing callbacks.
onPress = mergeFunctions(callback, onPress);
break;
case "renderer":
// Load initial value if not yet loaded.
if (!checked || typeof checked != boolean)
checked = eval("Engine.Renderer_Get" + option[2][action] + "Enabled()");
// Hacky macro to create the callback.
var callback = function(key)
{
return function()
{
eval("Engine.Renderer_Set" + key + "Enabled(" + this.checked + ")");
};
}(option[2][action]);
// Merge the new callback with any existing callbacks.
onPress = mergeFunctions(callback, onPress);
break;
case "function":
// This allows for doing low-level actions, like hiding/showing UI elements.
onPress = mergeFunctions(eval("function(){" + option[2][action] + "}"), onPress);
break;
default:
warn("Unknown option source type '" + action + "'");
}
}
// Load final data to the control element.
control.checked = checked;
control.onPress = onPress;
break;
case "number":
// TODO: Slider
case "string":
var control = Engine.GetGUIObjectByName(prefix + "Input[" + i + "]");
var caption;
var onPress = function(){};
for each (var action in Object.keys(option[2]))
{
switch (action)
{
case "config":
// Load initial value if not yet loaded.
if (!checked || typeof checked != boolean)
caption = Engine.ConfigDB_GetValue("user", option[2][action]);;
// Hacky macro to create the callback.
var callback = function(key)
{
return function()
{
Engine.ConfigDB_CreateValue("user", key, String(this.caption));
};
}(option[2][action]);
// Merge the new callback with any existing callbacks.
onPress = mergeFunctions(callback, onPress);
break;
case "function":
// This allows for doing low-level actions, like hiding/showing UI elements.
onPress = mergeFunctions(function(){eval(option[2][action])}, onPress);
break;
default:
warn("Unknown option source type '" + action + "'");
}
}
control.caption = caption;
control.onPress = onPress;
break;
default:
warn("Unknown option type '" + options[3] + "', assuming string. Valid types are 'number', 'string', or 'bool'.");
var control = Engine.GetGUIObjectByName(prefix + "Input[" + i + "]");
break;
}
control.hidden = false;
control.tooltip = option[1];
return control;
}
/**
* Merge two functions which don't expect arguments.
*
* @return Merged function.
*/
function mergeFunctions(function1, function2)
{
return function()
{
function1.apply(this);
function2.apply(this);
};
}
Index: ps/trunk/binaries/data/mods/public/gui/page_locale_advanced.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/page_locale_advanced.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/page_locale_advanced.xml (revision 14954)
@@ -0,0 +1,16 @@
+
+
+
+ common/modern/styles.xml
+ common/modern/sprites.xml
+ common/modern/setup.xml
+ common/setup.xml
+ common/styles.xml
+ common/sprite1.xml
+ common/common_sprites.xml
+ common/common_styles.xml
+
+ locale/locale_advanced.xml
+
+ common/global.xml
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/page_locale_advanced.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/plain
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/gui/savedgames/load.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/savedgames/load.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/savedgames/load.js (revision 14954)
@@ -1,112 +1,118 @@
var gameMetadatas = [];
function init()
{
var gameSelection = Engine.GetGUIObjectByName("gameSelection");
var savedGames = Engine.GetSavedGames();
if (savedGames.length == 0)
{
- gameSelection.list = [ "No saved games found" ];
+ gameSelection.list = [translate("No saved games found")];
gameSelection.selected = 0;
Engine.GetGUIObjectByName("loadGameButton").enabled = false;
Engine.GetGUIObjectByName("deleteGameButton").enabled = false;
return;
}
savedGames.sort(sortDecreasingDate);
// get current game version and loaded mods
var engineInfo = Engine.GetEngineInfo();
var gameListIds = [ game.id for each (game in savedGames) ];
var gameListLabels = [ generateLabel(game.metadata, engineInfo) for each (game in savedGames) ];
gameMetadatas = [ game.metadata for each (game in savedGames) ];
gameSelection.list = gameListLabels;
gameSelection.list_data = gameListIds;
if (gameSelection.selected == -1)
gameSelection.selected = 0;
}
function loadGame()
{
var gameSelection = Engine.GetGUIObjectByName("gameSelection");
var gameId = gameSelection.list_data[gameSelection.selected];
var gameLabel = gameSelection.list[gameSelection.selected];
var metadata = gameMetadatas[gameSelection.selected];
// check game compatibility before really loading it
var engineInfo = Engine.GetEngineInfo();
if (!hasSameVersion(metadata, engineInfo) || !hasSameMods(metadata, engineInfo))
{
// version not compatible ... ask for confirmation
- var btCaptions = ["Yes", "No"];
+ var btCaptions = [translate("Yes"), translate("No")];
var btCode = [function(){ reallyLoadGame(gameId); }, init];
- var message = "This saved game may not be compatible:";
+ var message = translate("This saved game may not be compatible:");
if (!hasSameVersion(metadata, engineInfo))
- message += "\nIt needs 0AD version " + metadata.version_major
- + " while you are running version " + engineInfo.version_major + ".";
+ message += "\n" + sprintf(translate("It needs 0 A.D. version %(requiredVersion)s, while you are running version %(currentVersion)s."), {
+ requiredVersion: metadata.version_major,
+ currentVersion: engineInfo.version_major
+ });
if (!hasSameMods(metadata, engineInfo))
{
if (!metadata.mods) // only for backwards compatibility with previous saved games
metadata.mods = [];
if (metadata.mods.length == 0)
- message += "\nIt does not need any mod"
- + " while you are running with \"" + engineInfo.mods.join() + "\".";
+ message += "\n" + sprintf(translate("It does not need any mod while you are running with \"%(currentMod)s\"."), {
+ currentMod: engineInfo.mods.join()
+ });
else if (engineInfo.mods.length == 0)
- message += "\nIt needs the mod \"" + metadata.mods.join() + "\""
- + " while you are running without mod.";
+ message += "\n" + sprintf(translate("It needs the mod \"%(requiredMod)s\" while you are running without a mod."), {
+ requiredMod: metadata.mods.join()
+ });
else
- message += "\nIt needs the mod \"" + metadata.mods.join() + "\""
- + " while you are running with \"" + engineInfo.mods.join() + "\".";
+ message += "\n" + sprintf(translate("It needs the mod \"%(requiredMod)s\" while you are running with \"%(currentMod)s\"."), {
+ requiredMod: metadata.mods.join(),
+ currentMod: engineInfo.mods.join()
+ });
}
- message += "\nDo you still want to proceed ?";
- messageBox(500, 250, message, "Warning", 0, btCaptions, btCode);
+ message += "\n" + translate("Do you still want to proceed?");
+ messageBox(500, 250, message, translate("Warning"), 0, btCaptions, btCode);
}
else
reallyLoadGame(gameId);
}
function reallyLoadGame(gameId)
{
var metadata = Engine.StartSavedGame(gameId);
if (!metadata)
{
// Probably the file wasn't found
// Show error and refresh saved game list
- error("Could not load saved game '"+gameId+"'");
+ error(sprintf("Could not load saved game '%(id)s'", { id: gameId }));
init();
}
else
{
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": metadata.initAttributes,
"isNetworked" : false,
"playerAssignments": metadata.gui.playerAssignments,
"savedGUIData": metadata.gui
});
}
}
function deleteGame()
{
var gameSelection = Engine.GetGUIObjectByName("gameSelection");
var gameLabel = gameSelection.list[gameSelection.selected];
var gameID = gameSelection.list_data[gameSelection.selected];
// Ask for confirmation
- var btCaptions = ["Yes", "No"];
+ var btCaptions = [translate("Yes"), translate("No")];
var btCode = [function(){ reallyDeleteGame(gameID); }, null];
- messageBox(500, 200, "\""+gameLabel+"\"\nSaved game will be permanently deleted, are you sure?", "DELETE", 0, btCaptions, btCode);
+ messageBox(500, 200, sprintf(translate("\"%(label)s\""), { label: gameLabel }) + "\n" + translate("Saved game will be permanently deleted, are you sure?"), translate("DELETE"), 0, btCaptions, btCode);
}
function reallyDeleteGame(gameID)
{
if (!Engine.DeleteSavedGame(gameID))
- error("Could not delete saved game '"+gameID+"'");
+ error(sprintf("Could not delete saved game '%(id)s'", { id: gameID }));
// Run init again to refresh saved game list
init();
}
Index: ps/trunk/binaries/data/mods/public/gui/savedgames/save.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/savedgames/save.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/savedgames/save.xml (revision 14954)
@@ -1,51 +1,53 @@
- Save Game
+ Save Game
selectDescription();
- Description:
+
+ Description:
+ saveGame();
- Save
+ SavesaveGame();
- Delete
+ DeletedeleteGame();
- Cancel
+ CancelcloseSave(true);
Index: ps/trunk/binaries/data/mods/public/gui/session/messages.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 14954)
@@ -1,567 +1,646 @@
// Chat data
const CHAT_TIMEOUT = 30000;
const MAX_NUM_CHAT_LINES = 20;
var chatMessages = [];
var chatTimers = [];
// Notification Data
const NOTIFICATION_TIMEOUT = 10000;
const MAX_NUM_NOTIFICATION_LINES = 3;
var notifications = [];
var notificationsTimers = [];
var cheats = getCheatsData();
function getCheatsData()
{
var cheats = {};
var cheatFileList = getJSONFileList("simulation/data/cheats/");
for each (var fileName in cheatFileList)
{
var currentCheat = parseJSONData("simulation/data/cheats/"+fileName+".json");
if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1)
- warn("Cheat name '"+currentCheat.Name+"' is already present");
+ warn(sprintf("Cheat name '%(name)s' is already present", { name: currentCheat.Name }));
else
cheats[currentCheat.Name] = currentCheat.Data;
}
return cheats;
}
// Notifications
function handleNotifications()
{
var notification = Engine.GuiInterfaceCall("GetNextNotification");
if (!notification)
return;
if (notification.type === undefined)
notification.type = "text";
// Handle chat notifications specially
if (notification.type == "chat")
{
var guid = findGuidForPlayerID(g_PlayerAssignments, notification.player);
if (guid == undefined)
{
addChatMessage({
"type": "message",
"guid": -1,
"player": notification.player,
"text": notification.message
});
} else {
addChatMessage({
"type": "message",
"guid": findGuidForPlayerID(g_PlayerAssignments, notification.player),
"text": notification.message
});
}
}
else if (notification.type == "defeat")
{
addChatMessage({
"type": "defeat",
"guid": findGuidForPlayerID(g_PlayerAssignments, notification.player),
"player": notification.player
});
// If the diplomacy panel is open refresh it.
if (isDiplomacyOpen)
openDiplomacy();
}
else if (notification.type == "diplomacy")
{
addChatMessage({
"type": "diplomacy",
"player": notification.player,
"player1": notification.player1,
"status": notification.status
});
// If the diplomacy panel is open refresh it.
if (isDiplomacyOpen)
openDiplomacy();
}
else if (notification.type == "quit")
{
// Used for AI testing
exit();
}
else if (notification.type == "tribute")
{
addChatMessage({
"type": "tribute",
"player": notification.player,
"player1": notification.player1,
"amounts": notification.amounts
});
}
else if (notification.type == "attack")
{
if (notification.player == Engine.GetPlayerID())
{
if (Engine.ConfigDB_GetValue("user", "gui.session.attacknotificationmessage") === "true")
{
addChatMessage({
"type": "attack",
"player": notification.player,
"attacker": notification.attacker
});
}
}
}
else if (notification.type == "text")
{
// Only display notifications directed to this player
if (notification.player == Engine.GetPlayerID())
{
notifications.push(notification);
notificationsTimers.push(setTimeout(removeOldNotifications, NOTIFICATION_TIMEOUT));
if (notifications.length > MAX_NUM_NOTIFICATION_LINES)
removeOldNotifications();
else
displayNotifications();
}
}
else
{
warn("notification of unknown type!");
}
}
function removeOldNotifications()
{
clearTimeout(notificationsTimers[0]); // The timer only needs to be cleared when new notifications bump old notifications off
notificationsTimers.shift();
notifications.shift();
displayNotifications();
}
function displayNotifications()
{
var messages = [];
for each (var n in notifications)
- messages.push(n.message);
+ {
+ var parameters = n.parameters || {};
+ if (n.translateParameters && n.translateParameters.length)
+ translateObjectKeys(parameters, n.translateParameters);
+ var message = n.message;
+ if (n.translateMessage)
+ message = translate(message);
+ messages.push(sprintf(message, parameters));
+ }
Engine.GetGUIObjectByName("notificationText").caption = messages.join("\n");
}
function updateTimeNotifications()
{
- Engine.GetGUIObjectByName("timeNotificationText").caption = Engine.GuiInterfaceCall("GetTimeNotificationText");
+ var notifications = Engine.GuiInterfaceCall("GetTimeNotifications");
+ var notificationText = "";
+ for (var n of notifications)
+ {
+ var message = n.message;
+ if (n.translateMessage)
+ message = translate(message);
+ var parameters = n.parameters || {};
+ if (n.translateParameters && n.translateParameters.length)
+ translateObjectKeys(parameters, n.translateParameters);
+ parameters.time = timeToString(n.time);
+ notificationText += sprintf(message, parameters) + "\n";
+ }
+ Engine.GetGUIObjectByName("timeNotificationText").caption = notificationText;
}
// Returns [username, playercolor] for the given player
function getUsernameAndColor(player)
{
// This case is hit for AIs, whose names don't exist in playerAssignments.
var color = g_Players[player].color;
return [
escapeText(g_Players[player].name),
color.r + " " + color.g + " " + color.b,
];
}
// Messages
function handleNetMessage(message)
{
- log("Net message: " + uneval(message));
+ log(sprintf(translate("Net message: %(message)s"), { message: uneval(message) }));
switch (message.type)
{
case "netstatus":
// If we lost connection, further netstatus messages are useless
if (g_Disconnected)
return;
var obj = Engine.GetGUIObjectByName("netStatus");
switch (message.status)
{
case "waiting_for_players":
- obj.caption = "Waiting for other players to connect...";
+ obj.caption = translate("Waiting for other players to connect...");
obj.hidden = false;
break;
case "join_syncing":
- obj.caption = "Synchronising gameplay with other players...";
+ obj.caption = translate("Synchronising gameplay with other players...");
obj.hidden = false;
break;
case "active":
obj.caption = "";
obj.hidden = true;
break;
case "connected":
- obj.caption = "Connected to the server.";
+ obj.caption = translate("Connected to the server.");
obj.hidden = false;
break;
case "authenticated":
- obj.caption = "Connection to the server has been authenticated.";
+ obj.caption = translate("Connection to the server has been authenticated.");
obj.hidden = false;
break;
case "disconnected":
g_Disconnected = true;
- obj.caption = "Connection to the server has been lost.\n\nThe game has ended.";
+ obj.caption = translate("Connection to the server has been lost.") + "\n\n" + translate("The game has ended.");
obj.hidden = false;
break;
default:
- error("Unrecognised netstatus type "+message.status);
+ error(sprintf("Unrecognised netstatus type %(netType)s", { netType: message.status }));
break;
}
break;
case "players":
// Find and report all leavings
for (var host in g_PlayerAssignments)
{
if (! message.hosts[host])
{
// Tell the user about the disconnection
addChatMessage({ "type": "disconnect", "guid": host });
// Update the cached player data, so we can display the disconnection status
updatePlayerDataRemove(g_Players, host);
}
}
// Find and report all joinings
for (var host in message.hosts)
{
if (! g_PlayerAssignments[host])
{
// Update the cached player data, so we can display the correct name
updatePlayerDataAdd(g_Players, host, message.hosts[host]);
// Tell the user about the connection
addChatMessage({ "type": "connect", "guid": host }, message.hosts);
}
}
g_PlayerAssignments = message.hosts;
if (g_IsController)
{
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ]
Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", "));
}
break;
case "chat":
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
break;
// To prevent errors, ignore these message types that occur during autostart
case "gamesetup":
case "start":
break;
default:
- error("Unrecognised net message type "+message.type);
+ error(sprintf("Unrecognised net message type %(messageType)s", { messageType: message.type }));
}
}
function submitChatDirectly(text)
{
if (text.length)
{
if (g_IsNetworked)
Engine.SendNetworkChat(text);
else
addChatMessage({ "type": "message", "guid": "local", "text": text });
}
}
function submitChatInput()
{
var input = Engine.GetGUIObjectByName("chatInput");
var text = input.caption;
var isCheat = false;
if (text.length)
{
if (!g_IsObserver && g_Players[Engine.GetPlayerID()].cheatsEnabled)
{
for each (var cheat in Object.keys(cheats))
{
// Line must start with the cheat.
if (text.indexOf(cheat) !== 0)
continue;
// test for additional parameter which is the rest of the string after the cheat
var parameter = "";
if (cheats[cheat].DefaultParameter !== undefined)
{
var par = text.substr(cheat.length);
par = par.replace(/^\W+/, '').replace(/\W+$/, ''); // remove whitespaces at start and end
// check, if the isNumeric flag is set
if (cheats[cheat].isNumeric)
{
// Match the first word in the substring.
var match = par.match(/\S+/);
if (match && match[0])
par = Math.floor(match[0]);
// check, if valid number could be parsed
if (par <= 0 || isNaN(par))
par = "";
}
// replace default parameter, if not empty or number
if (par.length > 0 || parseFloat(par) === par)
parameter = par;
else
parameter = cheats[cheat].DefaultParameter;
}
Engine.PostNetworkCommand({
"type": "cheat",
"action": cheats[cheat].Action,
"parameter": parameter,
"text": cheats[cheat].Type,
"selected": g_Selection.toList(),
"templates": cheats[cheat].Templates,
"player": Engine.GetPlayerID()});
isCheat = true;
break;
}
}
if (!isCheat)
{
if (Engine.GetGUIObjectByName("toggleTeamChat").checked)
text = "/team " + text;
if (g_IsNetworked)
Engine.SendNetworkChat(text);
else
addChatMessage({ "type": "message", "guid": "local", "text": text });
}
input.caption = ""; // Clear chat input
}
input.blur(); // Remove focus
toggleChatWindow();
}
function addChatMessage(msg, playerAssignments)
{
// Default to global assignments, but allow overriding for when reporting
// new players joining
if (!playerAssignments)
playerAssignments = g_PlayerAssignments;
var playerColor, username;
- // No prefix by default. May be set by parseChatCommands().
- msg.prefix = "";
+ // No context by default. May be set by parseChatCommands().
+ msg.context = "";
if (playerAssignments[msg.guid])
{
var n = playerAssignments[msg.guid].player;
// Observers have an ID of -1 which is not a valid index.
if (n < 0)
n = 0;
playerColor = g_Players[n].color.r + " " + g_Players[n].color.g + " " + g_Players[n].color.b;
username = escapeText(playerAssignments[msg.guid].name);
// Parse in-line commands in regular messages.
if (msg.type == "message")
parseChatCommands(msg, playerAssignments);
}
else if (msg.type == "defeat" && msg.player)
{
[username, playerColor] = getUsernameAndColor(msg.player);
}
else if (msg.type == "message")
{
[username, playerColor] = getUsernameAndColor(msg.player);
parseChatCommands(msg, playerAssignments);
}
else
{
playerColor = "255 255 255";
- username = "Unknown player";
+ username = translate("Unknown player");
}
-
- var message = escapeText(msg.text);
var formatted;
switch (msg.type)
{
case "connect":
- formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has joined the game.";
+ formatted = sprintf(translate("%(player)s has joined the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "disconnect":
- formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has left the game.";
+ formatted = sprintf(translate("%(player)s has left the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "defeat":
// In singleplayer, the local player is "You". "You has" is incorrect.
- var verb = (!g_IsNetworked && msg.player == Engine.GetPlayerID()) ? "have" : "has";
- formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] " + verb + " been defeated.";
+ if (!g_IsNetworked && msg.player == Engine.GetPlayerID())
+ formatted = translate("You have been defeated.");
+ else
+ formatted = sprintf(translate("%(player)s has been defeated."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "diplomacy":
var status = (msg.status == "ally" ? "allied" : (msg.status == "enemy" ? "at war" : "neutral"));
if (msg.player == Engine.GetPlayerID())
{
[username, playerColor] = getUsernameAndColor(msg.player1);
- formatted = "You are now "+status+" with [color=\"" + playerColor + "\"]"+username + "[/color].";
+ if (msg.status == "ally")
+ formatted = sprintf(translate("You are now allied with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
+ else if (msg.status == "enemy")
+ formatted = sprintf(translate("You are now at war with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
+ else // (msg.status == "neutral")
+ formatted = sprintf(translate("You are now neutral with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
}
else if (msg.player1 == Engine.GetPlayerID())
{
[username, playerColor] = getUsernameAndColor(msg.player);
- formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] is now " + status + " with you."
+ if (msg.status == "ally")
+ formatted = sprintf(translate("%(player)s is now allied with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
+ else if (msg.status == "enemy")
+ formatted = sprintf(translate("%(player)s is now at war with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
+ else // (msg.status == "neutral")
+ formatted = sprintf(translate("%(player)s is now neutral with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
}
else // No need for other players to know of this.
return;
break;
case "tribute":
if (msg.player != Engine.GetPlayerID())
return;
[username, playerColor] = getUsernameAndColor(msg.player1);
// Format the amounts to proper English: 200 food, 100 wood, and 300 metal; 100 food; 400 wood and 200 stone
var amounts = Object.keys(msg.amounts)
.filter(function (type) { return msg.amounts[type] > 0; })
.map(function (type) { return msg.amounts[type] + " " + type; });
if (amounts.length > 1)
{
var lastAmount = amounts.pop();
- amounts = amounts.join(", ") + " and " + lastAmount;
+ amounts = sprintf(translate("%(previousAmounts)s and %(lastAmount)s"), {
+ previousAmounts: amounts.join(translate(", ")),
+ lastAmount: lastAmount
+ });
}
- formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has sent you " + amounts + ".";
+ formatted = sprintf(translate("%(player)s has sent you %(amounts)s."), {
+ player: "[color=\"" + playerColor + "\"]" + username + "[/color]",
+ amounts: amounts
+ });
break;
case "attack":
if (msg.player != Engine.GetPlayerID())
return;
[username, playerColor] = getUsernameAndColor(msg.attacker);
- formatted = "You have been attacked by [color=\"" + playerColor + "\"]" + username + "[/color]!";
+ formatted = sprintf(translate("You have been attacked by %(attacker)s!"), { attacker: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "message":
// May have been hidden by the 'team' command.
if (msg.hide)
return;
+ var message = escapeText(msg.text);
+
if (msg.action)
{
- Engine.Console_Write(msg.prefix + "* " + username + " " + message);
- formatted = msg.prefix + "* [color=\"" + playerColor + "\"]" + username + "[/color] " + message;
+ if (msg.context !== "")
+ {
+ Engine.Console_Write(sprintf(translate("(%(context)s) * %(user)s %(message)s"), {
+ context: msg.context,
+ user: username,
+ message: message
+ }));
+ formatted = sprintf(translate("(%(context)s) * %(user)s %(message)s"), {
+ context: msg.context,
+ user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
+ message: message
+ });
+ }
+ else
+ {
+ Engine.Console_Write(sprintf(translate("* %(user)s %(message)s"), {
+ user: username,
+ message: message
+ }));
+ formatted = sprintf(translate("* %(user)s %(message)s"), {
+ user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
+ message: message
+ });
+ }
}
else
{
- Engine.Console_Write(msg.prefix + "<" + username + "> " + message);
- formatted = msg.prefix + "<[color=\"" + playerColor + "\"]" + username + "[/color]> " + message;
+ var userTag = sprintf(translate("<%(user)s>"), { user: username })
+ var formattedUserTag = sprintf(translate("<%(user)s>"), { user: "[color=\"" + playerColor + "\"]" + username + "[/color]" })
+ if (msg.context !== "")
+ {
+ Engine.Console_Write(sprintf(translate("(%(context)s) %(userTag)s %(message)s"), {
+ context: msg.context,
+ userTag: userTag,
+ message: message
+ }));
+ formatted = sprintf(translate("(%(context)s) %(userTag)s %(message)s"), {
+ context: msg.context,
+ userTag: formattedUserTag,
+ message: message
+ });
+ }
+ else
+ {
+ Engine.Console_Write(sprintf(translate("%(userTag)s %(message)s"), { userTag: userTag, message: message}));
+ formatted = sprintf(translate("%(userTag)s %(message)s"), { userTag: formattedUserTag, message: message});
+ }
}
break;
default:
- error("Invalid chat message '" + uneval(msg) + "'");
+ error(sprintf("Invalid chat message '%(message)s'", { message: uneval(msg) }));
return;
}
chatMessages.push(formatted);
chatTimers.push(setTimeout(removeOldChatMessages, CHAT_TIMEOUT));
if (chatMessages.length > MAX_NUM_CHAT_LINES)
removeOldChatMessages();
else
Engine.GetGUIObjectByName("chatText").caption = chatMessages.join("\n");
}
function removeOldChatMessages()
{
clearTimeout(chatTimers[0]); // The timer only needs to be cleared when new messages bump old messages off
chatTimers.shift();
chatMessages.shift();
Engine.GetGUIObjectByName("chatText").caption = chatMessages.join("\n");
}
// Parses chat messages for commands.
function parseChatCommands(msg, playerAssignments)
{
// Only interested in messages that start with '/'.
if (!msg.text || msg.text[0] != '/')
return;
var sender;
if (playerAssignments[msg.guid])
sender = playerAssignments[msg.guid].player;
else
sender = msg.player;
var recurse = false;
var split = msg.text.split(/\s/);
// Parse commands embedded in the message.
switch (split[0])
{
case "/all":
// Resets values that 'team' or 'enemy' may have set.
- msg.prefix = "";
+ msg.context = "";
msg.hide = false;
recurse = true;
break;
case "/team":
// Check if we are in a team.
if (g_Players[Engine.GetPlayerID()] && g_Players[Engine.GetPlayerID()].team != -1)
{
if (g_Players[Engine.GetPlayerID()].team != g_Players[sender].team)
msg.hide = true;
else
- msg.prefix = "(Team) ";
+ msg.context = translate("Team");
}
else
msg.hide = true;
recurse = true;
break;
case "/enemy":
// Check if we are in a team.
if (g_Players[Engine.GetPlayerID()] && g_Players[Engine.GetPlayerID()].team != -1)
{
if (g_Players[Engine.GetPlayerID()].team == g_Players[sender].team && sender != Engine.GetPlayerID())
msg.hide = true;
else
- msg.prefix = "(Enemy) ";
+ msg.context = translate("Enemy");
}
recurse = true;
break;
case "/me":
msg.action = true;
break;
case "/msg":
var trimmed = msg.text.substr(split[0].length + 1);
var matched = "";
// Reject names which don't match or are a superset of the intended name.
for each (var player in playerAssignments)
if (trimmed.indexOf(player.name + " ") == 0 && player.name.length > matched.length)
matched = player.name;
// If the local player's name was the longest one matched, show the message.
var playerName = g_Players[Engine.GetPlayerID()].name;
if (matched.length && (matched == playerName || sender == Engine.GetPlayerID()))
{
- msg.prefix = "(Private) ";
+ msg.context = translate("Private");
msg.text = trimmed.substr(matched.length + 1);
msg.hide = false; // Might override team message hiding.
return;
}
else
msg.hide = true;
break;
default:
return;
}
msg.text = msg.text.substr(split[0].length + 1);
// Hide the message if parsing commands left it empty.
if (!msg.text.length)
msg.hide = true;
// Attempt to parse more commands if the current command allows it.
if (recurse)
parseChatCommands(msg, playerAssignments);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 14954)
@@ -1,1344 +1,1451 @@
onTick();
onSimulationUpdate();
this.hidden = !this.hidden;
closeOpenDialogs();toggleChatWindow();toggleChatWindow(true);openMenu();
var newSetting = !Engine.Renderer_GetSilhouettesEnabled();
Engine.Renderer_SetSilhouettesEnabled(newSetting);
Engine.GetGUIObjectByName("silhouettesCheckbox").checked = newSetting;
var newSetting = !Engine.Renderer_GetShowSkyEnabled();
Engine.Renderer_SetShowSkyEnabled(newSetting);
togglePause();Engine.QuickSave();Engine.QuickLoad();performCommand(g_Selection.toList()[0], "delete");setCameraFollow(g_Selection.toList()[0]);jumpCamera(1);jumpCamera(2);jumpCamera(3);jumpCamera(4);jumpCamera(5);jumpCamera(6);jumpCamera(7);jumpCamera(8);jumpCamera(9);jumpCamera(10);setJumpCamera(1);setJumpCamera(2);setJumpCamera(3);setJumpCamera(4);setJumpCamera(5);setJumpCamera(6);setJumpCamera(7);setJumpCamera(8);setJumpCamera(9);setJumpCamera(10);stopUnits(g_Selection.toList());addTrainingByPosition(0);addTrainingByPosition(1);addTrainingByPosition(2);addTrainingByPosition(3);addTrainingByPosition(4);addTrainingByPosition(5);addTrainingByPosition(6);findIdleUnit(["Hero", "Champion", "CitizenSoldier", "Siege", "Warship", "Dog"]);clearSelection()toggleDeveloperOverlay();
- Control all units
+
+ Control all units
+
g_DevSettings.controlAll = this.checked;
Engine.PostNetworkCommand( {"type": "control-all", "flag": this.checked} );
- Change perspective
+
+ Change perspective
+ Engine.GetGUIObjectByName("viewPlayer").hidden = !this.checked;
- Display selection state
+
+ Display selection state
+
- Pathfinder overlay
+
+ Pathfinder overlay
+ Engine.GuiInterfaceCall("SetPathfinderDebugOverlay", this.checked);
- Obstruction overlay
+
+ Obstruction overlay
+ Engine.GuiInterfaceCall("SetObstructionDebugOverlay", this.checked);
- Unit motion overlay
+
+ Unit motion overlay
+ g_Selection.SetMotionDebugOverlay(this.checked);
- Range overlay
+
+ Range overlay
+ Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);
- Bounding box overlay
+
+ Bounding box overlay
+ Engine.SetBoundingBoxDebugOverlay(this.checked);
- Restrict camera
+
+ Restrict camera
+
Engine.GameView_SetConstrainCameraEnabled(this.checked);
// Make selection more durable at the expense of speed if unchecked.
if (this.checked)
SELECTION_SEARCH_RADIUS -= 200;
else
SELECTION_SEARCH_RADIUS += 200;
- Reveal map
+
+ Reveal map
+ this.checked = Engine.GuiInterfaceCall("IsMapRevealed");Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});
- Enable time warp
+
+ Enable time warp
+
if (this.checked)
- messageBox(500, 250, "Note: time warp mode is a developer option, and not intended\nfor use over long periods of time. Using it incorrectly may\ncause the game to run out of memory or crash.", "Time warp mode", 2);
+ showTimeWarpMessageBox();
Engine.EnableTimeWarpRecording(this.checked ? 10 : 0);
- Promote selected units
+
+ Promote selected units
+ Engine.PostNetworkCommand({"type": "promote", "entities": g_Selection.toList()});this.hidden = !this.hidden;
- Game Paused
- Click to Resume Game
+
+ Game Paused
+
+
+ Click to Resume Game
+ togglePause();submitChatInput();
- Send
- submitChatInput();
+ Send
+ submitChatInput();
- Cancel
- closeChat();
+ Cancel
+ closeChat();
Team Only
- Diplomacy
+
+ Diplomacy
+
-
-
-
-
-
-
-
-
+
+ Name
+
+
+ Civilization
+
+
+ Team
+
+
+ Theirs
+
+
+ A
+ Ally
+
+
+ N
+ Neutral
+
+
+ E
+ Enemy
+
+
+ Tribute
+
Close
closeDiplomacy();
- Trade
+
+ Trade
+
-
+
+ Trading goods selection:
+
-
+
+ Select one goods as origin of the changes,\nthen use the arrows of the target goods to make the changes.
-
-
-
-
-
+
+
+
- Close
+ ClosecloseTrade();
- Settings
+
+ Settings
+
- Enable Shadows
+
+ Enable Shadows
+ this.checked = Engine.Renderer_GetShadowsEnabled();Engine.Renderer_SetShadowsEnabled(this.checked);
- Enable Shadow Filtering
+
+ Enable Shadow Filtering
+ this.checked = Engine.Renderer_GetShadowPCFEnabled();Engine.Renderer_SetShadowPCFEnabled(this.checked);
- Water - HQ Waviness
+
+ Water - HQ Waviness
+ this.checked = Engine.Renderer_GetWaterNormalEnabled();Engine.Renderer_SetWaterNormalEnabled(this.checked);
- Water - Use Actual Depth
+
+ Water - Use Actual Depth
+ this.checked = Engine.Renderer_GetWaterRealDepthEnabled();Engine.Renderer_SetWaterRealDepthEnabled(this.checked);
- Water - Enable Reflections
+
+ Water - Enable Reflections
+ this.checked = Engine.Renderer_GetWaterReflectionEnabled();Engine.Renderer_SetWaterReflectionEnabled(this.checked);
- Water - Enable Refraction
+
+ Water - Enable Refraction
+ this.checked = Engine.Renderer_GetWaterRefractionEnabled();Engine.Renderer_SetWaterRefractionEnabled(this.checked);
- Water - Enable Shore Foam
+
+ Water - Enable Shore Foam
+ this.checked = Engine.Renderer_GetWaterFoamEnabled();Engine.Renderer_SetWaterFoamEnabled(this.checked);
- Water - Enable Shore Waves
+
+ Water - Enable Shore Waves
+ this.checked = Engine.Renderer_GetWaterCoastalWavesEnabled();Engine.Renderer_SetWaterCoastalWavesEnabled(this.checked);
- Water - Use Surface Shadows
+
+ Water - Use Surface Shadows
+ if (Engine.Renderer_GetWaterShadowEnabled()) this.checked = true; else this.checked = false;Engine.Renderer_SetWaterShadowEnabled(this.checked);
- Enable Particles
+
+ Enable Particles
+ this.checked = Engine.Renderer_GetParticlesEnabled();Engine.Renderer_SetParticlesEnabled(this.checked);
- Enable Unit Silhouettes
+
+ Enable Unit Silhouettes
+ this.checked = Engine.Renderer_GetSilhouettesEnabled();Engine.Renderer_SetSilhouettesEnabled(this.checked);
- Enable Music
+
+ Enable Music
+ if (this.checked) global.music.start(); else global.music.stop();
- Developer Overlay
+
+ Developer Overlay
+ toggleDeveloperOverlay();
Close
closeSettings(true);
-
+
+ Food
-
+
+ Wood
-
+
+ Stone
-
+
+ Metal
-
+
+ Population (current / limit)
-
- selectViewPlayer(this.selected);
+
+ Choose player to view
+ selectViewPlayer(this.selected);Observer Mode (experimental)
- ALPHA XV : Osiris
+
+ ALPHA XV : Osiris
-
-
- this.caption = Engine.BuildTime(0) + " (" + Engine.BuildTime(2) + ")"
-
+
+
+ this.caption = getBuildString()
+
+ >
+ Game speed
toggleGameSpeed();
-
-
+
+ Choose game speed
+
+
+ >
+ Diplomacy
toggleDiplomacy();
-
-
-
- toggleTrade();
-
+
+
+ Trade
+
+ toggleTrade();
+
- MENU
+
+ MENU
+
toggleMenu();
- Manual
+ ManualopenManual();
- Chat
+ ChatchatMenuButton();
- Save
+ Save
openSave();
- Settings
+ SettingssettingsMenuButton();
- Pause
+
+ Pause
+ togglePause();
- Resign
+ ResignresignMenuButton();
- Exit
+ ExitexitMenuButton();
-
+ tooltip_style="sessionToolTip">
+ Attack and Armor
-
+
+ Click to select grouped units, double-click to focus the grouped units and right-click to disband the group.
+ Find idle workerfindIdleUnit(["Female", "Trade", "FishingBoat", "CitizenSoldier", "Healer"]);Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle-highlight.png";Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle.png";Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle.png";Engine.GetGUIObjectByName("idleOverlay").sprite = "stretched:session/minimap-idle-highlight.png";handleMinimapEvent(arguments[0]);
- Exchange resources:
+
+ Exchange resources:
+
- Health:
+
+ Health:
+
- Stamina:
+
+ Stamina:
+
-
+
+ Attack and Armor
+
-
-
-
-
-
+
+ Experience
+
+
+
+
-
+
+ Rank
+
-
+
+ Hitpoints
-
+
+ Stamina
- Exit
+
+ Exit
+ leaveGame()
Index: ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.txt
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.txt (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.txt (revision 14954)
@@ -1,9 +1,9 @@
-[font="serif-bold-16"]Thank you for installing 0 A.D.!
+[font="serif-bold-16"]Thank you for installing 0 A.D.!
[font="serif-16"]
[icon="constructionIcon"] This is an early experimental version of the game. Features are missing and it contains bugs.
[icon=iconLag] The game lags when many units are moving.
[icon="iconShip"] The computer opponent can't use ships.
[icon="iconMap"] Large maps can cause problems.
Index: ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js (revision 14954)
@@ -1,282 +1,306 @@
function BuildRestrictions() {}
BuildRestrictions.prototype.Schema =
"Specifies building placement restrictions as they relate to terrain, territories, and distance." +
"" +
"" +
"land" +
"own" +
"Special" +
"" +
"CivilCentre" +
"40" +
"" +
"" +
"" +
"" +
"" +
"land" +
"shore" +
"land-shore"+
"" +
"" +
"" +
"" +
"" +
"" +
"own" +
"ally" +
"neutral" +
"enemy" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
BuildRestrictions.prototype.Init = function()
{
this.territories = this.template.Territory.split(/\s+/);
};
/**
* Checks whether building placement is valid
* 1. Visibility is not hidden (may be fogged or visible)
* 2. Check foundation
* a. Doesn't obstruct foundation-blocking entities
* b. On valid terrain, based on passability class
* 3. Territory type is allowed (see note below)
* 4. Dock is on shoreline and facing into water
* 5. Distance constraints satisfied
*
* Returns result object:
* {
- * "success": true iff the placement is valid, else false
- * "message": message to display in UI for invalid placement, else empty string
+ * "success": true iff the placement is valid, else false
+ * "message": message to display in UI for invalid placement, else ""
+ * "parameters": parameters to use in the GUI message
* }
*
* Note: The entity which is used to check this should be a preview entity
* (template name should be "preview|"+templateName), as otherwise territory
* checks for buildings with territory influence will not work as expected.
*/
BuildRestrictions.prototype.CheckPlacement = function()
{
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";
var result = {
"success": false,
- "message": name+" cannot be built due to unknown error",
+ "message": markForTranslation("%(name)s cannot be built due to unknown error"),
+ "parameters": {
+ "name": name,
+ }
};
// TODO: AI has no visibility info
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (!cmpPlayer.IsAI())
{
// Check whether it's in a visible or fogged region
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
// which would show them as hidden instead of fogged
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpRangeManager || !cmpOwnership)
return result; // Fail
var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner(), true) != "hidden");
if (!explored)
{
- result.message = name+" cannot be built in unexplored area";
+ result.message = markForTranslation("%(name)s cannot be built in unexplored area");
return result; // Fail
}
}
// Check obstructions and terrain passability
var passClassName = "";
switch (this.template.PlacementType)
{
case "shore":
passClassName = "building-shore";
break;
case "land-shore":
// 'default' is everywhere a normal unit can go
// So on passable land, and not too deep in the water
passClassName = "default";
break;
case "land":
default:
passClassName = "building-land";
}
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction)
return result; // Fail
if (this.template.Category == "Wall")
{
// for walls, only test the center point
var ret = cmpObstruction.CheckFoundation(passClassName, true);
}
else
{
var ret = cmpObstruction.CheckFoundation(passClassName, false);
}
if (ret != "success")
{
switch (ret)
{
case "fail_error":
case "fail_no_obstruction":
error("CheckPlacement: Error returned from CheckFoundation");
break;
case "fail_obstructs_foundation":
- result.message = name+" cannot be built on another building or resource";
+ result.message = markForTranslation("%(name)s cannot be built on another building or resource");
break;
case "fail_terrain_class":
// TODO: be more specific and/or list valid terrain?
- result.message = name+" cannot be built on invalid terrain";
+ result.message = markForTranslation("%(name)s cannot be built on invalid terrain");
}
return result; // Fail
}
// Check territory restrictions
var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!(cmpTerritoryManager && cmpPlayer && cmpPosition && cmpPosition.IsInWorld()))
return result; // Fail
var pos = cmpPosition.GetPosition2D();
var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
var isOwn = (tileOwner == cmpPlayer.GetPlayerID());
var isNeutral = (tileOwner == 0);
var isAlly = !isOwn && cmpPlayer.IsAlly(tileOwner);
// We count neutral players as enemies, so you can't build in their territory.
var isEnemy = !isNeutral && (cmpPlayer.IsEnemy(tileOwner) || cmpPlayer.IsNeutral(tileOwner));
var territoryFail = true;
var territoryType = "";
if (isAlly && !this.HasTerritory("ally"))
- territoryType = "ally";
+ // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
+ territoryType = markForTranslationWithContext("Territory type", "ally");
else if (isOwn && !this.HasTerritory("own"))
- territoryType = "own";
+ // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
+ territoryType = markForTranslationWithContext("Territory type", "own");
else if (isNeutral && !this.HasTerritory("neutral"))
- territoryType = "neutral";
+ // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
+ territoryType = markForTranslationWithContext("Territory type", "neutral");
else if (isEnemy && !this.HasTerritory("enemy"))
- territoryType = "enemy";
+ // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
+ territoryType = markForTranslationWithContext("Territory type", "enemy");
else
territoryFail = false;
if (territoryFail)
{
- result.message = name+" cannot be built in "+territoryType+" territory. Valid territories: " + this.GetTerritories().join(", ");
+ result.message = markForTranslation("%(name)s cannot be built in %(territoryType)s territory. Valid territories: %(validTerritories)s");
+ result.parameters.territoryType = {"context": "Territory type", "message": territoryType};
+ // gui code will join this array to a string
+ result.parameters.validTerritories = {"context": "Territory type list", "list": this.GetTerritories()};
return result; // Fail
}
// Check special requirements
if (this.template.Category == "Dock")
{
// TODO: Probably should check unit passability classes here, to determine if:
// 1. ships can be spawned "nearby"
// 2. builders can pass the terrain where the dock is placed (don't worry about paths)
// so it's correct even if the criteria changes for these units
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
if (!cmpFootprint)
return result; // Fail
// Get building's footprint
var shape = cmpFootprint.GetShape();
var halfSize = 0;
if (shape.type == "square")
halfSize = shape.depth/2;
else if (shape.type == "circle")
halfSize = shape.radius;
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
if (!cmpTerrain || !cmpWaterManager)
return result; // Fail
var ang = cmpPosition.GetRotation().y;
var sz = halfSize * Math.sin(ang);
var cz = halfSize * Math.cos(ang);
if ((cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz) - cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz)) < 1.0 // front
|| (cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz) - cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz)) > 2.0) // back
{
- result.message = name+" must be built on a valid shoreline";
+ result.message = markForTranslation("%(name)s must be built on a valid shoreline");
return result; // Fail
}
}
// Check distance restriction
if (this.template.Distance)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var cat = this.template.Distance.FromClass;
var filter = function(id)
{
var cmpIdentity = Engine.QueryInterface(id, IID_Identity);
return cmpIdentity.GetClassesList().indexOf(cat) > -1;
}
if (this.template.Distance.MinDistance)
{
var dist = +this.template.Distance.MinDistance
var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter);
if (nearEnts.length)
{
- result.message = name+" too close to a "+cat+", must be at least "+ +this.template.Distance.MinDistance+" units away";
+ result.message = markForTranslation("%(name)s too close to a %(category)s, must be at least %(distance)s meters away");
+ result.parameters.category = cat;
+ result.parameters.distance = this.template.Distance.MinDistance;
return result; // Fail
}
}
if (this.template.Distance.MaxDistance)
{
var dist = +this.template.Distance.MaxDistance;
var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter);
if (!nearEnts.length)
{
- result.message = name+" too far away from a "+cat+", must be within "+ +this.template.Distance.MaxDistance+" units";
+ result.message = markForTranslation("%(name)s too far from a %(category)s, must be within %(distance)s meters");
+ result.parameters.category = cat;
+ result.parameters.distance = this.template.Distance.MinDistance;
return result; // Fail
}
}
}
// Success
result.success = true;
result.message = "";
return result;
};
BuildRestrictions.prototype.GetCategory = function()
{
return this.template.Category;
};
BuildRestrictions.prototype.GetTerritories = function()
{
return ApplyValueModificationsToEntity("BuildRestrictions/Territory", this.territories, this.entity);
};
BuildRestrictions.prototype.HasTerritory = function(territory)
{
return (this.GetTerritories().indexOf(territory) != -1);
};
+// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
+markForTranslationWithContext("Territory type list", "own");
+// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
+markForTranslationWithContext("Territory type list", "ally");
+// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
+markForTranslationWithContext("Territory type list", "neutral");
+// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
+markForTranslationWithContext("Territory type list", "enemy");
+
Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions);
Index: ps/trunk/source/gui/scripting/ScriptFunctions.cpp
===================================================================
--- ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 14953)
+++ ps/trunk/source/gui/scripting/ScriptFunctions.cpp (revision 14954)
@@ -1,959 +1,959 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "scriptinterface/ScriptInterface.h"
#include "graphics/Camera.h"
#include "graphics/GameView.h"
#include "graphics/MapReader.h"
#include "gui/GUIManager.h"
#include "gui/GUI.h"
#include "gui/IGUIObject.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "graphics/scripting/JSInterface_GameView.h"
#include "i18n/L10n.h"
#include "i18n/scripting/JSInterface_L10n.h"
#include "lib/svn_revision.h"
#include "lib/sysdep/sysdep.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lobby/scripting/JSInterface_Lobby.h"
#include "maths/FixedVector3D.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "network/NetTurnManager.h"
#include "ps/CLogger.h"
#include "ps/CConsole.h"
#include "ps/Errors.h"
#include "ps/Game.h"
#include "ps/Globals.h" // g_frequencyFilter
#include "ps/GUID.h"
#include "ps/World.h"
#include "ps/Hotkey.h"
#include "ps/Overlay.h"
#include "ps/ProfileViewer.h"
#include "ps/Pyrogenesis.h"
#include "ps/SavedGame.h"
#include "ps/scripting/JSInterface_ConfigDB.h"
#include "ps/scripting/JSInterface_Console.h"
#include "ps/scripting/JSInterface_VFS.h"
#include "ps/UserReport.h"
#include "ps/GameSetup/Atlas.h"
#include "ps/GameSetup/Config.h"
#include "renderer/scripting/JSInterface_Renderer.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAIManager.h"
#include "simulation2/components/ICmpCommandQueue.h"
#include "simulation2/components/ICmpGuiInterface.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTemplateManager.h"
#include "simulation2/components/ICmpSelectable.h"
#include "simulation2/helpers/Selection.h"
#include "soundmanager/SoundManager.h"
#include "soundmanager/scripting/JSInterface_Sound.h"
/*
* This file defines a set of functions that are available to GUI scripts, to allow
* interaction with the rest of the engine.
* Functions are exposed to scripts within the global object 'Engine', so
* scripts should call "Engine.FunctionName(...)" etc.
*/
extern void restart_mainloop_in_atlas(); // from main.cpp
extern void EndGame();
extern void kill_mainloop();
namespace {
// Note that the initData argument may only contain clonable data.
// Functions aren't supported for example!
// TODO: Use LOGERROR to print a friendly error message when the requirements aren't met instead of failing with debug_warn when cloning.
void PushGuiPage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal initData)
{
g_GUI->PushPage(name, pCxPrivate->pScriptInterface->WriteStructuredClone(initData.get()));
}
void SwitchGuiPage(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal initData)
{
g_GUI->SwitchPage(name, pCxPrivate->pScriptInterface, initData);
}
void PopGuiPage(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_GUI->PopPage();
}
// Note that the args argument may only contain clonable data.
// Functions aren't supported for example!
// TODO: Use LOGERROR to print a friendly error message when the requirements aren't met instead of failing with debug_warn when cloning.
void PopGuiPageCB(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal args)
{
g_GUI->PopPageCB(pCxPrivate->pScriptInterface->WriteStructuredClone(args.get()));
}
CScriptVal GuiInterfaceCall(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name, CScriptVal data)
{
if (!g_Game)
return JSVAL_VOID;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpGuiInterface(*sim, SYSTEM_ENTITY);
if (!cmpGuiInterface)
return JSVAL_VOID;
int player = -1;
if (g_Game)
player = g_Game->GetPlayerID();
CScriptValRooted arg (sim->GetScriptInterface().GetContext(), sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), data.get()));
CScriptVal ret (cmpGuiInterface->ScriptCall(player, name, arg.get()));
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(sim->GetScriptInterface(), ret.get());
}
void PostNetworkCommand(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal cmd)
{
if (!g_Game)
return;
CSimulation2* sim = g_Game->GetSimulation2();
ENSURE(sim);
CmpPtr cmpCommandQueue(*sim, SYSTEM_ENTITY);
if (!cmpCommandQueue)
return;
jsval cmd2 = sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), cmd.get());
cmpCommandQueue->PostNetworkCommand(cmd2);
}
std::vector PickEntitiesAtPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y, int range)
{
return EntitySelection::PickEntitiesAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetPlayerID(), false, range);
}
std::vector PickFriendlyEntitiesInRect(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x0, int y0, int x1, int y1, int player)
{
return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player, false);
}
std::vector PickFriendlyEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player)
{
return PickFriendlyEntitiesInRect(pCxPrivate, 0, 0, g_xres, g_yres, player);
}
std::vector PickSimilarFriendlyEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
{
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
}
CFixedVector3D GetTerrainAtScreenPoint(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int x, int y)
{
CVector3D pos = g_Game->GetView()->GetCamera()->GetWorldCoordinates(x, y, true);
return CFixedVector3D(fixed::FromFloat(pos.X), fixed::FromFloat(pos.Y), fixed::FromFloat(pos.Z));
}
std::wstring SetCursor(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring name)
{
std::wstring old = g_CursorName;
g_CursorName = name;
return old;
}
int GetPlayerID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game)
return g_Game->GetPlayerID();
return -1;
}
void SetPlayerID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int id)
{
if (g_Game)
g_Game->SetPlayerID(id);
}
CScriptValRooted GetEngineInfo(ScriptInterface::CxPrivate* pCxPrivate)
{
return SavedGames::GetEngineInfo(*(pCxPrivate->pScriptInterface));
}
void StartNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
ENSURE(g_NetServer);
g_NetServer->StartGame();
}
void StartGame(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs, int playerID)
{
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
g_Game = new CGame();
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
CScriptValRooted gameAttribs (sim->GetScriptInterface().GetContext(),
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), attribs.get()));
g_Game->SetPlayerID(playerID);
g_Game->StartGame(gameAttribs, "");
}
CScriptVal StartSavedGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring name)
{
ENSURE(!g_NetServer);
ENSURE(!g_NetClient);
ENSURE(!g_Game);
// Load the saved game data from disk
CScriptValRooted metadata;
std::string savedState;
Status err = SavedGames::Load(name, *(pCxPrivate->pScriptInterface), metadata, savedState);
if (err < 0)
return CScriptVal();
g_Game = new CGame();
// Convert from GUI script context to sim script context
CSimulation2* sim = g_Game->GetSimulation2();
CScriptValRooted gameMetadata (sim->GetScriptInterface().GetContext(),
sim->GetScriptInterface().CloneValueFromOtherContext(*(pCxPrivate->pScriptInterface), metadata.get()));
CScriptValRooted gameInitAttributes;
sim->GetScriptInterface().GetProperty(gameMetadata.get(), "initAttributes", gameInitAttributes);
int playerID;
sim->GetScriptInterface().GetProperty(gameMetadata.get(), "player", playerID);
// Start the game
g_Game->SetPlayerID(playerID);
g_Game->StartGame(gameInitAttributes, savedState);
return metadata.get();
}
void SaveGame(ScriptInterface::CxPrivate* pCxPrivate, std::wstring filename, std::wstring description, CScriptVal GUIMetadata)
{
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata.get());
if (SavedGames::Save(filename, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR(L"Failed to save game");
}
void SaveGamePrefix(ScriptInterface::CxPrivate* pCxPrivate, std::wstring prefix, std::wstring description, CScriptVal GUIMetadata)
{
shared_ptr GUIMetadataClone = pCxPrivate->pScriptInterface->WriteStructuredClone(GUIMetadata.get());
if (SavedGames::SavePrefix(prefix, description, *g_Game->GetSimulation2(), GUIMetadataClone, g_Game->GetPlayerID()) < 0)
LOGERROR(L"Failed to save game");
}
void SetNetworkGameAttributes(ScriptInterface::CxPrivate* pCxPrivate, CScriptVal attribs)
{
ENSURE(g_NetServer);
g_NetServer->UpdateGameAttributes(attribs, *(pCxPrivate->pScriptInterface));
}
void StartNetworkHost(ScriptInterface::CxPrivate* pCxPrivate, std::wstring playerName)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_NetServer = new CNetServer();
if (!g_NetServer->SetupConnection())
{
pCxPrivate->pScriptInterface->ReportError("Failed to start server");
SAFE_DELETE(g_NetServer);
return;
}
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection("127.0.0.1"))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void StartNetworkJoin(ScriptInterface::CxPrivate* pCxPrivate, std::wstring playerName, std::string serverAddress)
{
ENSURE(!g_NetClient);
ENSURE(!g_NetServer);
ENSURE(!g_Game);
g_Game = new CGame();
g_NetClient = new CNetClient(g_Game);
g_NetClient->SetUserName(playerName);
if (!g_NetClient->SetupConnection(serverAddress))
{
pCxPrivate->pScriptInterface->ReportError("Failed to connect to server");
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
}
void DisconnectNetworkGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
// TODO: we ought to do async reliable disconnections
SAFE_DELETE(g_NetServer);
SAFE_DELETE(g_NetClient);
SAFE_DELETE(g_Game);
}
CScriptVal PollNetworkClient(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_NetClient)
return CScriptVal();
CScriptValRooted poll = g_NetClient->GuiPoll();
// Convert from net client context to GUI script context
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(g_NetClient->GetScriptInterface(), poll.get());
}
void AssignNetworkPlayer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int playerID, std::string guid)
{
ENSURE(g_NetServer);
g_NetServer->AssignPlayer(playerID, guid);
}
void SendNetworkChat(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring message)
{
ENSURE(g_NetClient);
g_NetClient->SendChatMessage(message);
}
std::vector GetAIs(ScriptInterface::CxPrivate* pCxPrivate)
{
return ICmpAIManager::GetAIs(*(pCxPrivate->pScriptInterface));
}
std::vector GetSavedGames(ScriptInterface::CxPrivate* pCxPrivate)
{
return SavedGames::GetSavedGames(*(pCxPrivate->pScriptInterface));
}
bool DeleteSavedGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring name)
{
return SavedGames::DeleteSavedGame(name);
}
void OpenURL(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string url)
{
sys_open_url(url);
}
std::wstring GetMatchID(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return ps_generate_guid().FromUTF8();
}
void RestartInAtlas(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
restart_mainloop_in_atlas();
}
bool AtlasIsAvailable(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return ATLAS_IsAvailable();
}
bool IsAtlasRunning(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return (g_AtlasGameLoop && g_AtlasGameLoop->running);
}
CScriptVal LoadMapSettings(ScriptInterface::CxPrivate* pCxPrivate, VfsPath pathname)
{
CMapSummaryReader reader;
if (reader.LoadMap(pathname) != PSRETURN_OK)
return CScriptVal();
return reader.GetMapSettings(*(pCxPrivate->pScriptInterface)).get();
}
CScriptVal GetMapSettings(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_Game)
return CScriptVal();
return pCxPrivate->pScriptInterface->CloneValueFromOtherContext(
g_Game->GetSimulation2()->GetScriptInterface(),
g_Game->GetSimulation2()->GetMapSettings().get());
}
/**
* Get the current X coordinate of the camera.
*/
float CameraGetX(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetCameraX();
return -1;
}
/**
* Get the current Z coordinate of the camera.
*/
float CameraGetZ(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetCameraZ();
return -1;
}
/**
* Start / stop camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollow(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, false);
}
/**
* Start / stop first-person camera following mode
* @param entityid unit id to follow. If zero, stop following mode
*/
void CameraFollowFPS(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_id_t entityid)
{
if (g_Game && g_Game->GetView())
g_Game->GetView()->CameraFollow(entityid, true);
}
/**
* Set the data (position, orientation and zoom) of the camera
*/
void SetCameraData(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_pos_t x, entity_pos_t y, entity_pos_t z, entity_pos_t rotx, entity_pos_t roty, entity_pos_t zoom)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CVector3D Pos = CVector3D(x.ToFloat(), y.ToFloat(), z.ToFloat());
float RotX = rotx.ToFloat();
float RotY = roty.ToFloat();
float Zoom = zoom.ToFloat();
g_Game->GetView()->SetCamera(Pos, RotX, RotY, Zoom);
}
/// Move camera to a 2D location
void CameraMoveTo(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), entity_pos_t x, entity_pos_t z)
{
// called from JS; must not fail
if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
return;
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
target.X = x.ToFloat();
target.Z = z.ToFloat();
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
entity_id_t GetFollowedEntity(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
if (g_Game && g_Game->GetView())
return g_Game->GetView()->GetFollowedEntity();
return INVALID_ENTITY;
}
bool HotkeyIsPressed_(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string hotkeyName)
{
return HotkeyIsPressed(hotkeyName);
}
void DisplayErrorDialog(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::wstring msg)
{
debug_DisplayError(msg.c_str(), DE_NO_DEBUG_INFO, NULL, NULL, NULL, 0, NULL, NULL);
}
CScriptVal GetProfilerState(ScriptInterface::CxPrivate* pCxPrivate)
{
return g_ProfileViewer.SaveToJS(*(pCxPrivate->pScriptInterface));
}
bool IsUserReportEnabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_UserReporter.IsReportingEnabled();
}
void SetUserReportEnabled(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
{
g_UserReporter.SetReportingEnabled(enabled);
}
std::string GetUserReportStatus(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_UserReporter.GetStatus();
}
void SubmitUserReport(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), std::string type, int version, std::wstring data)
{
g_UserReporter.SubmitReport(type.c_str(), version, utf8_from_wstring(data));
}
void SetSimRate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float rate)
{
g_Game->SetSimRate(rate);
}
float GetSimRate(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return g_Game->GetSimRate();
}
void SetTurnLength(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int length)
{
if (g_NetServer)
g_NetServer->SetTurnLength(length);
else
LOGERROR(L"Only network host can change turn length");
}
// Focus the game camera on a given position.
void SetCameraTarget(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), float x, float y, float z)
{
g_Game->GetView()->ResetCameraTarget(CVector3D(x, y, z));
}
// Deliberately cause the game to crash.
// Currently implemented via access violation (read of address 0).
// Useful for testing the crashlog/stack trace code.
int Crash(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
debug_printf(L"Crashing at user's request.\n");
return *(volatile int*)0;
}
void DebugWarn(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
debug_warn(L"Warning at user's request.");
}
// Force a JS garbage collection cycle to take place immediately.
// Writes an indication of how long this took to the console.
void ForceGC(ScriptInterface::CxPrivate* pCxPrivate)
{
double time = timer_Time();
JS_GC(pCxPrivate->pScriptInterface->GetJSRuntime());
time = timer_Time() - time;
g_Console->InsertMessage(L"Garbage collection completed in: %f", time);
}
void DumpSimState(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
OsPath path = psLogDir()/"sim_dump.txt";
std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
g_Game->GetSimulation2()->DumpDebugState(file);
}
void DumpTerrainMipmap(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
VfsPath filename(L"screenshots/terrainmipmap.png");
g_Game->GetWorld()->GetTerrain()->GetHeightMipmap().DumpToDisk(filename);
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(L"Terrain mipmap written to '%ls'", realPath.string().c_str());
}
void EnableTimeWarpRecording(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int numTurns)
{
g_Game->GetTurnManager()->EnableTimeWarpRecording(numTurns);
}
void RewindTimeWarp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->RewindTimeWarp();
}
void QuickSave(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->QuickSave();
}
void QuickLoad(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
g_Game->GetTurnManager()->QuickLoad();
}
void SetBoundingBoxDebugOverlay(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), bool enabled)
{
ICmpSelectable::ms_EnableDebugOverlays = enabled;
}
void Script_EndGame(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
EndGame();
}
// Cause the game to exit gracefully.
// params:
// returns:
// notes:
// - Exit happens after the current main loop iteration ends
// (since this only sets a flag telling it to end)
void ExitProgram(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
kill_mainloop();
}
// Is the game paused?
bool IsPaused(ScriptInterface::CxPrivate* pCxPrivate)
{
if (!g_Game)
{
JS_ReportError(pCxPrivate->pScriptInterface->GetContext(), "Game is not started");
return false;
}
return g_Game->m_Paused;
}
// Pause/unpause the game
void SetPaused(ScriptInterface::CxPrivate* pCxPrivate, bool pause)
{
if (!g_Game)
{
JS_ReportError(pCxPrivate->pScriptInterface->GetContext(), "Game is not started");
return;
}
g_Game->m_Paused = pause;
#if CONFIG2_AUDIO
if ( g_SoundManager )
g_SoundManager->Pause(pause);
#endif
}
// Return the global frames-per-second value.
// params:
// returns: FPS [int]
// notes:
// - This value is recalculated once a frame. We take special care to
// filter it, so it is both accurate and free of jitter.
int GetFps(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
int freq = 0;
if (g_frequencyFilter)
freq = g_frequencyFilter->StableFrequency();
return freq;
}
CScriptVal GetGUIObjectByName(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), CStr name)
{
IGUIObject* guiObj = g_GUI->FindObjectByName(name);
if (guiObj)
return OBJECT_TO_JSVAL(guiObj->GetJSObject());
else
return JSVAL_VOID;
}
// Return the date/time at which the current executable was compiled.
// params: mode OR an integer specifying
// what to display: -1 for "date time (svn revision)", 0 for date, 1 for time, 2 for svn revision
// returns: string with the requested timestamp info
// notes:
// - Displayed on main menu screen; tells non-programmers which auto-build
// they are running. Could also be determined via .EXE file properties,
// but that's a bit more trouble.
// - To be exact, the date/time returned is when scriptglue.cpp was
// last compiled, but the auto-build does full rebuilds.
// - svn revision is generated by calling svnversion and cached in
// lib/svn_revision.cpp. it is useful to know when attempting to
// reproduce bugs (the main EXE and PDB should be temporarily reverted to
// that revision so that they match user-submitted crashdumps).
std::wstring GetBuildTimestamp(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), int mode)
{
char buf[200];
if (mode == -1) // Date, time and revision.
{
UDate dateTime = L10n::Instance().ParseDateTime(__DATE__ " " __TIME__, "MMM d yyyy HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::DateTime, SimpleDateFormat::DATE_TIME);
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("%s (custom build)").c_str(), dateTimeString.c_str());
}
else
{
// Translation: First item is a date and time, item between parenthesis is the Subversion revision number of the current build.
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("%s (%ls)").c_str(), dateTimeString.c_str(), svn_revision);
}
}
else if (mode == 0) // Date.
{
UDate dateTime = L10n::Instance().ParseDateTime(__DATE__, "MMM d yyyy", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::Date, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 1) // Time.
{
UDate dateTime = L10n::Instance().ParseDateTime(__TIME__, "HH:mm:ss", Locale::getUS());
std::string dateTimeString = L10n::Instance().LocalizeDateTime(dateTime, L10n::Time, SimpleDateFormat::MEDIUM);
sprintf_s(buf, ARRAY_SIZE(buf), "%s", dateTimeString.c_str());
}
else if (mode == 2) // Revision.
{
char svnRevision[32];
sprintf_s(svnRevision, ARRAY_SIZE(svnRevision), "%ls", svn_revision);
if (strcmp(svnRevision, "custom build") == 0)
{
sprintf_s(buf, ARRAY_SIZE(buf), L10n::Instance().Translate("custom build").c_str());
}
else
{
sprintf_s(buf, ARRAY_SIZE(buf), "%ls", svn_revision);
}
}
return wstring_from_utf8(buf);
}
//-----------------------------------------------------------------------------
// Timer
//-----------------------------------------------------------------------------
// Script profiling functions: Begin timing a piece of code with StartJsTimer(num)
// and stop timing with StopJsTimer(num). The results will be printed to stdout
// when the game exits.
static const size_t MAX_JS_TIMERS = 20;
static TimerUnit js_start_times[MAX_JS_TIMERS];
static TimerUnit js_timer_overhead;
static TimerClient js_timer_clients[MAX_JS_TIMERS];
static wchar_t js_timer_descriptions_buf[MAX_JS_TIMERS * 12]; // depends on MAX_JS_TIMERS and format string below
static void InitJsTimers(ScriptInterface& scriptInterface)
{
wchar_t* pos = js_timer_descriptions_buf;
for(size_t i = 0; i < MAX_JS_TIMERS; i++)
{
const wchar_t* description = pos;
pos += swprintf_s(pos, 12, L"js_timer %d", (int)i)+1;
timer_AddClient(&js_timer_clients[i], description);
}
// call several times to get a good approximation of 'hot' performance.
// note: don't use a separate timer slot to warm up and then judge
// overhead from another: that causes worse results (probably some
// caching effects inside JS, but I don't entirely understand why).
std::wstring calibration_script =
L"Engine.StartXTimer(0);\n" \
L"Engine.StopXTimer (0);\n" \
L"\n";
scriptInterface.LoadGlobalScript("timer_calibration_script", calibration_script);
// slight hack: call LoadGlobalScript twice because we can't average several
// TimerUnit values because there's no operator/. this way is better anyway
// because it hopefully avoids the one-time JS init overhead.
js_timer_clients[0].sum.SetToZero();
scriptInterface.LoadGlobalScript("timer_calibration_script", calibration_script);
js_timer_clients[0].sum.SetToZero();
js_timer_clients[0].num_calls = 0;
}
void StartJsTimer(ScriptInterface::CxPrivate* pCxPrivate, unsigned int slot)
{
ONCE(InitJsTimers(*(pCxPrivate->pScriptInterface)));
if (slot >= MAX_JS_TIMERS)
LOGERROR(L"Exceeded the maximum number of timer slots for scripts!");
js_start_times[slot].SetFromTimer();
}
void StopJsTimer(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), unsigned int slot)
{
if (slot >= MAX_JS_TIMERS)
LOGERROR(L"Exceeded the maximum number of timer slots for scripts!");
TimerUnit now;
now.SetFromTimer();
now.Subtract(js_timer_overhead);
BillingPolicy_Default()(&js_timer_clients[slot], js_start_times[slot], now);
js_start_times[slot].SetToZero();
}
} // namespace
void GuiScriptingInit(ScriptInterface& scriptInterface)
{
JSI_IGUIObject::init(scriptInterface);
JSI_GUITypes::init(scriptInterface);
JSI_GameView::RegisterScriptFunctions(scriptInterface);
JSI_Renderer::RegisterScriptFunctions(scriptInterface);
JSI_Console::RegisterScriptFunctions(scriptInterface);
JSI_ConfigDB::RegisterScriptFunctions(scriptInterface);
JSI_Sound::RegisterScriptFunctions(scriptInterface);
JSI_L10n::RegisterScriptFunctions(scriptInterface);
// VFS (external)
scriptInterface.RegisterFunction("BuildDirEntList");
scriptInterface.RegisterFunction("FileExists");
scriptInterface.RegisterFunction("GetFileMTime");
scriptInterface.RegisterFunction("GetFileSize");
scriptInterface.RegisterFunction("ReadFile");
scriptInterface.RegisterFunction("ReadFileLines");
// GUI manager functions:
scriptInterface.RegisterFunction("PushGuiPage");
scriptInterface.RegisterFunction("SwitchGuiPage");
scriptInterface.RegisterFunction("PopGuiPage");
scriptInterface.RegisterFunction("PopGuiPageCB");
scriptInterface.RegisterFunction("GetGUIObjectByName");
// Simulation<->GUI interface functions:
scriptInterface.RegisterFunction("GuiInterfaceCall");
scriptInterface.RegisterFunction("PostNetworkCommand");
// Entity picking
scriptInterface.RegisterFunction, int, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
scriptInterface.RegisterFunction, int, int, int, int, int, &PickFriendlyEntitiesInRect>("PickFriendlyEntitiesInRect");
scriptInterface.RegisterFunction, int, &PickFriendlyEntitiesOnScreen>("PickFriendlyEntitiesOnScreen");
scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarFriendlyEntities>("PickSimilarFriendlyEntities");
scriptInterface.RegisterFunction("GetTerrainAtScreenPoint");
// Network / game setup functions
scriptInterface.RegisterFunction("StartNetworkGame");
scriptInterface.RegisterFunction("StartGame");
scriptInterface.RegisterFunction("EndGame");
scriptInterface.RegisterFunction("StartNetworkHost");
scriptInterface.RegisterFunction("StartNetworkJoin");
scriptInterface.RegisterFunction("DisconnectNetworkGame");
scriptInterface.RegisterFunction("PollNetworkClient");
scriptInterface.RegisterFunction("SetNetworkGameAttributes");
scriptInterface.RegisterFunction("AssignNetworkPlayer");
scriptInterface.RegisterFunction("SendNetworkChat");
scriptInterface.RegisterFunction, &GetAIs>("GetAIs");
scriptInterface.RegisterFunction("GetEngineInfo");
// Saved games
scriptInterface.RegisterFunction("StartSavedGame");
scriptInterface.RegisterFunction, &GetSavedGames>("GetSavedGames");
scriptInterface.RegisterFunction("DeleteSavedGame");
scriptInterface.RegisterFunction("SaveGame");
scriptInterface.RegisterFunction("SaveGamePrefix");
scriptInterface.RegisterFunction("QuickSave");
scriptInterface.RegisterFunction("QuickLoad");
// Misc functions
scriptInterface.RegisterFunction("SetCursor");
scriptInterface.RegisterFunction("GetPlayerID");
scriptInterface.RegisterFunction("SetPlayerID");
scriptInterface.RegisterFunction("OpenURL");
scriptInterface.RegisterFunction("GetMatchID");
scriptInterface.RegisterFunction("RestartInAtlas");
scriptInterface.RegisterFunction("AtlasIsAvailable");
scriptInterface.RegisterFunction("IsAtlasRunning");
scriptInterface.RegisterFunction("LoadMapSettings");
scriptInterface.RegisterFunction("GetMapSettings");
scriptInterface.RegisterFunction("CameraGetX");
scriptInterface.RegisterFunction("CameraGetZ");
scriptInterface.RegisterFunction("CameraFollow");
scriptInterface.RegisterFunction("CameraFollowFPS");
scriptInterface.RegisterFunction("SetCameraData");
scriptInterface.RegisterFunction("CameraMoveTo");
scriptInterface.RegisterFunction("GetFollowedEntity");
scriptInterface.RegisterFunction("HotkeyIsPressed");
scriptInterface.RegisterFunction("DisplayErrorDialog");
scriptInterface.RegisterFunction("GetProfilerState");
scriptInterface.RegisterFunction("Exit");
scriptInterface.RegisterFunction("IsPaused");
scriptInterface.RegisterFunction("SetPaused");
scriptInterface.RegisterFunction("GetFPS");
- scriptInterface.RegisterFunction("BuildTime");
+ scriptInterface.RegisterFunction("GetBuildTimestamp");
// User report functions
scriptInterface.RegisterFunction("IsUserReportEnabled");
scriptInterface.RegisterFunction("SetUserReportEnabled");
scriptInterface.RegisterFunction("GetUserReportStatus");
scriptInterface.RegisterFunction("SubmitUserReport");
// Development/debugging functions
scriptInterface.RegisterFunction("StartXTimer");
scriptInterface.RegisterFunction("StopXTimer");
scriptInterface.RegisterFunction("SetSimRate");
scriptInterface.RegisterFunction("GetSimRate");
scriptInterface.RegisterFunction("SetTurnLength");
scriptInterface.RegisterFunction("SetCameraTarget");
scriptInterface.RegisterFunction("Crash");
scriptInterface.RegisterFunction("DebugWarn");
scriptInterface.RegisterFunction("ForceGC");
scriptInterface.RegisterFunction("DumpSimState");
scriptInterface.RegisterFunction("DumpTerrainMipmap");
scriptInterface.RegisterFunction("EnableTimeWarpRecording");
scriptInterface.RegisterFunction("RewindTimeWarp");
scriptInterface.RegisterFunction("SetBoundingBoxDebugOverlay");
// Lobby functions
scriptInterface.RegisterFunction("HasXmppClient");
scriptInterface.RegisterFunction("IsRankedGame");
scriptInterface.RegisterFunction("SetRankedGame");
#if CONFIG2_LOBBY // Allow the lobby to be disabled
scriptInterface.RegisterFunction("StartXmppClient");
scriptInterface.RegisterFunction("StartRegisterXmppClient");
scriptInterface.RegisterFunction("StopXmppClient");
scriptInterface.RegisterFunction("ConnectXmppClient");
scriptInterface.RegisterFunction("DisconnectXmppClient");
scriptInterface.RegisterFunction("RecvXmppClient");
scriptInterface.RegisterFunction("SendGetGameList");
scriptInterface.RegisterFunction("SendGetBoardList");
scriptInterface.RegisterFunction("SendGetRatingList");
scriptInterface.RegisterFunction("SendRegisterGame");
scriptInterface.RegisterFunction("SendGameReport");
scriptInterface.RegisterFunction("SendUnregisterGame");
scriptInterface.RegisterFunction("SendChangeStateGame");
scriptInterface.RegisterFunction("GetPlayerList");
scriptInterface.RegisterFunction("GetGameList");
scriptInterface.RegisterFunction("GetBoardList");
scriptInterface.RegisterFunction("LobbyGuiPollMessage");
scriptInterface.RegisterFunction("LobbySendMessage");
scriptInterface.RegisterFunction("LobbySetPlayerPresence");
scriptInterface.RegisterFunction("LobbySetNick");
scriptInterface.RegisterFunction("LobbyGetNick");
scriptInterface.RegisterFunction("LobbyKick");
scriptInterface.RegisterFunction("LobbyBan");
scriptInterface.RegisterFunction("LobbyGetPlayerPresence");
scriptInterface.RegisterFunction("LobbyGetPlayerRole");
scriptInterface.RegisterFunction("EncryptPassword");
scriptInterface.RegisterFunction("LobbyGetRoomSubject");
#endif // CONFIG2_LOBBY
}
Index: ps/trunk/binaries/data/mods/public/gui/session/unit_commands.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/unit_commands.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/unit_commands.js (revision 14954)
@@ -1,1312 +1,1390 @@
// Panel types
const SELECTION = "Selection";
const QUEUE = "Queue";
const GARRISON = "Garrison";
const FORMATION = "Formation";
const TRAINING = "Training";
const RESEARCH = "Research";
const CONSTRUCTION = "Construction";
const TRADING = "Trading";
const COMMAND = "Command";
const STANCE = "Stance";
const GATE = "Gate";
const PACK = "Pack";
// Constants
const COMMANDS_PANEL_WIDTH = 228;
const UNIT_PANEL_BASE = -52; // QUEUE: The offset above the main panel (will often be negative)
const UNIT_PANEL_HEIGHT = 44; // QUEUE: The height needed for a row of buttons
// Trading constants
const TRADING_RESOURCES = ["food", "wood", "stone", "metal"];
// Barter constants
const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
const BARTER_BUNCH_MULTIPLIER = 5;
const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
const BARTER_ACTIONS = ["Sell", "Buy"];
// Gate constants
-const GATE_ACTIONS = ["Lock", "Unlock"];
+const GATE_ACTIONS = ["lock", "unlock"];
// The number of currently visible buttons (used to optimise showing/hiding)
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Trading": 0, "Construction": 0, "Command": 0, "Stance": 0, "Gate": 0, "Pack": 0};
// Unit panels are panels with row(s) of buttons
var g_unitPanels = ["Selection", "Queue", "Formation", "Garrison", "Training", "Barter", "Trading", "Construction", "Research", "Stance", "Command", "Gate", "Pack"];
// Indexes of resources to sell and buy on barter panel
var g_barterSell = 0;
// Lay out a row of centered buttons (does not work inside a loop like the other function)
function layoutButtonRowCentered(rowNumber, guiName, startIndex, endIndex, width)
{
var buttonSideLength = Engine.GetGUIObjectByName("unit"+guiName+"Button[0]").size.bottom;
var buttonSpacer = buttonSideLength+1;
var colNumber = 0;
// Collect buttons
var buttons = [];
var icons = [];
for (var i = startIndex; i < endIndex; i++)
{
var button = Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]");
var icon = Engine.GetGUIObjectByName("unit"+guiName+"Icon["+i+"]");
if (button)
{
buttons.push(button);
icons.push(icon);
}
}
// Location of middle button
var middleIndex = Math.ceil(buttons.length/2);
// Determine whether even or odd number of buttons
var center = (buttons.length/2 == Math.ceil(buttons.length/2))? Math.ceil(width/2) : Math.ceil(width/2+buttonSpacer/2);
// Left Side
for (var i = middleIndex-1; i >= 0; i--)
{
if (buttons[i])
{
var icon = icons[i];
var size = buttons[i].size;
size.left = center - buttonSpacer*colNumber - buttonSideLength;
size.right = center - buttonSpacer*colNumber;
size.top = buttonSpacer*rowNumber;
size.bottom = buttonSpacer*rowNumber + buttonSideLength;
buttons[i].size = size;
colNumber++;
}
}
// Right Side
center += 1; // add spacing to center buttons
colNumber = 0; // reset to 0
for (var i = middleIndex; i < buttons.length; i++)
{
if (buttons[i])
{
var icon = icons[i];
var size = buttons[i].size;
size.left = center + buttonSpacer*colNumber;
size.right = center + buttonSpacer*colNumber + buttonSideLength;
size.top = buttonSpacer*rowNumber;
size.bottom = buttonSpacer*rowNumber + buttonSideLength;
buttons[i].size = size;
colNumber++;
}
}
}
// Lay out button rows
function layoutButtonRow(rowNumber, guiName, buttonSideWidth, buttonSpacer, startIndex, endIndex)
{
layoutRow("Button", rowNumber, guiName, buttonSideWidth, buttonSpacer, buttonSideWidth, buttonSpacer, startIndex, endIndex);
}
// Lay out rows
function layoutRow(objectName, rowNumber, guiName, objectSideWidth, objectSpacerWidth, objectSideHeight, objectSpacerHeight, startIndex, endIndex)
{
var colNumber = 0;
for (var i = startIndex; i < endIndex; i++)
{
var button = Engine.GetGUIObjectByName("unit"+guiName+objectName+"["+i+"]");
if (button)
{
var size = button.size;
size.left = objectSpacerWidth*colNumber;
size.right = objectSpacerWidth*colNumber + objectSideWidth;
size.top = objectSpacerHeight*rowNumber;
size.bottom = objectSpacerHeight*rowNumber + objectSideHeight;
button.size = size;
colNumber++;
}
}
}
// Set the visibility of the object
function setOverlay(object, value)
{
object.hidden = !value;
}
/**
* Format entity count/limit message for the tooltip
*/
function formatLimitString(trainEntLimit, trainEntCount, trainEntLimitChangers)
{
if (trainEntLimit == undefined)
return "";
- var text = "\n\nCurrent Count: " + trainEntCount + ", Limit: " + trainEntLimit + ".";
+ var text = "\n\n" + sprintf(translate("Current Count: %(count)s, Limit: %(limit)s."), { count: trainEntCount, limit: trainEntLimit });
if (trainEntCount >= trainEntLimit)
text = "[color=\"red\"]" + text + "[/color]";
for (var c in trainEntLimitChangers)
{
if (trainEntLimitChangers[c] > 0)
- text += "\n" + c + " enlarges the limit with " + trainEntLimitChangers[c] + ".";
+ text += "\n" + sprintf("%(changer)s enlarges the limit with %(change)s.", { changer: c, change: trainEntLimitChangers[c] });
else if (trainEntLimitChangers[c] < 0)
- text += "\n" + c + " lessens the limit with " + (-trainEntLimitChangers[c]) + ".";
+ text += "\n" + sprintf("%(changer)s lessens the limit with %(change)s.", { changer: c, change: (-trainEntLimitChangers[c]) });
}
return text;
}
/**
* Format batch training string for the tooltip
* Examples:
* buildingsCountToTrainFullBatch = 1, fullBatchSize = 5, remainderBatch = 0:
* "Shift-click to train 5"
* buildingsCountToTrainFullBatch = 2, fullBatchSize = 5, remainderBatch = 0:
* "Shift-click to train 10 (2*5)"
* buildingsCountToTrainFullBatch = 1, fullBatchSize = 15, remainderBatch = 12:
* "Shift-click to train 27 (15 + 12)"
*/
function formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch)
{
var totalBatchTrainingCount = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
// Don't show the batch training tooltip if either units of this type can't be trained at all
// or only one unit can be trained
if (totalBatchTrainingCount < 2)
return "";
var batchTrainingString = "";
var fullBatchesString = "";
if (buildingsCountToTrainFullBatch > 0)
{
if (buildingsCountToTrainFullBatch > 1)
- fullBatchesString += buildingsCountToTrainFullBatch + "*";
- fullBatchesString += fullBatchSize;
+ fullBatchesString = sprintf(translate("%(buildings)s*%(batchSize)s"), {
+ buildings: buildingsCountToTrainFullBatch,
+ batchSize: fullBatchSize
+ });
+ else
+ fullBatchesString = fullBatchSize;
}
var remainderBatchString = remainderBatch > 0 ? remainderBatch : "";
var batchDetailsString = "";
+ var action = "[font=\"serif-bold-13\"]" + translate("Shift-click") + "[/font][font=\"serif-13\"]"
+
// We need to display the batch details part if there is either more than
// one building with full batch or one building with the full batch and
// another with a partial batch
if (buildingsCountToTrainFullBatch > 1 ||
(buildingsCountToTrainFullBatch == 1 && remainderBatch > 0))
{
- batchDetailsString += " (" + fullBatchesString;
- if (remainderBatchString != "")
- batchDetailsString += " + " + remainderBatchString;
- batchDetailsString += ")";
- }
+ if (remainderBatch > 0)
+ return "\n[font=\"serif-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s + %(remainderBatch)s)."), {
+ action: action,
+ number: totalBatchTrainingCount,
+ fullBatch: fullBatchesString,
+ remainderBatch: remainderBatch
+ }) + "[/font]";
+
+ return "\n[font=\"serif-13\"]" + sprintf(translate("%(action)s to train %(number)s (%(fullBatch)s)."), {
+ action: action,
+ number: totalBatchTrainingCount,
+ fullBatch: fullBatchesString
+ }) + "[/font]";
+ }
+
+ return "\n[font=\"serif-13\"]" + sprintf(translate("%(action)s to train %(number)s."), {
+ action: action,
+ number: totalBatchTrainingCount
+ }) + "[/font]";
+}
- return "\n[font=\"serif-bold-13\"]Shift-click[/font][font=\"serif-13\"] to train "
- + totalBatchTrainingCount + batchDetailsString + ".[/font]";
+function getStanceDisplayName(name)
+{
+ var displayName;
+ switch(name)
+ {
+ case "violent":
+ displayName = translate("Violent");
+ break;
+ case "aggressive":
+ displayName = translate("Aggressive");
+ break;
+ case "passive":
+ displayName = translate("Passive");
+ break;
+ case "defensive":
+ displayName = translate("Defensive");
+ break;
+ case "standground":
+ displayName = translate("Standground");
+ break;
+ default:
+ warn(sprintf("Internationalization: Unexpected stance found with code ‘%(stance)s’. This stance must be internationalized.", { stance: name }));
+ displayName = name;
+ break;
+ }
+ return displayName;
}
/**
* Helper function for updateUnitCommands; sets up "unit panels" (i.e. panels with rows of icons) for the currently selected
* unit.
*
* @param guiName Short identifier string of this panel; see constants defined at the top of this file.
* @param usedPanels Output object; usedPanels[guiName] will be set to 1 to indicate that this panel was used during this
* run of updateUnitCommands and should not be hidden. TODO: why is this done this way instead of having
* updateUnitCommands keep track of this?
* @param unitEntState Entity state of the (first) selected unit.
* @param items Panel-specific data to construct the icons with.
* @param callback Callback function to argument to execute when an item's icon gets clicked. Takes a single 'item' argument.
*/
function setupUnitPanel(guiName, usedPanels, unitEntState, playerState, items, callback)
{
usedPanels[guiName] = 1;
var numberOfItems = items.length;
var selection = g_Selection.toList();
var garrisonGroups = new EntityGroups();
// Determine how many buttons there should be
switch (guiName)
{
case SELECTION:
if (numberOfItems > 16)
numberOfItems = 16;
break;
case QUEUE:
if (numberOfItems > 16)
numberOfItems = 16;
break;
case GARRISON:
if (numberOfItems > 12)
numberOfItems = 12;
break;
case STANCE:
if (numberOfItems > 5)
numberOfItems = 5;
break;
case FORMATION:
if (numberOfItems > 16)
numberOfItems = 16;
break;
case TRAINING:
if (numberOfItems > 24)
numberOfItems = 24;
break;
case RESEARCH:
if (numberOfItems > 8)
numberOfItems = 8;
break;
case CONSTRUCTION:
if (numberOfItems > 24)
numberOfItems = 24;
break;
case COMMAND:
if (numberOfItems > 6)
numberOfItems = 6;
break;
case GATE:
if(numberOfItems > 8)
numberOfItems = 8;
break;
case PACK:
if(numberOfItems > 8)
numberOfItems = 8;
break;
default:
break;
}
switch (guiName)
{
case GARRISON:
case COMMAND:
// Common code for garrison and 'unload all' button counts.
for (var i = 0; i < selection.length; ++i)
{
var state = GetEntityState(selection[i]);
if (state.garrisonHolder)
garrisonGroups.add(state.garrisonHolder.entities)
}
break;
default:
break;
}
var rowLength = 8;
if (guiName == SELECTION)
rowLength = 4;
else if (guiName == FORMATION || guiName == GARRISON || guiName == COMMAND)
rowLength = 4;
// Make buttons
var i;
for (i = 0; i < numberOfItems; i++)
{
var item = items[i];
// If a tech has been researched it leaves an empty slot
if (guiName == RESEARCH && !item)
{
Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]").hidden = true;
// We also remove the paired tech and the pair symbol
Engine.GetGUIObjectByName("unit"+guiName+"Button["+(i+rowLength)+"]").hidden = true;
Engine.GetGUIObjectByName("unit"+guiName+"Pair["+i+"]").hidden = true;
continue;
}
// Get the entity type and load the template for that type if necessary
var entType;
var template;
var entType1;
var template1;
switch (guiName)
{
case QUEUE:
// The queue can hold both technologies and units so we need to use the correct code for
// loading the templates
if (item.unitTemplate)
{
entType = item.unitTemplate;
template = GetTemplateData(entType);
}
else if (item.technologyTemplate)
{
entType = item.technologyTemplate;
template = GetTechnologyData(entType);
}
if (!template)
continue; // ignore attempts to use invalid templates (an error should have been
// reported already)
break;
case RESEARCH:
if (item.pair)
{
entType1 = item.top;
template1 = GetTechnologyData(entType1);
if (!template1)
continue; // ignore attempts to use invalid templates (an error should have been
// reported already)
entType = item.bottom;
}
else
{
entType = item;
}
template = GetTechnologyData(entType);
if (!template)
continue; // ignore attempts to use invalid templates (an error should have been
// reported already)
break;
case SELECTION:
case GARRISON:
case TRAINING:
case CONSTRUCTION:
entType = item;
template = GetTemplateData(entType);
if (!template)
continue; // ignore attempts to use invalid templates (an error should have been
// reported already)
break;
}
switch (guiName)
{
case SELECTION:
var name = getEntityNames(template);
var tooltip = name;
var count = g_Selection.groups.getCount(item);
Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : "");
break;
case QUEUE:
var tooltip = getEntityNames(template);
if (item.neededSlots)
- tooltip += "\n[color=\"red\"]Insufficient population capacity:\n[/color]"+getCostComponentDisplayName("population")+" "+item.neededSlots;
+ tooltip += "\n[color=\"red\"]" + translate("Insufficient population capacity:") + "\n[/color]" + sprintf(translate("%(population)s %(neededSlots)s"), { population: getCostComponentDisplayName("population"), neededSlots: item.neededSlots });
var progress = Math.round(item.progress*100) + "%";
Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (item.count > 1 ? item.count : "");
if (i == 0)
{
Engine.GetGUIObjectByName("queueProgress").caption = (item.progress ? progress : "");
var size = Engine.GetGUIObjectByName("unit"+guiName+"ProgressSlider["+i+"]").size;
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
size.top = size.left + Math.round(item.progress * (size.right - size.left));
Engine.GetGUIObjectByName("unit"+guiName+"ProgressSlider["+i+"]").size = size;
}
break;
case GARRISON:
var name = getEntityNames(template);
- var tooltip = "Unload " + name + "\nSingle-click to unload 1. Shift-click to unload all of this type.";
+ var tooltip = sprintf(translate("Unload %(name)s"), { name: name })+ "\n" + translate("Single-click to unload 1. Shift-click to unload all of this type.");
var count = garrisonGroups.getCount(item);
Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : "");
break;
case GATE:
var tooltip = item.tooltip;
if (item.template)
{
var template = GetTemplateData(item.template);
var wallCount = g_Selection.toList().reduce(function (count, ent) {
var state = GetEntityState(ent);
if (hasClass(state, "LongWall") && !state.gate)
count++;
return count;
}, 0);
tooltip += "\n" + getEntityCostTooltip(template, wallCount);
var affordableMask = Engine.GetGUIObjectByName("unitGateUnaffordable["+i+"]");
affordableMask.hidden = true;
var neededResources = Engine.GuiInterfaceCall("GetNeededResources", multiplyEntityCosts(template, wallCount));
if (neededResources)
{
affordableMask.hidden = false;
tooltip += getNeededResourcesTooltip(neededResources);
}
}
break;
case PACK:
var tooltip = item.tooltip;
break;
case STANCE:
- var tooltip = toTitleCase(item);
+ var tooltip = getStanceDisplayName(item);
break;
case TRAINING:
var tooltip = getEntityNamesFormatted(template);
var key = Engine.ConfigDB_GetValue("user", "hotkey.session.queueunit." + (i + 1));
if (key)
tooltip = "[color=\"255 251 131\"][font=\"serif-bold-16\"][" + key + "][/font][/color] " + tooltip;
if (template.tooltip)
tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
var [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch] =
getTrainingBatchStatus(playerState, unitEntState.id, entType, selection);
if (Engine.HotkeyIsPressed("session.batchtrain"))
trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
tooltip += "\n" + getEntityCostTooltip(template, trainNum, unitEntState.id);
var [trainEntLimit, trainEntCount, canBeAddedCount, trainEntLimitChangers] =
getEntityLimitAndCount(playerState, entType);
tooltip += formatLimitString(trainEntLimit, trainEntCount, trainEntLimitChangers);
tooltip += "[color=\"255 251 131\"]" + formatBatchTrainingString(buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch) + "[/color]";
break;
case RESEARCH:
var tooltip = getEntityNamesFormatted(template);
if (template.tooltip)
tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
tooltip += "\n" + getEntityCostTooltip(template);
if (item.pair)
{
var tooltip1 = getEntityNamesFormatted(template1);
if (template1.tooltip)
tooltip1 += "\n[font=\"serif-13\"]" + template1.tooltip + "[/font]";
tooltip1 += "\n" + getEntityCostTooltip(template1);
}
break;
case CONSTRUCTION:
var tooltip = getEntityNamesFormatted(template);
if (template.tooltip)
tooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
tooltip += "\n" + getEntityCostTooltip(template);
tooltip += getPopulationBonusTooltip(template);
var [entLimit, entCount, canBeAddedCount, entLimitChangers] =
getEntityLimitAndCount(playerState, entType);
tooltip += formatLimitString(entLimit, entCount, entLimitChangers);
break;
case COMMAND:
// here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single)
if (item.name == "unload-all")
{
var count = garrisonGroups.getTotalCount();
Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 0 ? count : "");
}
else
{
Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = "";
}
tooltip = (item.tooltip ? item.tooltip : toTitleCase(item.name));
break;
default:
break;
}
// Button
var button = Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]");
var button1 = Engine.GetGUIObjectByName("unit"+guiName+"Button["+(i+rowLength)+"]");
var affordableMask = Engine.GetGUIObjectByName("unit"+guiName+"Unaffordable["+i+"]");
var affordableMask1 = Engine.GetGUIObjectByName("unit"+guiName+"Unaffordable["+(i+rowLength)+"]");
var icon = Engine.GetGUIObjectByName("unit"+guiName+"Icon["+i+"]");
var guiSelection = Engine.GetGUIObjectByName("unit"+guiName+"Selection["+i+"]");
var pair = Engine.GetGUIObjectByName("unit"+guiName+"Pair["+i+"]");
button.hidden = false;
button.tooltip = tooltip || "";
// Button Function (need nested functions to get the closure right)
// Items can have a callback element that overrides the normal caller-supplied callback function.
button.onpress = (function(e){ return function() { e.callback ? e.callback(e) : callback(e) } })(item);
if(guiName == SELECTION)
{
button.onpressright = (function(e){return function() {callback(e, true) } })(item);
button.onpress = (function(e){ return function() {callback(e, false) } })(item);
}
if (guiName == RESEARCH)
{
if (item.pair)
{
button.onpress = (function(e){ return function() { callback(e) } })(item.bottom);
var icon1 = Engine.GetGUIObjectByName("unit"+guiName+"Icon["+(i+rowLength)+"]");
button1.hidden = false;
button1.tooltip = tooltip1;
button1.onpress = (function(e){ return function() { callback(e) } })(item.top);
// when we hover over a pair, the other one gets a red cross over it to show it won't be available any more.
var unchosenIcon = Engine.GetGUIObjectByName("unit"+guiName+"UnchosenIcon["+i+"]");
var unchosenIcon1 = Engine.GetGUIObjectByName("unit"+guiName+"UnchosenIcon["+(i+rowLength)+"]");
button1.onmouseenter = (function(e){ return function() { setOverlay(e, true) } })(unchosenIcon);
button1.onmouseleave = (function(e){ return function() { setOverlay(e, false) } })(unchosenIcon);
button.onmouseenter = (function(e){ return function() { setOverlay(e, true) } })(unchosenIcon1);
button.onmouseleave = (function(e){ return function() { setOverlay(e, false) } })(unchosenIcon1);
pair.hidden = false;
}
else
{
// Hide the overlay.
var unchosenIcon = Engine.GetGUIObjectByName("unit"+guiName+"UnchosenIcon["+i+"]");
unchosenIcon.hidden = true;
}
}
// Get icon image
if (guiName == FORMATION)
{
var formationInfo = Engine.GuiInterfaceCall("GetFormationInfoFromTemplate", {"templateName": item});
- button.tooltip = formationInfo.name;
+ button.tooltip = translate(formationInfo.name);
var formationOk = canMoveSelectionIntoFormation(item);
var grayscale = "";
button.enabled = formationOk;
if (!formationOk)
{
grayscale = "grayscale:";
// Display a meaningful tooltip why the formation is disabled
- button.tooltip += " (disabled)"+formationInfo.tooltip;
- }
+ button.tooltip += "\n" + "[color=\"red\"]" + translate(formationInfo.tooltip) + "[/color]";
+ }
var formationSelected = Engine.GuiInterfaceCall("IsFormationSelected", {
"ents": g_Selection.toList(),
"formationTemplate": item
});
guiSelection.hidden = !formationSelected;
icon.sprite = "stretched:"+grayscale+"session/icons/"+item+".png";
}
else if (guiName == STANCE)
{
var stanceSelected = Engine.GuiInterfaceCall("IsStanceSelected", {
"ents": g_Selection.toList(),
"stance": item
});
guiSelection.hidden = !stanceSelected;
icon.sprite = "stretched:session/icons/stances/"+item+".png";
}
else if (guiName == COMMAND)
{
icon.sprite = "stretched:session/icons/" + item.icon;
}
else if (guiName == GATE)
{
var gateIcon;
// If already a gate, show locking actions
if (item.gate)
{
- gateIcon = "icons/lock_" + GATE_ACTIONS[item.locked ? 0 : 1].toLowerCase() + "ed.png";
+ gateIcon = "icons/lock_" + GATE_ACTIONS[item.locked ? 0 : 1] + "ed.png";
guiSelection.hidden = item.gate.locked === undefined ? false : item.gate.locked != item.locked;
}
// otherwise show gate upgrade icon
else
{
template = GetTemplateData(item.template);
gateIcon = template.icon ? "portraits/" + template.icon : "icons/gate_closed.png";
guiSelection.hidden = true;
}
icon.sprite = "stretched:session/" + gateIcon;
}
else if (guiName == PACK)
{
if (item.packing)
{
icon.sprite = "stretched:session/icons/cancel.png";
}
else
{
if (item.packed)
icon.sprite = "stretched:session/icons/unpack.png";
else
icon.sprite = "stretched:session/icons/pack.png";
}
}
else if (template.icon)
{
var grayscale = "";
button.enabled = true;
if (affordableMask)
affordableMask.hidden = true; // actually used for the red "lack of resource" overlay, and darkening if unavailable. Sort of a hack.
// In case this is an icon that would require tech checking, make sure we have the requirements.
if (guiName != SELECTION && guiName != GARRISON && guiName != QUEUE && template.requiredTechnology && !Engine.GuiInterfaceCall("IsTechnologyResearched", template.requiredTechnology))
{
button.enabled = false;
var techName = getEntityNames(GetTechnologyData(template.requiredTechnology));
- button.tooltip += "\nRequires " + techName;
+ button.tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { technology: techName });
grayscale = "grayscale:";
affordableMask.hidden = false;
affordableMask.sprite = "colour: 0 0 0 127";
}
if (guiName == RESEARCH && !Engine.GuiInterfaceCall("CheckTechnologyRequirements", entType))
{
button.enabled = false;
button.tooltip += "\n" + GetTechnologyData(entType).requirementsTooltip;
grayscale = "grayscale:";
affordableMask.hidden = false;
affordableMask.sprite = "colour: 0 0 0 127";
}
if ((guiName == CONSTRUCTION || guiName == TRAINING) && canBeAddedCount == 0)
{
grayscale = "grayscale:";
affordableMask.hidden = false;
affordableMask.sprite = "colour: 0 0 0 127";
}
if (guiName == GARRISON)
{
var ents = garrisonGroups.getEntsByName(item);
var entplayer = GetEntityState(ents[0]).player;
button.sprite = "colour: " + g_Players[entplayer].color.r + " " + g_Players[entplayer].color.g + " " + g_Players[entplayer].color.b;
var player = Engine.GetPlayerID();
if(player != unitEntState.player && !g_DevSettings.controlAll)
{
if (entplayer != player)
{
button.enabled = false;
grayscale = "grayscale:";
}
}
}
icon.sprite = "stretched:" + grayscale + "session/portraits/" + template.icon;
if (guiName == RESEARCH)
{
// Check resource requirements
var neededResources = Engine.GuiInterfaceCall("GetNeededResources", template.cost);
if (neededResources)
{
if (button.enabled !== false)
{
button.enabled = false;
affordableMask.hidden = false;
var totalCost = 0;
for each (var resource in neededResources)
totalCost += resource;
var alpha = 50 + totalCost/10;
alpha = alpha > 125 ? 125 : alpha;
affordableMask.sprite = "colour: 255 0 0 " + (alpha);
}
button.tooltip += getNeededResourcesTooltip(neededResources);
}
if (item.pair)
{
grayscale = "";
button1.enabled = true;
affordableMask1.hidden = true;
if (!Engine.GuiInterfaceCall("CheckTechnologyRequirements", entType1))
{
button1.enabled = false;
button1.tooltip += "\n" + GetTechnologyData(entType1).requirementsTooltip;
grayscale = "grayscale:";
affordableMask1.hidden = false;
affordableMask1.sprite = "colour: 0 0 0 127";
}
icon1.sprite = "stretched:" + grayscale + "session/portraits/" +template1.icon;
// Check resource requirements for second button
neededResources = Engine.GuiInterfaceCall("GetNeededResources", template1.cost);
if (neededResources)
{
if (button1.enabled !== false)
{
button1.enabled = false;
affordableMask1.hidden = false;
var totalCost = 0;
for each (var resource in neededResources)
totalCost += resource;
var alpha = 50 + totalCost/10;
alpha = alpha > 125 ? 125 : alpha;
affordableMask1.sprite = "colour: 255 0 0 " + (alpha);
}
button1.tooltip += getNeededResourcesTooltip(neededResources);
}
}
else
{
pair.hidden = true;
button1.hidden = true;
affordableMask1.hidden = true;
}
}
else if (guiName == CONSTRUCTION || guiName == TRAINING)
{
var totalCosts = {};
var trainNum = 1;
var button_disableable = true;
if (guiName == TRAINING)
{
if (Engine.HotkeyIsPressed("session.batchtrain"))
{
var [buildingsCountToTrainFullBatch, fullBatchSize, remainderBatch, batchTrainingCount] =
getTrainingBatchStatus(playerState, unitEntState.id, entType, selection);
trainNum = buildingsCountToTrainFullBatch * fullBatchSize + remainderBatch;
button_disableable = !Engine.HotkeyIsPressed("selection.remove");
}
Engine.GetGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (batchTrainingCount > 0) ? batchTrainingCount : "";
}
// Walls have no cost defined.
if (template.cost !== undefined)
totalCosts = multiplyEntityCosts(template, trainNum);
var neededResources = Engine.GuiInterfaceCall("GetNeededResources", totalCosts);
if (neededResources)
{
if (button.enabled !== false)
{
button.enabled = (button_disableable ? false : true);
// Don't display the red overlay if we can't even train/build it
if (canBeAddedCount != 0)
{
affordableMask.hidden = false;
var totalCost = 0;
for each (var resource in neededResources)
totalCost += resource;
var alpha = 50 + totalCost/10;
alpha = alpha > 125 ? 125 : alpha;
affordableMask.sprite = "colour: 255 0 0 " + (alpha);
}
}
button.tooltip += getNeededResourcesTooltip(neededResources);
}
}
}
else
{
// TODO: we should require all entities to have icons, so this case never occurs
icon.sprite = "bkFillBlack";
}
}
// Position the visible buttons (TODO: if there's lots, maybe they should be squeezed together to fit)
var numButtons = i;
var numRows = Math.ceil(numButtons / rowLength);
var buttonSideLength = Engine.GetGUIObjectByName("unit"+guiName+"Button[0]").size.bottom;
// We sort pairs upside down, so get the size from the topmost button.
if (guiName == RESEARCH)
buttonSideLength = Engine.GetGUIObjectByName("unit"+guiName+"Button["+(rowLength*numRows)+"]").size.bottom;
var buttonSpacer = buttonSideLength+1;
// Layout buttons
if (guiName == COMMAND)
{
layoutButtonRowCentered(0, guiName, 0, numButtons, COMMANDS_PANEL_WIDTH);
}
else if (guiName == RESEARCH)
{
// We support pairs so we need to add a row
numRows++;
// Layout rows from bottom to top
for (var i = 0, j = numRows; i < numRows; i++, j--)
{
layoutButtonRow(i, guiName, buttonSideLength, buttonSpacer, rowLength*(j-1), rowLength*j);
}
}
else
{
for (var i = 0; i < numRows; i++)
layoutButtonRow(i, guiName, buttonSideLength, buttonSpacer, rowLength*i, rowLength*(i+1) );
}
// Layout pair icons
if (guiName == RESEARCH)
{
var pairSize = Engine.GetGUIObjectByName("unit"+guiName+"Pair[0]").size;
var pairSideWidth = pairSize.right;
var pairSideHeight = pairSize.bottom;
var pairSpacerHeight = pairSideHeight + 1;
var pairSpacerWidth = pairSideWidth + 1;
layoutRow("Pair", 0, guiName, pairSideWidth, pairSpacerWidth, pairSideHeight, pairSpacerHeight, 0, rowLength);
}
// Resize Queue panel if needed
if (guiName == QUEUE) // or garrison
{
var panel = Engine.GetGUIObjectByName("unitQueuePanel");
var size = panel.size;
size.top = (UNIT_PANEL_BASE - ((numRows-1)*UNIT_PANEL_HEIGHT));
panel.size = size;
}
// Hide any buttons we're no longer using
for (var i = numButtons; i < g_unitPanelButtons[guiName]; ++i)
Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]").hidden = true;
// Hide unused pair buttons and symbols
if (guiName == RESEARCH)
{
for (var i = numButtons; i < g_unitPanelButtons[guiName]; ++i)
{
Engine.GetGUIObjectByName("unit"+guiName+"Button["+(i+rowLength)+"]").hidden = true;
Engine.GetGUIObjectByName("unit"+guiName+"Pair["+i+"]").hidden = true;
}
}
g_unitPanelButtons[guiName] = numButtons;
}
// Sets up "unit trading panel" - special case for setupUnitPanel
function setupUnitTradingPanel(usedPanels, unitEntState, selection)
{
usedPanels[TRADING] = 1;
var requiredGoods = unitEntState.trader.requiredGoods;
for (var i = 0; i < TRADING_RESOURCES.length; i++)
{
var resource = TRADING_RESOURCES[i];
var button = Engine.GetGUIObjectByName("unitTradingButton["+i+"]");
button.size = (i * 46) + " 0 " + ((i + 1) * 46) + " 46";
if (resource == requiredGoods)
var selectRequiredGoodsData = { "entities": selection, "requiredGoods": undefined };
else
var selectRequiredGoodsData = { "entities": selection, "requiredGoods": resource };
button.onpress = (function(e){ return function() { selectRequiredGoods(e); } })(selectRequiredGoodsData);
button.enabled = true;
- button.tooltip = "Set/unset " + resource + " as forced trading goods.";
+ button.tooltip = sprintf(translate("Set/unset %(resource)s as forced trading goods."), { resource: resource });
var icon = Engine.GetGUIObjectByName("unitTradingIcon["+i+"]");
var selected = Engine.GetGUIObjectByName("unitTradingSelection["+i+"]");
selected.hidden = !(resource == requiredGoods);
var grayscale = (resource != requiredGoods) ? "grayscale:" : "";
icon.sprite = "stretched:"+grayscale+"session/icons/resources/" + resource + ".png";
}
}
// Sets up "unit barter panel" - special case for setupUnitPanel
function setupUnitBarterPanel(unitEntState, playerState)
{
// Amount of player's resource to exchange
var amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
if (Engine.HotkeyIsPressed("session.massbarter"))
amountToSell *= BARTER_BUNCH_MULTIPLIER;
// One pass for each resource
for (var i = 0; i < BARTER_RESOURCES.length; i++)
{
var resource = BARTER_RESOURCES[i];
// One pass for 'sell' row and another for 'buy'
for (var j = 0; j < 2; j++)
{
var action = BARTER_ACTIONS[j];
if (j == 0)
{
// Display the selection overlay
var selection = Engine.GetGUIObjectByName("unitBarter" + action + "Selection["+i+"]");
selection.hidden = !(i == g_barterSell);
}
// We gray out the not selected icons in 'sell' row
var grayscale = (j == 0 && i != g_barterSell) ? "grayscale:" : "";
var icon = Engine.GetGUIObjectByName("unitBarter" + action + "Icon["+i+"]");
var button = Engine.GetGUIObjectByName("unitBarter" + action + "Button["+i+"]");
button.size = (i * 46) + " 0 " + ((i + 1) * 46) + " 46";
var amountToBuy;
// We don't display a button in 'buy' row if the same resource is selected in 'sell' row
if (j == 1 && i == g_barterSell)
{
button.hidden = true;
}
else
{
button.hidden = false;
button.tooltip = action + " " + resource;
icon.sprite = "stretched:"+grayscale+"session/icons/resources/" + resource + ".png";
var sellPrice = unitEntState.barterMarket.prices["sell"][BARTER_RESOURCES[g_barterSell]];
var buyPrice = unitEntState.barterMarket.prices["buy"][resource];
amountToBuy = "+" + Math.round(sellPrice / buyPrice * amountToSell);
}
var amount;
if (j == 0)
{
button.onpress = (function(i){ return function() { g_barterSell = i; } })(i);
if (i == g_barterSell)
{
amount = "-" + amountToSell;
var neededRes = {};
neededRes[resource] = amountToSell;
var neededResources = Engine.GuiInterfaceCall("GetNeededResources", neededRes);
var hidden = neededResources ? false : true;
for (var ii = 0; ii < BARTER_RESOURCES.length; ii++)
{
var affordableMask = Engine.GetGUIObjectByName("unitBarterBuyUnaffordable["+ii+"]");
affordableMask.hidden = hidden;
}
}
else
amount = "";
}
else
{
var exchangeResourcesParameters = { "sell": BARTER_RESOURCES[g_barterSell], "buy": BARTER_RESOURCES[i], "amount": amountToSell };
button.onpress = (function(exchangeResourcesParameters){ return function() { exchangeResources(exchangeResourcesParameters); } })(exchangeResourcesParameters);
amount = amountToBuy;
}
Engine.GetGUIObjectByName("unitBarter" + action + "Amount["+i+"]").caption = amount;
}
}
}
/**
* Updates the right hand side "Unit Commands" panel. Runs in the main session loop via updateSelectionDetails().
* Delegates to setupUnitPanel to set up individual subpanels, appropriately activated depending on the selected
* unit's state.
*
* @param entState Entity state of the (first) selected unit.
* @param supplementalDetailsPanel Reference to the "supplementalSelectionDetails" GUI Object
* @param commandsPanel Reference to the "commandsPanel" GUI Object
* @param selection Array of currently selected entity IDs.
*/
function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection)
{
// Panels that are active
var usedPanels = {};
// If the selection is friendly units, add the command panels
var player = Engine.GetPlayerID();
// Get player state to check some constraints
// e.g. presence of a hero or build limits
var simState = GetSimState();
var playerState = simState.players[player];
if (entState.player == player || g_DevSettings.controlAll)
{
if (selection.length > 1)
setupUnitPanel(SELECTION, usedPanels, entState, playerState, g_Selection.groups.getTemplateNames(),
function (entType, rightPressed) { changePrimarySelectionGroup(entType, rightPressed); } );
var commands = getEntityCommandsList(entState);
if (commands.length)
setupUnitPanel(COMMAND, usedPanels, entState, playerState, commands,
function (item) { performCommand(entState.id, item.name); } );
if (entState.garrisonHolder)
{
var groups = new EntityGroups();
for (var i in selection)
{
var state = GetEntityState(selection[i]);
if (state.garrisonHolder)
groups.add(state.garrisonHolder.entities)
}
setupUnitPanel(GARRISON, usedPanels, entState, playerState, groups.getTemplateNames(),
function (item) { unloadTemplate(item); } );
}
var formations = Engine.GuiInterfaceCall("GetAvailableFormations");
if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && !entState.garrisonHolder && formations.length)
{
setupUnitPanel(FORMATION, usedPanels, entState, playerState, formations,
function (item) { performFormation(entState.id, item); } );
}
// TODO: probably should load the stance list from a data file,
// and/or vary depending on what units are selected
var stances = ["violent", "aggressive", "passive", "defensive", "standground"];
if (hasClass(entState, "Unit") && !hasClass(entState, "Animal") && stances.length)
{
setupUnitPanel(STANCE, usedPanels, entState, playerState, stances,
function (item) { performStance(entState.id, item); } );
}
Engine.GetGUIObjectByName("unitBarterPanel").hidden = !entState.barterMarket;
if (entState.barterMarket)
{
usedPanels["Barter"] = 1;
setupUnitBarterPanel(entState, playerState);
}
var buildableEnts = getAllBuildableEntitiesFromSelection();
var trainableEnts = getAllTrainableEntitiesFromSelection();
// Whether the GUI's right panel has been filled.
var rightUsed = true;
// The first selected entity's type has priority.
if (entState.buildEntities)
setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts,
function (trainEntType) { startBuildingPlacement(trainEntType, playerState); } );
else if (entState.production && entState.production.entities)
setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
// else if (entState.trader)
// setupUnitTradingPanel(usedPanels, entState, selection);
else if (!entState.foundation && entState.gate || hasClass(entState, "LongWall"))
{
// Allow long wall pieces to be converted to gates
var longWallTypes = {};
var walls = [];
var gates = [];
for (var i in selection)
{
var state = GetEntityState(selection[i]);
if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
{
var gateTemplate = getWallGateTemplate(state.id);
if (gateTemplate)
{
- var wallName = GetTemplateData(state.template).name.generic;
- var gateName = GetTemplateData(gateTemplate).name.generic;
+ var wallName = GetTemplateDataWithoutLocalization(state.template).name.generic;
+ var gateName = GetTemplateDataWithoutLocalization(gateTemplate).name.generic;
+ var tooltipString;
+
+ // For internationalization purposes, when possible, available combinations should be provided
+ // as placeholder-free strings as below.
+ //
+ // The placeholder implementation is provided only so that undetected new combinations of wall
+ // and gate names are not simply printed in English, but as close to a perfect translation as
+ // possible.
+
+ if (wallName === "Wooden Wall" && gateName === "Wooden Gate")
+ {
+ tooltipString = translate("Convert Wooden Wall into Wooden Gate");
+ }
+ else if (wallName === "Stone Wall" && gateName === "City Gate")
+ {
+ tooltipString = translate("Convert Stone Wall into City Gate");
+ }
+ else if (wallName === "Siege Wall" && gateName === "Siege Wall Gate")
+ {
+ tooltipString = translate("Convert Siege Wall into Siege Wall Gate");
+ }
+ else
+ {
+ warn(sprintf("Internationalization: Unexpected combination of ‘%(localizedWall)s’ (%(englishWall)s) and ‘%(localizedGate)s’ (%(englishGate)s). This combination of wall and gate types must be internationalized.", { localizedWall: translate(wallName), englishWall: wallName, localizedGate: translate(gateName), englishGate: gateName }));
+ tooltipString = sprintf(translate("Convert %(wall)s into %(gate)s"), { wall: translate(wallName), gate: translate(gateName) });
+ }
walls.push({
- "tooltip": "Convert " + wallName + " to " + gateName,
+ "tooltip": tooltipString,
"template": gateTemplate,
"callback": function (item) { transformWallToGate(item.template); }
});
}
// We only need one entity per type.
longWallTypes[state.template] = true;
}
else if (state.gate && !gates.length)
- for (var j = 0; j < GATE_ACTIONS.length; ++j)
- gates.push({
- "gate": state.gate,
- "tooltip": GATE_ACTIONS[j] + " gate",
- "locked": j == 0,
- "callback": function (item) { lockGate(item.locked); }
- });
+ {
+ gates.push({
+ "gate": state.gate,
+ "tooltip": translate("Lock Gate"),
+ "locked": true,
+ "callback": function (item) { lockGate(item.locked); }
+ });
+ gates.push({
+ "gate": state.gate,
+ "tooltip": translate("Unlock Gate"),
+ "locked": false,
+ "callback": function (item) { lockGate(item.locked); }
+ });
+ }
// Show both 'locked' and 'unlocked' as active if the selected gates have both lock states.
else if (state.gate && state.gate.locked != gates[0].gate.locked)
for (var j = 0; j < gates.length; ++j)
delete gates[j].gate.locked;
}
// Place wall conversion options after gate lock/unlock icons.
var items = gates.concat(walls);
if (items.length)
setupUnitPanel(GATE, usedPanels, entState, playerState, items);
else
rightUsed = false;
}
else if (entState.pack)
{
var items = [];
var packButton = false;
var unpackButton = false;
var packCancelButton = false;
var unpackCancelButton = false;
for (var i in selection)
{
// Find un/packable entities
var state = GetEntityState(selection[i]);
if (state.pack)
{
if (state.pack.progress == 0)
{
if (!state.pack.packed)
packButton = true;
else if (state.pack.packed)
unpackButton = true;
}
else
{
// Already un/packing - show cancel button
if (!state.pack.packed)
packCancelButton = true;
else if (state.pack.packed)
unpackCancelButton = true;
}
}
}
if (packButton)
- items.push({ "packing": false, "packed": false, "tooltip": "Pack", "callback": function() { packUnit(true); } });
+ items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
if (unpackButton)
- items.push({ "packing": false, "packed": true, "tooltip": "Unpack", "callback": function() { packUnit(false); } });
+ items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
if (packCancelButton)
- items.push({ "packing": true, "packed": false, "tooltip": "Cancel packing", "callback": function() { cancelPackUnit(true); } });
+ items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
if (unpackCancelButton)
- items.push({ "packing": true, "packed": true, "tooltip": "Cancel unpacking", "callback": function() { cancelPackUnit(false); } });
+ items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
if (items.length)
setupUnitPanel(PACK, usedPanels, entState, playerState, items);
else
rightUsed = false;
}
else
rightUsed = false;
if (!rightUsed)
{
// The right pane is empty. Fill the pane with a sane type.
// Prefer buildables for units and trainables for structures.
if (buildableEnts.length && (hasClass(entState, "Unit") || !trainableEnts.length))
setupUnitPanel(CONSTRUCTION, usedPanels, entState, playerState, buildableEnts,
function (trainEntType) { startBuildingPlacement(trainEntType, playerState); });
else if (trainableEnts.length)
setupUnitPanel(TRAINING, usedPanels, entState, playerState, trainableEnts,
function (trainEntType) { addTrainingToQueue(selection, trainEntType, playerState); } );
}
// Show technologies if the active panel has at most one row of icons.
if (entState.production && entState.production.technologies.length)
{
var activepane = usedPanels[CONSTRUCTION] ? buildableEnts.length : trainableEnts.length;
if (selection.length == 1 || activepane <= 8)
setupUnitPanel(RESEARCH, usedPanels, entState, playerState, entState.production.technologies,
function (researchType) { addResearchToQueue(entState.id, researchType); } );
}
if (entState.production && entState.production.queue.length)
setupUnitPanel(QUEUE, usedPanels, entState, playerState, entState.production.queue,
function (item) { removeFromProductionQueue(entState.id, item.id); } );
supplementalDetailsPanel.hidden = false;
commandsPanel.hidden = false;
}
else if (playerState.isMutualAlly[entState.player]) // owned by allied player
{
if (entState.garrisonHolder)
{
var groups = new EntityGroups();
for (var i in selection)
{
var state = GetEntityState(selection[i]);
if (state.garrisonHolder)
groups.add(state.garrisonHolder.entities)
}
setupUnitPanel(GARRISON, usedPanels, entState, playerState, groups.getTemplateNames(),
function (item) { unloadTemplate(item); } );
supplementalDetailsPanel.hidden = false;
}
else
{
supplementalDetailsPanel.hidden = true;
}
commandsPanel.hidden = true;
}
else // owned by another player
{
supplementalDetailsPanel.hidden = true;
commandsPanel.hidden = true;
}
// Hides / unhides Unit Panels (panels should be grouped by type, not by order, but we will leave that for another time)
var offset = 0;
for each (var panelName in g_unitPanels)
{
var panel = Engine.GetGUIObjectByName("unit" + panelName + "Panel");
if (usedPanels[panelName])
panel.hidden = false;
else
panel.hidden = true;
}
}
// Force hide commands panels
function hideUnitCommands()
{
for each (var panelName in g_unitPanels)
Engine.GetGUIObjectByName("unit" + panelName + "Panel").hidden = true;
}
// Get all of the available entities which can be trained by the selected entities
function getAllTrainableEntities(selection)
{
var trainableEnts = [];
var state;
// Get all buildable and trainable entities
for (var i in selection)
{
if ((state = GetEntityState(selection[i])) && state.production && state.production.entities.length)
trainableEnts = trainableEnts.concat(state.production.entities);
}
// Remove duplicates
removeDupes(trainableEnts);
return trainableEnts;
}
function getAllTrainableEntitiesFromSelection()
{
if (!g_allTrainableEntities)
g_allTrainableEntities = getAllTrainableEntities(g_Selection.toList());
return g_allTrainableEntities;
}
// Get all of the available entities which can be built by the selected entities
function getAllBuildableEntities(selection)
{
var buildableEnts = [];
var state;
// Get all buildable entities
for (var i in selection)
{
if ((state = GetEntityState(selection[i])) && state.buildEntities && state.buildEntities.length)
buildableEnts = buildableEnts.concat(state.buildEntities);
}
// Remove duplicates
removeDupes(buildableEnts);
return buildableEnts;
}
function getAllBuildableEntitiesFromSelection()
{
if (!g_allBuildableEntities)
g_allBuildableEntities = getAllBuildableEntities(g_Selection.toList());
return g_allBuildableEntities;
}
// Check if the selection can move into formation, and cache the result
function canMoveSelectionIntoFormation(formationTemplate)
{
if (!(formationTemplate in g_canMoveIntoFormation))
{
g_canMoveIntoFormation[formationTemplate] = Engine.GuiInterfaceCall("CanMoveEntsIntoFormation", {
"ents": g_Selection.toList(),
"formationTemplate": formationTemplate
});
}
return g_canMoveIntoFormation[formationTemplate];
}
Index: ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.xml (revision 14954)
@@ -1,40 +1,42 @@
- Welcome to 0 A.D. !
+
+ Welcome to 0 A.D. !
+
- Show this message in the future
+
+ Show this message in the future
+
- OK
+ OK
- Known issues (web)
+ Known issues (web)
+ openURL("http://www.wildfiregames.com/forum/index.php?showtopic=15796");
+ ]]>
Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 14954)
@@ -1,2025 +1,2014 @@
function GuiInterface() {}
GuiInterface.prototype.Schema =
"";
GuiInterface.prototype.Serialize = function()
{
// This component isn't network-synchronised so we mustn't serialise
// its non-deterministic data. Instead just return an empty object.
return {};
};
GuiInterface.prototype.Deserialize = function(obj)
{
this.Init();
};
GuiInterface.prototype.Init = function()
{
this.placementEntity = undefined; // = undefined or [templateName, entityID]
this.placementWallEntities = undefined;
this.placementWallLastAngle = 0;
this.notifications = [];
this.renamedEntities = [];
this.timeNotificationID = 1;
this.timeNotifications = [];
this.entsRallyPointsDisplayed = [];
};
/*
* All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
* from GUI scripts, and executed here with arguments (player, arg).
*
* CAUTION: The input to the functions in this module is not network-synchronised, so it
* mustn't affect the simulation state (i.e. the data that is serialised and can affect
* the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
*/
/**
* Returns global information about the current game state.
* This is used by the GUI and also by AI scripts.
*/
GuiInterface.prototype.GetSimulationState = function(player)
{
var ret = {
"players": []
};
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerEntityLimits = Engine.QueryInterface(playerEnt, IID_EntityLimits);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
// Work out what phase we are in
var cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
var phase = "";
if (cmpTechnologyManager.IsTechnologyResearched("phase_city"))
phase = "city";
else if (cmpTechnologyManager.IsTechnologyResearched("phase_town"))
phase = "town";
else if (cmpTechnologyManager.IsTechnologyResearched("phase_village"))
phase = "village";
// store player ally/neutral/enemy data as arrays
var allies = [];
var mutualAllies = [];
var neutrals = [];
var enemies = [];
for (var j = 0; j < n; ++j)
{
allies[j] = cmpPlayer.IsAlly(j);
mutualAllies[j] = cmpPlayer.IsMutualAlly(j);
neutrals[j] = cmpPlayer.IsNeutral(j);
enemies[j] = cmpPlayer.IsEnemy(j);
}
var playerData = {
"name": cmpPlayer.GetName(),
"civ": cmpPlayer.GetCiv(),
"colour": cmpPlayer.GetColour(),
"popCount": cmpPlayer.GetPopulationCount(),
"popLimit": cmpPlayer.GetPopulationLimit(),
"popMax": cmpPlayer.GetMaxPopulation(),
"heroes": cmpPlayer.GetHeroes(),
"resourceCounts": cmpPlayer.GetResourceCounts(),
"trainingBlocked": cmpPlayer.IsTrainingBlocked(),
"state": cmpPlayer.GetState(),
"team": cmpPlayer.GetTeam(),
"teamsLocked": cmpPlayer.GetLockTeams(),
"cheatsEnabled": cmpPlayer.GetCheatsEnabled(),
"phase": phase,
"isAlly": allies,
"isMutualAlly": mutualAllies,
"isNeutral": neutrals,
"isEnemy": enemies,
"entityLimits": cmpPlayerEntityLimits.GetLimits(),
"entityCounts": cmpPlayerEntityLimits.GetCounts(),
"entityLimitChangers": cmpPlayerEntityLimits.GetLimitChangers(),
"researchQueued": cmpTechnologyManager.GetQueuedResearch(),
"researchStarted": cmpTechnologyManager.GetStartedResearch(),
"researchedTechs": cmpTechnologyManager.GetResearchedTechs(),
"classCounts": cmpTechnologyManager.GetClassCounts(),
"typeCountsByClass": cmpTechnologyManager.GetTypeCountsByClass()
};
ret.players.push(playerData);
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
{
ret.circularMap = cmpRangeManager.GetLosCircular();
}
// Add timeElapsed
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
ret.timeElapsed = cmpTimer.GetTime();
return ret;
};
GuiInterface.prototype.GetExtendedSimulationState = function(player)
{
// Get basic simulation info
var ret = this.GetSimulationState();
// Add statistics to each player
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics();
}
// Add bartering prices
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
ret.barterPrices = cmpBarter.GetPrices();
return ret;
};
GuiInterface.prototype.GetRenamedEntities = function(player)
{
return this.renamedEntities;
};
GuiInterface.prototype.ClearRenamedEntities = function(player)
{
this.renamedEntities = [];
};
/**
* Get common entity info, often used in the gui
*/
GuiInterface.prototype.GetEntityState = function(player, ent)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
// All units must have a template; if not then it's a nonexistent entity id
var template = cmpTemplateManager.GetCurrentTemplateName(ent);
if (!template)
return null;
var ret = {
"id": ent,
"template": template,
"alertRaiser": null,
"buildEntities": null,
"identity": null,
"foundation": null,
"garrisonHolder": null,
"gate": null,
"guard": null,
"pack": null,
"player": -1,
"position": null,
"production": null,
"rallyPoint": null,
"rotation": null,
"trader": null,
"unitAI": null,
"visibility": null,
};
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity)
{
ret.identity = {
"rank": cmpIdentity.GetRank(),
"classes": cmpIdentity.GetClassesList(),
"selectionGroupName": cmpIdentity.GetSelectionGroupName()
};
}
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition && cmpPosition.IsInWorld())
{
ret.position = cmpPosition.GetPosition();
ret.rotation = cmpPosition.GetRotation();
}
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (cmpHealth)
{
ret.hitpoints = Math.ceil(cmpHealth.GetHitpoints());
ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
ret.needsHeal = !cmpHealth.IsUnhealable();
}
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
if (cmpBuilder)
{
ret.buildEntities = cmpBuilder.GetEntitiesList();
}
var cmpPack = Engine.QueryInterface(ent, IID_Pack);
if (cmpPack)
{
ret.pack = {
"packed": cmpPack.IsPacked(),
"progress": cmpPack.GetProgress(),
};
}
var cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
if (cmpProductionQueue)
{
ret.production = {
"entities": cmpProductionQueue.GetEntitiesList(),
"technologies": cmpProductionQueue.GetTechnologiesList(),
"queue": cmpProductionQueue.GetQueue(),
};
}
var cmpTrader = Engine.QueryInterface(ent, IID_Trader);
if (cmpTrader)
{
ret.trader = {
"goods": cmpTrader.GetGoods(),
"requiredGoods": cmpTrader.GetRequiredGoods()
};
}
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (cmpFoundation)
{
ret.foundation = {
"progress": cmpFoundation.GetBuildPercentage(),
"numBuilders": cmpFoundation.GetNumBuilders()
};
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
{
ret.player = cmpOwnership.GetOwner();
}
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
ret.rallyPoint = {'position': cmpRallyPoint.GetPositions()[0]}; // undefined or {x,z} object
}
var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
ret.garrisonHolder = {
"entities": cmpGarrisonHolder.GetEntities(),
"allowedClasses": cmpGarrisonHolder.GetAllowedClassesList(),
"capacity": cmpGarrisonHolder.GetCapacity(),
"garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount()
};
}
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
ret.unitAI = {
"state": cmpUnitAI.GetCurrentState(),
"orders": cmpUnitAI.GetOrders(),
"hasWorkOrders": cmpUnitAI.HasWorkOrders(),
"canGuard": cmpUnitAI.CanGuard(),
"isGuarding": cmpUnitAI.IsGuardOf(),
};
// Add some information needed for ungarrisoning
if (cmpUnitAI.IsGarrisoned() && ret.player !== undefined)
ret.template = "p" + ret.player + "&" + ret.template;
}
var cmpGuard = Engine.QueryInterface(ent, IID_Guard);
if (cmpGuard)
{
ret.guard = {
"entities": cmpGuard.GetEntities(),
};
}
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
if (cmpGate)
{
ret.gate = {
"locked": cmpGate.IsLocked(),
};
}
var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser);
if (cmpAlertRaiser)
{
ret.alertRaiser = {
"level": cmpAlertRaiser.GetLevel(),
"canIncreaseLevel": cmpAlertRaiser.CanIncreaseLevel(),
"hasRaisedAlert": cmpAlertRaiser.HasRaisedAlert(),
};
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
return ret;
};
/**
* Get additionnal entity info, rarely used in the gui
*/
GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
{
var ret = {
"armour": null,
"attack": null,
"barterMarket": null,
"buildingAI": null,
"healer": null,
"obstruction": null,
"promotion": null,
"resourceCarrying": null,
"resourceDropsite": null,
"resourceGatherRates": null,
"resourceSupply": null,
};
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
if (cmpAttack)
{
var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show? show all?
ret.attack = cmpAttack.GetAttackStrengths(type);
var range = cmpAttack.GetRange(type);
ret.attack.type = type;
ret.attack.minRange = range.min;
ret.attack.maxRange = range.max;
var timers = cmpAttack.GetTimers(type);
ret.attack.prepareTime = timers.prepare;
ret.attack.repeatTime = timers.repeat;
if (type == "Ranged")
{
ret.attack.elevationBonus = range.elevationBonus;
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
{
// For units, take the rage in front of it, no spread. So angle = 0
ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 0);
}
else if(cmpPosition && cmpPosition.IsInWorld())
{
// For buildings, take the average elevation around it. So angle = 2*pi
ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 2*Math.PI);
}
else
{
// not in world, set a default?
ret.attack.elevationAdaptedRange = ret.attack.maxRange;
}
}
else
{
// not a ranged attack, set some defaults
ret.attack.elevationBonus = 0;
ret.attack.elevationAdaptedRange = ret.attack.maxRange;
}
}
var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
if (cmpArmour)
{
ret.armour = cmpArmour.GetArmourStrengths();
}
var cmpBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI);
if (cmpBuildingAI)
{
ret.buildingAI = {
"defaultArrowCount": cmpBuildingAI.GetDefaultArrowCount(),
"garrisonArrowMultiplier": cmpBuildingAI.GetGarrisonArrowMultiplier(),
"garrisonArrowClasses": cmpBuildingAI.GetGarrisonArrowClasses(),
"arrowCount": cmpBuildingAI.GetArrowCount()
};
}
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (cmpObstruction)
{
ret.obstruction = {
"controlGroup": cmpObstruction.GetControlGroup(),
"controlGroup2": cmpObstruction.GetControlGroup2(),
};
}
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply)
{
ret.resourceSupply = {
"isInfinite": cmpResourceSupply.IsInfinite(),
"max": cmpResourceSupply.GetMaxAmount(),
"amount": cmpResourceSupply.GetCurrentAmount(),
"type": cmpResourceSupply.GetType(),
"killBeforeGather": cmpResourceSupply.GetKillBeforeGather(),
"maxGatherers": cmpResourceSupply.GetMaxGatherers(),
"gatherers": cmpResourceSupply.GetGatherers()
};
}
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
if (cmpResourceGatherer)
{
ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
}
var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
ret.resourceDropsite = {
"types": cmpResourceDropsite.GetTypes()
};
}
var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
if (cmpPromotion)
{
ret.promotion = {
"curr": cmpPromotion.GetCurrentXp(),
"req": cmpPromotion.GetRequiredXp()
};
}
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("BarterMarket"))
{
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
ret.barterMarket = { "prices": cmpBarter.GetPrices() };
}
var cmpHeal = Engine.QueryInterface(ent, IID_Heal);
if (cmpHeal)
{
ret.healer = {
"unhealableClasses": cmpHeal.GetUnhealableClasses(),
"healableClasses": cmpHeal.GetHealableClasses(),
};
}
return ret;
};
GuiInterface.prototype.GetAverageRangeForBuildings = function(player, cmd)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var rot = {x:0, y:0, z:0};
var pos = {x:cmd.x,z:cmd.z};
pos.y = cmpTerrain.GetGroundLevel(cmd.x, cmd.z);
var elevationBonus = cmd.elevationBonus || 0;
var range = cmd.range;
return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, elevationBonus, 2*Math.PI);
};
GuiInterface.prototype.GetTemplateData = function(player, extendedName)
{
var name = extendedName;
// Special case for garrisoned units which have a extended template
if (extendedName.indexOf("&") != -1)
name = extendedName.slice(extendedName.indexOf("&")+1);
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(name);
if (!template)
return null;
var ret = {};
if (template.Armour)
{
ret.armour = {
"hack": ApplyValueModificationsToTemplate("Armour/Hack", +template.Armour.Hack, player, template),
"pierce": ApplyValueModificationsToTemplate("Armour/Pierce", +template.Armour.Pierce, player, template),
"crush": ApplyValueModificationsToTemplate("Armour/Crush", +template.Armour.Crush, player, template),
};
}
if (template.Attack)
{
ret.attack = {};
for (var type in template.Attack)
{
ret.attack[type] = {
"hack": ApplyValueModificationsToTemplate("Attack/"+type+"/Hack", +(template.Attack[type].Hack || 0), player, template),
"pierce": ApplyValueModificationsToTemplate("Attack/"+type+"/Pierce", +(template.Attack[type].Pierce || 0), player, template),
"crush": ApplyValueModificationsToTemplate("Attack/"+type+"/Crush", +(template.Attack[type].Crush || 0), player, template),
"minRange": ApplyValueModificationsToTemplate("Attack/"+type+"/MinRange", +(template.Attack[type].MinRange || 0), player, template),
"maxRange": ApplyValueModificationsToTemplate("Attack/"+type+"/MaxRange", +template.Attack[type].MaxRange, player, template),
"elevationBonus": ApplyValueModificationsToTemplate("Attack/"+type+"/ElevationBonus", +(template.Attack[type].ElevationBonus || 0), player, template),
};
}
}
if (template.BuildRestrictions)
{
// required properties
ret.buildRestrictions = {
"placementType": template.BuildRestrictions.PlacementType,
"territory": template.BuildRestrictions.Territory,
"category": template.BuildRestrictions.Category,
};
// optional properties
if (template.BuildRestrictions.Distance)
{
ret.buildRestrictions.distance = {
"fromCategory": template.BuildRestrictions.Distance.FromCategory,
};
if (template.BuildRestrictions.Distance.MinDistance) ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance;
if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
}
}
if (template.TrainingRestrictions)
{
ret.trainingRestrictions = {
"category": template.TrainingRestrictions.Category,
};
}
if (template.Cost)
{
ret.cost = {};
if (template.Cost.Resources.food) ret.cost.food = ApplyValueModificationsToTemplate("Cost/Resources/food", +template.Cost.Resources.food, player, template);
if (template.Cost.Resources.wood) ret.cost.wood = ApplyValueModificationsToTemplate("Cost/Resources/wood", +template.Cost.Resources.wood, player, template);
if (template.Cost.Resources.stone) ret.cost.stone = ApplyValueModificationsToTemplate("Cost/Resources/stone", +template.Cost.Resources.stone, player, template);
if (template.Cost.Resources.metal) ret.cost.metal = ApplyValueModificationsToTemplate("Cost/Resources/metal", +template.Cost.Resources.metal, player, template);
if (template.Cost.Population) ret.cost.population = ApplyValueModificationsToTemplate("Cost/Population", +template.Cost.Population, player, template);
if (template.Cost.PopulationBonus) ret.cost.populationBonus = ApplyValueModificationsToTemplate("Cost/PopulationBonus", +template.Cost.PopulationBonus, player, template);
if (template.Cost.BuildTime) ret.cost.time = ApplyValueModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, player, template);
}
if (template.Footprint)
{
ret.footprint = {"height": template.Footprint.Height};
if (template.Footprint.Square)
ret.footprint.square = {"width": +template.Footprint.Square["@width"], "depth": +template.Footprint.Square["@depth"]};
else if (template.Footprint.Circle)
ret.footprint.circle = {"radius": +template.Footprint.Circle["@radius"]};
else
warn("[GetTemplateData] Unrecognized Footprint type");
}
if (template.Obstruction)
{
ret.obstruction = {
"active": ("" + template.Obstruction.Active == "true"),
"blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
"blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
"blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
"blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
"disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
"shape": {}
};
if (template.Obstruction.Static)
{
ret.obstruction.shape.type = "static";
ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
}
else if (template.Obstruction.Unit)
{
ret.obstruction.shape.type = "unit";
ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
}
else
{
ret.obstruction.shape.type = "cluster";
}
}
if (template.Pack)
{
ret.pack = {
"state": template.Pack.State,
"time": ApplyValueModificationsToTemplate("Pack/Time", +template.Pack.Time, player, template),
};
}
if (template.Health)
{
ret.health = Math.round(ApplyValueModificationsToTemplate("Health/Max", +template.Health.Max, player, template));
}
if (template.Identity)
{
ret.selectionGroupName = template.Identity.SelectionGroupName;
ret.name = {
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
"generic": template.Identity.GenericName
};
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
ret.requiredTechnology = template.Identity.RequiredTechnology;
ret.identityClassesString = GetTemplateIdentityClassesString(template);
}
if (template.UnitMotion)
{
ret.speed = {
"walk": ApplyValueModificationsToTemplate("UnitMotion/WalkSpeed", +template.UnitMotion.WalkSpeed, player, template),
};
if (template.UnitMotion.Run) ret.speed.run = ApplyValueModificationsToTemplate("UnitMotion/Run/Speed", +template.UnitMotion.Run.Speed, player, template);
}
if (template.Trader)
ret.trader = template.Trader;
if (template.WallSet)
{
ret.wallSet = {
"templates": {
"tower": template.WallSet.Templates.Tower,
"gate": template.WallSet.Templates.Gate,
"long": template.WallSet.Templates.WallLong,
"medium": template.WallSet.Templates.WallMedium,
"short": template.WallSet.Templates.WallShort,
},
"maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
"minTowerOverlap": +template.WallSet.MinTowerOverlap,
};
}
if (template.WallPiece)
{
ret.wallPiece = {"length": +template.WallPiece.Length};
}
return ret;
};
GuiInterface.prototype.GetTechnologyData = function(player, name)
{
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
var template = cmpTechTempMan.GetTemplate(name);
if (!template)
{
warn("Tried to get data for invalid technology: " + name);
return null;
}
var ret = {};
// Get specific name for this civ or else the generic specific name
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
var specific = undefined;
if (template.specificName)
{
if (template.specificName[cmpPlayer.GetCiv()])
specific = template.specificName[cmpPlayer.GetCiv()];
else
specific = template.specificName['generic'];
}
ret.name = {
"specific": specific,
"generic": template.genericName,
};
if (template.icon)
ret.icon = "technologies/" + template.icon;
else
ret.icon = null;
ret.cost = {
"food": template.cost ? (+template.cost.food) : 0,
"wood": template.cost ? (+template.cost.wood) : 0,
"metal": template.cost ? (+template.cost.metal) : 0,
"stone": template.cost ? (+template.cost.stone) : 0,
"time": template.researchTime ? (+template.researchTime) : 0,
}
ret.tooltip = template.tooltip;
if (template.requirementsTooltip)
ret.requirementsTooltip = template.requirementsTooltip;
else
ret.requirementsTooltip = "";
ret.description = template.description;
return ret;
};
GuiInterface.prototype.IsTechnologyResearched = function(player, tech)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
return cmpTechnologyManager.IsTechnologyResearched(tech);
};
// Checks whether the requirements for this technology have been met
GuiInterface.prototype.CheckTechnologyRequirements = function(player, tech)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
return cmpTechnologyManager.CanResearch(tech);
};
// Returns technologies that are being actively researched, along with
// which entity is researching them and how far along the research is.
GuiInterface.prototype.GetStartedResearch = function(player)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
var ret = {};
for (var tech in cmpTechnologyManager.GetTechsStarted())
{
ret[tech] = { "researcher": cmpTechnologyManager.GetResearcher(tech) };
var cmpProductionQueue = Engine.QueryInterface(ret[tech].researcher, IID_ProductionQueue);
if (cmpProductionQueue)
ret[tech].progress = cmpProductionQueue.GetQueue()[0].progress;
else
ret[tech].progress = 0;
}
return ret;
};
// Returns the battle state of the player.
GuiInterface.prototype.GetBattleState = function(player)
{
var cmpBattleDetection = QueryPlayerIDInterface(player, IID_BattleDetection);
return cmpBattleDetection.GetState();
};
// Returns a list of ongoing attacks against the player.
GuiInterface.prototype.GetIncomingAttacks = function(player)
{
var cmpAttackDetection = QueryPlayerIDInterface(player, IID_AttackDetection);
return cmpAttackDetection.GetIncomingAttacks();
};
// Used to show a red square over GUI elements you can't yet afford.
GuiInterface.prototype.GetNeededResources = function(player, amounts)
{
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return cmpPlayer.GetNeededResources(amounts);
};
GuiInterface.prototype.AddTimeNotification = function(notification)
{
var time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime();
- notification.endTime = notification.time + time;
+ notification.endTime = notification.duration + time;
notification.id = ++this.timeNotificationID;
this.timeNotifications.push(notification);
this.timeNotifications.sort(function (n1, n2){return n2.endTime - n1.endTime});
return this.timeNotificationID;
};
GuiInterface.prototype.DeleteTimeNotification = function(notificationID)
{
for (var i in this.timeNotifications)
{
if (this.timeNotifications[i].id == notificationID)
{
this.timeNotifications.splice(i);
return;
}
}
};
-GuiInterface.prototype.GetTimeNotificationText = function(playerID)
-{
- var formatTime = function(time)
- {
- // add 1000 ms to get ceiled instead of floored millisecons
- // displaying 00:00 for a full second isn't nice
- time += 1000;
- var hours = Math.floor(time / 1000 / 60 / 60);
- var minutes = Math.floor(time / 1000 / 60) % 60;
- var seconds = Math.floor(time / 1000) % 60;
- return (hours ? hours + ':' : "") + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds);
- };
+GuiInterface.prototype.GetTimeNotifications = function(playerID)
+{
var time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime();
- var text = "";
- for each (var n in this.timeNotifications)
+ var toDelete = [];
+ for (var n of this.timeNotifications)
{
- if (time >= n.endTime)
- {
- // delete the notification and start over
- this.DeleteTimeNotification(n.id);
- return this.GetTimeNotificationText(playerID);
- }
- if (n.players.indexOf(playerID) >= 0)
- text += n.message.replace("%T",formatTime(n.endTime - time))+"\n";
- }
- return text;
+ n.time = n.endTime - time;
+ if (n.time < 0)
+ toDelete.push(n.id);
+ }
+ for (var id of toDelete)
+ this.DeleteTimeNotification(id);
+ return this.timeNotifications;
};
GuiInterface.prototype.PushNotification = function(notification)
{
this.notifications.push(notification);
};
GuiInterface.prototype.GetNextNotification = function()
{
if (this.notifications.length)
return this.notifications.pop();
else
return false;
};
GuiInterface.prototype.GetAvailableFormations = function(player, data)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
return cmpPlayer.GetFormations();
};
GuiInterface.prototype.GetFormationRequirements = function(player, data)
{
return GetFormationRequirements(data.formationTemplate);
};
GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data)
{
return CanMoveEntsIntoFormation(data.ents, data.formationTemplate);
};
GuiInterface.prototype.GetFormationInfoFromTemplate = function(player, data)
{
var r = {};
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(data.templateName);
if (!template || !template.Formation)
return r;
r.name = template.Formation.FormationName;
- r.tooltip = template.Formation.DisabledTooltip;
+ r.tooltip = template.Formation.DisabledTooltip || "";
return r;
};
GuiInterface.prototype.IsFormationSelected = function(player, data)
{
for each (var ent in data.ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
// GetLastFormationName is named in a strange way as it (also) is
// the value of the current formation (see Formation.js LoadFormation)
if (cmpUnitAI.GetLastFormationTemplate() == data.formationTemplate)
return true;
}
}
return false;
};
GuiInterface.prototype.IsStanceSelected = function(player, data)
{
for each (var ent in data.ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
if (cmpUnitAI.GetStanceName() == data.stance)
return true;
}
}
return false;
};
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var playerColours = {}; // cache of owner -> colour map
for each (var ent in cmd.entities)
{
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
if (!cmpSelectable)
continue;
// Find the entity's owner's colour:
var owner = -1;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
owner = cmpOwnership.GetOwner();
var colour = playerColours[owner];
if (!colour)
{
colour = {"r":1, "g":1, "b":1};
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player);
if (cmpPlayer)
colour = cmpPlayer.GetColour();
playerColours[owner] = colour;
}
cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":cmd.alpha}, cmd.selected);
}
};
GuiInterface.prototype.SetStatusBars = function(player, cmd)
{
for each (var ent in cmd.entities)
{
var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
if (cmpStatusBars)
cmpStatusBars.SetEnabled(cmd.enabled);
}
};
GuiInterface.prototype.GetPlayerEntities = function(player)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
return cmpRangeManager.GetEntitiesByPlayer(player);
};
/**
* Displays the rally points of a given list of entities (carried in cmd.entities).
*
* The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
* be rendered, in order to support instantaneously rendering a rally point marker at a specified location
* instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
* If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
* RallyPoint component.
*/
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
// If there are some rally points already displayed, first hide them
for each (var ent in this.entsRallyPointsDisplayed)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (cmpRallyPointRenderer)
cmpRallyPointRenderer.SetDisplayed(false);
}
this.entsRallyPointsDisplayed = [];
// Show the rally points for the passed entities
for each (var ent in cmd.entities)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (!cmpRallyPointRenderer)
continue;
// entity must have a rally point component to display a rally point marker
// (regardless of whether cmd specifies a custom location)
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
// Verify the owner
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position
var pos;
if (cmd.x && cmd.z)
pos = cmd;
else
pos = cmpRallyPoint.GetPositions()[0]; // may return undefined if no rally point is set
if (pos)
{
// Only update the position if we changed it (cmd.queued is set)
if (cmd.queued == true)
cmpRallyPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
else if (cmd.queued == false)
cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
// rebuild the renderer when not set (when reading saved game or in case of building update)
else if (!cmpRallyPointRenderer.IsSet())
for each (var posi in cmpRallyPoint.GetPositions())
cmpRallyPointRenderer.AddPosition({'x': posi.x, 'y': posi.z});
cmpRallyPointRenderer.SetDisplayed(true);
// remember which entities have their rally points displayed so we can hide them again
this.entsRallyPointsDisplayed.push(ent);
}
}
};
/**
* Display the building placement preview.
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
*
* Returns result object from CheckPlacement:
* {
- * "success": true iff the placement is valid, else false
- * "message": message to display in UI for invalid placement, else empty string
+ * "success": true iff the placement is valid, else false
+ * "message": message to display in UI for invalid placement, else ""
+ "parameters": parameters to use in the message
* }
*/
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
var result = {
"success": false,
"message": "",
+ "parameters": {},
}
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
{
// Destroy the old preview if there was one
if (this.placementEntity)
Engine.DestroyEntity(this.placementEntity[1]);
// Load the new template
if (cmd.template == "")
{
this.placementEntity = undefined;
}
else
{
this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)];
}
}
if (this.placementEntity)
{
var ent = this.placementEntity[1];
// Move the preview into the right location
var pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
{
pos.JumpTo(cmd.x, cmd.z);
pos.SetYRotation(cmd.angle);
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether building placement is valid
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
error("cmpBuildRestrictions not defined");
else
result = cmpBuildRestrictions.CheckPlacement();
// Set it to a red shade if this is an invalid location
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
{
if (cmd.actorSeed !== undefined)
cmpVisual.SetActorSeed(cmd.actorSeed);
if (!result.success)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
}
return result;
};
/**
* Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not
* specified. Returns an object with information about the list of entities that need to be newly constructed to complete
* at least a part of the wall, or false if there are entities required to build at least part of the wall but none of
* them can be validly constructed.
*
* It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one
* another depending on things like snapping and whether some of the entities inside them can be validly positioned.
* We have:
* - The list of entities that previews the wall. This list is usually equal to the entities required to construct the
* entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities
* to preview the completed tower on top of its foundation.
*
* - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether
* any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing
* towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we
* snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly
* constructed.
*
* - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same
* as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens
* e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly
* constructed but come after said first invalid entity are also truncated away.
*
* With this in mind, this method will return false if the second list is not empty, but the third one is. That is, if there
* were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in
* case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset
* argument (see below). Otherwise, it will return an object with the following information:
*
* result: {
* 'startSnappedEnt': ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers.
* 'endSnappedEnt': ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this
* can only be set if no truncation of the second list occurs; if we snapped to an entity at the ending side
* but the wall construction was truncated before we could reach it, it won't be set here. Currently only
* supports towers.
* 'pieces': Array with the following data for each of the entities in the third list:
* [{
* 'template': Template name of the entity.
* 'x': X coordinate of the entity's position.
* 'z': Z coordinate of the entity's position.
* 'angle': Rotation around the Y axis of the entity (in radians).
* },
* ...]
* 'cost': { The total cost required for constructing all the pieces as listed above.
* 'food': ...,
* 'wood': ...,
* 'stone': ...,
* 'metal': ...,
* 'population': ...,
* 'populationBonus': ...,
* }
* }
*
* @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview.
* @param cmd.start Starting point of the wall segment being created.
* @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only
* the starting point of the wall is available at this time (e.g. while the player is still in the process
* of picking a starting point), and that therefore only the first entity in the wall (a tower) should be
* previewed.
* @param cmd.snapEntities List of candidate entities to snap the start and ending positions to.
*/
GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
{
var wallSet = cmd.wallSet;
var start = {
"pos": cmd.start,
"angle": 0,
"snapped": false, // did the start position snap to anything?
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
};
var end = {
"pos": cmd.end,
"angle": 0,
"snapped": false, // did the start position snap to anything?
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
};
// --------------------------------------------------------------------------------
// do some entity cache management and check for snapping
if (!this.placementWallEntities)
this.placementWallEntities = {};
if (!wallSet)
{
// we're clearing the preview, clear the entity cache and bail
var numCleared = 0;
for (var tpl in this.placementWallEntities)
{
for each (var ent in this.placementWallEntities[tpl].entities)
Engine.DestroyEntity(ent);
this.placementWallEntities[tpl].numUsed = 0;
this.placementWallEntities[tpl].entities = [];
// keep template data around
}
return false;
}
else
{
// Move all existing cached entities outside of the world and reset their use count
for (var tpl in this.placementWallEntities)
{
for each (var ent in this.placementWallEntities[tpl].entities)
{
var pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
pos.MoveOutOfWorld();
}
this.placementWallEntities[tpl].numUsed = 0;
}
// Create cache entries for templates we haven't seen before
for each (var tpl in wallSet.templates)
{
if (!(tpl in this.placementWallEntities))
{
this.placementWallEntities[tpl] = {
"numUsed": 0,
"entities": [],
"templateData": this.GetTemplateData(player, tpl),
};
// ensure that the loaded template data contains a wallPiece component
if (!this.placementWallEntities[tpl].templateData.wallPiece)
{
error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'");
return false;
}
}
}
}
// prevent division by zero errors further on if the start and end positions are the same
if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z))
end.pos = undefined;
// See if we need to snap the start and/or end coordinates to any of our list of snap entities. Note that, despite the list
// of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping
// data to determine whether it snapped to an entity (if any), and to which one (see GetFoundationSnapData).
if (cmd.snapEntities)
{
var snapRadius = this.placementWallEntities[wallSet.templates.tower].templateData.wallPiece.length * 0.5; // determined through trial and error
var startSnapData = this.GetFoundationSnapData(player, {
"x": start.pos.x,
"z": start.pos.z,
"template": wallSet.templates.tower,
"snapEntities": cmd.snapEntities,
"snapRadius": snapRadius,
});
if (startSnapData)
{
start.pos.x = startSnapData.x;
start.pos.z = startSnapData.z;
start.angle = startSnapData.angle;
start.snapped = true;
if (startSnapData.ent)
start.snappedEnt = startSnapData.ent;
}
if (end.pos)
{
var endSnapData = this.GetFoundationSnapData(player, {
"x": end.pos.x,
"z": end.pos.z,
"template": wallSet.templates.tower,
"snapEntities": cmd.snapEntities,
"snapRadius": snapRadius,
});
if (endSnapData)
{
end.pos.x = endSnapData.x;
end.pos.z = endSnapData.z;
end.angle = endSnapData.angle;
end.snapped = true;
if (endSnapData.ent)
end.snappedEnt = endSnapData.ent;
}
}
}
// clear the single-building preview entity (we'll be rolling our own)
this.SetBuildingPlacementPreview(player, {"template": ""});
// --------------------------------------------------------------------------------
// calculate wall placement and position preview entities
var result = {
"pieces": [],
"cost": {"food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0},
};
var previewEntities = [];
if (end.pos)
previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js
// For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would
// otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of
// an issue, because all preview entities have their obstruction components deactivated, meaning that their
// obstruction shapes do not register in the simulation and hence cannot affect it. This implies that the preview
// entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces.
// Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION
// flag set), which is what we want. The only exception to this is when snapping to existing towers (or
// foundations thereof); the wall segments that connect up to these will be found to be obstructed by the
// existing tower/foundation, and be shaded red to indicate that they cannot be placed there. To prevent this,
// we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so
// that they are free from mutual obstruction (per definition of obstruction control groups). This is done by
// assigning them an extra "controlGroup" field, which we'll then set during the placement loop below.
// Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully
// constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed
// by the foundation it snaps to.
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
{
var startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction);
if (previewEntities.length > 0 && startEntObstruction)
previewEntities[0].controlGroups = [startEntObstruction.GetControlGroup()];
// if we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group
var startEntState = this.GetEntityState(player, start.snappedEnt);
if (startEntState.foundation)
{
var cmpPosition = Engine.QueryInterface(start.snappedEnt, IID_Position);
if (cmpPosition)
{
previewEntities.unshift({
"template": wallSet.templates.tower,
"pos": start.pos,
"angle": cmpPosition.GetRotation().y,
"controlGroups": [(startEntObstruction ? startEntObstruction.GetControlGroup() : undefined)],
"excludeFromResult": true, // preview only, must not appear in the result
});
}
}
}
else
{
// Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps
// when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned
// wall piece.
// To illustrate the last point, consider what happens if we used some constant instead, say, 0. Issuing the
// build command for a wall is asynchronous, so when the preview updates after shift-clicking, the wall piece
// foundations are not registered yet in the simulation. This means they cannot possibly be picked in the list
// of candidate entities for snapping. In the next preview update, we therefore hit this case, and would rotate
// the preview to 0 radians. Then, after one or two simulation updates or so, the foundations register and
// onSimulationUpdate in session.js updates the preview again. It first grabs a new list of snapping candidates,
// which this time does include the new foundations; so we snap to the entity, and rotate the preview back to
// the foundation's angle.
// The result is a noticeable rotation to 0 and back, which is undesirable. So, for a split second there until
// the simulation updates, we fake it by reusing the last angle and hope the player doesn't notice.
previewEntities.unshift({
"template": wallSet.templates.tower,
"pos": start.pos,
"angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle)
});
}
if (end.pos)
{
// Analogous to the starting side case above
if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY)
{
var endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction);
// Note that it's possible for the last entity in previewEntities to be the same as the first, i.e. the
// same wall piece snapping to both a starting and an ending tower. And it might be more common than you would
// expect; the allowed overlap between wall segments and towers facilitates this to some degree. To deal with
// the possibility of dual initial control groups, we use a '.controlGroups' array rather than a single
// '.controlGroup' property. Note that this array can only ever have 0, 1 or 2 elements (checked at a later time).
if (previewEntities.length > 0 && endEntObstruction)
{
previewEntities[previewEntities.length-1].controlGroups = (previewEntities[previewEntities.length-1].controlGroups || []);
previewEntities[previewEntities.length-1].controlGroups.push(endEntObstruction.GetControlGroup());
}
// if we're snapping to a foundation, add an extra preview tower and also set it to the same control group
var endEntState = this.GetEntityState(player, end.snappedEnt);
if (endEntState.foundation)
{
var cmpPosition = Engine.QueryInterface(end.snappedEnt, IID_Position);
if (cmpPosition)
{
previewEntities.push({
"template": wallSet.templates.tower,
"pos": end.pos,
"angle": cmpPosition.GetRotation().y,
"controlGroups": [(endEntObstruction ? endEntObstruction.GetControlGroup() : undefined)],
"excludeFromResult": true
});
}
}
}
else
{
previewEntities.push({
"template": wallSet.templates.tower,
"pos": end.pos,
"angle": (previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle)
});
}
}
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
if (!cmpTerrain)
{
error("[SetWallPlacementPreview] System Terrain component not found");
return false;
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (!cmpRangeManager)
{
error("[SetWallPlacementPreview] System RangeManager component not found");
return false;
}
// Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed
// to build at least a part of the wall (meaning that the subset is truncated after the first entity that needs to be,
// but cannot validly be, constructed). See method-level documentation for more details.
var allPiecesValid = true;
var numRequiredPieces = 0; // number of entities that are required to build the entire wall, regardless of validity
for (var i = 0; i < previewEntities.length; ++i)
{
var entInfo = previewEntities[i];
var ent = null;
var tpl = entInfo.template;
var tplData = this.placementWallEntities[tpl].templateData;
var entPool = this.placementWallEntities[tpl];
if (entPool.numUsed >= entPool.entities.length)
{
// allocate new entity
ent = Engine.AddLocalEntity("preview|" + tpl);
entPool.entities.push(ent);
}
else
{
// reuse an existing one
ent = entPool.entities[entPool.numUsed];
}
if (!ent)
{
error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'");
continue;
}
// move piece to right location
// TODO: consider reusing SetBuildingPlacementReview for this, enhanced to be able to deal with multiple entities
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition)
{
cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z);
cmpPosition.SetYRotation(entInfo.angle);
// if this piece is a tower, then it should have a Y position that is at least as high as its surrounding pieces
if (tpl === wallSet.templates.tower)
{
var terrainGroundPrev = null;
var terrainGroundNext = null;
if (i > 0)
terrainGroundPrev = cmpTerrain.GetGroundLevel(previewEntities[i-1].pos.x, previewEntities[i-1].pos.z);
if (i < previewEntities.length - 1)
terrainGroundNext = cmpTerrain.GetGroundLevel(previewEntities[i+1].pos.x, previewEntities[i+1].pos.z);
if (terrainGroundPrev != null || terrainGroundNext != null)
{
var targetY = Math.max(terrainGroundPrev, terrainGroundNext);
cmpPosition.SetHeightFixed(targetY);
}
}
}
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (!cmpObstruction)
{
error("[SetWallPlacementPreview] Preview entity of template '" + tpl + "' does not have an Obstruction component");
continue;
}
// Assign any predefined control groups. Note that there can only be 0, 1 or 2 predefined control groups; if there are
// more, we've made a programming error. The control groups are assigned from the entInfo.controlGroups array on a
// first-come first-served basis; the first value in the array is always assigned as the primary control group, and
// any second value as the secondary control group.
// By default, we reset the control groups to their standard values. Remember that we're reusing entities; if we don't
// reset them, then an ending wall segment that was e.g. at one point snapped to an existing tower, and is subsequently
// reused as a non-snapped ending wall segment, would no longer be capable of being obstructed by the same tower it was
// once snapped to.
var primaryControlGroup = ent;
var secondaryControlGroup = INVALID_ENTITY;
if (entInfo.controlGroups && entInfo.controlGroups.length > 0)
{
if (entInfo.controlGroups.length > 2)
{
error("[SetWallPlacementPreview] Encountered preview entity of template '" + tpl + "' with more than 2 initial control groups");
break;
}
primaryControlGroup = entInfo.controlGroups[0];
if (entInfo.controlGroups.length > 1)
secondaryControlGroup = entInfo.controlGroups[1];
}
cmpObstruction.SetControlGroup(primaryControlGroup);
cmpObstruction.SetControlGroup2(secondaryControlGroup);
// check whether this wall piece can be validly positioned here
var validPlacement = false;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether it's in a visible or fogged region
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
// which would show them as hidden instead of fogged
// TODO: should definitely reuse SetBuildingPlacementPreview, this is just straight up copy/pasta
var visible = (cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden");
if (visible)
{
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
{
error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for preview entity of template '" + tpl + "'");
continue;
}
// TODO: Handle results of CheckPlacement
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success);
// If a wall piece has two control groups, it's likely a segment that spans
// between two existing towers. To avoid placing a duplicate wall segment,
// check for collisions with entities that share both control groups.
if (validPlacement && entInfo.controlGroups && entInfo.controlGroups.length > 1)
validPlacement = cmpObstruction.CheckDuplicateFoundation();
}
allPiecesValid = allPiecesValid && validPlacement;
// The requirement below that all pieces so far have to have valid positions, rather than only this single one,
// ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible
// for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall
// through and past an existing building).
// Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed
// on top of foundations of incompleted towers that we snapped to; they must not be part of the result.
if (!entInfo.excludeFromResult)
numRequiredPieces++;
if (allPiecesValid && !entInfo.excludeFromResult)
{
result.pieces.push({
"template": tpl,
"x": entInfo.pos.x,
"z": entInfo.pos.z,
"angle": entInfo.angle,
});
this.placementWallLastAngle = entInfo.angle;
// grab the cost of this wall piece and add it up (note; preview entities don't have their Cost components
// copied over, so we need to fetch it from the template instead).
// TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
// boilerplate that's probably duplicated in tons of places.
result.cost.food += tplData.cost.food;
result.cost.wood += tplData.cost.wood;
result.cost.stone += tplData.cost.stone;
result.cost.metal += tplData.cost.metal;
result.cost.population += tplData.cost.population;
result.cost.populationBonus += tplData.cost.populationBonus;
result.cost.time += tplData.cost.time;
}
var canAfford = true;
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
if (cmpPlayer && cmpPlayer.GetNeededResources(result.cost))
var canAfford = false;
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
{
if (!allPiecesValid || !canAfford)
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColour(1, 1, 1, 1);
}
entPool.numUsed++;
}
// If any were entities required to build the wall, but none of them could be validly positioned, return failure
// (see method-level documentation).
if (numRequiredPieces > 0 && result.pieces.length == 0)
return false;
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
result.startSnappedEnt = start.snappedEnt;
// We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed,
// i.e. are included in result.pieces (see docs for the result object).
if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid)
result.endSnappedEnt = end.snappedEnt;
return result;
};
/**
* Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap
* it to (if necessary/useful).
*
* @param data.x The X position of the foundation to snap.
* @param data.z The Z position of the foundation to snap.
* @param data.template The template to get the foundation snapping data for.
* @param data.snapEntities Optional; list of entity IDs to snap to if {data.x, data.z} is within a circle of radius data.snapRadius
* around the entity. Only takes effect when used in conjunction with data.snapRadius.
* When this option is used and the foundation is found to snap to one of the entities passed in this list
* (as opposed to e.g. snapping to terrain features), then the result will contain an additional key "ent",
* holding the ID of the entity that was snapped to.
* @param data.snapRadius Optional; when used in conjunction with data.snapEntities, indicates the circle radius around an entity that
* {data.x, data.z} must be located within to have it snap to that entity.
*/
GuiInterface.prototype.GetFoundationSnapData = function(player, data)
{
var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateMgr.GetTemplate(data.template);
if (!template)
{
warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'");
return false;
}
if (data.snapEntities && data.snapRadius && data.snapRadius > 0)
{
// see if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest
// (TODO: break unlikely ties by choosing the lowest entity ID)
var minDist2 = -1;
var minDistEntitySnapData = null;
var radius2 = data.snapRadius * data.snapRadius;
for each (var ent in data.snapEntities)
{
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
continue;
var pos = cmpPosition.GetPosition();
var dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z);
if (dist2 > radius2)
continue;
if (minDist2 < 0 || dist2 < minDist2)
{
minDist2 = dist2;
minDistEntitySnapData = {"x": pos.x, "z": pos.z, "angle": cmpPosition.GetRotation().y, "ent": ent};
}
}
if (minDistEntitySnapData != null)
return minDistEntitySnapData;
}
if (template.BuildRestrictions.Category == "Dock")
{
// warning: copied almost identically in helpers/command.js , "GetDockAngle".
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
if (!cmpTerrain || !cmpWaterManager)
{
return false;
}
// Get footprint size
var halfSize = 0;
if (template.Footprint.Square)
{
halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"])/2;
}
else if (template.Footprint.Circle)
{
halfSize = template.Footprint.Circle["@radius"];
}
/* Find direction of most open water, algorithm:
* 1. Pick points in a circle around dock
* 2. If point is in water, add to array
* 3. Scan array looking for consecutive points
* 4. Find longest sequence of consecutive points
* 5. If sequence equals all points, no direction can be determined,
* expand search outward and try (1) again
* 6. Calculate angle using average of sequence
*/
const numPoints = 16;
for (var dist = 0; dist < 4; ++dist)
{
var waterPoints = [];
for (var i = 0; i < numPoints; ++i)
{
var angle = (i/numPoints)*2*Math.PI;
var d = halfSize*(dist+1);
var nx = data.x - d*Math.sin(angle);
var nz = data.z + d*Math.cos(angle);
if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
{
waterPoints.push(i);
}
}
var consec = [];
var length = waterPoints.length;
for (var i = 0; i < length; ++i)
{
var count = 0;
for (var j = 0; j < (length-1); ++j)
{
if (((waterPoints[(i + j) % length]+1) % numPoints) == waterPoints[(i + j + 1) % length])
{
++count;
}
else
{
break;
}
}
consec[i] = count;
}
var start = 0;
var count = 0;
for (var c in consec)
{
if (consec[c] > count)
{
start = c;
count = consec[c];
}
}
// If we've found a shoreline, stop searching
if (count != numPoints-1)
{
return {"x": data.x, "z": data.z, "angle": -(((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI)};
}
}
}
return false;
};
GuiInterface.prototype.PlaySound = function(player, data)
{
// Ignore if no entity was passed
if (!data.entity)
return;
PlaySound(data.name, data.entity);
};
GuiInterface.prototype.FindIdleUnits = function(player, data)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerEntities = cmpRangeManager.GetEntitiesByPlayer(player).filter( function(e) {
var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
if (!cmpUnitAI || !cmpUnitAI.IsIdle() || cmpUnitAI.IsGarrisoned())
return false;
var cmpIdentity = Engine.QueryInterface(e, IID_Identity);
if (!cmpIdentity || !cmpIdentity.HasClass(data.idleClass))
return false;
return true;
});
var idleUnits = [];
for (var j = 0; j < playerEntities.length; ++j)
{
var ent = playerEntities[j];
if (ent <= data.prevUnit|0 || data.excludeUnits.indexOf(ent) > -1)
continue;
idleUnits.push(ent);
playerEntities.splice(j--, 1);
if (data.limit && idleUnits.length >= data.limit)
break;
}
return idleUnits;
};
GuiInterface.prototype.GetTradingRouteGain = function(player, data)
{
if (!data.firstMarket || !data.secondMarket)
return null;
return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template);
};
GuiInterface.prototype.GetTradingDetails = function(player, data)
{
var cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
if (!cmpEntityTrader || !cmpEntityTrader.CanTrade(data.target))
return null;
var firstMarket = cmpEntityTrader.GetFirstMarket();
var secondMarket = cmpEntityTrader.GetSecondMarket();
var result = null;
if (data.target === firstMarket)
{
result = {
"type": "is first",
"hasBothMarkets": cmpEntityTrader.HasBothMarkets()
};
if (cmpEntityTrader.HasBothMarkets())
result.gain = cmpEntityTrader.GetGain();
}
else if (data.target === secondMarket)
{
result = {
"type": "is second",
"gain": cmpEntityTrader.GetGain(),
};
}
else if (!firstMarket)
{
result = {"type": "set first"};
}
else if (!secondMarket)
{
result = {
"type": "set second",
"gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
};
}
else
{
// Else both markets are not null and target is different from them
result = {"type": "set first"};
}
return result;
};
GuiInterface.prototype.CanAttack = function(player, data)
{
var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
if (!cmpAttack)
return false;
return cmpAttack.CanAttack(data.target);
};
/*
* Returns batch build time.
*/
GuiInterface.prototype.GetBatchTime = function(player, data)
{
var cmpProductionQueue = Engine.QueryInterface(data.entity, IID_ProductionQueue);
if (!cmpProductionQueue)
return 0;
return cmpProductionQueue.GetBatchTime(data.batchSize);
};
GuiInterface.prototype.IsMapRevealed = function(player)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
return cmpRangeManager.GetLosRevealAll(player);
};
GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
{
var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
cmpPathfinder.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
{
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
cmpObstructionManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
{
for each (var ent in data.entities)
{
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
cmpUnitMotion.SetDebugOverlay(data.enabled);
}
};
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.GetTraderNumber = function(player)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var traders = cmpRangeManager.GetEntitiesByPlayer(player).filter( function(e) {
return Engine.QueryInterface(e, IID_Trader);
});
var landTrader = { "total": 0, "trading": 0, "garrisoned": 0 };
var shipTrader = { "total": 0, "trading": 0 };
for each (var ent in traders)
{
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpIdentity || !cmpUnitAI)
continue;
if (cmpIdentity.HasClass("Ship"))
{
++shipTrader.total;
if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
++shipTrader.trading;
}
else
{
++landTrader.total;
if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
++landTrader.trading;
if (cmpUnitAI.order && cmpUnitAI.order.type == "Garrison")
{
var holder = cmpUnitAI.order.data.target;
var cmpHolderUnitAI = Engine.QueryInterface(holder, IID_UnitAI);
if (cmpHolderUnitAI && cmpHolderUnitAI.order && cmpHolderUnitAI.order.type == "Trade")
++landTrader.garrisoned;
}
}
}
return { "landTrader": landTrader, "shipTrader": shipTrader };
};
GuiInterface.prototype.GetTradingGoods = function(player, tradingGoods)
{
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return cmpPlayer.GetTradingGoods();
};
GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
{
this.renamedEntities.push(msg);
};
// List the GuiInterface functions that can be safely called by GUI scripts.
// (GUI scripts are non-deterministic and untrusted, so these functions must be
// appropriately careful. They are called with a first argument "player", which is
// trusted and indicates the player associated with the current client; no data should
// be returned unless this player is meant to be able to see it.)
var exposedFunctions = {
"GetSimulationState": 1,
"GetExtendedSimulationState": 1,
"GetRenamedEntities": 1,
"ClearRenamedEntities": 1,
"GetEntityState": 1,
"GetExtendedEntityState": 1,
"GetAverageRangeForBuildings": 1,
"GetTemplateData": 1,
"GetTechnologyData": 1,
"IsTechnologyResearched": 1,
"CheckTechnologyRequirements": 1,
"GetStartedResearch": 1,
"GetBattleState": 1,
"GetIncomingAttacks": 1,
"GetNeededResources": 1,
"GetNextNotification": 1,
- "GetTimeNotificationText": 1,
+ "GetTimeNotifications": 1,
"GetAvailableFormations": 1,
"GetFormationRequirements": 1,
"CanMoveEntsIntoFormation": 1,
"IsFormationSelected": 1,
"GetFormationInfoFromTemplate": 1,
"IsStanceSelected": 1,
"SetSelectionHighlight": 1,
"SetStatusBars": 1,
"GetPlayerEntities": 1,
"DisplayRallyPoint": 1,
"SetBuildingPlacementPreview": 1,
"SetWallPlacementPreview": 1,
"GetFoundationSnapData": 1,
"PlaySound": 1,
"FindIdleUnits": 1,
"GetTradingRouteGain": 1,
"GetTradingDetails": 1,
"CanAttack": 1,
"GetBatchTime": 1,
"IsMapRevealed": 1,
"SetPathfinderDebugOverlay": 1,
"SetObstructionDebugOverlay": 1,
"SetMotionDebugOverlay": 1,
"SetRangeDebugOverlay": 1,
"GetTraderNumber": 1,
"GetTradingGoods": 1,
};
GuiInterface.prototype.ScriptCall = function(player, name, args)
{
if (exposedFunctions[name])
return this[name](player, args);
else
throw new Error("Invalid GuiInterface Call name \""+name+"\"");
};
Engine.RegisterComponentType(IID_GuiInterface, "GuiInterface", GuiInterface);
Index: ps/trunk/binaries/data/mods/public/gui/session/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14954)
@@ -1,1076 +1,1107 @@
// 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;
// Is this user an observer?
var g_IsObserver = false;
// 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 } };
+var g_PlayerAssignments = { "local": { "name": translate("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}
+var g_TemplateDataWithoutLocalization = {};
function GetTemplateData(templateName)
{
if (!(templateName in g_TemplateData))
{
var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
+ translateObjectKeys(template, ["specific", "generic", "tooltip"]);
g_TemplateData[templateName] = template;
}
return g_TemplateData[templateName];
}
+function GetTemplateDataWithoutLocalization(templateName)
+{
+ if (!(templateName in g_TemplateDataWithoutLocalization))
+ {
+ var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
+ g_TemplateDataWithoutLocalization[templateName] = template;
+ }
+
+ return g_TemplateDataWithoutLocalization[templateName];
+}
+
// Cache TechnologyData
var g_TechnologyData = {}; // {id:template}
function GetTechnologyData(technologyName)
{
if (!(technologyName in g_TechnologyData))
{
var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
+ translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
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_CivData["gaia"] = { "Code": "gaia", "Name": translate("Gaia") };
if (Engine.GetPlayerID() <= 0)
{
g_IsObserver = true;
// Hide stuff observers don't use.
Engine.GetGUIObjectByName("food").hidden = true;
Engine.GetGUIObjectByName("wood").hidden = true;
Engine.GetGUIObjectByName("stone").hidden = true;
Engine.GetGUIObjectByName("metal").hidden = true;
Engine.GetGUIObjectByName("population").hidden = true;
Engine.GetGUIObjectByName("diplomacyButton1").hidden = true;
Engine.GetGUIObjectByName("tradeButton1").hidden = true;
Engine.GetGUIObjectByName("menuResignButton").enabled = false;
Engine.GetGUIObjectByName("pauseButton").enabled = false;
Engine.GetGUIObjectByName("observerText").hidden = false;
}
else
{
// TODO: Get a civ icon for gaia/observers.
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;
}
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]); }
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:
initMusic();
if (!g_IsObserver){
var civMusic = g_CivData[g_Players[Engine.GetPlayerID()].civ].Music;
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));
}
/**
* 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);
// Resume the game if not resigning.
if (!leaveGameAfterResign)
resumeGame();
}
/**
* Leave the game
* @param willRejoin If player is going to be rejoining a networked game.
*/
function leaveGame(willRejoin)
{
var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
var mapSettings = Engine.GetMapSettings();
var gameResult;
if (g_IsObserver)
{
// Observers don't win/lose.
- gameResult = "You have left the game.";
+ gameResult = translate("You have left the game.");
global.music.setState(global.music.states.VICTORY);
}
else
{
var playerState = extendedSimState.players[Engine.GetPlayerID()];
if (g_Disconnected)
- {
- gameResult = "You have been disconnected."
- }
+ gameResult = translate("You have been disconnected.");
else if (playerState.state == "won")
- {
- gameResult = "You have won the battle!";
- }
+ gameResult = translate("You have won the battle!");
else if (playerState.state == "defeated")
- {
- gameResult = "You have been defeated...";
- }
+ gameResult = translate("You have been defeated...");
else // "active"
{
global.music.setState(global.music.states.DEFEAT);
if (willRejoin)
- gameResult = "You have left the game.";
+ gameResult = translate("You have left the game.");
else
{
- gameResult = "You have abandoned the game.";
+ gameResult = translate("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)
{
// Restore camera if any
if (data.camera)
Engine.SetCameraData(data.camera.PosX, data.camera.PosY, data.camera.PosZ,
data.camera.RotX, data.camera.RotY, data.camera.Zoom);
// 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
if (!g_IsObserver)
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 || g_IsObserver)
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 btCaptions = [translate("OK")];
var btCode = [null];
- var message = "Press OK to continue";
+ var message = translate("Press OK to continue");
}
else
{
- var btCaptions = ["Yes", "No"];
+ var btCaptions = [translate("Yes"), translate("No")];
var btCode = [leaveGame, null];
- var message = "Do you want to quit?";
+ var message = translate("Do you want to quit?");
}
if (playerState.state == "defeated")
{
global.music.setState(global.music.states.DEFEAT);
- messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode);
+ messageBox(400, 200, message, translate("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);
+ messageBox(400, 200, message, translate("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();
updateBuildingPlacementPreview();
updateTimeElapsedCounter();
updateTimeNotifications();
if (!g_IsObserver)
{
updateResearchDisplay();
// 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)
+ var healthLabel = "[font=\"serif-bold-13\"]" + translate("Health:") + "[/font]";
+ tooltip += "\n" + sprintf(translate("%(label)s %(current)s / %(max)s"), { label: healthLabel, current: heroState.hitpoints, max: heroState.maxHitpoints });
+ var attackLabel = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(heroState.attack.type) + "[/font]";
if (heroState.attack && heroState.attack.type == "Ranged")
- tooltip += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(heroState.attack.maxRange/4);
+ // Show max attack range if ranged attack, also convert to tiles (4m per tile)
+ tooltip += "\n" + sprintf(
+ translate("%(attackLabel)s %(details)s, %(rangeLabel)s %(range)s"),
+ {
+ attackLabel: attackLabel,
+ details: damageTypeDetails(heroState.attack),
+ rangeLabel: "[font=\"serif-bold-13\"]" + translate("Range:") + "[/font]",
+ range: Math.round(heroState.attack.maxRange/4)
+ }
+ );
+ else
+ tooltip += "\n" + sprintf(translate("%(label)s %(details)s"), { label: attackLabel, details: damageTypeDetails(heroState.armour) });
- tooltip += "\n[font=\"serif-bold-13\"]Armor:[/font] " + damageTypeDetails(heroState.armour);
+ var armorLabel = "[font=\"serif-bold-13\"]" + translate("Armor:") + "[/font]";
+ tooltip += "\n" + sprintf(translate("%(label)s %(details)s"), { label: armorLabel, details: damageTypeDetails(heroState.attack) });
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;
+ if (g_CurrentSpeed != 1.0)
+ timeElapsedCounter.caption = sprintf(translate("%(time)s (%(speed)sx)"), { time: timeToString(simState.timeElapsed), speed: Engine.FormatDecimalNumberIntoString(g_CurrentSpeed) });
+ else
+ timeElapsedCounter.caption = timeToString(simState.timeElapsed);
}
// 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);
+ Engine.Console_Write(sprintf(translate("Unrecognized ambient type: %(ambientType)s"), { ambientType: type }));
break;
}
}
// Temporarily adding this here
function stopAmbient()
{
if (currentAmbient)
{
currentAmbient.free();
currentAmbient = null;
}
}
+
+function getBuildString()
+{
+ return sprintf(translate("Build: %(buildDate)s (%(revision)s)"), { buildDate: Engine.GetBuildTimestamp(0), revision: Engine.GetBuildTimestamp(2) });
+}
+
+function showTimeWarpMessageBox()
+{
+ messageBox(500, 250, translate("Note: time warp mode is a developer option, and not intended for use over long periods of time. Using it incorrectly may cause the game to run out of memory or crash."), translate("Time warp mode"), 2);
+}
+
// Send a report on the game status to the lobby
function reportGame(extendedSimState)
{
if (!Engine.HasXmppClient() || !Engine.IsRankedGame())
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/splashscreen/splashscreen.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/splashscreen/splashscreen.js (revision 14954)
@@ -1,6 +1,10 @@
function init(data)
{
- Engine.GetGUIObjectByName("mainText").caption = Engine.ReadFile("gui/splashscreen/" + data.page + ".txt");
-
+ Engine.GetGUIObjectByName("mainText").caption = Engine.TranslateLines(Engine.ReadFile("gui/splashscreen/" + data.page + ".txt"));
}
+function openURL(url)
+{
+ Engine.OpenURL(url);
+ messageBox(600, 200, sprintf(translate("Opening %(url)s\n in default web browser. Please wait..."), { url: url }), translate("Opening page"), 2);
+}
Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/summary/summary.xml (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/summary/summary.xml (revision 14954)
@@ -1,992 +1,1004 @@
onTick();
- Summary
+ SummaryselectPanel(0);
- Score
+
+ Score
+ selectPanel(1);
- Buildings
+
+ Buildings
+ selectPanel(2);
- Units
+
+ Units
+ selectPanel(3);
- Resources
+
+ Resources
+ selectPanel(4);
- Market
+
+ Market
+ selectPanel(5);
- Miscellaneous
+
+ Miscellaneous
+
- Player name
+ Player name
- Economy
score
+ Economy score
- Military
score
+ Military score
- Exploration
score
+ Exploration score
- Total
score
+ Total scoreyy
- Player name
+ Player name
- Buildings Statistics (Constructed / Lost / Destroyed)
+ Buildings Statistics (Constructed / Lost / Destroyed)
- Total
+ Total
- Houses
+ Houses
- Economic
+ Economic
- Outposts
+ Outposts
- Military
+ Military
- Fortresses
+ Fortresses
- Civ Centers
+ Civ Centers
- Wonders
+ Wonders
- Player name
+ Player name
- Units Statistics (Trained / Lost / Killed)
+ Units Statistics (Trained / Lost / Killed)
- Total
+ Total
- Infantry
+ Infantry
- Worker
+ Worker
- Cavalry
+ Cavalry
- Champion
+ Champion
- Heroes
+ Heroes
- Navy
+ Navy
- Player name
+ Player name
- Resource Statistics (Gathered / Used)
+ Resource Statistics (Gathered / Used)
- Food
+ Food
- Wood
+ Wood
- Stone
+ Stone
- Metal
+ Metal
- Total
+ Total
- Treasures
collected
+ Treasures collected
- Tributes
(Sent / Received)
+ Tributes (Sent / Received)
- Player name
+ Player name
- Food
exchanged
+ Food exchanged
- Wood
exchanged
+ Wood exchanged
- Stone
exchanged
+ Stone exchanged
- Metal
exchanged
+ Metal exchanged
- Barter
efficiency
+ Barter efficiency
- Trade
income
+ Trade income
- Player name
+ Player name
- Vegetarian
ratio
+ Vegetarian\nratio
- Feminisation
+ Feminisation
- Kill / Death
ratio
+ Kill / Death\nratio
- Map
exploration
+ Map\nexploration
- Continue
+ Continue
Index: ps/trunk/binaries/data/mods/public/simulation/components/Wonder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Wonder.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Wonder.js (revision 14954)
@@ -1,65 +1,69 @@
function Wonder() {}
Wonder.prototype.Schema =
"" +
"" +
"";
Wonder.prototype.Init = function()
{
};
Wonder.prototype.OnOwnershipChanged = function(msg)
{
this.ResetTimer(msg.to);
};
Wonder.prototype.OnGameTypeChanged = function()
{
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (!cmpPlayer)
return;
this.ResetTimer(cmpPlayer.GetPlayerID());
};
Wonder.prototype.ResetTimer = function(ownerID)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
// remove existing messages if any
if (this.timer)
{
cmpTimer.CancelTimer(this.timer);
cmpGuiInterface.DeleteTimeNotification(this.ownMessage);
cmpGuiInterface.DeleteTimeNotification(this.otherMessage);
}
if (ownerID <= 0)
return;
var cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
if (!cmpEndGameManager.CheckGameType("wonder"))
return;
// create new messages, and start timer to register defeat.
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var numPlayers = cmpPlayerManager.GetNumPlayers();
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var players = [];
for (var i = 1; i < numPlayers; i++)
if (i != ownerID)
players.push(i);
this.otherMessage = cmpGuiInterface.AddTimeNotification({
- "message": cmpPlayer.GetName() + " will have won in %T",
+ "message": markForTranslation("%(player)s will have won in %(time)s"),
"players": players,
- "time": +this.template.TimeTillVictory*1000
+ "duration": +this.template.TimeTillVictory*1000,
+ "parameters": {"player": cmpPlayer.GetName()},
+ "translateMessage": true,
+ "translateParameters": [],
});
this.ownMessage = cmpGuiInterface.AddTimeNotification({
- "message": "You will have won in %T",
+ "message": markForTranslation("You will have won in %(time)s"),
"players": [ownerID],
- "time": +this.template.TimeTillVictory*1000
+ "duration": +this.template.TimeTillVictory*1000,
+ "translateMessage": true,
});
this.timer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_EndGameManager, "MarkPlayerAsWon", +this.template.TimeTillVictory*1000, ownerID);
};
Engine.RegisterComponentType(IID_Wonder, "Wonder", Wonder);
Index: ps/trunk/binaries/data/mods/public/gui/session/selection_details.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/selection_details.js (revision 14954)
@@ -1,354 +1,439 @@
function layoutSelectionSingle()
{
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
function layoutSelectionMultiple()
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
+function getLocalizedResourceName(resourceCode)
+{
+ switch(resourceCode)
+ {
+ case "food": return translate("Food");
+ case "meat": return translate("Meat");
+ case "metal": return translate("Metal");
+ case "ore": return translate("Ore");
+ case "rock": return translate("Rock");
+ case "ruins": return translate("Ruins");
+ case "stone": return translate("Stone");
+ case "treasure": return translate("Treasure");
+ case "tree": return translate("Tree");
+ case "wood": return translate("Wood");
+ case "fruit": return translate("Fruit");
+ case "grain": return translate("Grain");
+ case "fish": return translate("Fish");
+ default:
+ warn(sprintf("Internationalization: Unexpected resource type found with code ‘%(resource)s’. This resource type must be internationalized.", { resource: resourceCode }));
+ return resourceCode; // It should never get here.
+ }
+}
+
+function getResourceTypeDisplayName(resourceType)
+{
+ var resourceCode = resourceType["generic"];
+ var displayName = "";
+ if (resourceCode == "treasure")
+ displayName = getLocalizedResourceName(resourceType["specific"]);
+ else
+ displayName = getLocalizedResourceName(resourceCode);
+ return displayName;
+}
+
// Fills out information that most entities have
function displaySingle(entState, template)
{
// Get general unit and player data
var specificName = template.name.specific;
var genericName = template.name.generic != template.name.specific ? template.name.generic : "";
// If packed, add that to the generic name (reduces template clutter)
if (genericName && template.pack && template.pack.state == "packed")
- genericName += " -- Packed";
+ genericName = sprintf(translate("%(genericName)s — Packed"), { genericName: genericName });
var playerState = g_Players[entState.player];
var civName = g_CivData[playerState.civ].Name;
var civEmblem = g_CivData[playerState.civ].Emblem;
var playerName = playerState.name;
var playerColor = playerState.color.r + " " + playerState.color.g + " " + playerState.color.b + " 128";
// Indicate disconnected players by prefixing their name
if (g_Players[entState.player].offline)
{
- playerName = "[OFFLINE] " + playerName;
+ playerName = sprintf(translate("[OFFLINE] %(player)s"), { player: playerName });
}
// Rank
if (entState.identity && entState.identity.rank && entState.identity.classes)
{
- Engine.GetGUIObjectByName("rankIcon").tooltip = entState.identity.rank + " Rank";
+ Engine.GetGUIObjectByName("rankIcon").tooltip = sprintf(translate("%(rank)s Rank"), { rank: entState.identity.rank });
Engine.GetGUIObjectByName("rankIcon").sprite = getRankIconSprite(entState);
Engine.GetGUIObjectByName("rankIcon").hidden = false;
}
else
{
Engine.GetGUIObjectByName("rankIcon").hidden = true;
Engine.GetGUIObjectByName("rankIcon").tooltip = "";
}
-
+
// Hitpoints
if (entState.hitpoints)
{
var unitHealthBar = Engine.GetGUIObjectByName("healthBar");
var healthSize = unitHealthBar.size;
healthSize.rright = 100*Math.max(0, Math.min(1, entState.hitpoints / entState.maxHitpoints));
unitHealthBar.size = healthSize;
- var hitpoints = Math.ceil(entState.hitpoints) + " / " + entState.maxHitpoints;
- Engine.GetGUIObjectByName("healthStats").caption = hitpoints;
+ Engine.GetGUIObjectByName("healthStats").caption = sprintf(translate("%(hitpoints)s / %(maxHitpoints)s"), {
+ hitpoints: Math.ceil(entState.hitpoints),
+ maxHitpoints: entState.maxHitpoints
+ });
Engine.GetGUIObjectByName("healthSection").hidden = false;
}
else
{
Engine.GetGUIObjectByName("healthSection").hidden = true;
}
// TODO: Stamina
var player = Engine.GetPlayerID();
if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))
- {
Engine.GetGUIObjectByName("staminaSection").hidden = false;
- }
else
- {
Engine.GetGUIObjectByName("staminaSection").hidden = true;
- }
// Experience
if (entState.promotion)
{
var experienceBar = Engine.GetGUIObjectByName("experienceBar");
var experienceSize = experienceBar.size;
experienceSize.rtop = 100 - (100 * Math.max(0, Math.min(1, 1.0 * +entState.promotion.curr / +entState.promotion.req)));
experienceBar.size = experienceSize;
- var experience = "[font=\"serif-bold-13\"]Experience: [/font]" + Math.floor(entState.promotion.curr);
if (entState.promotion.curr < entState.promotion.req)
- experience += " / " + entState.promotion.req;
- Engine.GetGUIObjectByName("experience").tooltip = experience;
+ Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s / %(required)s"), {
+ experience: "[font=\"serif-bold-13\"]" + translate("Experience:") + "[/font]",
+ current: Math.floor(entState.promotion.curr),
+ required: entState.promotion.req
+ });
+ else
+ Engine.GetGUIObjectByName("experience").tooltip = sprintf(translate("%(experience)s %(current)s"), {
+ experience: "[font=\"serif-bold-13\"]" + translate("Experience:") + "[/font]",
+ current: Math.floor(entState.promotion.curr)
+ });
Engine.GetGUIObjectByName("experience").hidden = false;
}
else
{
Engine.GetGUIObjectByName("experience").hidden = true;
}
// Resource stats
if (entState.resourceSupply)
{
- var resources = entState.resourceSupply.isInfinite ? "\u221E" : // Infinity symbol
- Math.ceil(+entState.resourceSupply.amount) + " / " + entState.resourceSupply.max;
- var resourceType = entState.resourceSupply.type["generic"];
- if (resourceType == "treasure")
- resourceType = entState.resourceSupply.type["specific"];
+ var resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol
+ sprintf(translate("%(amount)s / %(max)s"), { amount: Math.ceil(+entState.resourceSupply.amount), max: entState.resourceSupply.max });
+ var resourceType = getResourceTypeDisplayName(entState.resourceSupply.type);
var unitResourceBar = Engine.GetGUIObjectByName("resourceBar");
var resourceSize = unitResourceBar.size;
resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
unitResourceBar.size = resourceSize;
- Engine.GetGUIObjectByName("resourceLabel").caption = toTitleCase(resourceType) + ":";
+ Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), { resource: resourceType });
Engine.GetGUIObjectByName("resourceStats").caption = resources;
if (entState.hitpoints)
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("staminaSection").size;
else
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("healthSection").size;
Engine.GetGUIObjectByName("resourceSection").hidden = false;
}
else
{
Engine.GetGUIObjectByName("resourceSection").hidden = true;
}
// Resource carrying
if (entState.resourceCarrying && entState.resourceCarrying.length)
{
// We should only be carrying one resource type at once, so just display the first
var carried = entState.resourceCarrying[0];
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+carried.type+".png";
- Engine.GetGUIObjectByName("resourceCarryingText").caption = carried.amount + " / " + carried.max;
+ Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { amount: carried.amount, max: carried.max });
Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "";
}
// Use the same indicators for traders
else if (entState.trader && entState.trader.goods.amount)
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/resources/"+entState.trader.goods.type+".png";
var totalGain = entState.trader.goods.amount.traderGain;
if (entState.trader.goods.amount.market1Gain)
totalGain += entState.trader.goods.amount.market1Gain;
if (entState.trader.goods.amount.market2Gain)
totalGain += entState.trader.goods.amount.market2Gain;
Engine.GetGUIObjectByName("resourceCarryingText").caption = totalGain;
- Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "Gain: " + getTradingTooltip(entState.trader.goods.amount);
+ Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = sprintf(translate("Gain: %(amount)s"), { amount: getTradingTooltip(entState.trader.goods.amount) });
}
// And for number of workers
else if (entState.foundation)
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.foundation.numBuilders + " ";
- Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "Number of builders";
+ Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Number of builders");
}
else if (entState.resourceSupply && (!entState.resourceSupply.killBeforeGather || !entState.hitpoints))
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = false;
Engine.GetGUIObjectByName("resourceCarryingIcon").sprite = "stretched:session/icons/repair.png";
- Engine.GetGUIObjectByName("resourceCarryingText").caption = entState.resourceSupply.gatherers.length + " / " + entState.resourceSupply.maxGatherers + " ";
- Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = "Current/max gatherers";
+ Engine.GetGUIObjectByName("resourceCarryingText").caption = sprintf(translate("%(amount)s / %(max)s"), { amount: entState.resourceSupply.gatherers.length, max: entState.resourceSupply.maxGatherers }) + " ";
+ Engine.GetGUIObjectByName("resourceCarryingIcon").tooltip = translate("Current/max gatherers");
}
else
{
Engine.GetGUIObjectByName("resourceCarryingIcon").hidden = true;
Engine.GetGUIObjectByName("resourceCarryingText").hidden = true;
}
// Set Player details
Engine.GetGUIObjectByName("specific").caption = specificName;
Engine.GetGUIObjectByName("player").caption = playerName;
Engine.GetGUIObjectByName("playerColorBackground").sprite = "colour: " + playerColor;
if (genericName)
{
- Engine.GetGUIObjectByName("generic").caption = "(" + genericName + ")";
+ Engine.GetGUIObjectByName("generic").caption = sprintf(translate("(%(genericName)s)"), { genericName: genericName });
}
else
{
Engine.GetGUIObjectByName("generic").caption = "";
}
if ("Gaia" != civName)
{
Engine.GetGUIObjectByName("playerCivIcon").sprite = "stretched:grayscale:" + civEmblem;
Engine.GetGUIObjectByName("player").tooltip = civName;
}
else
{
Engine.GetGUIObjectByName("playerCivIcon").sprite = "";
Engine.GetGUIObjectByName("player").tooltip = "";
}
// Icon image
if (template.icon)
{
Engine.GetGUIObjectByName("icon").sprite = "stretched:session/portraits/" + template.icon;
}
else
{
// TODO: we should require all entities to have icons, so this case never occurs
Engine.GetGUIObjectByName("icon").sprite = "bkFillBlack";
}
+ var armorLabel = "[font=\"serif-bold-13\"]" + translate("Armor:") + "[/font]"
+ var armorString = sprintf(translate("%(label)s %(details)s"), { label: armorLabel, details: armorTypeDetails(entState.armour) });
+
// Attack and Armor
- var type = "";
- var attack = "[font=\"serif-bold-13\"]"+type+"Attack:[/font] " + damageTypeDetails(entState.attack);
if (entState.attack)
{
- type = entState.attack.type + " ";
+ // Rate
+ if (entState.buildingAI)
+ var rateLabel = "[font=\"serif-bold-13\"]" + translate("Interval:") + "[/font]";
+ else
+ var rateLabel = "[font=\"serif-bold-13\"]" + translate("Rate:") + "[/font]";
+
+ var rate = sprintf(translate("%(label)s %(details)s"), {
+ label: rateLabel,
+ details: attackRateDetails(entState)
+ });
- // Show max attack range if ranged attack, also convert to tiles (4m per tile)
+ var attack;
+ var label = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(entState.attack.type) + "[/font]"
if (entState.attack.type == "Ranged")
{
var realRange = entState.attack.elevationAdaptedRange;
var range = entState.attack.maxRange;
- attack += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(range) +
- "[font=\"sans-10\"][color=\"orange\"] meters[/color][/font]";
-
- if (Math.round(realRange - range) > 0)
- attack += " (+" + Math.round(realRange - range) + ")";
- else if (Math.round(realRange - range) < 0)
- attack += " (" + Math.round(realRange - range) + ")";
-
+ var rangeLabel = "[font=\"serif-bold-13\"]" + translate("Range:") + "[/font]"
+ var relativeRange = Math.round((realRange - range));
+ var meters = "[font=\"sans-10\"][color=\"orange\"]" + translate("meters") + "[/color][/font]";
+
+ if (relativeRange > 0)
+ attack = sprintf(translate("%(label)s %(details)s, %(rangeLabel)s %(range)s %(meters)s (%(relative)s), %(rate)s"), {
+ label: label,
+ details: damageTypeDetails(entState.attack),
+ rangeLabel: rangeLabel,
+ range: Math.round(range),
+ meters: meters,
+ relative: "+" + relativeRange,
+ rate: rate
+ });
+ else if (relativeRange < 0)
+ attack = sprintf(translate("%(label)s %(details)s, %(rangeLabel)s %(range)s %(meters)s (%(relative)s), %(rate)s"), {
+ label: label,
+ details: damageTypeDetails(entState.attack),
+ rangeLabel: rangeLabel,
+ range: Math.round(range),
+ meters: meters,
+ relative: relativeRange,
+ rate: rate
+ });
+ else // don't show when it's 0
+ attack = sprintf(translate("%(label)s %(details)s, %(rangeLabel)s %(range)s %(meters)s, %(rate)s"), {
+ label: label,
+ details: damageTypeDetails(entState.attack),
+ rangeLabel: rangeLabel,
+ range: Math.round(range),
+ meters: meters,
+ rate: rate
+ });
+ }
+ else
+ {
+ attack = sprintf(translate("%(label)s %(details)s, %(rate)s"), {
+ label: label,
+ details: damageTypeDetails(entState.attack),
+ rate: rate
+ });
}
- attack += ", [font=\"serif-bold-13\"]" + (entState.buildingAI ? "Rate" : "Interval") + ":[/font] " + attackRateDetails(entState);
+
+ Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n" + armorString;
+ }
+ else
+ {
+ Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = armorString;
}
-
- Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
// Icon Tooltip
var iconTooltip = "";
if (genericName)
iconTooltip = "[font=\"serif-bold-16\"]" + genericName + "[/font]";
if (template.tooltip)
iconTooltip += "\n[font=\"serif-13\"]" + template.tooltip + "[/font]";
Engine.GetGUIObjectByName("iconBorder").tooltip = iconTooltip;
// Unhide Details Area
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = false;
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
}
// Fills out information for multiple entities
function displayMultiple(selection, template)
{
var averageHealth = 0;
var maxHealth = 0;
for (var i = 0; i < selection.length; i++)
{
var entState = GetEntityState(selection[i])
if (entState)
{
if (entState.hitpoints)
{
averageHealth += entState.hitpoints;
maxHealth += entState.maxHitpoints;
}
}
}
if (averageHealth > 0)
{
var unitHealthBar = Engine.GetGUIObjectByName("healthBarMultiple");
var healthSize = unitHealthBar.size;
healthSize.rtop = 100-100*Math.max(0, Math.min(1, averageHealth / maxHealth));
unitHealthBar.size = healthSize;
- var hitpoints = "[font=\"serif-bold-13\"]Hitpoints [/font]" + averageHealth + " / " + maxHealth;
+ var hitpointsLabel = "[font=\"serif-bold-13\"]" + translate("Hitpoints:") + "[/font]"
+ var hitpoints = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: hitpointsLabel, current: averageHealth, max: maxHealth });
var healthMultiple = Engine.GetGUIObjectByName("healthMultiple");
healthMultiple.tooltip = hitpoints;
healthMultiple.hidden = false;
}
else
{
Engine.GetGUIObjectByName("healthMultiple").hidden = true;
}
// TODO: Stamina
// Engine.GetGUIObjectByName("staminaBarMultiple");
Engine.GetGUIObjectByName("numberOfUnits").caption = selection.length;
// Unhide Details Area
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
}
// Updates middle entity Selection Details Panel
function updateSelectionDetails()
{
var supplementalDetailsPanel = Engine.GetGUIObjectByName("supplementalSelectionDetails");
var detailsPanel = Engine.GetGUIObjectByName("selectionDetails");
var commandsPanel = Engine.GetGUIObjectByName("unitCommands");
g_Selection.update();
var selection = g_Selection.toList();
if (selection.length == 0)
{
Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = true;
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
hideUnitCommands();
supplementalDetailsPanel.hidden = true;
detailsPanel.hidden = true;
commandsPanel.hidden = true;
return;
}
/* If the unit has no data (e.g. it was killed), don't try displaying any
data for it. (TODO: it should probably be removed from the selection too;
also need to handle multi-unit selections) */
var entState = GetExtendedEntityState(selection[0]);
if (!entState)
return;
var template = GetTemplateData(entState.template);
// Fill out general info and display it
if (selection.length == 1)
displaySingle(entState, template);
else
displayMultiple(selection, template);
// Show basic details.
detailsPanel.hidden = false;
if (g_IsObserver)
{
// Observers don't need these displayed.
supplementalDetailsPanel.hidden = true;
commandsPanel.hidden = true;
}
else
{
// Fill out commands panel for specific unit selected (or first unit of primary group)
updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, selection);
// Show panels
supplementalDetailsPanel.hidden = false;
commandsPanel.hidden = false;
}
}
Index: ps/trunk/binaries/data/mods/public/gui/session/utility_functions.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/utility_functions.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/session/utility_functions.js (revision 14954)
@@ -1,671 +1,720 @@
const GEOLOGY = "geology";
const FLORA = "flora";
const FAUNA = "fauna";
const SPECIAL = "special";
const COST_DISPLAY_NAMES = {
"food": "[icon=\"iconFood\"]",
"wood": "[icon=\"iconWood\"]",
"stone": "[icon=\"iconStone\"]",
"metal": "[icon=\"iconMetal\"]",
"population": "[icon=\"iconPopulation\"]",
"time": "[icon=\"iconTime\"]"
};
//-------------------------------- -------------------------------- --------------------------------
// Utility functions
//-------------------------------- -------------------------------- --------------------------------
function toTitleCase(word)
{
if (word.length > 0)
{
var titleCased = word.substring(0, 1).toUpperCase();
if (word.length > 1)
{
titleCased += word.substring(1).toLowerCase();
}
return titleCased;
}
return word;
}
-function pluralize(word, count, pluralWord)
-{
- if (count == 1 && pluralWord != null)
- return pluralWord;
-
- var plural = "s";
- if (word[word.length - 1] == "s")
- plural = "es";
- return word + (count == 1 ? "" : plural);
-}
-
// Get the basic player data
function getPlayerData(playerAssignments)
{
var players = [];
var simState = GetSimState();
if (!simState)
return players;
for (var i = 0; i < simState.players.length; i++)
{
var playerState = simState.players[i];
var name = playerState.name;
var civ = playerState.civ;
var color = {
"r": playerState.colour.r*255,
"g": playerState.colour.g*255,
"b": playerState.colour.b*255,
"a": playerState.colour.a*255
};
var player = {
"name": name,
"civ": civ,
"color": color,
"team": playerState.team,
"teamsLocked": playerState.teamsLocked,
"cheatsEnabled": playerState.cheatsEnabled,
"state": playerState.state,
"isAlly": playerState.isAlly,
"isMutualAlly": playerState.isMutualAlly,
"isNeutral": playerState.isNeutral,
"isEnemy": playerState.isEnemy,
"guid": undefined, // network guid for players controlled by hosts
"disconnected": false // flag for host-controlled players who have left the game
};
players.push(player);
}
// Overwrite default player names with multiplayer names
if (playerAssignments)
{
for (var playerGuid in playerAssignments)
{
var playerAssignment = playerAssignments[playerGuid];
if (players[playerAssignment.player])
{
players[playerAssignment.player].guid = playerGuid;
players[playerAssignment.player].name = playerAssignment.name;
}
}
}
return players;
}
function findGuidForPlayerID(playerAssignments, player)
{
for (var playerGuid in playerAssignments)
{
var playerAssignment = playerAssignments[playerGuid];
if (playerAssignment.player == player)
return playerGuid;
}
return undefined;
}
// Update player data when a host has connected
function updatePlayerDataAdd(players, hostGuid, playerAssignment)
{
if (players[playerAssignment.player])
{
players[playerAssignment.player].guid = hostGuid;
players[playerAssignment.player].name = playerAssignment.name;
players[playerAssignment.player].offline = false;
}
}
// Update player data when a host has disconnected
function updatePlayerDataRemove(players, hostGuid)
{
for each (var player in players)
if (player.guid == hostGuid)
player.offline = true;
}
function hasClass(entState, className)
{
if (entState.identity)
{
var classes = entState.identity.classes;
if (classes && classes.length)
return (classes.indexOf(className) != -1);
}
return false;
}
// For the unit details panel
function damageValues(dmg)
{
if (dmg)
{
var dmgArray = [];
dmg.hack? dmgArray.push(dmg.hack) : dmgArray.push(0);
dmg.pierce? dmgArray.push(dmg.pierce) : dmgArray.push(0);
dmg.crush? dmgArray.push(dmg.crush) : dmgArray.push(0);
return dmgArray;
}
else
{
return [0, 0, 0];
}
}
// For the unit details panel
function damageTypeDetails(dmg)
{
- if (dmg)
- {
- var dmgArray = [];
- if (dmg.hack) dmgArray.push(dmg.hack + "[font=\"sans-10\"][color=\"orange\"] Hack[/color][/font]");
- if (dmg.pierce) dmgArray.push(dmg.pierce + "[font=\"sans-10\"][color=\"orange\"] Pierce[/color][/font]");
- if (dmg.crush) dmgArray.push(dmg.crush + "[font=\"sans-10\"][color=\"orange\"] Crush[/color][/font]");
+ if (!dmg)
+ return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
- return dmgArray.join(", ");
- }
- else
- {
- return "[font=\"serif-12\"](None)[/font]";
- }
+ var dmgArray = [];
+ if (dmg.hack)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
+ damage: dmg.hack,
+ damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]"
+ }));
+ if (dmg.pierce)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
+ damage: dmg.pierce,
+ damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]"
+ }));
+ if (dmg.crush)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
+ damage: dmg.crush,
+ damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]"
+ }));
+
+ return dmgArray.join(translate(", "));
}
function attackRateDetails(entState) {
- if (entState.buildingAI)
- var arrows = entState.buildingAI.arrowCount;
-
var time = entState.attack.repeatTime / 1000;
if (entState.buildingAI) {
- return Math.max(arrows, entState.buildingAI.defaultArrowCount) + "[font=\"sans-10\"][color=\"orange\"] " +
- pluralize("arrow", arrows) + "[/color][/font]" + " / " + (time == 1 ? "" : time) +
- " [font=\"sans-10\"][color=\"orange\"]" + pluralize("second", time) + "[/color][/font]";
+ var arrows = Math.max(entState.buildingAI.arrowCount, entState.buildingAI.defaultArrowCount);
+ // TODO TODO TODO color, font
+ return sprintf(translate("%(arrowString)s / %(timeString)s"), {
+ arrowString: sprintf(translatePlural("%(arrows)s arrow", "%(arrows)s arrows", arrows), { arrows: arrows}),
+ timeString: sprintf(translatePlural("%(time)s second", "%(time)s seconds", time), { time: time })
+ });
}
- return time + "[font=\"sans-10\"][color=\"orange\"] " + pluralize("second", time) + "[/color][/font]";
+ // TODO TODO TODO color, font
+ return sprintf(translatePlural("%(time)s second", "%(time)s seconds", time), { time: time });
}
// Converts an armor level into the actual reduction percentage
-function armorLevelToPercentage(level)
+function armorLevelToPercentageString(level)
{
- return 100 - Math.round(Math.pow(0.9, level) * 100);
+ return (100 - Math.round(Math.pow(0.9, level) * 100)) + "%";
+ // return sprintf(translate("%(armorPercentage)s%"), { armorPercentage: (100 - Math.round(Math.pow(0.9, level) * 100)) }); // Not supported by our sprintf implementation.
}
// Also for the unit details panel
function armorTypeDetails(dmg)
{
- if (dmg)
- {
- var dmgArray = [];
- if (dmg.hack)
- {
- dmgArray.push(dmg.hack + "[font=\"sans-10\"][color=\"orange\"] Hack[/color][/font] " +
- " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.hack) + "%)[/font]");
- }
- if (dmg.pierce)
- {
- dmgArray.push(dmg.pierce + "[font=\"sans-10\"][color=\"orange\"] Pierce[/color][/font] " +
- " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.pierce) + "%)[/font]");
- }
- if (dmg.crush)
- {
- dmgArray.push(dmg.crush + "[font=\"sans-10\"][color=\"orange\"] Crush[/color][/font] " +
- " [font=\"sans-10\"](" + armorLevelToPercentage(dmg.crush) + "%)[/font]");
- }
+ if (!dmg)
+ return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
- return dmgArray.join(", ");
- }
- else
- {
- return "[font=\"serif-12\"](None)[/font]";
- }
+ var dmgArray = [];
+ if (dmg.hack)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
+ damage: dmg.hack,
+ damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Hack") + "[/color][/font]",
+ armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.hack) }) + "[/font]"
+ }));
+ if (dmg.pierce)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
+ damage: dmg.pierce,
+ damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Pierce") + "[/color][/font]",
+ armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.pierce) }) + "[/font]"
+ }));
+ if (dmg.crush)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
+ damage: dmg.crush,
+ damageType: "[font=\"sans-10\"][color=\"orange\"]" + translate("Crush") + "[/color][/font]",
+ armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
+ }));
+
+ return dmgArray.join(translate(", "));
}
// For the training tooltip
function damageTypesToText(dmg)
{
if (!dmg)
- return "[font=\"serif-12\"](None)[/font]";
-
- var hackLabel = "[font=\"serif-12\"] Hack[/font]";
- var pierceLabel = "[font=\"serif-12\"] Pierce[/font]";
- var crushLabel = "[font=\"serif-12\"] Crush[/font]";
- var hackDamage = dmg.hack;
- var pierceDamage = dmg.pierce;
- var crushDamage = dmg.crush;
+ return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
- if (hackDamage) dmgArray.push(Math.round(hackDamage) + hackLabel);
- if (pierceDamage) dmgArray.push(Math.round(pierceDamage) + pierceLabel);
- if (crushDamage) dmgArray.push(Math.round(crushDamage) + crushLabel);
+ if (dmg.hack)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
+ damage: dmg.hack,
+ damageType: "[font=\"serif-12\"]" + translate("Hack") + "[/font]"
+ }));
+ if (dmg.pierce)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
+ damage: dmg.pierce,
+ damageType: "[font=\"serif-12\"]" + translate("Pierce") + "[/font]"
+ }));
+ if (dmg.crush)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s"), {
+ damage: dmg.crush,
+ damageType: "[font=\"serif-12\"]" + translate("Crush") + "[/font]"
+ }));
- return dmgArray.join("[font=\"serif-12\"], [/font]");
+ return dmgArray.join("[font=\"serif-12\"]" + translate(", ") + "[/font]");
}
// Also for the training tooltip
function armorTypesToText(dmg)
{
if (!dmg)
- return "[font=\"serif-12\"](None)[/font]";
-
- var hackDamage = dmg.hack;
- var pierceDamage = dmg.pierce;
- var crushDamage = dmg.crush;
- var hackLabel = "[font=\"serif-12\"] Hack (" + armorLevelToPercentage(hackDamage) + "%)[/font]";
- var pierceLabel = "[font=\"serif-12\"] Pierce (" + armorLevelToPercentage(pierceDamage) + "%)[/font]";
- var crushLabel = "[font=\"serif-12\"] Crush (" + armorLevelToPercentage(crushDamage) + "%)[/font]";
+ return "[font=\"serif-12\"]" + translate("(None)") + "[/font]";
var dmgArray = [];
- if (hackDamage) dmgArray.push(hackDamage + hackLabel);
- if (pierceDamage) dmgArray.push(pierceDamage + pierceLabel);
- if (crushDamage) dmgArray.push(crushDamage + crushLabel);
+ if (dmg.hack)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
+ damage: dmg.hack,
+ damageType: "[font=\"serif-12\"]" + translate("Hack") + "[/font]",
+ armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.hack) }) + "[/font]"
+ }));
+ if (dmg.pierce)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
+ damage: dmg.pierce,
+ damageType: "[font=\"serif-12\"]" + translate("Pierce") + "[/font]",
+ armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.pierce) }) + "[/font]"
+ }));
+ if (dmg.crush)
+ dmgArray.push(sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
+ damage: dmg.crush,
+ damageType: "[font=\"serif-12\"]" + translate("Crush") + "[/font]",
+ armorPercentage: "[font=\"sans-10\"]" + sprintf(translate("(%(armorPercentage)s)"), { armorPercentage: armorLevelToPercentageString(dmg.crush) }) + "[/font]"
+ }));
- return dmgArray.join("[font=\"serif-12\"], [/font]");
+ return dmgArray.join("[font=\"serif-12\"]" + translate(", ") + "[/font]");
}
function getEntityCommandsList(entState)
{
var commands = [];
if (entState.garrisonHolder)
{
commands.push({
"name": "unload-all",
- "tooltip": "Unload All",
+ "tooltip": translate("Unload All"),
"icon": "garrison-out.png"
});
}
commands.push({
"name": "delete",
- "tooltip": "Delete",
+ "tooltip": translate("Delete"),
"icon": "kill_small.png"
});
if (hasClass(entState, "Unit"))
{
commands.push({
"name": "stop",
- "tooltip": "Stop",
+ "tooltip": translate("Stop"),
"icon": "stop.png"
});
commands.push({
"name": "garrison",
- "tooltip": "Garrison",
+ "tooltip": translate("Garrison"),
"icon": "garrison.png"
});
}
if (entState.buildEntities)
{
commands.push({
"name": "repair",
- "tooltip": "Repair",
+ "tooltip": translate("Repair"),
"icon": "repair.png"
});
}
if (entState.rallyPoint)
{
commands.push({
"name": "focus-rally",
- "tooltip": "Focus on Rally Point",
+ "tooltip": translate("Focus on Rally Point"),
"icon": "focus-rally.png"
});
}
-
+
if (entState.unitAI && entState.unitAI.hasWorkOrders)
{
commands.push({
"name": "back-to-work",
- "tooltip": "Back to Work",
+ "tooltip": translate("Back to Work"),
"icon": "production.png"
});
}
if (entState.unitAI && entState.unitAI.canGuard && !entState.unitAI.isGuarding)
{
commands.push({
"name": "add-guard",
- "tooltip": "Guard",
+ "tooltip": translate("Guard"),
"icon": "add-guard.png"
});
}
if (entState.unitAI && entState.unitAI.isGuarding)
{
commands.push({
"name": "remove-guard",
- "tooltip": "Remove guard",
+ "tooltip": translate("Remove guard"),
"icon": "remove-guard.png"
});
}
if (hasClass(entState, "Market"))
{
commands.push({
"name": "select-trading-goods",
- "tooltip": "Select trading goods",
+ "tooltip": translate("Select trading goods"),
"icon": "economics.png"
});
}
if(entState.alertRaiser)
{
if(entState.alertRaiser.canIncreaseLevel)
{
if(entState.alertRaiser.hasRaisedAlert)
- var tooltip = "Increase the alert level to protect more units";
+ var tooltip = translate("Increase the alert level to protect more units");
else
- var tooltip = "Raise an alert!";
+ var tooltip = translate("Raise an alert!");
commands.push({
"name": "increase-alert-level",
"tooltip": tooltip,
"icon": "bell_level1.png"
});
}
if(entState.alertRaiser.hasRaisedAlert)
commands.push({
"name": "alert-end",
- "tooltip": "End of alert.",
+ "tooltip": translate("End of alert."),
"icon": "bell_level0.png"
});
}
return commands;
}
/**
* Translates a cost component identifier as they are used internally (e.g. "population", "food", etc.) to proper
* display names.
*/
function getCostComponentDisplayName(costComponentName)
{
return COST_DISPLAY_NAMES[costComponentName];
}
/**
* Multiplies the costs for a template by a given batch size.
*/
function multiplyEntityCosts(template, trainNum)
{
var totalCosts = {};
for (var r in template.cost)
totalCosts[r] = Math.floor(template.cost[r] * trainNum);
return totalCosts;
}
/**
* Helper function for getEntityCostTooltip.
*/
function getEntityCostComponentsTooltipString(template, trainNum, entity)
{
if (!trainNum)
trainNum = 1;
var totalCosts = multiplyEntityCosts(template, trainNum);
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", {"entity": entity, "batchSize": trainNum}) : 1));
var costs = [];
- if (totalCosts.food) costs.push(getCostComponentDisplayName("food") + " " + totalCosts.food);
- if (totalCosts.wood) costs.push(getCostComponentDisplayName("wood") + " " + totalCosts.wood);
- if (totalCosts.metal) costs.push(getCostComponentDisplayName("metal") + " " + totalCosts.metal);
- if (totalCosts.stone) costs.push(getCostComponentDisplayName("stone") + " " + totalCosts.stone);
- if (totalCosts.population) costs.push(getCostComponentDisplayName("population") + " " + totalCosts.population);
- if (totalCosts.time) costs.push(getCostComponentDisplayName("time") + " " + totalCosts.time);
+ if (totalCosts.food) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("food"), cost: totalCosts.food }));
+ if (totalCosts.wood) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("wood"), cost: totalCosts.wood }));
+ if (totalCosts.metal) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("metal"), cost: totalCosts.metal }));
+ if (totalCosts.stone) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("stone"), cost: totalCosts.stone }));
+ if (totalCosts.population) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("population"), cost: totalCosts.population }));
+ if (totalCosts.time) costs.push(sprintf(translate("%(component)s %(cost)s"), { component: getCostComponentDisplayName("time"), cost: totalCosts.time }));
return costs;
}
/**
* Returns an array of strings for a set of wall pieces. If the pieces share
* resource type requirements, output will be of the form '10 to 30 Stone',
* otherwise output will be, e.g. '10 Stone, 20 Stone, 30 Stone'.
*/
function getWallPieceTooltip(wallTypes)
{
var out = [];
var resourceCount = {};
// Initialize the acceptable types for '$x to $y $resource' mode.
for (var resource in wallTypes[0].cost)
if (wallTypes[0].cost[resource])
resourceCount[resource] = [wallTypes[0].cost[resource]];
var sameTypes = true;
for (var i = 1; i < wallTypes.length; ++i)
{
for (var resource in wallTypes[i].cost)
{
// Break out of the same-type mode if this wall requires
// resource types that the first didn't.
if (wallTypes[i].cost[resource] && !resourceCount[resource])
{
sameTypes = false;
break;
}
}
for (var resource in resourceCount)
{
if (wallTypes[i].cost[resource])
resourceCount[resource].push(wallTypes[i].cost[resource]);
else
{
sameTypes = false;
break;
}
}
}
if (sameTypes)
{
for (var resource in resourceCount)
{
var resourceMin = Math.min.apply(Math, resourceCount[resource]);
var resourceMax = Math.max.apply(Math, resourceCount[resource]);
out.push(getCostComponentDisplayName(resource) + " " + resourceMin + " to " + getCostComponentDisplayName(resource) + " " + resourceMax);
}
}
else
for (var i = 0; i < wallTypes.length; ++i)
out.push(getEntityCostComponentsTooltipString(wallTypes[i]).join(", "));
return out;
}
/**
* Returns the cost information to display in the specified entity's construction button tooltip.
*/
function getEntityCostTooltip(template, trainNum, entity)
{
var cost = "";
// Entities with a wallset component are proxies for initiating wall placement and as such do not have a cost of
// their own; the individual wall pieces within it do.
if (template.wallSet)
{
var templateLong = GetTemplateData(template.wallSet.templates.long);
var templateMedium = GetTemplateData(template.wallSet.templates.medium);
var templateShort = GetTemplateData(template.wallSet.templates.short);
var templateTower = GetTemplateData(template.wallSet.templates.tower);
var wallCosts = getWallPieceTooltip([templateShort, templateMedium, templateLong]);
var towerCosts = getEntityCostComponentsTooltipString(templateTower);
- cost += "\n";
- cost += " Walls: " + wallCosts.join(" ") + "\n";
- cost += " Towers: " + towerCosts.join(" ");
+ cost += " " + sprintf(translate("Walls: %(costs)s"), { costs: wallCosts.join(translate(" ")) }) + "\n";
+ cost += " " + sprintf(translate("Towers: %(costs)s"), { costs: towerCosts.join(translate(" ")) });
}
else if (template.cost)
{
var costs = getEntityCostComponentsTooltipString(template, trainNum, entity);
- cost += costs.join(" ");
+ cost = costs.join(translate(" "));
}
else
{
cost = ""; // cleaner than duplicating the serif-bold-13 stuff
}
return cost;
}
/**
* Returns the population bonus information to display in the specified entity's construction button tooltip.
*/
function getPopulationBonusTooltip(template)
{
var popBonus = "";
if (template.cost && template.cost.populationBonus)
- popBonus = "\n[font=\"serif-bold-13\"]Population Bonus:[/font] " + template.cost.populationBonus;
+ popBonus = "\n" + sprintf(translate("%(label)s %(populationBonus)s"), {
+ label: "[font=\"serif-bold-13\"]" + translate("Population Bonus:") + "[/font]",
+ populationBonus: template.cost.populationBonus
+ });
return popBonus;
}
/**
* Returns a message with the amount of each resource needed to create an entity.
*/
function getNeededResourcesTooltip(resources)
{
var formatted = [];
for (var resource in resources)
- formatted.push("[font=\"serif-12\"]" + getCostComponentDisplayName(resource) + "[/font] " + resources[resource]);
+ formatted.push(sprintf(translate("%(component)s %(cost)s"), {
+ component: "[font=\"serif-12\"]" + getCostComponentDisplayName(resource) + "[/font]",
+ cost: resources[resource]
+ }));
- return "\n\n[font=\"serif-bold-13\"][color=\"red\"]Insufficient resources:[/color][/font]\n" + formatted.join(" ");
+ return "\n\n[font=\"serif-bold-13\"][color=\"red\"]" + translate("Insufficient resources:") + "[/color][/font]\n" + formatted.join(translate(" "));
}
function getEntitySpeed(template)
{
var speed = "";
if (template.speed)
{
- speed += "[font=\"serif-bold-13\"]Speed:[/font] ";
+ var label = "[font=\"serif-bold-13\"]" + translate("Speed:") + "[/font]";
var speeds = [];
- if (template.speed.walk) speeds.push(template.speed.walk + " [font=\"serif-12\"]Walk[/font]");
- if (template.speed.run) speeds.push(template.speed.run + " [font=\"serif-12\"]Run[/font]");
+ if (template.speed.walk) speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.walk, movementType: "[font=\"serif-12\"]" + translate("Walk") + "[/font]"}));
+ if (template.speed.run) speeds.push(sprintf(translate("%(speed)s %(movementType)s"), { speed: template.speed.run, movementType: "[font=\"serif-12\"]" + translate("Run") + "[/font]"}));
- speed += speeds.join(", ");
+ speed = sprintf(translate("%(label)s %(speeds)s"), { label: label, speeds: speeds.join(translate(", ")) })
}
return speed;
}
function getEntityAttack(template)
{
var attacks = [];
if (template.attack)
{
// Don't show slaughter attack
delete template.attack['Slaughter'];
for (var type in template.attack)
{
- var attack = "[font=\"serif-bold-13\"]" + type + " Attack:[/font] " + damageTypesToText(template.attack[type]);
- // Show max attack range if ranged attack, also convert to tiles (4m per tile)
+ var attack = "";
+ var attackLabel = "[font=\"serif-bold-13\"]" + getAttackTypeLabel(type) + "[/font]";
if (type == "Ranged")
- attack += ", [font=\"serif-bold-13\"]Range:[/font] "+Math.round(template.attack[type].maxRange/4);
+ {
+ // Show max attack range if ranged attack, also convert to tiles (4m per tile)
+ attack = sprintf(translate("%(attackLabel)s %(damageTypes)s, %(rangeLabel)s %(range)s"), {
+ attackLabel: attackLabel,
+ damageTypes: damageTypesToText(template.attack[type]),
+ rangeLabel: "[font=\"serif-bold-13\"]" + translate("Range:") + "[/font]",
+ range: Math.round(template.attack[type].maxRange/4)
+ });
+ }
+ else
+ {
+ attack = sprintf(translate("%(attackLabel)s %(damageTypes)s"), {
+ attackLabel: attackLabel,
+ damageTypes: damageTypesToText(template.attack[type])
+ });
+ }
attacks.push(attack);
}
}
return attacks.join("\n");
}
-function getEntityName(template)
-{
- return template.name.specific || template.name.generic || "???";
-}
-
function getEntityNames(template)
{
- var names = [];
if (template.name.specific)
- {
- names.push(template.name.specific);
- if (template.name.generic && names[0] != template.name.generic)
- names.push("(" + template.name.generic + ")");
- }
- else if (template.name.generic)
- names.push(template.name.generic);
+ {
+ if (template.name.generic && template.name.specific != template.name.generic)
+ return sprintf(translate("%(specificName)s (%(genericName)s)"), {
+ specificName: template.name.specific,
+ genericName: template.name.generic
+ });
+ return template.name.specific;
+ }
+ if (template.name.generic)
+ return template.name.generic;
- return (names.length) ? names.join(" ") : "???";
+ warn("Entity name requested on an entity without a name, specific or generic.");
+ return translate("???");
}
function getEntityNamesFormatted(template)
{
var names = "";
var generic = template.name.generic;
var specific = template.name.specific;
if (specific)
{
// drop caps for specific name
names += '[font="serif-bold-16"]' + specific[0] + '[/font]' +
'[font="serif-bold-12"]' + specific.slice(1).toUpperCase() + '[/font]';
if (generic)
names += '[font="serif-bold-16"] (' + generic + ')[/font]';
}
else if (generic)
names = '[font="serif-bold-16"]' + generic + "[/font]";
else
names = "???";
return names;
}
function getEntityRankedName(entState)
{
var template = GetTemplateData(entState.template)
var rank = entState.identity.rank;
if (rank)
- return rank + " " + template.name.specific;
+ return sprintf(translate("%(rank)s %(name)s"), { rank: rank, name: template.name.specific });
else
return template.name.specific;
}
function getRankIconSprite(entState)
{
if ("Elite" == entState.identity.rank)
return "stretched:session/icons/rank3.png";
else if ("Advanced" == entState.identity.rank)
return "stretched:session/icons/rank2.png";
else if (entState.identity.classes && entState.identity.classes.length && -1 != entState.identity.classes.indexOf("CitizenSoldier"))
return "stretched:session/icons/rank1.png";
return "";
}
/**
* Returns a message with the details of the trade gain.
*/
function getTradingTooltip(gain)
{
- var tooltip = gain.traderGain;
+ var gainString = gain.traderGain;
if (gain.market1Gain && gain.market1Owner == gain.traderOwner)
- tooltip += "+" + gain.market1Gain;
+ gainString += translate("+") + gain.market1Gain;
if (gain.market2Gain && gain.market2Owner == gain.traderOwner)
- tooltip += "+" + gain.market2Gain;
- tooltip += " (you)";
+ gainString += translate("+") + gain.market2Gain;
+
+ var tooltip = sprintf(translate("%(gain)s (%(player)s)"), {
+ gain: gainString,
+ player: translate("you")
+ });
if (gain.market1Gain && gain.market1Owner != gain.traderOwner)
- tooltip += ", " + gain.market1Gain + " (player " + gain.market1Owner + ")";
+ tooltip += translate(", ") + sprintf(translate("%(gain)s (%(player)s)"), {
+ gain: gain.market1Gain,
+ player: sprintf(translate("player %(name)s"), { name: gain.market1Owner })
+ });
if (gain.market2Gain && gain.market2Owner != gain.traderOwner)
- tooltip += ", " + gain.market2Gain + " (player " + gain.market2Owner + ")";
+ tooltip += translate(", ") + sprintf(translate("%(gain)s (%(player)s)"), {
+ gain: gain.market2Gain,
+ player: sprintf(translate("player %(name)s"), { name: gain.market2Owner })
+ });
return tooltip;
}
+function getAttackTypeLabel(type)
+{
+ if (type === "Charge") return translate("Charge Attack:");
+ if (type === "Melee") return translate("Melee Attack:");
+ if (type === "Ranged") return translate("Ranged Attack:");
+
+ warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { attackType: type }));
+ return translate("Attack:");
+}
+
/**
* Returns the entity itself except when garrisoned where it returns its garrisonHolder
*/
function getEntityOrHolder(ent)
{
var entState = GetEntityState(ent);
if (entState && !entState.position && entState.unitAI && entState.unitAI.orders.length > 0 &&
entState.unitAI.orders[0].type == "Garrison")
return entState.unitAI.orders[0].data.target;
return ent;
}
Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/summary/summary.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/gui/summary/summary.js (revision 14954)
@@ -1,830 +1,830 @@
// Max player slots for any map (should read from config)
const MAX_SLOTS = 8;
/**
* Select active panel
* @param panelNumber Number of panel, which should get active state (integer)
*/
function selectPanel(panelNumber)
{
var panelNames = [ 'scorePanel', 'buildingsPanel', 'unitsPanel', 'resourcesPanel', 'marketPanel', 'miscPanel' ];
function adjustTabDividers(tabSize)
{
var leftSpacer = Engine.GetGUIObjectByName("tabDividerLeft");
var rightSpacer = Engine.GetGUIObjectByName("tabDividerRight");
leftSpacer.size = "20 " + leftSpacer.size.top + " " + (tabSize.left + 2) + " " + leftSpacer.size.bottom;
rightSpacer.size = (tabSize.right - 2) + " " + rightSpacer.size.top + " 100%-20 " + rightSpacer.size.bottom;
}
for (var i = 0; i < panelNames.length; i++)
{
if (i != panelNumber)
{
Engine.GetGUIObjectByName(panelNames[i]).hidden = true;
Engine.GetGUIObjectByName(panelNames[i] + 'Button').sprite = "BackgroundTab";
}
else
{
Engine.GetGUIObjectByName(panelNames[i]).hidden = false;
Engine.GetGUIObjectByName(panelNames[i] + 'Button').sprite = "ForegroundTab";
adjustTabDividers(Engine.GetGUIObjectByName(panelNames[i] + 'Button').size);
}
}
}
function init(data)
{
// LOCAL CONSTS, VARIABLES & FUNCTIONS
// const for filtering long collective headings
const LONG_HEADING_WIDTH = 250;
// number of panels
const PANELS_COUNT = 6;
// alpha for player box
const PLAYER_BOX_ALPHA = " 32";
// alpha for player colour box
const PLAYER_COLOUR_BOX_ALPHA = " 255";
// yStart value for spaceing teams boxes (and noTeamsBox)
const TEAMS_BOX_Y_START = 65;
// vertical size of player box
const PLAYER_BOX_Y_SIZE = 30;
// gap between players boxes
const PLAYER_BOX_GAP = 2;
// colours used for units and buildings
const TRAINED_COLOR = '[color="201 255 200"]';
const LOST_COLOR = '[color="255 213 213"]';
const KILLED_COLOR = '[color="196 198 255"]';
// colours used for gathered and traded resources
const INCOME_COLOR = '[color="201 255 200"]';
const OUTCOME_COLOR = '[color="255 213 213"]';
const BUILDINGS_TYPES = [ "total", "House", "Economic", "Outpost", "Military", "Fortress", "CivCentre", "Wonder" ];
const UNITS_TYPES = [ "total", "Infantry", "Worker", "Cavalry", "Champion", "Hero", "Ship" ];
const RESOURCES_TYPES = [ "food", "wood", "stone", "metal" ];
var panels = {
"score": { // score panel
"headings": { // headings on score panel
"playerName0Heading": { "yStart": 26, "width": 200 },
"economyScoreHeading": { "yStart": 16, "width": 100 },
"militaryScoreHeading": { "yStart": 16, "width": 100 },
"explorationScoreHeading": { "yStart": 16, "width": 100 },
"totalScoreHeading": { "yStart": 16, "width": 100 }
},
"counters": { // counters on score panel
"economyScore": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"militaryScore": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"explorationScore": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"totalScore": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] }
}
},
"buildings": { // buildings panel
"headings": { // headings on buildings panel
"playerName1Heading": {"yStart": 26, "width": 200 },
"buildingsHeading": {"yStart": 16, "width": (85 * 7 + 105) }, // width = 735
"totalBuildingsHeading": {"yStart": 34, "width": 105 },
"houseBuildingsHeading": {"yStart": 34, "width": 85 },
"economicBuildingsHeading": {"yStart": 34, "width": 85 },
"outpostBuildingsHeading": {"yStart": 34, "width": 85 },
"militaryBuildingsHeading": {"yStart": 34, "width": 85 },
"fortressBuildingsHeading": {"yStart": 34, "width": 85 },
"civCentreBuildingsHeading": {"yStart": 34, "width": 85 },
"wonderBuildingsHeading": {"yStart": 34, "width": 85 }
},
"counters": { // counters on buildings panel
"totalBuildings": {"width": 105, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"houseBuildings": {"width": 85, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"economicBuildings": {"width": 85, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"outpostBuildings": {"width": 85, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"militaryBuildings": {"width": 85, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"fortressBuildings": {"width": 85, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"civCentreBuildings": {"width": 85, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"wonderBuildings": {"width": 85, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] }
}
},
"units": { // units panel
"headings": { // headings on units panel
"playerName2Heading": {"yStart": 26, "width": 200 },
"unitsHeading": {"yStart": 16, "width": (100 * 6 + 120) }, // width = 720
"totalUnitsHeading": {"yStart": 34, "width": 120 },
"infantryUnitsHeading": {"yStart": 34, "width": 100 },
"workerUnitsHeading": {"yStart": 34, "width": 100 },
"cavalryUnitsHeading": {"yStart": 34, "width": 100 },
"championUnitsHeading": {"yStart": 34, "width": 100 },
"heroesUnitsHeading": {"yStart": 34, "width": 100 },
"navyUnitsHeading": {"yStart": 34, "width": 100 }
},
"counters": { // counters on units panel
"totalUnits": {"width": 120, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"infantryUnits": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"workerUnits": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"cavalryUnits": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"championUnits": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"heroesUnits": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"navyUnits": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] }
}
},
"resources": { // resources panel
"headings": { // headings on resources panel
"playerName3Heading": {"yStart": 26, "width": 200 },
"resourceHeading": {"yStart": 16, "width": (100 * 4 + 110) },//width = 510
"foodGatheredHeading": {"yStart": 34, "width": 100 },
"woodGatheredHeading": {"yStart": 34, "width": 100 },
"stoneGatheredHeading": {"yStart": 34, "width": 100 },
"metalGatheredHeading": {"yStart": 34, "width": 100 },
"totalGatheredHeading": {"yStart": 34, "width": 110 },
"treasuresCollectedHeading": {"yStart": 16, "width": 100 },
"resourcesTributedHeading": {"yStart": 16, "width": 121 }
},
"counters": { // counters on resources panel
"foodGathered": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"woodGathered": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"stoneGathered": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"metalGathered": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"totalGathered": {"width": 110, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"treasuresCollected": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"resourcesTributed": {"width": 121, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] }
}
},
"market": { // market panel
"headings": { // headings on market panel
"playerName4Heading": {"yStart": 26, "width": 200 },
"exchangedFoodHeading": {"yStart": 16, "width": 100 },
"exchangedWoodHeading": {"yStart": 16, "width": 100 },
"exchangedStoneHeading": {"yStart": 16, "width": 100 },
"exchangedMetalHeading": {"yStart": 16, "width": 100 },
"barterEfficiencyHeading": {"yStart": 16, "width": 100 },
"tradeIncomeHeading": {"yStart": 16, "width": 100 }
},
"counters": { // counters on market panel
"exchangedFood": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"exchangedWood": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"exchangedStone": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"exchangedMetal": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"barterEfficiency": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"tradeIncome": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] }
}
},
"miscelanous": { // miscelanous panel
"headings": { // headings on miscelanous panel
"playerName5Heading": {"yStart": 26, "width": 200 },
"vegetarianRatioHeading": {"yStart": 16, "width": 100 },
"feminisationHeading": {"yStart": 26, "width": 100 },
"killDeathRatioHeading": {"yStart": 16, "width": 100 },
"mapExplorationHeading": {"yStart": 16, "width": 100 }
},
"counters": { // counters on miscelanous panel
"vegetarianRatio": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"feminisation": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"killDeathRatio": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] },
"mapExploration": {"width": 100, "objects": [ ], "teamsScores": [ ], "teamsScoresCaption": [ ] }
}
}
};
function alignHeaders(headings)
{
left = 50;
for (var h in headings)
{
Engine.GetGUIObjectByName(h).size = left + " " + headings[h].yStart + " " + (left + headings[h].width) + " 100%";
if (headings[h].width < LONG_HEADING_WIDTH)
left += headings[h].width;
}
}
function alignCounters(counters, player)
{
left = 240;
for each (var counter in counters)
{
counter.objects[player].size = left + " 2 " + (left + counter.width) + " 100%";
left += counter.width;
}
return left;
}
// caption counters functions
function captionEconomyScore()
{
var total = 0;
for each (var res in playerState.statistics.resourcesGathered)
total += res;
return Math.round(total / 10);
}
function captionBuildings(object, type)
{
object.caption = TRAINED_COLOR + playerState.statistics.buildingsConstructed[type] + '[/color] / ' +
LOST_COLOR + playerState.statistics.buildingsLost[type] + '[/color] / ' +
KILLED_COLOR + playerState.statistics.enemyBuildingsDestroyed[type] + '[/color]';
}
function captionUnits(object, type)
{
object.caption = TRAINED_COLOR + playerState.statistics.unitsTrained[type] + '[/color] / ' +
LOST_COLOR + playerState.statistics.unitsLost[type] + '[/color] / ' +
KILLED_COLOR + playerState.statistics.enemyUnitsKilled[type] + '[/color]';
}
function captionResourcesGathered(object, type)
{
object.caption = INCOME_COLOR + playerState.statistics.resourcesGathered[type] + '[/color] / ' +
OUTCOME_COLOR + (playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type]) + '[/color]';
}
function captionTotalResourcesGathered()
{
var totalGathered = 0;
var totalUsed = 0;
for each (var type in RESOURCES_TYPES)
{
totalGathered += playerState.statistics.resourcesGathered[type];
totalUsed += playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type];
}
return INCOME_COLOR + totalGathered + '[/color] / ' + OUTCOME_COLOR + totalUsed + '[/color]';
}
function captionResourcesTributed()
{
return INCOME_COLOR + playerState.statistics.tributesSent + "[/color] / " + OUTCOME_COLOR + playerState.statistics.tributesReceived + "[/color]";
}
function captionResourcesExchanged(object, type)
{
object.caption = INCOME_COLOR + '+' + playerState.statistics.resourcesBought[type] + '[/color] ' +
OUTCOME_COLOR + '-' + playerState.statistics.resourcesSold[type] + '[/color]';
}
function captionBarterEfficiency()
{
var totalBought = 0;
for each (var boughtAmount in playerState.statistics.resourcesBought)
totalBought += boughtAmount;
var totalSold = 0;
for each (var soldAmount in playerState.statistics.resourcesSold)
totalSold += soldAmount;
return Math.floor(totalSold > 0 ? (totalBought / totalSold) * 100 : 0) + "%";
}
function captionVegetarianRatio()
{
if (playerState.statistics.resourcesGathered.vegetarianFood && playerState.statistics.resourcesGathered.food)
return Math.floor((playerState.statistics.resourcesGathered.vegetarianFood / playerState.statistics.resourcesGathered.food) * 100) + "%";
else
return 0 + "%";
}
function captionFeminisation()
{
if (playerState.statistics.unitsTrained.Worker && playerState.statistics.unitsTrained.Female)
return Math.floor((playerState.statistics.unitsTrained.Female / playerState.statistics.unitsTrained.Worker) * 100) + "%";
else
return 0 + "%";
}
function captionKillDeathRatio()
{
if (!playerState.statistics.enemyUnitsKilled.total)
return "0.00";
if (!playerState.statistics.unitsLost.total) // and enemyUnitsKilled.total > 0
return "\u221E"; // infinity symbol
return Math.round((playerState.statistics.enemyUnitsKilled.total / playerState.statistics.unitsLost.total)*100)/100;
}
function sumTeamBuildings(counter, type)
{
if (counter.teamsScores[playerState.team] == 0)
{
counter.teamsScores[playerState.team] = { };
counter.teamsScores[playerState.team].buildingsConstructed = 0;
counter.teamsScores[playerState.team].buildingsLost = 0;
counter.teamsScores[playerState.team].enemyBuildingsDestroyed = 0;
}
counter.teamsScores[playerState.team].buildingsConstructed += playerState.statistics.buildingsConstructed[type];
counter.teamsScores[playerState.team].buildingsLost += playerState.statistics.buildingsLost[type];
counter.teamsScores[playerState.team].enemyBuildingsDestroyed += playerState.statistics.enemyBuildingsDestroyed[type];
counter.teamsScoresCaption[playerState.team] = TRAINED_COLOR + counter.teamsScores[playerState.team].buildingsConstructed + '[/color] / ' +
LOST_COLOR + counter.teamsScores[playerState.team].buildingsLost + '[/color] / ' +
KILLED_COLOR + counter.teamsScores[playerState.team].enemyBuildingsDestroyed + '[/color]';
}
function sumTeamUnits(counter, type)
{
if (counter.teamsScores[playerState.team] == 0)
{
counter.teamsScores[playerState.team] = { };
counter.teamsScores[playerState.team].unitsTrained = 0;
counter.teamsScores[playerState.team].unitsLost = 0;
counter.teamsScores[playerState.team].enemyUnitsKilled = 0;
}
counter.teamsScores[playerState.team].unitsTrained += playerState.statistics.unitsTrained[type];
counter.teamsScores[playerState.team].unitsLost += playerState.statistics.unitsLost[type];
counter.teamsScores[playerState.team].enemyUnitsKilled += playerState.statistics.enemyUnitsKilled[type];
counter.teamsScoresCaption[playerState.team] = TRAINED_COLOR + counter.teamsScores[playerState.team].unitsTrained + '[/color] / ' +
LOST_COLOR + counter.teamsScores[playerState.team].unitsLost + '[/color] / ' +
KILLED_COLOR + counter.teamsScores[playerState.team].enemyUnitsKilled + '[/color]';
}
function sumResourcesGathered(counter, type)
{
if (counter.teamsScores[playerState.team] == 0)
{
counter.teamsScores[playerState.team] = { };
counter.teamsScores[playerState.team].resourcesGathered = 0;
counter.teamsScores[playerState.team].resourcesUsed = 0;
}
counter.teamsScores[playerState.team].resourcesGathered += playerState.statistics.resourcesGathered[type];
counter.teamsScores[playerState.team].resourcesUsed += playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type];
counter.teamsScoresCaption[playerState.team] = INCOME_COLOR + counter.teamsScores[playerState.team].resourcesGathered + '[/color] / ' +
OUTCOME_COLOR + counter.teamsScores[playerState.team].resourcesUsed + '[/color]';
}
function sumTotalResourcesGathered()
{
if (panels.resources.counters.totalGathered.teamsScores[playerState.team] == 0)
{
panels.resources.counters.totalGathered.teamsScores[playerState.team] = { };
panels.resources.counters.totalGathered.teamsScores[playerState.team].resourcesGathered = 0;
panels.resources.counters.totalGathered.teamsScores[playerState.team].resourcesUsed = 0;
}
for each (var type in RESOURCES_TYPES)
{
panels.resources.counters.totalGathered.teamsScores[playerState.team].resourcesGathered += playerState.statistics.resourcesGathered[type];
panels.resources.counters.totalGathered.teamsScores[playerState.team].resourcesUsed +=
playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type];
}
panels.resources.counters.totalGathered.teamsScoresCaption[playerState.team] =
INCOME_COLOR + panels.resources.counters.totalGathered.teamsScores[playerState.team].resourcesGathered + '[/color] / ' +
OUTCOME_COLOR + panels.resources.counters.totalGathered.teamsScores[playerState.team].resourcesUsed + '[/color]';
}
function sumResourcesTributed()
{
if (panels.resources.counters.resourcesTributed.teamsScores[playerState.team] == 0)
{
panels.resources.counters.resourcesTributed.teamsScores[playerState.team] = { };
panels.resources.counters.resourcesTributed.teamsScores[playerState.team].tributesSent = 0;
panels.resources.counters.resourcesTributed.teamsScores[playerState.team].tributesReceived = 0;
}
panels.resources.counters.resourcesTributed.teamsScores[playerState.team].tributesSent += playerState.statistics.tributesSent;
panels.resources.counters.resourcesTributed.teamsScores[playerState.team].tributesReceived += playerState.statistics.tributesReceived;
panels.resources.counters.resourcesTributed.teamsScoresCaption[playerState.team] =
INCOME_COLOR + panels.resources.counters.resourcesTributed.teamsScores[playerState.team].tributesSent + "[/color] / " +
OUTCOME_COLOR + panels.resources.counters.resourcesTributed.teamsScores[playerState.team].tributesReceived + "[/color]";
}
function sumResourcesExchanged(counter, type)
{
if (counter.teamsScores[playerState.team] == 0)
{
counter.teamsScores[playerState.team] = { };
counter.teamsScores[playerState.team].resourcesBought = 0;
counter.teamsScores[playerState.team].resourcesSold = 0;
}
counter.teamsScores[playerState.team].resourcesBought += playerState.statistics.resourcesBought[type];
counter.teamsScores[playerState.team].resourcesSold += playerState.statistics.resourcesSold[type];
counter.teamsScoresCaption[playerState.team] =
INCOME_COLOR + '+' + counter.teamsScores[playerState.team].resourcesBought + '[/color] ' +
OUTCOME_COLOR + '-' + counter.teamsScores[playerState.team].resourcesSold + '[/color]';
}
function sumBarterEfficiency()
{
if (panels.market.counters.barterEfficiency.teamsScores[playerState.team] == 0)
{
panels.market.counters.barterEfficiency.teamsScores[playerState.team] = { };
panels.market.counters.barterEfficiency.teamsScores[playerState.team].resourcesBought = 0;
panels.market.counters.barterEfficiency.teamsScores[playerState.team].resourcesSold = 0;
}
for each (var boughtAmount in playerState.statistics.resourcesBought)
panels.market.counters.barterEfficiency.teamsScores[playerState.team].resourcesBought += boughtAmount;
for each (var soldAmount in playerState.statistics.resourcesSold)
panels.market.counters.barterEfficiency.teamsScores[playerState.team].resourcesSold += soldAmount;
panels.market.counters.barterEfficiency.teamsScoresCaption[playerState.team] =
Math.floor(panels.market.counters.barterEfficiency.teamsScores[playerState.team].resourcesSold > 0 ?
(panels.market.counters.barterEfficiency.teamsScores[playerState.team].resourcesBought /
panels.market.counters.barterEfficiency.teamsScores[playerState.team].resourcesSold) * 100 : 0) + "%";
}
function sumVegetarianRatio()
{
if (panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team] == 0)
{
panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team] = { };
panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].vegetarianFood = 0;
panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].food = 0;
}
panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].vegetarianFood += playerState.statistics.resourcesGathered.vegetarianFood;
panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].food += playerState.statistics.resourcesGathered.food;
if (panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].food &&
panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].vegetarianFood)
{
panels.miscelanous.counters.vegetarianRatio.teamsScoresCaption[playerState.team] =
Math.floor((panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].vegetarianFood /
panels.miscelanous.counters.vegetarianRatio.teamsScores[playerState.team].food) * 100) + "%";
return;
}
panels.miscelanous.counters.vegetarianRatio.teamsScoresCaption[playerState.team] = 0 + "%";
}
function sumFeminisation()
{
if (panels.miscelanous.counters.feminisation.teamsScores[playerState.team] == 0)
{
panels.miscelanous.counters.feminisation.teamsScores[playerState.team] = { };
panels.miscelanous.counters.feminisation.teamsScores[playerState.team].femalesTrained = 0;
panels.miscelanous.counters.feminisation.teamsScores[playerState.team].workersTrained = 0;
}
panels.miscelanous.counters.feminisation.teamsScores[playerState.team].femalesTrained += playerState.statistics.unitsTrained.Female;
panels.miscelanous.counters.feminisation.teamsScores[playerState.team].workersTrained += playerState.statistics.unitsTrained.Worker;
if (panels.miscelanous.counters.feminisation.teamsScores[playerState.team].femalesTrained &&
panels.miscelanous.counters.feminisation.teamsScores[playerState.team].workersTrained)
{
panels.miscelanous.counters.feminisation.teamsScoresCaption[playerState.team] =
Math.floor((panels.miscelanous.counters.feminisation.teamsScores[playerState.team].femalesTrained /
panels.miscelanous.counters.feminisation.teamsScores[playerState.team].workersTrained) * 100) + "%";
return;
}
panels.miscelanous.counters.feminisation.teamsScoresCaption[playerState.team] = 0 + "%";
}
function sumKillDeathRatio()
{
if (panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team] == 0)
{
panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team] = { };
panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].enemyUnitsKilled = 0;
panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].unitsLost = 0;
}
panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].enemyUnitsKilled += playerState.statistics.enemyUnitsKilled.total;
panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].unitsLost += playerState.statistics.unitsLost.total;
if (!panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].enemyUnitsKilled)
{
panels.miscelanous.counters.killDeathRatio.teamsScoresCaption[playerState.team] = "0.00";
return;
}
if (!panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].unitsLost)
{
panels.miscelanous.counters.killDeathRatio.teamsScoresCaption[playerState.team] = "\u221E"; // infinity symbol
return;
}
panels.miscelanous.counters.killDeathRatio.teamsScoresCaption[playerState.team] =
Math.round((panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].enemyUnitsKilled /
panels.miscelanous.counters.killDeathRatio.teamsScores[playerState.team].unitsLost) * 100)/100;
}
// FUNCTION BODY
// Load data
var civData = loadCivData();
// Map
- var mapSize = "Scenario";
+ var mapDisplayType = translate("Scenario");
- Engine.GetGUIObjectByName("timeElapsed").caption = "Time elapsed: " + timeToString(data.timeElapsed);
+ Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(translate("Time elapsed: %(time)s"), { time: timeToString(data.timeElapsed) });
Engine.GetGUIObjectByName("summaryText").caption = data.gameResult;
// This is only defined for random maps
if (data.mapSettings.Size)
{
// load the map sizes from the JSON file
var mapSizes = initMapSizes();
// retrieve the index of the map size
for (var mapSizeIndex in mapSizes.tiles)
{
if (mapSizes.tiles[mapSizeIndex] == data.mapSettings.Size)
{
- mapSize = mapSizes.names[mapSizeIndex];
+ mapDisplayType = mapSizes.names[mapSizeIndex];
break;
}
}
}
- Engine.GetGUIObjectByName("mapName").caption = data.mapSettings.Name + " - " + mapSize;
+ Engine.GetGUIObjectByName("mapName").caption = sprintf(translate("%(mapName)s - %(mapType)s"), { mapName: data.mapSettings.Name, mapType: mapDisplayType});
// Panels
// Align headers
var left = 50;
for each (var panel in panels) // for all panels
alignHeaders(panel.headings);
// TODO set maxPlayers as playerCounters.length
var maxPlayers = data.playerStates.length - 1;
var maxTeams = 0;
var teams = [ ];
if (data.mapSettings.LockTeams) // teams ARE locked
{
// count teams
for(var t = 0; t < maxPlayers; ++t)
{
if (!teams[data.playerStates[t+1].team])
{
teams[data.playerStates[t+1].team] = 1;
continue;
}
teams[data.playerStates[t+1].team]++;
}
if (teams.length == maxPlayers)
teams = false; // Each player has his own team. Displaying teams makes no sense.
}
else // teams are NOT locked
teams = false;
// Erase teams data if teams are not displayed
if (!teams)
{
for(var p = 0; p < maxPlayers; ++p)
data.playerStates[p+1].team = -1;
}
// Count players without team (or all if teams are not displayed)
var withoutTeam = maxPlayers;
if (teams)
{
// count players without team (or all if teams are not displayed)
for (var i = 0; i < teams.length; ++i)
withoutTeam -= teams[i];
// Display teams boxes
var p = 0;
for each (var panel in panels)
{
var yStart = TEAMS_BOX_Y_START + withoutTeam * (PLAYER_BOX_Y_SIZE + PLAYER_BOX_GAP);
for (var i = 0; i < teams.length; ++i)
{
var teamBox = Engine.GetGUIObjectByName("teamBox"+p+"t"+i);
teamBox.hidden = false;
var teamBoxSize = teamBox.size;
teamBoxSize.top = yStart;
teamBox.size = teamBoxSize;
yStart += 30 + teams[i] * (PLAYER_BOX_Y_SIZE + PLAYER_BOX_GAP) + 32;
Engine.GetGUIObjectByName("teamNameHeading"+p+"t"+i).caption = "Team "+(i+1);
// Make place to store team scores
for each (var counter in panel.counters)
{
counter.teamsScores[i] = 0;
counter.teamsScoresCaption[i] = "0";
}
}
// If there are no players without team, hide "player name" heading
if (!withoutTeam)
Engine.GetGUIObjectByName("playerName"+p+"Heading").caption = "";
p++;
}
}
if (withoutTeam)
{
// Show boxes for no teams
for (var b = 0; b < PANELS_COUNT; ++b)
Engine.GetGUIObjectByName("noTeamsBox"+b).hidden = false;
}
var playerBoxesCounts = [ ];
for (var i = 0; i < maxPlayers; ++i)
{
var tn = "";
var playerState = data.playerStates[i+1];
if (!playerBoxesCounts[playerState.team+1])
playerBoxesCounts[playerState.team+1] = 1;
else
playerBoxesCounts[playerState.team+1] += 1;
if (playerState.team != -1)
tn = "t"+playerState.team+"p";
var j = 0;
for each (var panel in panels)
{
var playerIdentityString = tn+"["+(playerBoxesCounts[playerState.team+1]-1)+"]";
// Display boxes for players
var playerBox = Engine.GetGUIObjectByName("playerBox"+j+playerIdentityString);
playerBox.hidden = false;
var boxSize = playerBox.size;
boxSize.top += (playerBoxesCounts[playerState.team+1] - 1) * (PLAYER_BOX_Y_SIZE + PLAYER_BOX_GAP);
boxSize.bottom = boxSize.top + PLAYER_BOX_Y_SIZE;
playerBox.size = boxSize;
var colourString = "colour: "
+ Math.floor(playerState.colour.r * 255) + " "
+ Math.floor(playerState.colour.g * 255) + " "
+ Math.floor(playerState.colour.b * 255);
playerBox.sprite = colourString + PLAYER_BOX_ALPHA;
var playerColourBox = Engine.GetGUIObjectByName("playerColourBox"+j+playerIdentityString);
playerColourBox.sprite = colourString + PLAYER_COLOUR_BOX_ALPHA;
// Show the multiplayer name, e.g. "Foobar" rather than "Player 1".
// TODO: Perhaps show both the multiplayer and map-specific name?
var playerName = Engine.GetGUIObjectByName("playerName"+j+playerIdentityString);
playerName.caption = data.players[i+1].name;
var civIcon = Engine.GetGUIObjectByName("civIcon"+j+playerIdentityString);
civIcon.sprite = "stretched:"+civData[playerState.civ].Emblem;
civIcon.tooltip = civData[playerState.civ].Name;
// Get counters
for (var c in panel.counters)
{
panel.counters[c].objects[i] = Engine.GetGUIObjectByName(c+playerIdentityString);
}
// Align counters
var right = alignCounters(panel.counters, i);
boxSize.right = right;
playerBox.size = boxSize;
j++;
}
// Assign counters
// score panel
panels.score.counters.economyScore.objects[i].caption = captionEconomyScore();
panels.score.counters.militaryScore.objects[i].caption = Math.round((playerState.statistics.enemyUnitsKilledValue +
playerState.statistics.enemyBuildingsDestroyedValue) / 10);
panels.score.counters.explorationScore.objects[i].caption = playerState.statistics.percentMapExplored * 10;
panels.score.counters.totalScore.objects[i].caption = (+panels.score.counters.economyScore.objects[i].caption) +
(+panels.score.counters.militaryScore.objects[i].caption) +
(+panels.score.counters.explorationScore.objects[i].caption);
// buildings panel
var t = 0;
for each (var counter in panels.buildings.counters)
{
captionBuildings(counter.objects[i], BUILDINGS_TYPES[t]);
t++;
}
// units panel
t = 0;
for each (var counter in panels.units.counters)
{
captionUnits(counter.objects[i], UNITS_TYPES[t]);
t++;
}
// resources panel
t = 0;
for each (var counter in panels.resources.counters)
{
if (t >= 4) // only 4 first counters
break;
captionResourcesGathered(counter.objects[i], RESOURCES_TYPES[t]);
t++;
}
panels.resources.counters.totalGathered.objects[i].caption = captionTotalResourcesGathered();
panels.resources.counters.treasuresCollected.objects[i].caption = playerState.statistics.treasuresCollected;
panels.resources.counters.resourcesTributed.objects[i].caption = captionResourcesTributed();
// market panel
t = 0;
for (var c in panels.market.counters)
{
if (t >= 4) // only 4 first counters
break;
captionResourcesExchanged(panels.market.counters[c].objects[i], RESOURCES_TYPES[t]);
t++;
}
panels.market.counters.barterEfficiency.objects[i].caption = captionBarterEfficiency();
panels.market.counters.tradeIncome.objects[i].caption = playerState.statistics.tradeIncome;
// miscelanous panel
panels.miscelanous.counters.vegetarianRatio.objects[i].caption = captionVegetarianRatio();
panels.miscelanous.counters.feminisation.objects[i].caption = captionFeminisation();
panels.miscelanous.counters.killDeathRatio.objects[i].caption = captionKillDeathRatio();
panels.miscelanous.counters.mapExploration.objects[i].caption = playerState.statistics.percentMapExplored + "%";
if (!teams)
continue;
if (playerState.team == -1)
continue;
// Evaluate team total score
// score panel
for (var c in panels.score.counters)
{
panels.score.counters[c].teamsScores[playerState.team] += (+panels.score.counters[c].objects[i].caption);
panels.score.counters[c].teamsScoresCaption[playerState.team] = panels.score.counters[c].teamsScores[playerState.team];
}
// buildings panel
var t = 0;
for each (var counter in panels.buildings.counters)
{
sumTeamBuildings(counter, BUILDINGS_TYPES[t]);
t++;
}
// units panel
t = 0;
for each (var counter in panels.units.counters)
{
sumTeamUnits(counter, UNITS_TYPES[t]);
t++;
}
// resources panel
t = 0;
for each (var counter in panels.resources.counters)
{
if (t >= 4) // only 4 first counters
break;
sumResourcesGathered(counter, RESOURCES_TYPES[t]);
t++;
}
sumTotalResourcesGathered();
panels.resources.counters.treasuresCollected.teamsScores[playerState.team] += playerState.statistics.treasuresCollected;
panels.resources.counters.treasuresCollected.teamsScoresCaption[playerState.team] = panels.resources.counters.treasuresCollected.teamsScores[playerState.team];
sumResourcesTributed();
// market panel
t = 0;
for (var c in panels.market.counters)
{
if (t >= 4) // only 4 first counters
break;
sumResourcesExchanged(panels.market.counters[c], RESOURCES_TYPES[t]);
t++;
}
sumBarterEfficiency();
panels.market.counters.tradeIncome.teamsScores[playerState.team] += playerState.statistics.tradeIncome;
panels.market.counters.tradeIncome.teamsScoresCaption[playerState.team] = panels.market.counters.tradeIncome.teamsScores[playerState.team];
// miscelanous panel
sumVegetarianRatio();
sumFeminisation();
sumKillDeathRatio();
// TODO: probably change from simple sum to union from range manager
panels.miscelanous.counters.mapExploration.teamsScores[playerState.team] += playerState.statistics.percentMapExplored;
panels.miscelanous.counters.mapExploration.teamsScoresCaption[playerState.team] = panels.miscelanous.counters.mapExploration.teamsScores[playerState.team] + "%";
}
if (!teams)
{
selectPanel(0);
return;
}
// Display teams totals counters
for (var i = 0; i < teams.length; ++i)
{
var pn = 0;
for each (var panel in panels)
{
var teamHeading = Engine.GetGUIObjectByName("teamHeading"+pn+"t"+i);
var yStart = 30 + teams[i] * (PLAYER_BOX_Y_SIZE + PLAYER_BOX_GAP) + 2;
teamHeading.size = "50 "+yStart+" 100% "+(yStart+20);
teamHeading.caption = "Team total";
var left = 250;
for (var c in panel.counters)
{
var counter = Engine.GetGUIObjectByName(c+"t"+i);
counter.size = left + " " + yStart + " " + (left + panel.counters[c].width) + " " + (yStart+20);
counter.caption = panel.counters[c].teamsScoresCaption[i];
left += panel.counters[c].width;
}
pn++;
}
}
selectPanel(0);
}
function onTick()
{
}
Index: ps/trunk/binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 14953)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 14954)
@@ -1,667 +1,701 @@
function Player() {}
Player.prototype.Schema =
"";
Player.prototype.Init = function()
{
this.playerID = undefined;
this.name = undefined; // define defaults elsewhere (supporting other languages)
this.civ = undefined;
this.colour = { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 };
this.popUsed = 0; // population of units owned or trained by this player
this.popBonuses = 0; // sum of population bonuses of player's entities
this.maxPop = 300; // maximum population
this.trainingBlocked = false; // indicates whether any training queue is currently blocked
this.resourceCount = {
"food": 300,
"wood": 300,
"metal": 300,
"stone": 300
};
this.tradingGoods = [ // goods for next trade-route and its proba in % (the sum of probas must be 100)
{ "goods": "wood", "proba": 30 },
{ "goods": "stone", "proba": 35 },
{ "goods": "metal", "proba": 35 } ];
this.team = -1; // team number of the player, players on the same team will always have ally diplomatic status - also this is useful for team emblems, scoring, etc.
this.teamsLocked = false;
this.state = "active"; // game state - one of "active", "defeated", "won"
this.diplomacy = []; // array of diplomatic stances for this player with respect to other players (including gaia and self)
this.conquestCriticalEntitiesCount = 0; // number of owned units with ConquestCritical class
this.formations = [];
this.startCam = undefined;
this.controlAllUnits = false;
this.isAI = false;
this.gatherRateMultiplier = 1;
this.cheatsEnabled = false;
this.cheatTimeMultiplier = 1;
this.heroes = [];
+ this.resourceNames = {
+ "food": markForTranslation("Food"),
+ "wood": markForTranslation("Wood"),
+ "metal": markForTranslation("Metal"),
+ "stone": markForTranslation("Stone"),
+ }
Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager).CheckConquestCriticalEntities();
};
Player.prototype.SetPlayerID = function(id)
{
this.playerID = id;
};
Player.prototype.GetPlayerID = function()
{
return this.playerID;
};
Player.prototype.SetName = function(name)
{
this.name = name;
};
Player.prototype.GetName = function()
{
return this.name;
};
Player.prototype.SetCiv = function(civcode)
{
this.civ = civcode;
};
Player.prototype.GetCiv = function()
{
return this.civ;
};
Player.prototype.SetColour = function(r, g, b)
{
this.colour = { "r": r/255.0, "g": g/255.0, "b": b/255.0, "a": 1.0 };
};
Player.prototype.GetColour = function()
{
return this.colour;
};
// Try reserving num population slots. Returns 0 on success or number of missing slots otherwise.
Player.prototype.TryReservePopulationSlots = function(num)
{
if (num != 0 && num > (this.GetPopulationLimit() - this.GetPopulationCount()))
return num - (this.GetPopulationLimit() - this.GetPopulationCount());
this.popUsed += num;
return 0;
};
Player.prototype.UnReservePopulationSlots = function(num)
{
this.popUsed -= num;
};
Player.prototype.GetPopulationCount = function()
{
return this.popUsed;
};
Player.prototype.SetPopulationBonuses = function(num)
{
this.popBonuses = num;
};
Player.prototype.AddPopulationBonuses = function(num)
{
this.popBonuses += num;
};
Player.prototype.GetPopulationLimit = function()
{
return Math.min(this.GetMaxPopulation(), this.popBonuses);
};
Player.prototype.SetMaxPopulation = function(max)
{
this.maxPop = max;
};
Player.prototype.GetMaxPopulation = function()
{
return Math.round(ApplyValueModificationsToPlayer("Player/MaxPopulation", this.maxPop, this.entity));
};
Player.prototype.SetGatherRateMultiplier = function(value)
{
this.gatherRateMultiplier = value;
};
Player.prototype.GetGatherRateMultiplier = function()
{
return this.gatherRateMultiplier;
};
Player.prototype.GetHeroes = function()
{
return this.heroes;
};
Player.prototype.IsTrainingBlocked = function()
{
return this.trainingBlocked;
};
Player.prototype.BlockTraining = function()
{
this.trainingBlocked = true;
};
Player.prototype.UnBlockTraining = function()
{
this.trainingBlocked = false;
};
Player.prototype.SetResourceCounts = function(resources)
{
if (resources.food !== undefined)
this.resourceCount.food = resources.food;
if (resources.wood !== undefined)
this.resourceCount.wood = resources.wood;
if (resources.stone !== undefined)
this.resourceCount.stone = resources.stone;
if (resources.metal !== undefined)
this.resourceCount.metal = resources.metal;
};
Player.prototype.GetResourceCounts = function()
{
return this.resourceCount;
};
/**
* Add resource of specified type to player
* @param type Generic type of resource (string)
* @param amount Amount of resource, which should be added (integer)
*/
Player.prototype.AddResource = function(type, amount)
{
this.resourceCount[type] += (+amount);
};
/**
* Add resources to player
*/
Player.prototype.AddResources = function(amounts)
{
for (var type in amounts)
{
this.resourceCount[type] += (+amounts[type]);
}
};
Player.prototype.GetNeededResources = function(amounts)
{
// Check if we can afford it all
var amountsNeeded = {};
for (var type in amounts)
if (this.resourceCount[type] != undefined && amounts[type] > this.resourceCount[type])
amountsNeeded[type] = amounts[type] - Math.floor(this.resourceCount[type]);
if (Object.keys(amountsNeeded).length == 0)
return undefined;
return amountsNeeded;
};
Player.prototype.SubtractResourcesOrNotify = function(amounts)
{
var amountsNeeded = this.GetNeededResources(amounts);
// If we don't have enough resources, send a notification to the player
if (amountsNeeded)
{
- var formatted = [];
+ var parameters = {};
+ var i = 0;
for (var type in amountsNeeded)
- formatted.push(amountsNeeded[type] + " " + type[0].toUpperCase() + type.substr(1) );
- var notification = {"player": this.playerID, "message": "Insufficient resources - " + formatted.join(", ")};
+ {
+ i++;
+ parameters["resourceType"+i] = this.resourceNames[type];
+ parameters["resourceAmount"+i] = amountsNeeded[type];
+ }
+
+ var msg = "";
+ // when marking strings for translations, you need to include the actual string,
+ // not some way to derive the string
+ if (i < 1)
+ warn("Amounts needed but no amounts given?");
+ else if (i == 1)
+ msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s");
+ else if (i == 2)
+ msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s");
+ else if (i == 3)
+ msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s");
+ else if (i == 4)
+ msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s, %(resourceAmount4)s %(resourceType4)s");
+ else
+ warn("Localisation: Strings are not localised for more than 4 resources");
+
+ var notification = {
+ "player": this.playerID,
+ "message": msg,
+ "parameters": parameters,
+ "translateMessage": true,
+ "translateParameters": ["resourceType1", "resourceType2", "resourceType3", "resourceType4"],
+ };
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
return false;
}
// Subtract the resources
for (var type in amounts)
this.resourceCount[type] -= amounts[type];
return true;
};
Player.prototype.TrySubtractResources = function(amounts)
{
if (!this.SubtractResourcesOrNotify(amounts))
return false;
var cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
if (cmpStatisticsTracker)
for (var type in amounts)
cmpStatisticsTracker.IncreaseResourceUsedCounter(type, amounts[type]);
return true;
};
Player.prototype.GetNextTradingGoods = function()
{
var value = 100*Math.random();
var last = this.tradingGoods.length - 1;
var sumProba = 0;
for (var i = 0; i < last; ++i)
{
sumProba += this.tradingGoods[i].proba;
if (value < sumProba)
return this.tradingGoods[i].goods;
}
return this.tradingGoods[last].goods;
};
Player.prototype.GetTradingGoods = function()
{
var tradingGoods = {};
for each (var resource in this.tradingGoods)
tradingGoods[resource.goods] = resource.proba;
return tradingGoods;
};
Player.prototype.SetTradingGoods = function(tradingGoods)
{
var sumProba = 0;
for (var resource in tradingGoods)
sumProba += tradingGoods[resource];
if (sumProba != 100) // consistency check
{
error("Player.js SetTradingGoods: " + uneval(tradingGoods));
tradingGoods = { "food": 20, "wood":20, "stone":30, "metal":30 };
}
this.tradingGoods = [];
for (var resource in tradingGoods)
this.tradingGoods.push( {"goods": resource, "proba": tradingGoods[resource]} );
};
Player.prototype.GetState = function()
{
return this.state;
};
Player.prototype.SetState = function(newState)
{
this.state = newState;
};
Player.prototype.GetConquestCriticalEntitiesCount = function()
{
return this.conquestCriticalEntitiesCount;
};
Player.prototype.GetTeam = function()
{
return this.team;
};
Player.prototype.SetTeam = function(team)
{
if (!this.teamsLocked)
{
this.team = team;
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (cmpPlayerManager && this.team != -1)
{
// Set all team members as allies
for (var i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
{
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(i), IID_Player);
if (this.team == cmpPlayer.GetTeam())
{
this.SetAlly(i);
cmpPlayer.SetAlly(this.playerID);
}
}
}
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
}
};
Player.prototype.SetLockTeams = function(value)
{
this.teamsLocked = value;
};
Player.prototype.GetLockTeams = function()
{
return this.teamsLocked;
};
Player.prototype.GetDiplomacy = function()
{
return this.diplomacy;
};
Player.prototype.SetDiplomacy = function(dipl)
{
// Should we check for teamsLocked here?
this.diplomacy = dipl;
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
};
Player.prototype.SetDiplomacyIndex = function(idx, value)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return;
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(idx), IID_Player);
if (!cmpPlayer)
return;
if (this.state != "active" || cmpPlayer.state != "active")
return;
// You can have alliances with other players,
if (this.teamsLocked)
{
// but can't stab your team members in the back
if (this.team == -1 || this.team != cmpPlayer.GetTeam())
{
// Break alliance or declare war
if (Math.min(this.diplomacy[idx],cmpPlayer.diplomacy[this.playerID]) > value)
{
this.diplomacy[idx] = value;
cmpPlayer.SetDiplomacyIndex(this.playerID, value);
}
else
{
this.diplomacy[idx] = value;
}
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
}
}
else
{
// Break alliance or declare war (worsening of relations is mutual)
if (Math.min(this.diplomacy[idx],cmpPlayer.diplomacy[this.playerID]) > value)
{
// This is duplicated because otherwise we get too much recursion
this.diplomacy[idx] = value;
cmpPlayer.SetDiplomacyIndex(this.playerID, value);
}
else
{
this.diplomacy[idx] = value;
}
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
}
};
Player.prototype.UpdateSharedLos = function()
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (!cmpRangeManager)
return;
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return;
var sharedLos = [];
for (var i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
if (this.IsMutualAlly(i))
sharedLos.push(i);
cmpRangeManager.SetSharedLos(this.playerID, sharedLos);
};
Player.prototype.GetFormations = function()
{
return this.formations;
};
Player.prototype.SetFormations = function(formations)
{
this.formations = formations;
};
Player.prototype.GetStartingCameraPos = function()
{
return this.startCam.position;
};
Player.prototype.GetStartingCameraRot = function()
{
return this.startCam.rotation;
};
Player.prototype.SetStartingCamera = function(pos, rot)
{
this.startCam = {"position": pos, "rotation": rot};
};
Player.prototype.HasStartingCamera = function()
{
return (this.startCam !== undefined);
};
Player.prototype.SetControlAllUnits = function(c)
{
this.controlAllUnits = c;
};
Player.prototype.CanControlAllUnits = function()
{
return this.controlAllUnits;
};
Player.prototype.SetAI = function(flag)
{
this.isAI = flag;
};
Player.prototype.IsAI = function()
{
return this.isAI;
};
Player.prototype.SetAlly = function(id)
{
this.SetDiplomacyIndex(id, 1);
};
/**
* Check if given player is our ally
*/
Player.prototype.IsAlly = function(id)
{
return this.diplomacy[id] > 0;
};
/**
* Check if given player is our ally, and we are its ally
*/
Player.prototype.IsMutualAlly = function(id)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return false;
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(id), IID_Player);
return this.IsAlly(id) && cmpPlayer && cmpPlayer.IsAlly(this.playerID);
};
Player.prototype.SetEnemy = function(id)
{
this.SetDiplomacyIndex(id, -1);
};
/**
* Get all enemies of a given player.
*/
Player.prototype.GetEnemies = function()
{
var enemies = [];
for (var i = 0; i < this.diplomacy.length; i++)
if (this.diplomacy[i] < 0)
enemies.push(i);
return enemies;
};
/**
* Check if given player is our enemy
*/
Player.prototype.IsEnemy = function(id)
{
return this.diplomacy[id] < 0;
};
Player.prototype.SetNeutral = function(id)
{
this.SetDiplomacyIndex(id, 0);
};
/**
* Check if given player is neutral
*/
Player.prototype.IsNeutral = function(id)
{
return this.diplomacy[id] == 0;
};
/**
* Keep track of population effects of all entities that
* become owned or unowned by this player
*/
Player.prototype.OnGlobalOwnershipChanged = function(msg)
{
if (msg.from != this.playerID && msg.to != this.playerID)
return;
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
var cmpCost = Engine.QueryInterface(msg.entity, IID_Cost);
if (msg.from == this.playerID)
{
if (cmpIdentity && cmpIdentity.HasClass("ConquestCritical"))
this.conquestCriticalEntitiesCount--;
if (this.conquestCriticalEntitiesCount == 0) // end game when needed
Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager).CheckConquestCriticalEntities();
if (cmpCost)
this.popUsed -= cmpCost.GetPopCost();
if (cmpIdentity && cmpIdentity.HasClass("Hero"))
{
//Remove from Heroes list
var index = this.heroes.indexOf(msg.entity);
if (index >= 0)
this.heroes.splice(index, 1);
}
}
if (msg.to == this.playerID)
{
if (cmpIdentity && cmpIdentity.HasClass("ConquestCritical"))
this.conquestCriticalEntitiesCount++;
if (cmpCost)
this.popUsed += cmpCost.GetPopCost();
if (cmpIdentity && cmpIdentity.HasClass("Hero"))
this.heroes.push(msg.entity);
}
};
Player.prototype.OnPlayerDefeated = function(msg)
{
this.state = "defeated";
// TODO: Tribute all resources to this player's active allies (if any)
// Reassign all player's entities to Gaia
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var entities = cmpRangeManager.GetEntitiesByPlayer(this.playerID);
// The ownership change is done in two steps so that entities don't hit idle
// (and thus possibly look for "enemies" to attack) before nearby allies get
// converted to Gaia as well.
for each (var entity in entities)
{
var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
cmpOwnership.SetOwnerQuiet(0);
}
// With the real ownership change complete, send OwnershipChanged messages.
for each (var entity in entities)
Engine.PostMessage(entity, MT_OwnershipChanged, { "entity": entity,
"from": this.playerID, "to": 0 });
// Reveal the map for this player.
cmpRangeManager.SetLosRevealAll(this.playerID, true);
// Send a chat message notifying of the player's defeat.
var notification = {"type": "defeat", "player": this.playerID};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
};
Player.prototype.OnDiplomacyChanged = function()
{
this.UpdateSharedLos();
Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager).CheckConquestCriticalEntities();
};
Player.prototype.SetCheatsEnabled = function(flag)
{
this.cheatsEnabled = flag;
};
Player.prototype.GetCheatsEnabled = function()
{
return this.cheatsEnabled;
};
Player.prototype.SetCheatTimeMultiplier = function(time)
{
this.cheatTimeMultiplier = time;
};
Player.prototype.GetCheatTimeMultiplier = function()
{
return this.cheatTimeMultiplier;
};
Player.prototype.TributeResource = function(player, amounts)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return;
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(player), IID_Player);
if (!cmpPlayer)
return;
if (this.state != "active" || cmpPlayer.state != "active")
return;
if (!this.SubtractResourcesOrNotify(amounts))
return;
cmpPlayer.AddResources(amounts);
var total = Object.keys(amounts).reduce(function (sum, type){ return sum + amounts[type]; }, 0);
var cmpOurStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
if (cmpOurStatisticsTracker)
cmpOurStatisticsTracker.IncreaseTributesSentCounter(total);
var cmpTheirStatisticsTracker = QueryPlayerIDInterface(player, IID_StatisticsTracker);
if (cmpTheirStatisticsTracker)
cmpTheirStatisticsTracker.IncreaseTributesReceivedCounter(total);
var notification = {"type": "tribute", "player": player, "player1": this.playerID, "amounts": amounts};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
if (cmpGUIInterface)
cmpGUIInterface.PushNotification(notification);
};
Engine.RegisterComponentType(IID_Player, "Player", Player);