Index: ps/trunk/binaries/data/mods/public/gui/common/settings.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/settings.js (revision 18074)
+++ ps/trunk/binaries/data/mods/public/gui/common/settings.js (revision 18075)
@@ -1,356 +1,378 @@
/**
* The maximum number of players that the engine supports.
* TODO: Maybe we can support more than 8 players sometime.
*/
const g_MaxPlayers = 8;
/**
* The maximum number of teams allowed.
*/
const g_MaxTeams = 4;
/**
* Directory containing all editable settings.
*/
const g_SettingsDirectory = "simulation/data/settings/";
/**
* An object containing all values given by setting name.
* Used by lobby, gamesetup, session, summary screen and replay menu.
*/
const g_Settings = loadSettingsValues();
/**
* Loads and translates all values of all settings which
* can be configured by dropdowns in the gamesetup.
*
* @returns {Object|undefined}
*/
function loadSettingsValues()
{
var settings = {
"AIDescriptions": loadAIDescriptions(),
"AIDifficulties": loadAIDifficulties(),
"Ceasefire": loadCeasefire(),
+ "WonderDurations": loadWonderDuration(),
"GameSpeeds": loadSettingValuesFile("game_speeds.json"),
"MapTypes": loadMapTypes(),
"MapSizes": loadSettingValuesFile("map_sizes.json"),
"PlayerDefaults": loadPlayerDefaults(),
"PopulationCapacities": loadPopulationCapacities(),
"StartingResources": loadSettingValuesFile("starting_resources.json"),
"VictoryConditions": loadVictoryConditions()
};
if (Object.keys(settings).some(key => settings[key] === undefined))
return undefined;
return settings;
}
/**
* Returns an array of objects reflecting all possible values for a given setting.
*
* @param {string} filename
* @see simulation/data/settings/
* @returns {Array|undefined}
*/
function loadSettingValuesFile(filename)
{
var json = Engine.ReadJSONFile(g_SettingsDirectory + filename);
if (!json || !json.Data)
{
error("Could not load " + filename + "!");
return undefined;
}
if (json.TranslatedKeys)
{
let keyContext = json.TranslatedKeys;
if (json.TranslationContext)
{
keyContext = {};
for (let key of json.TranslatedKeys)
keyContext[key] = json.TranslationContext;
}
translateObjectKeys(json.Data, keyContext);
}
return json.Data;
}
/**
* Loads the descriptions as defined in simulation/ai/.../data.json and loaded by ICmpAIManager.cpp.
*
* @returns {Array}
*/
function loadAIDescriptions()
{
var ais = Engine.GetAIs();
translateObjectKeys(ais, ["name", "description"]);
return ais.sort((a, b) => a.data.name.localeCompare(b.data.name));
}
/**
* Hardcoded, as modding is not supported without major changes.
* Notice the AI code parses the difficulty level by the index, not by name.
*
* @returns {Array}
*/
function loadAIDifficulties()
{
return [
{
"Name": "sandbox",
"Title": translateWithContext("aiDiff", "Sandbox")
},
{
"Name": "very easy",
"Title": translateWithContext("aiDiff", "Very Easy")
},
{
"Name": "easy",
"Title": translateWithContext("aiDiff", "Easy")
},
{
"Name": "medium",
"Title": translateWithContext("aiDiff", "Medium"),
"Default": true
},
{
"Name": "hard",
"Title": translateWithContext("aiDiff", "Hard")
},
{
"Name": "very hard",
"Title": translateWithContext("aiDiff", "Very Hard")
}
];
}
/**
+ * Loads available wonder-victory times
+ */
+function loadWonderDuration()
+{
+ var jsonFile = "wonder_times.json";
+ var json = Engine.ReadJSONFile(g_SettingsDirectory + jsonFile);
+
+ if (!json || json.Default === undefined || !json.Times || !Array.isArray(json.Times))
+ {
+ error("Could not load " + jsonFile);
+ return undefined;
+ }
+
+ return json.Times.map(duration => ({
+ "Duration": duration,
+ "Default": duration == json.Default,
+ "Title": sprintf(translatePluralWithContext("wonder victory", "%(min)s minute", "%(min)s minutes", duration), { "min": duration })
+ }));
+}
+
+/**
* Loads available ceasefire settings.
*
* @returns {Array|undefined}
*/
function loadCeasefire()
{
var json = Engine.ReadJSONFile(g_SettingsDirectory + "ceasefire.json");
if (!json || json.Default === undefined || !json.Times || !Array.isArray(json.Times))
{
error("Could not load ceasefire.json");
return undefined;
}
return json.Times.map(timeout => ({
"Duration": timeout,
"Default": timeout == json.Default,
"Title": timeout == 0 ? translateWithContext("ceasefire", "No ceasefire") :
sprintf(translatePluralWithContext("ceasefire", "%(minutes)s minute", "%(minutes)s minutes", timeout), { "minutes": timeout })
}));
}
/**
* Hardcoded, as modding is not supported without major changes.
*
* @returns {Array}
*/
function loadMapTypes()
{
return [
{
"Name": "skirmish",
"Title": translateWithContext("map", "Skirmish"),
"Default": true
},
{
"Name": "random",
"Title": translateWithContext("map", "Random")
},
{
"Name": "scenario",
"Title": translateWithContext("map", "Scenario")
}
];
}
/**
* Loads available gametypes.
*
* @returns {Array|undefined}
*/
function loadVictoryConditions()
{
const subdir = "victory_conditions/";
const files = Engine.BuildDirEntList(g_SettingsDirectory + subdir, "*.json", false).map(
file => file.substr(g_SettingsDirectory.length));
var victoryConditions = files.map(file => {
let vc = loadSettingValuesFile(file);
if (vc)
vc.Name = file.substr(subdir.length, file.length - (subdir + ".json").length);
return vc;
});
if (victoryConditions.some(vc => vc == undefined))
return undefined;
// TODO: We might support enabling victory conditions separately sometime.
// Until then, we supplement the endless gametype here.
victoryConditions.push({
"Name": "endless",
"Title": translate("None"),
"Description": translate("Endless Game"),
"Scripts": []
});
return victoryConditions;
}
/**
* Loads the default player settings (like civs and colors).
*
* @returns {Array|undefined}
*/
function loadPlayerDefaults()
{
var json = Engine.ReadJSONFile(g_SettingsDirectory + "player_defaults.json");
if (!json || !json.PlayerData)
{
error("Could not load player_defaults.json");
return undefined;
}
return json.PlayerData;
}
/**
* Loads available population capacities.
*
* @returns {Array|undefined}
*/
function loadPopulationCapacities()
{
var json = Engine.ReadJSONFile(g_SettingsDirectory + "population_capacities.json");
if (!json || json.Default === undefined || !json.PopulationCapacities || !Array.isArray(json.PopulationCapacities))
{
error("Could not load population_capacities.json");
return undefined;
}
return json.PopulationCapacities.map(population => ({
"Population": population,
"Default": population == json.Default,
"Title": population < 10000 ? population : translate("Unlimited")
}));
}
/**
* Creates an object with all values of that property of the given setting and
* finds the index of the default value.
*
* This allows easy copying of setting values to dropdown lists.
*
* @param {Array} settingValues
* @returns {Object|undefined}
*/
function prepareForDropdown(settingValues)
{
if (!settingValues)
return undefined;
var settings = { "Default": 0 };
for (let index in settingValues)
{
for (let property in settingValues[index])
{
if (property == "Default")
continue;
if (!settings[property])
settings[property] = [];
// Switch property and index
settings[property][index] = settingValues[index][property];
}
// Copy default value
if (settingValues[index].Default)
settings.Default = +index;
}
return settings;
}
/**
* Returns title or placeholder.
*
* @param {string} aiName - for example "petra"
*/
function translateAIName(aiName)
{
var description = g_Settings.AIDescriptions.find(ai => ai.id == aiName);
return description ? translate(description.data.name) : translate("Unknown");
}
/**
* Returns title or placeholder.
*
* @param {Number} index - index of AIDifficulties
*/
function translateAIDifficulty(index)
{
var difficulty = g_Settings.AIDifficulties[index];
return difficulty ? difficulty.Title : translate("Unknown");
}
/**
* Returns title or placeholder.
*
* @param {string} mapType - for example "skirmish"
* @returns {string}
*/
function translateMapType(mapType)
{
var type = g_Settings.MapTypes.find(t => t.Name == mapType);
return type ? type.Title : translate("Unknown");
}
/**
* Returns title or placeholder "Default".
*
* @param {Number} mapSize - tilecount
* @returns {string}
*/
function translateMapSize(tiles)
{
var mapSize = g_Settings.MapSizes.find(mapSize => mapSize.Tiles == +tiles);
return mapSize ? mapSize.Name : translateWithContext("map size", "Default");
}
/**
* Returns title or placeholder.
*
* @param {Number} population - for example 300
* @returns {string}
*/
function translatePopulationCapacity(population)
{
var popCap = g_Settings.PopulationCapacities.find(p => p.Population == population);
return popCap ? popCap.Title : translate("Unknown");
}
/**
* Returns title or placeholder.
*
* @param {string} gameType - for example "conquest"
* @returns {string}
*/
function translateVictoryCondition(gameType)
{
var vc = g_Settings.VictoryConditions.find(vc => vc.Name == gameType);
return vc ? vc.Title : translate("Unknown");
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 18074)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 18075)
@@ -1,1797 +1,1868 @@
const g_MatchSettings_SP = "config/matchsettings.json";
const g_MatchSettings_MP = "config/matchsettings.mp.json";
const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire);
const g_GameSpeeds = prepareForDropdown(g_Settings && g_Settings.GameSpeeds.filter(speed => !speed.ReplayOnly));
const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities);
const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources);
const g_VictoryConditions = prepareForDropdown(g_Settings && g_Settings.VictoryConditions);
+const g_WonderDurations = prepareForDropdown(g_Settings && g_Settings.WonderDurations);
/**
* All selectable playercolors except gaia.
*/
const g_PlayerColors = g_Settings ? g_Settings.PlayerDefaults.slice(1).map(pData => pData.Color) : undefined;
/**
* Directory containing all maps of the given type.
*/
const g_MapPath = {
"random": "maps/random/",
"scenario": "maps/scenarios/",
"skirmish": "maps/skirmishes/"
};
/**
* Processes a CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
*/
const g_NetMessageTypes = {
"netstatus": msg => handleNetStatusMessage(msg),
"netwarn": msg => addNetworkWarning(msg),
"gamesetup": msg => handleGamesetupMessage(msg),
"players": msg => handlePlayerAssignmentMessage(msg),
"ready": msg => handleReadyMessage(msg),
"start": msg => handleGamestartMessage(msg),
"kicked": msg => addChatMessage({ "type": "kicked", "username": msg.username }),
"banned": msg => addChatMessage({ "type": "banned", "username": msg.username }),
"chat": msg => addChatMessage({ "type": "chat", "guid": msg.guid, "text": msg.text })
};
const g_FormatChatMessage = {
"system": (msg, user) => systemMessage(msg.text),
"settings": (msg, user) => systemMessage(translate('Game settings have been changed')),
"connect": (msg, user) => systemMessage(sprintf(translate("%(username)s has joined"), { "username": user })),
"disconnect": (msg, user) => systemMessage(sprintf(translate("%(username)s has left"), { "username": user })),
"kicked": (msg, user) => systemMessage(sprintf(translate("%(username)s has been kicked"), { "username": user })),
"banned": (msg, user) => systemMessage(sprintf(translate("%(username)s has been banned"), { "username": user })),
"chat": (msg, user) => sprintf(translate("%(username)s %(message)s"), {
"username": senderFont(sprintf(translate("<%(username)s>"), { "username": user })),
"message": escapeText(msg.text || "")
}),
"ready": (msg, user) => sprintf(translate("* %(username)s is ready!"), {
"username": user
}),
"not-ready": (msg, user) => sprintf(translate("* %(username)s is not ready."), {
"username": user
}),
"clientlist": (msg, user) => getUsernameList()
};
/**
* The dropdownlist items will appear in the order they are added.
*/
const g_MapFilters = [
{
"id": "default",
"name": translate("Default"),
"filter": mapKeywords => mapKeywords.every(keyword => ["naval", "demo", "hidden"].indexOf(keyword) == -1)
},
{
"id": "naval",
"name": translate("Naval Maps"),
"filter": mapKeywords => mapKeywords.indexOf("naval") != -1
},
{
"id": "demo",
"name": translate("Demo Maps"),
"filter": mapKeywords => mapKeywords.indexOf("demo") != -1
},
{
"id": "all",
"name": translate("All Maps"),
"filter": mapKeywords => true
}
];
/**
* Used for generating the botnames.
*/
const g_RomanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
/**
* Offer users to select playable civs only.
* Load unselectable civs as they could appear in scenario maps.
*/
const g_CivData = loadCivData();
/**
* Used for highlighting the sender of chat messages.
*/
const g_SenderFont = "sans-bold-13";
/**
* Highlight the "random" dropdownlist item.
*/
const g_ColorRandom = "orange";
/**
* Highlight AIs in the player-dropdownlist.
*/
const g_AIColor = "70 150 70";
/**
* Color for "Unassigned"-placeholder item in the dropdownlist.
*/
const g_UnassignedColor = "140 140 140";
/**
* Highlight observer players in the dropdownlist.
*/
const g_UnassignedPlayerColor = "170 170 250";
/**
* Highlight ready players.
*/
const g_ReadyColor = "green";
/**
* Highlights the victory condition in the game-description.
*/
const g_VictoryColor = "orange";
/**
* Placeholder item for the map-dropdownlist.
*/
const g_RandomMap = '[color="' + g_ColorRandom + '"]' + translateWithContext("map type", "Random") + "[/color]";
/**
* Placeholder item for the civ-dropdownlists.
*/
const g_RandomCiv = '[color="' + g_ColorRandom + '"]' + translateWithContext("civilization", "Random") + '[/color]';
// 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;
/**
* Used to prevent calling resetReadyData when starting a game.
*/
var g_GameStarted = false;
var g_PlayerAssignments = {};
var g_DefaultPlayerData = [];
var g_GameAttributes = { "settings": {} };
var g_ChatMessages = [];
/**
* Cache containing the mapsettings for scenario/skirmish maps. Just-in-time loading.
*/
var g_MapData = {};
// 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
/**
* Only send a lobby update if something actually changed.
*/
var g_LastGameStanza;
/**
* Initializes some globals without touching the GUI.
*
* @param {Object} attribs - context data sent by the lobby / mainmenu
*/
function init(attribs)
{
if (!g_Settings)
{
cancelSetup();
return;
}
if (["offline", "server", "client"].indexOf(attribs.type) == -1)
{
error("Unexpected 'type' in gamesetup init: " + attribs.type);
cancelSetup();
return;
}
g_IsNetworked = attribs.type != "offline";
g_IsController = attribs.type != "client";
g_ServerName = attribs.serverName || undefined;
// Get default player data - remove gaia
g_DefaultPlayerData = g_Settings.PlayerDefaults;
g_DefaultPlayerData.shift();
for (let i in g_DefaultPlayerData)
g_DefaultPlayerData[i].Civ = "random";
setTimeout(displayGamestateNotifications, 1000);
}
/**
* Called after the first tick.
*/
function initGUIObjects()
{
Engine.GetGUIObjectByName("cancelGame").tooltip = Engine.HasXmppClient() ? translate("Return to the lobby.") : translate("Return to the main menu.");
initCivNameList();
initMapTypes();
initMapFilters();
if (g_IsController)
{
// Unique ID to be used for identifying the same game reports for the lobby
g_GameAttributes.matchID = Engine.GetMatchID();
g_GameAttributes.settings.CheatsEnabled = !g_IsNetworked;
g_GameAttributes.settings.RatingEnabled = Engine.IsRankedGame() || undefined;
initMapNameList();
initNumberOfPlayers();
initGameSpeed();
initPopulationCaps();
initStartingResources();
initCeasefire();
+ initWonderDurations();
initVictoryConditions();
initMapSizes();
initRadioButtons();
}
else
hideControls();
initMultiplayerSettings();
initPlayerAssignments();
resizeMoreOptionsWindow();
if (g_IsNetworked)
Engine.GetGUIObjectByName("chatInput").focus();
if (g_IsController)
{
loadPersistMatchSettings();
if (g_IsInGuiUpdate)
warn("initGUIObjects() called while in GUI update");
updateGameAttributes();
}
}
function initMapTypes()
{
let mapTypes = Engine.GetGUIObjectByName("mapTypeSelection");
mapTypes.list = g_MapTypes.Title;
mapTypes.list_data = g_MapTypes.Name;
mapTypes.onSelectionChange = function() {
if (this.selected != -1)
selectMapType(this.list_data[this.selected]);
};
if (g_IsController)
mapTypes.selected = g_MapTypes.Default;
}
function initMapFilters()
{
let mapFilters = Engine.GetGUIObjectByName("mapFilterSelection");
mapFilters.list = g_MapFilters.map(mapFilter => mapFilter.name);
mapFilters.list_data = g_MapFilters.map(mapFilter => mapFilter.id);
mapFilters.onSelectionChange = function() {
if (this.selected != -1)
selectMapFilter(this.list_data[this.selected]);
};
if (g_IsController)
mapFilters.selected = 0;
g_GameAttributes.mapFilter = "default";
}
/**
- * Sets the size of the more-options dialog.
+ * Remove empty space in case of hidden options (like cheats, rating or wonder duration)
*/
function resizeMoreOptionsWindow()
{
- // For singleplayer reduce the size of more options dialog by two options (cheats, rated game = 60px)
- if (!g_IsNetworked)
- {
- Engine.GetGUIObjectByName("moreOptions").size = "50%-200 50%-195 50%+200 50%+160";
- Engine.GetGUIObjectByName("hideMoreOptions").size = "50%-70 310 50%+70 336";
- }
- // For non-lobby multiplayergames reduce the size of the dialog by one option (rated game, 30px)
- else if (!Engine.HasXmppClient())
- {
- Engine.GetGUIObjectByName("moreOptions").size = "50%-200 50%-195 50%+200 50%+190";
- Engine.GetGUIObjectByName("hideMoreOptions").size = "50%-70 340 50%+70 366";
+ const elementHeight = 30;
+ const elements = [
+ "optionGameSpeed",
+ "optionVictoryCondition",
+ "optionWonderDuration",
+ "optionPopulationCap",
+ "optionStartingResources",
+ "optionCeasefire",
+ "optionRevealMap",
+ "optionExploreMap",
+ "optionDisableTreasures",
+ "optionLockTeams",
+ "optionCheats",
+ "optionRating",
+ "hideMoreOptions"
+ ];
+
+ let yPos = undefined;
+ for (let element of elements)
+ {
+ let guiOption = Engine.GetGUIObjectByName(element);
+ let gSize = guiOption.size;
+ yPos = yPos || gSize.top;
+
+ if (guiOption.hidden)
+ continue;
+
+ gSize.top = yPos;
+ gSize.bottom = yPos + elementHeight - 2;
+ guiOption.size = gSize;
+
+ yPos += elementHeight;
}
+
+ // Resize the vertically centered window containing the options
+ let moreOptions = Engine.GetGUIObjectByName("moreOptions");
+ let mSize = moreOptions.size;
+ mSize.bottom = mSize.top + yPos + 20;
+ moreOptions.size = mSize;
}
function initNumberOfPlayers()
{
let playersArray = Array(g_MaxPlayers).fill(0).map((v, i) => i + 1); // 1, 2, ..., MaxPlayers
let numPlayersSelection = Engine.GetGUIObjectByName("numPlayersSelection");
numPlayersSelection.list = playersArray;
numPlayersSelection.list_data = playersArray;
numPlayersSelection.onSelectionChange = function() {
if (this.selected != -1)
selectNumPlayers(this.list_data[this.selected]);
};
numPlayersSelection.selected = g_MaxPlayers - 1;
}
function initGameSpeed()
{
let gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
gameSpeed.hidden = false;
Engine.GetGUIObjectByName("gameSpeedText").hidden = true;
gameSpeed.list = g_GameSpeeds.Title;
gameSpeed.list_data = g_GameSpeeds.Speed;
gameSpeed.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.gameSpeed = g_GameSpeeds.Speed[this.selected];
updateGameAttributes();
};
gameSpeed.selected = g_GameSpeeds.Default;
}
function initPopulationCaps()
{
let populationCaps = Engine.GetGUIObjectByName("populationCap");
populationCaps.list = g_PopulationCapacities.Title;
populationCaps.list_data = g_PopulationCapacities.Population;
populationCaps.selected = g_PopulationCapacities.Default;
populationCaps.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.settings.PopulationCap = g_PopulationCapacities.Population[this.selected];
updateGameAttributes();
};
}
function initStartingResources()
{
let startingResourcesL = Engine.GetGUIObjectByName("startingResources");
startingResourcesL.list = g_StartingResources.Title;
startingResourcesL.list_data = g_StartingResources.Resources;
startingResourcesL.selected = g_StartingResources.Default;
startingResourcesL.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.settings.StartingResources = g_StartingResources.Resources[this.selected];
updateGameAttributes();
};
}
function initCeasefire()
{
let ceasefireL = Engine.GetGUIObjectByName("ceasefire");
ceasefireL.list = g_Ceasefire.Title;
ceasefireL.list_data = g_Ceasefire.Duration;
ceasefireL.selected = g_Ceasefire.Default;
ceasefireL.onSelectionChange = function() {
if (this.selected != -1)
g_GameAttributes.settings.Ceasefire = g_Ceasefire.Duration[this.selected];
updateGameAttributes();
};
}
function initVictoryConditions()
{
let victoryConditions = Engine.GetGUIObjectByName("victoryCondition");
victoryConditions.list = g_VictoryConditions.Title;
victoryConditions.list_data = g_VictoryConditions.Name;
victoryConditions.onSelectionChange = function() {
if (this.selected != -1)
{
g_GameAttributes.settings.GameType = g_VictoryConditions.Name[this.selected];
g_GameAttributes.settings.VictoryScripts = g_VictoryConditions.Scripts[this.selected];
}
updateGameAttributes();
};
victoryConditions.selected = g_VictoryConditions.Default;
}
+function initWonderDurations()
+{
+ let wonderConditions = Engine.GetGUIObjectByName("wonderDuration");
+ wonderConditions.list = g_WonderDurations.Title;
+ wonderConditions.list_data = g_WonderDurations.Duration;
+ wonderConditions.onSelectionChange = function()
+ {
+ if (this.selected != -1)
+ g_GameAttributes.settings.WonderDuration = g_WonderDurations.Duration[this.selected];
+
+ updateGameAttributes();
+ };
+ wonderConditions.selected = g_WonderDurations.Default;
+}
+
function initMapSizes()
{
let mapSize = Engine.GetGUIObjectByName("mapSize");
mapSize.list = g_MapSizes.LongName;
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;
}
/**
* Assign update-functions to all checkboxes.
*/
function initRadioButtons()
{
let options = {
"RevealMap": "revealMap",
"ExploreMap": "exploreMap",
"DisableTreasures": "disableTreasures",
"LockTeams": "lockTeams",
"CheatsEnabled": "enableCheats"
};
Object.keys(options).forEach(attribute => {
Engine.GetGUIObjectByName(options[attribute]).onPress = function() {
g_GameAttributes.settings[attribute] = 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();
};
}
/**
* If we're a network client, hide the controls and show the text instead.
*/
function hideControls()
{
hideControl("mapTypeSelection", "mapTypeText");
hideControl("mapFilterSelection", "mapFilterText");
hideControl("mapSelection", "mapSelectionText");
hideControl("victoryCondition", "victoryConditionText");
hideControl("gameSpeed", "gameSpeedText");
hideControl("numPlayersSelection", "numPlayersText");
// TODO: Shouldn't players be able to choose their own assignment?
for (let i = 0; i < g_MaxPlayers; ++i)
{
Engine.GetGUIObjectByName("playerAssignment["+i+"]").hidden = true;
Engine.GetGUIObjectByName("playerCiv["+i+"]").hidden = true;
Engine.GetGUIObjectByName("playerTeam["+i+"]").hidden = true;
}
Engine.GetGUIObjectByName("startGame").enabled = true;
}
/**
* Hides the GUI controls for clients and shows the read-only label instead.
*
* @param {string} control - name of the GUI object able to change a setting
* @param {string} label - name of the GUI object displaying a setting
* @param {boolean} [allowControl] - Whether the current user is allowed to change the control.
*/
function hideControl(control, label, allowControl = g_IsController)
{
Engine.GetGUIObjectByName(control).hidden = !allowControl;
Engine.GetGUIObjectByName(label).hidden = allowControl;
}
/**
* Checks a boolean checkbox for the host and sets the text of the label for the client.
*
* @param {string} control - name of the GUI object able to change a setting
* @param {string} label - name of the GUI object displaying a setting
* @param {boolean} checked - Whether the setting is active / enabled.
*/
function setGUIBoolean(control, label, checked)
{
Engine.GetGUIObjectByName(control).checked = checked;
Engine.GetGUIObjectByName(label).caption = checked ? translate("Yes") : translate("No");
}
/**
* Hide and set some elements depending on whether we play single- or multiplayer.
*/
function initMultiplayerSettings()
{
Engine.GetGUIObjectByName("chatPanel").hidden = !g_IsNetworked;
Engine.GetGUIObjectByName("optionCheats").hidden = !g_IsNetworked;
Engine.GetGUIObjectByName("optionRating").hidden = !Engine.HasXmppClient();
Engine.GetGUIObjectByName("enableCheats").enabled = !Engine.IsRankedGame();
Engine.GetGUIObjectByName("lockTeams").enabled = !Engine.IsRankedGame();
Engine.GetGUIObjectByName("enableCheats").checked = g_GameAttributes.settings.CheatsEnabled;
Engine.GetGUIObjectByName("enableRating").checked = !!g_GameAttributes.settings.RatingEnabled;
hideControl("enableCheats", "enableCheatsText");
hideControl("enableRating", "enableRatingText");
}
/**
* Populate team-, color- and civ-dropdowns.
*/
function initPlayerAssignments()
{
let boxSpacing = 32;
for (let i = 0; i < g_MaxPlayers; ++i)
{
let box = Engine.GetGUIObjectByName("playerBox["+i+"]");
let boxSize = box.size;
let h = boxSize.bottom - boxSize.top;
boxSize.top = i * boxSpacing;
boxSize.bottom = i * boxSpacing + h;
box.size = boxSize;
let team = Engine.GetGUIObjectByName("playerTeam["+i+"]");
let teamsArray = Array(g_MaxTeams).fill(0).map((v, i) => i + 1); // 1, 2, ... MaxTeams
team.list = [translateWithContext("team", "None")].concat(teamsArray); // "None", 1, 2, ..., maxTeams
team.list_data = [-1].concat(teamsArray.map(team => team - 1)); // -1, 0, ..., (maxTeams-1)
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();
};
let colorPicker = Engine.GetGUIObjectByName("playerColorPicker["+i+"]");
colorPicker.list = g_PlayerColors.map(color => '[color="' + color.r + ' ' + color.g + ' ' + color.b + '"] ■[/color]');
colorPicker.list_data = g_PlayerColors.map((color, index) => index);
colorPicker.selected = -1;
colorPicker.onSelectionChange = function() { selectPlayerColor(playerSlot, this.selected); };
Engine.GetGUIObjectByName("playerCiv["+i+"]").onSelectionChange = function() {
if ((this.selected != -1)&&(g_GameAttributes.mapType !== "scenario"))
g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected];
updateGameAttributes();
};
}
}
/**
* Called when the client disconnects.
* The other cases from NetClient should never occur in the gamesetup.
* @param {Object} message
*/
function handleNetStatusMessage(message)
{
if (message.status != "disconnected")
{
error("Unrecognised netstatus type " + message.status);
return;
}
cancelSetup();
reportDisconnect(message.reason);
}
/**
* Called whenever a client clicks on ready (or not ready).
* @param {Object} message
*/
function handleReadyMessage(message)
{
g_ReadyChanged -= 1;
if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1)
addChatMessage({
"type": message.status == 1 ? "ready" : "not-ready",
"guid": message.guid
});
if (!g_IsController)
return;
g_PlayerAssignments[message.guid].status = +message.status == 1;
Engine.SetNetworkPlayerStatus(message.guid, +message.status);
updateReadyUI();
}
/**
* Called after every player is ready and the host decided to finally start the game.
* @param {Object} message
*/
function handleGamestartMessage(message)
{
if (g_IsController && Engine.HasXmppClient())
{
let playerNames = Object.keys(g_PlayerAssignments).map(guid => g_PlayerAssignments[guid].name);
Engine.SendChangeStateGame(playerNames.length, playerNames.join(", "));
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments,
"isController": g_IsController
});
}
/**
* Called whenever the host changed any setting.
* @param {Object} message
*/
function handleGamesetupMessage(message)
{
if (!message.data)
return;
g_GameAttributes = message.data;
if (!!g_GameAttributes.settings.RatingEnabled)
{
g_GameAttributes.settings.CheatsEnabled = false;
g_GameAttributes.settings.LockTeams = true;
}
Engine.SetRankedGame(!!g_GameAttributes.settings.RatingEnabled);
updateGUIObjects();
}
/**
* Called whenever a client joins/leaves or any gamesetting is changed.
* @param {Object} message
*/
function handlePlayerAssignmentMessage(message)
{
let resetReady = false;
let newGUID = "";
// Report joinings
for (let guid in message.hosts)
{
if (g_PlayerAssignments[guid])
continue;
addChatMessage({ "type": "connect", "guid": guid, "username": message.hosts[guid].name });
newGUID = guid;
// Assign the new player
if (!g_IsController || message.hosts[guid].player != -1)
continue;
let freeSlot = g_GameAttributes.settings.PlayerData.findIndex((v,i) =>
Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player != i+1)
);
if (freeSlot != -1)
Engine.AssignNetworkPlayer(freeSlot+1, guid);
}
// Report leavings
for (let guid in g_PlayerAssignments)
{
if (message.hosts[guid])
continue;
addChatMessage({ "type": "disconnect", "guid": guid });
if (g_PlayerAssignments[guid].player != -1)
resetReady = true; // Observers shouldn't reset ready.
}
g_PlayerAssignments = message.hosts;
updatePlayerList();
if (g_PlayerAssignments[newGUID] && g_PlayerAssignments[newGUID].player != -1)
resetReady = true;
if (resetReady)
resetReadyData();
updateReadyUI();
sendRegisterGameStanza();
}
function getMapDisplayName(map)
{
let 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)
{
let 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];
if (defaults && (property in defaults))
return defaults[property];
return undefined;
}
/**
* Initialize the dropdowns containing all selectable civs (including random).
*/
function initCivNameList()
{
let civList = Object.keys(g_CivData).filter(civ => g_CivData[civ].SelectableInGameSetup).map(civ => ({ "name": g_CivData[civ].Name, "code": civ })).sort(sortNameIgnoreCase);
let civListNames = [g_RandomCiv].concat(civList.map(civ => civ.name));
let civListCodes = ["random"].concat(civList.map(civ => civ.code));
for (let i = 0; i < g_MaxPlayers; ++i)
{
let civ = Engine.GetGUIObjectByName("playerCiv["+i+"]");
civ.list = civListNames;
civ.list_data = civListCodes;
civ.selected = 0;
}
}
/**
* Initialize the dropdown containing all maps for the selected maptype and mapfilter.
*/
function initMapNameList()
{
if (!g_MapPath[g_GameAttributes.mapType])
{
error("initMapNameList: Unexpected map type " + g_GameAttributes.mapType);
return;
}
let mapFiles = g_GameAttributes.mapType == "random" ? getJSONFileList(g_GameAttributes.mapPath) : getXMLFileList(g_GameAttributes.mapPath);
// Apply map filter, if any defined
// TODO: Should verify these are valid maps before adding to list
let mapList = [];
for (let mapFile of mapFiles)
{
let file = g_GameAttributes.mapPath + mapFile;
let mapData = loadMapData(file);
let mapFilter = g_MapFilters.find(mapFilter => mapFilter.id == (g_GameAttributes.mapFilter || "all"));
if (!!mapData.settings && mapFilter && mapFilter.filter(mapData.settings.Keywords || []))
mapList.push({ "name": getMapDisplayName(file), "file": file });
}
translateObjectKeys(mapList, ["name"]);
mapList.sort(sortNameIgnoreCase);
let mapListNames = mapList.map(map => map.name);
let mapListFiles = mapList.map(map => map.file);
// Scenario/skirmish maps have a fixed playercount
if (g_GameAttributes.mapType == "random")
{
mapListNames.unshift(g_RandomMap);
mapListFiles.unshift("random");
}
let mapSelectionBox = Engine.GetGUIObjectByName("mapSelection");
mapSelectionBox.list = mapListNames;
mapSelectionBox.list_data = mapListFiles;
mapSelectionBox.onSelectionChange = function() {
if (this.selected != -1)
selectMap(this.list_data[this.selected]);
};
mapSelectionBox.selected = Math.max(0, mapListFiles.indexOf(g_GameAttributes.map || ""));
}
function loadMapData(name)
{
if (!name || !g_MapPath[g_GameAttributes.mapType])
return undefined;
if (name == "random")
return { "settings": { "Name": "", "Description": "" } };
if (!g_MapData[name])
g_MapData[name] = g_GameAttributes.mapType == "random" ? Engine.ReadJSONFile(name + ".json") : Engine.LoadMapSettings(name);
return g_MapData[name];
}
/**
* Sets the gameattributes the way they were the last time the user left the gamesetup.
*/
function loadPersistMatchSettings()
{
if (Engine.ConfigDB_GetValue("user", "persistmatchsettings") != "true")
return;
let settingsFile = g_IsNetworked ? g_MatchSettings_MP : g_MatchSettings_SP;
if (!Engine.FileExists(settingsFile))
return;
let attrs = Engine.ReadJSONFile(settingsFile);
if (!attrs || !attrs.settings)
return;
g_IsInGuiUpdate = true;
let mapName = attrs.map || "";
let mapSettings = attrs.settings;
g_GameAttributes = attrs;
g_GameAttributes.matchID = Engine.GetMatchID();
mapSettings.Seed = Math.floor(Math.random() * 65536);
mapSettings.AISeed = Math.floor(Math.random() * 65536);
if (!g_IsNetworked)
mapSettings.CheatsEnabled = true;
// Replace unselectable civs with random civ
let playerData = mapSettings.PlayerData;
if (playerData && g_GameAttributes.mapType != "scenario")
for (let i in playerData)
if (!g_CivData[playerData[i].Civ] || !g_CivData[playerData[i].Civ].SelectableInGameSetup)
playerData[i].Civ = "random";
// Apply map settings
let newMapData = loadMapData(mapName);
if (newMapData && newMapData.settings)
{
for (let prop in newMapData.settings)
mapSettings[prop] = newMapData.settings[prop];
if (playerData)
mapSettings.PlayerData = playerData;
}
if (mapSettings.PlayerData)
sanitizePlayerData(mapSettings.PlayerData);
// Reload, as the maptype or mapfilter might have changed
initMapNameList();
g_GameAttributes.settings.RatingEnabled = Engine.HasXmppClient();
Engine.SetRankedGame(g_GameAttributes.settings.RatingEnabled);
updateGUIObjects();
}
function savePersistMatchSettings()
{
let attributes = Engine.ConfigDB_GetValue("user", "persistmatchsettings") == "true" ? g_GameAttributes : {};
Engine.WriteJSONFile(g_IsNetworked ? g_MatchSettings_MP : g_MatchSettings_SP, attributes);
}
function sanitizePlayerData(playerData)
{
// Remove gaia
if (playerData.length && !playerData[0])
playerData.shift();
playerData.forEach((pData, index) => {
pData.Color = pData.Color || g_PlayerColors[index];
pData.Civ = pData.Civ || "random";
});
// Replace colors with the best matching color of PlayerDefaults
if (g_GameAttributes.mapType != "scenario")
{
playerData.forEach((pData, index) => {
let colorDistances = g_PlayerColors.map(color => colorDistance(color, pData.Color));
let smallestDistance = colorDistances.find(distance => colorDistances.every(distance2 => (distance2 >= distance)));
pData.Color = g_PlayerColors.find(color => colorDistance(color, pData.Color) == smallestDistance);
});
}
ensureUniquePlayerColors(playerData);
}
function cancelSetup()
{
if (g_IsController)
savePersistMatchSettings();
Engine.DisconnectNetworkGame();
if (Engine.HasXmppClient())
{
Engine.LobbySetPlayerPresence("available");
if (g_IsController)
Engine.SendUnregisterGame();
Engine.SwitchGuiPage("page_lobby.xml");
}
else
Engine.SwitchGuiPage("page_pregame.xml");
}
function onTick()
{
if (!g_Settings)
return;
// 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;
initGUIObjects();
++g_LoadingState;
}
else if (g_LoadingState == 2)
{
while (true)
{
let message = Engine.PollNetworkClient();
if (!message)
break;
log("Net message: " + uneval(message));
if (g_NetMessageTypes[message.type])
g_NetMessageTypes[message.type](message);
else
error("Unrecognised net message type " + message.type);
}
}
updateTimers();
}
/**
* Called when the host choses the number of players on a random map.
* @param {Number} num
*/
function selectNumPlayers(num)
{
// Avoid recursion
if (g_IsInGuiUpdate || !g_IsController || g_GameAttributes.mapType != "random")
return;
// Unassign players from nonexistent slots
if (g_IsNetworked)
{
for (let i = g_MaxPlayers; i > num; --i)
Engine.AssignNetworkPlayer(i, "");
}
else if (g_PlayerAssignments.local.player > num)
g_PlayerAssignments.local.player = 1;
// Update player data
let pData = g_GameAttributes.settings.PlayerData;
if (num < pData.length)
g_GameAttributes.settings.PlayerData = pData.slice(0, num);
else
for (let i = pData.length; i < num; ++i)
g_GameAttributes.settings.PlayerData.push(g_DefaultPlayerData[i]);
updateGameAttributes();
}
/**
* Assigns the given color to that player.
*/
function selectPlayerColor(playerSlot, colorIndex)
{
if (colorIndex == -1)
return;
let playerData = g_GameAttributes.settings.PlayerData;
// If someone else has that color, give that player the old color
let pData = playerData.find(pData => sameColor(g_PlayerColors[colorIndex], pData.Color));
if (pData)
pData.Color = playerData[playerSlot].Color;
// Assign the new color
playerData[playerSlot].Color = g_PlayerColors[colorIndex];
// Ensure colors are not used twice after increasing the number of players
ensureUniquePlayerColors(playerData);
if (!g_IsInGuiUpdate)
updateGameAttributes();
}
function ensureUniquePlayerColors(playerData)
{
for (let i = playerData.length - 1; i >= 0; --i)
// If someone else has that color, assign an unused color
if (playerData.some((pData, j) => i != j && sameColor(playerData[i].Color, pData.Color)))
playerData[i].Color = g_PlayerColors.find(color => playerData.every(pData => !sameColor(color, pData.Color)));
}
/**
* Called when the user selects a map type from the list.
*
* @param {string} type - scenario, skirmish or random
*/
function selectMapType(type)
{
// Avoid recursion
if (g_IsInGuiUpdate || !g_IsController)
return;
if (!g_MapPath[type])
{
error("selectMapType: Unexpected map type " + type);
return;
}
g_MapData = {};
g_GameAttributes.map = "";
g_GameAttributes.mapType = type;
g_GameAttributes.mapPath = g_MapPath[type];
if (type != "scenario")
g_GameAttributes.settings = {
"PlayerData": g_DefaultPlayerData.slice(0, 4),
"Seed": Math.floor(Math.random() * 65536),
"CheatsEnabled": g_GameAttributes.settings.CheatsEnabled
};
g_GameAttributes.settings.AISeed = Math.floor(Math.random() * 65536);
initMapNameList();
updateGameAttributes();
}
function selectMapFilter(id)
{
// Avoid recursion
if (g_IsInGuiUpdate || !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 || !g_IsController || !name)
return;
// Reset some map specific properties which are not necessarily redefined on each map
for (let prop of ["TriggerScripts", "CircularMap", "Garrison"])
g_GameAttributes.settings[prop] = undefined;
let mapData = loadMapData(name);
let mapSettings = mapData && mapData.settings ? deepcopy(mapData.settings) : {};
// Reset victory conditions
if (g_GameAttributes.mapType != "random")
{
let victoryIdx = g_VictoryConditions.Name.indexOf(mapSettings.GameType || "") != -1 ? g_VictoryConditions.Name.indexOf(mapSettings.GameType) : g_VictoryConditions.Default;
g_GameAttributes.settings.GameType = g_VictoryConditions.Name[victoryIdx];
g_GameAttributes.settings.VictoryScripts = g_VictoryConditions.Scripts[victoryIdx];
}
+ if (g_GameAttributes.mapType == "scenario")
+ delete g_GameAttributes.settings.WonderDuration;
+
if (mapSettings.PlayerData)
sanitizePlayerData(mapSettings.PlayerData);
// Copy any new settings
g_GameAttributes.map = name;
g_GameAttributes.script = mapSettings.Script;
if (g_GameAttributes.map !== "random")
for (let prop in mapSettings)
g_GameAttributes.settings[prop] = mapSettings[prop];
// Use default AI if the map doesn't specify any explicitly
for (let i in g_GameAttributes.settings.PlayerData)
{
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)
g_PlayerAssignments = { "local": { "name": translate("You"), "player": 1, "civ": "", "team": -1, "ready": 0 } };
else
{
let numPlayers = mapSettings.PlayerData ? mapSettings.PlayerData.length : g_GameAttributes.settings.PlayerData.length;
for (let guid in g_PlayerAssignments)
{ // Unassign extra players
let player = g_PlayerAssignments[guid].player;
if (player <= g_MaxPlayers && player > numPlayers)
Engine.AssignNetworkPlayer(player, "");
}
}
updateGameAttributes();
}
function launchGame()
{
if (!g_IsController)
{
error("Only host can start game");
return;
}
if (!g_GameAttributes.map)
return;
savePersistMatchSettings();
// Select random map
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;
}
g_GameAttributes.settings.TriggerScripts = g_GameAttributes.settings.VictoryScripts.concat(g_GameAttributes.settings.TriggerScripts || []);
// Prevent reseting the readystate
g_GameStarted = true;
g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
// Get a unique array of selectable cultures
let cultures = Object.keys(g_CivData).filter(civ => g_CivData[civ].SelectableInGameSetup).map(civ => g_CivData[civ].Culture);
cultures = cultures.filter((culture, index) => cultures.indexOf(culture) === index);
// Determine random civs and botnames
for (let i in g_GameAttributes.settings.PlayerData)
{
// Pick a random civ of a random culture
let chosenCiv = g_GameAttributes.settings.PlayerData[i].Civ || "random";
if (chosenCiv == "random")
{
let culture = cultures[Math.floor(Math.random() * cultures.length)];
let civs = Object.keys(g_CivData).filter(civ => g_CivData[civ].Culture == culture);
chosenCiv = civs[Math.floor(Math.random() * civs.length)];
}
g_GameAttributes.settings.PlayerData[i].Civ = chosenCiv;
// Pick one of the available botnames for the chosen civ
if (g_GameAttributes.mapType === "scenario" || !g_GameAttributes.settings.PlayerData[i].AI)
continue;
let chosenName = g_CivData[chosenCiv].AINames[Math.floor(Math.random() * g_CivData[chosenCiv].AINames.length)];
if (!g_IsNetworked)
chosenName = translate(chosenName);
// Count how many players use the chosenName
let usedName = g_GameAttributes.settings.PlayerData.filter(pData => pData.Name && pData.Name.indexOf(chosenName) !== -1).length;
g_GameAttributes.settings.PlayerData[i].Name = !usedName ? chosenName : sprintf(translate("%(playerName)s %(romanNumber)s"), { "playerName": chosenName, "romanNumber": g_RomanNumbers[usedName+1] });
}
// Copy playernames from initial player assignment to the settings
for (let guid in g_PlayerAssignments)
{
let player = g_PlayerAssignments[guid];
if (player.player > 0) // not observer or GAIA
g_GameAttributes.settings.PlayerData[player.player - 1].Name = player.name;
}
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
Engine.StartNetworkGame();
}
else
{
// Find the player ID which the user has been assigned to
let playerID = -1;
for (let i in g_GameAttributes.settings.PlayerData)
{
let assignBox = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
if (assignBox.list_data[assignBox.selected] == "local")
playerID = +i+1;
}
Engine.StartGame(g_GameAttributes, playerID);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments
});
}
}
/**
* Don't set any attributes here, just show the changes in the GUI.
*
* Unless the mapsettings don't specify a property and the user didn't set it in g_GameAttributes previously.
*/
function updateGUIObjects()
{
g_IsInGuiUpdate = true;
let mapSettings = g_GameAttributes.settings;
// These dropdowns don't set values while g_IsInGuiUpdate
let mapName = g_GameAttributes.map || "";
let mapFilterIdx = g_MapFilters.findIndex(mapFilter => mapFilter.id == (g_GameAttributes.mapFilter || "default"));
let mapTypeIdx = g_GameAttributes.mapType !== undefined ? g_MapTypes.Name.indexOf(g_GameAttributes.mapType) : g_MapTypes.Default;
let gameSpeedIdx = g_GameAttributes.gameSpeed !== undefined ? g_GameSpeeds.Speed.indexOf(g_GameAttributes.gameSpeed) : g_GameSpeeds.Default;
// These dropdowns might set the default (as they ignore g_IsInGuiUpdate)
let mapSizeIdx = mapSettings.Size !== undefined ? g_MapSizes.Tiles.indexOf(mapSettings.Size) : g_MapSizes.Default;
let victoryIdx = mapSettings.GameType !== undefined ? g_VictoryConditions.Name.indexOf(mapSettings.GameType) : g_VictoryConditions.Default;
+ let wonderDurationIdx = mapSettings.WonderDuration !== undefined ? g_WonderDurations.Duration.indexOf(mapSettings.WonderDuration) : g_WonderDurations.Default;
let popIdx = mapSettings.PopulationCap !== undefined ? g_PopulationCapacities.Population.indexOf(mapSettings.PopulationCap) : g_PopulationCapacities.Default;
let startingResIdx = mapSettings.StartingResources !== undefined ? g_StartingResources.Resources.indexOf(mapSettings.StartingResources) : g_StartingResources.Default;
let ceasefireIdx = mapSettings.Ceasefire !== undefined ? g_Ceasefire.Duration.indexOf(mapSettings.Ceasefire) : g_Ceasefire.Default;
let numPlayers = mapSettings.PlayerData ? mapSettings.PlayerData.length : g_MaxPlayers;
if (g_IsController)
{
Engine.GetGUIObjectByName("mapTypeSelection").selected = mapTypeIdx;
Engine.GetGUIObjectByName("mapFilterSelection").selected = mapFilterIdx;
Engine.GetGUIObjectByName("mapSelection").selected = Engine.GetGUIObjectByName("mapSelection").list_data.indexOf(mapName);
Engine.GetGUIObjectByName("mapSize").selected = mapSizeIdx;
Engine.GetGUIObjectByName("numPlayersSelection").selected = numPlayers - 1;
Engine.GetGUIObjectByName("victoryCondition").selected = victoryIdx;
+ Engine.GetGUIObjectByName("wonderDuration").selected = wonderDurationIdx;
Engine.GetGUIObjectByName("populationCap").selected = popIdx;
Engine.GetGUIObjectByName("gameSpeed").selected = gameSpeedIdx;
Engine.GetGUIObjectByName("ceasefire").selected = ceasefireIdx;
Engine.GetGUIObjectByName("startingResources").selected = startingResIdx;
}
else
{
Engine.GetGUIObjectByName("mapTypeText").caption = g_MapTypes.Title[mapTypeIdx];
Engine.GetGUIObjectByName("mapFilterText").caption = g_MapFilters[mapFilterIdx].name;
Engine.GetGUIObjectByName("mapSelectionText").caption = mapName == "random" ? g_RandomMap : translate(getMapDisplayName(mapName));
initMapNameList();
}
// Can be visible to both host and clients
Engine.GetGUIObjectByName("mapSizeText").caption = g_GameAttributes.mapType == "random" ? g_MapSizes.LongName[mapSizeIdx] : translate("Default");
Engine.GetGUIObjectByName("numPlayersText").caption = numPlayers;
Engine.GetGUIObjectByName("victoryConditionText").caption = g_VictoryConditions.Title[victoryIdx];
+ Engine.GetGUIObjectByName("wonderDurationText").caption = g_WonderDurations.Title[wonderDurationIdx];
Engine.GetGUIObjectByName("populationCapText").caption = g_PopulationCapacities.Title[popIdx];
Engine.GetGUIObjectByName("startingResourcesText").caption = g_StartingResources.Title[startingResIdx];
Engine.GetGUIObjectByName("ceasefireText").caption = g_Ceasefire.Title[ceasefireIdx];
Engine.GetGUIObjectByName("gameSpeedText").caption = g_GameSpeeds.Title[gameSpeedIdx];
setGUIBoolean("enableCheats", "enableCheatsText", !!mapSettings.CheatsEnabled);
setGUIBoolean("disableTreasures", "disableTreasuresText", !!mapSettings.DisableTreasures);
setGUIBoolean("exploreMap", "exploreMapText", !!mapSettings.ExploreMap);
setGUIBoolean("revealMap", "revealMapText", !!mapSettings.RevealMap);
setGUIBoolean("lockTeams", "lockTeamsText", !!mapSettings.LockTeams);
setGUIBoolean("enableRating", "enableRatingText", !!mapSettings.RatingEnabled);
+ Engine.GetGUIObjectByName("optionWonderDuration").hidden =
+ g_GameAttributes.settings.GameType &&
+ g_GameAttributes.settings.GameType != "wonder";
+
Engine.GetGUIObjectByName("cheatWarningText").hidden = !g_IsNetworked || !mapSettings.CheatsEnabled;
Engine.GetGUIObjectByName("enableCheats").enabled = !mapSettings.RatingEnabled;
Engine.GetGUIObjectByName("lockTeams").enabled = !mapSettings.RatingEnabled;
// Mapsize completely hidden for non-random maps
let isRandom = g_GameAttributes.mapType == "random";
Engine.GetGUIObjectByName("mapSizeDesc").hidden = !isRandom;
Engine.GetGUIObjectByName("mapSize").hidden = !isRandom || !g_IsController;
Engine.GetGUIObjectByName("mapSizeText").hidden = !isRandom || g_IsController;
hideControl("numPlayersSelection", "numPlayersText", isRandom && g_IsController);
let notScenario = g_GameAttributes.mapType != "scenario" && g_IsController ;
hideControl("victoryCondition", "victoryConditionText", notScenario);
+ hideControl("wonderDuration", "wonderDurationText", notScenario);
hideControl("populationCap", "populationCapText", notScenario);
hideControl("startingResources", "startingResourcesText", notScenario);
hideControl("ceasefire", "ceasefireText", notScenario);
hideControl("revealMap", "revealMapText", notScenario);
hideControl("exploreMap", "exploreMapText", notScenario);
hideControl("disableTreasures", "disableTreasuresText", notScenario);
hideControl("lockTeams", "lockTeamsText", notScenario);
setMapDescription();
for (let i = 0; i < g_MaxPlayers; ++i)
{
Engine.GetGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers);
if (i >= numPlayers)
continue;
let pName = Engine.GetGUIObjectByName("playerName["+i+"]");
let pAssignment = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
let pAssignmentText = Engine.GetGUIObjectByName("playerAssignmentText["+i+"]");
let pCiv = Engine.GetGUIObjectByName("playerCiv["+i+"]");
let pCivText = Engine.GetGUIObjectByName("playerCivText["+i+"]");
let pTeam = Engine.GetGUIObjectByName("playerTeam["+i+"]");
let pTeamText = Engine.GetGUIObjectByName("playerTeamText["+i+"]");
let pColor = Engine.GetGUIObjectByName("playerColor["+i+"]");
let pData = mapSettings.PlayerData ? mapSettings.PlayerData[i] : {};
let pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[i] : {};
let color = getSetting(pData, pDefs, "Color");
pColor.sprite = "color:" + rgbToGuiColor(color) + " 100";
pName.caption = translate(getSetting(pData, pDefs, "Name"));
let team = getSetting(pData, pDefs, "Team");
let civ = getSetting(pData, pDefs, "Civ");
pAssignmentText.caption = pAssignment.list[0] ? pAssignment.list[Math.max(0, pAssignment.selected)] : translate("Loading...");
pCivText.caption = civ == "random" ? g_RandomCiv : (g_CivData[civ] ? g_CivData[civ].Name : "Unknown");
pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
pCiv.selected = civ ? pCiv.list_data.indexOf(civ) : 0;
pTeam.selected = team !== undefined && team >= 0 ? team+1 : 0;
hideControl("playerAssignment["+i+"]", "playerAssignmentText["+i+"]", g_IsController);
hideControl("playerCiv["+i+"]", "playerCivText["+i+"]", notScenario);
hideControl("playerTeam["+i+"]", "playerTeamText["+i+"]", notScenario);
// Allow host to chose player colors on non-scenario maps
let pColorPicker = Engine.GetGUIObjectByName("playerColorPicker["+i+"]");
let pColorPickerHeading = Engine.GetGUIObjectByName("playerColorHeading");
let canChangeColors = g_IsController && g_GameAttributes.mapType != "scenario";
pColorPicker.hidden = !canChangeColors;
pColorPickerHeading.hidden = !canChangeColors;
if (canChangeColors)
pColorPicker.selected = g_PlayerColors.findIndex(col => sameColor(col, color));
}
+ resizeMoreOptionsWindow();
+
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();
}
/**
* Sets an additional map label, map preview image and mapsettings description.
*/
function setMapDescription()
{
let numPlayers = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData.length : 0;
let mapName = g_GameAttributes.map || "";
let victoryIdx = Math.max(0, g_VictoryConditions.Name.indexOf(g_GameAttributes.settings.GameType || ""));
- let victoryTitle = g_VictoryConditions.Title[victoryIdx];
+ let victoryTitle;
+
+ if (g_VictoryConditions.Name[victoryIdx] == "wonder")
+ victoryTitle = sprintf(
+ translatePluralWithContext(
+ "victory condition",
+ "Wonder (%(min)s minute)",
+ "Wonder (%(min)s minutes)",
+ g_GameAttributes.settings.WonderDuration
+ ),
+ { "min": g_GameAttributes.settings.WonderDuration }
+ );
+ else
+ victoryTitle = g_VictoryConditions.Title[victoryIdx];
+
if (victoryIdx != g_VictoryConditions.Default)
victoryTitle = "[color=\"" + g_VictoryColor + "\"]" + victoryTitle + "[/color]";
let mapDescription = g_GameAttributes.settings.Description ? translate(g_GameAttributes.settings.Description) : translate("Sorry, no description available.");
if (mapName == "random")
mapDescription = translate("Randomly selects a map from the list");
let gameDescription = sprintf(translatePlural("%(number)s player. ", "%(number)s players. ", numPlayers), { "number": numPlayers });
gameDescription += translate("Victory Condition:") + " " + victoryTitle + ".\n\n";
gameDescription += mapDescription;
Engine.GetGUIObjectByName("mapInfoName").caption = mapName == "random" ? translateWithContext("map selection", "Random") : translate(getMapDisplayName(mapName));
Engine.GetGUIObjectByName("mapInfoDescription").caption = gameDescription;
setMapPreviewImage("mapPreview", getMapPreview(mapName));
}
/**
* Broadcast the changed settings to all clients and the lobbybot.
*/
function updateGameAttributes()
{
if (g_IsInGuiUpdate || !g_IsController)
return;
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
if (g_LoadingState >= 2)
sendRegisterGameStanza();
}
else
updateGUIObjects();
}
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;
let hostNameList = [];
let hostGuidList = [];
let assignments = [];
let aiAssignments = {};
let noAssignment;
let assignedCount = 0;
for (let guid of sortGUIDsByPlayerID())
{
let player = g_PlayerAssignments[guid].player;
if (player != -1)
hostNameList.push(g_PlayerAssignments[guid].name);
else
hostNameList.push("[color=\""+ g_UnassignedPlayerColor + "\"]" + g_PlayerAssignments[guid].name + "[/color]");
hostGuidList.push(guid);
assignments[player] = hostNameList.length-1;
if (player != -1)
++assignedCount;
}
// Only enable start button if we have enough assigned players
if (g_IsController)
Engine.GetGUIObjectByName("startGame").enabled = assignedCount > 0;
for (let ai of g_Settings.AIDescriptions)
{
// If the map uses a hidden AI then don't hide it
if (ai.data.hidden && g_GameAttributes.settings.PlayerData.every(pData => pData.AI != ai.id))
continue;
aiAssignments[ai.id] = hostNameList.length;
hostNameList.push("[color=\""+ g_AIColor + "\"]" + sprintf(translate("AI: %(ai)s"), { "ai": translate(ai.data.name) }));
hostGuidList.push("ai:" + ai.id);
}
noAssignment = hostNameList.length;
hostNameList.push("[color=\""+ g_UnassignedColor + "\"]" + translate("Unassigned"));
hostGuidList.push("");
for (let i = 0; i < g_MaxPlayers; ++i)
{
let playerSlot = i;
let playerID = i+1; // we don't show Gaia, so first slot is ID 1
let selection = assignments[playerID];
let 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)
{
let 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("AI \"" + aiId + "\" not present. Defaulting to unassigned.");
}
}
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", {
"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 = "";
if (g_IsNetworked)
Engine.SetNetworkGameAttributes(g_GameAttributes);
}
let assignBox = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
let 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;
let 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.
let newPlayerID = newSlot + 1;
let 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 (let guid in g_PlayerAssignments)
{
// Move the player in the destination slot into the current slot.
if (g_PlayerAssignments[guid].player != newPlayerID)
continue;
if (g_IsNetworked)
Engine.AssignNetworkPlayer(playerID, guid);
else
g_PlayerAssignments[guid].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()
{
let input = Engine.GetGUIObjectByName("chatInput");
let text = input.caption;
if (!text.length)
return;
input.caption = "";
if (executeNetworkCommand(text))
return;
Engine.SendNetworkChat(text);
}
function senderFont(text)
{
return '[font="' + g_SenderFont + '"]' + text + '[/font]';
}
function systemMessage(message)
{
return senderFont(sprintf(translate("== %(message)s"), { "message": message }));
}
function colorizePlayernameByGUID(guid, username = "")
{
// TODO: Maybe the host should have the moderator-prefix?
if (!username)
username = g_PlayerAssignments[guid] ? escapeText(g_PlayerAssignments[guid].name) : translate("Unknown Player");
let playerID = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].player : -1;
let color = "white";
if (playerID > 0)
{
color = g_GameAttributes.settings.PlayerData[playerID - 1].Color;
// Enlighten playercolor to improve readability
let [h, s, l] = rgbToHsl(color.r, color.g, color.b);
let [r, g, b] = hslToRgb(h, s, Math.max(0.6, l));
color = rgbToGuiColor({ "r": r, "g": g, "b": b });
}
return '[color="'+ color +'"]' + username + '[/color]';
}
function addChatMessage(msg)
{
if (!g_FormatChatMessage[msg.type])
return;
let user = colorizePlayernameByGUID(msg.guid || -1, msg.username || "");
g_ChatMessages.push(g_FormatChatMessage[msg.type](msg, user));
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
function showMoreOptions(show)
{
Engine.GetGUIObjectByName("moreOptionsFade").hidden = !show;
Engine.GetGUIObjectByName("moreOptions").hidden = !show;
}
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;
let isAI = new Array(g_MaxPlayers + 1).fill(true);
let allReady = true;
for (let 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;
let pData = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1] : {};
let 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="' + g_ReadyColor + '"]' + 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 (let playerid = 0; playerid < g_MaxPlayers; ++playerid)
{
if (!g_GameAttributes.settings.PlayerData[playerid])
continue;
let pData = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[playerid] : {};
let pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[playerid] : {};
if (isAI[playerid + 1])
Engine.GetGUIObjectByName("playerName[" + playerid + "]").caption = '[color="' + g_ReadyColor + '"]' + translate(getSetting(pData, pDefs, "Name")) + '[/color]';
}
// The host is not allowed to start until everyone is ready.
if (g_IsNetworked && g_IsController)
{
let startGameButton = Engine.GetGUIObjectByName("startGame");
startGameButton.enabled = allReady;
// Add a explanation on to the tooltip if disabled.
let 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!");
}
}
/**
* Send the relevant gamesettings to the lobbybot.
*/
function sendRegisterGameStanza()
{
if (!g_IsController || !Engine.HasXmppClient())
return;
let selectedMapSize = Engine.GetGUIObjectByName("mapSize").selected;
let selectedVictoryCondition = Engine.GetGUIObjectByName("victoryCondition").selected;
let mapSize = g_GameAttributes.mapType == "random" ? Engine.GetGUIObjectByName("mapSize").list_data[selectedMapSize] : "Default";
let victoryCondition = Engine.GetGUIObjectByName("victoryCondition").list[selectedVictoryCondition];
let playerNames = Object.keys(g_PlayerAssignments).map(guid => g_PlayerAssignments[guid].name).sort();
let stanza = {
"name": g_ServerName,
"mapName": g_GameAttributes.map,
"niceMapName": getMapDisplayName(g_GameAttributes.map),
"mapSize": mapSize,
"mapType": g_GameAttributes.mapType,
"victoryCondition": victoryCondition,
"nbp": Object.keys(g_PlayerAssignments).length || 1,
"tnbp": g_GameAttributes.settings.PlayerData.length,
"players": playerNames.join(", ")
};
// Only send the stanza if the relevant settings actually changed
if (g_LastGameStanza && Object.keys(stanza).every(prop => g_LastGameStanza[prop] == stanza[prop]))
return;
g_LastGameStanza = stanza;
Engine.SendRegisterGame(stanza);
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 18074)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 18075)
@@ -1,406 +1,416 @@
Index: ps/trunk/binaries/data/mods/public/maps/scripts/WonderVictory.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scripts/WonderVictory.js (revision 18074)
+++ ps/trunk/binaries/data/mods/public/maps/scripts/WonderVictory.js (revision 18075)
@@ -1,58 +1,58 @@
Trigger.prototype.CheckWonderVictory = function(data)
{
var ent = data.entity;
var cmpWonder = Engine.QueryInterface(ent, IID_Wonder);
if (!cmpWonder)
return;
var timer = this.wonderVictoryTimers[ent];
var messages = this.wonderVictoryMessages[ent] || {};
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
// Remove existing messages if any
if (timer)
{
cmpTimer.CancelTimer(timer);
cmpGuiInterface.DeleteTimeNotification(messages.ownMessage);
cmpGuiInterface.DeleteTimeNotification(messages.otherMessage);
}
if (data.to <= 0)
return;
// Create new messages, and start timer to register defeat.
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var numPlayers = cmpPlayerManager.GetNumPlayers();
var cmpPlayer = QueryOwnerInterface(ent, IID_Player);
// Add -1 to notify observers too
var players = [-1];
for (var i = 1; i < numPlayers; i++)
if (i != data.to)
players.push(i);
- var time = cmpWonder.GetTimeTillVictory()*1000;
+ var time = cmpWonder.GetVictoryDuration();
messages.otherMessage = cmpGuiInterface.AddTimeNotification({
"message": markForTranslation("%(player)s will have won in %(time)s"),
"players": players,
"parameters": {"player": cmpPlayer.GetName()},
"translateMessage": true,
"translateParameters": [],
}, time);
messages.ownMessage = cmpGuiInterface.AddTimeNotification({
"message": markForTranslation("You will have won in %(time)s"),
"players": [data.to],
"translateMessage": true,
}, time);
timer = cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_EndGameManager, "MarkPlayerAsWon", time, data.to);
this.wonderVictoryTimers[ent] = timer;
this.wonderVictoryMessages[ent] = messages;
};
var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
var data = {"enabled": true};
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CheckWonderVictory", data);
cmpTrigger.wonderVictoryTimers = {};
cmpTrigger.wonderVictoryMessages = {};
Index: ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js (revision 18074)
+++ ps/trunk/binaries/data/mods/public/simulation/components/EndGameManager.js (revision 18075)
@@ -1,103 +1,115 @@
// Repetition interval (msecs) for checking end game conditions
var g_ProgressInterval = 1000;
/**
* System component which regularly checks victory/defeat conditions
* and if they are satisfied then it marks the player as victorious/defeated.
*/
function EndGameManager() {}
EndGameManager.prototype.Schema =
"";
EndGameManager.prototype.Init = function()
{
// Game type, initialised from the map settings.
// One of: "conquest" (default) and "endless"
this.gameType = "conquest";
-
+
+ this.wonderDuration = 10 * 60 * 1000;
+
// Allied victory means allied players can win if victory conditions are met for each of them
// Would be false for a "last man standing" game (when diplomacy is fully implemented)
this.alliedVictory = true;
};
EndGameManager.prototype.GetGameType = function()
{
return this.gameType;
};
EndGameManager.prototype.SetGameType = function(newGameType)
{
this.gameType = newGameType;
Engine.BroadcastMessage(MT_GameTypeChanged, {});
};
EndGameManager.prototype.CheckGameType = function(type)
{
return this.gameType == type;
};
+EndGameManager.prototype.SetWonderDuration = function(wonderDuration)
+{
+ this.wonderDuration = wonderDuration;
+};
+
+EndGameManager.prototype.GetWonderDuration = function()
+{
+ return this.wonderDuration;
+};
+
EndGameManager.prototype.MarkPlayerAsWon = function(playerID)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var numPlayers = cmpPlayerManager.GetNumPlayers();
for (var i = 1; i < numPlayers; i++)
{
var playerEntityId = cmpPlayerManager.GetPlayerByID(i);
var cmpPlayer = Engine.QueryInterface(playerEntityId, IID_Player);
if (cmpPlayer.GetState() != "active")
continue;
if (playerID == cmpPlayer.GetPlayerID() || this.alliedVictory && cmpPlayer.IsMutualAlly(playerID))
cmpPlayer.SetState("won");
else
Engine.PostMessage(playerEntityId, MT_PlayerDefeated, { "playerId": i, "skip": true } );
}
// Reveal the map to all players
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetLosRevealAll(-1, true);
};
EndGameManager.prototype.SetAlliedVictory = function(flag)
{
this.alliedVictory = flag;
};
EndGameManager.prototype.OnGlobalPlayerDefeated = function(msg)
{
if (msg.skip)
return;
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var cmpPlayers = [];
var allies = [];
var onlyAlliesLeft = true;
var numPlayers = cmpPlayerManager.GetNumPlayers();
for (var i = 1; i < numPlayers; ++i)
{
cmpPlayers[i] = QueryPlayerIDInterface(i);
if (cmpPlayers[i].GetState() != "active" || i == msg.playerId)
continue;
if (!allies.length || cmpPlayers[allies[0]].IsMutualAlly(i))
allies.push(i);
else
onlyAlliesLeft = false;
}
// check if there are winners, or the game needs to continue
if (!allies.length || !onlyAlliesLeft || !this.alliedVictory)
return;
for (var p of allies)
cmpPlayers[p].SetState("won");
// Reveal the map to all players
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetLosRevealAll(-1, true);
};
Engine.RegisterSystemComponentType(IID_EndGameManager, "EndGameManager", EndGameManager);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Wonder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Wonder.js (revision 18074)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Wonder.js (revision 18075)
@@ -1,19 +1,23 @@
function Wonder() {}
Wonder.prototype.Schema =
- "" +
- "" +
+ "" +
+ "" +
"";
Wonder.prototype.Init = function()
{
};
Wonder.prototype.Serialize = null;
-Wonder.prototype.GetTimeTillVictory = function()
+/**
+ * Returns the number of minutes that a player has to keep the wonder in order to win.
+ */
+Wonder.prototype.GetVictoryDuration = function()
{
- return +this.template.TimeTillVictory;
+ var cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
+ return cmpEndGameManager.GetWonderDuration() * this.template.DurationMultiplier;
};
Engine.RegisterComponentType(IID_Wonder, "Wonder", Wonder);
Index: ps/trunk/binaries/data/mods/public/simulation/data/settings/wonder_times.json
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/data/settings/wonder_times.json (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/data/settings/wonder_times.json (revision 18075)
@@ -0,0 +1,5 @@
+{
+ "Times": [1, 3, 5, 10, 15, 20, 25, 30, 45, 60],
+ "Default": 15
+}
+
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 18074)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 18075)
@@ -1,70 +1,72 @@
/**
* 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.WonderDuration)
+ cmpEndGameManager.SetWonderDuration(settings.WonderDuration * 60 * 1000);
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);
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml (revision 18074)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml (revision 18075)
@@ -1,96 +1,96 @@
5
25
5
2
10
2
wonder_pop_1 wonder_pop_2
Wonder
2000
5.0
1000
1000
1000
1000
1000
10.0
30
0.1
Unit
Support Infantry Cavalry
1
2
5000
rubble/rubble_stone_6x6
Wonder
Bring glory to your civilization and add large tracts of land to your empire.
City Wonder
structures/wonder.png
phase_city
200
0
100
100
100
0.7
pop_wonder
interface/complete/building/complete_wonder.xml
attack/destruction/building_collapse_large.xml
6.0
0.6
12.0
true
100
65535
72
structures/fndn_6x6.xml
- 600
+ 1