Index: ps/trunk/binaries/data/mods/public/gui/session/session.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 22808) +++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 22809) @@ -1,1719 +1,1719 @@ const g_IsReplay = Engine.IsVisualReplay(); const g_CivData = loadCivData(false, true); const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire); const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes); const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes); const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities); const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources); const g_VictoryDurations = prepareForDropdown(g_Settings && g_Settings.VictoryDurations); const g_VictoryConditions = g_Settings && g_Settings.VictoryConditions; var g_GameSpeeds; /** * Whether to display diplomacy colors (where players see self/ally/neutral/enemy each in different colors and * observers see each team in a different color) or regular player colors. */ var g_DiplomacyColorsToggle = false; /** * The array of displayed player colors (either the diplomacy color or regular color for each player). */ var g_DisplayedPlayerColors; /** * Colors to flash when pop limit reached. */ var g_DefaultPopulationColor = "white"; var g_PopulationAlertColor = "orange"; /** * Seen in the tooltip of the top panel. */ var g_ResourceTitleFont = "sans-bold-16"; /** * A random file will be played. TODO: more variety */ var g_Ambient = ["audio/ambient/dayscape/day_temperate_gen_03.ogg"]; /** * Map, player and match settings set in gamesetup. */ const g_GameAttributes = deepfreeze(Engine.GetInitAttributes()); /** * True if this is a multiplayer game. */ const g_IsNetworked = Engine.HasNetClient(); /** * Is this user in control of game settings (i.e. is a network server, or offline player). */ var g_IsController = !g_IsNetworked || Engine.HasNetServer(); /** * Whether we have finished the synchronization and * can start showing simulation related message boxes. */ var g_IsNetworkedActive = false; /** * True if the connection to the server has been lost. */ var g_Disconnected = false; /** * True if the current user has observer capabilities. */ var g_IsObserver = false; /** * True if the current user has rejoined (or joined the game after it started). */ var g_HasRejoined = false; /** * Shows a message box asking the user to leave if "won" or "defeated". */ var g_ConfirmExit = false; /** * True if the current player has paused the game explicitly. */ var g_Paused = false; /** * The list of GUIDs of players who have currently paused the game, if the game is networked. */ var g_PausingClients = []; /** * The playerID selected in the change perspective tool. */ var g_ViewedPlayer = Engine.GetPlayerID(); /** * True if the camera should focus on attacks and player commands * and select the affected units. */ var g_FollowPlayer = false; /** * Cache the basic player data (name, civ, color). */ var g_Players = []; /** * Last time when onTick was called(). * Used for animating the main menu. */ var g_LastTickTime = Date.now(); /** * Recalculate which units have their status bars shown with this frequency in milliseconds. */ var g_StatusBarUpdate = 200; /** * For restoring selection, order and filters when returning to the replay menu */ var g_ReplaySelectionData; /** * Remembers which clients are assigned to which player slots. * The keys are guids or "local" in Singleplayer. */ var g_PlayerAssignments; var g_DeveloperOverlay; /** * Whether the entire UI should be hidden (useful for promotional screenshots). * Can be toggled with a hotkey. */ var g_ShowGUI = true; /** * Whether status bars should be shown for all of the player's units. */ var g_ShowAllStatusBars = false; /** * Blink the population counter if the player can't train more units. */ var g_IsTrainingBlocked = false; /** * Cache of simulation state and template data (apart from TechnologyData, updated on every simulation update). */ var g_SimState; var g_EntityStates = {}; var g_TemplateData = {}; var g_TechnologyData = {}; var g_ResourceData = new Resources(); /** * Top coordinate of the research list. * Changes depending on the number of displayed counters. */ var g_ResearchListTop = 4; /** * List of additional entities to highlight. */ var g_ShowGuarding = false; var g_ShowGuarded = false; var g_AdditionalHighlight = []; /** * Display data of the current players entities shown in the top panel. */ var g_PanelEntities = []; /** * Order in which the panel entities are shown. */ var g_PanelEntityOrder = ["Hero", "Relic"]; /** * Unit classes to be checked for the idle-worker-hotkey. */ -var g_WorkerTypes = ["FemaleCitizen", "Trader", "FishingBoat", "CitizenSoldier"]; +var g_WorkerTypes = ["FemaleCitizen", "Trader", "FishingBoat", "Citizen"]; /** * Unit classes to be checked for the military-only-selection modifier and for the idle-warrior-hotkey. */ var g_MilitaryTypes = ["Melee", "Ranged"]; function GetSimState() { if (!g_SimState) g_SimState = deepfreeze(Engine.GuiInterfaceCall("GetSimulationState")); return g_SimState; } function GetMultipleEntityStates(ents) { if (!ents.length) return null; let entityStates = Engine.GuiInterfaceCall("GetMultipleEntityStates", ents); for (let item of entityStates) g_EntityStates[item.entId] = item.state && deepfreeze(item.state); return entityStates; } function GetEntityState(entId) { if (!g_EntityStates[entId]) { let entityState = Engine.GuiInterfaceCall("GetEntityState", entId); g_EntityStates[entId] = entityState && deepfreeze(entityState); } return g_EntityStates[entId]; } function GetTemplateData(templateName) { if (!(templateName in g_TemplateData)) { let template = Engine.GuiInterfaceCall("GetTemplateData", templateName); translateObjectKeys(template, ["specific", "generic", "tooltip"]); g_TemplateData[templateName] = deepfreeze(template); } return g_TemplateData[templateName]; } function GetTechnologyData(technologyName, civ) { if (!g_TechnologyData[civ]) g_TechnologyData[civ] = {}; if (!(technologyName in g_TechnologyData[civ])) { let template = GetTechnologyDataHelper(TechnologyTemplates.Get(technologyName), civ, g_ResourceData); translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]); g_TechnologyData[civ][technologyName] = deepfreeze(template); } return g_TechnologyData[civ][technologyName]; } function init(initData, hotloadData) { if (!g_Settings) { Engine.EndGame(); Engine.SwitchGuiPage("page_pregame.xml"); return; } // Fallback used by atlas g_PlayerAssignments = initData ? initData.playerAssignments : { "local": { "player": 1 } }; // Fallback used by atlas and autostart games if (g_PlayerAssignments.local && !g_PlayerAssignments.local.name) g_PlayerAssignments.local.name = singleplayerName(); if (initData) { g_ReplaySelectionData = initData.replaySelectionData; g_HasRejoined = initData.isRejoining; if (initData.savedGUIData) restoreSavedGameData(initData.savedGUIData); } g_DeveloperOverlay = new DeveloperOverlay(); LoadModificationTemplates(); updatePlayerData(); initializeMusic(); // before changing the perspective initGUIObjects(); if (hotloadData) g_Selection.selected = hotloadData.selection; sendLobbyPlayerlistUpdate(); onSimulationUpdate(); setTimeout(displayGamestateNotifications, 1000); } function initGUIObjects() { initMenu(); updateGameSpeedControl(); resizeDiplomacyDialog(); resizeTradeDialog(); initBarterButtons(); initPanelEntities(); initViewedPlayerDropdown(); initChatWindow(); Engine.SetBoundingBoxDebugOverlay(false); updateEnabledRangeOverlayTypes(); } function updatePlayerData() { let simState = GetSimState(); if (!simState) return; let playerData = []; for (let i = 0; i < simState.players.length; ++i) { let playerState = simState.players[i]; playerData.push({ "name": playerState.name, "civ": playerState.civ, "color": { "r": playerState.color.r * 255, "g": playerState.color.g * 255, "b": playerState.color.b * 255, "a": playerState.color.a * 255 }, "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 "offline": g_Players[i] && !!g_Players[i].offline }); } for (let guid in g_PlayerAssignments) { let playerID = g_PlayerAssignments[guid].player; if (!playerData[playerID]) continue; playerData[playerID].guid = guid; playerData[playerID].name = g_PlayerAssignments[guid].name; } g_Players = playerData; } function updateDiplomacyColorsButton() { g_DiplomacyColorsToggle = !g_DiplomacyColorsToggle; let diplomacyColorsButton = Engine.GetGUIObjectByName("diplomacyColorsButton"); diplomacyColorsButton.sprite = g_DiplomacyColorsToggle ? "stretched:session/minimap-diplomacy-on.png" : "stretched:session/minimap-diplomacy-off.png"; diplomacyColorsButton.sprite_over = g_DiplomacyColorsToggle ? "stretched:session/minimap-diplomacy-on-highlight.png" : "stretched:session/minimap-diplomacy-off-highlight.png"; Engine.GetGUIObjectByName("diplomacyColorsWindowButtonIcon").sprite = g_DiplomacyColorsToggle ? "stretched:session/icons/diplomacy-on.png" : "stretched:session/icons/diplomacy.png"; updateDisplayedPlayerColors(); } /** * Updates the displayed colors of players in the simulation and GUI. */ function updateDisplayedPlayerColors() { if (g_DiplomacyColorsToggle) { let getDiplomacyColor = stance => guiToRgbColor(Engine.ConfigDB_GetValue("user", "gui.session.diplomacycolors." + stance)) || guiToRgbColor(Engine.ConfigDB_GetValue("default", "gui.session.diplomacycolors." + stance)); let teamRepresentatives = {}; for (let i = 1; i < g_Players.length; ++i) if (g_ViewedPlayer <= 0) { // Observers and gaia see team colors let team = g_Players[i].team; g_DisplayedPlayerColors[i] = g_Players[teamRepresentatives[team] || i].color; if (team != -1 && !teamRepresentatives[team]) teamRepresentatives[team] = i; } else // Players see colors depending on diplomacy g_DisplayedPlayerColors[i] = g_ViewedPlayer == i ? getDiplomacyColor("self") : g_Players[g_ViewedPlayer].isAlly[i] ? getDiplomacyColor("ally") : g_Players[g_ViewedPlayer].isNeutral[i] ? getDiplomacyColor("neutral") : getDiplomacyColor("enemy"); g_DisplayedPlayerColors[0] = g_Players[0].color; } else g_DisplayedPlayerColors = g_Players.map(player => player.color); Engine.GuiInterfaceCall("UpdateDisplayedPlayerColors", { "displayedPlayerColors": g_DisplayedPlayerColors, "displayDiplomacyColors": g_DiplomacyColorsToggle, "showAllStatusBars": g_ShowAllStatusBars, "selected": g_Selection.toList() }); updateGUIObjects(); } /** * Depends on the current player (g_IsObserver). */ function updateHotkeyTooltips() { Engine.GetGUIObjectByName("chatInput").tooltip = translateWithContext("chat input", "Type the message to send.") + "\n" + colorizeAutocompleteHotkey() + colorizeHotkey("\n" + translate("Press %(hotkey)s to open the public chat."), "chat") + colorizeHotkey( "\n" + (g_IsObserver ? translate("Press %(hotkey)s to open the observer chat.") : translate("Press %(hotkey)s to open the ally chat.")), "teamchat") + colorizeHotkey("\n" + translate("Press %(hotkey)s to open the previously selected private chat."), "privatechat"); Engine.GetGUIObjectByName("idleWorkerButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "selection.idleworker") + translate("Find idle worker"); Engine.GetGUIObjectByName("diplomacyColorsButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.diplomacycolors") + translate("Toggle Diplomacy Colors"); Engine.GetGUIObjectByName("diplomacyColorsWindowButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.diplomacycolors") + translate("Toggle Diplomacy Colors"); Engine.GetGUIObjectByName("diplomacyButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.gui.diplomacy.toggle") + translate("Diplomacy"); Engine.GetGUIObjectByName("tradeButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.gui.barter.toggle") + translate("Barter & Trade"); Engine.GetGUIObjectByName("tradeHelp").tooltip = colorizeHotkey( translate("Select one type of goods you want to modify by clicking on it, and then use the arrows of the other types to modify their shares. You can also press %(hotkey)s while selecting one type of goods to bring its share to 100%%."), "session.fulltradeswap"); Engine.GetGUIObjectByName("barterHelp").tooltip = sprintf( translate("Start by selecting the resource you wish to sell from the upper row. For each time the lower buttons are pressed, %(quantity)s of the upper resource will be sold for the displayed quantity of the lower. Press and hold %(hotkey)s to temporarily multiply the traded amount by %(multiplier)s."), { "quantity": g_BarterResourceSellQuantity, "hotkey": colorizeHotkey("%(hotkey)s", "session.massbarter"), "multiplier": g_BarterMultiplier }); Engine.GetGUIObjectByName("objectivesButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.gui.objectives.toggle") + translate("Objectives"); } function initPanelEntities() { Engine.GetGUIObjectByName("panelEntityPanel").children.forEach((button, slot) => { button.onPress = function() { let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot); if (!panelEnt) return; if (!Engine.HotkeyIsPressed("selection.add")) g_Selection.reset(); g_Selection.addList([panelEnt.ent]); }; button.onDoublePress = function() { let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot); if (panelEnt) selectAndMoveTo(getEntityOrHolder(panelEnt.ent)); }; }); } /** * Returns the entity itself except when garrisoned where it returns its garrisonHolder */ function getEntityOrHolder(ent) { let entState = GetEntityState(ent); if (entState && !entState.position && entState.unitAI && entState.unitAI.orders.length && entState.unitAI.orders[0].type == "Garrison") return getEntityOrHolder(entState.unitAI.orders[0].data.target); return ent; } function initializeMusic() { initMusic(); if (g_ViewedPlayer != -1 && g_CivData[g_Players[g_ViewedPlayer].civ].Music) global.music.storeTracks(g_CivData[g_Players[g_ViewedPlayer].civ].Music); global.music.setState(global.music.states.PEACE); playAmbient(); } function initViewedPlayerDropdown() { g_DisplayedPlayerColors = g_Players.map(player => player.color); updateViewedPlayerDropdown(); // Select "observer" in the view player dropdown when rejoining as a defeated player let player = g_Players[Engine.GetPlayerID()]; Engine.GetGUIObjectByName("viewPlayer").selected = player && player.state == "defeated" ? 0 : Engine.GetPlayerID() + 1; } function updateViewedPlayerDropdown() { let viewPlayer = Engine.GetGUIObjectByName("viewPlayer"); viewPlayer.list_data = [-1].concat(g_Players.map((player, i) => i)); viewPlayer.list = [translate("Observer")].concat(g_Players.map( (player, i) => colorizePlayernameHelper("■", i) + " " + player.name )); } /** * Change perspective tool. * Shown to observers or when enabling the developers option. */ function selectViewPlayer(playerID) { if (playerID < -1 || playerID > g_Players.length - 1) return; if (g_ShowAllStatusBars) recalculateStatusBarDisplay(true); g_IsObserver = isPlayerObserver(Engine.GetPlayerID()); if (g_IsObserver || g_DeveloperOverlay.isChangePerspective()) { if (g_ViewedPlayer != playerID) clearSelection(); g_ViewedPlayer = playerID; } if (g_DeveloperOverlay.isChangePerspective()) { Engine.SetPlayerID(g_ViewedPlayer); g_IsObserver = isPlayerObserver(g_ViewedPlayer); } Engine.SetViewedPlayer(g_ViewedPlayer); updateDisplayedPlayerColors(); updateTopPanel(); updateChatAddressees(); updateHotkeyTooltips(); // Update GUI and clear player-dependent cache g_TemplateData = {}; Engine.GuiInterfaceCall("ResetTemplateModified"); onSimulationUpdate(); if (g_IsDiplomacyOpen) openDiplomacy(); if (g_IsTradeOpen) openTrade(); } /** * Returns true if the player with that ID is in observermode. */ function isPlayerObserver(playerID) { let playerStates = GetSimState().players; return !playerStates[playerID] || playerStates[playerID].state != "active"; } /** * Returns true if the current user can issue commands for that player. */ function controlsPlayer(playerID) { let playerStates = GetSimState().players; return !!playerStates[Engine.GetPlayerID()] && playerStates[Engine.GetPlayerID()].controlsAll || Engine.GetPlayerID() == playerID && !!playerStates[playerID] && playerStates[playerID].state != "defeated"; } /** * Called when one or more players have won or were defeated. * * @param {array} - IDs of the players who have won or were defeated. * @param {object} - a plural string stating the victory reason. * @param {boolean} - whether these players have won or lost. */ function playersFinished(players, victoryString, won) { addChatMessage({ "type": "defeat-victory", "message": victoryString, "players": players }); if (players.indexOf(Engine.GetPlayerID()) != -1) reportGame(); sendLobbyPlayerlistUpdate(); updatePlayerData(); updateChatAddressees(); updateGameSpeedControl(); if (players.indexOf(g_ViewedPlayer) == -1) return; // Select "observer" item on loss. On win enable observermode without changing perspective Engine.GetGUIObjectByName("viewPlayer").selected = won ? g_ViewedPlayer + 1 : 0; if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning()) return; global.music.setState( won ? global.music.states.VICTORY : global.music.states.DEFEAT ); g_ConfirmExit = won ? "won" : "defeated"; } /** * Sets civ icon for the currently viewed player. * Hides most gui objects for observers. */ function updateTopPanel() { let isPlayer = g_ViewedPlayer > 0; let civIcon = Engine.GetGUIObjectByName("civIcon"); civIcon.hidden = !isPlayer; if (isPlayer) { civIcon.sprite = "stretched:" + g_CivData[g_Players[g_ViewedPlayer].civ].Emblem; Engine.GetGUIObjectByName("civIconOverlay").tooltip = sprintf( translate("%(civ)s\n%(hotkey_civinfo)s / %(hotkey_structree)s: View History / Structure Tree\nLast opened will be reopened on click."), { "civ": setStringTags(g_CivData[g_Players[g_ViewedPlayer].civ].Name, { "font": "sans-bold-stroke-14" }), "hotkey_civinfo": colorizeHotkey("%(hotkey)s", "civinfo"), "hotkey_structree": colorizeHotkey("%(hotkey)s", "structree") }); } // Following gaia can be interesting on scripted maps Engine.GetGUIObjectByName("optionFollowPlayer").hidden = !g_IsObserver || g_ViewedPlayer == -1; let viewPlayer = Engine.GetGUIObjectByName("viewPlayer"); viewPlayer.hidden = !g_IsObserver && !g_DeveloperOverlay.isChangePerspective(); let followPlayerLabel = Engine.GetGUIObjectByName("followPlayerLabel"); followPlayerLabel.hidden = Engine.GetTextWidth(followPlayerLabel.font, followPlayerLabel.caption + " ") + followPlayerLabel.getComputedSize().left > viewPlayer.getComputedSize().left; let resCodes = g_ResourceData.GetCodes(); let r = 0; for (let res of resCodes) { if (!Engine.GetGUIObjectByName("resource[" + r + "]")) { warn("Current GUI limits prevent displaying more than " + r + " resources in the top panel!"); break; } Engine.GetGUIObjectByName("resource[" + r + "]_icon").sprite = "stretched:session/icons/resources/" + res + ".png"; Engine.GetGUIObjectByName("resource[" + r + "]").hidden = !isPlayer; ++r; } horizontallySpaceObjects("resourceCounts", 5); hideRemaining("resourceCounts", r); let resPop = Engine.GetGUIObjectByName("population"); let resPopSize = resPop.size; resPopSize.left = Engine.GetGUIObjectByName("resource[" + (r - 1) + "]").size.right; resPop.size = resPopSize; Engine.GetGUIObjectByName("population").hidden = !isPlayer; Engine.GetGUIObjectByName("diplomacyButton").hidden = !isPlayer; Engine.GetGUIObjectByName("tradeButton").hidden = !isPlayer; Engine.GetGUIObjectByName("observerText").hidden = isPlayer; let alphaLabel = Engine.GetGUIObjectByName("alphaLabel"); alphaLabel.hidden = isPlayer && !viewPlayer.hidden; alphaLabel.size = isPlayer ? "50%+44 0 100%-283 100%" : "155 0 85%-279 100%"; Engine.GetGUIObjectByName("pauseButton").enabled = !g_IsObserver || !g_IsNetworked || g_IsController; Engine.GetGUIObjectByName("menuResignButton").enabled = !g_IsObserver; Engine.GetGUIObjectByName("lobbyButton").enabled = Engine.HasXmppClient(); } /** * Resign a player. * @param leaveGameAfterResign If player is quitting after resignation. */ function resignGame(leaveGameAfterResign) { if (g_IsObserver || g_Disconnected) return; Engine.PostNetworkCommand({ "type": "resign" }); if (!leaveGameAfterResign) resumeGame(true); } /** * Leave the game * @param willRejoin If player is going to be rejoining a networked game. */ function leaveGame(willRejoin) { if (!willRejoin && !g_IsObserver) resignGame(true); // Before ending the game let replayDirectory = Engine.GetCurrentReplayDirectory(); let simData = getReplayMetadata(); let playerID = Engine.GetPlayerID(); Engine.EndGame(); // After the replay file was closed in EndGame // Done here to keep EndGame small if (!g_IsReplay) Engine.AddReplayToCache(replayDirectory); if (g_IsController && Engine.HasXmppClient()) Engine.SendUnregisterGame(); Engine.SwitchGuiPage("page_summary.xml", { "sim": simData, "gui": { "dialog": false, "assignedPlayer": playerID, "disconnected": g_Disconnected, "isReplay": g_IsReplay, "replayDirectory": !g_HasRejoined && replayDirectory, "replaySelectionData": g_ReplaySelectionData } }); } // Return some data that we'll use when hotloading this file after changes function getHotloadData() { return { "selection": g_Selection.selected }; } function getSavedGameData() { return { "groups": g_Groups.groups }; } 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 (let groupNumber in data.groups) { g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups; g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents; } updateGroups(); } /** * Called every frame. */ function onTick() { if (!g_Settings) return; let now = Date.now(); let tickLength = now - g_LastTickTime; g_LastTickTime = now; handleNetMessages(); updateCursorAndTooltip(); if (g_Selection.dirty) { g_Selection.dirty = false; // When selection changed, get the entityStates of new entities GetMultipleEntityStates(g_Selection.toList().filter(entId => !g_EntityStates[entId])); updateGUIObjects(); // Display rally points for selected buildings if (Engine.GetPlayerID() != -1) Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() }); } else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength) recalculateStatusBarDisplay(); updateTimers(); updateMenuPosition(tickLength); // When training is blocked, flash population (alternates color every 500msec) Engine.GetGUIObjectByName("resourcePop").textcolor = g_IsTrainingBlocked && now % 1000 < 500 ? g_PopulationAlertColor : g_DefaultPopulationColor; Engine.GuiInterfaceCall("ClearRenamedEntities"); } function onWindowResized() { // Update followPlayerLabel updateTopPanel(); resizeChatWindow(); } function changeGameSpeed(speed) { if (!g_IsNetworked) Engine.SetSimRate(speed); } function updateIdleWorkerButton() { Engine.GetGUIObjectByName("idleWorkerButton").enabled = Engine.GuiInterfaceCall("HasIdleUnits", { "viewedPlayer": g_ViewedPlayer, "idleClasses": g_WorkerTypes, "excludeUnits": [] }); } function onSimulationUpdate() { // Templates change depending on technologies and auras, so they have to be reloaded after such a change. // g_TechnologyData data never changes, so it shouldn't be deleted. g_EntityStates = {}; if (Engine.GuiInterfaceCall("IsTemplateModified")) { g_TemplateData = {}; Engine.GuiInterfaceCall("ResetTemplateModified"); } g_SimState = undefined; if (!GetSimState()) return; GetMultipleEntityStates(g_Selection.toList()); updateCinemaPath(); handleNotifications(); updateGUIObjects(); if (g_ConfirmExit) confirmExit(); } /** * Don't show the message box before all playerstate changes are processed. */ function confirmExit() { if (g_IsNetworked && !g_IsNetworkedActive) return; closeOpenDialogs(); // Don't ask for exit if other humans are still playing let askExit = !Engine.HasNetServer() || g_Players.every((player, i) => i == 0 || player.state != "active" || g_GameAttributes.settings.PlayerData[i].AI != ""); let subject = g_PlayerStateMessages[g_ConfirmExit]; if (askExit) subject += "\n" + translate("Do you want to quit?"); messageBox( 400, 200, subject, g_ConfirmExit == "won" ? translate("VICTORIOUS!") : translate("DEFEATED!"), askExit ? [translate("No"), translate("Yes")] : [translate("OK")], askExit ? [resumeGame, leaveGame] : [resumeGame] ); g_ConfirmExit = false; } function toggleGUI() { g_ShowGUI = !g_ShowGUI; updateCinemaPath(); } function updateCinemaPath() { let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected; Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath; Engine.Renderer_SetSilhouettesEnabled(!isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true"); } function updateGUIObjects() { g_Selection.update(); if (g_ShowAllStatusBars) recalculateStatusBarDisplay(); if (g_ShowGuarding || g_ShowGuarded) updateAdditionalHighlight(); updatePanelEntities(); displayPanelEntities(); updateGroups(); updatePlayerDisplay(); updateResearchDisplay(); updateSelectionDetails(); updateBuildingPlacementPreview(); updateTimeNotifications(); updateIdleWorkerButton(); if (g_IsTradeOpen) { updateTraderTexts(); updateBarterButtons(); } if (g_ViewedPlayer > 0) { let playerState = GetSimState().players[g_ViewedPlayer]; g_DeveloperOverlay.setControlAll(playerState && playerState.controlsAll); } if (!g_IsObserver) { // Update music state on basis of battle state. let battleState = Engine.GuiInterfaceCall("GetBattleState", g_ViewedPlayer); if (battleState) global.music.setState(global.music.states[battleState]); } updateViewedPlayerDropdown(); updateDiplomacy(); g_DeveloperOverlay.update(); } function saveResPopTooltipSort() { Engine.ConfigDB_CreateAndWriteValueToFile("user", "gui.session.respoptooltipsort", String((+Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort") + 2) % 3 - 1), "config/user.cfg"); } function onReplayFinished() { closeOpenDialogs(); pauseGame(); messageBox(400, 200, translateWithContext("replayFinished", "The replay has finished. Do you want to quit?"), translateWithContext("replayFinished", "Confirmation"), [translateWithContext("replayFinished", "No"), translateWithContext("replayFinished", "Yes")], [resumeGame, leaveGame]); } /** * 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 let statusBar = Engine.GetGUIObjectByName(nameOfBar); if (!statusBar) return; let healthSize = statusBar.size; let 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; statusBar.size = healthSize; } function updatePanelEntities() { let panelEnts = g_ViewedPlayer == -1 ? GetSimState().players.reduce((ents, pState) => ents.concat(pState.panelEntities), []) : GetSimState().players[g_ViewedPlayer].panelEntities; g_PanelEntities = g_PanelEntities.filter(panelEnt => panelEnts.find(ent => ent == panelEnt.ent)); for (let ent of panelEnts) { let panelEntState = GetEntityState(ent); let template = GetTemplateData(panelEntState.template); let panelEnt = g_PanelEntities.find(pEnt => ent == pEnt.ent); if (!panelEnt) { panelEnt = { "ent": ent, "tooltip": undefined, "sprite": "stretched:session/portraits/" + template.icon, "maxHitpoints": undefined, "currentHitpoints": panelEntState.hitpoints, "previousHitpoints": undefined }; g_PanelEntities.push(panelEnt); } panelEnt.tooltip = createPanelEntityTooltip(panelEntState, template); panelEnt.previousHitpoints = panelEnt.currentHitpoints; panelEnt.currentHitpoints = panelEntState.hitpoints; panelEnt.maxHitpoints = panelEntState.maxHitpoints; } let panelEntIndex = ent => g_PanelEntityOrder.findIndex(entClass => GetEntityState(ent).identity.classes.indexOf(entClass) != -1); g_PanelEntities = g_PanelEntities.sort((panelEntA, panelEntB) => panelEntIndex(panelEntA.ent) - panelEntIndex(panelEntB.ent)); } function createPanelEntityTooltip(panelEntState, template) { let getPanelEntNameTooltip = panelEntState => "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]"; return [ getPanelEntNameTooltip, getCurrentHealthTooltip, getAttackTooltip, getArmorTooltip, getEntityTooltip, getAurasTooltip ].map(tooltip => tooltip(panelEntState)).filter(tip => tip).join("\n"); } function displayPanelEntities() { let buttons = Engine.GetGUIObjectByName("panelEntityPanel").children; buttons.forEach((button, slot) => { if (button.hidden || g_PanelEntities.some(ent => ent.slot !== undefined && ent.slot == slot)) return; button.hidden = true; stopColorFade("panelEntityHitOverlay[" + slot + "]"); }); // The slot identifies the button, displayIndex determines its position. for (let displayIndex = 0; displayIndex < Math.min(g_PanelEntities.length, buttons.length); ++displayIndex) { let panelEnt = g_PanelEntities[displayIndex]; // Find the first unused slot if new, otherwise reuse previous. let slot = panelEnt.slot === undefined ? buttons.findIndex(button => button.hidden) : panelEnt.slot; let panelEntButton = Engine.GetGUIObjectByName("panelEntityButton[" + slot + "]"); panelEntButton.tooltip = panelEnt.tooltip; updateGUIStatusBar("panelEntityHealthBar[" + slot + "]", panelEnt.currentHitpoints, panelEnt.maxHitpoints); if (panelEnt.slot === undefined) { let panelEntImage = Engine.GetGUIObjectByName("panelEntityImage[" + slot + "]"); panelEntImage.sprite = panelEnt.sprite; panelEntButton.hidden = false; panelEnt.slot = slot; } // If the health of the panelEnt changed since the last update, trigger the animation. if (panelEnt.previousHitpoints > panelEnt.currentHitpoints) startColorFade("panelEntityHitOverlay[" + slot + "]", 100, 0, colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit); // TODO: Instead of instant position changes, animate button movement. setPanelObjectPosition(panelEntButton, displayIndex, buttons.length); } } function updateGroups() { g_Groups.update(); // Determine the sum of the costs of a given template let getCostSum = (ent) => { let cost = GetTemplateData(GetEntityState(ent).template).cost; return cost ? Object.keys(cost).map(key => cost[key]).reduce((sum, cur) => sum + cur) : 0; }; for (let i in Engine.GetGUIObjectByName("unitGroupPanel").children) { Engine.GetGUIObjectByName("unitGroupLabel[" + i + "]").caption = i; let button = Engine.GetGUIObjectByName("unitGroupButton[" + i + "]"); button.hidden = g_Groups.groups[i].getTotalCount() == 0; 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); // Choose the icon of the most common template (or the most costly if it's not unique) if (g_Groups.groups[i].getTotalCount() > 0) { let icon = GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre, cur) => { if (pre.ents.length == cur.ents.length) return getCostSum(pre.ents[0]) > getCostSum(cur.ents[0]) ? pre : cur; return pre.ents.length > cur.ents.length ? pre : cur; }).ents[0]).template).icon; Engine.GetGUIObjectByName("unitGroupIcon[" + i + "]").sprite = icon ? ("stretched:session/portraits/" + icon) : "groupsIcon"; } setPanelObjectPosition(button, i, 1); } } /** * Create ally player stat tooltip. * @param {string} resource - Resource type, on which values will be sorted. * @param {object} playerStates - Playerstates from players whos stats are viewed in the tooltip. * @param {number} sort - 0 no order, -1 descending, 1 ascending order. * @returns {string} Tooltip string. */ function getAllyStatTooltip(resource, playerStates, sort) { let tooltip = []; for (let player in playerStates) tooltip.push({ "playername": colorizePlayernameHelper("■", player) + " " + g_Players[player].name, "statValue": resource == "pop" ? sprintf(translate("%(popCount)s/%(popLimit)s/%(popMax)s"), playerStates[player]) : Math.round(playerStates[player].resourceCounts[resource]), "orderValue": resource == "pop" ? playerStates[player].popCount : Math.round(playerStates[player].resourceCounts[resource]) }); if (sort) tooltip.sort((a, b) => sort * (b.orderValue - a.orderValue)); return "\n" + tooltip.map(stat => sprintf(translate("%(playername)s: %(statValue)s"), stat)).join("\n"); } function updatePlayerDisplay() { let allPlayerStates = GetSimState().players; let viewedPlayerState = allPlayerStates[g_ViewedPlayer]; let viewablePlayerStates = {}; for (let player in allPlayerStates) if (player != 0 && player != g_ViewedPlayer && g_Players[player].state != "defeated" && (g_IsObserver || viewedPlayerState.hasSharedLos && g_Players[player].isMutualAlly[g_ViewedPlayer])) viewablePlayerStates[player] = allPlayerStates[player]; if (!viewedPlayerState) return; let tooltipSort = +Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort"); let orderHotkeyTooltip = Object.keys(viewablePlayerStates).length <= 1 ? "" : "\n" + sprintf(translate("%(order)s: %(hotkey)s to change order."), { "hotkey": setStringTags("\\[Click]", g_HotkeyTags), "order": tooltipSort == 0 ? translate("Unordered") : tooltipSort == 1 ? translate("Descending") : translate("Ascending") }); let resCodes = g_ResourceData.GetCodes(); for (let r = 0; r < resCodes.length; ++r) { let resourceObj = Engine.GetGUIObjectByName("resource[" + r + "]"); if (!resourceObj) break; let res = resCodes[r]; let tooltip = '[font="' + g_ResourceTitleFont + '"]' + resourceNameFirstWord(res) + '[/font]'; let descr = g_ResourceData.GetResource(res).description; if (descr) tooltip += "\n" + translate(descr); tooltip += orderHotkeyTooltip + getAllyStatTooltip(res, viewablePlayerStates, tooltipSort); resourceObj.tooltip = tooltip; Engine.GetGUIObjectByName("resource[" + r + "]_count").caption = Math.floor(viewedPlayerState.resourceCounts[res]); } Engine.GetGUIObjectByName("resourcePop").caption = sprintf(translate("%(popCount)s/%(popLimit)s"), viewedPlayerState); Engine.GetGUIObjectByName("population").tooltip = translate("Population (current / limit)") + "\n" + sprintf(translate("Maximum population: %(popCap)s"), { "popCap": viewedPlayerState.popMax }) + orderHotkeyTooltip + getAllyStatTooltip("pop", viewablePlayerStates, tooltipSort); g_IsTrainingBlocked = viewedPlayerState.trainingBlocked; } function selectAndMoveTo(ent) { let entState = GetEntityState(ent); if (!entState || !entState.position) return; g_Selection.reset(); g_Selection.addList([ent]); let position = entState.position; Engine.CameraMoveTo(position.x, position.z); } function updateResearchDisplay() { let researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", g_ViewedPlayer); // Set up initial positioning. let buttonSideLength = Engine.GetGUIObjectByName("researchStartedButton[0]").size.right; for (let i = 0; i < 10; ++i) { let button = Engine.GetGUIObjectByName("researchStartedButton[" + i + "]"); let size = button.size; size.top = g_ResearchListTop + (4 + buttonSideLength) * i; size.bottom = size.top + buttonSideLength; button.size = size; } let numButtons = 0; for (let tech in researchStarted) { // Show at most 10 in-progress techs. if (numButtons >= 10) break; let template = GetTechnologyData(tech, g_Players[g_ViewedPlayer].civ); let button = Engine.GetGUIObjectByName("researchStartedButton[" + numButtons + "]"); button.hidden = false; button.tooltip = getEntityNames(template); button.onpress = (function(e) { return function() { selectAndMoveTo(e); }; })(researchStarted[tech].researcher); let icon = "stretched:session/portraits/" + template.icon; Engine.GetGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon; // Scale the progress indicator. let 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; Engine.GetGUIObjectByName("researchStartedTimeRemaining[" + numButtons + "]").caption = Engine.FormatMillisecondsIntoDateStringGMT(researchStarted[tech].timeRemaining, translateWithContext("countdown format", "m:ss")); ++numButtons; } // Hide unused buttons. for (let i = numButtons; i < 10; ++i) Engine.GetGUIObjectByName("researchStartedButton[" + i + "]").hidden = true; } /** * Toggles the display of status bars for all of the player's entities. * * @param {Boolean} remove - Whether to hide all previously shown status bars. */ function recalculateStatusBarDisplay(remove = false) { let entities; if (g_ShowAllStatusBars && !remove) entities = g_ViewedPlayer == -1 ? Engine.PickNonGaiaEntitiesOnScreen() : Engine.PickPlayerEntitiesOnScreen(g_ViewedPlayer); else { let selected = g_Selection.toList(); for (let ent in g_Selection.highlighted) selected.push(g_Selection.highlighted[ent]); // Remove selected entities from the 'all entities' array, // to avoid disabling their status bars. entities = Engine.GuiInterfaceCall( g_ViewedPlayer == -1 ? "GetNonGaiaEntities" : "GetPlayerEntities", { "viewedPlayer": g_ViewedPlayer }).filter(idx => selected.indexOf(idx) == -1); } Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars && !remove, "showRank": Engine.ConfigDB_GetValue("user", "gui.session.rankabovestatusbar") == "true", "showExperience": Engine.ConfigDB_GetValue("user", "gui.session.experiencestatusbar") == "true" }); } /** * Inverts the given configuration boolean and returns the current state. * For example "silhouettes". */ function toggleConfigBool(configName) { let enabled = Engine.ConfigDB_GetValue("user", configName) != "true"; Engine.ConfigDB_CreateAndWriteValueToFile("user", configName, String(enabled), "config/user.cfg"); return enabled; } /** * Toggles the display of range overlays of selected entities for the given range type. * @param {string} type - for example "Auras" */ function toggleRangeOverlay(type) { let enabled = toggleConfigBool("gui.session." + type.toLowerCase() + "range"); Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", { "type": type, "enabled": enabled }); let selected = g_Selection.toList(); for (let ent in g_Selection.highlighted) selected.push(g_Selection.highlighted[ent]); Engine.GuiInterfaceCall("SetRangeOverlays", { "entities": selected, "enabled": enabled }); } function updateEnabledRangeOverlayTypes() { for (let type of ["Attack", "Auras", "Heal"]) Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", { "type": type, "enabled": Engine.ConfigDB_GetValue("user", "gui.session." + type.toLowerCase() + "range") == "true" }); } // Update the additional list of entities to be highlighted. function updateAdditionalHighlight() { let entsAdd = []; // list of entities units to be highlighted let entsRemove = []; let highlighted = g_Selection.toList(); for (let ent in g_Selection.highlighted) highlighted.push(g_Selection.highlighted[ent]); if (g_ShowGuarding) // flag the guarding entities to add in this additional highlight for (let sel in g_Selection.selected) { let state = GetEntityState(g_Selection.selected[sel]); if (!state.guard || !state.guard.entities.length) continue; for (let ent of 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 (let sel in g_Selection.selected) { let state = GetEntityState(g_Selection.selected[sel]); if (!state.unitAI || !state.unitAI.isGuarding) continue; let 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 (let ent of g_AdditionalHighlight) if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1) entsRemove.push(ent); _setHighlight(entsAdd, g_HighlightedAlpha, true); _setHighlight(entsRemove, 0, false); g_AdditionalHighlight = entsAdd; } function playAmbient() { Engine.PlayAmbientSound(pickRandom(g_Ambient), true); } /** * Adds the ingame time and ceasefire counter to the global FPS and * realtime counters shown in the top right corner. */ function appendSessionCounters(counters) { let simState = GetSimState(); if (Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") === "true") { let currentSpeed = Engine.GetSimRate(); if (currentSpeed != 1.0) // Translation: The "x" means "times", with the mathematical meaning of multiplication. counters.push(sprintf(translate("%(time)s (%(speed)sx)"), { "time": timeToString(simState.timeElapsed), "speed": Engine.FormatDecimalNumberIntoString(currentSpeed) })); else counters.push(timeToString(simState.timeElapsed)); } if (simState.ceasefireActive && Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") === "true") counters.push(timeToString(simState.ceasefireTimeRemaining)); g_ResearchListTop = 4 + 14 * counters.length; } /** * Send the current list of players, teams, AIs, observers and defeated/won and offline states to the lobby. * The playerData format from g_GameAttributes is kept to reuse the GUI function presenting the data. */ function sendLobbyPlayerlistUpdate() { if (!g_IsController || !Engine.HasXmppClient()) return; // Extract the relevant player data and minimize packet load let minPlayerData = []; for (let playerID in g_GameAttributes.settings.PlayerData) { if (+playerID == 0) continue; let pData = g_GameAttributes.settings.PlayerData[playerID]; let minPData = { "Name": pData.Name, "Civ": pData.Civ }; if (g_GameAttributes.settings.LockTeams) minPData.Team = pData.Team; if (pData.AI) { minPData.AI = pData.AI; minPData.AIDiff = pData.AIDiff; minPData.AIBehavior = pData.AIBehavior; } if (g_Players[playerID].offline) minPData.Offline = true; // Whether the player has won or was defeated let state = g_Players[playerID].state; if (state != "active") minPData.State = state; minPlayerData.push(minPData); } // Add observers let connectedPlayers = 0; for (let guid in g_PlayerAssignments) { let pData = g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player]; if (pData) ++connectedPlayers; else minPlayerData.push({ "Name": g_PlayerAssignments[guid].name, "Team": "observer" }); } Engine.SendChangeStateGame(connectedPlayers, playerDataToStringifiedTeamList(minPlayerData)); } /** * Send a report on the gamestatus to the lobby. * Keep in sync with source/tools/XpartaMuPP/LobbyRanking.py */ function reportGame() { // Only 1v1 games are rated (and Gaia is part of g_Players) if (!Engine.HasXmppClient() || !Engine.IsRankedGame() || g_Players.length != 3 || Engine.GetPlayerID() == -1) return; let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); let unitsClasses = [ "total", "Infantry", "Worker", "FemaleCitizen", "Cavalry", "Champion", "Hero", "Siege", "Ship", "Trader" ]; let unitsCountersTypes = [ "unitsTrained", "unitsLost", "enemyUnitsKilled" ]; let buildingsClasses = [ "total", "CivCentre", "House", "Economic", "Outpost", "Military", "Fortress", "Wonder" ]; let buildingsCountersTypes = [ "buildingsConstructed", "buildingsLost", "enemyBuildingsDestroyed" ]; let resourcesTypes = [ "wood", "food", "stone", "metal" ]; let resourcesCounterTypes = [ "resourcesGathered", "resourcesUsed", "resourcesSold", "resourcesBought" ]; let misc = [ "tradeIncome", "tributesSent", "tributesReceived", "treasuresCollected", "lootCollected", "percentMapExplored" ]; let playerStatistics = {}; // Unit Stats for (let unitCounterType of unitsCountersTypes) { if (!playerStatistics[unitCounterType]) playerStatistics[unitCounterType] = { }; for (let unitsClass of unitsClasses) playerStatistics[unitCounterType][unitsClass] = ""; } playerStatistics.unitsLostValue = ""; playerStatistics.unitsKilledValue = ""; // Building stats for (let buildingCounterType of buildingsCountersTypes) { if (!playerStatistics[buildingCounterType]) playerStatistics[buildingCounterType] = { }; for (let buildingsClass of buildingsClasses) playerStatistics[buildingCounterType][buildingsClass] = ""; } playerStatistics.buildingsLostValue = ""; playerStatistics.enemyBuildingsDestroyedValue = ""; // Resources for (let resourcesCounterType of resourcesCounterTypes) { if (!playerStatistics[resourcesCounterType]) playerStatistics[resourcesCounterType] = { }; for (let resourcesType of resourcesTypes) playerStatistics[resourcesCounterType][resourcesType] = ""; } playerStatistics.resourcesGathered.vegetarianFood = ""; for (let type of misc) playerStatistics[type] = ""; // Total playerStatistics.economyScore = ""; playerStatistics.militaryScore = ""; playerStatistics.totalScore = ""; let mapName = g_GameAttributes.settings.Name; let playerStates = ""; let playerCivs = ""; let teams = ""; let teamsLocked = true; // Serialize the statistics for each player into a comma-separated list. // Ignore gaia for (let i = 1; i < extendedSimState.players.length; ++i) { let player = extendedSimState.players[i]; let maxIndex = player.sequences.time.length - 1; playerStates += player.state + ","; playerCivs += player.civ + ","; teams += player.team + ","; teamsLocked = teamsLocked && player.teamsLocked; for (let resourcesCounterType of resourcesCounterTypes) for (let resourcesType of resourcesTypes) playerStatistics[resourcesCounterType][resourcesType] += player.sequences[resourcesCounterType][resourcesType][maxIndex] + ","; playerStatistics.resourcesGathered.vegetarianFood += player.sequences.resourcesGathered.vegetarianFood[maxIndex] + ","; for (let unitCounterType of unitsCountersTypes) for (let unitsClass of unitsClasses) playerStatistics[unitCounterType][unitsClass] += player.sequences[unitCounterType][unitsClass][maxIndex] + ","; for (let buildingCounterType of buildingsCountersTypes) for (let buildingsClass of buildingsClasses) playerStatistics[buildingCounterType][buildingsClass] += player.sequences[buildingCounterType][buildingsClass][maxIndex] + ","; let total = 0; for (let type in player.sequences.resourcesGathered) total += player.sequences.resourcesGathered[type][maxIndex]; playerStatistics.economyScore += total + ","; playerStatistics.militaryScore += Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10) + ","; playerStatistics.totalScore += (total + Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10)) + ","; for (let type of misc) playerStatistics[type] += player.sequences[type][maxIndex] + ","; } // Send the report with serialized data let reportObject = {}; reportObject.timeElapsed = extendedSimState.timeElapsed; reportObject.playerStates = playerStates; reportObject.playerID = Engine.GetPlayerID(); reportObject.matchID = g_GameAttributes.matchID; reportObject.civs = playerCivs; reportObject.teams = teams; reportObject.teamsLocked = String(teamsLocked); reportObject.ceasefireActive = String(extendedSimState.ceasefireActive); reportObject.ceasefireTimeRemaining = String(extendedSimState.ceasefireTimeRemaining); reportObject.mapName = mapName; reportObject.economyScore = playerStatistics.economyScore; reportObject.militaryScore = playerStatistics.militaryScore; reportObject.totalScore = playerStatistics.totalScore; for (let rct of resourcesCounterTypes) for (let rt of resourcesTypes) reportObject[rt + rct.substr(9)] = playerStatistics[rct][rt]; // eg. rt = food rct.substr = Gathered rct = resourcesGathered reportObject.vegetarianFoodGathered = playerStatistics.resourcesGathered.vegetarianFood; for (let type of 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 (let type of 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]; } for (let type of misc) reportObject[type] = playerStatistics[type]; Engine.SendGameReport(reportObject); } Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/iber_player_teambonus.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/iber_player_teambonus.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/iber_player_teambonus.json (revision 22809) @@ -1,13 +1,13 @@ { "type": "global", - "affects": ["CitizenSoldier Javelin"], + "affects": ["Citizen Javelin"], "affectedPlayers": ["ExclusiveMutualAlly"], "modifications": [ { "value": "Cost/Resources/food", "multiply": 0.8 }, { "value": "Cost/Resources/wood", "multiply": 0.8 }, { "value": "Cost/Resources/metal", "multiply": 0.8 }, { "value": "Cost/Resources/stone", "multiply": 0.8 } ], "auraName": "Saripeko", - "auraDescription": "Allied Citizen-Soldier Javelinists −20% resource costs." + "auraDescription": "Allied Citizen Javelinists −20% resource costs." } Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/rome_player_teambonus.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/rome_player_teambonus.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/rome_player_teambonus.json (revision 22809) @@ -1,10 +1,10 @@ { "type": "global", - "affects": ["CitizenSoldier Infantry"], + "affects": ["Citizen Infantry"], "affectedPlayers": ["ExclusiveMutualAlly"], "modifications": [ { "value": "Cost/BuildTime", "multiply": 0.8 } ], "auraName": "Conscription", - "auraDescription": "Allied Citizen-Soldier Infantry −20% training time." + "auraDescription": "Allied Citizen Infantry −20% training time." } Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/spart_player_teambonus.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/spart_player_teambonus.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/teambonuses/spart_player_teambonus.json (revision 22809) @@ -1,10 +1,10 @@ { "type": "global", - "affects": ["CitizenSoldier Spear Infantry"], + "affects": ["Citizen Infantry Spear"], "affectedPlayers": ["ExclusiveMutualAlly"], "modifications": [ { "value": "Health/Max", "multiply": 1.10 } ], "auraName": "Peloponnesian League", - "auraDescription": "Allied Citizen-Soldier Infantry Spearmen +10% health." + "auraDescription": "Allied Citizen Infantry Spearmen +10% health." } Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/iber_catafalque_1.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/iber_catafalque_1.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/iber_catafalque_1.json (revision 22809) @@ -1,10 +1,10 @@ { "type": "global", - "affects": ["Mercenary CitizenSoldier"], + "affects": ["Citizen Mercenary"], "affectedPlayers": ["Ally"], "modifications": [ { "value": "Cost/Resources/metal", "multiply": 0.75 } ], "auraName": "Mercenary Commander", - "auraDescription": "Along with his brother Indibil, Mandonius commanded the Iberian recruits and mercenaries that took part in the Punic Wars.\nAllied Citizen-Soldier Mercenaries −25% metal cost." + "auraDescription": "Along with his brother Indibil, Mandonius commanded the Iberian recruits and mercenaries that took part in the Punic Wars.\nAllied Citizen Mercenaries −25% metal cost." } Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/spart_catafalque_1.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/spart_catafalque_1.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/units/catafalques/spart_catafalque_1.json (revision 22809) @@ -1,12 +1,12 @@ { "type": "global", - "affects": ["CitizenSoldier Spear Infantry"], + "affects": ["Citizen Infantry Spear"], "modifications": [ { "value": "Cost/Resources/food", "multiply": 0.9 }, { "value": "Cost/Resources/wood", "multiply": 0.9 }, { "value": "Cost/Resources/metal", "multiply": 0.9 }, { "value": "Cost/Resources/stone", "multiply": 0.9 } ], "auraName": "Lycurgan Military Reforms", - "auraDescription": "Lycurgus instituted several military reforms, thus the complete and undivided allegiance to Sparta from its citizens was implemented under his form of government.\nCitizen-Soldier Infantry Spearmen −10% resource costs." + "auraDescription": "Lycurgus instituted several military reforms, thus the complete and undivided allegiance to Sparta from its citizens was implemented under his form of government.\nCitizen Infantry Spearmen −10% resource costs." } Index: ps/trunk/binaries/data/mods/public/simulation/data/civs/iber.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/civs/iber.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/civs/iber.json (revision 22809) @@ -1,150 +1,150 @@ { "Code": "iber", "Culture": "iber", "Name": "Iberians", "Emblem": "session/portraits/emblems/emblem_iberians.png", "History": "The Iberians were a people of mysterious origins and language, with a strong tradition of horsemanship and metalworking. A relatively peaceful culture, they usually fought in other's battles only as mercenaries. However, they proved tenacious when Rome sought to take their land and freedom from them, and employed pioneering guerrilla tactics and flaming javelins as they fought back.", "Music":[ {"File":"An_old_Warhorse_goes_to_Pasture.ogg", "Type":"peace"}, {"File":"Celtica.ogg", "Type":"peace"}, {"File":"Harsh_Lands_Rugged_People.ogg", "Type":"peace"} ], "Factions": [ { "Name": "", "Description": "", "Technologies": [ { "Name": "Suzko Txabalina", "History": "Iberian tribesmen were noted for wrapping bundles of grass about the shafts of their throwing spears, soaking that in some sort of flammable pitch, then setting it afire just before throwing.", "Description": "Causes targets struck to become inflamed and lose hitpoints at a constant rate until and if either healed or repaired, as appropriate." }, { "Name": "Maisu Burdina Langileak", "History": "The Iberians were known to produce the finest iron and steel implements and weapons of the age. The famous 'Toledo Steel.'", "Description": "Metal costs for units and technologies reduced by 50%." } ], "Heroes": [ { "Name": "Viriato", "Class": "", "Armament": "", "Emblem": "", "History": "Viriato, like Vercingetorix amongst the Gauls, was the most famous of the Iberian tribal war leaders, having conducted at least 7 campaigns against the Romans in the southern half of the peninsula during the 'Lusitani Wars' from 147-139 B.C. He surfaced as a survivor of the treacherous massacre of 9,000 men and the selling into slavery of 21,000 elderly, women, and children of the Lusitani. They had signed a treaty of peace with the Romans, conducted by Servius Sulpicius Galba, governor of Hispania Ulterior, as the 'final solution' to the Lusitani problem. He emerged from humble beginnings in 151 B.C. to become war chief of the Lusitani. He was intelligent and a superior tactician, never really defeated in any encounter (though suffered losses in some requiring retreat). He succumbed instead to another treachery arranged by a later Roman commander, Q. Servilius Caepio, to have him assassinated by three comrades that were close to him." }, { "Name": "Karos", "Class": "", "Armament": "", "Emblem": "", "History": "Karos was a chief of the Belli tribe located just east of the Celtiberi (Numantines at the center). Leading the confederated tribes of the meseta central (central upland plain) he concealed 20,000 foot and 5,000 mounted troops along a densely wooded track. Q. Fulvius Nobilior neglected proper reconnaissance and lead his army into the trap strung out in a long column. Some 10,000 of 15,000 Roman legionaries fell in the massive ambush that was sprung upon them. The date was 23 August of 153 B.C., the day when Rome celebrated the feast of Vulcan. By later Senatorial Decree it was ever thereafter known as dies ater, a 'sinister day', and Rome never again fought a battle on the 23rd of August. Karos was wounded in an after battle small cavalry action the same evening and soon died thereafter, but he had carried off one of the most humiliating defeats that Rome ever suffered." }, { "Name": "Indibil", "Class": "", "Armament": "", "Emblem": "", "History": "Indibil was king of the Ilergetes, a large federation ranged principally along the Ebro River in the northwest of the Iberian Peninsula. During the Barcid expansion, from 212 B.C. he had initially been talked into allying himself with the Carthaginians who had taken control of a lot of territory to the south and west, however after loss and his capture in a major battle he was convinced, some say tricked, to switch to the Roman side by Scipio Africanus. But that alliance didn't last long, as Roman promises were hollow and the Romans acted more like conquerors than allies. So, while the Romans and their allies had ended Carthaginian presence in 'Hispania' in 206 B.C., Indibil and another tribal prince by the name of Mandonio, who may have been his brother, rose up in rebellion against the Romans. They were defeated in battle, but rose up in a 2nd even larger rebellion that had unified all the Ilergetes again in 205 B.C. Outnumbered and outarmed they were again defeated, Indibil losing his life in the final battle and Mandonio being captured then later put to death. From that date onward the Ilergetes remained a pacified tribe under Roman rule." } ] } ], "CivBonuses": [ { "Name": "Harritsu Leku", "History": "With exception to alluvial plains and river valleys, stone is abundant in the Iberian Peninsula and was greatly used in construction of structures of all types.", "Description": "Iberians start with a powerful prefabricated circuit of stone walls." }, { "Name": "Zaldi Saldoa", "History": "Not unlike Numidia in North Africa, the Iberian Peninsula was known as 'horse country', capable of producing up to 100,000 new mounts each year.", "Description": "The resource cost of training horse-mounted units (cavalry) is reduced by 5% per animal corralled." } ], "TeamBonuses": [ { "Name": "Saripeko", "History": "The Iberians were long known to provide mercenaries to other nations to serve as auxiliaries to their armies in foreign wars. Carthage is the most well known example, and we have evidence of them serving in such a capacity in Aquitania.", - "Description": "Allied Citizen-Soldier Javelinists −20% resource costs." + "Description": "Allied Citizen Javelinists −20% resource costs." } ], "Structures": [ { "Name": "Gur Oroigarri", "Class": "", "Emblem": "", "History": "'Revered Monument' The Iberians were a religious people who built small monuments to their various gods. These monuments could also serve as family tombs.", "Requirements": "", "Phase": "", "Special": "Defensive Aura - Gives all Iberian units and buildings within vision range of the monument a 10-15% attack boost. Build Limit: Only 5 may be built per map." } ], "WallSets": [ "structures/wallset_palisade", "structures/iber_wallset_stone" ], "StartEntities": [ { "Template": "structures/iber_civil_centre" }, { "Template": "units/iber_support_female_citizen", "Count": 4 }, { "Template": "units/iber_infantry_swordsman_b", "Count": 2 }, { "Template": "units/iber_infantry_javelinist_b", "Count": 2 }, { "Template": "units/iber_cavalry_javelinist_b" } ], "Formations": [ "special/formations/null", "special/formations/box", "special/formations/column_closed", "special/formations/line_closed", "special/formations/column_open", "special/formations/line_open", "special/formations/flank", "special/formations/skirmish", "special/formations/wedge", "special/formations/battle_line" ], "AINames": [ "Viriato", "Viriato", "Karos", "Indibil", "Audax", "Ditalcus", "Minurus", "Tautalus" ], "SkirmishReplacements": { "skirmish/units/default_infantry_melee_b": "units/iber_infantry_swordsman_b", "skirmish/structures/default_house_5": "structures/{civ}_house", "skirmish/structures/iber_wall_short": "structures/iber_wall_short", "skirmish/structures/iber_wall_medium": "structures/iber_wall_medium", "skirmish/structures/iber_wall_long": "structures/iber_wall_long", "skirmish/structures/iber_wall_gate": "structures/iber_wall_gate", "skirmish/structures/iber_wall_tower": "structures/iber_wall_tower" }, "SelectableInGameSetup": true } Index: ps/trunk/binaries/data/mods/public/simulation/data/civs/rome.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/civs/rome.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/civs/rome.json (revision 22809) @@ -1,159 +1,159 @@ { "Code": "rome", "Culture": "rome", "Name": "Romans", "Emblem": "session/portraits/emblems/emblem_romans.png", "History": "The Romans controlled one of the largest empires of the ancient world, stretching at its peak from southern Scotland to the Sahara Desert, and containing between 60 million and 80 million inhabitants, one quarter of the Earth's population at that time. Rome also remained one of the strongest nations on earth for almost 800 years. The Romans were the supreme builders of the ancient world, excelled at siege warfare and had an exquisite infantry and navy.", "Music":[ {"File":"Juno_Protect_You.ogg", "Type":"peace"}, {"File":"Mediterranean_Waves.ogg", "Type":"peace"}, {"File":"Elysian_Fields.ogg", "Type":"peace"}, {"File":"The_Governor.ogg", "Type":"peace"} ], "Factions": [ { "Name": "", "Description": "", "Technologies": [ { "Name": "Divide et Impera", "History": "'Divide and conquer' was the main principle in Rome's foreign politics throughout its long history. The Romans lured enemies or neutral factions to their side by offering them certain privileges. In due period of time, friends as well as foes were subjugated.", "Description": "Roman heroes can convert enemy units with great cost." } ], "Heroes": [ { "Name": "Quintus Fabius Maximus", "Class": "", "Armament": "", "Emblem": "", "History": "Dictator for six months during the Second Punic War. Instead of attacking the most powerful Hannibal, he started a very effective war of attrition against him." }, { "Name": "Marcus Claudius Marcellus", "Class": "", "Armament": "", "Emblem": "", "History": "A soldier of the first war with Carthage, a hero of the Second Punic War, and victor over the Gauls at Clastidium. Plutarch describes him as a man of war, strong in body and constitution, with an iron will to fight on. As a general he was immensely capable, standing alongside Scipio Africanus and Claudius Nero as the most effective Roman generals of the entire Second Punic War. In addition to his military achievements Marcellus was a fan of Greek culture and arts, which he enthusiastically promoted in Rome. He met his demise when his men were ambushed near Venusia. In honor of the respect the people held for him, Marcellus was granted the title of 'Sword of Rome.'" }, { "Name": "Scipio Africanus", "Class": "", "Armament": "", "Emblem": "", "History": "He was the first really successful Roman general against the Carthaginians. His campaigns in Spain and Africa helped to bring Carthage to its knees during the Second Punic War. He defeated Hannibal at the Battle of Zama in 202 B.C." } ] } ], "CivBonuses": [ { "Name": "Testudo Formation", "History": "The Romans commonly used the Testudo or 'turtle' formation for defense: Legionaries were formed into hollow squares with twelve men on each side, standing so close together that their shields overlapped like fish scales.", "Description": "Roman Legionaries can form a Testudo." }, { "Name": "Citizenship", "History": "Roman Citizenship was highly prized in the ancient world. Basic rights and privileges were afforded Roman citizens that were denied other conquered peoples. It is said that harming a Roman citizen was akin to harming Rome herself, and would cause the entire might of Rome to fall upon the perpetrator.", "Description": "Any Roman citizen-soldier fighting within Roman territory gains a non-permanent +10% bonus in armor." } ], "TeamBonuses": [ { "Name": "Conscription", "History": "Many Roman soldiers were conscripted into service. While volunteers were preferred, the Roman state did maintain an annual military draft. During an emergency the draft and the terms of service were enlarged. The importance of military service in Republican Rome was so great it was a prerequisite for a political career. Members of the Senate were called Conscript Fathers because of this, reflected in how the ordo equester was said to have been \"conscripted\" into the Senate.", - "Description": "Allied Citizen-Soldier Infantry −20% training time." + "Description": "Allied Citizen Infantry −20% training time." } ], "Structures": [ { "Name": "Entrenched Camp", "Class": "", "Emblem": "", "History": "Sometimes it was a temporary camp built facing the route by which the army is to march, other times a defensive or offensive (for sieges) structure. Within the Praetorian gate, which should either front the east or the enemy, the tents of the first centuries or cohorts are pitched, and the dracos (ensigns of cohorts) and other ensigns planted. The Decumane gate is directly opposite to the Praetorian in the rear of the camp, and through this the soldiers are conducted to the place appointed for punishment or execution. It has a turf wall, and it's surrounded by a canal filled with water whenever possible for extra defense. Many towns started up as bigger military camps to evolve to more complicated cities.", "Requirements": "", "Phase": "", "Special": "Trains citizen-soldiers from neutral or enemy territory." }, { "Name": "Murus Latericius", "Class": "", "Emblem": "", "History": "Turf walls built by legionaries during sieges.", "Requirements": "", "Phase": "", "Special": "Can be built in neutral and enemy territory to strangle enemy towns." } ], "WallSets": [ "structures/rome_wallset_stone", "structures/rome_wallset_siege" ], "StartEntities": [ { "Template": "structures/rome_civil_centre" }, { "Template": "units/rome_support_female_citizen", "Count": 4 }, { "Template": "units/rome_infantry_swordsman_b", "Count": 2 }, { "Template": "units/rome_infantry_javelinist_b", "Count": 2 }, { "Template": "units/rome_cavalry_spearman_b" } ], "Formations": [ "special/formations/null", "special/formations/box", "special/formations/column_closed", "special/formations/line_closed", "special/formations/column_open", "special/formations/line_open", "special/formations/flank", "special/formations/skirmish", "special/formations/wedge", "special/formations/battle_line", "special/formations/testudo", "special/formations/anti_cavalry" ], "AINames": [ "Lucius Junius Brutus", "Lucius Tarquinius Collatinus", "Gaius Julius Caesar Octavianus", "Marcus Vipsanius Agrippa", "Gaius Iulius Iullus", "Gaius Servilius Structus Ahala", "Publius Cornelius Rufinus", "Lucius Papirius Cursor", "Aulus Manlius Capitolinus", "Publius Cornelius Scipio Africanus", "Publius Sempronius Tuditanus", "Marcus Cornelius Cethegus", "Quintus Caecilius Metellus Pius", "Marcus Licinius Crassus" ], "SkirmishReplacements": { "skirmish/units/default_cavalry" : "units/rome_cavalry_spearman_b", "skirmish/units/default_infantry_melee_b": "units/rome_infantry_swordsman_b", "skirmish/structures/default_house_10" : "structures/{civ}_house" }, "SelectableInGameSetup": true } Index: ps/trunk/binaries/data/mods/public/simulation/data/civs/spart.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/civs/spart.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/civs/spart.json (revision 22809) @@ -1,174 +1,174 @@ { "Code":"spart", "Culture":"hele", "Name":"Spartans", "Emblem":"session/portraits/emblems/emblem_spartans.png", "History":"Sparta was a prominent city-state in ancient Greece, and its dominant military power on land from circa 650 B.C. Spartan culture was obsessed with military training and excellence, with rigorous training for boys beginning at age seven. Thanks to its military might, Sparta led a coalition of Greek forces during the Greco-Persian Wars, and won over Athens in the Peloponnesian Wars, though at great cost.", "Music":[ {"File":"Helen_Leaves_Sparta.ogg", "Type":"peace"}, {"File":"Peaks_of_Atlas.ogg", "Type":"peace"}, {"File":"Forging_a_City-State.ogg", "Type":"peace"}, {"File":"The_Hellespont.ogg", "Type":"peace"} ], "Factions": [ { "Name":"Spartans", "Description":"", "Technologies": [ { "Name":"Feminine Mystique", "History":"Spartan women were some of the freest in the ancient world. They could own land and slaves and even exercise naked like Spartan men. It is said that only Spartan women gave birth to real men. Such tough-as-nails women more than once helped save their city from disaster, for example when after a lost battle against Pyrrhus of Epirus they overnight built an earthen rampart to protect the city while their men slept in preparation for the next day's siege.", "Description":"Spartan female citizens cannot be captured and will doggedly fight back against any attackers. They are also capable of constructing defense towers and palisades." }, { "Name":"Tyrtean Paeans", "History":"Paeans were battle hymns that were sung by the hoplites when they charged the enemy lines. One of the first known Paeans were composed by Tirteus, a warrior poet of Sparta, during the First Messenian War.", "Description":"Units in phalanx formation move faster." }, { "Name":"The Agoge", "History":"Spartans were housed and trained from a young age to be superlative warriors and to endure any hardship a military life can give them.", "Description":"+25% health for spear infantry, but also +10% train time." } ], "Heroes": [ { "Name":"Leonidas I", "Class":"", "Armament":"", "Emblem":"", "History":"The king of Sparta, who fought and died at the battle of Thermopylae in 480 B.C. He successfully blocked the way of the huge Persian army through the narrow passage with his 7000 men, until Xerxes was made aware of a secret unobstructed path. Finding the enemy at his rear, Leonidas sent home most of his troops, choosing to stay behind with 300 hand-picked hoplites and win time for the others to withdraw." }, { "Name":"Brasidas", "Class":"", "Armament":"", "Emblem":"", "History":"Because Brasidas has sponsored their citizenship in return for service, Helot Skirmishers fight longer and harder for Sparta while within range of him." }, { "Name":"Agis III", "Class":"", "Armament":"", "Emblem":"", "History":"Agis III was the 20th Spartan king of the Eurypontid lineage. Agis cobbled together an alliance of Southern Greek states to fight off Macedonian hegemony while Alexander the Great was away in Asia on his conquest march. After securing Crete as a Spartan tributary, Agis then moved to besiege the city of Megalopolis in the Peloponnese, who was an ally of Macedon. Antipater, the Macedonian regent, lead an army to stop this new uprising. In the Battle of Megalopolis, the Macedonians prevailed in a long and bloody battle. Much like Leonidas 150 years earlier, instead of surrendering, Agis made a heroic final stand in order to buy time for his troops to retreat." } ] } ], "CivBonuses": [ { "Name":"Othismos", "History":"The Spartans were undisputed masters of phalanx warfare. The Spartans were so feared for their discipline that the enemy army would sometimes break up and run away before a single shield clashed. 'Othismos' refers to the point in a phalanx battle where both sides try to shove each other out of formation, attempting to breaking up the enemy lines and routing them.", "Description":"Spartans can use the powerful Phalanx formation." }, { "Name":"Laws of Lycurgus", "History":"Under the Constitution written by the mythical law-giver Lycurgus, the institution of The Agoge was established, where Spartans were trained from the age of 6 to be superior warriors in defense of the Spartan state.", "Description":"The Spartan rank upgrades at the Barracks cost no resources, except time." }, { "Name":"Hellenization", "History":"The Greeks were highly successful in Hellenising various foreigners. During the Hellenistic Age, Greek was the lingua franca of the Ancient World, spoken widely from Spain to India.", "Description":"Constructing a Theatron increases the territory expanse of all buildings by +20%." } ], "TeamBonuses": [ { "Name":"Peloponnesian League", "History":"Much of the Peloponnese was subject to Sparta in one way or another. This loose confederation, with Sparta as its leader, was later dubbed the Peloponnesian League by historians, but in ancient times was called 'The Lacedaemonians and their allies.'", - "Description":"Allied Citizen-Soldier Infantry Spearmen +10% health." + "Description":"Allied Citizen Infantry Spearmen +10% health." } ], "Structures": [ { "Name":"Theatron", "Class":"", "Emblem":"", "History":"Greek theatres were places where the immortal tragedies of Aeschylus, Sophocles and many other talented dramatists were staged to the delight of the populace. They were instrumental in enriching Hellenic culture.", "Requirements":"", "Phase":"City", "Special":"The Hellenization civ bonus. Building a Theatron increases the territory effect of all buildings by 25%." }, { "Name":"Syssition", "Class":"", "Emblem":"", "History":"The Syssition was the Mess Hall for full-blooded Spartiates. Every Spartan peer, even kings, belonged to one.", "Requirements":"", "Phase":"City", "Special":"Train heroes and Spartiates and research technologies related to them." } ], "WallSets": [ "structures/wallset_palisade", "structures/spart_wallset_stone" ], "StartEntities": [ { "Template": "structures/spart_civil_centre" }, { "Template": "units/spart_support_female_citizen", "Count": 4 }, { "Template": "units/spart_infantry_spearman_b", "Count": 2 }, { "Template": "units/spart_infantry_javelinist_b", "Count": 2 }, { "Template": "units/spart_cavalry_javelinist_b" } ], "Formations": [ "special/formations/null", "special/formations/box", "special/formations/column_closed", "special/formations/line_closed", "special/formations/column_open", "special/formations/line_open", "special/formations/flank", "special/formations/skirmish", "special/formations/wedge", "special/formations/battle_line", "special/formations/phalanx" ], "AINames": [ "Leonidas", "Dienekes", "Brasidas", "Agis", "Archidamus", "Lysander", "Pausanias", "Agesilaus", "Echestratus", "Eurycrates", "Eucleidas", "Agesipolis" ], "SkirmishReplacements": { "skirmish/structures/default_house_10" : "structures/{civ}_house", "skirmish/structures/default_wall_tower": "", "skirmish/structures/default_wall_gate": "", "skirmish/structures/default_wall_short": "", "skirmish/structures/default_wall_medium": "", "skirmish/structures/default_wall_long": "" }, "SelectableInGameSetup": true } Index: ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_city_athen.json (revision 22809) @@ -1,22 +1,22 @@ { "genericName": "City Phase", "specificName": { "athen": "Megalópolis" }, "description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology. This is the Athenian city phase, where metal gathering rates are boosted because of the 'Silver Owls' bonus.", "cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 }, "requirements": { "entity": { "class": "Town", "number": 4 } }, "requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).", "supersedes": "phase_town_athen", "replaces": ["phase_city"], "icon": "city_phase.png", "researchTime": 60, - "tooltip": "Advance to City Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by another +50%. Silver Owls civ bonus grants an extra +10% metal gather rate to all workers. Citizen soldiers max health increased by +10%. All structures +9 garrisoned regeneration rate.", + "tooltip": "Advance to City Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by another +50%. Silver Owls civ bonus grants an extra +10% metal gather rate to all workers. Citizen Soldiers +10% health. All structures +9 garrisoned regeneration rate.", "modifications": [ { "value": "ResourceGatherer/Rates/metal.ore", "multiply": 1.1, "affects": "Worker" }, { "value": "TerritoryInfluence/Radius", "multiply": 1.5, "affects": "CivCentre" }, - { "value": "Health/Max", "multiply": 1.1, "affects": "CitizenSoldier" }, + { "value": "Health/Max", "multiply": 1.1, "affects": "Citizen Soldier" }, { "value": "Capturable/GarrisonRegenRate", "add": 9, "affects": "Structure" } ], "soundComplete": "interface/alarm/alarm_phase.xml" } Index: ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_city_generic.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_city_generic.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_city_generic.json (revision 22809) @@ -1,22 +1,22 @@ { "genericName": "City Phase", "specificName": { "mace": "Megalópolis", "spart": "Megalópolis" }, "description": "Advances from a bustling town to a veritable metropolis, full of the wonders of modern technology.", "cost": { "food": 0, "wood": 0, "stone": 750, "metal": 750 }, "requirements": { "entity": { "class": "Town", "number": 4 } }, "requirementsTooltip": "Requires 4 new Town Phase structures (except Walls and Civic Centers).", "supersedes": "phase_town_generic", "replaces": ["phase_city"], "icon": "city_phase.png", "researchTime": 60, - "tooltip": "Advance to City Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by another +50%. Citizen soldiers max health increased by +10%. All structures +9 garrisoned regeneration rate.", + "tooltip": "Advance to City Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by another +50%. Citizen Soldiers +10% health. All structures +9 garrisoned regeneration rate.", "modifications": [ { "value": "TerritoryInfluence/Radius", "multiply": 1.5, "affects": "CivCentre" }, - { "value": "Health/Max", "multiply": 1.1, "affects": "CitizenSoldier" }, + { "value": "Health/Max", "multiply": 1.1, "affects": "Citizen Soldier" }, { "value": "Capturable/GarrisonRegenRate", "add": 9, "affects": "Structure" } ], "soundComplete": "interface/alarm/alarm_phase.xml" } Index: ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_town_athen.json (revision 22809) @@ -1,22 +1,22 @@ { "genericName": "Town Phase", "specificName": { "athen": "Kōmópolis" }, "description": "Advances from a small village to a bustling town, ready to expand rapidly. This is the Athenian town phase, where metal gathering rates are boosted because of the 'Silver Owls' bonus.", "cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 }, "requirements": { "entity": { "class": "Village", "number": 5 } }, "requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).", "supersedes": "phase_village", "replaces": ["phase_town"], "icon": "town_phase.png", "researchTime": 30, - "tooltip": "Advance to Town Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by +30%. 'Silver Owls' civ bonus grants an extra +10% metal gather rate to all workers. Citizen soldiers max health increased by +20%. All structures +7 garrisoned regeneration rate.", + "tooltip": "Advance to Town Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by +30%. 'Silver Owls' civ bonus grants an extra +10% metal gather rate to all workers. Citizen Soldiers +20% health. All structures +7 garrisoned regeneration rate.", "modifications": [ { "value": "ResourceGatherer/Rates/metal.ore", "multiply": 1.1, "affects": "Worker" }, { "value": "TerritoryInfluence/Radius", "multiply": 1.3, "affects": "CivCentre" }, - { "value": "Health/Max", "multiply": 1.2, "affects": "CitizenSoldier" }, + { "value": "Health/Max", "multiply": 1.2, "affects": "Citizen Soldier" }, { "value": "Capturable/GarrisonRegenRate", "add": 7, "affects": "Structure" } ], "soundComplete": "interface/alarm/alarm_phase.xml" } Index: ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_town_generic.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_town_generic.json (revision 22808) +++ ps/trunk/binaries/data/mods/public/simulation/data/technologies/phase_town_generic.json (revision 22809) @@ -1,22 +1,22 @@ { "genericName": "Town Phase", "specificName": { "mace": "Kōmópolis", "spart": "Kōmópolis" }, "description": "Advances from a small village to a bustling town, ready to expand rapidly.", "cost": { "food": 500, "wood": 500, "stone": 0, "metal": 0 }, "requirements": { "entity": { "class": "Village", "number": 5 } }, "requirementsTooltip": "Requires 5 Village Phase structures (except Palisades and Farm Fields).", "supersedes": "phase_village", "replaces": ["phase_town"], "icon": "town_phase.png", "researchTime": 30, - "tooltip": "Advance to Town Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by +30%. Citizen soldiers max health increased by +20%. All structures +7 garrisoned regeneration rate.", + "tooltip": "Advance to Town Phase, which unlocks more structures and units. Territory radius for Civic Centers increased by +30%. Citizen Soldiers +20% health. All structures +7 garrisoned regeneration rate.", "modifications": [ { "value": "TerritoryInfluence/Radius", "multiply": 1.3, "affects": "CivCentre" }, - { "value": "Health/Max", "multiply": 1.2, "affects": "CitizenSoldier" }, + { "value": "Health/Max", "multiply": 1.2, "affects": "Citizen Soldier" }, { "value": "Capturable/GarrisonRegenRate", "add": 7, "affects": "Structure" } ], "soundComplete": "interface/alarm/alarm_phase.xml" }