Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 14112) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 14113) @@ -1,1557 +1,1561 @@ //////////////////////////////////////////////////////////////////////////////////////////////// // Constants const DEFAULT_NETWORKED_MAP = "Acropolis 01"; const DEFAULT_OFFLINE_MAP = "Acropolis 01"; // TODO: Move these somewhere like simulation\data\game_types.json, Atlas needs them too const VICTORY_TEXT = ["Conquest", "None"]; const VICTORY_DATA = ["conquest", "endless"]; const VICTORY_DEFAULTIDX = 0; const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", "Unlimited"]; const POPULATION_CAP_DATA = [50, 100, 150, 200, 250, 300, 10000]; const POPULATION_CAP_DEFAULTIDX = 5; const STARTING_RESOURCES = ["Very Low", "Low", "Medium", "High", "Very High", "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; 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 = []; // Warn about the AI's nonexistent naval map support. var g_NavalWarning = "\n\n[font=\"serif-bold-12\"][color=\"orange\"]Warning:[/color][/font] \ The AI does not support naval maps and may cause severe performance issues. \ Naval maps are recommended to be played with human opponents only."; // 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 //////////////////////////////////////////////////////////////////////////////////////////////// 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("Unexpected 'type' in gamesetup init: "+attribs.type); } if (attribs.serverName) g_ServerName = attribs.serverName; // Init the Cancel Button caption and tooltip var cancelButton = getGUIObjectByName("cancelGame"); if(!Engine.HasXmppClient()) { cancelButton.caption = "Main menu"; cancelButton.tooltip = "Return to the main menu." } else { cancelButton.caption = "Quit"; cancelButton.tooltip = "Return to the lobby." } } // Called after the map data is loaded and cached function initMain() { // Load AI list and hide deprecated AIs 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 = getGUIObjectByName("mapTypeSelection"); mapTypes.list = ["Skirmish","Random","Scenario"]; mapTypes.list_data = ["skirmish","random","scenario"]; // Setup map filters - will appear in order they are added addFilter("Default", function(settings) { return settings && !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"]); }); addFilter("Naval Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["naval"]); }); addFilter("Demo Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["demo"]); }); addFilter("All Maps", function(settings) { return true; }); // Populate map filters dropdown var mapFilters = getGUIObjectByName("mapFilterSelection"); mapFilters.list = getFilters(); 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 = 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 = getGUIObjectByName("gameSpeed"); gameSpeed.hidden = false; getGUIObjectByName("gameSpeedText").hidden = true; gameSpeed.list = g_GameSpeeds.names; gameSpeed.list_data = g_GameSpeeds.speeds; gameSpeed.onSelectionChange = function() { // Update attributes so other players can see change if (this.selected != -1) g_GameAttributes.gameSpeed = g_GameSpeeds.speeds[this.selected]; if (!g_IsInGuiUpdate) updateGameAttributes(); } gameSpeed.selected = g_GameSpeeds["default"]; var populationCaps = 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]; if (!g_IsInGuiUpdate) updateGameAttributes(); } var startingResourcesL = 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]; if (!g_IsInGuiUpdate) updateGameAttributes(); } var victoryConditions = getGUIObjectByName("victoryCondition"); victoryConditions.list = VICTORY_TEXT; victoryConditions.list_data = VICTORY_DATA; victoryConditions.onSelectionChange = function() { // Update attributes so other players can see change if (this.selected != -1) g_GameAttributes.settings.GameType = VICTORY_DATA[this.selected]; if (!g_IsInGuiUpdate) updateGameAttributes(); }; victoryConditions.selected = VICTORY_DEFAULTIDX; var mapSize = getGUIObjectByName("mapSize"); mapSize.list = g_MapSizes.names; mapSize.list_data = g_MapSizes.tiles; mapSize.onSelectionChange = function() { // Update attributes so other players can see change if (this.selected != -1) g_GameAttributes.settings.Size = g_MapSizes.tiles[this.selected]; if (!g_IsInGuiUpdate) updateGameAttributes(); }; mapSize.selected = 0; getGUIObjectByName("revealMap").onPress = function() { // Update attributes so other players can see change g_GameAttributes.settings.RevealMap = this.checked; if (!g_IsInGuiUpdate) updateGameAttributes(); }; getGUIObjectByName("lockTeams").onPress = function() { // Update attributes so other players can see change g_GameAttributes.settings.LockTeams = this.checked; if (!g_IsInGuiUpdate) updateGameAttributes(); }; getGUIObjectByName("enableCheats").onPress = function() { // Update attributes so other players can see change g_GameAttributes.settings.CheatsEnabled = this.checked; if (!g_IsInGuiUpdate) updateGameAttributes(); }; } else { // If we're a network client, disable all the map controls // TODO: make them look visually disabled so it's obvious why they don't work getGUIObjectByName("mapTypeSelection").hidden = true; getGUIObjectByName("mapTypeText").hidden = false; getGUIObjectByName("mapFilterSelection").hidden = true; getGUIObjectByName("mapFilterText").hidden = false; getGUIObjectByName("mapSelectionText").hidden = false; getGUIObjectByName("mapSelection").hidden = true; getGUIObjectByName("victoryConditionText").hidden = false; getGUIObjectByName("victoryCondition").hidden = true; getGUIObjectByName("gameSpeedText").hidden = false; 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) { getGUIObjectByName("playerAssignment["+i+"]").enabled = false; getGUIObjectByName("playerCiv["+i+"]").hidden = true; getGUIObjectByName("playerTeam["+i+"]").hidden = true; } getGUIObjectByName("numPlayersSelection").hidden = true; } // Set up multiplayer/singleplayer bits: if (!g_IsNetworked) { getGUIObjectByName("chatPanel").hidden = true; getGUIObjectByName("enableCheats").checked = true; g_GameAttributes.settings.CheatsEnabled = true; } else { getGUIObjectByName("enableCheatsDesc").hidden = false; getGUIObjectByName("enableCheats").checked = false; g_GameAttributes.settings.CheatsEnabled = false; if (g_IsController) getGUIObjectByName("enableCheats").hidden = false; else getGUIObjectByName("enableCheatsText").hidden = false; } // Settings for all possible player slots var boxSpacing = 32; for (var i = 0; i < MAX_PLAYERS; ++i) { // Space player boxes var box = 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 = getGUIObjectByName("playerTeam["+i+"]"); team.list = ["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() { // Update team if (this.selected != -1) g_GameAttributes.settings.PlayerData[playerSlot].Team = this.selected - 1; if (!g_IsInGuiUpdate) updateGameAttributes(); }; // Set events var civ = getGUIObjectByName("playerCiv["+i+"]"); civ.onSelectionChange = function() { // Update civ if ((this.selected != -1)&&(g_GameAttributes.mapType !== "scenario")) g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected]; if (!g_IsInGuiUpdate) updateGameAttributes(); }; } if (g_IsNetworked) { // For multiplayer, focus the chat input box by default getGUIObjectByName("chatInput").focus(); } else { // For single-player, focus the map list by default, // to allow easy keyboard selection of maps getGUIObjectByName("mapSelection").focus(); } } function handleNetMessage(message) { log("Net message: "+uneval(message)); switch (message.type) { case "netstatus": switch (message.status) { case "disconnected": cancelSetup(); 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; onGameAttributesChange(); break; case "players": // Find and report all joinings/leavings for (var host in message.hosts) if (! g_PlayerAssignments[host]) addChatMessage({ "type": "connect", "username": message.hosts[host].name }); for (var host in g_PlayerAssignments) if (! message.hosts[host]) addChatMessage({ "type": "disconnect", "guid": host }); // Update the player list g_PlayerAssignments = message.hosts; updatePlayerList(); if (g_IsController) sendRegisterGameStanza(); break; case "start": - if (g_IsController) + 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; default: error("Unrecognised net message type "+message.type); } } // Get display name from map data function getMapDisplayName(map) { var mapData = loadMapData(map); if (!mapData || !mapData.settings || !mapData.settings.Name) { // Give some msg that map format is unsupported log("Map data missing in scenario '"+map+"' - likely unsupported format"); return map; } return mapData.settings.Name; } // Get display name from map data function getMapPreview(map) { var mapData = loadMapData(map); if (!mapData || !mapData.settings || !mapData.settings.Preview) { // Give some msg that map format is unsupported 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\"]Random"); civListCodes.unshift("random"); // Update the dropdowns for (var i = 0; i < MAX_PLAYERS; ++i) { var civ = 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 = 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("initMapNameList: Unexpected map type '"+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 mapList.sort(sortNameIgnoreCase); if (g_GameAttributes.mapType == "random") mapList.unshift({ "name": "[color=\"orange\"]Random[/color]", "file": "random" }); 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 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") g_MapData[name] = {settings : {"Name" : "Random", "Description" : "Randomly selects a map from the list"}}; else g_MapData[name] = parseJSONData(name+".json"); break; default: error("loadMapData: Unexpected map type '"+g_GameAttributes.mapType+"'"); return undefined; } } return g_MapData[name]; } //////////////////////////////////////////////////////////////////////////////////////////////// // GUI event handlers function cancelSetup() { Engine.DisconnectNetworkGame(); - // Set player presence - Engine.LobbySetPlayerPresence("available"); - - if(g_IsController) + if (Engine.HasXmppClient()) { + // Set player presence + Engine.LobbySetPlayerPresence("available"); + // Unregister the game - Engine.SendUnregisterGame(); + 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) { getGUIObjectByName("loadingWindow").hidden = true; 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": "You", "player": 1, "civ": "", "team": -1} }; } } 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 // TODO: This should be remembered from the last session g_GameAttributes.mapPath = "maps/scenarios/"; g_GameAttributes.map = g_GameAttributes.mapPath + (g_IsNetworked ? DEFAULT_NETWORKED_MAP : DEFAULT_OFFLINE_MAP); break; case "skirmish": g_GameAttributes.mapPath = "maps/skirmishes/"; g_GameAttributes.settings = { PlayerData: g_DefaultPlayerData.slice(0, 4), Seed: 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), CheatsEnabled: g_GameAttributes.settings.CheatsEnabled }; break; default: error("selectMapType: Unexpected map type '"+g_GameAttributes.mapType+"'"); return; } initMapNameList(); updateGameAttributes(); } function selectMapFilter(filterName) { // Avoid recursion if (g_IsInGuiUpdate) return; // Network clients can't change map filter if (g_IsNetworked && !g_IsController) return; g_GameAttributes.mapFilter = filterName; 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; var mapData = loadMapData(name); var mapSettings = (mapData && mapData.settings ? deepcopy(mapData.settings) : {}); // Copy any new settings g_GameAttributes.map = name; g_GameAttributes.script = mapSettings.Script; if (mapData !== "Random") for (var prop in mapSettings) g_GameAttributes.settings[prop] = mapSettings[prop]; // 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": "You", "player": 1, "civ": "", "team": -1} }; } 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; if (g_GameAttributes.map == "random") selectMap(getGUIObjectByName("mapSelection").list_data[Math.floor(Math.random() * (getGUIObjectByName("mapSelection").list.length - 1)) + 1]); 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 === undefined || civ.SelectableInGameSetup)) 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 === undefined || civ.SelectableInGameSetup)) 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) { 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 if (usedName) g_GameAttributes.settings.PlayerData[i].Name = chosenName + " " + 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 = 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) { getGUIObjectByName("mapFilterText").caption = g_GameAttributes.mapFilter; var mapTypeSelection = getGUIObjectByName("mapTypeSelection"); var idx = mapTypeSelection.list_data.indexOf(g_GameAttributes.mapType); getGUIObjectByName("mapTypeText").caption = mapTypeSelection.list[idx]; var mapSelectionBox = getGUIObjectByName("mapSelection"); mapSelectionBox.selected = mapSelectionBox.list_data.indexOf(mapName); getGUIObjectByName("mapSelectionText").caption = getMapDisplayName(mapName); var populationCapBox = getGUIObjectByName("populationCap"); populationCapBox.selected = populationCapBox.list_data.indexOf(mapSettings.PopulationCap); var startingResourcesBox = getGUIObjectByName("startingResources"); startingResourcesBox.selected = startingResourcesBox.list_data.indexOf(mapSettings.StartingResources); initMapNameList(); } // Controls common to all map types var numPlayersSelection = getGUIObjectByName("numPlayersSelection"); var revealMap = getGUIObjectByName("revealMap"); var victoryCondition = getGUIObjectByName("victoryCondition"); var lockTeams = getGUIObjectByName("lockTeams"); var mapSize = getGUIObjectByName("mapSize"); var enableCheats = getGUIObjectByName("enableCheats"); var populationCap = getGUIObjectByName("populationCap"); var startingResources = getGUIObjectByName("startingResources"); var numPlayersText= getGUIObjectByName("numPlayersText"); var mapSizeText = getGUIObjectByName("mapSizeText"); var revealMapText = getGUIObjectByName("revealMapText"); var victoryConditionText = getGUIObjectByName("victoryConditionText"); var lockTeamsText = getGUIObjectByName("lockTeamsText"); var enableCheatsText = getGUIObjectByName("enableCheatsText"); var populationCapText = getGUIObjectByName("populationCapText"); var startingResourcesText = getGUIObjectByName("startingResourcesText"); var gameSpeedText = getGUIObjectByName("gameSpeedText"); var sizeIdx = (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 victoryIdx = (VICTORY_DATA.indexOf(mapSettings.GameType) != -1 ? VICTORY_DATA.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX); enableCheats.checked = (g_GameAttributes.settings.CheatsEnabled === undefined || !g_GameAttributes.settings.CheatsEnabled ? false : true) enableCheatsText.caption = (enableCheats.checked ? "Yes" : "No"); gameSpeedText.caption = g_GameSpeeds.names[speedIdx]; populationCap.selected = (POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX); populationCapText.caption = POPULATION_CAP[populationCap.selected]; startingResources.selected = (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 getGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName); // Handle map type specific logic switch (g_GameAttributes.mapType) { case "random": if (g_IsController) { //Host numPlayersSelection.selected = numPlayers - 1; numPlayersSelection.hidden = false; mapSize.hidden = false; revealMap.hidden = false; victoryCondition.hidden = false; lockTeams.hidden = false; populationCap.hidden = false; startingResources.hidden = false; numPlayersText.hidden = true; mapSizeText.hidden = true; revealMapText.hidden = true; victoryConditionText.hidden = true; lockTeamsText.hidden = true; populationCapText.hidden = true; startingResourcesText.hidden = true; mapSizeText.caption = "Map size:"; mapSize.selected = sizeIdx; revealMapText.caption = "Reveal map:"; revealMap.checked = (mapSettings.RevealMap ? true : false); victoryConditionText.caption = "Victory condition:"; victoryCondition.selected = victoryIdx; lockTeamsText.caption = "Teams locked:"; lockTeams.checked = (mapSettings.LockTeams ? true : false); } else { // Client numPlayersText.hidden = false; mapSizeText.hidden = false; revealMapText.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 = g_MapSizes.names[sizeIdx]; revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No"); victoryConditionText.caption = VICTORY_TEXT[victoryIdx]; lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No"); } break; case "skirmish": mapSizeText.caption = "Default"; numPlayersText.caption = numPlayers; numPlayersSelection.hidden = true; mapSize.hidden = true; if (g_IsController) { //Host revealMap.hidden = false; victoryCondition.hidden = false; lockTeams.hidden = false; populationCap.hidden = false; startingResources.hidden = false; numPlayersText.hidden = false; mapSizeText.hidden = false; revealMapText.hidden = true; victoryConditionText.hidden = true; lockTeamsText.hidden = true; populationCapText.hidden = true; startingResourcesText.hidden = true; revealMapText.caption = "Reveal map:"; revealMap.checked = (mapSettings.RevealMap ? true : false); victoryConditionText.caption = "Victory condition:"; victoryCondition.selected = victoryIdx; lockTeamsText.caption = "Teams locked:"; lockTeams.checked = (mapSettings.LockTeams ? true : false); } else { // Client numPlayersText.hidden = false; mapSizeText.hidden = false; revealMapText.hidden = false; victoryConditionText.hidden = false; lockTeamsText.hidden = false; populationCap.hidden = true; populationCapText.hidden = false; startingResources.hidden = true; startingResourcesText.hidden = false; revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No"); victoryConditionText.caption = VICTORY_TEXT[victoryIdx]; lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No"); } break; case "scenario": // For scenario just reflect settings for the current map numPlayersSelection.hidden = true; mapSize.hidden = true; revealMap.hidden = true; victoryCondition.hidden = true; lockTeams.hidden = true; numPlayersText.hidden = false; mapSizeText.hidden = false; revealMapText.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 = "Default"; revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No"); victoryConditionText.caption = VICTORY_TEXT[victoryIdx]; lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No"); getGUIObjectByName("populationCap").selected = POPULATION_CAP_DEFAULTIDX; break; default: error("onGameAttributesChange: Unexpected map type '"+g_GameAttributes.mapType+"'"); return; } // Display map name getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName); // Load the description from the map file, if there is one var description = mapSettings.Description || "Sorry, no description available."; if (g_GameAttributes.mapFilter == "Naval Maps") description += g_NavalWarning; // Describe the number of players var playerString = numPlayers + " " + (numPlayers == 1 ? "player" : "players") + ". "; for (var i = 0; i < MAX_PLAYERS; ++i) { // Show only needed player slots getGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers); // Show player data or defaults as necessary if (i >= numPlayers) continue; var pName = getGUIObjectByName("playerName["+i+"]"); var pCiv = getGUIObjectByName("playerCiv["+i+"]"); var pCivText = getGUIObjectByName("playerCivText["+i+"]"); var pTeam = getGUIObjectByName("playerTeam["+i+"]"); var pTeamText = getGUIObjectByName("playerTeamText["+i+"]"); var pColor = getGUIObjectByName("playerColour["+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 = iColorToString(getSetting(pData, pDefs, "Colour")); pColor.sprite = "colour:"+color+" 100"; pName.caption = getSetting(pData, pDefs, "Name"); var team = getSetting(pData, pDefs, "Team"); var civ = getSetting(pData, pDefs, "Civ"); // For clients or scenarios, hide some player dropdowns 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\"]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; } } getGUIObjectByName("mapInfoDescription").caption = playerString + description; g_IsInGuiUpdate = false; // Game attributes include AI settings, so update the player list updatePlayerList(); } function updateGameAttributes() { if (g_IsNetworked) { Engine.SetNetworkGameAttributes(g_GameAttributes); if (g_IsController && g_LoadingState >= 2) sendRegisterGameStanza(); } else { onGameAttributesChange(); } } function updatePlayerList() { g_IsInGuiUpdate = true; var hostNameList = []; var hostGuidList = []; var assignments = []; var aiAssignments = {}; var noAssignment; var 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 != 255) assignedCount++; } // Only enable start button if we have enough assigned players if (g_IsController) getGUIObjectByName("startGame").enabled = (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\"]AI: " + ai.data.name); hostGuidList.push("ai:" + ai.id); } noAssignment = hostNameList.length; hostNameList.push("[color=\"140 140 140 255\"]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 = 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 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", { ais: g_AIs, id: g_GameAttributes.settings.PlayerData[playerSlot].AI, difficulty: g_GameAttributes.settings.PlayerData[playerSlot].AIDiff, callback: function(ai) { g_GameAttributes.settings.PlayerData[playerSlot].AI = ai.id; g_GameAttributes.settings.PlayerData[playerSlot].AIDiff = ai.difficulty; if (g_IsNetworked) Engine.SetNetworkGameAttributes(g_GameAttributes); else updatePlayerList(); } }); }; } } // 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 = getGUIObjectByName("playerAssignment["+i+"]"); assignBox.list = hostNameList; assignBox.list_data = hostGuidList; if (assignBox.selected != selection) assignBox.selected = selection; if (g_IsNetworked && g_IsController) { assignBox.onselectionchange = function () { if (!g_IsInGuiUpdate) { var guid = hostGuidList[this.selected]; if (guid == "") { // 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:") { // 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); Engine.SetNetworkGameAttributes(g_GameAttributes); } }; } else if (!g_IsNetworked) { assignBox.onselectionchange = function () { if (!g_IsInGuiUpdate) { var guid = hostGuidList[this.selected]; if (guid == "") { // Remove AI from this player slot g_GameAttributes.settings.PlayerData[playerSlot].AI = ""; } else if (guid.substr(0, 3) == "ai:") { // Set the AI for this player slot g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3); } else swapPlayers(guid, playerSlot); updatePlayerList(); } }; } } 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 != 255) { 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 = getGUIObjectByName("chatInput"); var text = input.caption; if (text.length) { Engine.SendNetworkChat(text); input.caption = ""; } } function addChatMessage(msg) { var username = escapeText(msg.username || g_PlayerAssignments[msg.guid].name); var message = escapeText(msg.text); // TODO: Maybe host should have distinct font/color? var color = "white"; if (g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != 255) { // Valid player who has been assigned - get player colour 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 = iColorToString(getSetting(pData, pDefs, "Colour")); } var formatted; switch (msg.type) { case "connect": formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has joined[/color]'; break; case "disconnect": formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has left[/color]'; break; case "message": formatted = '[font="serif-bold-13"]<[color="'+ color +'"]' + username + '[/color]>[/font] ' + message; break; default: error("Invalid chat message '" + uneval(msg) + "'"); return; } g_ChatMessages.push(formatted); getGUIObjectByName("chatText").caption = g_ChatMessages.join("\n"); } function toggleMoreOptions() { getGUIObjectByName("moreOptions").hidden = !getGUIObjectByName("moreOptions").hidden; } //////////////////////////////////////////////////////////////////////////////////////////////// // Basic map filters API // Add a new map list filter function addFilter(name, filterFunc) { if (filterFunc instanceof Object) { // Basic validity test var newFilter = {}; newFilter.name = name; newFilter.filter = filterFunc; g_MapFilters.push(newFilter); } else { error("Invalid map filter: "+name); } } // Get array of map filter names function getFilters() { 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(name, mapSettings) { for (var i = 0; i < g_MapFilters.length; ++i) if (g_MapFilters[i].name == name) return g_MapFilters[i].filter(mapSettings); error("Invalid map filter: "+name); 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 = getGUIObjectByName("mapSize").selected; var selectedVictoryCondition = getGUIObjectByName("victoryCondition").selected; // Map sizes only apply to random maps. if (g_GameAttributes.mapType == "random") var mapSize = getGUIObjectByName("mapSize").list[selectedMapSize]; else var mapSize = "Default"; var victoryCondition = 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; 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); } Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js (revision 14112) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup_mp.js (revision 14113) @@ -1,226 +1,229 @@ var g_IsConnecting = false; var g_GameType; // "server" or "client" var g_ServerName = ""; var g_IsRejoining = false; var g_GameAttributes; // used when rejoining var g_PlayerAssignments; // used when rejoining function init(attribs) { switch (attribs.multiplayerGameType) { case "join": if(Engine.HasXmppClient()) { if (startJoin(attribs.name, attribs.ip)) switchSetupPage("pageJoin", "pageConnecting"); } else { getGUIObjectByName("pageJoin").hidden = false; getGUIObjectByName("pageHost").hidden = true; } break; case "host": getGUIObjectByName("pageJoin").hidden = true; getGUIObjectByName("pageHost").hidden = false; if(Engine.HasXmppClient()) { getGUIObjectByName("hostServerNameWrapper").hidden = false; getGUIObjectByName("hostPlayerName").caption = attribs.name; getGUIObjectByName("hostServerName").caption = attribs.name + "'s game"; } else getGUIObjectByName("hostPlayerNameWrapper").hidden = false; break; default: error("Unrecognised multiplayer game type : " + attribs.multiplayerGameType); break; } } function cancelSetup() { if (g_IsConnecting) Engine.DisconnectNetworkGame(); // Set player lobby presence - Engine.LobbySetPlayerPresence("available"); + if (Engine.HasXmppClient()) + Engine.LobbySetPlayerPresence("available"); Engine.PopGuiPage(); } function startConnectionStatus(type) { g_GameType = type; g_IsConnecting = true; g_IsRejoining = false; getGUIObjectByName("connectionStatus").caption = "Connecting to server..."; } function onTick() { if (!g_IsConnecting) return; pollAndHandleNetworkClient(); } function pollAndHandleNetworkClient() { while (true) { var message = Engine.PollNetworkClient(); if (!message) break; log("Net message: "+uneval(message)); // If we're rejoining an active game, we don't want to actually display // the game setup screen, so perform similar processing to gamesetup.js // in this screen if (g_IsRejoining) { switch (message.type) { case "netstatus": switch (message.status) { case "disconnected": cancelSetup(); reportDisconnect(message.reason); return; default: error("Unrecognised netstatus type "+message.status); break; } break; case "gamesetup": g_GameAttributes = message.data; break; case "players": g_PlayerAssignments = message.hosts; break; case "start": Engine.SwitchGuiPage("page_loading.xml", { "attribs": g_GameAttributes, "isNetworked" : true, "playerAssignments": g_PlayerAssignments }); break; case "chat": // Ignore, since we have nowhere to display chat messages break; default: error("Unrecognised net message type "+message.type); } } else { // Not rejoining - just trying to connect to server switch (message.type) { case "netstatus": switch (message.status) { case "connected": getGUIObjectByName("connectionStatus").caption = "Registering with server..."; break; case "authenticated": if (message.rejoining) { getGUIObjectByName("connectionStatus").caption = "Game has already started - rejoining..."; g_IsRejoining = true; return; // we'll process the game setup messages in the next tick } else { Engine.SwitchGuiPage("page_gamesetup.xml", { "type": g_GameType, "serverName": g_ServerName }); return; // don't process any more messages - leave them for the game GUI loop } case "disconnected": cancelSetup(); reportDisconnect(message.reason); return; default: error("Unrecognised netstatus type "+message.status); break; } break; default: error("Unrecognised net message type "+message.type); break; } } } } function switchSetupPage(oldpage, newpage) { getGUIObjectByName(oldpage).hidden = true; getGUIObjectByName(newpage).hidden = false; } function startHost(playername, servername) { // Disallow identically named games in the multiplayer lobby if (Engine.HasXmppClient()) { for each (g in Engine.GetGameList()) { if (g.name === servername) { getGUIObjectByName("hostFeedback").caption = "Game name already in use."; return false; } } } try { Engine.StartNetworkHost(playername); } catch (e) { cancelSetup(); messageBox(400, 200, "Cannot host game: " + e.message + ".", "Error", 2); return false; } startConnectionStatus("server"); g_ServerName = servername; // Set player lobby presence - Engine.LobbySetPlayerPresence("playing"); + if (Engine.HasXmppClient()) + Engine.LobbySetPlayerPresence("playing"); return true; } function startJoin(playername, ip) { try { Engine.StartNetworkJoin(playername, ip); } catch (e) { cancelSetup(); messageBox(400, 200, "Cannot join game: " + e.message + ".", "Error", 2); return false; } startConnectionStatus("client"); // Set player lobby presence - Engine.LobbySetPlayerPresence("playing"); + if (Engine.HasXmppClient()) + Engine.LobbySetPlayerPresence("playing"); return true; } Index: ps/trunk/binaries/data/mods/public/gui/session/menu.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 14112) +++ ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 14113) @@ -1,444 +1,447 @@ const PAUSE = "Pause"; const RESUME = "Resume"; /* * MENU POSITION CONSTANTS */ // Menu / panel border size const MARGIN = 4; // Includes the main menu button const NUM_BUTTONS = 8; // Regular menu buttons const BUTTON_HEIGHT = 32; // The position where the bottom of the menu will end up (currently 228) const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN; // Menu starting position: bottom const MENU_BOTTOM = 0; // Menu starting position: top const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION; // Menu starting position: overall const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM; // Number of pixels per millisecond to move const MENU_SPEED = 1.2; var isMenuOpen = false; var menu; var isDiplomacyOpen = false; // Redefined every time someone makes a tribute (so we can save some data in a closure). Called in input.js handleInputBeforeGui. var flushTributing = function() {}; // Ignore size defined in XML and set the actual menu size here function initMenuPosition() { menu = getGUIObjectByName("menu"); menu.size = INITIAL_MENU_POSITION; } // ============================================================================= // Overall Menu // ============================================================================= // // Slide menu function updateMenuPosition(dt) { if (isMenuOpen) { var maxOffset = END_MENU_POSITION - menu.size.bottom; if (maxOffset > 0) { var offset = Math.min(MENU_SPEED * dt, maxOffset); var size = menu.size; size.top += offset; size.bottom += offset; menu.size = size; } } else { var maxOffset = menu.size.top - MENU_TOP; if (maxOffset > 0) { var offset = Math.min(MENU_SPEED * dt, maxOffset); var size = menu.size; size.top -= offset; size.bottom -= offset; menu.size = size; } } } // Opens the menu by revealing the screen which contains the menu function openMenu() { // playButtonSound(); isMenuOpen = true; } // Closes the menu and resets position function closeMenu() { // playButtonSound(); isMenuOpen = false; } function toggleMenu() { if (isMenuOpen == true) closeMenu(); else openMenu(); } // Menu buttons // ============================================================================= function settingsMenuButton() { closeMenu(); closeOpenDialogs(); openSettings(true); } function chatMenuButton() { closeMenu(); closeOpenDialogs(); openChat(); } function diplomacyMenuButton() { closeMenu(); closeOpenDialogs(); openDiplomacy(); } function pauseMenuButton() { togglePause(); } function resignMenuButton() { closeMenu(); closeOpenDialogs(); pauseGame(); var btCaptions = ["Yes", "No"]; var btCode = [resignGame, resumeGame]; messageBox(400, 200, "Are you sure you want to resign?", "Confirmation", 0, btCaptions, btCode); } function exitMenuButton() { closeMenu(); closeOpenDialogs(); pauseGame(); var btCaptions = ["Yes", "No"]; var btCode = [leaveGame, resumeGame]; messageBox(400, 200, "Are you sure you want to quit?", "Confirmation", 0, btCaptions, btCode); } function openDeleteDialog(selection) { closeMenu(); closeOpenDialogs(); var deleteSelectedEntities = function () { Engine.PostNetworkCommand({"type": "delete-entities", "entities": selection}); }; var btCaptions = ["Yes", "No"]; var btCode = [deleteSelectedEntities, resumeGame]; messageBox(400, 200, "Destroy everything currently selected?", "Delete", 0, btCaptions, btCode); } // Menu functions // ============================================================================= function openSave() { closeMenu(); closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_savegame.xml", {"gameDataCallback": getSavedGameData, "closeCallback": resumeGame}); } function openSettings(pause) { getGUIObjectByName("settingsDialogPanel").hidden = false; if (pause) pauseGame(); } function closeSettings(resume) { getGUIObjectByName("settingsDialogPanel").hidden = true; if (resume) resumeGame(); } function openChat() { getGUIObjectByName("chatInput").focus(); // Grant focus to the input area getGUIObjectByName("chatDialogPanel").hidden = false; } function closeChat() { getGUIObjectByName("chatInput").caption = ""; // Clear chat input getGUIObjectByName("chatInput").blur(); // Remove focus getGUIObjectByName("chatDialogPanel").hidden = true; } function toggleChatWindow(teamChat) { closeSettings(); var chatWindow = getGUIObjectByName("chatDialogPanel"); var chatInput = getGUIObjectByName("chatInput"); if (chatWindow.hidden) chatInput.focus(); // Grant focus to the input area else { if (chatInput.caption.length) { submitChatInput(); return; } chatInput.caption = ""; // Clear chat input } getGUIObjectByName("toggleTeamChat").checked = teamChat; chatWindow.hidden = !chatWindow.hidden; } function setDiplomacy(data) { Engine.PostNetworkCommand({"type": "diplomacy", "to": data.to, "player": data.player}); } function tributeResource(data) { Engine.PostNetworkCommand({"type": "tribute", "player": data.player, "amounts": data.amounts}); } function openDiplomacy() { isDiplomacyOpen = true; var we = Engine.GetPlayerID(); var players = getPlayerData(g_PlayerAssignments); // Get offset for one line var onesize = getGUIObjectByName("diplomacyPlayer[0]").size; var rowsize = onesize.bottom - onesize.top; // We don't include gaia for (var i = 1; i < players.length; i++) { // Apply offset var row = getGUIObjectByName("diplomacyPlayer["+(i-1)+"]"); var size = row.size; size.top = rowsize*(i-1); size.bottom = rowsize*i; row.size = size; // Set background colour var playerColor = players[i].color.r+" "+players[i].color.g+" "+players[i].color.b; row.sprite = "colour: "+playerColor + " 32"; getGUIObjectByName("diplomacyPlayerName["+(i-1)+"]").caption = "[color=\"" + playerColor + "\"]" + players[i].name + "[/color]"; getGUIObjectByName("diplomacyPlayerCiv["+(i-1)+"]").caption = g_CivData[players[i].civ].Name; getGUIObjectByName("diplomacyPlayerTeam["+(i-1)+"]").caption = (players[i].team < 0) ? "None" : players[i].team+1; if (i != we) getGUIObjectByName("diplomacyPlayerTheirs["+(i-1)+"]").caption = (players[i].isAlly[we] ? "Ally" : (players[i].isNeutral[we] ? "Neutral" : "Enemy")); // Don't display the options for ourself, or if we or the other player aren't active anymore if (i == we || players[we].state != "active" || players[i].state != "active") { // Hide the unused/unselectable options for each (var a in ["TributeFood", "TributeWood", "TributeStone", "TributeMetal", "Ally", "Neutral", "Enemy"]) getGUIObjectByName("diplomacyPlayer"+a+"["+(i-1)+"]").hidden = true; continue; } // Tribute for each (var resource in ["food", "wood", "stone", "metal"]) { var button = getGUIObjectByName("diplomacyPlayerTribute"+toTitleCase(resource)+"["+(i-1)+"]"); button.onpress = (function(player, resource, button){ // Implement something like how unit batch training works. Shift+click to send 500, shift+click+click to send 1000, etc. // Also see input.js (searching for "INPUT_MASSTRIBUTING" should get all the relevant parts). var multiplier = 1; return function() { var isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute"); if (isBatchTrainPressed) { inputState = INPUT_MASSTRIBUTING; multiplier += multiplier == 1 ? 4 : 5; } var amounts = { "food": (resource == "food" ? 100 : 0) * multiplier, "wood": (resource == "wood" ? 100 : 0) * multiplier, "stone": (resource == "stone" ? 100 : 0) * multiplier, "metal": (resource == "metal" ? 100 : 0) * multiplier, }; button.tooltip = formatTributeTooltip(players[player], resource, amounts[resource]); // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some // evil global variable hackery. flushTributing = function() { tributeResource({"player": player, "amounts": amounts}); multiplier = 1; button.tooltip = formatTributeTooltip(players[player], resource, 100); }; if (!isBatchTrainPressed) flushTributing(); }; })(i, resource, button); button.hidden = false; button.tooltip = formatTributeTooltip(players[i], resource, 100); } // Skip our own teams on teams locked if (players[we].teamsLocked && players[we].team != -1 && players[we].team == players[i].team) continue; // Diplomacy settings // Set up the buttons for each (var setting in ["ally", "neutral", "enemy"]) { var button = getGUIObjectByName("diplomacyPlayer"+toTitleCase(setting)+"["+(i-1)+"]"); if (setting == "ally") { if (players[we].isAlly[i]) button.caption = "x"; else button.caption = ""; } else if (setting == "neutral") { if (players[we].isNeutral[i]) button.caption = "x"; else button.caption = ""; } else // "enemy" { if (players[we].isEnemy[i]) button.caption = "x"; else button.caption = ""; } button.onpress = (function(e){ return function() { setDiplomacy(e) } })({"player": i, "to": setting}); button.hidden = false; } } getGUIObjectByName("diplomacyDialogPanel").hidden = false; } function closeDiplomacy() { isDiplomacyOpen = false; getGUIObjectByName("diplomacyDialogPanel").hidden = true; } function toggleDiplomacy() { if (isDiplomacyOpen) closeDiplomacy(); else openDiplomacy(); }; function toggleGameSpeed() { var gameSpeed = getGUIObjectByName("gameSpeed"); gameSpeed.hidden = !gameSpeed.hidden; } function pauseGame() { getGUIObjectByName("pauseButtonText").caption = RESUME; getGUIObjectByName("pauseOverlay").hidden = false; setPaused(true); } function resumeGame() { getGUIObjectByName("pauseButtonText").caption = PAUSE; getGUIObjectByName("pauseOverlay").hidden = true; setPaused(false); } function togglePause() { closeMenu(); closeOpenDialogs(); var pauseOverlay = getGUIObjectByName("pauseOverlay"); if (pauseOverlay.hidden) { getGUIObjectByName("pauseButtonText").caption = RESUME; setPaused(true); } else { setPaused(false); getGUIObjectByName("pauseButtonText").caption = PAUSE; } pauseOverlay.hidden = !pauseOverlay.hidden; } function openManual() { closeMenu(); closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_manual.xml", {"page": "intro", "closeCallback": resumeGame}); } function toggleDeveloperOverlay() { + if (Engine.HasXmppClient() && Engine.IsRankedGame()) + return; + var devCommands = getGUIObjectByName("devCommands"); var text = devCommands.hidden ? "opened." : "closed."; submitChatDirectly("The Developer Overlay was " + text); // Update the options dialog getGUIObjectByName("developerOverlayCheckbox").checked = devCommands.hidden; devCommands.hidden = !devCommands.hidden; } function closeOpenDialogs() { closeMenu(); closeChat(); closeDiplomacy(); closeSettings(false); } function formatTributeTooltip(player, resource, amount) { var playerColor = player.color.r + " " + player.color.g + " " + player.color.b; return "Tribute " + amount + " " + resource + " to [color=\"" + playerColor + "\"]" + player.name + "[/color]. Shift-click to tribute " + (amount < 500 ? 500 : amount + 500) + "."; } Index: ps/trunk/binaries/data/mods/public/gui/session/session.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14112) +++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14113) @@ -1,816 +1,818 @@ // Network Mode var g_IsNetworked = false; // Is this user in control of game settings (i.e. is a network server, or offline player) var g_IsController; // Match ID for tracking var g_MatchID; // Cache the basic player data (name, civ, color) var g_Players = []; // Cache the useful civ data var g_CivData = {}; var g_GameSpeeds = {}; var g_CurrentSpeed; var g_PlayerAssignments = { "local": { "name": "You", "player": 1 } }; // Cache dev-mode settings that are frequently or widely used var g_DevSettings = { controlAll: false }; // Whether status bars should be shown for all of the player's units. var g_ShowAllStatusBars = false; // Indicate when one of the current player's training queues is blocked // (this is used to support population counter blinking) var g_IsTrainingBlocked = false; // Cache simulation state (updated on every simulation update) var g_SimState; // Cache EntityStates var g_EntityStates = {}; // {id:entState} // Whether the player has lost/won and reached the end of their game var g_GameEnded = false; var g_Disconnected = false; // Lost connection to server // Holds player states from the last tick var g_CachedLastStates = ""; // Colors to flash when pop limit reached const DEFAULT_POPULATION_COLOR = "white"; const POPULATION_ALERT_COLOR = "orange"; function GetSimState() { if (!g_SimState) { g_SimState = Engine.GuiInterfaceCall("GetSimulationState"); } return g_SimState; } function GetEntityState(entId) { if (!(entId in g_EntityStates)) { var entState = Engine.GuiInterfaceCall("GetEntityState", entId); g_EntityStates[entId] = entState; } return g_EntityStates[entId]; } // Cache TemplateData var g_TemplateData = {}; // {id:template} function GetTemplateData(templateName) { if (!(templateName in g_TemplateData)) { var template = Engine.GuiInterfaceCall("GetTemplateData", templateName); g_TemplateData[templateName] = template; } return g_TemplateData[templateName]; } // Cache TechnologyData var g_TechnologyData = {}; // {id:template} function GetTechnologyData(technologyName) { if (!(technologyName in g_TechnologyData)) { var template = Engine.GuiInterfaceCall("GetTechnologyData", technologyName); g_TechnologyData[technologyName] = template; } return g_TechnologyData[technologyName]; } // Init function init(initData, hotloadData) { if (initData) { g_IsNetworked = initData.isNetworked; // Set network mode g_IsController = initData.isController; // Set controller mode g_PlayerAssignments = initData.playerAssignments; g_MatchID = initData.attribs.matchID; // Cache the player data // (This may be updated at runtime by handleNetMessage) g_Players = getPlayerData(g_PlayerAssignments); if (initData.savedGUIData) restoreSavedGameData(initData.savedGUIData); getGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked; } else // Needed for autostart loading option { g_Players = getPlayerData(null); } // Cache civ data g_CivData = loadCivData(); g_CivData["gaia"] = { "Code": "gaia", "Name": "Gaia" }; g_GameSpeeds = initGameSpeeds(); g_CurrentSpeed = Engine.GetSimRate(); var gameSpeed = getGUIObjectByName("gameSpeed"); gameSpeed.list = g_GameSpeeds.names; gameSpeed.list_data = g_GameSpeeds.speeds; var idx = g_GameSpeeds.speeds.indexOf(g_CurrentSpeed); gameSpeed.selected = idx != -1 ? idx : g_GameSpeeds["default"]; gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); } getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[Engine.GetPlayerID()].civ].Emblem; getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[Engine.GetPlayerID()].civ].Name; initMenuPosition(); // set initial position // Populate player selection dropdown var playerNames = []; var playerIDs = []; for (var player in g_Players) { playerNames.push(g_Players[player].name); playerIDs.push(player); } var viewPlayerDropdown = getGUIObjectByName("viewPlayer"); viewPlayerDropdown.list = playerNames; viewPlayerDropdown.list_data = playerIDs; viewPlayerDropdown.selected = Engine.GetPlayerID(); // If in Atlas editor, disable the exit button if (Engine.IsAtlasRunning()) getGUIObjectByName("menuExitButton").enabled = false; if (hotloadData) { g_Selection.selected = hotloadData.selection; } else { // Starting for the first time: var civMusic = g_CivData[g_Players[Engine.GetPlayerID()].civ].Music; initMusic(); global.music.storeTracks(civMusic); global.music.setState(global.music.states.PEACE); playRandomAmbient("temperate"); } if (Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") === "true") getGUIObjectByName("timeElapsedCounter").hidden = false; onSimulationUpdate(); // Report the performance after 5 seconds (when we're still near // the initial camera view) and a minute (when the profiler will // have settled down if framerates as very low), to give some // extremely rough indications of performance setTimeout(function() { reportPerformance(5); }, 5000); setTimeout(function() { reportPerformance(60); }, 60000); } function selectViewPlayer(playerID) { Engine.SetPlayerID(playerID); if (playerID != 0) { getGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[playerID].civ].Emblem; getGUIObjectByName("civIcon").tooltip = g_CivData[g_Players[playerID].civ].Name; } } function reportPerformance(time) { var settings = Engine.GetMapSettings(); var data = { time: time, map: settings.Name, seed: settings.Seed, // only defined for random maps size: settings.Size, // only defined for random maps profiler: Engine.GetProfilerState() }; Engine.SubmitUserReport("profile", 3, JSON.stringify(data)); } function resignGame() { var simState = GetSimState(); // Players can't resign if they've already won or lost. if (simState.players[Engine.GetPlayerID()].state != "active" || g_Disconnected) return; // Tell other players that we have given up and been defeated Engine.PostNetworkCommand({ "type": "defeat-player", "playerId": Engine.GetPlayerID() }); global.music.setState(global.music.states.DEFEAT); resumeGame(); } function leaveGame() { var extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); var playerState = extendedSimState.players[Engine.GetPlayerID()]; var mapSettings = Engine.GetMapSettings(); var gameResult; if (g_Disconnected) { gameResult = "You have been disconnected." } else if (playerState.state == "won") { gameResult = "You have won the battle!"; } else if (playerState.state == "defeated") { gameResult = "You have been defeated..."; } else // "active" { gameResult = "You have abandoned the game."; // Tell other players that we have given up and been defeated Engine.PostNetworkCommand({ "type": "defeat-player", "playerId": Engine.GetPlayerID() }); global.music.setState(global.music.states.DEFEAT); } stopAmbient(); endGame(); - if (g_IsController) - { + if (g_IsController && Engine.HasXmppClient()) Engine.SendUnregisterGame(); - } + Engine.SwitchGuiPage("page_summary.xml", { "gameResult" : gameResult, "timeElapsed" : extendedSimState.timeElapsed, "playerStates": extendedSimState.players, "players": g_Players, "mapSettings": mapSettings }); } // Return some data that we'll use when hotloading this file after changes function getHotloadData() { return { selection: g_Selection.selected }; } // Return some data that will be stored in saved game files function getSavedGameData() { var data = {}; data.playerAssignments = g_PlayerAssignments; data.groups = g_Groups.groups; // TODO: any other gui state? return data; } function restoreSavedGameData(data) { // Clear selection when loading a game g_Selection.reset(); // Restore control groups for (var groupNumber in data.groups) { g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups; g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents; } updateGroups(); } var lastTickTime = new Date; /** * Called every frame. */ function onTick() { var now = new Date; var tickLength = new Date - lastTickTime; lastTickTime = now; checkPlayerState(); while (true) { var message = Engine.PollNetworkClient(); if (!message) break; handleNetMessage(message); } updateCursorAndTooltip(); // If the selection changed, we need to regenerate the sim display (the display depends on both the // simulation state and the current selection). if (g_Selection.dirty) { onSimulationUpdate(); // Display rally points for selected buildings Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() }); } // Run timers updateTimers(); // Animate menu updateMenuPosition(tickLength); // When training is blocked, flash population (alternates colour every 500msec) if (g_IsTrainingBlocked && (Date.now() % 1000) < 500) getGUIObjectByName("resourcePop").textcolor = POPULATION_ALERT_COLOR; else getGUIObjectByName("resourcePop").textcolor = DEFAULT_POPULATION_COLOR; // Clear renamed entities list Engine.GuiInterfaceCall("ClearRenamedEntities"); } function checkPlayerState() { // Once the game ends, we're done here. if (g_GameEnded) return; // Send a game report for each player in this game. var m_simState = GetSimState(); var playerState = m_simState.players[Engine.GetPlayerID()]; var tempStates = ""; for each (var player in m_simState.players) {tempStates += player.state + ",";} if (g_CachedLastStates != tempStates) { g_CachedLastStates = tempStates; reportGame(Engine.GuiInterfaceCall("GetExtendedSimulationState")); } // If the local player hasn't finished playing, we return here to avoid the victory/defeat messages. if (playerState.state == "active") return; // We can't resign once the game is over. getGUIObjectByName("menuResignButton").enabled = false; // Make sure nothing is open to avoid stacking. closeMenu(); closeOpenDialogs(); // Make sure this doesn't run again. g_GameEnded = true; if (Engine.IsAtlasRunning()) { // If we're in Atlas, we can't leave the game var btCaptions = ["OK"]; var btCode = [null]; var message = "Press OK to continue"; } else { var btCaptions = ["Yes", "No"]; var btCode = [leaveGame, null]; var message = "Do you want to quit?"; } if (playerState.state == "defeated") { global.music.setState(global.music.states.DEFEAT); messageBox(400, 200, message, "DEFEATED!", 0, btCaptions, btCode); } else if (playerState.state == "won") { global.music.setState(global.music.states.VICTORY); // TODO: Reveal map directly instead of this silly proxy. if (!getGUIObjectByName("devCommandsRevealMap").checked) getGUIObjectByName("devCommandsRevealMap").checked = true; messageBox(400, 200, message, "VICTORIOUS!", 0, btCaptions, btCode); } } function changeGameSpeed(speed) { // For non-networked games only if (!g_IsNetworked) { Engine.SetSimRate(speed); g_CurrentSpeed = speed; } } /** * Recomputes GUI state that depends on simulation state or selection state. Called directly every simulation * update (see session.xml), or from onTick when the selection has changed. */ function onSimulationUpdate() { g_Selection.dirty = false; g_EntityStates = {}; g_TemplateData = {}; g_TechnologyData = {}; g_SimState = Engine.GuiInterfaceCall("GetSimulationState"); // If we're called during init when the game is first loading, there will be no simulation yet, so do nothing if (!g_SimState) return; handleNotifications(); if (g_ShowAllStatusBars) recalculateStatusBarDisplay(); updateHero(); updateGroups(); updateDebug(); updatePlayerDisplay(); updateSelectionDetails(); updateResearchDisplay(); updateBuildingPlacementPreview(); updateTimeElapsedCounter(); // Update music state on basis of battle state. var battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID()); if (battleState) global.music.setState(global.music.states[battleState]); } function updateHero() { var simState = GetSimState(); var playerState = simState.players[Engine.GetPlayerID()]; var heroButton = getGUIObjectByName("unitHeroButton"); if (!playerState || playerState.heroes.length <= 0) { heroButton.hidden = true; return; } var heroImage = getGUIObjectByName("unitHeroImage"); var heroState = GetEntityState(playerState.heroes[0]); var template = GetTemplateData(heroState.template); heroImage.sprite = "stretched:session/portraits/" + template.icon; var hero = playerState.heroes[0]; heroButton.onpress = function() { if (!Engine.HotkeyIsPressed("selection.add")) g_Selection.reset(); g_Selection.addList([hero]); }; heroButton.ondoublepress = function() { selectAndMoveTo(getEntityOrHolder(hero)); }; heroButton.hidden = false; // Setup tooltip var tooltip = "[font=\"serif-bold-16\"]" + template.name.specific + "[/font]"; tooltip += "\n[font=\"serif-bold-13\"]Health:[/font] " + heroState.hitpoints + "/" + heroState.maxHitpoints; tooltip += "\n[font=\"serif-bold-13\"]" + (heroState.attack ? heroState.attack.type + " " : "") + "Attack:[/font] " + damageTypeDetails(heroState.attack); // Show max attack range if ranged attack, also convert to tiles (4m per tile) if (heroState.attack && heroState.attack.type == "Ranged") tooltip += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(heroState.attack.maxRange/4); tooltip += "\n[font=\"serif-bold-13\"]Armor:[/font] " + damageTypeDetails(heroState.armour); tooltip += "\n" + template.tooltip; heroButton.tooltip = tooltip; }; function updateGroups() { var guiName = "Group"; g_Groups.update(); for (var i = 0; i < 10; i++) { var button = getGUIObjectByName("unit"+guiName+"Button["+i+"]"); var label = getGUIObjectByName("unit"+guiName+"Label["+i+"]").caption = i; if (g_Groups.groups[i].getTotalCount() == 0) button.hidden = true; else button.hidden = false; button.onpress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); } })(i); button.ondoublepress = (function(i) { return function() { performGroup("snap", i); } })(i); } var numButtons = i; var rowLength = 1; var numRows = Math.ceil(numButtons / rowLength); var buttonSideLength = getGUIObjectByName("unit"+guiName+"Button[0]").size.bottom; var buttonSpacer = buttonSideLength+1; for (var i = 0; i < numRows; i++) layoutButtonRow(i, guiName, buttonSideLength, buttonSpacer, rowLength*i, rowLength*(i+1) ); } function updateDebug() { var simState = GetSimState(); var debug = getGUIObjectByName("debug"); if (getGUIObjectByName("devDisplayState").checked) { debug.hidden = false; } else { debug.hidden = true; return; } var conciseSimState = deepcopy(simState); conciseSimState.players = "<<>>"; var text = "simulation: " + uneval(conciseSimState); var selection = g_Selection.toList(); if (selection.length) { var entState = GetEntityState(selection[0]); if (entState) { var template = GetTemplateData(entState.template); text += "\n\nentity: {\n"; for (var k in entState) text += " "+k+":"+uneval(entState[k])+"\n"; text += "}\n\ntemplate: " + uneval(template); } } debug.caption = text; } function updatePlayerDisplay() { var simState = GetSimState(); var playerState = simState.players[Engine.GetPlayerID()]; if (!playerState) return; getGUIObjectByName("resourceFood").caption = playerState.resourceCounts.food; getGUIObjectByName("resourceWood").caption = playerState.resourceCounts.wood; getGUIObjectByName("resourceStone").caption = playerState.resourceCounts.stone; getGUIObjectByName("resourceMetal").caption = playerState.resourceCounts.metal; getGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit; g_IsTrainingBlocked = playerState.trainingBlocked; } function selectAndMoveTo(ent) { var entState = GetEntityState(ent); if (!entState || !entState.position) return; g_Selection.reset(); g_Selection.addList([ent]); var position = entState.position; Engine.CameraMoveTo(position.x, position.z); } function updateResearchDisplay() { var researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", Engine.GetPlayerID()); if (!researchStarted) return; // Set up initial positioning. var buttonSideLength = getGUIObjectByName("researchStartedButton[0]").size.right; for (var i = 0; i < 10; ++i) { var button = getGUIObjectByName("researchStartedButton[" + i + "]"); var size = button.size; size.top = (4 + buttonSideLength) * i; size.bottom = size.top + buttonSideLength; button.size = size; } var numButtons = 0; for (var tech in researchStarted) { // Show at most 10 in-progress techs. if (numButtons >= 10) break; var template = GetTechnologyData(tech); var button = getGUIObjectByName("researchStartedButton[" + numButtons + "]"); button.hidden = false; button.tooltip = getEntityNames(template); button.onpress = (function(e) { return function() { selectAndMoveTo(e) } })(researchStarted[tech].researcher); var icon = "stretched:session/portraits/" + template.icon; getGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon; // Scale the progress indicator. var size = getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size; // Buttons are assumed to be square, so left/right offsets can be used for top/bottom. size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left)); getGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size; ++numButtons; } // Hide unused buttons. for (var i = numButtons; i < 10; ++i) getGUIObjectByName("researchStartedButton[" + i + "]").hidden = true; } function updateTimeElapsedCounter() { var simState = GetSimState(); var speed = g_CurrentSpeed != 1.0 ? " (" + g_CurrentSpeed + "x)" : ""; var timeElapsedCounter = getGUIObjectByName("timeElapsedCounter"); timeElapsedCounter.caption = timeToString(simState.timeElapsed) + speed; } // Toggles the display of status bars for all of the player's entities. function recalculateStatusBarDisplay() { if (g_ShowAllStatusBars) var entities = Engine.PickFriendlyEntitiesOnScreen(Engine.GetPlayerID()); else { var selected = g_Selection.toList(); for each (var ent in g_Selection.highlighted) selected.push(ent); // Remove selected entities from the 'all entities' array, to avoid disabling their status bars. var entities = Engine.GuiInterfaceCall("GetPlayerEntities").filter( function(idx) { return (selected.indexOf(idx) == -1); } ); } Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars }); } // Temporarily adding this here const AMBIENT_TEMPERATE = "temperate"; var currentAmbient; function playRandomAmbient(type) { switch (type) { case AMBIENT_TEMPERATE: // Seem to need the underscore at the end of "temperate" to avoid crash // (Might be caused by trying to randomly load day_temperate.xml) // currentAmbient = newRandomSound("ambient", "temperate_", "dayscape"); const AMBIENT = "audio/ambient/dayscape/day_temperate_gen_03.ogg"; Engine.PlayAmbientSound( AMBIENT, true ); break; default: Engine.Console_Write("Unrecognized ambient type: " + type); break; } } // Temporarily adding this here function stopAmbient() { if (currentAmbient) { currentAmbient.free(); currentAmbient = null; } } // Send a report on the game status to the lobby function reportGame(extendedSimState) { + if (!Engine.HasXmppClient()) + return; + // Resources gathered and used var playerFoodGatheredString = ""; var playerWoodGatheredString = ""; var playerStoneGatheredString = ""; var playerMetalGatheredString = ""; var playerFoodUsedString = ""; var playerWoodUsedString = ""; var playerStoneUsedString = ""; var playerMetalUsedString = ""; // Resources exchanged var playerFoodBoughtString = ""; var playerWoodBoughtString = ""; var playerStoneBoughtString = ""; var playerMetalBoughtString = ""; var playerFoodSoldString = ""; var playerWoodSoldString = ""; var playerStoneSoldString = ""; var playerMetalSoldString = ""; var playerTradeIncomeString = ""; // Unit Stats var playerUnitsLostString = ""; var playerUnitsTrainedString = ""; var playerEnemyUnitsKilledString = ""; // Building stats var playerBuildingsConstructedString = ""; var playerBuildingsLostString = ""; var playerEnemyBuildingsDestroyedString = ""; var playerCivCentersBuiltString = ""; var playerEnemyCivCentersDestroyedString = ""; // Tribute var playerTributeSentString = ""; var playerTributeReceivedString = ""; // Various var mapName = Engine.GetMapSettings().Name; var playerStatesString = ""; var playerCivsString = ""; var playerPercentMapExploredString = ""; var playerTreasuresCollectedString = ""; // Serialize the statistics for each player into a comma-separated list. for each (var player in extendedSimState.players) { playerStatesString += player.state + ","; playerCivsString += player.civ + ","; playerFoodGatheredString += player.statistics.resourcesGathered.food + ","; playerWoodGatheredString += player.statistics.resourcesGathered.wood + ","; playerStoneGatheredString += player.statistics.resourcesGathered.stone + ","; playerMetalGatheredString += player.statistics.resourcesGathered.metal + ","; playerFoodUsedString += player.statistics.resourcesUsed.food + ","; playerWoodUsedString += player.statistics.resourcesUsed.wood + ","; playerStoneUsedString += player.statistics.resourcesUsed.stone + ","; playerMetalUsedString += player.statistics.resourcesUsed.metal + ","; playerUnitsLostString += player.statistics.unitsLost + ","; playerUnitsTrainedString += player.statistics.unitsTrained + ","; playerEnemyUnitsKilledString += player.statistics.enemyUnitsKilled + ","; playerBuildingsConstructedString += player.statistics.buildingsConstructed + ","; playerBuildingsLostString += player.statistics.buildingsLost + ","; playerEnemyBuildingsDestroyedString += player.statistics.enemyBuildingsDestroyed + ","; playerFoodBoughtString += player.statistics.resourcesBought.food + ","; playerWoodBoughtString += player.statistics.resourcesBought.wood + ","; playerStoneBoughtString += player.statistics.resourcesBought.stone + ","; playerMetalBoughtString += player.statistics.resourcesBought.metal + ","; playerFoodSoldString += player.statistics.resourcesSold.food + ","; playerWoodSoldString += player.statistics.resourcesSold.wood + ","; playerStoneSoldString += player.statistics.resourcesSold.stone + ","; playerMetalSoldString += player.statistics.resourcesSold.metal + ","; playerTributeSentString += player.statistics.tributesSent + ","; playerTributeReceivedString += player.statistics.tributesReceived + ","; playerPercentMapExploredString += player.statistics.precentMapExplored = ","; playerCivCentersBuiltString += player.statistics.civCentresBuilt + ","; playerEnemyCivCentersDestroyedString += player.statistics.enemyCivCentresDestroyed + ","; playerTreasuresCollectedString += player.statistics.treasuresCollected + ","; playerTradeIncomeString += player.statistics.tradeIncome + ","; } // Send the report with serialized data Engine.SendGameReport({ "timeElapsed" : extendedSimState.timeElapsed, "playerStates" : playerStatesString, "playerID": Engine.GetPlayerID(), "matchID": g_MatchID, "civs" : playerCivsString, "mapName" : mapName, "foodGathered": playerFoodGatheredString, "woodGathered": playerWoodGatheredString, "stoneGathered": playerStoneGatheredString, "metalGathered": playerMetalGatheredString, "foodUsed": playerFoodUsedString, "woodUsed": playerWoodUsedString, "stoneUsed": playerStoneUsedString, "metalUsed": playerMetalUsedString, "unitsLost": playerUnitsLostString, "unitsTrained": playerUnitsTrainedString, "enemyUnitsKilled": playerEnemyUnitsKilledString, "buildingsLost": playerBuildingsLostString, "buildingsConstructed": playerBuildingsConstructedString, "enemyBuildingsDestroyed": playerEnemyBuildingsDestroyedString, "foodBought": playerFoodBoughtString, "woodBought": playerWoodBoughtString, "stoneBought": playerStoneBoughtString, "metalBought": playerMetalBoughtString, "foodSold": playerFoodSoldString, "woodSold": playerWoodSoldString, "stoneSold": playerStoneSoldString, "metalSold": playerMetalSoldString, "tributeSent": playerTributeSentString, "tributeReceived": playerTributeReceivedString, "precentMapExplored": playerPercentMapExploredString, "civCentersBuilt": playerCivCentersBuiltString, "enemyCivCentersDestroyed": playerEnemyCivCentersDestroyedString, "treasuresCollected": playerTreasuresCollectedString, "tradeIncome": playerTradeIncomeString }); } Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 14112) +++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 14113) @@ -1,1260 +1,1261 @@