Index: ps/trunk/binaries/data/mods/public/gui/common/global.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/global.xml (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/common/global.xml (revision 16624)
@@ -1,97 +1,97 @@
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 16624)
@@ -1,1948 +1,1985 @@
////////////////////////////////////////////////////////////////////////////////////////////////
// Constants
const DEFAULT_NETWORKED_MAP = "Acropolis 01";
const DEFAULT_OFFLINE_MAP = "Acropolis 01";
const VICTORY_DEFAULTIDX = 1;
// TODO: Move these somewhere like simulation\data\game_types.json, Atlas needs them too
// Translation: Type of victory condition.
const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", translate("Unlimited")];
const POPULATION_CAP_DATA = [50, 100, 150, 200, 250, 300, 10000];
const POPULATION_CAP_DEFAULTIDX = 5;
// Translation: Amount of starting resources.
const STARTING_RESOURCES = [translateWithContext("startingResources", "Very Low"), translateWithContext("startingResources", "Low"), translateWithContext("startingResources", "Medium"), translateWithContext("startingResources", "High"), translateWithContext("startingResources", "Very High"), translateWithContext("startingResources", "Deathmatch")];
const STARTING_RESOURCES_DATA = [100, 300, 500, 1000, 3000, 50000];
const STARTING_RESOURCES_DEFAULTIDX = 1;
+// Translation: Ceasefire.
+const CEASEFIRE = [translateWithContext("ceasefire", "No ceasefire"), translateWithContext("ceasefire", "5 minutes"), translateWithContext("ceasefire", "10 minutes"), translateWithContext("ceasefire", "15 minutes"), translateWithContext("ceasefire", "20 minutes"), translateWithContext("ceasefire", "30 minutes"), translateWithContext("ceasefire", "45 minutes"), translateWithContext("ceasefire", "60 minutes")];
+const CEASEFIRE_DATA = [0, 5, 10, 15, 20, 30, 45, 60];
+const CEASEFIRE_DEFAULTIDX = 0;
// Max number of players for any map
const MAX_PLAYERS = 8;
////////////////////////////////////////////////////////////////////////////////////////////////
// Is this is a networked game, or offline
var g_IsNetworked;
// Is this user in control of game settings (i.e. is a network server, or offline player)
var g_IsController;
// Server name, if user is a server, connected to the multiplayer lobby
var g_ServerName;
// Are we currently updating the GUI in response to network messages instead of user input
// (and therefore shouldn't send further messages to the network)
var g_IsInGuiUpdate;
// Is this user ready
var g_IsReady;
// There are some duplicate orders on init, we can ignore these [bool].
var g_ReadyInit = true;
// If no one has changed ready status, we have no need to spam the settings changed message.
// 2 - Host's initial ready, suppressed settings message, 1 - Will show settings message, <=0 - Suppressed settings message
var g_ReadyChanged = 2;
// Has the game started?
var g_GameStarted = false;
var g_PlayerAssignments = {};
// Default game setup attributes
var g_DefaultPlayerData = [];
var g_GameAttributes = {
settings: {}
};
var g_GameSpeeds = {};
var g_MapSizes = {};
var g_AIs = [];
var g_ChatMessages = [];
// Data caches
var g_MapData = {};
var g_CivData = {};
var g_MapFilters = [];
// Current number of assigned human players.
var g_AssignedCount = 0;
// To prevent the display locking up while we load the map metadata,
// we'll start with a 'loading' message and switch to the main screen in the
// tick handler
var g_LoadingState = 0; // 0 = not started, 1 = loading, 2 = loaded
// Filled by scripts in victory_conditions/
var g_VictoryConditions = {};
////////////////////////////////////////////////////////////////////////////////////////////////
function init(attribs)
{
switch (attribs.type)
{
case "offline":
g_IsNetworked = false;
g_IsController = true;
break;
case "server":
g_IsNetworked = true;
g_IsController = true;
break;
case "client":
g_IsNetworked = true;
g_IsController = false;
break;
default:
error(sprintf("Unexpected 'type' in gamesetup init: %(unexpectedType)s", { unexpectedType: attribs.type }));
}
if (attribs.serverName)
g_ServerName = attribs.serverName;
// Init the Cancel Button caption and tooltip
var cancelButton = Engine.GetGUIObjectByName("cancelGame");
if (!Engine.HasXmppClient())
cancelButton.tooltip = translate("Return to the main menu.");
else
cancelButton.tooltip = translate("Return to the lobby.");
}
// Called after the map data is loaded and cached
function initMain()
{
// Load AI list
g_AIs = Engine.GetAIs();
// Sort AIs by displayed name
g_AIs.sort(function (a, b) {
return a.data.name < b.data.name ? -1 : b.data.name < a.data.name ? +1 : 0;
});
// Get default player data - remove gaia
g_DefaultPlayerData = initPlayerDefaults();
g_DefaultPlayerData.shift();
for (var i = 0; i < g_DefaultPlayerData.length; ++i)
g_DefaultPlayerData[i].Civ = "random";
g_GameSpeeds = initGameSpeeds();
g_MapSizes = initMapSizes();
// Init civs
initCivNameList();
// Init map types
var mapTypes = Engine.GetGUIObjectByName("mapTypeSelection");
mapTypes.list = [translateWithContext("map", "Skirmish"), translateWithContext("map", "Random"), translate("Scenario")];
mapTypes.list_data = ["skirmish","random","scenario"];
// Setup map filters - will appear in order they are added
addFilter("default", translate("Default"), function(settings) { return settings && (settings.Keywords === undefined || !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"])); });
addFilter("naval", translate("Naval Maps"), function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["naval"]); });
addFilter("demo", translate("Demo Maps"), function(settings) { return settings && settings.Keywords !== undefined && keywordTestAND(settings.Keywords, ["demo"]); });
addFilter("all", translate("All Maps"), function(settings) { return true; });
// Populate map filters dropdown
var mapFilters = Engine.GetGUIObjectByName("mapFilterSelection");
mapFilters.list = getFilterNames();
mapFilters.list_data = getFilterIds();
g_GameAttributes.mapFilter = "default";
// Setup controls for host only
if (g_IsController)
{
mapTypes.selected = 0;
mapFilters.selected = 0;
// Create a unique ID for this match, to be used for identifying the same game reports
// for the lobby.
g_GameAttributes.matchID = Engine.GetMatchID();
initMapNameList();
var numPlayersSelection = Engine.GetGUIObjectByName("numPlayersSelection");
var players = [];
for (var i = 1; i <= MAX_PLAYERS; ++i)
players.push(i);
numPlayersSelection.list = players;
numPlayersSelection.list_data = players;
numPlayersSelection.selected = MAX_PLAYERS - 1;
var gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
gameSpeed.hidden = false;
Engine.GetGUIObjectByName("gameSpeedText").hidden = true;
gameSpeed.list = g_GameSpeeds.names;
gameSpeed.list_data = g_GameSpeeds.speeds;
gameSpeed.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.gameSpeed = g_GameSpeeds.speeds[this.selected];
updateGameAttributes();
}
gameSpeed.selected = g_GameSpeeds["default"];
var populationCaps = Engine.GetGUIObjectByName("populationCap");
populationCaps.list = POPULATION_CAP;
populationCaps.list_data = POPULATION_CAP_DATA;
populationCaps.selected = POPULATION_CAP_DEFAULTIDX;
populationCaps.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.settings.PopulationCap = POPULATION_CAP_DATA[this.selected];
updateGameAttributes();
}
var startingResourcesL = Engine.GetGUIObjectByName("startingResources");
startingResourcesL.list = STARTING_RESOURCES;
startingResourcesL.list_data = STARTING_RESOURCES_DATA;
startingResourcesL.selected = STARTING_RESOURCES_DEFAULTIDX;
startingResourcesL.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.settings.StartingResources = STARTING_RESOURCES_DATA[this.selected];
updateGameAttributes();
}
+ var ceasefireL = Engine.GetGUIObjectByName("ceasefire");
+ ceasefireL.list = CEASEFIRE;
+ ceasefireL.list_data = CEASEFIRE_DATA;
+ ceasefireL.selected = CEASEFIRE_DEFAULTIDX;
+ ceasefireL.onSelectionChange = function() {
+ if (this.selected != -1)
+ g_GameAttributes.settings.Ceasefire = CEASEFIRE_DATA[this.selected];
+
+ updateGameAttributes();
+ }
+
var victoryConditions = Engine.GetGUIObjectByName("victoryCondition");
var victories = getVictoryConditions();
victoryConditions.list = victories.text;
victoryConditions.list_data = victories.data;
victoryConditions.onSelectionChange = function() {
if (this.selected != -1)
{
g_GameAttributes.settings.GameType = victories.data[this.selected];
g_GameAttributes.settings.VictoryScripts = victories.scripts[this.selected];
}
updateGameAttributes();
};
victoryConditions.selected = VICTORY_DEFAULTIDX;
var mapSize = Engine.GetGUIObjectByName("mapSize");
mapSize.list = g_MapSizes.names;
mapSize.list_data = g_MapSizes.tiles;
mapSize.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.settings.Size = g_MapSizes.tiles[this.selected];
updateGameAttributes();
};
mapSize.selected = 0;
Engine.GetGUIObjectByName("revealMap").onPress = function() {
g_GameAttributes.settings.RevealMap = this.checked;
updateGameAttributes();
};
Engine.GetGUIObjectByName("exploreMap").onPress = function() {
g_GameAttributes.settings.ExploreMap = this.checked;
updateGameAttributes();
};
Engine.GetGUIObjectByName("disableTreasures").onPress = function() {
g_GameAttributes.settings.DisableTreasures = this.checked;
updateGameAttributes();
};
Engine.GetGUIObjectByName("lockTeams").onPress = function() {
g_GameAttributes.settings.LockTeams = this.checked;
updateGameAttributes();
};
Engine.GetGUIObjectByName("enableCheats").onPress = function() {
g_GameAttributes.settings.CheatsEnabled = this.checked;
updateGameAttributes();
};
Engine.GetGUIObjectByName("enableRating").onPress = function() {
g_GameAttributes.settings.RatingEnabled = this.checked;
Engine.SetRankedGame(this.checked);
Engine.GetGUIObjectByName("enableCheats").enabled = !this.checked;
Engine.GetGUIObjectByName("lockTeams").enabled = !this.checked;
updateGameAttributes();
};
}
else
{
// If we're a network client, disable all the map controls
Engine.GetGUIObjectByName("mapTypeSelection").hidden = true;
Engine.GetGUIObjectByName("mapTypeText").hidden = false;
Engine.GetGUIObjectByName("mapFilterSelection").hidden = true;
Engine.GetGUIObjectByName("mapFilterText").hidden = false;
Engine.GetGUIObjectByName("mapSelectionText").hidden = false;
Engine.GetGUIObjectByName("mapSelection").hidden = true;
Engine.GetGUIObjectByName("victoryConditionText").hidden = false;
Engine.GetGUIObjectByName("victoryCondition").hidden = true;
Engine.GetGUIObjectByName("gameSpeedText").hidden = false;
Engine.GetGUIObjectByName("gameSpeed").hidden = true;
// Disable player and game options controls
// TODO: Shouldn't players be able to choose their own assignment?
for (var i = 0; i < MAX_PLAYERS; ++i)
{
Engine.GetGUIObjectByName("playerAssignment["+i+"]").hidden = true;
Engine.GetGUIObjectByName("playerCiv["+i+"]").hidden = true;
Engine.GetGUIObjectByName("playerTeam["+i+"]").hidden = true;
}
Engine.GetGUIObjectByName("numPlayersSelection").hidden = true;
Engine.GetGUIObjectByName("startGame").enabled = true;
}
// Set up multiplayer/singleplayer bits:
if (!g_IsNetworked)
{
Engine.GetGUIObjectByName("chatPanel").hidden = true;
Engine.GetGUIObjectByName("enableCheats").checked = true;
g_GameAttributes.settings.CheatsEnabled = true;
}
else
{
Engine.GetGUIObjectByName("optionCheats").hidden = false;
Engine.GetGUIObjectByName("enableCheats").checked = false;
g_GameAttributes.settings.CheatsEnabled = false;
// Setup ranked option if we are connected to the lobby.
if (Engine.HasXmppClient())
{
Engine.GetGUIObjectByName("optionRating").hidden = false;
Engine.GetGUIObjectByName("enableRating").checked = Engine.IsRankedGame();
g_GameAttributes.settings.RatingEnabled = Engine.IsRankedGame();
// We force locked teams and disabled cheats in ranked games.
Engine.GetGUIObjectByName("enableCheats").enabled = !Engine.IsRankedGame();
Engine.GetGUIObjectByName("lockTeams").enabled = !Engine.IsRankedGame();
}
if (g_IsController)
{
Engine.GetGUIObjectByName("enableCheatsText").hidden = true;
Engine.GetGUIObjectByName("enableCheats").hidden = false;
if (Engine.HasXmppClient())
{
Engine.GetGUIObjectByName("enableRatingText").hidden = true;
Engine.GetGUIObjectByName("enableRating").hidden = false;
}
}
}
// Settings for all possible player slots
var boxSpacing = 32;
for (var i = 0; i < MAX_PLAYERS; ++i)
{
// Space player boxes
var box = Engine.GetGUIObjectByName("playerBox["+i+"]");
var boxSize = box.size;
var h = boxSize.bottom - boxSize.top;
boxSize.top = i * boxSpacing;
boxSize.bottom = i * boxSpacing + h;
box.size = boxSize;
// Populate team dropdowns
var team = Engine.GetGUIObjectByName("playerTeam["+i+"]");
team.list = [translateWithContext("team", "None"), "1", "2", "3", "4"];
team.list_data = [-1, 0, 1, 2, 3];
team.selected = 0;
let playerSlot = i; // declare for inner function use
team.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.settings.PlayerData[playerSlot].Team = this.selected - 1;
updateGameAttributes();
};
// Set events
var civ = Engine.GetGUIObjectByName("playerCiv["+i+"]");
civ.onSelectionChange = function() {
if ((this.selected != -1)&&(g_GameAttributes.mapType !== "scenario"))
g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected];
updateGameAttributes();
};
}
if (g_IsNetworked)
{
// For multiplayer, focus the chat input box by default
Engine.GetGUIObjectByName("chatInput").focus();
}
else
{
// For single-player, focus the map list by default,
// to allow easy keyboard selection of maps
Engine.GetGUIObjectByName("mapSelection").focus();
}
if (g_IsController)
{
loadGameAttributes();
// Sync g_GameAttributes to everyone.
if (g_IsInGuiUpdate)
warn("initMain() called while in GUI update");
updateGameAttributes();
}
}
function handleNetMessage(message)
{
log("Net message: "+uneval(message));
switch (message.type)
{
case "netstatus":
switch (message.status)
{
case "disconnected":
cancelSetup();
if (Engine.HasXmppClient())
Engine.SwitchGuiPage("page_lobby.xml");
else
Engine.SwitchGuiPage("page_pregame.xml");
reportDisconnect(message.reason);
break;
default:
error("Unrecognised netstatus type "+message.status);
break;
}
break;
case "gamesetup":
if (message.data) // (the host gets undefined data on first connect, so skip that)
{
g_GameAttributes = message.data;
// Validate some settings for rated games.
if (g_GameAttributes.settings.RatingEnabled)
{
// Cheats can never be on in rated games.
g_GameAttributes.settings.CheatsEnabled = false;
// Teams must be locked in rated games.
g_GameAttributes.settings.LockTeams = true;
}
}
onGameAttributesChange();
break;
case "players":
var resetReady = false;
var newPlayer = "";
// Find and report all joinings/leavings
for (var host in message.hosts)
{
if (!g_PlayerAssignments[host])
{
// If we have extra player slots and we are the controller, give the player an ID.
if (g_IsController && message.hosts[host].player === -1 && g_AssignedCount < g_GameAttributes.settings.PlayerData.length)
Engine.AssignNetworkPlayer(g_AssignedCount + 1, host);
addChatMessage({ "type": "connect", "username": message.hosts[host].name });
newPlayer = host;
}
}
for (var host in g_PlayerAssignments)
{
if (!message.hosts[host])
{
addChatMessage({ "type": "disconnect", "guid": host });
if (g_PlayerAssignments[host].player != -1)
resetReady = true; // Observers shouldn't reset ready.
}
}
// Update the player list
g_PlayerAssignments = message.hosts;
updatePlayerList();
if (g_PlayerAssignments[newPlayer] && g_PlayerAssignments[newPlayer].player != -1)
resetReady = true;
if (resetReady)
resetReadyData(); // Observers shouldn't reset ready.
updateReadyUI();
if (g_IsController)
sendRegisterGameStanza();
break;
case "start":
if (g_IsController && Engine.HasXmppClient())
{
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ]
Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", "));
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments,
"isController": g_IsController
});
break;
case "chat":
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
break;
// Singular client to host message
case "ready":
g_ReadyChanged -= 1;
if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1)
addChatMessage({ "type": "ready", "guid": message.guid, "ready": +message.status == 1 });
if (!g_IsController)
break;
g_PlayerAssignments[message.guid].status = +message.status == 1;
Engine.SetNetworkPlayerStatus(message.guid, +message.status);
updateReadyUI();
break;
default:
error("Unrecognised net message type "+message.type);
}
}
function getMapDisplayName(map)
{
var mapData = loadMapData(map);
if (!mapData || !mapData.settings || !mapData.settings.Name)
{
log("Map data missing in scenario '"+map+"' - likely unsupported format");
return map;
}
return mapData.settings.Name;
}
function getMapPreview(map)
{
var mapData = loadMapData(map);
if (!mapData || !mapData.settings || !mapData.settings.Preview)
return "nopreview.png";
return mapData.settings.Preview;
}
// Get a setting if it exists or return default
function getSetting(settings, defaults, property)
{
if (settings && (property in settings))
return settings[property];
// Use defaults
if (defaults && (property in defaults))
return defaults[property];
return undefined;
}
// Initialize the dropdowns containing all the available civs
function initCivNameList()
{
// Cache civ data
g_CivData = loadCivData();
// Extract name/code, and skip civs that are explicitly disabled
// (intended for unusable incomplete civs)
var civList = [
{ "name": civ.Name, "code": civ.Code }
for each (civ in g_CivData)
if (civ.SelectableInGameSetup !== false)
];
// Alphabetically sort the list, ignoring case
civList.sort(sortNameIgnoreCase);
var civListNames = [ civ.name for each (civ in civList) ];
var civListCodes = [ civ.code for each (civ in civList) ];
// Add random civ to beginning of list
civListNames.unshift('[color="orange"]' + translateWithContext("civilization", "Random") + '[/color]');
civListCodes.unshift("random");
// Update the dropdowns
for (var i = 0; i < MAX_PLAYERS; ++i)
{
var civ = Engine.GetGUIObjectByName("playerCiv["+i+"]");
civ.list = civListNames;
civ.list_data = civListCodes;
civ.selected = 0;
}
}
// Initialise the list control containing all the available maps
function initMapNameList()
{
// Get a list of map filenames
// TODO: Should verify these are valid maps before adding to list
var mapSelectionBox = Engine.GetGUIObjectByName("mapSelection")
var mapFiles;
switch (g_GameAttributes.mapType)
{
case "scenario":
case "skirmish":
mapFiles = getXMLFileList(g_GameAttributes.mapPath);
break;
case "random":
mapFiles = getJSONFileList(g_GameAttributes.mapPath);
break;
default:
error(sprintf("initMapNameList: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return;
}
// Apply map filter, if any defined
var mapList = [];
for (var i = 0; i < mapFiles.length; ++i)
{
var file = g_GameAttributes.mapPath + mapFiles[i];
var mapData = loadMapData(file);
if (g_GameAttributes.mapFilter && mapData && testFilter(g_GameAttributes.mapFilter, mapData.settings))
mapList.push({ "name": getMapDisplayName(file), "file": file });
}
// Alphabetically sort the list, ignoring case
translateObjectKeys(mapList, ["name"]);
mapList.sort(sortNameIgnoreCase);
var mapListNames = [ map.name for each (map in mapList) ];
var mapListFiles = [ map.file for each (map in mapList) ];
// Select the default map
var selected = mapListFiles.indexOf(g_GameAttributes.map);
// Default to the first element if list is not empty and we can't find the one we searched for
if (selected == -1 && mapList.length)
{
selected = 0;
}
// Update the list control
if (g_GameAttributes.mapType == "random")
{
mapListNames.unshift("[color=\"orange\"]" + translateWithContext("map", "Random") + "[/color]");
mapListFiles.unshift("random");
}
mapSelectionBox.list = mapListNames;
mapSelectionBox.list_data = mapListFiles;
mapSelectionBox.selected = selected;
}
function loadMapData(name)
{
if (!name)
return undefined;
if (!g_MapData[name])
{
switch (g_GameAttributes.mapType)
{
case "scenario":
case "skirmish":
g_MapData[name] = Engine.LoadMapSettings(name);
break;
case "random":
if (name == "random")
// To be defined later.
g_MapData[name] = { settings: { "Name": "", "Description": "" } };
else
g_MapData[name] = Engine.ReadJSONFile(name+".json");
break;
default:
error(sprintf("loadMapData: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return undefined;
}
}
return g_MapData[name];
}
const FILEPATH_MATCHSETTINGS_SP = "config/matchsettings.json";
const FILEPATH_MATCHSETTINGS_MP = "config/matchsettings.mp.json";
function loadGameAttributes()
{
if (Engine.ConfigDB_GetValue("user", "persistmatchsettings") != "true")
return;
var settingsFile = g_IsNetworked ? FILEPATH_MATCHSETTINGS_MP : FILEPATH_MATCHSETTINGS_SP;
if (!Engine.FileExists(settingsFile))
return;
var attrs = Engine.ReadJSONFile(settingsFile);
if (!attrs || !attrs.settings)
return;
g_IsInGuiUpdate = true;
var mapName = attrs.map || "";
var mapSettings = attrs.settings;
g_GameAttributes = attrs;
// Assign new seeds and match id
g_GameAttributes.matchID = Engine.GetMatchID();
mapSettings.Seed = Math.floor(Math.random() * 65536);
mapSettings.AISeed = Math.floor(Math.random() * 65536);
// Ensure that cheats are enabled in singleplayer
if (!g_IsNetworked)
mapSettings.CheatsEnabled = true;
var aiCodes = [ ai.id for each (ai in g_AIs) ];
var civListCodes = [ civ.Code for each (civ in g_CivData) if (civ.SelectableInGameSetup !== false) ];
civListCodes.push("random");
var playerData = mapSettings.PlayerData;
// Validate player civs
if (playerData)
{
for (var i = 0; i < playerData.length; ++i)
{
if (civListCodes.indexOf(playerData[i].Civ) < 0)
playerData[i].Civ = "random";
}
}
// Refresh probably obsoleted/incomplete map data.
var newMapData = loadMapData(mapName);
if (newMapData && newMapData.settings)
{
for (var prop in newMapData.settings)
mapSettings[prop] = newMapData.settings[prop];
// Set player data
if (playerData)
mapSettings.PlayerData = playerData;
}
var mapFilterSelection = Engine.GetGUIObjectByName("mapFilterSelection");
mapFilterSelection.selected = mapFilterSelection.list_data.indexOf(attrs.mapFilter);
var mapTypeSelection = Engine.GetGUIObjectByName("mapTypeSelection");
mapTypeSelection.selected = mapTypeSelection.list_data.indexOf(attrs.mapType);
initMapNameList();
var mapSelectionBox = Engine.GetGUIObjectByName("mapSelection");
mapSelectionBox.selected = mapSelectionBox.list_data.indexOf(mapName);
if (mapSettings.PopulationCap)
{
var populationCapBox = Engine.GetGUIObjectByName("populationCap");
populationCapBox.selected = populationCapBox.list_data.indexOf(mapSettings.PopulationCap);
}
if (mapSettings.StartingResources)
{
var startingResourcesBox = Engine.GetGUIObjectByName("startingResources");
startingResourcesBox.selected = startingResourcesBox.list_data.indexOf(mapSettings.StartingResources);
}
+ if (mapSettings.Ceasefire)
+ {
+ var ceasefireBox = Engine.GetGUIObjectByName("ceasefire");
+ ceasefireBox.selected = ceasefireBox.list_data.indexOf(mapSettings.Ceasefire);
+ }
+
if (attrs.gameSpeed)
{
var gameSpeedBox = Engine.GetGUIObjectByName("gameSpeed");
gameSpeedBox.selected = g_GameSpeeds.speeds.indexOf(attrs.gameSpeed);
}
if (!Engine.HasXmppClient())
{
g_GameAttributes.settings.RatingEnabled = false;
Engine.SetRankedGame(false);
Engine.GetGUIObjectByName("enableRating").checked = false;
Engine.GetGUIObjectByName("enableCheats").enabled = true;
Engine.GetGUIObjectByName("lockTeams").enabled = true;
}
g_IsInGuiUpdate = false;
onGameAttributesChange();
}
function saveGameAttributes()
{
var attributes = Engine.ConfigDB_GetValue("user", "persistmatchsettings") == "true" ? g_GameAttributes : {};
Engine.WriteJSONFile(g_IsNetworked ? FILEPATH_MATCHSETTINGS_MP : FILEPATH_MATCHSETTINGS_SP, attributes);
}
////////////////////////////////////////////////////////////////////////////////////////////////
// GUI event handlers
function cancelSetup()
{
if (g_IsController)
saveGameAttributes();
Engine.DisconnectNetworkGame();
if (Engine.HasXmppClient())
{
// Set player presence
Engine.LobbySetPlayerPresence("available");
// Unregister the game
if (g_IsController)
Engine.SendUnregisterGame();
}
}
function onTick()
{
// First tick happens before first render, so don't load yet
if (g_LoadingState == 0)
{
g_LoadingState++;
}
else if (g_LoadingState == 1)
{
Engine.GetGUIObjectByName("loadingWindow").hidden = true;
Engine.GetGUIObjectByName("setupWindow").hidden = false;
initMain();
g_LoadingState++;
}
else if (g_LoadingState == 2)
{
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
}
}
// Called when user selects number of players
function selectNumPlayers(num)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change number of players
if (g_IsNetworked && !g_IsController)
return;
// Only meaningful for random maps
if (g_GameAttributes.mapType != "random")
return;
// Update player data
var pData = g_GameAttributes.settings.PlayerData;
if (pData && num < pData.length)
{
// Remove extra player data
g_GameAttributes.settings.PlayerData = pData.slice(0, num);
}
else
{
// Add player data from defaults
for (var i = pData.length; i < num; ++i)
g_GameAttributes.settings.PlayerData.push(g_DefaultPlayerData[i]);
}
// Some players may have lost their assigned slot
for (var guid in g_PlayerAssignments)
{
var player = g_PlayerAssignments[guid].player;
if (player > num)
{
if (g_IsNetworked)
Engine.AssignNetworkPlayer(player, "");
else
g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1, "ready": 0} };
}
}
updateGameAttributes();
}
// Called when the user selects a map type from the list
function selectMapType(type)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change map type
if (g_IsNetworked && !g_IsController)
return;
// Reset game attributes
g_GameAttributes.map = "";
g_GameAttributes.mapType = type;
// Clear old map data
g_MapData = {};
// Select correct path
switch (g_GameAttributes.mapType)
{
case "scenario":
// Set a default map
g_GameAttributes.mapPath = "maps/scenarios/";
g_GameAttributes.map = g_GameAttributes.mapPath + (g_IsNetworked ? DEFAULT_NETWORKED_MAP : DEFAULT_OFFLINE_MAP);
g_GameAttributes.settings.AISeed = Math.floor(Math.random() * 65536);
break;
case "skirmish":
g_GameAttributes.mapPath = "maps/skirmishes/";
g_GameAttributes.settings = {
PlayerData: g_DefaultPlayerData.slice(0, 4),
Seed: Math.floor(Math.random() * 65536),
AISeed: Math.floor(Math.random() * 65536),
CheatsEnabled: g_GameAttributes.settings.CheatsEnabled
};
break;
case "random":
g_GameAttributes.mapPath = "maps/random/";
g_GameAttributes.settings = {
PlayerData: g_DefaultPlayerData.slice(0, 4),
Seed: Math.floor(Math.random() * 65536),
AISeed: Math.floor(Math.random() * 65536),
CheatsEnabled: g_GameAttributes.settings.CheatsEnabled
};
break;
default:
error(sprintf("selectMapType: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return;
}
initMapNameList();
updateGameAttributes();
}
function selectMapFilter(id)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change map filter
if (g_IsNetworked && !g_IsController)
return;
g_GameAttributes.mapFilter = id;
initMapNameList();
updateGameAttributes();
}
// Called when the user selects a map from the list
function selectMap(name)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
// Network clients can't change map
if (g_IsNetworked && !g_IsController)
return;
// Return if we have no map
if (!name)
return;
// reset some map specific properties which are not necessarily redefined on each map
for (let prop of ["TriggerScripts", "CircularMap", "Garrison"])
if (g_GameAttributes.settings[prop] !== undefined)
g_GameAttributes.settings[prop] = undefined;
var mapData = loadMapData(name);
var mapSettings = (mapData && mapData.settings ? deepcopy(mapData.settings) : {});
// Reset victory conditions
var victories = getVictoryConditions();
var victoryIdx = (mapSettings.GameType !== undefined && victories.data.indexOf(mapSettings.GameType) != -1 ? victories.data.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
g_GameAttributes.settings.GameType = victories.data[victoryIdx];
g_GameAttributes.settings.VictoryScripts = victories.scripts[victoryIdx];
// Copy any new settings
g_GameAttributes.map = name;
g_GameAttributes.script = mapSettings.Script;
if (g_GameAttributes.map !== "random")
for (var prop in mapSettings)
g_GameAttributes.settings[prop] = mapSettings[prop];
// Ignore gaia
if (g_GameAttributes.settings.PlayerData.length && !g_GameAttributes.settings.PlayerData[0])
g_GameAttributes.settings.PlayerData.shift();
// Use default AI if the map doesn't specify any explicitly
for (var i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i)
{
if (!('AI' in g_GameAttributes.settings.PlayerData[i]))
g_GameAttributes.settings.PlayerData[i].AI = g_DefaultPlayerData[i].AI;
if (!('AIDiff' in g_GameAttributes.settings.PlayerData[i]))
g_GameAttributes.settings.PlayerData[i].AIDiff = g_DefaultPlayerData[i].AIDiff;
}
// Reset player assignments on map change
if (!g_IsNetworked)
{ // Slot 1
g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1, "ready": 0} };
}
else
{
var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : g_GameAttributes.settings.PlayerData.length);
for (var guid in g_PlayerAssignments)
{ // Unassign extra players
var player = g_PlayerAssignments[guid].player;
if (player <= MAX_PLAYERS && player > numPlayers)
Engine.AssignNetworkPlayer(player, "");
}
}
updateGameAttributes();
}
function launchGame()
{
if (g_IsNetworked && !g_IsController)
{
error("Only host can start game");
return;
}
// Check that we have a map
if (!g_GameAttributes.map)
return;
saveGameAttributes();
if (g_GameAttributes.map == "random")
{
let victoryScriptsSelected = g_GameAttributes.settings.VictoryScripts;
let gameTypeSelected = g_GameAttributes.settings.GameType;
selectMap(Engine.GetGUIObjectByName("mapSelection").list_data[Math.floor(Math.random() *
(Engine.GetGUIObjectByName("mapSelection").list.length - 1)) + 1]);
g_GameAttributes.settings.VictoryScripts = victoryScriptsSelected;
g_GameAttributes.settings.GameType = gameTypeSelected;
}
if (!g_GameAttributes.settings.TriggerScripts)
g_GameAttributes.settings.TriggerScripts = g_GameAttributes.settings.VictoryScripts;
else
g_GameAttributes.settings.TriggerScripts = g_GameAttributes.settings.VictoryScripts.concat(g_GameAttributes.settings.TriggerScripts);
g_GameStarted = true;
g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
var numPlayers = g_GameAttributes.settings.PlayerData.length;
// Assign random civilizations to players with that choice
// (this is synchronized because we're the host)
var cultures = [];
for each (var civ in g_CivData)
if (civ.Culture !== undefined && cultures.indexOf(civ.Culture) < 0 && civ.SelectableInGameSetup !== false)
cultures.push(civ.Culture);
var allcivs = new Array(cultures.length);
for (var i = 0; i < allcivs.length; ++i)
allcivs[i] = [];
for each (var civ in g_CivData)
if (civ.Culture !== undefined && civ.SelectableInGameSetup !== false)
allcivs[cultures.indexOf(civ.Culture)].push(civ.Code);
const romanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
for (var i = 0; i < numPlayers; ++i)
{
var civs = allcivs[Math.floor(Math.random()*allcivs.length)];
if (!g_GameAttributes.settings.PlayerData[i].Civ || g_GameAttributes.settings.PlayerData[i].Civ == "random")
g_GameAttributes.settings.PlayerData[i].Civ = civs[Math.floor(Math.random()*civs.length)];
// Setting names for AI players. Check if the player is AI and the match is not a scenario
if (g_GameAttributes.mapType !== "scenario" && g_GameAttributes.settings.PlayerData[i].AI)
{
// Get the civ specific names
if (g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames !== undefined)
var civAINames = shuffleArray(g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames);
else
var civAINames = [g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].Name];
// Choose the name
var usedName = 0;
if (i < civAINames.length)
var chosenName = civAINames[i];
else
var chosenName = civAINames[Math.floor(Math.random() * civAINames.length)];
for (var j = 0; j < numPlayers; ++j)
if (g_GameAttributes.settings.PlayerData[j].Name && g_GameAttributes.settings.PlayerData[j].Name.indexOf(chosenName) !== -1)
usedName++;
// Assign civ specific names to AI players
chosenName = translate(chosenName);
if (usedName)
g_GameAttributes.settings.PlayerData[i].Name = sprintf(translate("%(playerName)s %(romanNumber)s"), { playerName: chosenName, romanNumber: romanNumbers[usedName+1]});
else
g_GameAttributes.settings.PlayerData[i].Name = chosenName;
}
}
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
Engine.StartNetworkGame();
}
else
{
// Find the player ID which the user has been assigned to
var numPlayers = g_GameAttributes.settings.PlayerData.length;
var playerID = -1;
for (var i = 0; i < numPlayers; ++i)
{
var assignBox = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
if (assignBox.list_data[assignBox.selected] == "local")
playerID = i+1;
}
// Remove extra player data
g_GameAttributes.settings.PlayerData = g_GameAttributes.settings.PlayerData.slice(0, numPlayers);
Engine.StartGame(g_GameAttributes, playerID);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments
});
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
function onGameAttributesChange()
{
g_IsInGuiUpdate = true;
// Don't set any attributes here, just show the changes in GUI
var mapName = g_GameAttributes.map || "";
var mapSettings = g_GameAttributes.settings;
var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : MAX_PLAYERS);
// Update some controls for clients
if (!g_IsController)
{
var mapFilterSelection = Engine.GetGUIObjectByName("mapFilterSelection");
var mapFilterId = mapFilterSelection.list_data.indexOf(g_GameAttributes.mapFilter);
Engine.GetGUIObjectByName("mapFilterText").caption = mapFilterSelection.list[mapFilterId];
var mapTypeSelection = Engine.GetGUIObjectByName("mapTypeSelection");
var idx = mapTypeSelection.list_data.indexOf(g_GameAttributes.mapType);
Engine.GetGUIObjectByName("mapTypeText").caption = mapTypeSelection.list[idx];
var mapSelectionBox = Engine.GetGUIObjectByName("mapSelection");
mapSelectionBox.selected = mapSelectionBox.list_data.indexOf(mapName);
Engine.GetGUIObjectByName("mapSelectionText").caption = translate(getMapDisplayName(mapName));
if (mapSettings.PopulationCap)
{
var populationCapBox = Engine.GetGUIObjectByName("populationCap");
populationCapBox.selected = populationCapBox.list_data.indexOf(mapSettings.PopulationCap);
}
if (mapSettings.StartingResources)
{
var startingResourcesBox = Engine.GetGUIObjectByName("startingResources");
startingResourcesBox.selected = startingResourcesBox.list_data.indexOf(mapSettings.StartingResources);
}
+ if (mapSettings.Ceasefire)
+ {
+ var ceasefireBox = Engine.GetGUIObjectByName("ceasefire");
+ ceasefireBox.selected = ceasefireBox.list_data.indexOf(mapSettings.Ceasefire);
+ }
+
initMapNameList();
}
// Controls common to all map types
var numPlayersSelection = Engine.GetGUIObjectByName("numPlayersSelection");
var revealMap = Engine.GetGUIObjectByName("revealMap");
var exploreMap = Engine.GetGUIObjectByName("exploreMap");
var disableTreasures = Engine.GetGUIObjectByName("disableTreasures");
var victoryCondition = Engine.GetGUIObjectByName("victoryCondition");
var lockTeams = Engine.GetGUIObjectByName("lockTeams");
var mapSize = Engine.GetGUIObjectByName("mapSize");
var enableCheats = Engine.GetGUIObjectByName("enableCheats");
var enableRating = Engine.GetGUIObjectByName("enableRating");
var populationCap = Engine.GetGUIObjectByName("populationCap");
var startingResources = Engine.GetGUIObjectByName("startingResources");
+ var ceasefire = Engine.GetGUIObjectByName("ceasefire");
var numPlayersText= Engine.GetGUIObjectByName("numPlayersText");
var mapSizeDesc = Engine.GetGUIObjectByName("mapSizeDesc");
var mapSizeText = Engine.GetGUIObjectByName("mapSizeText");
var revealMapText = Engine.GetGUIObjectByName("revealMapText");
var exploreMapText = Engine.GetGUIObjectByName("exploreMapText");
var disableTreasuresText = Engine.GetGUIObjectByName("disableTreasuresText");
var victoryConditionText = Engine.GetGUIObjectByName("victoryConditionText");
var lockTeamsText = Engine.GetGUIObjectByName("lockTeamsText");
var enableCheatsText = Engine.GetGUIObjectByName("enableCheatsText");
var enableRatingText = Engine.GetGUIObjectByName("enableRatingText");
var populationCapText = Engine.GetGUIObjectByName("populationCapText");
var startingResourcesText = Engine.GetGUIObjectByName("startingResourcesText");
+ var ceasefireText = Engine.GetGUIObjectByName("ceasefireText");
var gameSpeedText = Engine.GetGUIObjectByName("gameSpeedText");
var gameSpeedBox = Engine.GetGUIObjectByName("gameSpeed");
// We have to check for undefined on these properties as not all maps define them.
var sizeIdx = (mapSettings.Size !== undefined && g_MapSizes.tiles.indexOf(mapSettings.Size) != -1 ? g_MapSizes.tiles.indexOf(mapSettings.Size) : g_MapSizes["default"]);
var speedIdx = (g_GameAttributes.gameSpeed !== undefined && g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) != -1) ? g_GameSpeeds.speeds.indexOf(g_GameAttributes.gameSpeed) : g_GameSpeeds["default"];
var victories = getVictoryConditions();
var victoryIdx = (mapSettings.GameType !== undefined && victories.data.indexOf(mapSettings.GameType) != -1 ? victories.data.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
enableCheats.checked = (mapSettings.CheatsEnabled === undefined || !mapSettings.CheatsEnabled ? false : true)
enableCheatsText.caption = (enableCheats.checked ? translate("Yes") : translate("No"));
if (mapSettings.RatingEnabled !== undefined)
{
enableRating.checked = mapSettings.RatingEnabled;
Engine.SetRankedGame(enableRating.checked);
enableRatingText.caption = (enableRating.checked ? translate("Yes") : translate("No"));
enableCheats.enabled = !enableRating.checked;
lockTeams.enabled = !enableRating.checked;
}
else
enableRatingText.caption = "Unknown";
gameSpeedText.caption = g_GameSpeeds.names[speedIdx];
gameSpeedBox.selected = speedIdx;
populationCap.selected = (mapSettings.PopulationCap !== undefined && POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);
populationCapText.caption = POPULATION_CAP[populationCap.selected];
startingResources.selected = (mapSettings.StartingResources !== undefined && STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) != -1 ? STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) : STARTING_RESOURCES_DEFAULTIDX);
startingResourcesText.caption = STARTING_RESOURCES[startingResources.selected];
+ ceasefire.selected = (mapSettings.Ceasefire !== undefined && CEASEFIRE_DATA.indexOf(mapSettings.Ceasefire) != -1 ? CEASEFIRE_DATA.indexOf(mapSettings.Ceasefire) : CEASEFIRE_DEFAULTIDX);
+ ceasefireText.caption = CEASEFIRE[ceasefire.selected];
// Update map preview
Engine.GetGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);
// Hide/show settings depending on whether we can change them or not
var updateDisplay = function(guiObjChg, guiObjDsp, chg) {
guiObjChg.hidden = !chg;
guiObjDsp.hidden = chg;
};
// Handle map type specific logic
switch (g_GameAttributes.mapType)
{
case "random":
mapSizeDesc.hidden = false;
updateDisplay(numPlayersSelection, numPlayersText, g_IsController);
updateDisplay(mapSize, mapSizeText, g_IsController);
updateDisplay(revealMap, revealMapText, g_IsController);
updateDisplay(exploreMap, exploreMapText, g_IsController);
updateDisplay(disableTreasures, disableTreasuresText, g_IsController);
updateDisplay(victoryCondition, victoryConditionText, g_IsController);
updateDisplay(lockTeams, lockTeamsText, g_IsController);
updateDisplay(populationCap, populationCapText, g_IsController);
updateDisplay(startingResources, startingResourcesText, g_IsController);
+ updateDisplay(ceasefire, ceasefireText, g_IsController);
if (g_IsController)
{
//Host
numPlayersSelection.selected = numPlayers - 1;
mapSize.selected = sizeIdx;
revealMap.checked = (mapSettings.RevealMap ? true : false);
exploreMap.checked = (mapSettings.ExploreMap ? true : false);
disableTreasures.checked = (mapSettings.DisableTreasures ? true : false);
victoryCondition.selected = victoryIdx;
lockTeams.checked = (mapSettings.LockTeams ? true : false);
}
else
{
// Client
numPlayersText.caption = numPlayers;
mapSizeText.caption = g_MapSizes.names[sizeIdx];
revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
exploreMapText.caption = (mapSettings.ExporeMap ? translate("Yes") : translate("No"));
disableTreasuresText.caption = (mapSettings.DisableTreasures ? translate("Yes") : translate("No"));
victoryConditionText.caption = victories.text[victoryIdx];
lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
}
break;
case "skirmish":
mapSizeText.caption = translate("Default");
numPlayersText.caption = numPlayers;
numPlayersText.hidden = false;
numPlayersSelection.hidden = true;
mapSize.hidden = true;
mapSizeText.hidden = true;
mapSizeDesc.hidden = true;
updateDisplay(revealMap, revealMapText, g_IsController);
updateDisplay(exploreMap, exploreMapText, g_IsController);
updateDisplay(disableTreasures, disableTreasuresText, g_IsController);
updateDisplay(victoryCondition, victoryConditionText, g_IsController);
updateDisplay(lockTeams, lockTeamsText, g_IsController);
updateDisplay(populationCap, populationCapText, g_IsController);
updateDisplay(startingResources, startingResourcesText, g_IsController);
+ updateDisplay(ceasefire, ceasefireText, g_IsController);
if (g_IsController)
{
//Host
revealMap.checked = (mapSettings.RevealMap ? true : false);
exploreMap.checked = (mapSettings.ExploreMap ? true : false);
disableTreasures.checked = (mapSettings.DisableTreasures ? true : false);
victoryCondition.selected = victoryIdx;
lockTeams.checked = (mapSettings.LockTeams ? true : false);
}
else
{
// Client
revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
exploreMapText.caption = (mapSettings.ExploreMap ? translate("Yes") : translate("No"));
disableTreasuresText.caption = (mapSettings.DisableTreasures ? translate("Yes") : translate("No"));
victoryConditionText.caption = victories.text[victoryIdx];
lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
}
break;
case "scenario":
// For scenario just reflect settings for the current map
numPlayersSelection.hidden = true;
- mapSize.hidden = true;
- revealMap.hidden = true;
- exploreMap.hidden = true;
- disableTreasures.hidden = true;
- victoryCondition.hidden = true;
- lockTeams.hidden = true;
numPlayersText.hidden = false;
+ mapSize.hidden = true;
mapSizeText.hidden = true;
mapSizeDesc.hidden = true;
+ revealMap.hidden = true;
revealMapText.hidden = false;
+ exploreMap.hidden = true;
exploreMapText.hidden = false;
+ disableTreasures.hidden = true;
disableTreasuresText.hidden = false;
+ victoryCondition.hidden = true;
victoryConditionText.hidden = false;
+ lockTeams.hidden = true;
lockTeamsText.hidden = false;
- populationCap.hidden = true;
- populationCapText.hidden = false;
startingResources.hidden = true;
startingResourcesText.hidden = false;
-
+ populationCap.hidden = true;
+ populationCapText.hidden = false;
+ ceasefire.hidden = true;
+ ceasefireText.hidden = false;
+
numPlayersText.caption = numPlayers;
mapSizeText.caption = translate("Default");
revealMapText.caption = (mapSettings.RevealMap ? translate("Yes") : translate("No"));
exploreMapText.caption = (mapSettings.ExploreMap ? translate("Yes") : translate("No"));
disableTreasuresText.caption = translate("No");
victoryConditionText.caption = victories.text[victoryIdx];
lockTeamsText.caption = (mapSettings.LockTeams ? translate("Yes") : translate("No"));
- Engine.GetGUIObjectByName("populationCap").selected = POPULATION_CAP_DEFAULTIDX;
+ startingResourcesText.caption = translate("Determined by scenario");
+ populationCapText.caption = translate("Determined by scenario");
+ ceasefireText.caption = translate("Determined by scenario");
break;
default:
error(sprintf("onGameAttributesChange: Unexpected map type '%(mapType)s'", { mapType: g_GameAttributes.mapType }));
return;
}
// Display map name
if (mapName == "random")
{
var mapDisplayName = translateWithContext("map", "Random");
mapSettings.Description = markForTranslation("Randomly selects a map from the list");
}
else
var mapDisplayName = translate(getMapDisplayName(mapName));
Engine.GetGUIObjectByName("mapInfoName").caption = mapDisplayName;
// Load the description from the map file, if there is one
var description = mapSettings.Description ? translate(mapSettings.Description) : translate("Sorry, no description available.");
// Describe the number of players and the victory conditions
var playerString = sprintf(translatePlural("%(number)s player. ", "%(number)s players. ", numPlayers), { number: numPlayers });
let victory = translate(victories.text[victoryIdx]);
if (victoryIdx != VICTORY_DEFAULTIDX)
victory = "[color=\"orange\"]" + victory + "[/color]";
playerString += translate("Victory Condition:") + " " + victory + ".\n\n" + description;
for (var i = 0; i < MAX_PLAYERS; ++i)
{
// Show only needed player slots
Engine.GetGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers);
// Show player data or defaults as necessary
if (i >= numPlayers)
continue;
var pName = Engine.GetGUIObjectByName("playerName["+i+"]");
var pAssignment = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
var pAssignmentText = Engine.GetGUIObjectByName("playerAssignmentText["+i+"]");
var pCiv = Engine.GetGUIObjectByName("playerCiv["+i+"]");
var pCivText = Engine.GetGUIObjectByName("playerCivText["+i+"]");
var pTeam = Engine.GetGUIObjectByName("playerTeam["+i+"]");
var pTeamText = Engine.GetGUIObjectByName("playerTeamText["+i+"]");
var pColor = Engine.GetGUIObjectByName("playerColor["+i+"]");
// Player data / defaults
var pData = mapSettings.PlayerData ? mapSettings.PlayerData[i] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[i] : {};
// Common to all game types
var color = rgbToGuiColor(getSetting(pData, pDefs, "Color"));
pColor.sprite = "color:" + color + " 100";
pName.caption = translate(getSetting(pData, pDefs, "Name"));
var team = getSetting(pData, pDefs, "Team");
var civ = getSetting(pData, pDefs, "Civ");
// Nobody but the controller can assign people
pAssignmentText.hidden = g_IsController;
pAssignment.hidden = !g_IsController;
if (!pAssignment.list[0])
pAssignmentText.caption = translate("Loading...");
else
pAssignmentText.caption = pAssignment.list[pAssignment.selected === -1 ? 0 : pAssignment.selected];
// For clients or scenarios, hide some player dropdowns
// TODO: Allow clients to choose their own civ and team
if (!g_IsController || g_GameAttributes.mapType == "scenario")
{
pCivText.hidden = false;
pCiv.hidden = true;
pTeamText.hidden = false;
pTeam.hidden = true;
// Set text values
if (civ == "random")
pCivText.caption = "[color=\"orange\"]" + translateWithContext("civilization", "Random");
else
pCivText.caption = g_CivData[civ].Name;
pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
}
else if (g_GameAttributes.mapType != "scenario")
{
pCivText.hidden = true;
pCiv.hidden = false;
pTeamText.hidden = true;
pTeam.hidden = false;
// Set dropdown values
pCiv.selected = (civ ? pCiv.list_data.indexOf(civ) : 0);
pTeam.selected = (team !== undefined && team >= 0) ? team+1 : 0;
}
}
Engine.GetGUIObjectByName("mapInfoDescription").caption = playerString;
g_IsInGuiUpdate = false;
// Game attributes include AI settings, so update the player list
updatePlayerList();
// We should have everyone confirm that the new settings are acceptable.
resetReadyData();
}
function updateGameAttributes()
{
if (g_IsInGuiUpdate)
return;
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
if (g_IsController && g_LoadingState >= 2)
sendRegisterGameStanza();
}
else
onGameAttributesChange();
}
function AIConfigCallback(ai)
{
g_GameAttributes.settings.PlayerData[ai.playerSlot].AI = ai.id;
g_GameAttributes.settings.PlayerData[ai.playerSlot].AIDiff = ai.difficulty;
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
else
updatePlayerList();
}
function updatePlayerList()
{
g_IsInGuiUpdate = true;
var hostNameList = [];
var hostGuidList = [];
var assignments = [];
var aiAssignments = {};
var noAssignment;
g_AssignedCount = 0;
for (var guid in g_PlayerAssignments)
{
var name = g_PlayerAssignments[guid].name;
var hostID = hostNameList.length;
var player = g_PlayerAssignments[guid].player;
hostNameList.push(name);
hostGuidList.push(guid);
assignments[player] = hostID;
if (player != -1)
g_AssignedCount++;
}
// Only enable start button if we have enough assigned players
if (g_IsController)
Engine.GetGUIObjectByName("startGame").enabled = (g_AssignedCount > 0);
for each (var ai in g_AIs)
{
if (ai.data.hidden)
{
// If the map uses a hidden AI then don't hide it
var usedByMap = false;
for (var i = 0; i < MAX_PLAYERS; ++i)
if (i < g_GameAttributes.settings.PlayerData.length &&
g_GameAttributes.settings.PlayerData[i].AI == ai.id)
{
usedByMap = true;
break;
}
if (!usedByMap)
continue;
}
// Give AI a different color so it stands out
aiAssignments[ai.id] = hostNameList.length;
hostNameList.push("[color=\"70 150 70 255\"]" + sprintf(translate("AI: %(ai)s"), { ai: translate(ai.data.name) }));
hostGuidList.push("ai:" + ai.id);
}
noAssignment = hostNameList.length;
hostNameList.push("[color=\"140 140 140 255\"]" + translate("Unassigned"));
hostGuidList.push("");
for (var i = 0; i < MAX_PLAYERS; ++i)
{
let playerSlot = i;
let playerID = i+1; // we don't show Gaia, so first slot is ID 1
var selection = assignments[playerID];
var configButton = Engine.GetGUIObjectByName("playerConfig["+i+"]");
configButton.hidden = true;
// Look for valid player slots
if (playerSlot >= g_GameAttributes.settings.PlayerData.length)
continue;
// If no human is assigned, look for an AI instead
if (selection === undefined)
{
var aiId = g_GameAttributes.settings.PlayerData[playerSlot].AI;
if (aiId)
{
// Check for a valid AI
if (aiId in aiAssignments)
selection = aiAssignments[aiId];
else
{
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
warn(sprintf("AI \"%(id)s\" not present. Defaulting to unassigned.", { id: aiId }));
}
}
if (!selection)
selection = noAssignment;
// Since no human is assigned, show the AI config button
if (g_IsController)
{
configButton.hidden = false;
configButton.onpress = function() {
Engine.PushGuiPage("page_aiconfig.xml", {
ais: g_AIs,
id: g_GameAttributes.settings.PlayerData[playerSlot].AI,
difficulty: g_GameAttributes.settings.PlayerData[playerSlot].AIDiff,
callback: "AIConfigCallback",
playerSlot: playerSlot // required by the callback function
});
};
}
}
// There was a human, so make sure we don't have any AI left
// over in their slot, if we're in charge of the attributes
else if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
{
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
}
var assignBox = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
var assignBoxText = Engine.GetGUIObjectByName("playerAssignmentText["+i+"]");
assignBox.list = hostNameList;
assignBox.list_data = hostGuidList;
if (assignBox.selected != selection)
assignBox.selected = selection;
assignBoxText.caption = hostNameList[selection];
if (g_IsController)
assignBox.onselectionchange = function() {
if (g_IsInGuiUpdate)
return;
var guid = hostGuidList[this.selected];
if (guid == "")
{
if (g_IsNetworked)
// Unassign any host from this player slot
Engine.AssignNetworkPlayer(playerID, "");
// Remove AI from this player slot
g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
}
else if (guid.substr(0, 3) == "ai:")
{
if (g_IsNetworked)
// Unassign any host from this player slot
Engine.AssignNetworkPlayer(playerID, "");
// Set the AI for this player slot
g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
}
else
swapPlayers(guid, playerSlot);
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
else
updatePlayerList();
updateReadyUI();
};
}
g_IsInGuiUpdate = false;
}
function swapPlayers(guid, newSlot)
{
// Player slots are indexed from 0 as Gaia is omitted.
var newPlayerID = newSlot + 1;
var playerID = g_PlayerAssignments[guid].player;
// Attempt to swap the player or AI occupying the target slot,
// if any, into the slot this player is currently in.
if (playerID != -1)
{
for (var i in g_PlayerAssignments)
{
// Move the player in the destination slot into the current slot.
if (g_PlayerAssignments[i].player == newPlayerID)
{
if (g_IsNetworked)
Engine.AssignNetworkPlayer(playerID, i);
else
g_PlayerAssignments[i].player = playerID;
break;
}
}
// Transfer the AI from the target slot to the current slot.
g_GameAttributes.settings.PlayerData[playerID - 1].AI = g_GameAttributes.settings.PlayerData[newSlot].AI;
}
if (g_IsNetworked)
Engine.AssignNetworkPlayer(newPlayerID, guid);
else
g_PlayerAssignments[guid].player = newPlayerID;
// Remove AI from this player slot
g_GameAttributes.settings.PlayerData[newSlot].AI = "";
}
function submitChatInput()
{
var input = Engine.GetGUIObjectByName("chatInput");
var text = input.caption;
if (text.length)
{
Engine.SendNetworkChat(text);
input.caption = "";
}
}
function addChatMessage(msg)
{
var username = "";
if (msg.username)
username = escapeText(msg.username);
else if (msg.guid && g_PlayerAssignments[msg.guid])
username = escapeText(g_PlayerAssignments[msg.guid].name);
var message = "";
if ("text" in msg && msg.text)
message = escapeText(msg.text);
// TODO: Maybe host should have distinct font/color?
var color = "white";
if (msg.guid && g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != -1)
{
// Valid player who has been assigned - get player color
var player = g_PlayerAssignments[msg.guid].player - 1;
var mapName = g_GameAttributes.map;
var mapData = loadMapData(mapName);
var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
var pData = mapSettings.PlayerData ? mapSettings.PlayerData[player] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[player] : {};
color = rgbToGuiColor(getSetting(pData, pDefs, "Color"));
}
var formatted;
switch (msg.type)
{
case "connect":
var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: sprintf(translate("%(username)s has joined"), { username: formattedUsername }) }) + '[/font]';
break;
case "disconnect":
var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: sprintf(translate("%(username)s has left"), { username: formattedUsername }) }) + '[/font]';
break;
case "message":
var formattedUsername = '[color="'+ color +'"]' + username + '[/color]';
var formattedUsernamePrefix = '[font="sans-bold-13"]' + sprintf(translate("<%(username)s>"), { username: formattedUsername }) + '[/font]'
formatted = sprintf(translate("%(username)s %(message)s"), { username: formattedUsernamePrefix, message: message });
break;
case "ready":
var formattedUsername = '[font="sans-bold-13"][color="'+ color +'"]' + username + '[/color][/font]'
if (msg.ready)
formatted = ' ' + sprintf(translate("* %(username)s is ready!"), { username: formattedUsername });
else
formatted = ' ' + sprintf(translate("* %(username)s is not ready."), { username: formattedUsername });
break;
case "settings":
formatted = '[font="sans-bold-13"] ' + sprintf(translate("== %(message)s"), { message: translate('Game settings have been changed') }) + '[/font]';
break;
default:
error(sprintf("Invalid chat message '%(message)s'", { message: uneval(msg) }));
return;
}
g_ChatMessages.push(formatted);
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
function toggleMoreOptions()
{
Engine.GetGUIObjectByName("moreOptionsFade").hidden = !Engine.GetGUIObjectByName("moreOptionsFade").hidden;
Engine.GetGUIObjectByName("moreOptions").hidden = !Engine.GetGUIObjectByName("moreOptions").hidden;
}
function toggleReady()
{
g_IsReady = !g_IsReady;
if (g_IsReady)
{
Engine.SendNetworkReady(1);
Engine.GetGUIObjectByName("startGame").caption = translate("I'm not ready");
Engine.GetGUIObjectByName("startGame").tooltip = translate("State that you are not ready to play.");
}
else
{
Engine.SendNetworkReady(0);
Engine.GetGUIObjectByName("startGame").caption = translate("I'm ready!");
Engine.GetGUIObjectByName("startGame").tooltip = translate("State that you are ready to play!");
}
}
function updateReadyUI()
{
if (!g_IsNetworked)
return; // Disabled for single-player games.
var isAI = new Array(MAX_PLAYERS + 1);
for (var i = 0; i < isAI.length; ++i)
isAI[i] = true;
var allReady = true;
for (var guid in g_PlayerAssignments)
{
// We don't really care whether observers are ready.
if (g_PlayerAssignments[guid].player == -1 || !g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1])
continue;
var pData = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[g_PlayerAssignments[guid].player - 1] : {};
isAI[g_PlayerAssignments[guid].player] = false;
if (g_PlayerAssignments[guid].status || !g_IsNetworked)
Engine.GetGUIObjectByName("playerName[" + (g_PlayerAssignments[guid].player - 1) + "]").caption = '[color="0 255 0"]' + translate(getSetting(pData, pDefs, "Name")) + '[/color]';
else
{
Engine.GetGUIObjectByName("playerName[" + (g_PlayerAssignments[guid].player - 1) + "]").caption = translate(getSetting(pData, pDefs, "Name"));
allReady = false;
}
}
// AIs are always ready.
for (var playerid = 0; playerid < MAX_PLAYERS; ++playerid)
{
if (!g_GameAttributes.settings.PlayerData[playerid])
continue;
var pData = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[playerid] : {};
var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[playerid] : {};
if (isAI[playerid + 1])
Engine.GetGUIObjectByName("playerName[" + playerid + "]").caption = '[color="0 255 0"]' + translate(getSetting(pData, pDefs, "Name")) + '[/color]';
}
// The host is not allowed to start until everyone is ready.
if (g_IsNetworked && g_IsController)
{
var startGameButton = Engine.GetGUIObjectByName("startGame");
startGameButton.enabled = allReady;
// Add a explanation on to the tooltip if disabled.
var disabledIndex = startGameButton.tooltip.indexOf('Disabled');
if (disabledIndex != -1 && allReady)
startGameButton.tooltip = startGameButton.tooltip.substring(0, disabledIndex - 2);
else if (disabledIndex == -1 && !allReady)
startGameButton.tooltip = startGameButton.tooltip + " (Disabled until all players are ready)";
}
}
function resetReadyData()
{
if (g_GameStarted)
return;
if (g_ReadyChanged < 1)
addChatMessage({ "type": "settings"});
else if (g_ReadyChanged == 2 && !g_ReadyInit)
return; // duplicate calls on init
else
g_ReadyInit = false;
g_ReadyChanged = 2;
if (!g_IsNetworked)
g_IsReady = true;
else if (g_IsController)
{
Engine.ClearAllPlayerReady();
g_IsReady = true;
Engine.SendNetworkReady(1);
}
else
{
g_IsReady = false;
Engine.GetGUIObjectByName("startGame").caption = translate("I'm ready!");
Engine.GetGUIObjectByName("startGame").tooltip = translate("State that you accept the current settings and are ready to play!");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Basic map filters API
// Add a new map list filter
function addFilter(id, name, filterFunc)
{
if (!filterFunc instanceof Object)
{
error(sprintf("Invalid map filter: %(name)s", { name: name }));
return;
}
var newFilter = {};
newFilter.id = id;
newFilter.name = name;
newFilter.filter = filterFunc;
g_MapFilters.push(newFilter);
}
// Get array of map filter IDs
function getFilterIds()
{
var filters = [];
for (var i = 0; i < g_MapFilters.length; ++i)
filters.push(g_MapFilters[i].id);
return filters;
}
// Get array of map filter names
function getFilterNames()
{
var filters = [];
for (var i = 0; i < g_MapFilters.length; ++i)
filters.push(g_MapFilters[i].name);
return filters;
}
// Test map filter on given map settings object
function testFilter(id, mapSettings)
{
for (var i = 0; i < g_MapFilters.length; ++i)
if (g_MapFilters[i].id == id)
return g_MapFilters[i].filter(mapSettings);
error(sprintf("Invalid map filter: %(id)s", { id: id }));
return false;
}
// Test an array of keywords against a match array using AND logic
function keywordTestAND(keywords, matches)
{
if (!keywords || !matches)
return false;
for (var m = 0; m < matches.length; ++m)
if (keywords.indexOf(matches[m]) == -1)
return false;
return true;
}
// Test an array of keywords against a match array using OR logic
function keywordTestOR(keywords, matches)
{
if (!keywords || !matches)
return false;
for (var m = 0; m < matches.length; ++m)
if (keywords.indexOf(matches[m]) != -1)
return true;
return false;
}
function sendRegisterGameStanza()
{
if (!Engine.HasXmppClient())
return;
var selectedMapSize = Engine.GetGUIObjectByName("mapSize").selected;
var selectedVictoryCondition = Engine.GetGUIObjectByName("victoryCondition").selected;
// Map sizes only apply to random maps.
if (g_GameAttributes.mapType == "random")
var mapSize = Engine.GetGUIObjectByName("mapSize").list_data[selectedMapSize];
else
var mapSize = "Default";
var victoryCondition = Engine.GetGUIObjectByName("victoryCondition").list[selectedVictoryCondition];
var numberOfPlayers = Object.keys(g_PlayerAssignments).length;
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ].join(", ");
var nbp = numberOfPlayers ? numberOfPlayers : 1;
var tnbp = g_GameAttributes.settings.PlayerData.length;
var gameData = {
"name":g_ServerName,
"mapName":g_GameAttributes.map,
"niceMapName":getMapDisplayName(g_GameAttributes.map),
"mapSize":mapSize,
"mapType":g_GameAttributes.mapType,
"victoryCondition":victoryCondition,
"nbp":nbp,
"tnbp":tnbp,
"players":players
};
Engine.SendRegisterGame(gameData);
}
function getVictoryConditions()
{
var r = {};
r.text = [translate("None")];
r.data = ["endless"];
r.scripts = [[]];
for (var vc in g_VictoryConditions)
{
r.data.push(vc);
r.text.push(g_VictoryConditions[vc].name);
r.scripts.push(g_VictoryConditions[vc].scripts);
}
return r;
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 16624)
@@ -1,394 +1,404 @@
Match SetupLoadingLoading map data. Please wait...
onTick();
Player NamePlayer PlacementCivilizationView civilization infoTeamSelect player.Configure AI settings.Select player's civilization.Select player's team.Map Type:Map Filter:Select Map:Number of Players:Map Size:Select a map type.selectMapType(this.list_data[this.selected]);Select a map filter.selectMapFilter(this.list_data[this.selected]);Select a map to play on.
if (this.list_data[this.selected])
selectMap(this.list_data[this.selected]);
Select number of players.selectNumPlayers(this.list_data[this.selected]);Select map size. (Larger sizes may reduce performance.)submitChatInput();
var players = [];
for (var player in g_PlayerAssignments)
players.push(g_PlayerAssignments[player]);
autoCompleteNick("chatInput", players);
SendsubmitChatInput();Start game!Start a new game with the current settings.
if (g_IsController)
launchGame();
else
toggleReady();
BackMore OptionsSee more game optionstoggleMoreOptions();
-
+ More OptionsGame Speed:Select game speed.Victory Condition:Select victory condition.Population Cap:Select population cap.Starting Resources:Select the game's starting resources.
+ Ceasefire:
+
+
+
+ Set time where no attacks are possible.
+
+
+
+
+ Revealed Map:Toggle revealed map (see everything).
-
+ Explored Map:Toggle explored map (see initial map).
-
+ Disable Treasures:Disable all treasures on the map.
-
+ Teams Locked:Toggle locked teams.
-
+ Cheats:Toggle the usability of cheats.
-
+ Rated Game:Toggle if this game will be rated for the leaderboard.OKClose more game options windowtoggleMoreOptions();
Index: ps/trunk/binaries/data/mods/public/gui/options/options.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 16624)
@@ -1,233 +1,234 @@
var g_hasCallback = false;
/**
* This array holds the data to populate the general section with.
* Data is in the form [Title, Tooltip, {ActionType:Action}, InputType].
*/
var options = {
"generalSetting":
[
[translate("Windowed Mode"), translate("Start 0 A.D. in a window"), {"config":"windowed"}, "boolean"],
[translate("Background Pause"), translate("Pause single player games when window loses focus"), {"config":"pauseonfocusloss"}, "boolean"],
[translate("Disable Welcome Screen"), translate("If you disable this screen completely, you may miss important announcements.\nYou can still launch it using the main menu."), {"config":"splashscreendisable"}, "boolean"],
[translate("Detailed Tooltips"), translate("Show detailed tooltips for trainable units in unit-producing buildings."), {"config":"showdetailedtooltips"}, "boolean"],
[translate("FPS Overlay"), translate("Show frames per second in top right corner."), {"config":"overlay.fps"}, "boolean"],
[translate("Realtime Overlay"), translate("Show current system time in top right corner."), {"config":"overlay.realtime"}, "boolean"],
[translate("Gametime Overlay"), translate("Show current simulation time in top right corner."), {"config":"gui.session.timeelapsedcounter"}, "boolean"],
+ [translate("Ceasefire Time Overlay"), translate("Always show the remaining ceasefire time."), {"config":"gui.session.ceasefirecounter"}, "boolean"],
[translate("Persist match settings"), translate("Save and restore match settings for quick reuse when hosting another game"), {"config":"persistmatchsettings"}, "boolean"],
],
"graphicsSetting":
[
[translate("Prefer GLSL"), translate("Use OpenGL 2.0 shaders (recommended)"), {"renderer":"PreferGLSL", "config":"preferglsl"}, "boolean"],
[translate("Post Processing"), translate("Use screen-space postprocessing filters (HDR, Bloom, DOF, etc)"), {"renderer":"Postproc", "config":"postproc"}, "boolean"],
[translate("Shadows"), translate("Enable shadows"), {"renderer":"Shadows", "config":"shadows"}, "boolean"],
[translate("Particles"), translate("Enable particles"), {"renderer":"Particles", "config":"particles"}, "boolean"],
[translate("Show Sky"), translate("Render Sky"), {"renderer":"ShowSky", "config":"showsky"}, "boolean"],
[translate("Smooth LOS"), translate("Lift darkness and fog-of-war smoothly"), {"renderer":"SmoothLOS", "config":"smoothlos"}, "boolean"],
[translate("Unit Silhouettes"), translate("Show outlines of units behind buildings"), {"renderer":"Silhouettes", "config":"silhouettes"}, "boolean"],
[translate("Shadow Filtering"), translate("Smooth shadows"), {"renderer":"ShadowPCF", "config":"shadowpcf"}, "boolean"],
[translate("Fast & Ugly Water"), translate("Use the lowest settings possible to render water. This makes other settings irrelevant."), {"renderer":"WaterUgly", "config":"waterugly"}, "boolean"],
[translate("HQ Water Effects"), translate("Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails."), {"renderer":"WaterFancyEffects", "config":"waterfancyeffects"}, "boolean"],
[translate("Real Water Depth"), translate("Use actual water depth in rendering calculations"), {"renderer":"WaterRealDepth", "config":"waterrealdepth"}, "boolean"],
[translate("Water Reflections"), translate("Allow water to reflect a mirror image"), {"renderer":"WaterReflection", "config":"waterreflection"}, "boolean"],
[translate("Water Refraction"), translate("Use a real water refraction map and not transparency"), {"renderer":"WaterRefraction", "config":"waterrefraction"}, "boolean"],
[translate("Shadows on Water"), translate("Cast shadows on water"), {"renderer":"WaterShadows", "config":"watershadows"}, "boolean"],
[translate("VSync"), translate("Run vertical sync to fix screen tearing. REQUIRES GAME RESTART"), {"config":"vsync"}, "boolean"],
],
"soundSetting":
[
[translate("Master Gain"), translate("Master audio gain"), {"config":"sound.mastergain", "function":"Engine.SetMasterGain(Number(this.caption));"}, "number"],
[translate("Music Gain"), translate("In game music gain"), {"config":"sound.musicgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
[translate("Ambient Gain"), translate("In game ambient sound gain"), {"config":"sound.ambientgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
[translate("Action Gain"), translate("In game unit action sound gain"), {"config":"sound.actiongain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
[translate("UI Gain"), translate("UI sound gain"), {"config":"sound.uigain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"],
],
"lobbySetting":
[
[translate("Chat Backlog"), translate("Number of backlogged messages to load when joining the lobby"), {"config":"lobby.history"}, "number"],
[translate("Chat Timestamp"), translate("Show time that messages are posted in the lobby chat"), {"config":"lobby.chattimestamp"}, "boolean"],
],
};
function init(data)
{
if (data && data.callback)
g_hasCallback = true;
// WARNING: We assume a strict formatting of the XML and do minimal checking.
for each (var prefix in Object.keys(options))
{
var lastSize;
for (var i = 0; i < options[prefix].length; i++)
{
var body = Engine.GetGUIObjectByName(prefix + "[" + i + "]");
var label = Engine.GetGUIObjectByName(prefix + "Label[" + i + "]");
// Setup control.
setupControl(options[prefix][i], i, prefix);
// Setup label.
label.caption = options[prefix][i][0];
label.tooltip = options[prefix][i][1];
// Move each element to the correct place.
if (i > 0)
{
var newSize = new GUISize();
newSize.left = lastSize.left;
newSize.rright = lastSize.rright;
newSize.top = lastSize.bottom;
newSize.bottom = newSize.top + 25;
body.size = newSize;
lastSize = newSize;
}
else
{
lastSize = body.size;
}
// Show element.
body.hidden = false;
}
}
}
/**
* Setup the apropriate control for a given option.
*
* @param option Structure containing the data to setup an option.
* @param prefix Prefix to use when accessing control, for example "generalSetting" when the tickbox name is generalSettingTickbox[i].
*/
function setupControl(option, i, prefix)
{
switch (option[3])
{
case "boolean":
// More space for the label
let text = Engine.GetGUIObjectByName(prefix + "Label[" + i + "]");
let size = text.size;
size.rright = 87;
text.size = size;
var control = Engine.GetGUIObjectByName(prefix + "Tickbox[" + i + "]");
var checked;
var onPress = function(){};
// Different option action load and save differently, so this switch is needed.
for each (var action in Object.keys(option[2]))
{
switch (action)
{
case "config":
// Load initial value if not yet loaded.
if (!checked || typeof checked != "boolean")
checked = Engine.ConfigDB_GetValue("user", option[2][action]) === "true" ? true : false;
// Hacky macro to create the callback.
var callback = function(key)
{
return function()
Engine.ConfigDB_CreateValue("user", key, String(this.checked));
}(option[2][action]);
// Merge the new callback with any existing callbacks.
onPress = mergeFunctions(callback, onPress);
// TODO: Remove this once GLSL works without GenTangents (#2506)
if (option[2][action] == "preferglsl")
{
callback = function()
Engine.ConfigDB_CreateValue("user", "gentangents", String(this.checked));
onPress = mergeFunctions(callback, onPress);
}
break;
case "renderer":
// Load initial value if not yet loaded.
if (!checked || typeof checked != "boolean")
checked = eval("Engine.Renderer_Get" + option[2][action] + "Enabled()");
// Hacky macro to create the callback.
var callback = function(key)
{
return function()
eval("Engine.Renderer_Set" + key + "Enabled(" + this.checked + ")");
}(option[2][action]);
// Merge the new callback with any existing callbacks.
onPress = mergeFunctions(callback, onPress);
// TODO: Remove this once GLSL works without GenTangents (#2506)
if (option[2][action] == "PreferGLSL")
{
callback = function()
eval("Engine.Renderer_SetGenTangentsEnabled(" + this.checked + ")");
onPress = mergeFunctions(callback, onPress);
}
break;
case "function":
// This allows for doing low-level actions, like hiding/showing UI elements.
onPress = mergeFunctions(eval("function(){" + option[2][action] + "}"), onPress);
break;
default:
warn("Unknown option source type '" + action + "'");
}
}
// Load final data to the control element.
control.checked = checked;
control.onPress = onPress;
break;
case "number":
// TODO: Slider
case "string":
var control = Engine.GetGUIObjectByName(prefix + "Input[" + i + "]");
var caption;
var onPress = function(){};
for each (var action in Object.keys(option[2]))
{
switch (action)
{
case "config":
// Load initial value if not yet loaded.
if (!checked || typeof checked != boolean)
caption = Engine.ConfigDB_GetValue("user", option[2][action]);;
// Hacky macro to create the callback.
var callback = function(key)
{
return function()
Engine.ConfigDB_CreateValue("user", key, String(this.caption));
}(option[2][action]);
// Merge the new callback with any existing callbacks.
onPress = mergeFunctions(callback, onPress);
break;
case "function":
// This allows for doing low-level actions, like hiding/showing UI elements.
onPress = mergeFunctions(function(){eval(option[2][action])}, onPress);
break;
default:
warn("Unknown option source type '" + action + "'");
}
}
control.caption = caption;
control.onPress = onPress;
break;
default:
warn("Unknown option type '" + options[3] + "', assuming string. Valid types are 'number', 'string', or 'bool'.");
var control = Engine.GetGUIObjectByName(prefix + "Input[" + i + "]");
break;
}
control.hidden = false;
control.tooltip = option[1];
return control;
}
/**
* Merge two functions which don't expect arguments.
*
* @return Merged function.
*/
function mergeFunctions(function1, function2)
{
return function()
{
function1.apply(this);
function2.apply(this);
};
}
/**
* Close GUI page and call callbacks if they exist.
**/
function closePage()
{
if (g_hasCallback)
Engine.PopGuiPageCB();
else
Engine.PopGuiPage();
}
Index: ps/trunk/binaries/data/mods/public/gui/session/diplomacy_window.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/diplomacy_window.xml (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/session/diplomacy_window.xml (revision 16624)
@@ -1,80 +1,82 @@
DiplomacyNameCivilizationTeamTheirsAAllyNNeutralEEnemyTribute
-
+
+
+
ClosecloseDiplomacy();
Index: ps/trunk/binaries/data/mods/public/gui/session/menu.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 16624)
@@ -1,712 +1,713 @@
const PAUSE = translate("Pause");
const RESUME = translate("Resume");
/*
* MENU POSITION CONSTANTS
*/
// Menu / panel border size
const MARGIN = 4;
// Includes the main menu button
const NUM_BUTTONS = 8;
// Regular menu buttons
const BUTTON_HEIGHT = 32;
// The position where the bottom of the menu will end up (currently 228)
const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN;
// Menu starting position: bottom
const MENU_BOTTOM = 0;
// Menu starting position: top
const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION;
// Menu starting position: overall
const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
// Number of pixels per millisecond to move
const MENU_SPEED = 1.2;
// Trade menu: available resources and step for probability changes
const RESOURCES = ["food", "wood", "stone", "metal"];
const STEP = 5;
var isMenuOpen = false;
var menu;
var isDiplomacyOpen = false;
var isTradeOpen = false;
// Redefined every time someone makes a tribute (so we can save some data in a closure). Called in input.js handleInputBeforeGui.
var flushTributing = function() {};
// Ignore size defined in XML and set the actual menu size here
function initMenuPosition()
{
menu = Engine.GetGUIObjectByName("menu");
menu.size = INITIAL_MENU_POSITION;
}
// =============================================================================
// Overall Menu
// =============================================================================
//
// Slide menu
function updateMenuPosition(dt)
{
if (isMenuOpen)
{
var maxOffset = END_MENU_POSITION - menu.size.bottom;
if (maxOffset > 0)
{
var offset = Math.min(MENU_SPEED * dt, maxOffset);
var size = menu.size;
size.top += offset;
size.bottom += offset;
menu.size = size;
}
}
else
{
var maxOffset = menu.size.top - MENU_TOP;
if (maxOffset > 0)
{
var offset = Math.min(MENU_SPEED * dt, maxOffset);
var size = menu.size;
size.top -= offset;
size.bottom -= offset;
menu.size = size;
}
}
}
// Opens the menu by revealing the screen which contains the menu
function openMenu()
{
isMenuOpen = true;
}
// Closes the menu and resets position
function closeMenu()
{
isMenuOpen = false;
}
function toggleMenu()
{
if (isMenuOpen == true)
closeMenu();
else
openMenu();
}
// Menu buttons
// =============================================================================
function optionsMenuButton()
{
closeMenu();
closeOpenDialogs();
openOptions();
}
function chatMenuButton()
{
closeMenu();
closeOpenDialogs();
openChat();
}
function diplomacyMenuButton()
{
closeMenu();
closeOpenDialogs();
openDiplomacy();
}
function pauseMenuButton()
{
togglePause();
}
function resignMenuButton()
{
closeMenu();
closeOpenDialogs();
pauseGame();
var btCaptions = [translate("Yes"), translate("No")];
var btCode = [resignGame, resumeGame];
messageBox(400, 200, translate("Are you sure you want to resign?"), translate("Confirmation"), 0, btCaptions, btCode);
}
function exitMenuButton()
{
closeMenu();
closeOpenDialogs();
pauseGame();
if (g_IsNetworked && g_IsController)
{
var btCode = [resumeGame, leaveGame];
var message = translate("Are you sure you want to quit? Leaving will disconnect all other players.");
}
else if (g_IsNetworked && !g_GameEnded && !g_IsObserver)
{
var btCode = [resumeGame, networkReturnQuestion];
var message = translate("Are you sure you want to quit?");
}
else
{
var btCode = [resumeGame, leaveGame];
var message = translate("Are you sure you want to quit?");
}
messageBox(400, 200, message, translate("Confirmation"), 0, [translate("No"), translate("Yes")], btCode);
}
function networkReturnQuestion()
{
var btCaptions = [translate("I will return"), translate("I resign")];
var btCode = [leaveGame, resignGame];
var btArgs = [true, false];
messageBox(400, 200, translate("Do you want to resign or will you return soon?"), translate("Confirmation"), 0, btCaptions, btCode, btArgs);
}
function openDeleteDialog(selection)
{
closeMenu();
closeOpenDialogs();
var deleteSelectedEntities = function (selectionArg)
{
Engine.PostNetworkCommand({"type": "delete-entities", "entities": selectionArg});
};
var btCaptions = [translate("Yes"), translate("No")];
var btCode = [deleteSelectedEntities, resumeGame];
var btArgs = [selection, null];
messageBox(400, 200, translate("Destroy everything currently selected?"), translate("Delete"), 0, btCaptions, btCode, btArgs);
}
// Menu functions
// =============================================================================
function openSave()
{
closeMenu();
closeOpenDialogs();
pauseGame();
var savedGameData = getSavedGameData();
Engine.PushGuiPage("page_savegame.xml", {"savedGameData":savedGameData, "callback":"resumeGame"});
}
function openOptions()
{
pauseGame();
Engine.PushGuiPage("page_options.xml", {"callback":"resumeGame"});
}
function openChat()
{
updateTeamCheckbox(false);
Engine.GetGUIObjectByName("chatInput").focus(); // Grant focus to the input area
Engine.GetGUIObjectByName("chatDialogPanel").hidden = false;
}
function closeChat()
{
Engine.GetGUIObjectByName("chatInput").caption = ""; // Clear chat input
Engine.GetGUIObjectByName("chatInput").blur(); // Remove focus
Engine.GetGUIObjectByName("chatDialogPanel").hidden = true;
}
function updateTeamCheckbox(check)
{
Engine.GetGUIObjectByName("toggleTeamChatLabel").hidden = g_IsObserver;
let toggleTeamChat = Engine.GetGUIObjectByName("toggleTeamChat");
toggleTeamChat.hidden = g_IsObserver;
toggleTeamChat.checked = !g_IsObserver && check;
}
function toggleChatWindow(teamChat)
{
var chatWindow = Engine.GetGUIObjectByName("chatDialogPanel");
var chatInput = Engine.GetGUIObjectByName("chatInput");
if (chatWindow.hidden)
chatInput.focus(); // Grant focus to the input area
else
{
if (chatInput.caption.length)
{
submitChatInput();
return;
}
chatInput.caption = ""; // Clear chat input
}
updateTeamCheckbox(teamChat);
chatWindow.hidden = !chatWindow.hidden;
}
function setDiplomacy(data)
{
Engine.PostNetworkCommand({"type": "diplomacy", "to": data.to, "player": data.player});
}
function tributeResource(data)
{
Engine.PostNetworkCommand({"type": "tribute", "player": data.player, "amounts": data.amounts});
}
function openDiplomacy()
{
if (isTradeOpen)
closeTrade();
isDiplomacyOpen = true;
let we = Engine.GetPlayerID();
// Get offset for one line
let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size;
let rowsize = onesize.bottom - onesize.top;
// We don't include gaia
for (let i = 1; i < g_Players.length; ++i)
{
// Apply offset
let row = Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]");
let size = row.size;
size.top = rowsize*(i-1);
size.bottom = rowsize*i;
row.size = size;
// Set background color
let playerColor = rgbToGuiColor(g_Players[i].color);
row.sprite = "color: "+playerColor + " 32";
Engine.GetGUIObjectByName("diplomacyPlayerName["+(i-1)+"]").caption = "[color=\"" + playerColor + "\"]" + g_Players[i].name + "[/color]";
Engine.GetGUIObjectByName("diplomacyPlayerCiv["+(i-1)+"]").caption = g_CivData[g_Players[i].civ].Name;
Engine.GetGUIObjectByName("diplomacyPlayerTeam["+(i-1)+"]").caption = (g_Players[i].team < 0) ? translateWithContext("team", "None") : g_Players[i].team+1;
if (i != we)
Engine.GetGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = (g_Players[i].isAlly[we] ? translate("Ally") : (g_Players[i].isNeutral[we] ? translate("Neutral") : translate("Enemy")));
// Don't display the options for ourself, or if we or the other player aren't active anymore
if (i == we || g_Players[we].state != "active" || g_Players[i].state != "active")
{
// Hide the unused/unselectable options
for each (let a in ["TributeFood", "TributeWood", "TributeStone", "TributeMetal", "Ally", "Neutral", "Enemy"])
Engine.GetGUIObjectByName("diplomacyPlayer"+a+"["+(i-1)+"]").hidden = true;
Engine.GetGUIObjectByName("diplomacyAttackRequest["+(i-1)+"]").hidden = true;
continue;
}
// Tribute
for each (let resource in ["food", "wood", "stone", "metal"])
{
let button = Engine.GetGUIObjectByName("diplomacyPlayerTribute"+resource[0].toUpperCase()+resource.substring(1)+"["+(i-1)+"]");
button.onpress = (function(player, resource, button){
// Implement something like how unit batch training works. Shift+click to send 500, shift+click+click to send 1000, etc.
// Also see input.js (searching for "INPUT_MASSTRIBUTING" should get all the relevant parts).
let multiplier = 1;
return function() {
let isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute");
if (isBatchTrainPressed)
{
inputState = INPUT_MASSTRIBUTING;
multiplier += multiplier == 1 ? 4 : 5;
}
let amounts = {
"food": (resource == "food" ? 100 : 0) * multiplier,
"wood": (resource == "wood" ? 100 : 0) * multiplier,
"stone": (resource == "stone" ? 100 : 0) * multiplier,
"metal": (resource == "metal" ? 100 : 0) * multiplier,
};
button.tooltip = formatTributeTooltip(g_Players[player], resource, amounts[resource]);
// This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some
// evil global variable hackery.
flushTributing = function() {
tributeResource({"player": player, "amounts": amounts});
multiplier = 1;
button.tooltip = formatTributeTooltip(g_Players[player], resource, 100);
};
if (!isBatchTrainPressed)
flushTributing();
};
})(i, resource, button);
button.hidden = false;
button.tooltip = formatTributeTooltip(g_Players[i], resource, 100);
}
// Attack Request
+ var simState = GetSimState();
let button = Engine.GetGUIObjectByName("diplomacyAttackRequest["+(i-1)+"]");
- button.hidden = !(g_Players[i].isEnemy[we]);
+ button.hidden = simState.ceasefireActive && !(g_Players[i].isEnemy[we]);
button.tooltip = translate("request for your allies to attack this enemy");
button.onpress = (function(i, we){ return function() {
Engine.PostNetworkCommand({"type": "attack-request", "source": we, "target": i});
} })(i, we);
// Skip our own teams on teams locked
if (g_Players[we].teamsLocked && g_Players[we].team != -1 && g_Players[we].team == g_Players[i].team)
continue;
// Diplomacy settings
// Set up the buttons
for each (let setting in ["Ally", "Neutral", "Enemy"])
{
let button = Engine.GetGUIObjectByName("diplomacyPlayer"+setting+"["+(i-1)+"]");
button.caption = g_Players[we]["is"+setting][i] ? translate("x") : "";
button.onpress = (function(e){ return function() { setDiplomacy(e) } })({"player": i, "to": setting.toLowerCase()});
- button.hidden = false;
+ button.hidden = simState.ceasefireActive;
}
}
Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
}
function closeDiplomacy()
{
isDiplomacyOpen = false;
Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = true;
}
function toggleDiplomacy()
{
if (isDiplomacyOpen)
closeDiplomacy();
else
openDiplomacy();
}
function openTrade()
{
if (isDiplomacyOpen)
closeDiplomacy();
isTradeOpen = true;
var updateButtons = function()
{
for (var res in button)
{
button[res].label.caption = proba[res] + "%";
if (res == selec)
{
button[res].sel.hidden = false;
button[res].up.hidden = true;
button[res].dn.hidden = true;
}
else
{
button[res].sel.hidden = true;
button[res].up.hidden = (proba[res] == 100 || proba[selec] == 0);
button[res].dn.hidden = (proba[res] == 0 || proba[selec] == 100);
}
}
}
var proba = Engine.GuiInterfaceCall("GetTradingGoods");
var button = {};
var selec = RESOURCES[0];
for (var i = 0; i < RESOURCES.length; ++i)
{
var buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
if (i > 0)
{
var size = Engine.GetGUIObjectByName("tradeResource["+(i-1)+"]").size;
var width = size.right - size.left;
size.left += width;
size.right += width;
Engine.GetGUIObjectByName("tradeResource["+i+"]").size = size;
}
var resource = RESOURCES[i];
proba[resource] = (proba[resource] ? proba[resource] : 0);
var buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
var icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
icon.sprite = "stretched:session/icons/resources/" + resource + ".png";
var label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]");
var buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
var buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
var iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
button[resource] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
buttonResource.onpress = (function(resource){
return function() {
if (Engine.HotkeyIsPressed("session.fulltradeswap"))
{
for (var ress of RESOURCES)
proba[ress] = 0;
proba[resource] = 100;
Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
}
selec = resource;
updateButtons();
}
})(resource);
buttonUp.onpress = (function(resource){
return function() {
proba[resource] += Math.min(STEP, proba[selec]);
proba[selec] -= Math.min(STEP, proba[selec]);
Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
updateButtons();
}
})(resource);
buttonDn.onpress = (function(resource){
return function() {
proba[selec] += Math.min(STEP, proba[resource]);
proba[resource] -= Math.min(STEP, proba[resource]);
Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
updateButtons();
}
})(resource);
}
updateButtons();
var traderNumber = Engine.GuiInterfaceCall("GetTraderNumber");
var caption = "";
if (traderNumber.landTrader.total == 0)
caption = translate("There are no land traders.");
else
{
var inactive = traderNumber.landTrader.total - traderNumber.landTrader.trading - traderNumber.landTrader.garrisoned;
var inactiveString = "";
if (inactive > 0)
inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfLandTraders)s inactive", "%(numberOfLandTraders)s inactive", inactive), { numberOfLandTraders: inactive }) + "[/color]";
if (traderNumber.landTrader.trading > 0)
{
var openingTradingString = sprintf(translatePlural("There is %(numberTrading)s land trader trading", "There are %(numberTrading)s land traders trading", traderNumber.landTrader.trading), { numberTrading: traderNumber.landTrader.trading });
if (traderNumber.landTrader.garrisoned > 0)
{
var garrisonedString = sprintf(translatePlural("%(numberGarrisoned)s garrisoned on a trading merchant ship", "%(numberGarrisoned)s garrisoned on a trading merchant ship", traderNumber.landTrader.garrisoned), { numberGarrisoned: traderNumber.landTrader.garrisoned });
if (inactive > 0)
{
caption = sprintf(translate("%(openingTradingString)s, %(garrisonedString)s, and %(inactiveString)s."), {
openingTradingString: openingTradingString,
garrisonedString: garrisonedString,
inactiveString: inactiveString
});
}
else
{
caption = sprintf(translate("%(openingTradingString)s, and %(garrisonedString)s."), {
openingTradingString: openingTradingString,
garrisonedString: garrisonedString
});
}
}
else
{
if (inactive > 0)
{
caption = sprintf(translate("%(openingTradingString)s, and %(inactiveString)s."), {
openingTradingString: openingTradingString,
inactiveString: inactiveString
});
}
else
{
caption = sprintf(translate("%(openingTradingString)s."), {
openingTradingString: openingTradingString,
});
}
}
}
else
{
if (traderNumber.landTrader.garrisoned > 0)
{
var openingGarrisonedString = sprintf(translatePlural("There is %(numberGarrisoned)s land trader garrisoned on a trading merchant ship", "There are %(numberGarrisoned)s land traders garrisoned on a trading merchant ship", traderNumber.landTrader.garrisoned), { numberGarrisoned: traderNumber.landTrader.garrisoned });
if (inactive > 0)
{
caption = sprintf(translate("%(openingGarrisonedString)s, and %(inactiveString)s."), {
openingGarrisonedString: openingGarrisonedString,
inactiveString: inactiveString
});
}
else
{
caption = sprintf(translate("%(openingGarrisonedString)s."), {
openingGarrisonedString: openingGarrisonedString
});
}
}
else
{
if (inactive > 0)
{
inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfLandTraders)s land trader inactive", "%(numberOfLandTraders)s land traders inactive", inactive), { numberOfLandTraders: inactive }) + "[/color]";
caption = sprintf(translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), {
inactiveString: inactiveString
});
}
// The “else” here is already handled by “if (traderNumber.landTrader.total == 0)” above.
}
}
}
Engine.GetGUIObjectByName("landTraders").caption = caption;
caption = "";
if (traderNumber.shipTrader.total == 0)
caption = translate("There are no merchant ships.");
else
{
var inactive = traderNumber.shipTrader.total - traderNumber.shipTrader.trading;
var inactiveString = "";
if (inactive > 0)
inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfShipTraders)s inactive", "%(numberOfShipTraders)s inactive", inactive), { numberOfShipTraders: inactive }) + "[/color]";
if (traderNumber.shipTrader.trading > 0)
{
var openingTradingString = sprintf(translatePlural("There is %(numberTrading)s merchant ship trading", "There are %(numberTrading)s merchant ships trading", traderNumber.shipTrader.trading), { numberTrading: traderNumber.shipTrader.trading });
if (inactive > 0)
{
caption = sprintf(translate("%(openingTradingString)s, and %(inactiveString)s."), {
openingTradingString: openingTradingString,
inactiveString: inactiveString
});
}
else
{
caption = sprintf(translate("%(openingTradingString)s."), {
openingTradingString: openingTradingString,
});
}
}
else
{
if (inactive > 0)
{
inactiveString = "[color=\"orange\"]" + sprintf(translatePlural("%(numberOfShipTraders)s merchant ship inactive", "%(numberOfShipTraders)s merchant ships inactive", inactive), { numberOfShipTraders: inactive }) + "[/color]";
caption = sprintf(translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), {
inactiveString: inactiveString
});
}
// The “else” here is already handled by “if (traderNumber.shipTrader.total == 0)” above.
}
}
Engine.GetGUIObjectByName("shipTraders").caption = caption;
Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false;
}
function closeTrade()
{
isTradeOpen = false;
Engine.GetGUIObjectByName("tradeDialogPanel").hidden = true;
}
function toggleTrade()
{
if (isTradeOpen)
closeTrade();
else
openTrade();
}
function toggleGameSpeed()
{
var gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
gameSpeed.hidden = !gameSpeed.hidden;
}
function openStrucTree()
{
closeMenu();
closeOpenDialogs();
pauseGame();
var data = { // TODO add info about researched techs and unlocked entities
"civ" : g_Players[Engine.GetPlayerID()].civ,
"callback": "resumeGame",
};
Engine.PushGuiPage("page_structree.xml", data);
}
/**
* Pause the game in single player mode.
*/
function pauseGame()
{
if (g_IsNetworked)
return;
Engine.GetGUIObjectByName("pauseButtonText").caption = RESUME;
Engine.GetGUIObjectByName("pauseOverlay").hidden = false;
Engine.SetPaused(true);
}
function resumeGame()
{
Engine.GetGUIObjectByName("pauseButtonText").caption = PAUSE;
Engine.GetGUIObjectByName("pauseOverlay").hidden = true;
Engine.SetPaused(false);
}
function togglePause()
{
closeMenu();
closeOpenDialogs();
var pauseOverlay = Engine.GetGUIObjectByName("pauseOverlay");
if (pauseOverlay.hidden)
{
Engine.GetGUIObjectByName("pauseButtonText").caption = RESUME;
Engine.SetPaused(true);
}
else
{
Engine.SetPaused(false);
Engine.GetGUIObjectByName("pauseButtonText").caption = PAUSE;
}
pauseOverlay.hidden = !pauseOverlay.hidden;
}
function openManual()
{
closeMenu();
closeOpenDialogs();
pauseGame();
Engine.PushGuiPage("page_manual.xml", {"page": "manual/intro", "title":translate("Manual"), "url":"http://trac.wildfiregames.com/wiki/0adManual", "callback": "resumeGame"});
}
function toggleDeveloperOverlay()
{
// The developer overlay is disabled in ranked games
if (Engine.HasXmppClient() && Engine.IsRankedGame())
return;
var devCommands = Engine.GetGUIObjectByName("devCommands");
if (devCommands.hidden)
submitChatDirectly(translate("The Developer Overlay was opened."));
else
submitChatDirectly(translate("The Developer Overlay was closed."));
// Toggle the overlay
devCommands.hidden = !devCommands.hidden;
}
function closeOpenDialogs()
{
closeMenu();
closeChat();
closeDiplomacy();
closeTrade();
}
function formatTributeTooltip(player, resource, amount)
{
let playerColor = rgbToGuiColor(player.color);
return sprintf(translate("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s. Shift-click to tribute %(greaterAmount)s."), {
resourceAmount: amount,
resourceType: getLocalizedResourceName(resource, "withinSentence"),
playerName: "[color=\"" + playerColor + "\"]" + player.name + "[/color]",
greaterAmount: (amount < 500 ? 500 : amount + 500)
});
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 16624)
@@ -1,1078 +1,1096 @@
// Network Mode
var g_IsNetworked = false;
// Is this user in control of game settings (i.e. is a network server, or offline player)
var g_IsController;
// Match ID for tracking
var g_MatchID;
// Is this user an observer?
var g_IsObserver = false;
// Cache the basic player data (name, civ, color)
var g_Players = [];
// Cache the useful civ data
var g_CivData = {};
var g_GameSpeeds = {};
var g_CurrentSpeed;
var g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1 } };
// Cache dev-mode settings that are frequently or widely used
var g_DevSettings = {
controlAll: false
};
// Whether status bars should be shown for all of the player's units.
var g_ShowAllStatusBars = false;
// Indicate when one of the current player's training queues is blocked
// (this is used to support population counter blinking)
var g_IsTrainingBlocked = false;
// Cache simulation state (updated on every simulation update)
var g_SimState;
// Cache EntityStates
var g_EntityStates = {}; // {id:entState}
// Whether the player has lost/won and reached the end of their game
var g_GameEnded = false;
var g_Disconnected = false; // Lost connection to server
// Holds player states from the last tick
var g_CachedLastStates = "";
// Colors to flash when pop limit reached
const DEFAULT_POPULATION_COLOR = "white";
const POPULATION_ALERT_COLOR = "orange";
// List of additional entities to highlight
var g_ShowGuarding = false;
var g_ShowGuarded = false;
var g_AdditionalHighlight = [];
// for saving the hitpoins of the hero (is there a better way to do that?)
// Should be possible with AttackDetection but might be an overkill because it would have to loop
// always through the list of all ongoing attacks...
var g_previousHeroHitPoints = undefined;
function GetSimState()
{
if (!g_SimState)
g_SimState = Engine.GuiInterfaceCall("GetSimulationState");
return g_SimState;
}
function GetEntityState(entId)
{
if (!(entId in g_EntityStates))
{
var entState = Engine.GuiInterfaceCall("GetEntityState", entId);
if (entState)
entState.extended = false;
g_EntityStates[entId] = entState;
}
return g_EntityStates[entId];
}
function GetExtendedEntityState(entId)
{
if (entId in g_EntityStates)
var entState = g_EntityStates[entId];
else
var entState = Engine.GuiInterfaceCall("GetEntityState", entId);
if (!entState || entState.extended)
return entState;
var extension = Engine.GuiInterfaceCall("GetExtendedEntityState", entId);
for (var prop in extension)
entState[prop] = extension[prop];
entState.extended = true;
g_EntityStates[entId] = entState;
return entState;
}
// Cache TemplateData
var g_TemplateData = {}; // {id:template}
var g_TemplateDataWithoutLocalization = {};
function GetTemplateData(templateName)
{
if (!(templateName in g_TemplateData))
{
var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
translateObjectKeys(template, ["specific", "generic", "tooltip"]);
g_TemplateData[templateName] = template;
}
return g_TemplateData[templateName];
}
function GetTemplateDataWithoutLocalization(templateName)
{
if (!(templateName in g_TemplateDataWithoutLocalization))
{
var template = Engine.GuiInterfaceCall("GetTemplateData", templateName);
g_TemplateDataWithoutLocalization[templateName] = template;
}
return g_TemplateDataWithoutLocalization[templateName];
}
// Cache TechnologyData
var g_TechnologyData = {}; // {id:template}
function GetTechnologyData(technologyName)
{
if (!(technologyName in g_TechnologyData))
{
var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName);
translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]);
g_TechnologyData[technologyName] = template;
}
return g_TechnologyData[technologyName];
}
// Init
function init(initData, hotloadData)
{
if (initData)
{
g_IsNetworked = initData.isNetworked; // Set network mode
g_IsController = initData.isController; // Set controller mode
g_PlayerAssignments = initData.playerAssignments;
g_MatchID = initData.attribs.matchID;
// Cache the player data
// (This may be updated at runtime by handleNetMessage)
g_Players = getPlayerData(g_PlayerAssignments);
if (initData.savedGUIData)
restoreSavedGameData(initData.savedGUIData);
Engine.GetGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked;
}
else // Needed for autostart loading option
{
g_Players = getPlayerData(null);
}
// Cache civ data
g_CivData = loadCivData();
g_CivData["gaia"] = { "Code": "gaia", "Name": translate("Gaia") };
if (Engine.GetPlayerID() <= 0)
{
g_IsObserver = true;
// Hide stuff observers don't use.
Engine.GetGUIObjectByName("food").hidden = true;
Engine.GetGUIObjectByName("wood").hidden = true;
Engine.GetGUIObjectByName("stone").hidden = true;
Engine.GetGUIObjectByName("metal").hidden = true;
Engine.GetGUIObjectByName("population").hidden = true;
Engine.GetGUIObjectByName("diplomacyButton1").hidden = true;
Engine.GetGUIObjectByName("tradeButton1").hidden = true;
Engine.GetGUIObjectByName("menuResignButton").enabled = false;
Engine.GetGUIObjectByName("pauseButton").enabled = false;
Engine.GetGUIObjectByName("observerText").hidden = false;
}
else
{
var civName = g_CivData[g_Players[Engine.GetPlayerID()].civ].Name;
// TODO: Get a civ icon for gaia/observers.
Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[Engine.GetPlayerID()].civ].Emblem;
Engine.GetGUIObjectByName("civIconOverlay").tooltip = sprintf(translate("%(civ)s - Structure Tree"), {"civ": civName});
}
g_GameSpeeds = initGameSpeeds();
g_CurrentSpeed = Engine.GetSimRate();
var gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
gameSpeed.list = g_GameSpeeds.names;
gameSpeed.list_data = g_GameSpeeds.speeds;
var idx = g_GameSpeeds.speeds.indexOf(g_CurrentSpeed);
gameSpeed.selected = idx != -1 ? idx : g_GameSpeeds["default"];
gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); }
initMenuPosition(); // set initial position
// Populate player selection dropdown
var playerNames = [];
var playerIDs = [];
for (var player in g_Players)
{
playerNames.push(g_Players[player].name);
playerIDs.push(player);
}
var viewPlayerDropdown = Engine.GetGUIObjectByName("viewPlayer");
viewPlayerDropdown.list = playerNames;
viewPlayerDropdown.list_data = playerIDs;
viewPlayerDropdown.selected = Engine.GetPlayerID();
// If in Atlas editor, disable the exit button
if (Engine.IsAtlasRunning())
Engine.GetGUIObjectByName("menuExitButton").enabled = false;
if (hotloadData)
g_Selection.selected = hotloadData.selection;
// Starting for the first time:
initMusic();
if (!g_IsObserver)
{
var civMusic = g_CivData[g_Players[Engine.GetPlayerID()].civ].Music;
global.music.storeTracks(civMusic);
}
global.music.setState(global.music.states.PEACE);
playRandomAmbient("temperate");
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
//
// DISABLED: this information isn't currently useful for anything much,
// and it generates a massive amount of data to transmit and store
//setTimeout(function() { reportPerformance(5); }, 5000);
//setTimeout(function() { reportPerformance(60); }, 60000);
}
function selectViewPlayer(playerID)
{
Engine.SetPlayerID(playerID);
if (playerID > 0)
{
var civName = g_CivData[g_Players[playerID].civ].Name
Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem;
Engine.GetGUIObjectByName("civIconOverlay").tooltip = sprintf(translate("%(civ)s - Structure Tree"), {"civ": civName});
}
}
function reportPerformance(time)
{
var settings = Engine.GetMapSettings();
var data = {
time: time,
map: settings.Name,
seed: settings.Seed, // only defined for random maps
size: settings.Size, // only defined for random maps
profiler: Engine.GetProfilerState()
};
Engine.SubmitUserReport("profile", 3, JSON.stringify(data));
}
/**
* Resign a player.
* @param leaveGameAfterResign If player is quitting after resignation.
*/
function resignGame(leaveGameAfterResign)
{
var simState = GetSimState();
// Players can't resign if they've already won or lost.
if (simState.players[Engine.GetPlayerID()].state != "active" || g_Disconnected)
return;
// Tell other players that we have given up and been defeated
Engine.PostNetworkCommand({
"type": "defeat-player",
"playerId": Engine.GetPlayerID()
});
Engine.GetGUIObjectByName("menuResignButton").enabled = false;
global.music.setState(global.music.states.DEFEAT);
// Resume the game if not resigning.
if (!leaveGameAfterResign)
resumeGame();
}
/**
* Leave the game
* @param willRejoin If player is going to be rejoining a networked game.
*/
function leaveGame(willRejoin)
{
var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState");
var mapSettings = Engine.GetMapSettings();
var gameResult;
if (g_IsObserver)
{
// Observers don't win/lose.
gameResult = translate("You have left the game.");
global.music.setState(global.music.states.VICTORY);
}
else
{
var playerState = extendedSimState.players[Engine.GetPlayerID()];
if (g_Disconnected)
gameResult = translate("You have been disconnected.");
else if (playerState.state == "won")
gameResult = translate("You have won the battle!");
else if (playerState.state == "defeated")
gameResult = translate("You have been defeated...");
else // "active"
{
global.music.setState(global.music.states.DEFEAT);
if (willRejoin)
gameResult = translate("You have left the game.");
else
{
gameResult = translate("You have abandoned the game.");
resignGame(true);
}
}
}
stopAmbient();
Engine.EndGame();
if (g_IsController && Engine.HasXmppClient())
Engine.SendUnregisterGame();
Engine.SwitchGuiPage("page_summary.xml", {
"gameResult" : gameResult,
"timeElapsed" : extendedSimState.timeElapsed,
"playerStates": extendedSimState.players,
"players": g_Players,
"mapSettings": mapSettings
});
}
// Return some data that we'll use when hotloading this file after changes
function getHotloadData()
{
return { selection: g_Selection.selected };
}
// Return some data that will be stored in saved game files
function getSavedGameData()
{
var data = {};
data.playerAssignments = g_PlayerAssignments;
data.groups = g_Groups.groups;
// TODO: any other gui state?
return data;
}
function restoreSavedGameData(data)
{
// Restore camera if any
if (data.camera)
Engine.SetCameraData(data.camera.PosX, data.camera.PosY, data.camera.PosZ,
data.camera.RotX, data.camera.RotY, data.camera.Zoom);
// Clear selection when loading a game
g_Selection.reset();
// Restore control groups
for (var groupNumber in data.groups)
{
g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups;
g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents;
}
updateGroups();
}
var lastTickTime = new Date;
/**
* Called every frame.
*/
function onTick()
{
var now = new Date;
var tickLength = new Date - lastTickTime;
lastTickTime = now;
checkPlayerState();
-
while (true)
{
var message = Engine.PollNetworkClient();
if (!message)
break;
handleNetMessage(message);
}
updateCursorAndTooltip();
// If the selection changed, we need to regenerate the sim display (the display depends on both the
// simulation state and the current selection).
if (g_Selection.dirty)
{
g_Selection.dirty = false;
onSimulationUpdate();
// Display rally points for selected buildings
if (!g_IsObserver)
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
}
// Run timers
updateTimers();
// Animate menu
updateMenuPosition(tickLength);
// When training is blocked, flash population (alternates color every 500msec)
if (g_IsTrainingBlocked && (Date.now() % 1000) < 500)
Engine.GetGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR;
else
Engine.GetGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR;
// Clear renamed entities list
Engine.GuiInterfaceCall("ClearRenamedEntities");
}
function checkPlayerState()
{
// Once the game ends, we're done here.
if (g_GameEnded || g_IsObserver)
return;
// Send a game report for each player in this game.
var m_simState = GetSimState();
var playerState = m_simState.players[Engine.GetPlayerID()];
var tempStates = "";
for each (var player in m_simState.players) {tempStates += player.state + ",";}
if (g_CachedLastStates != tempStates)
{
g_CachedLastStates = tempStates;
reportGame(Engine.GuiInterfaceCall("GetExtendedSimulationState"));
}
// If the local player hasn't finished playing, we return here to avoid the victory/defeat messages.
if (playerState.state == "active")
return;
// We can't resign once the game is over.
Engine.GetGUIObjectByName("menuResignButton").enabled = false;
// Make sure nothing is open to avoid stacking.
closeMenu();
closeOpenDialogs();
// Make sure this doesn't run again.
g_GameEnded = true;
if (Engine.IsAtlasRunning())
{
// If we're in Atlas, we can't leave the game
var btCaptions = [translate("OK")];
var btCode = [null];
var message = translate("Press OK to continue");
}
else
{
var btCaptions = [translate("No"), translate("Yes")];
var btCode = [null, leaveGame];
var message = translate("Do you want to quit?");
}
if (playerState.state == "defeated")
{
global.music.setState(global.music.states.DEFEAT);
messageBox(400, 200, message, translate("DEFEATED!"), 0, btCaptions, btCode);
}
else if (playerState.state == "won")
{
global.music.setState(global.music.states.VICTORY);
// TODO: Reveal map directly instead of this silly proxy.
if (!Engine.GetGUIObjectByName("devCommandsRevealMap").checked)
Engine.GetGUIObjectByName("devCommandsRevealMap").checked = true;
messageBox(400, 200, message, translate("VICTORIOUS!"), 0, btCaptions, btCode);
}
}
function changeGameSpeed(speed)
{
// For non-networked games only
if (!g_IsNetworked)
{
Engine.SetSimRate(speed);
g_CurrentSpeed = speed;
}
}
/**
* Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation
* update (see session.xml), or from onTick when the selection has changed.
*/
function onSimulationUpdate()
{
g_EntityStates = {};
g_TemplateData = {};
g_TechnologyData = {};
g_SimState = Engine.GuiInterfaceCall("GetSimulationState");
// If we're called during init when the game is first loading, there will be no simulation yet, so do nothing
if (!g_SimState)
return;
handleNotifications();
if (g_ShowAllStatusBars)
recalculateStatusBarDisplay();
if (g_ShowGuarding || g_ShowGuarded)
updateAdditionalHighlight();
updateHero();
updateGroups();
updateDebug();
updatePlayerDisplay();
updateSelectionDetails();
updateBuildingPlacementPreview();
updateTimeElapsedCounter();
+ updateCeasefireCounter();
updateTimeNotifications();
if (!g_IsObserver)
updateResearchDisplay();
if (!g_IsObserver && !g_GameEnded)
{
// Update music state on basis of battle state.
var battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID());
if (battleState)
global.music.setState(global.music.states[battleState]);
}
}
/**
* updates a status bar on the GUI
* nameOfBar: name of the bar
* points: points to show
* maxPoints: max points
* direction: gets less from (right to left) 0; (top to bottom) 1; (left to right) 2; (bottom to top) 3;
*/
function updateGUIStatusBar(nameOfBar, points, maxPoints, direction)
{
// check, if optional direction parameter is valid.
if (!direction || !(direction >= 0 && direction < 4))
direction = 0;
// get the bar and update it
var statusBar = Engine.GetGUIObjectByName(nameOfBar);
if (!statusBar)
return;
var healthSize = statusBar.size;
var value = 100*Math.max(0, Math.min(1, points / maxPoints));
// inverse bar
if(direction == 2 || direction == 3)
value = 100 - value;
if(direction == 0)
healthSize.rright = value;
else if(direction == 1)
healthSize.rbottom = value;
else if(direction == 2)
healthSize.rleft = value;
else if(direction == 3)
healthSize.rtop = value;
// update bar
statusBar.size = healthSize;
}
function updateHero()
{
var simState = GetSimState();
var playerState = simState.players[Engine.GetPlayerID()];
var unitHeroPanel = Engine.GetGUIObjectByName("unitHeroPanel");
var heroButton = Engine.GetGUIObjectByName("unitHeroButton");
if (!playerState || playerState.heroes.length <= 0)
{
g_previousHeroHitPoints = undefined;
unitHeroPanel.hidden = true;
return;
}
var heroImage = Engine.GetGUIObjectByName("unitHeroImage");
var heroState = GetExtendedEntityState(playerState.heroes[0]);
var template = GetTemplateData(heroState.template);
heroImage.sprite = "stretched:session/portraits/" + template.icon;
var hero = playerState.heroes[0];
heroButton.onpress = function()
{
if (!Engine.HotkeyIsPressed("selection.add"))
g_Selection.reset();
g_Selection.addList([hero]);
};
heroButton.ondoublepress = function() { selectAndMoveTo(getEntityOrHolder(hero)); };
unitHeroPanel.hidden = false;
// Setup tooltip
var tooltip = "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]";
var healthLabel = "[font=\"sans-bold-13\"]" + translate("Health:") + "[/font]";
tooltip += "\n" + sprintf(translate("%(label)s %(current)s / %(max)s"), { label: healthLabel, current: heroState.hitpoints, max: heroState.maxHitpoints });
if (heroState.attack)
tooltip += "\n" + getAttackTooltip(heroState);
tooltip += "\n" + getArmorTooltip(heroState.armour);
if (template.tooltip)
tooltip += "\n" + template.tooltip;
heroButton.tooltip = tooltip;
// update heros health bar
updateGUIStatusBar("heroHealthBar", heroState.hitpoints, heroState.maxHitpoints);
// define the hit points if not defined
if (!g_previousHeroHitPoints)
g_previousHeroHitPoints = heroState.hitpoints;
// if the health of the hero changed since the last update, trigger the animation
if (heroState.hitpoints < g_previousHeroHitPoints)
startColorFade("heroHitOverlay", 100, 0, colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit);
g_previousHeroHitPoints = heroState.hitpoints;
}
function updateGroups()
{
var guiName = "Group";
g_Groups.update();
for (var i = 0; i < 10; i++)
{
var button = Engine.GetGUIObjectByName("unit"+guiName+"Button["+i+"]");
var label = Engine.GetGUIObjectByName("unit"+guiName+"Label["+i+"]").caption = i;
if (g_Groups.groups[i].getTotalCount() == 0)
button.hidden = true;
else
button.hidden = false;
button.onpress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); } })(i);
button.ondoublepress = (function(i) { return function() { performGroup("snap", i); } })(i);
button.onpressright = (function(i) { return function() { performGroup("breakUp", i); } })(i);
setPanelObjectPosition(button, i, 1);
}
}
function updateDebug()
{
let simState = GetSimState();
let debug = Engine.GetGUIObjectByName("debug");
if (!Engine.GetGUIObjectByName("devDisplayState").checked)
{
debug.hidden = true;
return;
}
debug.hidden = false;
let conciseSimState = deepcopy(simState);
conciseSimState.players = "<<>>";
let text = "simulation: " + uneval(conciseSimState);
let selection = g_Selection.toList();
if (selection.length)
{
let entState = GetExtendedEntityState(selection[0]);
if (entState)
{
let template = GetTemplateData(entState.template);
text += "\n\nentity: {\n";
for (let k in entState)
text += " "+k+":"+uneval(entState[k])+"\n";
text += "}\n\ntemplate: " + uneval(template);
}
}
debug.caption = text.replace(/\[/g, "\\[");
}
function updatePlayerDisplay()
{
var simState = GetSimState();
var playerState = simState.players[Engine.GetPlayerID()];
if (!playerState)
return;
Engine.GetGUIObjectByName("resourceFood").caption = Math.floor(playerState.resourceCounts.food);
Engine.GetGUIObjectByName("resourceWood").caption = Math.floor(playerState.resourceCounts.wood);
Engine.GetGUIObjectByName("resourceStone").caption = Math.floor(playerState.resourceCounts.stone);
Engine.GetGUIObjectByName("resourceMetal").caption = Math.floor(playerState.resourceCounts.metal);
Engine.GetGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
Engine.GetGUIObjectByName("population").tooltip = translate("Population (current / limit)") + "\n" +
sprintf(translate("Maximum population: %(popCap)s"), { "popCap": playerState.popMax });
g_IsTrainingBlocked = playerState.trainingBlocked;
}
function selectAndMoveTo(ent)
{
var entState = GetEntityState(ent);
if (!entState || !entState.position)
return;
g_Selection.reset();
g_Selection.addList([ent]);
var position = entState.position;
Engine.CameraMoveTo(position.x, position.z);
}
function updateResearchDisplay()
{
var researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", Engine.GetPlayerID());
if (!researchStarted)
return;
// Set up initial positioning.
var buttonSideLength = Engine.GetGUIObjectByName("researchStartedButton[0]").size.right;
for (var i = 0; i < 10; ++i)
{
var button = Engine.GetGUIObjectByName("researchStartedButton[" + i + "]");
var size = button.size;
size.top = (4 + buttonSideLength) * i;
size.bottom = size.top + buttonSideLength;
button.size = size;
}
var numButtons = 0;
for (var tech in researchStarted)
{
// Show at most 10 in-progress techs.
if (numButtons >= 10)
break;
var template = GetTechnologyData(tech);
var button = Engine.GetGUIObjectByName("researchStartedButton[" + numButtons + "]");
button.hidden = false;
button.tooltip = getEntityNames(template);
button.onpress = (function(e) { return function() { selectAndMoveTo(e) } })(researchStarted[tech].researcher);
var icon = "stretched:session/portraits/" + template.icon;
Engine.GetGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon;
// Scale the progress indicator.
var size = Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size;
// Buttons are assumed to be square, so left/right offsets can be used for top/bottom.
size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left));
Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size;
++numButtons;
}
// Hide unused buttons.
for (var i = numButtons; i < 10; ++i)
Engine.GetGUIObjectByName("researchStartedButton[" + i + "]").hidden = true;
}
+function updateCeasefireCounter()
+{
+ var simState = GetSimState();
+ var isActive = simState.ceasefireActive;
+ var remainingTimeString = timeToString(simState.ceasefireTimeRemaining);
+
+ var ceasefireCounter = Engine.GetGUIObjectByName("ceasefireCounter");
+ var diplomacyCeasefireCounter = Engine.GetGUIObjectByName("diplomacyCeasefireCounter");
+
+ ceasefireCounter.hidden = !isActive || Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") !== "true";
+ diplomacyCeasefireCounter.hidden = !isActive;
+
+ ceasefireCounter.caption = remainingTimeString;
+ diplomacyCeasefireCounter.caption = sprintf(translateWithContext("ceasefire", "Time remaining until ceasefire is over: %(time)s."), {"time": remainingTimeString});
+}
+
function updateTimeElapsedCounter()
{
var simState = GetSimState();
var timeElapsedCounter = Engine.GetGUIObjectByName("timeElapsedCounter");
if (g_CurrentSpeed != 1.0)
timeElapsedCounter.caption = sprintf(translate("%(time)s (%(speed)sx)"), { time: timeToString(simState.timeElapsed), speed: Engine.FormatDecimalNumberIntoString(g_CurrentSpeed) });
else
timeElapsedCounter.caption = timeToString(simState.timeElapsed);
}
// Toggles the display of status bars for all of the player's entities.
function recalculateStatusBarDisplay()
{
if (g_ShowAllStatusBars)
var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID());
else
{
var selected = g_Selection.toList();
for each (var ent in g_Selection.highlighted)
selected.push(ent);
// Remove selected entities from the 'all entities' array, to avoid disabling their status bars.
var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter(
function(idx) { return (selected.indexOf(idx) == -1); }
);
}
Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars });
}
// Update the additional list of entities to be highlighted.
function updateAdditionalHighlight()
{
var entsAdd = []; // list of entities units to be highlighted
var entsRemove = [];
var highlighted = g_Selection.toList();
for each (var ent in g_Selection.highlighted)
highlighted.push(ent);
if (g_ShowGuarding)
{
// flag the guarding entities to add in this additional highlight
for each (var sel in g_Selection.selected)
{
var state = GetEntityState(sel);
if (!state.guard || !state.guard.entities.length)
continue;
for each (var ent in state.guard.entities)
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
entsAdd.push(ent);
}
}
if (g_ShowGuarded)
{
// flag the guarded entities to add in this additional highlight
for each (var sel in g_Selection.selected)
{
var state = GetEntityState(sel);
if (!state.unitAI || !state.unitAI.isGuarding)
continue;
var ent = state.unitAI.isGuarding;
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1)
entsAdd.push(ent);
}
}
// flag the entities to remove (from the previously added) from this additional highlight
for each (var ent in g_AdditionalHighlight)
if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1)
entsRemove.push(ent);
_setHighlight(entsAdd , HIGHLIGHTED_ALPHA, true );
_setHighlight(entsRemove, 0 , false);
g_AdditionalHighlight = entsAdd;
}
// Temporarily adding this here
const AMBIENT_TEMPERATE = "temperate";
var currentAmbient;
function playRandomAmbient(type)
{
switch (type)
{
case AMBIENT_TEMPERATE:
const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg";
Engine.PlayAmbientSound(AMBIENT, true);
break;
default:
error("Unrecognized ambient type: '" + type + "'");
break;
}
}
// Temporarily adding this here
function stopAmbient()
{
if (currentAmbient)
{
currentAmbient.free();
currentAmbient = null;
}
}
function getBuildString()
{
return sprintf(translate("Build: %(buildDate)s (%(revision)s)"), { buildDate: Engine.GetBuildTimestamp(0), revision: Engine.GetBuildTimestamp(2) });
}
function showTimeWarpMessageBox()
{
messageBox(500, 250, translate("Note: time warp mode is a developer option, and not intended for use over long periods of time. Using it incorrectly may cause the game to run out of memory or crash."), translate("Time warp mode"), 2);
}
// Send a report on the game status to the lobby
function reportGame(extendedSimState)
{
if (!Engine.HasXmppClient() || !Engine.IsRankedGame())
return;
// units
var unitsClasses = [
"total",
"Infantry",
"Worker",
"Female",
"Cavalry",
"Champion",
"Hero",
"Ship"
];
var unitsCountersTypes = [
"unitsTrained",
"unitsLost",
"enemyUnitsKilled"
];
// buildings
var buildingsClasses = [
"total",
"CivCentre",
"House",
"Economic",
"Outpost",
"Military",
"Fortress",
"Wonder"
];
var buildingsCountersTypes = [
"buildingsConstructed",
"buildingsLost",
"enemyBuildingsDestroyed"
];
// resources
var resourcesTypes = [
"wood",
"food",
"stone",
"metal"
];
var resourcesCounterTypes = [
"resourcesGathered",
"resourcesUsed",
"resourcesSold",
"resourcesBought"
];
var playerStatistics = { };
// Unit Stats
for each (var unitCounterType in unitsCountersTypes)
{
if (!playerStatistics[unitCounterType])
playerStatistics[unitCounterType] = { };
for each (var unitsClass in unitsClasses)
playerStatistics[unitCounterType][unitsClass] = "";
}
playerStatistics.unitsLostValue = "";
playerStatistics.unitsKilledValue = "";
// Building stats
for each (var buildingCounterType in buildingsCountersTypes)
{
if (!playerStatistics[buildingCounterType])
playerStatistics[buildingCounterType] = { };
for each (var buildingsClass in buildingsClasses)
playerStatistics[buildingCounterType][buildingsClass] = "";
}
playerStatistics.buildingsLostValue = "";
playerStatistics.enemyBuildingsDestroyedValue = "";
// Resources
for each (var resourcesCounterType in resourcesCounterTypes)
{
if (!playerStatistics[resourcesCounterType])
playerStatistics[resourcesCounterType] = { };
for each (var resourcesType in resourcesTypes)
playerStatistics[resourcesCounterType][resourcesType] = "";
}
playerStatistics.resourcesGathered.vegetarianFood = "";
playerStatistics.tradeIncome = "";
// Tribute
playerStatistics.tributesSent = "";
playerStatistics.tributesReceived = "";
// Total
playerStatistics.economyScore = "";
playerStatistics.militaryScore = "";
playerStatistics.totalScore = "";
// Various
playerStatistics.treasuresCollected = "";
playerStatistics.feminisation = "";
playerStatistics.percentMapExplored = "";
var mapName = Engine.GetMapSettings().Name;
var playerStates = "";
var playerCivs = "";
var teams = "";
var teamsLocked = true;
// Serialize the statistics for each player into a comma-separated list.
// Ignore gaia
for (let i = 1; i < extendedSimState.players.length; ++i)
{
let player = extendedSimState.players[i];
playerStates += player.state + ",";
playerCivs += player.civ + ",";
teams += player.team + ",";
teamsLocked = teamsLocked && player.teamsLocked;
for each (var resourcesCounterType in resourcesCounterTypes)
for each (var resourcesType in resourcesTypes)
playerStatistics[resourcesCounterType][resourcesType] += player.statistics[resourcesCounterType][resourcesType] + ",";
playerStatistics.resourcesGathered.vegetarianFood += player.statistics.resourcesGathered.vegetarianFood + ",";
for each (var unitCounterType in unitsCountersTypes)
for each (var unitsClass in unitsClasses)
playerStatistics[unitCounterType][unitsClass] += player.statistics[unitCounterType][unitsClass] + ",";
for each (var buildingCounterType in buildingsCountersTypes)
for each (var buildingsClass in buildingsClasses)
playerStatistics[buildingCounterType][buildingsClass] += player.statistics[buildingCounterType][buildingsClass] + ",";
var total = 0;
for each (var res in player.statistics.resourcesGathered)
total += res;
playerStatistics.economyScore += total + ",";
playerStatistics.militaryScore += Math.round((player.statistics.enemyUnitsKilledValue +
player.statistics.enemyBuildingsDestroyedValue) / 10) + ",";
playerStatistics.totalScore += (total + Math.round((player.statistics.enemyUnitsKilledValue +
player.statistics.enemyBuildingsDestroyedValue) / 10)) + ",";
playerStatistics.tradeIncome += player.statistics.tradeIncome + ",";
playerStatistics.tributesSent += player.statistics.tributesSent + ",";
playerStatistics.tributesReceived += player.statistics.tributesReceived + ",";
playerStatistics.percentMapExplored += player.statistics.percentMapExplored + ",";
playerStatistics.treasuresCollected += player.statistics.treasuresCollected + ",";
}
// Send the report with serialized data
var reportObject = { };
reportObject.timeElapsed = extendedSimState.timeElapsed;
reportObject.playerStates = playerStates;
reportObject.playerID = Engine.GetPlayerID();
reportObject.matchID = g_MatchID;
reportObject.civs = playerCivs;
reportObject.teams = teams;
reportObject.teamsLocked = String(teamsLocked);
+ reportObject.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 each (var rct in resourcesCounterTypes)
{
for each (var rt in resourcesTypes)
reportObject[rt+rct.substr(9)] = playerStatistics[rct][rt];
// eg. rt = food rct.substr = Gathered rct = resourcesGathered
}
reportObject.vegetarianFoodGathered = playerStatistics.resourcesGathered.vegetarianFood;
for each (var type in unitsClasses)
{
// eg. type = Infantry (type.substr(0,1)).toLowerCase()+type.substr(1) = infantry
reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"UnitsTrained"] = playerStatistics.unitsTrained[type];
reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"UnitsLost"] = playerStatistics.unitsLost[type];
reportObject["enemy"+type+"UnitsKilled"] = playerStatistics.enemyUnitsKilled[type];
}
for each (var type in buildingsClasses)
{
reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"BuildingsConstructed"] = playerStatistics.buildingsConstructed[type];
reportObject[(type.substr(0,1)).toLowerCase()+type.substr(1)+"BuildingsLost"] = playerStatistics.buildingsLost[type];
reportObject["enemy"+type+"BuildingsDestroyed"] = playerStatistics.enemyBuildingsDestroyed[type];
}
reportObject.tributesSent = playerStatistics.tributesSent;
reportObject.tributesReceived = playerStatistics.tributesReceived;
reportObject.percentMapExplored = playerStatistics.percentMapExplored;
reportObject.treasuresCollected = playerStatistics.treasuresCollected;
reportObject.tradeIncome = playerStatistics.tradeIncome;
Engine.SendGameReport(reportObject);
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 16624)
@@ -1,342 +1,347 @@
onTick();
onSimulationUpdate();
this.hidden = !this.hidden;
toggleDeveloperOverlay();Control all units
g_DevSettings.controlAll = this.checked;
Engine.PostNetworkCommand( {"type": "control-all", "flag": this.checked} );
Change perspectiveEngine.GetGUIObjectByName("viewPlayer").hidden = !this.checked;Display selection statePathfinder overlayEngine.GuiInterfaceCall("SetPathfinderDebugOverlay", this.checked);Obstruction overlayEngine.GuiInterfaceCall("SetObstructionDebugOverlay", this.checked);Unit motion overlayg_Selection.SetMotionDebugOverlay(this.checked);Range overlayEngine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);Bounding box overlayEngine.SetBoundingBoxDebugOverlay(this.checked);Restrict camera
Engine.GameView_SetConstrainCameraEnabled(this.checked);
Reveal mapthis.checked = Engine.GuiInterfaceCall("IsMapRevealed");Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});Enable time warp
if (this.checked)
showTimeWarpMessageBox();
Engine.EnableTimeWarpRecording(this.checked ? 10 : 0);Promote selected unitsEngine.PostNetworkCommand({"type": "promote", "entities": g_Selection.toList()});
-
+
this.hidden = Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") !== "true";
Engine.ConfigDB_CreateValue("user", "gui.session.timeelapsedcounter", ""+this.hidden);
+
+
+ Engine.ConfigDB_CreateValue("user", "gui.session.ceasefirecounter", ""+this.hidden);
+
+ Game PausedClick to Resume GametogglePause();submitChatInput();
var players = [];
for (var player in g_PlayerAssignments)
players.push(g_PlayerAssignments[player]);
autoCompleteNick("chatInput", players);
CancelcloseChat();Team OnlySendsubmitChatInput();ExitleaveGame()
Index: ps/trunk/binaries/data/mods/public/gui/session/session_objects/research_progress.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session_objects/research_progress.xml (revision 16623)
+++ ps/trunk/binaries/data/mods/public/gui/session/session_objects/research_progress.xml (revision 16624)
@@ -1,9 +1,9 @@
-
+
Index: ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/simulation/ai/petra/defenseManager.js (revision 16624)
@@ -1,542 +1,542 @@
var PETRA = function(m)
{
m.DefenseManager = function(Config)
{
this.armies = []; // array of "army" Objects
this.Config = Config;
this.targetList = [];
this.armyMergeSize = this.Config.Defense.armyMergeSize;
}
m.DefenseManager.prototype.update = function(gameState, events)
{
Engine.ProfileStart("Defense Manager");
this.territoryMap = gameState.ai.HQ.territoryMap;
this.checkEvents(gameState, events);
this.checkEnemyArmies(gameState, events);
this.checkEnemyUnits(gameState);
this.assignDefenders(gameState);
Engine.ProfileStop();
};
m.DefenseManager.prototype.makeIntoArmy = function(gameState, entityID)
{
// Try to add it to an existing army.
for (let army of this.armies)
if (army.addFoe(gameState, entityID))
return; // over
// Create a new army for it.
var army = new m.DefenseArmy(gameState, [], [entityID]);
this.armies.push(army);
};
m.DefenseManager.prototype.getArmy = function(partOfArmy)
{
// Find the army corresponding to this ID partOfArmy
for (let army of this.armies)
if (army.ID === partOfArmy)
return army;
return undefined;
};
// TODO: this algorithm needs to be improved, sorta.
m.DefenseManager.prototype.isDangerous = function(gameState, entity)
{
if (!entity.position())
return false;
if (this.territoryMap.getOwner(entity.position()) === entity.owner())
return false;
// check if the entity is trying to build a new base near our buildings, and if yes, add this base in our target list
if (entity.unitAIState() && entity.unitAIState() == "INDIVIDUAL.REPAIR.REPAIRING")
{
var targetId = entity.unitAIOrderData()[0]["target"];
if (this.targetList.indexOf(targetId) !== -1)
return true;
var target = gameState.getEntityById(targetId);
if (target && this.territoryMap.getOwner(entity.position()) === PlayerID)
{
this.targetList.push(targetId);
return true;
}
else if (target && target.hasClass("CivCentre"))
{
var myBuildings = gameState.getOwnStructures();
for (let building of myBuildings.values())
{
if (API3.SquareVectorDistance(building.position(), entity.position()) > 30000)
continue;
this.targetList.push(targetId);
return true;
}
}
}
if (entity.attackTypes() === undefined || entity.hasClass("Support"))
return false;
for (var i = 0; i < this.targetList.length; ++i)
{
var target = gameState.getEntityById(this.targetList[i]);
if (!target || !target.position()) // the enemy base is either destroyed or built
this.targetList.splice(i--, 1);
else if (API3.SquareVectorDistance(target.position(), entity.position()) < 6000)
return true;
}
if (this.Config.personality.cooperative > 0.3)
{
let ccEnts = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
for (let cc of ccEnts.values())
{
if (!gameState.isEntityExclusiveAlly(cc))
continue;
if (this.Config.personality.cooperative < 0.6 && cc.foundationProgress() !== undefined)
continue;
if (API3.SquareVectorDistance(cc.position(), entity.position()) < 6000)
return true;
}
}
var myBuildings = gameState.getOwnStructures();
for (let building of myBuildings.values())
{
if (building.foundationProgress() == 0)
continue;
if (API3.SquareVectorDistance(building.position(), entity.position()) < 6000)
return true;
}
return false;
};
m.DefenseManager.prototype.checkEnemyUnits = function(gameState)
{
var nbPlayers = gameState.sharedScript.playersData.length;
var i = gameState.ai.playedTurn % nbPlayers;
- if (i === PlayerID || gameState.isPlayerAlly(i))
+ if (i === PlayerID || !gameState.isPlayerEnemy(i))
return;
// loop through enemy units
for (let ent of gameState.getEnemyUnits(i).values())
{
if (ent.getMetadata(PlayerID, "PartOfArmy") !== undefined)
continue;
// keep animals attacking us or our allies
if (ent.hasClass("Animal"))
{
if (!ent.unitAIState() || ent.unitAIState().split(".")[1] !== "COMBAT")
continue;
let orders = ent.unitAIOrderData();
if (!orders || !orders.length || !orders[0]["target"])
continue;
let target = gameState.getEntityById(orders[0]["target"]);
if (!target || !gameState.isPlayerAlly(target.owner()))
continue;
}
// TODO what to do for ships ?
if (ent.hasClass("Ship") || ent.hasClass("Trader"))
continue;
// check if unit is dangerous "a priori"
if (this.isDangerous(gameState, ent))
this.makeIntoArmy(gameState, ent.id());
}
if (i !== 0 || this.armies.length > 1 || gameState.ai.HQ.numActiveBase() === 0)
return;
// look for possible gaia buildings inside our territory (may happen when enemy resign or after structure decay)
for (let ent of gameState.getEnemyStructures(i).values())
{
if (!ent.position() || ent.getMetadata(PlayerID, "PartOfArmy") !== undefined)
continue;
let owner = this.territoryMap.getOwner(ent.position());;
if (owner === PlayerID)
this.makeIntoArmy(gameState, ent.id());
}
};
m.DefenseManager.prototype.checkEnemyArmies = function(gameState, events)
{
for (var i = 0; i < this.armies.length; ++i)
{
var army = this.armies[i];
army.checkEvents(gameState, events); // must be called every turn for all armies
// this returns a list of IDs: the units that broke away from the army for being too far.
var breakaways = army.update(gameState);
for (let breakers of breakaways)
this.makeIntoArmy(gameState, breakers); // assume dangerosity
if (army.getState(gameState) === 0)
{
army.clear(gameState);
this.armies.splice(i--,1);
continue;
}
}
// Check if we can't merge it with another
for (let i = 0; i < this.armies.length - 1; ++i)
{
let army = this.armies[i];
if (army.isCapturing(gameState))
continue;
for (let j = i+1; j < this.armies.length; ++j)
{
let otherArmy = this.armies[j];
if (otherArmy.isCapturing(gameState) ||
API3.SquareVectorDistance(army.foePosition, otherArmy.foePosition) > this.armyMergeSize)
continue;
// no need to clear here.
army.merge(gameState, otherArmy);
this.armies.splice(j--,1);
}
}
if (gameState.ai.playedTurn % 5 !== 0)
return;
// Check if any army is no more dangerous (possibly because it has defeated us and destroyed our base)
for (var i = 0; i < this.armies.length; ++i)
{
var army = this.armies[i];
army.recalculatePosition(gameState);
var owner = this.territoryMap.getOwner(army.foePosition);
- if (gameState.isPlayerAlly(owner))
+ if (!gameState.isPlayerEnemy(owner))
continue;
else if (owner !== 0) // enemy army back in its territory
{
army.clear(gameState);
this.armies.splice(i--,1);
continue;
}
// army in neutral territory
// TODO check smaller distance with all our buildings instead of only ccs with big distance
var stillDangerous = false;
let bases = gameState.updatingGlobalCollection("allCCs", API3.Filters.byClass("CivCentre"));
for (let base of bases.values())
{
if (!gameState.isEntityAlly(base))
continue;
if (this.Config.personality.cooperative < 0.3 && !gameState.isEntityOwn(base))
continue;
if (API3.SquareVectorDistance(base.position(), army.foePosition) > 40000)
continue;
if(this.Config.debug > 1)
API3.warn("army in neutral territory, but still near one of our CC");
stillDangerous = true;
break;
}
if (stillDangerous)
continue;
army.clear(gameState);
this.armies.splice(i--,1);
}
};
m.DefenseManager.prototype.assignDefenders = function(gameState)
{
if (this.armies.length === 0)
return;
var armiesNeeding = [];
// Okay, let's add defenders
// TODO: this is dumb.
for (let army of this.armies)
{
let needsDef = army.needsDefenders(gameState);
if (needsDef === false)
continue;
// Okay for now needsDef is the total needed strength.
// we're dumb so we don't choose if we have a defender shortage.
armiesNeeding.push( {"army": army, "need": needsDef} );
}
if (armiesNeeding.length === 0)
return;
// let's get our potential units
var potentialDefenders = [];
gameState.getOwnUnits().forEach(function(ent) {
if (!ent.position())
return;
if (ent.getMetadata(PlayerID, "plan") === -2 || ent.getMetadata(PlayerID, "plan") === -3)
return;
if (ent.hasClass("Support") || ent.attackTypes() === undefined)
return;
if (ent.hasClass("Siege") && !ent.hasClass("Melee"))
return;
if (ent.hasClass("FishingBoat") || ent.hasClass("Trader"))
return;
if (ent.getMetadata(PlayerID, "transport") !== undefined || ent.getMetadata(PlayerID, "transporter") !== undefined)
return;
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
{
var subrole = ent.getMetadata(PlayerID, "subrole");
if (subrole && (subrole === "completing" || subrole === "walking" || subrole === "attacking"))
return;
}
potentialDefenders.push(ent.id());
});
for (var a = 0; a < armiesNeeding.length; ++a)
armiesNeeding[a]["army"].recalculatePosition(gameState);
for (var i = 0; i < potentialDefenders.length; ++i)
{
var ent = gameState.getEntityById(potentialDefenders[i]);
if (!ent.position())
continue;
var aMin = undefined;
var distMin = undefined;
for (var a = 0; a < armiesNeeding.length; ++a)
{
var dist = API3.SquareVectorDistance(ent.position(), armiesNeeding[a]["army"].foePosition);
if (aMin !== undefined && dist > distMin)
continue;
aMin = a;
distMin = dist;
}
if (aMin === undefined)
{
for (var a = 0; a < armiesNeeding.length; ++a)
API3.warn(" defense/armiesNeeding " + uneval(armiesNeeding[a]["need"]));
}
var str = m.getMaxStrength(ent);
armiesNeeding[aMin]["need"] -= str;
armiesNeeding[aMin]["army"].addOwn(gameState, potentialDefenders[i]);
armiesNeeding[aMin]["army"].assignUnit(gameState, potentialDefenders[i]);
if (armiesNeeding[aMin]["need"] <= 0)
armiesNeeding.splice(aMin, 1);
if (armiesNeeding.length === 0)
break;
}
if (armiesNeeding.length === 0)
return;
// If shortage of defenders, produce infantry garrisoned in nearest civil centre
var armiesPos = [];
for (var a = 0; a < armiesNeeding.length; ++a)
armiesPos.push(armiesNeeding[a]["army"].foePosition);
gameState.ai.HQ.trainEmergencyUnits(gameState, armiesPos);
};
m.DefenseManager.prototype.abortArmy = function(gameState, army)
{
army.clear(gameState);
for (let i = 0; i < this.armies.length; ++i)
{
if (this.armies[i].ID !== army.ID)
continue;
this.armies.splice(i, 1);
break;
}
};
// If our defense structures are attacked, garrison soldiers inside when possible
// and if a support unit is attacked and has less than 55% health, garrison it inside the nearest healing structure
// and if a ranged siege unit (not used for defense) is attacked, garrison it in the nearest fortress
m.DefenseManager.prototype.checkEvents = function(gameState, events)
{
var attackedEvents = events["Attacked"];
for (var evt of attackedEvents)
{
var target = gameState.getEntityById(evt.target);
if (!target || !gameState.isEntityOwn(target) || !target.position())
continue;
if (target.hasClass("Ship")) // TODO integrate ships later need to be sure it is accessible
continue;
if (target.getMetadata(PlayerID, "PartOfArmy") !== undefined)
{
let army = this.getArmy(target.getMetadata(PlayerID, "PartOfArmy"));
if (army.isCapturing(gameState))
this.abortArmy(gameState, army);
}
if (target.hasClass("Support") && target.healthLevel() < 0.55 && !target.getMetadata(PlayerID, "transport")
&& target.getMetadata(PlayerID, "plan") !== -2 && target.getMetadata(PlayerID, "plan") !== -3)
{
this.garrisonUnitForHealing(gameState, target);
continue;
}
if (target.hasClass("Siege") && !target.hasClass("Melee") && !target.getMetadata(PlayerID, "transport")
&& target.getMetadata(PlayerID, "plan") !== -2 && target.getMetadata(PlayerID, "plan") !== -3)
{
if (target.getMetadata(PlayerID, "plan") !== undefined && target.getMetadata(PlayerID, "plan") !== -1)
{
var subrole = target.getMetadata(PlayerID, "subrole");
if (subrole && (subrole === "completing" || subrole === "walking" || subrole === "attacking"))
continue;
}
this.garrisonSiegeUnit(gameState, target);
continue;
}
var attacker = gameState.getEntityById(evt.attacker);
if (!attacker || !attacker.position())
continue;
if (target.isGarrisonHolder() && target.getArrowMultiplier())
this.garrisonRangedUnitsInside(gameState, target, {"attacker": attacker});
}
};
m.DefenseManager.prototype.garrisonRangedUnitsInside = function(gameState, target, data)
{
let minGarrison = (data.min ? data.min : target.garrisonMax());
let typeGarrison = (data.type ? data.type : "protection");
if (gameState.ai.HQ.garrisonManager.numberOfGarrisonedUnits(target) >= minGarrison)
return;
if (target.hitpoints() < target.garrisonEjectHealth() * target.maxHitpoints())
return;
if (data.attacker)
{
let attackTypes = target.attackTypes();
if (!attackTypes || attackTypes.indexOf("Ranged") === -1)
return;
let dist = API3.SquareVectorDistance(data.attacker.position(), target.position());
let range = target.attackRange("Ranged").max;
if (dist >= range*range)
return;
}
var index = gameState.ai.accessibility.getAccessValue(target.position());
var garrisonManager = gameState.ai.HQ.garrisonManager;
var garrisonArrowClasses = target.getGarrisonArrowClasses();
var units = gameState.getOwnUnits().filter(function (ent) { return MatchesClassList(garrisonArrowClasses, ent.classes()); }).filterNearest(target.position());
for (let ent of units.values())
{
if (garrisonManager.numberOfGarrisonedUnits(target) >= minGarrison)
break;
if (!ent.position())
continue;
if (ent.getMetadata(PlayerID, "transport") !== undefined)
continue;
if (ent.getMetadata(PlayerID, "plan") === -2 || ent.getMetadata(PlayerID, "plan") === -3)
continue;
if (ent.getMetadata(PlayerID, "plan") !== undefined && ent.getMetadata(PlayerID, "plan") !== -1)
{
var subrole = ent.getMetadata(PlayerID, "subrole");
if (subrole && (subrole === "completing" || subrole === "walking" || subrole === "attacking"))
continue;
}
if (gameState.ai.accessibility.getAccessValue(ent.position()) !== index)
continue;
garrisonManager.garrison(gameState, ent, target, typeGarrison);
}
};
// garrison a attacked siege ranged unit inside the nearest fortress
m.DefenseManager.prototype.garrisonSiegeUnit = function(gameState, unit)
{
let distmin = Math.min();
let nearest = undefined;
let unitAccess = gameState.ai.accessibility.getAccessValue(unit.position());
let garrisonManager = gameState.ai.HQ.garrisonManager;
gameState.getAllyStructures().forEach(function(ent) {
if (!MatchesClassList(ent.garrisonableClasses(), unit.classes()))
return;
if (garrisonManager.numberOfGarrisonedUnits(ent) >= ent.garrisonMax())
return;
if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints())
return;
var entAccess = ent.getMetadata(PlayerID, "access");
if (!entAccess)
{
entAccess = gameState.ai.accessibility.getAccessValue(ent.position());
ent.setMetadata(PlayerID, "access", entAccess);
}
if (entAccess !== unitAccess)
return;
var dist = API3.SquareVectorDistance(ent.position(), unit.position());
if (dist > distmin)
return;
distmin = dist;
nearest = ent;
});
if (nearest)
garrisonManager.garrison(gameState, unit, nearest, "protection");
};
// garrison a hurt unit inside the nearest healing structure
m.DefenseManager.prototype.garrisonUnitForHealing = function(gameState, unit)
{
let distmin = Math.min();
let nearest = undefined;
let unitAccess = gameState.ai.accessibility.getAccessValue(unit.position());
let garrisonManager = gameState.ai.HQ.garrisonManager;
gameState.getAllyStructures().forEach(function(ent) {
if (!ent.buffHeal())
return;
if (!MatchesClassList(ent.garrisonableClasses(), unit.classes()))
return;
if (garrisonManager.numberOfGarrisonedUnits(ent) >= ent.garrisonMax())
return;
if (ent.hitpoints() < ent.garrisonEjectHealth() * ent.maxHitpoints())
return;
var entAccess = ent.getMetadata(PlayerID, "access");
if (!entAccess)
{
entAccess = gameState.ai.accessibility.getAccessValue(ent.position());
ent.setMetadata(PlayerID, "access", entAccess);
}
if (entAccess !== unitAccess)
return;
var dist = API3.SquareVectorDistance(ent.position(), unit.position());
if (dist > distmin)
return;
distmin = dist;
nearest = ent;
});
if (nearest)
garrisonManager.garrison(gameState, unit, nearest, "protection");
};
m.DefenseManager.prototype.Serialize = function()
{
let properties = {
"targetList" : this.targetList,
"armyMergeSize": this.armyMergeSize
};
let armies = [];
for (var army of this.armies)
armies.push(army.Serialize());
return { "properties": properties, "armies": armies };
};
m.DefenseManager.prototype.Deserialize = function(gameState, data)
{
for (let key in data.properties)
this[key] = data.properties[key];
this.armies = [];
for (let dataArmy of data.armies)
{
let army = new m.DefenseArmy(gameState, [], []);
army.Deserialize(dataArmy);
this.armies.push(army);
}
};
return m;
}(PETRA);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Capturable.js (revision 16624)
@@ -1,243 +1,243 @@
function Capturable() {}
Capturable.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Capturable.prototype.Init = function()
{
// Cache this value
this.maxCp = +this.template.CapturePoints;
this.cp = [];
this.StartRegenTimer();
};
//// Interface functions ////
/**
* Returns the current capture points array
*/
Capturable.prototype.GetCapturePoints = function()
{
return this.cp;
};
Capturable.prototype.GetMaxCapturePoints = function()
{
return this.maxCp;
};
Capturable.prototype.GetGarrisonRegenRate = function()
{
return ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", +this.template.GarrisonRegenRate, this.entity);
};
/**
* Set the new capture points, used for cloning entities
* The caller should assure that the sum of capture points
* matches the max.
*/
Capturable.prototype.SetCapturePoints = function(capturePointsArray)
{
this.cp = capturePointsArray;
};
/**
* Reduces the amount of capture points of an entity,
* in favour of the player of the source
* Returns the number of capture points actually taken
*/
Capturable.prototype.Reduce = function(amount, playerID)
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
return 0;
var cmpPlayerSource = QueryPlayerIDInterface(playerID);
if (!cmpPlayerSource)
return 0;
// Before changing the value, activate Fogging if necessary to hide changes
var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging)
cmpFogging.Activate();
- var enemiesFilter = function(v, i) { return v > 0 && !cmpPlayerSource.IsAlly(i); };
+ var enemiesFilter = function(v, i) { return v > 0 && cmpPlayerSource.IsEnemy(i); };
var numberOfEnemies = this.cp.filter(enemiesFilter).length;
if (numberOfEnemies == 0)
return 0;
// distribute the capture points over all enemies
var distributedAmount = amount / numberOfEnemies;
for (let i in this.cp)
{
- if (cmpPlayerSource.IsAlly(i))
+ if (!cmpPlayerSource.IsEnemy(i))
continue;
if (this.cp[i] > distributedAmount)
this.cp[i] -= distributedAmount;
else
this.cp[i] = 0;
}
// give all cp taken to the player
var takenCp = this.maxCp - this.cp.reduce(function(a, b) { return a + b; });
this.cp[playerID] += takenCp;
this.StartRegenTimer();
Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp })
if (this.cp[cmpOwnership.GetOwner()] > 0)
return takenCp;
// if all cp has been taken from the owner, convert it to the best player
var bestPlayer = 0;
for (let i in this.cp)
if (this.cp[i] >= this.cp[bestPlayer])
bestPlayer = +i;
cmpOwnership.SetOwner(bestPlayer);
return takenCp;
};
/**
* Check if the source can (re)capture points from this building
*/
Capturable.prototype.CanCapture = function(playerID)
{
var cmpPlayerSource = QueryPlayerIDInterface(playerID);
if (!cmpPlayerSource)
warn(playerID + " has no player component defined on its id");
var cp = this.GetCapturePoints()
var sourceEnemyCp = 0;
for (let i in this.GetCapturePoints())
- if (!cmpPlayerSource.IsAlly(i))
+ if (cmpPlayerSource.IsEnemy(i))
sourceEnemyCp += cp[i];
return sourceEnemyCp > 0;
};
//// Private functions ////
Capturable.prototype.GetRegenRate = function()
{
var regenRate = +this.template.RegenRate;
regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity);
var cmpTerritoryDecay = Engine.QueryInterface(this.entity, IID_TerritoryDecay);
if (cmpTerritoryDecay && cmpTerritoryDecay.IsDecaying())
var territoryDecayRate = cmpTerritoryDecay.GetDecayRate();
else
var territoryDecayRate = 0;
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (cmpGarrisonHolder)
var garrisonRegenRate = this.GetGarrisonRegenRate() * cmpGarrisonHolder.GetEntities().length;
else
var garrisonRegenRate = 0;
return regenRate + garrisonRegenRate - territoryDecayRate;
};
Capturable.prototype.RegenCapturePoints = function()
{
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
return;
var regenRate = this.GetRegenRate();
if (regenRate < 0)
var takenCp = this.Reduce(-regenRate, 0);
else
var takenCp = this.Reduce(regenRate, cmpOwnership.GetOwner())
if (takenCp > 0)
return;
// no capture points taken, stop the timer
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.regenTimer);
this.regenTimer = 0;
Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": false, "rate": 0});
};
/**
* Start the regeneration timer when no timer exists
* When nothing can be regenerated (f.e. because the
* rate is 0, or because it is fully regenerated),
* the timer stops automatically after one execution.
*/
Capturable.prototype.StartRegenTimer = function()
{
if (this.regenTimer)
return;
var rate = this.GetRegenRate();
if (rate == 0)
return;
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.regenTimer = cmpTimer.SetInterval(this.entity, IID_Capturable, "RegenCapturePoints", 1000, 1000, null);
Engine.PostMessage(this.entity, MT_CaptureRegenStateChanged, {"regenerating": true, "rate": rate});
};
//// Message Listeners ////
Capturable.prototype.OnValueModification = function(msg)
{
if (msg.component != "Capturable")
return;
var oldMaxCp = this.GetMaxCapturePoints();
this.maxCp = ApplyValueModificationsToEntity("Capturable/Max", +this.template.Max, this.entity);
if (oldMaxCp == this.maxCp)
return;
var scale = this.maxCp / oldMaxCp;
for (let i in this.cp)
this.cp[i] *= scale;
Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp });
this.StartRegenTimer();
};
Capturable.prototype.OnGarrisonedUnitsChanged = function(msg)
{
this.StartRegenTimer();
};
Capturable.prototype.OnTerritoryDecayChanged = function(msg)
{
if (msg.to)
this.StartRegenTimer();
};
Capturable.prototype.OnOwnershipChanged = function(msg)
{
this.StartRegenTimer();
// if the new owner has no capture points, it means that either
// * it's being initialised now, or
// * it changed ownership for a different reason (defeat, atlas, ...)
if (this.cp[msg.to])
return;
// initialise the capture points when created
this.cp = [];
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
if (i == msg.to)
this.cp[i] = this.maxCp;
else
this.cp[i] = 0;
};
Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable);
Index: ps/trunk/binaries/data/mods/public/simulation/components/CeasefireManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/CeasefireManager.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/components/CeasefireManager.js (revision 16624)
@@ -0,0 +1,174 @@
+function CeasefireManager() {}
+
+CeasefireManager.prototype.Schema =
+ "Lists the sound groups associated with this unit." +
+ "" +
+ "" +
+ "interface/alarm/alarm_alert_0.xml" +
+ "" +
+ "" +
+ "" +
+ "" + /* TODO: make this more specific, like a list of specific elements */
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+
+CeasefireManager.prototype.Init = function()
+{
+ // Weather or not ceasefire is active currently.
+ this.ceasefireIsActive = false;
+
+ // Ceasefire timeout in milliseconds
+ this.ceasefireTime = 0;
+
+ // Time elapsed when the ceasefire was started
+ this.ceasefireStartedTime = 0;
+
+ // diplomacy states before the ceasefire started
+ this.diplomacyBeforeCeasefire = [];
+
+ // Message duration for the countdown in milliseconds
+ this.countdownMessageDuration = 10000;
+
+ // Duration for the post ceasefire message in milliseconds
+ this.postCountdownMessageDuration = 5000;
+};
+
+CeasefireManager.prototype.IsCeasefireActive = function()
+{
+ return this.ceasefireIsActive;
+};
+
+CeasefireManager.prototype.GetCeasefireStartedTime = function()
+{
+ return this.ceasefireStartedTime;
+};
+
+CeasefireManager.prototype.GetCeasefireTime = function()
+{
+ return this.ceasefireTime;
+};
+
+CeasefireManager.prototype.GetDiplomacyBeforeCeasefire = function()
+{
+ return this.diplomacyBeforeCeasefire;
+};
+
+CeasefireManager.prototype.StartCeasefire = function(ceasefireTime)
+{
+ // If invalid timeout given, return
+ if (ceasefireTime <= 0)
+ return;
+
+ // Remove existing timers
+ var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ if (this.ceasefireCountdownMessageTimer)
+ cmpTimer.CancelTimer(this.ceasefireCountdownMessageTimer);
+
+ if (this.stopCeasefireTimer)
+ cmpTimer.CancelTimer(this.stopCeasefireTimer);
+
+ // Remove existing messages
+ var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ if (this.ceasefireCountdownMessage)
+ cmpGuiInterface.DeleteTimeNotification(this.ceasefireCountdownMessage);
+
+ if (this.ceasefireEndedMessage)
+ cmpGuiInterface.DeleteTimeNotification(this.ceasefireEndedMessage);
+
+
+ // Save diplomacy and set everyone neutral
+ if (!this.ceasefireIsActive)
+ {
+ // Save diplomacy
+ var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities();
+ for (var i = 1; i < playerEntities.length; i++)
+ {
+ // Copy array with slice(), otherwise it will change
+ var cmpPlayer = Engine.QueryInterface(playerEntities[i], IID_Player);
+ this.diplomacyBeforeCeasefire.push(cmpPlayer.GetDiplomacy().slice());
+ }
+
+ // Set every enemy (except gaia) to neutral
+ for (var i = 1; i < playerEntities.length; i++)
+ {
+ var cmpPlayer = Engine.QueryInterface(playerEntities[i], IID_Player);
+ for (var j = 1; j < playerEntities.length; j++)
+ {
+ if (this.diplomacyBeforeCeasefire[i-1][j] < 0)
+ cmpPlayer.SetNeutral(j);
+ }
+ }
+ }
+
+ // Save other data
+ this.ceasefireIsActive = true;
+ this.ceasefireTime = ceasefireTime;
+ this.ceasefireStartedTime = cmpTimer.GetTime();
+
+ // Send message
+ Engine.PostMessage(SYSTEM_ENTITY, MT_CeasefireStarted);
+
+ // Add timers for countdown message and reseting diplomacy
+ this.stopCeasefireTimer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_CeasefireManager, "StopCeasefire", this.ceasefireTime);
+ this.ceasefireCountdownMessageTimer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_CeasefireManager, "ShowCeasefireCountdownMessage",
+ this.ceasefireTime - this.countdownMessageDuration, this.countdownMessageDuration);
+};
+
+CeasefireManager.prototype.ShowCeasefireCountdownMessage = function(duration)
+{
+ var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ this.ceasefireCountdownMessage = cmpGuiInterface.AddTimeNotification({
+ "message": markForTranslation("You can attack in %(time)s"),
+ "translateMessage": true
+ }, duration);
+};
+
+CeasefireManager.prototype.StopCeasefire = function()
+{
+ var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+
+ // Remove previous message
+ if (this.ceasefireCountdownMessage)
+ cmpGuiInterface.DeleteTimeNotification(this.ceasefireCountdownMessage);
+
+ // Show new message
+ this.ceasefireEndedMessage = cmpGuiInterface.AddTimeNotification({
+ "message": markForTranslation("You can attack now!"),
+ "translateMessage": true
+ }, this.postCountdownMessageDuration);
+
+ // Reset diplomacies to original settings
+ var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities();
+ for (var i = 1; i < playerEntities.length; i++)
+ {
+ var cmpPlayer = Engine.QueryInterface(playerEntities[i], IID_Player);
+ cmpPlayer.SetDiplomacy(this.diplomacyBeforeCeasefire[i-1]);
+ }
+
+ // Send chat notifications and update the diplomacy screen
+ for (var i = 1; i < playerEntities.length; i++)
+ {
+ var cmpPlayer = Engine.QueryInterface(playerEntities[i], IID_Player);
+ for (var j = 1; j < playerEntities.length; j++)
+ {
+ if (i != j && this.diplomacyBeforeCeasefire[i-1][j] == -1)
+ cmpGuiInterface.PushNotification({"type": "diplomacy", "players": [j], "player1": [i], "status": "enemy"});
+ }
+ }
+
+ // Reset values
+ this.ceasefireIsActive = false;
+ this.ceasefireTime = 0;
+ this.ceasefireStartedTime = 0;
+ this.diplomacyBeforeCeasefire = [];
+
+ // Send message
+ Engine.PostMessage(SYSTEM_ENTITY, MT_CeasefireEnded);
+};
+
+Engine.RegisterSystemComponentType(IID_CeasefireManager, "CeasefireManager", CeasefireManager);
Index: ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/simulation/components/GuiInterface.js (revision 16624)
@@ -1,1905 +1,1924 @@
function GuiInterface() {}
GuiInterface.prototype.Schema =
"";
GuiInterface.prototype.Serialize = function()
{
// This component isn't network-synchronised for the biggest part
// So most of the attributes shouldn't be serialized
// Return an object with a small selection of deterministic data
return {
"timeNotifications": this.timeNotifications,
"timeNotificationID": this.timeNotificationID
};
};
GuiInterface.prototype.Deserialize = function(data)
{
this.Init();
this.timeNotifications = data.timeNotifications;
this.timeNotificationID = data.timeNotificationID;
};
GuiInterface.prototype.Init = function()
{
this.placementEntity = undefined; // = undefined or [templateName, entityID]
this.placementWallEntities = undefined;
this.placementWallLastAngle = 0;
this.notifications = [];
this.renamedEntities = [];
this.miragedEntities = [];
this.timeNotificationID = 1;
this.timeNotifications = [];
this.entsRallyPointsDisplayed = [];
this.entsWithAuraAndStatusBars = new Set();
};
/*
* All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
* from GUI scripts, and executed here with arguments (player, arg).
*
* CAUTION: The input to the functions in this module is not network-synchronised, so it
* mustn't affect the simulation state (i.e. the data that is serialised and can affect
* the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
*/
/**
* Returns global information about the current game state.
* This is used by the GUI and also by AI scripts.
*/
GuiInterface.prototype.GetSimulationState = function(player)
{
var ret = {
"players": []
};
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerEntityLimits = Engine.QueryInterface(playerEnt, IID_EntityLimits);
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
// Work out what phase we are in
var phase = "";
var cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
if (cmpTechnologyManager)
{
if (cmpTechnologyManager.IsTechnologyResearched("phase_city"))
phase = "city";
else if (cmpTechnologyManager.IsTechnologyResearched("phase_town"))
phase = "town";
else if (cmpTechnologyManager.IsTechnologyResearched("phase_village"))
phase = "village";
}
// store player ally/neutral/enemy data as arrays
var allies = [];
var mutualAllies = [];
var neutrals = [];
var enemies = [];
for (var j = 0; j < n; ++j)
{
allies[j] = cmpPlayer.IsAlly(j);
mutualAllies[j] = cmpPlayer.IsMutualAlly(j);
neutrals[j] = cmpPlayer.IsNeutral(j);
enemies[j] = cmpPlayer.IsEnemy(j);
}
var playerData = {
"name": cmpPlayer.GetName(),
"civ": cmpPlayer.GetCiv(),
"color": cmpPlayer.GetColor(),
"popCount": cmpPlayer.GetPopulationCount(),
"popLimit": cmpPlayer.GetPopulationLimit(),
"popMax": cmpPlayer.GetMaxPopulation(),
"heroes": cmpPlayer.GetHeroes(),
"resourceCounts": cmpPlayer.GetResourceCounts(),
"trainingBlocked": cmpPlayer.IsTrainingBlocked(),
"state": cmpPlayer.GetState(),
"team": cmpPlayer.GetTeam(),
"teamsLocked": cmpPlayer.GetLockTeams(),
"cheatsEnabled": cmpPlayer.GetCheatsEnabled(),
"disabledTemplates": cmpPlayer.GetDisabledTemplates(),
"phase": phase,
"isAlly": allies,
"isMutualAlly": mutualAllies,
"isNeutral": neutrals,
"isEnemy": enemies,
"entityLimits": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetLimits() : null,
"entityCounts": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetCounts() : null,
"entityLimitChangers": cmpPlayerEntityLimits ? cmpPlayerEntityLimits.GetLimitChangers() : null,
"researchQueued": cmpTechnologyManager ? cmpTechnologyManager.GetQueuedResearch() : null,
"researchStarted": cmpTechnologyManager ? cmpTechnologyManager.GetStartedResearch() : null,
"researchedTechs": cmpTechnologyManager ? cmpTechnologyManager.GetResearchedTechs() : null,
"classCounts": cmpTechnologyManager ? cmpTechnologyManager.GetClassCounts() : null,
"typeCountsByClass": cmpTechnologyManager ? cmpTechnologyManager.GetTypeCountsByClass() : null
};
ret.players.push(playerData);
}
-
+
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
ret.circularMap = cmpRangeManager.GetLosCircular();
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
if (cmpTerrain)
ret.mapSize = 4 * cmpTerrain.GetTilesPerSide();
// Add timeElapsed
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
ret.timeElapsed = cmpTimer.GetTime();
-
+
+ // Add ceasefire info
+ var cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager);
+ if (cmpCeasefireManager)
+ {
+ ret.ceasefireActive = cmpCeasefireManager.IsCeasefireActive();
+ ret.ceasefireTimeRemaining = cmpCeasefireManager.GetCeasefireStartedTime() + cmpCeasefireManager.GetCeasefireTime() - ret.timeElapsed;
+ }
+
// Add the game type
var cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
ret.gameType = cmpEndGameManager.GetGameType();
// Add bartering prices
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
ret.barterPrices = cmpBarter.GetPrices();
// Add basic statistics to each player
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
if (cmpPlayerStatisticsTracker)
ret.players[i].statistics = cmpPlayerStatisticsTracker.GetBasicStatistics();
}
return ret;
};
/**
* Returns global information about the current game state, plus statistics.
* This is used by the GUI at the end of a game, in the summary screen.
* Note: Amongst statistics, the team exploration map percentage is computed from
* scratch, so the extended simulation state should not be requested too often.
*/
GuiInterface.prototype.GetExtendedSimulationState = function(player)
{
// Get basic simulation info
var ret = this.GetSimulationState();
// Add statistics to each player
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var n = cmpPlayerMan.GetNumPlayers();
for (var i = 0; i < n; ++i)
{
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
if (cmpPlayerStatisticsTracker)
ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics();
}
return ret;
};
GuiInterface.prototype.GetRenamedEntities = function(player)
{
if (this.miragedEntities[player])
return this.renamedEntities.concat(this.miragedEntities[player]);
else
return this.renamedEntities;
};
GuiInterface.prototype.ClearRenamedEntities = function(player)
{
this.renamedEntities = [];
this.miragedEntities = [];
};
GuiInterface.prototype.AddMiragedEntity = function(player, entity, mirage)
{
if (!this.miragedEntities[player])
this.miragedEntities[player] = [];
this.miragedEntities[player].push({"entity": entity, "newentity": mirage});
};
/**
* Get common entity info, often used in the gui
*/
GuiInterface.prototype.GetEntityState = function(player, ent)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
// All units must have a template; if not then it's a nonexistent entity id
var template = cmpTemplateManager.GetCurrentTemplateName(ent);
if (!template)
return null;
var ret = {
"id": ent,
"template": template,
"alertRaiser": null,
"builder": null,
"identity": null,
"fogging": null,
"foundation": null,
"garrisonHolder": null,
"gate": null,
"guard": null,
"mirage": null,
"pack": null,
"player": -1,
"position": null,
"production": null,
"rallyPoint": null,
"resourceCarrying": null,
"rotation": null,
"trader": null,
"unitAI": null,
"visibility": null,
};
var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
if (cmpMirage)
ret.mirage = true;
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (cmpIdentity)
{
ret.identity = {
"rank": cmpIdentity.GetRank(),
"classes": cmpIdentity.GetClassesList(),
"visibleClasses": cmpIdentity.GetVisibleClassesList(),
"selectionGroupName": cmpIdentity.GetSelectionGroupName()
};
}
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition && cmpPosition.IsInWorld())
{
ret.position = cmpPosition.GetPosition();
ret.rotation = cmpPosition.GetRotation();
}
var cmpHealth = QueryMiragedInterface(ent, IID_Health);
if (cmpHealth)
{
ret.hitpoints = Math.ceil(cmpHealth.GetHitpoints());
ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
ret.needsHeal = !cmpHealth.IsUnhealable();
}
var cmpCapturable = QueryMiragedInterface(ent, IID_Capturable);
if (cmpCapturable)
{
ret.capturePoints = cmpCapturable.GetCapturePoints();
ret.maxCapturePoints = cmpCapturable.GetMaxCapturePoints();
}
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
if (cmpBuilder)
ret.builder = true;
var cmpPack = Engine.QueryInterface(ent, IID_Pack);
if (cmpPack)
{
ret.pack = {
"packed": cmpPack.IsPacked(),
"progress": cmpPack.GetProgress(),
};
}
var cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
if (cmpProductionQueue)
{
ret.production = {
"entities": cmpProductionQueue.GetEntitiesList(),
"technologies": cmpProductionQueue.GetTechnologiesList(),
"queue": cmpProductionQueue.GetQueue(),
};
}
var cmpTrader = Engine.QueryInterface(ent, IID_Trader);
if (cmpTrader)
{
ret.trader = {
"goods": cmpTrader.GetGoods(),
"requiredGoods": cmpTrader.GetRequiredGoods()
};
}
var cmpFogging = Engine.QueryInterface(ent, IID_Fogging);
if (cmpFogging)
{
if (cmpFogging.IsMiraged(player))
ret.fogging = {"mirage": cmpFogging.GetMirage(player)};
else
ret.fogging = {"mirage": null};
}
var cmpFoundation = QueryMiragedInterface(ent, IID_Foundation);
if (cmpFoundation)
{
ret.foundation = {
"progress": cmpFoundation.GetBuildPercentage(),
"numBuilders": cmpFoundation.GetNumBuilders()
};
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
{
ret.player = cmpOwnership.GetOwner();
}
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (cmpRallyPoint)
{
ret.rallyPoint = {'position': cmpRallyPoint.GetPositions()[0]}; // undefined or {x,z} object
}
var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
if (cmpGarrisonHolder)
{
ret.garrisonHolder = {
"entities": cmpGarrisonHolder.GetEntities(),
"allowedClasses": cmpGarrisonHolder.GetAllowedClasses(),
"capacity": cmpGarrisonHolder.GetCapacity(),
"garrisonedEntitiesCount": cmpGarrisonHolder.GetGarrisonedEntitiesCount()
};
}
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
ret.unitAI = {
"state": cmpUnitAI.GetCurrentState(),
"orders": cmpUnitAI.GetOrders(),
"hasWorkOrders": cmpUnitAI.HasWorkOrders(),
"canGuard": cmpUnitAI.CanGuard(),
"isGuarding": cmpUnitAI.IsGuardOf(),
"possibleStances": cmpUnitAI.GetPossibleStances(),
"isIdle":cmpUnitAI.IsIdle(),
};
// Add some information needed for ungarrisoning
if (cmpUnitAI.IsGarrisoned() && ret.player !== undefined)
ret.template = "p" + ret.player + "&" + ret.template;
}
var cmpGuard = Engine.QueryInterface(ent, IID_Guard);
if (cmpGuard)
{
ret.guard = {
"entities": cmpGuard.GetEntities(),
};
}
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
if (cmpResourceGatherer)
{
ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
}
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
if (cmpGate)
{
ret.gate = {
"locked": cmpGate.IsLocked(),
};
}
var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser);
if (cmpAlertRaiser)
{
ret.alertRaiser = {
"level": cmpAlertRaiser.GetLevel(),
"canIncreaseLevel": cmpAlertRaiser.CanIncreaseLevel(),
"hasRaisedAlert": cmpAlertRaiser.HasRaisedAlert(),
};
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player);
return ret;
};
/**
* Get additionnal entity info, rarely used in the gui
*/
GuiInterface.prototype.GetExtendedEntityState = function(player, ent)
{
var ret = {
"armour": null,
"attack": null,
"barterMarket": null,
"buildingAI": null,
"healer": null,
"obstruction": null,
"turretParent":null,
"promotion": null,
"resourceDropsite": null,
"resourceGatherRates": null,
"resourceSupply": null,
};
var cmpMirage = Engine.QueryInterface(ent, IID_Mirage);
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
if (cmpAttack)
{
var types = cmpAttack.GetAttackTypes();
if (types.length)
ret.attack = {};
for (var type of types)
{
ret.attack[type] = cmpAttack.GetAttackStrengths(type);
var range = cmpAttack.GetRange(type);
ret.attack[type].minRange = range.min;
ret.attack[type].maxRange = range.max;
var timers = cmpAttack.GetTimers(type);
ret.attack[type].prepareTime = timers.prepare;
ret.attack[type].repeatTime = timers.repeat;
if (type != "Ranged")
{
// not a ranged attack, set some defaults
ret.attack[type].elevationBonus = 0;
ret.attack[type].elevationAdaptedRange = ret.attack.maxRange;
continue;
}
ret.attack[type].elevationBonus = range.elevationBonus;
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
{
// For units, take the rage in front of it, no spread. So angle = 0
ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 0);
}
else if(cmpPosition && cmpPosition.IsInWorld())
{
// For buildings, take the average elevation around it. So angle = 2*pi
ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 2*Math.PI);
}
else
{
// not in world, set a default?
ret.attack[type].elevationAdaptedRange = ret.attack.maxRange;
}
}
}
var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
if (cmpArmour)
{
ret.armour = cmpArmour.GetArmourStrengths();
}
var cmpAuras = Engine.QueryInterface(ent, IID_Auras)
if (cmpAuras)
{
ret.auras = cmpAuras.GetDescriptions();
}
var cmpBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI);
if (cmpBuildingAI)
{
ret.buildingAI = {
"defaultArrowCount": cmpBuildingAI.GetDefaultArrowCount(),
"garrisonArrowMultiplier": cmpBuildingAI.GetGarrisonArrowMultiplier(),
"garrisonArrowClasses": cmpBuildingAI.GetGarrisonArrowClasses(),
"arrowCount": cmpBuildingAI.GetArrowCount()
};
}
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (cmpObstruction)
{
ret.obstruction = {
"controlGroup": cmpObstruction.GetControlGroup(),
"controlGroup2": cmpObstruction.GetControlGroup2(),
};
}
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition && cmpPosition.GetTurretParent() != INVALID_ENTITY)
ret.turretParent = cmpPosition.GetTurretParent();
var cmpResourceSupply = QueryMiragedInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply)
{
ret.resourceSupply = {
"isInfinite": cmpResourceSupply.IsInfinite(),
"max": cmpResourceSupply.GetMaxAmount(),
"amount": cmpResourceSupply.GetCurrentAmount(),
"type": cmpResourceSupply.GetType(),
"killBeforeGather": cmpResourceSupply.GetKillBeforeGather(),
"maxGatherers": cmpResourceSupply.GetMaxGatherers(),
"numGatherers": cmpResourceSupply.GetNumGatherers()
};
}
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
if (cmpResourceGatherer)
{
ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
}
var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
if (cmpResourceDropsite)
{
ret.resourceDropsite = {
"types": cmpResourceDropsite.GetTypes()
};
}
var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
if (cmpPromotion)
{
ret.promotion = {
"curr": cmpPromotion.GetCurrentXp(),
"req": cmpPromotion.GetRequiredXp()
};
}
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("BarterMarket"))
{
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
ret.barterMarket = { "prices": cmpBarter.GetPrices() };
}
var cmpHeal = Engine.QueryInterface(ent, IID_Heal);
if (cmpHeal)
{
ret.healer = {
"unhealableClasses": cmpHeal.GetUnhealableClasses(),
"healableClasses": cmpHeal.GetHealableClasses(),
};
}
return ret;
};
GuiInterface.prototype.GetAverageRangeForBuildings = function(player, cmd)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var rot = {x:0, y:0, z:0};
var pos = {x:cmd.x,z:cmd.z};
pos.y = cmpTerrain.GetGroundLevel(cmd.x, cmd.z);
var elevationBonus = cmd.elevationBonus || 0;
var range = cmd.range;
return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, elevationBonus, 2*Math.PI);
};
GuiInterface.prototype.GetTemplateData = function(player, extendedName)
{
var name = extendedName;
// Special case for garrisoned units which have a extended template
if (extendedName.indexOf("&") != -1)
name = extendedName.slice(extendedName.indexOf("&")+1);
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(name);
if (!template)
return null;
return GetTemplateDataHelper(template, player);
};
GuiInterface.prototype.GetTechnologyData = function(player, name)
{
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
var template = cmpTechTempMan.GetTemplate(name);
if (!template)
{
warn("Tried to get data for invalid technology: " + name);
return null;
}
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return GetTechnologyDataHelper(template, cmpPlayer.GetCiv());
};
GuiInterface.prototype.IsTechnologyResearched = function(player, tech)
{
if (!tech)
return true;
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
return cmpTechnologyManager.IsTechnologyResearched(tech);
};
// Checks whether the requirements for this technology have been met
GuiInterface.prototype.CheckTechnologyRequirements = function(player, tech)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
return cmpTechnologyManager.CanResearch(tech);
};
// Returns technologies that are being actively researched, along with
// which entity is researching them and how far along the research is.
GuiInterface.prototype.GetStartedResearch = function(player)
{
var cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
if (!cmpTechnologyManager)
return false;
var ret = {};
for (var tech in cmpTechnologyManager.GetTechsStarted())
{
ret[tech] = { "researcher": cmpTechnologyManager.GetResearcher(tech) };
var cmpProductionQueue = Engine.QueryInterface(ret[tech].researcher, IID_ProductionQueue);
if (cmpProductionQueue)
ret[tech].progress = cmpProductionQueue.GetQueue()[0].progress;
else
ret[tech].progress = 0;
}
return ret;
};
// Returns the battle state of the player.
GuiInterface.prototype.GetBattleState = function(player)
{
var cmpBattleDetection = QueryPlayerIDInterface(player, IID_BattleDetection);
if (cmpBattleDetection)
return cmpBattleDetection.GetState();
else
return false;
};
// Returns a list of ongoing attacks against the player.
GuiInterface.prototype.GetIncomingAttacks = function(player)
{
var cmpAttackDetection = QueryPlayerIDInterface(player, IID_AttackDetection);
return cmpAttackDetection.GetIncomingAttacks();
};
// Used to show a red square over GUI elements you can't yet afford.
GuiInterface.prototype.GetNeededResources = function(player, amounts)
{
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return cmpPlayer.GetNeededResources(amounts);
};
/**
* Add a timed notification.
* Warning: timed notifacations are serialised
* (to also display them on saved games or after a rejoin)
* so they should allways be added and deleted in a deterministic way.
*/
GuiInterface.prototype.AddTimeNotification = function(notification, duration = 10000)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
notification.endTime = duration + cmpTimer.GetTime();
notification.id = ++this.timeNotificationID;
+
+ // Let all players receive the notification by default
+ if (notification.players == undefined)
+ {
+ var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ var numPlayers = cmpPlayerManager.GetNumPlayers();
+ notification.players = [];
+ for (var i = 1; i < numPlayers; i++)
+ notification.players.push(i);
+ }
+
this.timeNotifications.push(notification);
this.timeNotifications.sort(function (n1, n2){return n2.endTime - n1.endTime});
cmpTimer.SetTimeout(this.entity, IID_GuiInterface, "DeleteTimeNotification", duration, this.timeNotificationID);
return this.timeNotificationID;
};
GuiInterface.prototype.DeleteTimeNotification = function(notificationID)
{
this.timeNotifications = this.timeNotifications.filter(n => n.id != notificationID);
};
GuiInterface.prototype.GetTimeNotifications = function(playerID)
{
var time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime();
// filter on players and time, since the delete timer might be executed with a delay
return this.timeNotifications.filter(n => n.players.indexOf(playerID) != -1 && n.endTime > time);
};
GuiInterface.prototype.PushNotification = function(notification)
{
if (!notification.type || notification.type == "text")
this.AddTimeNotification(notification);
else
this.notifications.push(notification);
};
GuiInterface.prototype.GetNextNotification = function()
{
if (this.notifications.length)
return this.notifications.pop();
else
return false;
};
GuiInterface.prototype.GetAvailableFormations = function(player, wantedPlayer)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(wantedPlayer), IID_Player);
return cmpPlayer.GetFormations();
};
GuiInterface.prototype.GetFormationRequirements = function(player, data)
{
return GetFormationRequirements(data.formationTemplate);
};
GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data)
{
return CanMoveEntsIntoFormation(data.ents, data.formationTemplate);
};
GuiInterface.prototype.GetFormationInfoFromTemplate = function(player, data)
{
var r = {};
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(data.templateName);
if (!template || !template.Formation)
return r;
r.name = template.Formation.FormationName;
r.tooltip = template.Formation.DisabledTooltip || "";
r.icon = template.Formation.Icon;
return r;
};
GuiInterface.prototype.IsFormationSelected = function(player, data)
{
for each (var ent in data.ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
// GetLastFormationName is named in a strange way as it (also) is
// the value of the current formation (see Formation.js LoadFormation)
if (cmpUnitAI.GetLastFormationTemplate() == data.formationTemplate)
return true;
}
}
return false;
};
GuiInterface.prototype.IsStanceSelected = function(player, data)
{
for each (var ent in data.ents)
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
{
if (cmpUnitAI.GetStanceName() == data.stance)
return true;
}
}
return false;
};
GuiInterface.prototype.GetAllBuildableEntities = function(player, cmd)
{
var buildableEnts = [];
for each (var ent in cmd.entities)
{
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
if (!cmpBuilder)
continue;
for (var building of cmpBuilder.GetEntitiesList())
if (buildableEnts.indexOf(building) == -1)
buildableEnts.push(building);
}
return buildableEnts;
};
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var playerColors = {}; // cache of owner -> color map
for each (var ent in cmd.entities)
{
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
if (!cmpSelectable)
continue;
// Find the entity's owner's color:
var owner = -1;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
owner = cmpOwnership.GetOwner();
var color = playerColors[owner];
if (!color)
{
color = {"r":1, "g":1, "b":1};
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player);
if (cmpPlayer)
color = cmpPlayer.GetColor();
playerColors[owner] = color;
}
cmpSelectable.SetSelectionHighlight({"r":color.r, "g":color.g, "b":color.b, "a":cmd.alpha}, cmd.selected);
}
};
GuiInterface.prototype.GetEntitiesWithStatusBars = function()
{
return [...this.entsWithAuraAndStatusBars];
};
GuiInterface.prototype.SetStatusBars = function(player, cmd)
{
let affectedEnts = new Set();
for (let ent of cmd.entities)
{
let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
if (!cmpStatusBars)
continue;
cmpStatusBars.SetEnabled(cmd.enabled);
let cmpAuras = Engine.QueryInterface(ent, IID_Auras);
if (!cmpAuras)
continue;
for (let name of cmpAuras.GetAuraNames())
{
if (!cmpAuras.GetOverlayIcon(name))
continue;
for (let e of cmpAuras.GetAffectedEntities(name))
affectedEnts.add(e);
if (cmd.enabled)
this.entsWithAuraAndStatusBars.add(ent);
else
this.entsWithAuraAndStatusBars.delete(ent);
}
}
for (let ent of affectedEnts)
{
let cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars)
if (cmpStatusBars)
cmpStatusBars.RegenerateSprites();
}
};
GuiInterface.prototype.GetPlayerEntities = function(player)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
return cmpRangeManager.GetEntitiesByPlayer(player);
};
/**
* Displays the rally points of a given list of entities (carried in cmd.entities).
*
* The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
* be rendered, in order to support instantaneously rendering a rally point marker at a specified location
* instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
* If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
* RallyPoint component.
*/
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
{
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
// If there are some rally points already displayed, first hide them
for each (var ent in this.entsRallyPointsDisplayed)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (cmpRallyPointRenderer)
cmpRallyPointRenderer.SetDisplayed(false);
}
this.entsRallyPointsDisplayed = [];
// Show the rally points for the passed entities
for each (var ent in cmd.entities)
{
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
if (!cmpRallyPointRenderer)
continue;
// entity must have a rally point component to display a rally point marker
// (regardless of whether cmd specifies a custom location)
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
if (!cmpRallyPoint)
continue;
// Verify the owner
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position
var pos;
if (cmd.x && cmd.z)
pos = cmd;
else
pos = cmpRallyPoint.GetPositions()[0]; // may return undefined if no rally point is set
if (pos)
{
// Only update the position if we changed it (cmd.queued is set)
if ("queued" in cmd)
if (cmd.queued == true)
cmpRallyPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
else
cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
// rebuild the renderer when not set (when reading saved game or in case of building update)
else if (!cmpRallyPointRenderer.IsSet())
for each (var posi in cmpRallyPoint.GetPositions())
cmpRallyPointRenderer.AddPosition({'x': posi.x, 'y': posi.z});
cmpRallyPointRenderer.SetDisplayed(true);
// remember which entities have their rally points displayed so we can hide them again
this.entsRallyPointsDisplayed.push(ent);
}
}
};
/**
* Display the building placement preview.
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
*
* Returns result object from CheckPlacement:
* {
* "success": true iff the placement is valid, else false
* "message": message to display in UI for invalid placement, else ""
* "parameters": parameters to use in the message
* "translateMessage": localisation info
* "translateParameters": localisation info
* }
*/
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
{
var result = {
"success": false,
"message": "",
"parameters": {},
"translateMessage": false,
"translateParameters": [],
}
// See if we're changing template
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
{
// Destroy the old preview if there was one
if (this.placementEntity)
Engine.DestroyEntity(this.placementEntity[1]);
// Load the new template
if (cmd.template == "")
{
this.placementEntity = undefined;
}
else
{
this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)];
}
}
if (this.placementEntity)
{
var ent = this.placementEntity[1];
// Move the preview into the right location
var pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
{
pos.JumpTo(cmd.x, cmd.z);
pos.SetYRotation(cmd.angle);
}
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether building placement is valid
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
error("cmpBuildRestrictions not defined");
else
result = cmpBuildRestrictions.CheckPlacement();
// Set it to a red shade if this is an invalid location
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
{
if (cmd.actorSeed !== undefined)
cmpVisual.SetActorSeed(cmd.actorSeed);
if (!result.success)
cmpVisual.SetShadingColor(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColor(1, 1, 1, 1);
}
}
return result;
};
/**
* Previews the placement of a wall between cmd.start and cmd.end, or just the starting piece of a wall if cmd.end is not
* specified. Returns an object with information about the list of entities that need to be newly constructed to complete
* at least a part of the wall, or false if there are entities required to build at least part of the wall but none of
* them can be validly constructed.
*
* It's important to distinguish between three lists of entities that are at play here, because they may be subsets of one
* another depending on things like snapping and whether some of the entities inside them can be validly positioned.
* We have:
* - The list of entities that previews the wall. This list is usually equal to the entities required to construct the
* entire wall. However, if there is snapping to an incomplete tower (i.e. a foundation), it includes extra entities
* to preview the completed tower on top of its foundation.
*
* - The list of entities that need to be newly constructed to build the entire wall. This list is regardless of whether
* any of them can be validly positioned. The emphasishere here is on 'newly'; this list does not include any existing
* towers at either side of the wall that we snapped to. Or, more generally; it does not include any _entities_ that we
* snapped to; we might still snap to e.g. terrain, in which case the towers on either end will still need to be newly
* constructed.
*
* - The list of entities that need to be newly constructed to build at least a part of the wall. This list is the same
* as the one above, except that it is truncated at the first entity that cannot be validly positioned. This happens
* e.g. if the player tries to build a wall straight through an obstruction. Note that any entities that can be validly
* constructed but come after said first invalid entity are also truncated away.
*
* With this in mind, this method will return false if the second list is not empty, but the third one is. That is, if there
* were entities that are needed to build the wall, but none of them can be validly constructed. False is also returned in
* case of unexpected errors (typically missing components), and when clearing the preview by passing an empty wallset
* argument (see below). Otherwise, it will return an object with the following information:
*
* result: {
* 'startSnappedEnt': ID of the entity that we snapped to at the starting side of the wall. Currently only supports towers.
* 'endSnappedEnt': ID of the entity that we snapped to at the (possibly truncated) ending side of the wall. Note that this
* can only be set if no truncation of the second list occurs; if we snapped to an entity at the ending side
* but the wall construction was truncated before we could reach it, it won't be set here. Currently only
* supports towers.
* 'pieces': Array with the following data for each of the entities in the third list:
* [{
* 'template': Template name of the entity.
* 'x': X coordinate of the entity's position.
* 'z': Z coordinate of the entity's position.
* 'angle': Rotation around the Y axis of the entity (in radians).
* },
* ...]
* 'cost': { The total cost required for constructing all the pieces as listed above.
* 'food': ...,
* 'wood': ...,
* 'stone': ...,
* 'metal': ...,
* 'population': ...,
* 'populationBonus': ...,
* }
* }
*
* @param cmd.wallSet Object holding the set of wall piece template names. Set to an empty value to clear the preview.
* @param cmd.start Starting point of the wall segment being created.
* @param cmd.end (Optional) Ending point of the wall segment being created. If not defined, it is understood that only
* the starting point of the wall is available at this time (e.g. while the player is still in the process
* of picking a starting point), and that therefore only the first entity in the wall (a tower) should be
* previewed.
* @param cmd.snapEntities List of candidate entities to snap the start and ending positions to.
*/
GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
{
var wallSet = cmd.wallSet;
var start = {
"pos": cmd.start,
"angle": 0,
"snapped": false, // did the start position snap to anything?
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
};
var end = {
"pos": cmd.end,
"angle": 0,
"snapped": false, // did the start position snap to anything?
"snappedEnt": INVALID_ENTITY, // if we snapped, was it to an entity? if yes, holds that entity's ID
};
// --------------------------------------------------------------------------------
// do some entity cache management and check for snapping
if (!this.placementWallEntities)
this.placementWallEntities = {};
if (!wallSet)
{
// we're clearing the preview, clear the entity cache and bail
var numCleared = 0;
for (var tpl in this.placementWallEntities)
{
for each (var ent in this.placementWallEntities[tpl].entities)
Engine.DestroyEntity(ent);
this.placementWallEntities[tpl].numUsed = 0;
this.placementWallEntities[tpl].entities = [];
// keep template data around
}
return false;
}
else
{
// Move all existing cached entities outside of the world and reset their use count
for (var tpl in this.placementWallEntities)
{
for each (var ent in this.placementWallEntities[tpl].entities)
{
var pos = Engine.QueryInterface(ent, IID_Position);
if (pos)
pos.MoveOutOfWorld();
}
this.placementWallEntities[tpl].numUsed = 0;
}
// Create cache entries for templates we haven't seen before
for each (var tpl in wallSet.templates)
{
if (!(tpl in this.placementWallEntities))
{
this.placementWallEntities[tpl] = {
"numUsed": 0,
"entities": [],
"templateData": this.GetTemplateData(player, tpl),
};
// ensure that the loaded template data contains a wallPiece component
if (!this.placementWallEntities[tpl].templateData.wallPiece)
{
error("[SetWallPlacementPreview] No WallPiece component found for wall set template '" + tpl + "'");
return false;
}
}
}
}
// prevent division by zero errors further on if the start and end positions are the same
if (end.pos && (start.pos.x === end.pos.x && start.pos.z === end.pos.z))
end.pos = undefined;
// See if we need to snap the start and/or end coordinates to any of our list of snap entities. Note that, despite the list
// of snapping candidate entities, it might still snap to e.g. terrain features. Use the "ent" key in the returned snapping
// data to determine whether it snapped to an entity (if any), and to which one (see GetFoundationSnapData).
if (cmd.snapEntities)
{
var snapRadius = this.placementWallEntities[wallSet.templates.tower].templateData.wallPiece.length * 0.5; // determined through trial and error
var startSnapData = this.GetFoundationSnapData(player, {
"x": start.pos.x,
"z": start.pos.z,
"template": wallSet.templates.tower,
"snapEntities": cmd.snapEntities,
"snapRadius": snapRadius,
});
if (startSnapData)
{
start.pos.x = startSnapData.x;
start.pos.z = startSnapData.z;
start.angle = startSnapData.angle;
start.snapped = true;
if (startSnapData.ent)
start.snappedEnt = startSnapData.ent;
}
if (end.pos)
{
var endSnapData = this.GetFoundationSnapData(player, {
"x": end.pos.x,
"z": end.pos.z,
"template": wallSet.templates.tower,
"snapEntities": cmd.snapEntities,
"snapRadius": snapRadius,
});
if (endSnapData)
{
end.pos.x = endSnapData.x;
end.pos.z = endSnapData.z;
end.angle = endSnapData.angle;
end.snapped = true;
if (endSnapData.ent)
end.snappedEnt = endSnapData.ent;
}
}
}
// clear the single-building preview entity (we'll be rolling our own)
this.SetBuildingPlacementPreview(player, {"template": ""});
// --------------------------------------------------------------------------------
// calculate wall placement and position preview entities
var result = {
"pieces": [],
"cost": {"food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0},
};
var previewEntities = [];
if (end.pos)
previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js
// For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would
// otherwise be allowed by their obstruction shapes. However, during this preview phase, this is not so much of
// an issue, because all preview entities have their obstruction components deactivated, meaning that their
// obstruction shapes do not register in the simulation and hence cannot affect it. This implies that the preview
// entities cannot be found to obstruct each other, which largely solves the issue of overlap between wall pieces.
// Note that they will still be obstructed by existing shapes in the simulation (that have the BLOCK_FOUNDATION
// flag set), which is what we want. The only exception to this is when snapping to existing towers (or
// foundations thereof); the wall segments that connect up to these will be found to be obstructed by the
// existing tower/foundation, and be shaded red to indicate that they cannot be placed there. To prevent this,
// we manually set the control group of the outermost wall pieces equal to those of the snapped-to towers, so
// that they are free from mutual obstruction (per definition of obstruction control groups). This is done by
// assigning them an extra "controlGroup" field, which we'll then set during the placement loop below.
// Additionally, in the situation that we're snapping to merely a foundation of a tower instead of a fully
// constructed one, we'll need an extra preview entity for the starting tower, which also must not be obstructed
// by the foundation it snaps to.
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
{
var startEntObstruction = Engine.QueryInterface(start.snappedEnt, IID_Obstruction);
if (previewEntities.length > 0 && startEntObstruction)
previewEntities[0].controlGroups = [startEntObstruction.GetControlGroup()];
// if we're snapping to merely a foundation, add an extra preview tower and also set it to the same control group
var startEntState = this.GetEntityState(player, start.snappedEnt);
if (startEntState.foundation)
{
var cmpPosition = Engine.QueryInterface(start.snappedEnt, IID_Position);
if (cmpPosition)
{
previewEntities.unshift({
"template": wallSet.templates.tower,
"pos": start.pos,
"angle": cmpPosition.GetRotation().y,
"controlGroups": [(startEntObstruction ? startEntObstruction.GetControlGroup() : undefined)],
"excludeFromResult": true, // preview only, must not appear in the result
});
}
}
}
else
{
// Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps
// when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned
// wall piece.
// To illustrate the last point, consider what happens if we used some constant instead, say, 0. Issuing the
// build command for a wall is asynchronous, so when the preview updates after shift-clicking, the wall piece
// foundations are not registered yet in the simulation. This means they cannot possibly be picked in the list
// of candidate entities for snapping. In the next preview update, we therefore hit this case, and would rotate
// the preview to 0 radians. Then, after one or two simulation updates or so, the foundations register and
// onSimulationUpdate in session.js updates the preview again. It first grabs a new list of snapping candidates,
// which this time does include the new foundations; so we snap to the entity, and rotate the preview back to
// the foundation's angle.
// The result is a noticeable rotation to 0 and back, which is undesirable. So, for a split second there until
// the simulation updates, we fake it by reusing the last angle and hope the player doesn't notice.
previewEntities.unshift({
"template": wallSet.templates.tower,
"pos": start.pos,
"angle": (previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle)
});
}
if (end.pos)
{
// Analogous to the starting side case above
if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY)
{
var endEntObstruction = Engine.QueryInterface(end.snappedEnt, IID_Obstruction);
// Note that it's possible for the last entity in previewEntities to be the same as the first, i.e. the
// same wall piece snapping to both a starting and an ending tower. And it might be more common than you would
// expect; the allowed overlap between wall segments and towers facilitates this to some degree. To deal with
// the possibility of dual initial control groups, we use a '.controlGroups' array rather than a single
// '.controlGroup' property. Note that this array can only ever have 0, 1 or 2 elements (checked at a later time).
if (previewEntities.length > 0 && endEntObstruction)
{
previewEntities[previewEntities.length-1].controlGroups = (previewEntities[previewEntities.length-1].controlGroups || []);
previewEntities[previewEntities.length-1].controlGroups.push(endEntObstruction.GetControlGroup());
}
// if we're snapping to a foundation, add an extra preview tower and also set it to the same control group
var endEntState = this.GetEntityState(player, end.snappedEnt);
if (endEntState.foundation)
{
var cmpPosition = Engine.QueryInterface(end.snappedEnt, IID_Position);
if (cmpPosition)
{
previewEntities.push({
"template": wallSet.templates.tower,
"pos": end.pos,
"angle": cmpPosition.GetRotation().y,
"controlGroups": [(endEntObstruction ? endEntObstruction.GetControlGroup() : undefined)],
"excludeFromResult": true
});
}
}
}
else
{
previewEntities.push({
"template": wallSet.templates.tower,
"pos": end.pos,
"angle": (previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle)
});
}
}
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
if (!cmpTerrain)
{
error("[SetWallPlacementPreview] System Terrain component not found");
return false;
}
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (!cmpRangeManager)
{
error("[SetWallPlacementPreview] System RangeManager component not found");
return false;
}
// Loop through the preview entities, and construct the subset of them that need to be, and can be, validly constructed
// to build at least a part of the wall (meaning that the subset is truncated after the first entity that needs to be,
// but cannot validly be, constructed). See method-level documentation for more details.
var allPiecesValid = true;
var numRequiredPieces = 0; // number of entities that are required to build the entire wall, regardless of validity
for (var i = 0; i < previewEntities.length; ++i)
{
var entInfo = previewEntities[i];
var ent = null;
var tpl = entInfo.template;
var tplData = this.placementWallEntities[tpl].templateData;
var entPool = this.placementWallEntities[tpl];
if (entPool.numUsed >= entPool.entities.length)
{
// allocate new entity
ent = Engine.AddLocalEntity("preview|" + tpl);
entPool.entities.push(ent);
}
else
{
// reuse an existing one
ent = entPool.entities[entPool.numUsed];
}
if (!ent)
{
error("[SetWallPlacementPreview] Failed to allocate or reuse preview entity of template '" + tpl + "'");
continue;
}
// move piece to right location
// TODO: consider reusing SetBuildingPlacementReview for this, enhanced to be able to deal with multiple entities
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (cmpPosition)
{
cmpPosition.JumpTo(entInfo.pos.x, entInfo.pos.z);
cmpPosition.SetYRotation(entInfo.angle);
// if this piece is a tower, then it should have a Y position that is at least as high as its surrounding pieces
if (tpl === wallSet.templates.tower)
{
var terrainGroundPrev = null;
var terrainGroundNext = null;
if (i > 0)
terrainGroundPrev = cmpTerrain.GetGroundLevel(previewEntities[i-1].pos.x, previewEntities[i-1].pos.z);
if (i < previewEntities.length - 1)
terrainGroundNext = cmpTerrain.GetGroundLevel(previewEntities[i+1].pos.x, previewEntities[i+1].pos.z);
if (terrainGroundPrev != null || terrainGroundNext != null)
{
var targetY = Math.max(terrainGroundPrev, terrainGroundNext);
cmpPosition.SetHeightFixed(targetY);
}
}
}
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
if (!cmpObstruction)
{
error("[SetWallPlacementPreview] Preview entity of template '" + tpl + "' does not have an Obstruction component");
continue;
}
// Assign any predefined control groups. Note that there can only be 0, 1 or 2 predefined control groups; if there are
// more, we've made a programming error. The control groups are assigned from the entInfo.controlGroups array on a
// first-come first-served basis; the first value in the array is always assigned as the primary control group, and
// any second value as the secondary control group.
// By default, we reset the control groups to their standard values. Remember that we're reusing entities; if we don't
// reset them, then an ending wall segment that was e.g. at one point snapped to an existing tower, and is subsequently
// reused as a non-snapped ending wall segment, would no longer be capable of being obstructed by the same tower it was
// once snapped to.
var primaryControlGroup = ent;
var secondaryControlGroup = INVALID_ENTITY;
if (entInfo.controlGroups && entInfo.controlGroups.length > 0)
{
if (entInfo.controlGroups.length > 2)
{
error("[SetWallPlacementPreview] Encountered preview entity of template '" + tpl + "' with more than 2 initial control groups");
break;
}
primaryControlGroup = entInfo.controlGroups[0];
if (entInfo.controlGroups.length > 1)
secondaryControlGroup = entInfo.controlGroups[1];
}
cmpObstruction.SetControlGroup(primaryControlGroup);
cmpObstruction.SetControlGroup2(secondaryControlGroup);
// check whether this wall piece can be validly positioned here
var validPlacement = false;
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpOwnership.SetOwner(player);
// Check whether it's in a visible or fogged region
// TODO: should definitely reuse SetBuildingPlacementPreview, this is just straight up copy/pasta
var visible = (cmpRangeManager.GetLosVisibility(ent, player) != "hidden");
if (visible)
{
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
if (!cmpBuildRestrictions)
{
error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for preview entity of template '" + tpl + "'");
continue;
}
// TODO: Handle results of CheckPlacement
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success);
// If a wall piece has two control groups, it's likely a segment that spans
// between two existing towers. To avoid placing a duplicate wall segment,
// check for collisions with entities that share both control groups.
if (validPlacement && entInfo.controlGroups && entInfo.controlGroups.length > 1)
validPlacement = cmpObstruction.CheckDuplicateFoundation();
}
allPiecesValid = allPiecesValid && validPlacement;
// The requirement below that all pieces so far have to have valid positions, rather than only this single one,
// ensures that no more foundations will be placed after a first invalidly-positioned piece. (It is possible
// for pieces past some invalidly-positioned ones to still have valid positions, e.g. if you drag a wall
// through and past an existing building).
// Additionally, the excludeFromResult flag is set for preview entities that were manually added to be placed
// on top of foundations of incompleted towers that we snapped to; they must not be part of the result.
if (!entInfo.excludeFromResult)
numRequiredPieces++;
if (allPiecesValid && !entInfo.excludeFromResult)
{
result.pieces.push({
"template": tpl,
"x": entInfo.pos.x,
"z": entInfo.pos.z,
"angle": entInfo.angle,
});
this.placementWallLastAngle = entInfo.angle;
// grab the cost of this wall piece and add it up (note; preview entities don't have their Cost components
// copied over, so we need to fetch it from the template instead).
// TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
// boilerplate that's probably duplicated in tons of places.
result.cost.food += tplData.cost.food;
result.cost.wood += tplData.cost.wood;
result.cost.stone += tplData.cost.stone;
result.cost.metal += tplData.cost.metal;
result.cost.population += tplData.cost.population;
result.cost.populationBonus += tplData.cost.populationBonus;
result.cost.time += tplData.cost.time;
}
var canAfford = true;
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
if (cmpPlayer && cmpPlayer.GetNeededResources(result.cost))
var canAfford = false;
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (cmpVisual)
{
if (!allPiecesValid || !canAfford)
cmpVisual.SetShadingColor(1.4, 0.4, 0.4, 1);
else
cmpVisual.SetShadingColor(1, 1, 1, 1);
}
entPool.numUsed++;
}
// If any were entities required to build the wall, but none of them could be validly positioned, return failure
// (see method-level documentation).
if (numRequiredPieces > 0 && result.pieces.length == 0)
return false;
if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
result.startSnappedEnt = start.snappedEnt;
// We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed,
// i.e. are included in result.pieces (see docs for the result object).
if (end.pos && end.snappedEnt && end.snappedEnt != INVALID_ENTITY && allPiecesValid)
result.endSnappedEnt = end.snappedEnt;
return result;
};
/**
* Given the current position {data.x, data.z} of an foundation of template data.template, returns the position and angle to snap
* it to (if necessary/useful).
*
* @param data.x The X position of the foundation to snap.
* @param data.z The Z position of the foundation to snap.
* @param data.template The template to get the foundation snapping data for.
* @param data.snapEntities Optional; list of entity IDs to snap to if {data.x, data.z} is within a circle of radius data.snapRadius
* around the entity. Only takes effect when used in conjunction with data.snapRadius.
* When this option is used and the foundation is found to snap to one of the entities passed in this list
* (as opposed to e.g. snapping to terrain features), then the result will contain an additional key "ent",
* holding the ID of the entity that was snapped to.
* @param data.snapRadius Optional; when used in conjunction with data.snapEntities, indicates the circle radius around an entity that
* {data.x, data.z} must be located within to have it snap to that entity.
*/
GuiInterface.prototype.GetFoundationSnapData = function(player, data)
{
var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateMgr.GetTemplate(data.template);
if (!template)
{
warn("[GetFoundationSnapData] Failed to load template '" + data.template + "'");
return false;
}
if (data.snapEntities && data.snapRadius && data.snapRadius > 0)
{
// see if {data.x, data.z} is inside the snap radius of any of the snap entities; and if so, to which it is closest
// (TODO: break unlikely ties by choosing the lowest entity ID)
var minDist2 = -1;
var minDistEntitySnapData = null;
var radius2 = data.snapRadius * data.snapRadius;
for each (var ent in data.snapEntities)
{
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
continue;
var pos = cmpPosition.GetPosition();
var dist2 = (data.x - pos.x) * (data.x - pos.x) + (data.z - pos.z) * (data.z - pos.z);
if (dist2 > radius2)
continue;
if (minDist2 < 0 || dist2 < minDist2)
{
minDist2 = dist2;
minDistEntitySnapData = {"x": pos.x, "z": pos.z, "angle": cmpPosition.GetRotation().y, "ent": ent};
}
}
if (minDistEntitySnapData != null)
return minDistEntitySnapData;
}
if (template.BuildRestrictions.Category == "Dock")
{
var angle = GetDockAngle(template, data.x, data.z);
if (angle !== undefined)
return {"x": data.x, "z": data.z, "angle": angle};
}
return false;
};
GuiInterface.prototype.PlaySound = function(player, data)
{
// Ignore if no entity was passed
if (!data.entity)
return;
PlaySound(data.name, data.entity);
};
GuiInterface.prototype.FindIdleUnits = function(player, data)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerEntities = cmpRangeManager.GetEntitiesByPlayer(player).filter( function(e) {
var cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI);
if (!cmpUnitAI || !cmpUnitAI.IsIdle() || cmpUnitAI.IsGarrisoned())
return false;
var cmpIdentity = Engine.QueryInterface(e, IID_Identity);
if (!cmpIdentity || !cmpIdentity.HasClass(data.idleClass))
return false;
return true;
});
var idleUnits = [];
for (var j = 0; j < playerEntities.length; ++j)
{
var ent = playerEntities[j];
if (ent <= data.prevUnit|0 || data.excludeUnits.indexOf(ent) > -1)
continue;
idleUnits.push(ent);
playerEntities.splice(j--, 1);
if (data.limit && idleUnits.length >= data.limit)
break;
}
return idleUnits;
};
GuiInterface.prototype.GetTradingRouteGain = function(player, data)
{
if (!data.firstMarket || !data.secondMarket)
return null;
return CalculateTraderGain(data.firstMarket, data.secondMarket, data.template);
};
GuiInterface.prototype.GetTradingDetails = function(player, data)
{
var cmpEntityTrader = Engine.QueryInterface(data.trader, IID_Trader);
if (!cmpEntityTrader || !cmpEntityTrader.CanTrade(data.target))
return null;
var firstMarket = cmpEntityTrader.GetFirstMarket();
var secondMarket = cmpEntityTrader.GetSecondMarket();
var result = null;
if (data.target === firstMarket)
{
result = {
"type": "is first",
"hasBothMarkets": cmpEntityTrader.HasBothMarkets()
};
if (cmpEntityTrader.HasBothMarkets())
result.gain = cmpEntityTrader.GetGain();
}
else if (data.target === secondMarket)
{
result = {
"type": "is second",
"gain": cmpEntityTrader.GetGain(),
};
}
else if (!firstMarket)
{
result = {"type": "set first"};
}
else if (!secondMarket)
{
result = {
"type": "set second",
"gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
};
}
else
{
// Else both markets are not null and target is different from them
result = {"type": "set first"};
}
return result;
};
GuiInterface.prototype.CanCapture = function(player, data)
{
var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
if (!cmpAttack)
return false;
var owner = QueryOwnerInterface(data.entity).GetPlayerID();
var cmpCapturable = QueryMiragedInterface(data.target, IID_Capturable);
if (cmpCapturable && cmpCapturable.CanCapture(owner) && cmpAttack.GetAttackTypes().indexOf("Capture") != -1)
return cmpAttack.CanAttack(data.target);
return false;
};
GuiInterface.prototype.CanAttack = function(player, data)
{
var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
if (!cmpAttack)
return false;
var cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
var cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
if (!cmpEntityPlayer || !cmpTargetPlayer)
return false;
// if the owner is an enemy, it's up to the attack component to decide
- if (!cmpEntityPlayer.IsAlly(cmpTargetPlayer.GetPlayerID()))
+ if (cmpEntityPlayer.IsEnemy(cmpTargetPlayer.GetPlayerID()))
return cmpAttack.CanAttack(data.target);
return false;
};
/*
* Returns batch build time.
*/
GuiInterface.prototype.GetBatchTime = function(player, data)
{
var cmpProductionQueue = Engine.QueryInterface(data.entity, IID_ProductionQueue);
if (!cmpProductionQueue)
return 0;
return cmpProductionQueue.GetBatchTime(data.batchSize);
};
GuiInterface.prototype.IsMapRevealed = function(player)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
return cmpRangeManager.GetLosRevealAll(player);
};
GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
{
var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
cmpPathfinder.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
{
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
cmpObstructionManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
{
for each (var ent in data.entities)
{
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion)
cmpUnitMotion.SetDebugOverlay(data.enabled);
}
};
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetDebugOverlay(enabled);
};
GuiInterface.prototype.GetTraderNumber = function(player)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var traders = cmpRangeManager.GetEntitiesByPlayer(player).filter( function(e) {
return Engine.QueryInterface(e, IID_Trader);
});
var landTrader = { "total": 0, "trading": 0, "garrisoned": 0 };
var shipTrader = { "total": 0, "trading": 0 };
for each (var ent in traders)
{
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpIdentity || !cmpUnitAI)
continue;
if (cmpIdentity.HasClass("Ship"))
{
++shipTrader.total;
if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
++shipTrader.trading;
}
else
{
++landTrader.total;
if (cmpUnitAI.order && cmpUnitAI.order.type == "Trade")
++landTrader.trading;
if (cmpUnitAI.order && cmpUnitAI.order.type == "Garrison")
{
var holder = cmpUnitAI.order.data.target;
var cmpHolderUnitAI = Engine.QueryInterface(holder, IID_UnitAI);
if (cmpHolderUnitAI && cmpHolderUnitAI.order && cmpHolderUnitAI.order.type == "Trade")
++landTrader.garrisoned;
}
}
}
return { "landTrader": landTrader, "shipTrader": shipTrader };
};
GuiInterface.prototype.GetTradingGoods = function(player, tradingGoods)
{
var cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return cmpPlayer.GetTradingGoods();
};
GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
{
this.renamedEntities.push(msg);
};
// List the GuiInterface functions that can be safely called by GUI scripts.
// (GUI scripts are non-deterministic and untrusted, so these functions must be
// appropriately careful. They are called with a first argument "player", which is
// trusted and indicates the player associated with the current client; no data should
// be returned unless this player is meant to be able to see it.)
var exposedFunctions = {
"GetSimulationState": 1,
"GetExtendedSimulationState": 1,
"GetRenamedEntities": 1,
"ClearRenamedEntities": 1,
"GetEntityState": 1,
"GetExtendedEntityState": 1,
"GetAverageRangeForBuildings": 1,
"GetTemplateData": 1,
"GetTechnologyData": 1,
"IsTechnologyResearched": 1,
"CheckTechnologyRequirements": 1,
"GetStartedResearch": 1,
"GetBattleState": 1,
"GetIncomingAttacks": 1,
"GetNeededResources": 1,
"GetNextNotification": 1,
"GetTimeNotifications": 1,
"GetAvailableFormations": 1,
"GetFormationRequirements": 1,
"CanMoveEntsIntoFormation": 1,
"IsFormationSelected": 1,
"GetFormationInfoFromTemplate": 1,
"IsStanceSelected": 1,
"SetSelectionHighlight": 1,
"GetAllBuildableEntities": 1,
"SetStatusBars": 1,
"GetPlayerEntities": 1,
"DisplayRallyPoint": 1,
"SetBuildingPlacementPreview": 1,
"SetWallPlacementPreview": 1,
"GetFoundationSnapData": 1,
"PlaySound": 1,
"FindIdleUnits": 1,
"GetTradingRouteGain": 1,
"GetTradingDetails": 1,
"CanCapture": 1,
"CanAttack": 1,
"GetBatchTime": 1,
"IsMapRevealed": 1,
"SetPathfinderDebugOverlay": 1,
"SetObstructionDebugOverlay": 1,
"SetMotionDebugOverlay": 1,
"SetRangeDebugOverlay": 1,
"GetTraderNumber": 1,
"GetTradingGoods": 1,
};
GuiInterface.prototype.ScriptCall = function(player, name, args)
{
if (exposedFunctions[name])
return this[name](player, args);
else
throw new Error("Invalid GuiInterface Call name \""+name+"\"");
};
Engine.RegisterSystemComponentType(IID_GuiInterface, "GuiInterface", GuiInterface);
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/CeasefireManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/CeasefireManager.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/CeasefireManager.js (revision 16624)
@@ -0,0 +1,4 @@
+Engine.RegisterInterface("CeasefireManager");
+
+Engine.RegisterMessageType("CeasefireStarted");
+Engine.RegisterMessageType("CeasefireEnded");
\ No newline at end of file
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 16623)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 16624)
@@ -1,66 +1,70 @@
/**
* Used to initialize non-player settings relevant to the map, like
* default stance and victory conditions. DO NOT load players here
*/
function LoadMapSettings(settings)
{
// Default settings
if (!settings)
settings = {};
if (settings.DefaultStance)
{
for each (var ent in Engine.GetEntitiesWithInterface(IID_UnitAI))
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI.SwitchToStance(settings.DefaultStance);
}
}
if (settings.RevealMap)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetLosRevealAll(-1, true);
}
if (settings.DisableTreasures)
{
for (let ent of Engine.GetEntitiesWithInterface(IID_ResourceSupply))
{
let cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply.GetType().generic == "treasure")
Engine.DestroyEntity(ent);
}
}
if (settings.CircularMap)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetLosCircular(true);
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
if (cmpObstructionManager)
cmpObstructionManager.SetPassabilityCircular(true);
}
var cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
if (settings.GameType)
cmpEndGameManager.SetGameType(settings.GameType);
if (settings.Garrison)
{
for (let holder in settings.Garrison)
{
let cmpGarrisonHolder = Engine.QueryInterface(+holder, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
warn("Map error in Setup.js: entity " + holder + " can not garrison units");
else
cmpGarrisonHolder.initGarrison = settings.Garrison[holder];
}
}
+
+ var cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager);
+ if (settings.Ceasefire)
+ cmpCeasefireManager.StartCeasefire(settings.Ceasefire * 60 * 1000);
}
Engine.RegisterGlobal("LoadMapSettings", LoadMapSettings);