//////////////////////////////////////////////////////////////////////////////////////////////// // 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; // 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 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 (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); } 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 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 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]; // 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); 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); 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; mapSizeText.hidden = true; mapSizeDesc.hidden = true; revealMapText.hidden = false; exploreMapText.hidden = false; disableTreasuresText.hidden = false; victoryConditionText.hidden = false; lockTeamsText.hidden = false; populationCap.hidden = true; populationCapText.hidden = false; startingResources.hidden = true; startingResourcesText.hidden = false; numPlayersText.caption = numPlayers; mapSizeText.caption = 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; 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; }