Index: ps/trunk/binaries/data/mods/public/gui/session/session.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 13977) +++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 13978) @@ -1,684 +1,684 @@ // Network Mode var g_IsNetworked = 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 } }; // 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 // Colors to flash when pop limit reached const DEFAULT_POPULATION_COLOR = "white"; const POPULATION_ALERT_COLOR = "orange"; 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); g_EntityStates[entId] = entState; } return g_EntityStates[entId]; } // Cache TemplateData var g_TemplateData = {}; // {id:template} function GetTemplateData(templateName) { if (!(templateName in g_TemplateData)) { var template = Engine.GuiInterfaceCall("GetTemplateData", templateName); g_TemplateData[templateName] = template; } return g_TemplateData[templateName]; } // Cache TechnologyData var g_TechnologyData = {}; // {id:template} function GetTechnologyData(technologyName) { if (!(technologyName in g_TechnologyData)) { var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName); g_TechnologyData[technologyName] = template; } return g_TechnologyData[technologyName]; } // Init function init(initData, hotloadData) { if (initData) { g_IsNetworked = initData.isNetworked; // Set network mode g_PlayerAssignments = initData.playerAssignments; // Cache the player data // (This may be updated at runtime by handleNetMessage) g_Players = getPlayerData(g_PlayerAssignments); if (initData.savedGUIData) restoreSavedGameData(initData.savedGUIData); getGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked; } else // Needed for autostart loading option { g_Players = getPlayerData(null); } // Cache civ data g_CivData = loadCivData(); g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia" }; g_GameSpeeds = initGameSpeeds(); g_CurrentSpeed = Engine.GetSimRate(); var gameSpeed = 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]); } getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[Engine.GetPlayerID()].civ].Emblem; getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[Engine.GetPlayerID()].civ].Name; initMenuPosition(); // set initial position // Populate player selection dropdown var playerNames = []; var playerIDs = []; for (var player in g_Players) { playerNames.push(g_Players[player].name); playerIDs.push(player); } var viewPlayerDropdown = getGUIObjectByName("viewPlayer"); viewPlayerDropdown.list = playerNames; viewPlayerDropdown.list_data = playerIDs; viewPlayerDropdown.selected = Engine.GetPlayerID(); // If in Atlas editor, disable the exit button if (Engine.IsAtlasRunning()) getGUIObjectByName("menuExitButton").enabled = false; if (hotloadData) { g_Selection.selected = hotloadData.selection; } else { // Starting for the first time: var civMusic = g_CivData[g_Players[Engine.GetPlayerID()].civ].Music; initMusic(); global.music.storeTracks(civMusic); global.music.setState(global.music.states.PEACE); playRandomAmbient("temperate"); } 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) { getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem; getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name; } } function reportPerformance(time) { var settings = Engine.GetMapSettings(); var data = { time: time, map: settings.Name, seed: settings.Seed, // only defined for random maps size: settings.Size, // only defined for random maps profiler: Engine.GetProfilerState() }; Engine.SubmitUserReport("profile", 3, JSON.stringify(data)); } function resignGame() { var simState = GetSimState(); // Players can't resign if they've already won or lost. if (simState.players[Engine.GetPlayerID()].state != "active" || g_Disconnected) return; // Tell other players that we have given up and been defeated Engine.PostNetworkCommand({ "type": "defeat-player", "playerId": Engine.GetPlayerID() }); global.music.setState(global.music.states.DEFEAT); resumeGame(); } function leaveGame() { var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); var playerState = extendedSimState.players[Engine.GetPlayerID()]; var gameResult; if (g_Disconnected) { gameResult = "You have been disconnected." } else if (playerState.state == "won") { gameResult = "You have won the battle!"; } else if (playerState.state == "defeated") { gameResult = "You have been defeated..."; } else // "active" { gameResult = "You have abandoned the game."; // Tell other players that we have given up and been defeated Engine.PostNetworkCommand({ "type": "defeat-player", "playerId": Engine.GetPlayerID() }); global.music.setState(global.music.states.DEFEAT); } var mapSettings = Engine.GetMapSettings(); stopAmbient(); endGame(); 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 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; /** * 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) { onSimulationUpdate(); // Display rally points for selected buildings Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() }); } // Run timers updateTimers(); // Animate menu updateMenuPosition(tickLength); // When training is blocked, flash population (alternates colour every 500msec) if (g_IsTrainingBlocked && (Date.now() % 1000) < 500) getGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR; else getGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR; // Clear renamed entities list Engine.GuiInterfaceCall("ClearRenamedEntities"); } function checkPlayerState() { var simState = GetSimState(); var playerState = simState.players[Engine.GetPlayerID()]; if (!g_GameEnded) { // If the game is about to end, disable the ability to resign. if (playerState.state != "active") getGUIObjectByName("menuResignButton").enabled = false; else return; if (playerState.state == "defeated") { g_GameEnded = true; // TODO: DEFEAT_CUE is missing? global.music.setState(global.music.states.DEFEAT); closeMenu(); closeOpenDialogs(); if (Engine.IsAtlasRunning()) { // If we're in Atlas, we can't leave the game var btCaptions = ["OK"]; var btCode = [null]; var message = "Press OK to continue"; } else { var btCaptions = ["Yes", "No"]; var btCode = [leaveGame, null]; var message = "Do you want to quit?"; } messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode); } else if (playerState.state == "won") { g_GameEnded = true; global.music.setState(global.music.states.VICTORY); closeMenu(); closeOpenDialogs(); if (!getGUIObjectByName("devCommandsRevealMap").checked) getGUIObjectByName("devCommandsRevealMap").checked = true; if (Engine.IsAtlasRunning()) { // If we're in Atlas, we can't leave the game var btCaptions = ["OK"]; var btCode = [null]; var message = "Press OK to continue"; } else { var btCaptions = ["Yes", "No"]; var btCode = [leaveGame, null]; var message = "Do you want to quit?"; } messageBox(400, 200, message, "VICTORIOUS!", 0, btCaptions, btCode); } } } function changeGameSpeed(speed) { // For non-networked games only if (!g_IsNetworked) { Engine.SetSimRate(speed); g_CurrentSpeed = speed; } } /** * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation * update (see session.xml), or from onTick when the selection has changed. */ function onSimulationUpdate() { g_Selection.dirty = false; 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(); updateHero(); updateGroups(); updateDebug(); updatePlayerDisplay(); updateSelectionDetails(); updateResearchDisplay(); updateBuildingPlacementPreview(); updateTimeElapsedCounter(); // Update music state on basis of battle state. var battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID()); if (battleState) global.music.setState(global.music.states[battleState]); } function updateHero() { var simState = GetSimState(); var playerState = simState.players[Engine.GetPlayerID()]; var heroButton = getGUIObjectByName("unitHeroButton"); if (!playerState || playerState.heroes.length <= 0) { heroButton.hidden = true; return; } var heroImage = getGUIObjectByName("unitHeroImage"); var heroState = GetEntityState(playerState.heroes[0]); var template = GetTemplateData(heroState.template); heroImage.sprite = "stretched:session/portraits/" + template.icon; var hero = playerState.heroes[0]; - heroButton.onpress = function() - { + heroButton.onpress = function() + { if (!Engine.HotkeyIsPressed("selection.add")) - g_Selection.reset(); - g_Selection.addList([hero]); + g_Selection.reset(); + g_Selection.addList([hero]); }; - heroButton.ondoublepress = function() { selectAndMoveTo(hero) }; + heroButton.ondoublepress = function() { selectAndMoveTo(getEntityOrHolder(hero)); }; heroButton.hidden = false; // Setup tooltip var tooltip = "[font=\"serif-bold-16\"]" + template.name.specific + "[/font]"; tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + heroState.hitpoints + "/" + heroState.maxHitpoints; tooltip += "\n[font=\"serif-bold-13\"]" + (heroState.attack ? heroState.attack.type + " " : "") + "Attack:[/font] " + damageTypeDetails(heroState.attack); // Show max attack range if ranged attack, also convert to tiles (4m per tile) if (heroState.attack && heroState.attack.type == "Ranged") tooltip += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(heroState.attack.maxRange/4); tooltip += "\n[font=\"serif-bold-13\"]Armor:[/font] " + damageTypeDetails(heroState.armour); tooltip += "\n" + template.tooltip; heroButton.tooltip = tooltip; }; function updateGroups() { var guiName = "Group"; g_Groups.update(); for (var i = 0; i < 10; i++) { var button = getGUIObjectByName("unit"+guiName+"Button["+i+"]"); var label = 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); } var numButtons = i; var rowLength = 1; var numRows = Math.ceil(numButtons / rowLength); var buttonSideLength = 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 = getGUIObjectByName("debug"); if (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 = GetEntityState(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; getGUIObjectByName("resourceFood").caption = playerState.resourceCounts.food; getGUIObjectByName("resourceWood").caption = playerState.resourceCounts.wood; getGUIObjectByName("resourceStone").caption = playerState.resourceCounts.stone; getGUIObjectByName("resourceMetal").caption = playerState.resourceCounts.metal; 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 = getGUIObjectByName("researchStartedButton[0]").size.right; for (var i = 0; i < 10; ++i) { var button = 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 = 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; getGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon; // Scale the progress indicator. var size = 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)); getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size; ++numButtons; } // Hide unused buttons. for (var i = numButtons; i < 10; ++i) getGUIObjectByName("researchStartedButton[" + i + "]").hidden = true; } function updateTimeElapsedCounter() { var simState = GetSimState(); var speed = g_CurrentSpeed != 1.0 ? " (" + g_CurrentSpeed + "x)" : ""; var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter"); timeElapsedCounter.caption = timeToString(simState.timeElapsed) + speed; } // Toggles the display of status bars for all of the player's entities. function recalculateStatusBarDisplay() { if (g_ShowAllStatusBars) var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID()); else { var selected = g_Selection.toList(); for each (var ent in g_Selection.highlighted) selected.push(ent); // Remove selected entities from the 'all entities' array, to avoid disabling their status bars. var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter( function(idx) { return (selected.indexOf(idx) == -1); } ); } Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars }); } // Temporarily adding this here const AMBIENT_TEMPERATE = "temperate"; var currentAmbient; function playRandomAmbient(type) { switch (type) { case AMBIENT_TEMPERATE: // Seem to need the underscore at the end of "temperate" to avoid crash // (Might be caused by trying to randomly load day_temperate.xml) // currentAmbient = newRandomSound("ambient", "temperate_", "dayscape"); const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg"; Engine.PlayAmbientSound( AMBIENT, true ); break; default: Engine.Console_Write("Unrecognized ambient type: " + type); break; } } // Temporarily adding this here function stopAmbient() { if (currentAmbient) { currentAmbient.free(); currentAmbient = null; } } Index: ps/trunk/binaries/data/mods/public/gui/session/utility_functions.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/utility_functions.js (revision 13977) +++ ps/trunk/binaries/data/mods/public/gui/session/utility_functions.js (revision 13978) @@ -1,575 +1,588 @@ 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; } // 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]"); return dmgArray.join(", "); } else { return "[font=\"serif-12\"](None)[/font]"; } } // Converts an armor level into the actual reduction percentage function armorLevelToPercentage(level) { return 100 - Math.round(Math.pow(0.9, level) * 100); } // 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]"); } return dmgArray.join(", "); } else { return "[font=\"serif-12\"](None)[/font]"; } } // 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; 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); return dmgArray.join("[font=\"serif-12\"], [/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]"; var dmgArray = []; if (hackDamage) dmgArray.push(hackDamage + hackLabel); if (pierceDamage) dmgArray.push(pierceDamage + pierceLabel); if (crushDamage) dmgArray.push(crushDamage + crushLabel); return dmgArray.join("[font=\"serif-12\"], [/font]"); } function getEntityCommandsList(entState) { var commands = []; if (entState.garrisonHolder) { commands.push({ "name": "unload-all", "tooltip": "Unload All", "icon": "garrison-out.png" }); } commands.push({ "name": "delete", "tooltip": "Delete", "icon": "kill_small.png" }); if (hasClass(entState, "Unit")) { commands.push({ "name": "stop", "tooltip": "Stop", "icon": "stop.png" }); commands.push({ "name": "garrison", "tooltip": "Garrison", "icon": "garrison.png" }); } if (entState.buildEntities) { commands.push({ "name": "repair", "tooltip": "Repair", "icon": "repair.png" }); } if (entState.rallyPoint) { commands.push({ "name": "focus-rally", "tooltip": "Focus on Rally Point", "icon": "focus-rally.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); 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(" "); } else if (template.cost) { var costs = getEntityCostComponentsTooltipString(template, trainNum, entity); cost += costs.join(" "); } 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; 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]); return "\n\n[font=\"serif-bold-13\"][color=\"red\"]Insufficient resources:[/color][/font]\n" + formatted.join(" "); } function getEntitySpeed(template) { var speed = ""; if (template.speed) { speed += "[font=\"serif-bold-13\"]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]"); speed += speeds.join(", "); } 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) if (type == "Ranged") attack += ", [font=\"serif-bold-13\"]Range:[/font] "+Math.round(template.attack[type].maxRange/4); 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); return (names.length) ? names.join(" ") : "???"; } 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]' + + 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; 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; if (gain.market1Gain && gain.market1Owner == gain.traderOwner) tooltip += "+" + gain.market1Gain; if (gain.market2Gain && gain.market2Owner == gain.traderOwner) tooltip += "+" + gain.market2Gain; tooltip += " (you)"; if (gain.market1Gain && gain.market1Owner != gain.traderOwner) tooltip += ", " + gain.market1Gain + " (player " + gain.market1Owner + ")"; if (gain.market2Gain && gain.market2Owner != gain.traderOwner) tooltip += ", " + gain.market2Gain + " (player " + gain.market2Owner + ")"; return tooltip; } + +/** + * 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; +}