Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 14417) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 14418) @@ -1,1562 +1,1562 @@ //////////////////////////////////////////////////////////////////////////////////////////////// // 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_TEXT = ["Conquest", "Wonder", "None"]; +const VICTORY_DATA = ["conquest", "wonder", "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.tooltip = "Return to the main menu." } else { 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(); 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; 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 && 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(); 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) { 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) { 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 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; 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); } Index: ps/trunk/binaries/data/mods/public/gui/session/messages.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 14417) +++ ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 14418) @@ -1,538 +1,543 @@ // Chat data const CHAT_TIMEOUT = 30000; const MAX_NUM_CHAT_LINES = 20; var chatMessages = []; var chatTimers = []; // Notification Data const NOTIFICATION_TIMEOUT = 10000; const MAX_NUM_NOTIFICATION_LINES = 3; var notifications = []; var notificationsTimers = []; var cheats = getCheatsData(); function getCheatsData() { var cheats = {}; var cheatFileList = getJSONFileList("simulation/data/cheats/"); for each (var fileName in cheatFileList) { var currentCheat = parseJSONData("simulation/data/cheats/"+fileName+".json"); if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1) warn("Cheat name '"+currentCheat.Name+"' is already present"); else cheats[currentCheat.Name] = currentCheat.Data; } return cheats; } // Notifications function handleNotifications() { var notification = Engine.GuiInterfaceCall("GetNextNotification"); if (!notification) return; // Handle chat notifications specially if (notification.type == "chat") { var guid = findGuidForPlayerID(g_PlayerAssignments, notification.player); if (guid == undefined) { addChatMessage({ "type": "message", "guid": -1, "player": notification.player, "text": notification.message }); } else { addChatMessage({ "type": "message", "guid": findGuidForPlayerID(g_PlayerAssignments, notification.player), "text": notification.message }); } } else if (notification.type == "defeat") { addChatMessage({ "type": "defeat", "guid": findGuidForPlayerID(g_PlayerAssignments, notification.player), "player": notification.player }); // If the diplomacy panel is open refresh it. if (isDiplomacyOpen) openDiplomacy(); } else if (notification.type == "diplomacy") { addChatMessage({ "type": "diplomacy", "player": notification.player, "player1": notification.player1, "status": notification.status }); // If the diplomacy panel is open refresh it. if (isDiplomacyOpen) openDiplomacy(); } else if (notification.type == "quit") { // Used for AI testing exit(); } else if (notification.type == "tribute") { addChatMessage({ "type": "tribute", "player": notification.player, "player1": notification.player1, "amounts": notification.amounts }); } else if (notification.type == "attack") { if (notification.player == Engine.GetPlayerID()) { if (Engine.ConfigDB_GetValue("user", "gui.session.attacknotificationmessage") === "true") { addChatMessage({ "type": "attack", "player": notification.player, "attacker": notification.attacker }); } } } else { // Only display notifications directed to this player if (notification.player == Engine.GetPlayerID()) { notifications.push(notification); notificationsTimers.push(setTimeout(removeOldNotifications, NOTIFICATION_TIMEOUT)); if (notifications.length > MAX_NUM_NOTIFICATION_LINES) removeOldNotifications(); else displayNotifications(); } } } function removeOldNotifications() { clearTimeout(notificationsTimers[0]); // The timer only needs to be cleared when new notifications bump old notifications off notificationsTimers.shift(); notifications.shift(); displayNotifications(); } function displayNotifications() { var messages = []; for each (var n in notifications) messages.push(n.message); - getGUIObjectByName("notificationText").caption = messages.join("\n"); -} - -// Returns [username, playercolor] for the given player -function getUsernameAndColor(player) -{ + getGUIObjectByName("notificationText").caption = messages.join("\n"); +} + +function updateTimeNotifications() +{ + getGUIObjectByName("timeNotificationText").caption = Engine.GuiInterfaceCall("GetTimeNotificationText"); +} + +// Returns [username, playercolor] for the given player +function getUsernameAndColor(player) +{ // This case is hit for AIs, whose names don't exist in playerAssignments. var color = g_Players[player].color; return [ escapeText(g_Players[player].name), color.r + " " + color.g + " " + color.b, ]; } // Messages function handleNetMessage(message) { log("Net message: " + uneval(message)); switch (message.type) { case "netstatus": // If we lost connection, further netstatus messages are useless if (g_Disconnected) return; var obj = getGUIObjectByName("netStatus"); switch (message.status) { case "waiting_for_players": obj.caption = "Waiting for other players to connect..."; obj.hidden = false; break; case "join_syncing": obj.caption = "Synchronising gameplay with other players..."; obj.hidden = false; break; case "active": obj.caption = ""; obj.hidden = true; break; case "connected": obj.caption = "Connected to the server."; obj.hidden = false; break; case "authenticated": obj.caption = "Connection to the server has been authenticated."; obj.hidden = false; break; case "disconnected": g_Disconnected = true; obj.caption = "Connection to the server has been lost.\n\nThe game has ended."; obj.hidden = false; break; default: error("Unrecognised netstatus type "+message.status); break; } break; case "players": // Find and report all leavings for (var host in g_PlayerAssignments) { if (! message.hosts[host]) { // Tell the user about the disconnection addChatMessage({ "type": "disconnect", "guid": host }); // Update the cached player data, so we can display the disconnection status updatePlayerDataRemove(g_Players, host); } } // Find and report all joinings for (var host in message.hosts) { if (! g_PlayerAssignments[host]) { // Update the cached player data, so we can display the correct name updatePlayerDataAdd(g_Players, host, message.hosts[host]); // Tell the user about the connection addChatMessage({ "type": "connect", "guid": host }, message.hosts); } } g_PlayerAssignments = message.hosts; if (g_IsController) { var players = [ assignment.name for each (assignment in g_PlayerAssignments) ] Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", ")); } break; case "chat": addChatMessage({ "type": "message", "guid": message.guid, "text": message.text }); break; // To prevent errors, ignore these message types that occur during autostart case "gamesetup": case "start": break; default: error("Unrecognised net message type "+message.type); } } function submitChatDirectly(text) { if (text.length) { if (g_IsNetworked) Engine.SendNetworkChat(text); else addChatMessage({ "type": "message", "guid": "local", "text": text }); } } function submitChatInput() { var input = getGUIObjectByName("chatInput"); var text = input.caption; var isCheat = false; if (text.length) { if (g_Players[Engine.GetPlayerID()].cheatsEnabled) { for each (var cheat in Object.keys(cheats)) { // Line must start with the cheat. if (text.indexOf(cheat) !== 0) continue; var number; if (cheats[cheat].DefaultNumber !== undefined) { // Match the first word in the substring. var match = text.substr(cheat.length).match(/\S+/); if (match && match[0]) number = Math.floor(match[0]); if (number <= 0 || isNaN(number)) number = cheats[cheat].DefaultNumber; } Engine.PostNetworkCommand({ "type": "cheat", "action": cheats[cheat].Action, "number": number, "text": cheats[cheat].Type, "selected": g_Selection.toList(), "templates": cheats[cheat].Templates, "player": Engine.GetPlayerID()}); isCheat = true; break; } } if (!isCheat) { if (getGUIObjectByName("toggleTeamChat").checked) text = "/team " + text; if (g_IsNetworked) Engine.SendNetworkChat(text); else addChatMessage({ "type": "message", "guid": "local", "text": text }); } input.caption = ""; // Clear chat input } input.blur(); // Remove focus toggleChatWindow(); } function addChatMessage(msg, playerAssignments) { // Default to global assignments, but allow overriding for when reporting // new players joining if (!playerAssignments) playerAssignments = g_PlayerAssignments; var playerColor, username; // No prefix by default. May be set by parseChatCommands(). msg.prefix = ""; if (playerAssignments[msg.guid]) { var n = playerAssignments[msg.guid].player; playerColor = g_Players[n].color.r + " " + g_Players[n].color.g + " " + g_Players[n].color.b; username = escapeText(playerAssignments[msg.guid].name); // Parse in-line commands in regular messages. if (msg.type == "message") parseChatCommands(msg, playerAssignments); } else if (msg.type == "defeat" && msg.player) { [username, playerColor] = getUsernameAndColor(msg.player); } else if (msg.type == "message") { [username, playerColor] = getUsernameAndColor(msg.player); parseChatCommands(msg, playerAssignments); } else { playerColor = "255 255 255"; username = "Unknown player"; } var message = escapeText(msg.text); var formatted; switch (msg.type) { case "connect": formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has joined the game."; break; case "disconnect": formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has left the game."; break; case "defeat": // In singleplayer, the local player is "You". "You has" is incorrect. var verb = (!g_IsNetworked && msg.player == Engine.GetPlayerID()) ? "have" : "has"; formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] " + verb + " been defeated."; break; case "diplomacy": var status = (msg.status == "ally" ? "allied" : (msg.status == "enemy" ? "at war" : "neutral")); if (msg.player == Engine.GetPlayerID()) { [username, playerColor] = getUsernameAndColor(msg.player1); formatted = "You are now "+status+" with [color=\"" + playerColor + "\"]"+username + "[/color]."; } else if (msg.player1 == Engine.GetPlayerID()) { [username, playerColor] = getUsernameAndColor(msg.player); formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] is now " + status + " with you." } else // No need for other players to know of this. return; break; case "tribute": if (msg.player != Engine.GetPlayerID()) return; [username, playerColor] = getUsernameAndColor(msg.player1); // Format the amounts to proper English: 200 food, 100 wood, and 300 metal; 100 food; 400 wood and 200 stone var amounts = Object.keys(msg.amounts) .filter(function (type) { return msg.amounts[type] > 0; }) .map(function (type) { return msg.amounts[type] + " " + type; }); if (amounts.length > 1) { var lastAmount = amounts.pop(); amounts = amounts.join(", ") + " and " + lastAmount; } formatted = "[color=\"" + playerColor + "\"]" + username + "[/color] has sent you " + amounts + "."; break; case "attack": if (msg.player != Engine.GetPlayerID()) return; [username, playerColor] = getUsernameAndColor(msg.attacker); formatted = "You have been attacked by [color=\"" + playerColor + "\"]" + username + "[/color]!"; break; case "message": // May have been hidden by the 'team' command. if (msg.hide) return; if (msg.action) { Engine.Console_Write(msg.prefix + "* " + username + " " + message); formatted = msg.prefix + "* [color=\"" + playerColor + "\"]" + username + "[/color] " + message; } else { Engine.Console_Write(msg.prefix + "<" + username + "> " + message); formatted = msg.prefix + "<[color=\"" + playerColor + "\"]" + username + "[/color]> " + message; } break; default: error("Invalid chat message '" + uneval(msg) + "'"); return; } chatMessages.push(formatted); chatTimers.push(setTimeout(removeOldChatMessages, CHAT_TIMEOUT)); if (chatMessages.length > MAX_NUM_CHAT_LINES) removeOldChatMessages(); else getGUIObjectByName("chatText").caption = chatMessages.join("\n"); } function removeOldChatMessages() { clearTimeout(chatTimers[0]); // The timer only needs to be cleared when new messages bump old messages off chatTimers.shift(); chatMessages.shift(); getGUIObjectByName("chatText").caption = chatMessages.join("\n"); } // Parses chat messages for commands. function parseChatCommands(msg, playerAssignments) { // Only interested in messages that start with '/'. if (!msg.text || msg.text[0] != '/') return; var sender; if (playerAssignments[msg.guid]) sender = playerAssignments[msg.guid].player; else sender = msg.player; var recurse = false; var split = msg.text.split(/\s/); // Parse commands embedded in the message. switch (split[0]) { case "/all": // Resets values that 'team' or 'enemy' may have set. msg.prefix = ""; msg.hide = false; recurse = true; break; case "/team": // Check if we are in a team. if (g_Players[Engine.GetPlayerID()] && g_Players[Engine.GetPlayerID()].team != -1) { if (g_Players[Engine.GetPlayerID()].team != g_Players[sender].team) msg.hide = true; else msg.prefix = "(Team) "; } else msg.hide = true; recurse = true; break; case "/enemy": // Check if we are in a team. if (g_Players[Engine.GetPlayerID()] && g_Players[Engine.GetPlayerID()].team != -1) { if (g_Players[Engine.GetPlayerID()].team == g_Players[sender].team && sender != Engine.GetPlayerID()) msg.hide = true; else msg.prefix = "(Enemy) "; } recurse = true; break; case "/me": msg.action = true; break; case "/msg": var trimmed = msg.text.substr(split[0].length + 1); var matched = ""; // Reject names which don't match or are a superset of the intended name. for each (var player in playerAssignments) if (trimmed.indexOf(player.name + " ") == 0 && player.name.length > matched.length) matched = player.name; // If the local player's name was the longest one matched, show the message. var playerName = g_Players[Engine.GetPlayerID()].name; if (matched.length && (matched == playerName || sender == Engine.GetPlayerID())) { msg.prefix = "(Private) "; msg.text = trimmed.substr(matched.length + 1); msg.hide = false; // Might override team message hiding. return; } else msg.hide = true; break; default: return; } msg.text = msg.text.substr(split[0].length + 1); // Hide the message if parsing commands left it empty. if (!msg.text.length) msg.hide = true; // Attempt to parse more commands if the current command allows it. if (recurse) parseChatCommands(msg, playerAssignments); } Index: ps/trunk/binaries/data/mods/public/gui/session/session.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14417) +++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 14418) @@ -1,893 +1,894 @@ // 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"; // List of additional entities to highlight var g_ShowGuarding = false; var g_ShowGuarded = false; var g_AdditionalHighlight = []; function GetSimState() { if (!g_SimState) g_SimState = Engine.GuiInterfaceCall("GetSimulationState"); return g_SimState; } function GetEntityState(entId) { if (!(entId in g_EntityStates)) { var entState = Engine.GuiInterfaceCall("GetEntityState", entId); if (entState) entState.extended = false; g_EntityStates[entId] = entState; } return g_EntityStates[entId]; } function GetExtendedEntityState(entId) { if (entId in g_EntityStates) var entState = g_EntityStates[entId]; else var entState = Engine.GuiInterfaceCall("GetEntityState", entId); if (!entState || entState.extended) return entState; var extension = Engine.GuiInterfaceCall("GetExtendedEntityState", entId); for (var prop in extension) entState[prop] = extension[prop]; entState.extended = true; g_EntityStates[entId] = entState; return entState; } // Cache TemplateData var g_TemplateData = {}; // {id:template} 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 && 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) { g_Selection.dirty = false; 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_EntityStates = {}; g_TemplateData = {}; g_TechnologyData = {}; g_SimState = Engine.GuiInterfaceCall("GetSimulationState"); // If we're called during init when the game is first loading, there will be no simulation yet, so do nothing if (!g_SimState) return; handleNotifications(); if (g_ShowAllStatusBars) recalculateStatusBarDisplay(); if (g_ShowGuarding || g_ShowGuarded) updateAdditionalHighlight(); updateHero(); updateGroups(); updateDebug(); updatePlayerDisplay(); updateSelectionDetails(); updateResearchDisplay(); updateBuildingPlacementPreview(); updateTimeElapsedCounter(); + updateTimeNotifications(); // 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 = GetExtendedEntityState(playerState.heroes[0]); var template = GetTemplateData(heroState.template); heroImage.sprite = "stretched:session/portraits/" + template.icon; var hero = playerState.heroes[0]; heroButton.onpress = function() { if (!Engine.HotkeyIsPressed("selection.add")) g_Selection.reset(); g_Selection.addList([hero]); }; heroButton.ondoublepress = function() { selectAndMoveTo(getEntityOrHolder(hero)); }; 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 = GetExtendedEntityState(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 }); } // Update the additional list of entities to be highlighted. function updateAdditionalHighlight() { var entsAdd = []; // list of entities units to be highlighted var entsRemove = []; var highlighted = g_Selection.toList(); for each (var ent in g_Selection.highlighted) highlighted.push(ent); if (g_ShowGuarding) { // flag the guarding entities to add in this additional highlight for each (var sel in g_Selection.selected) { var state = GetEntityState(sel); if (!state.guard || !state.guard.entities.length) continue; for each (var ent in state.guard.entities) if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1) entsAdd.push(ent); } } if (g_ShowGuarded) { // flag the guarded entities to add in this additional highlight for each (var sel in g_Selection.selected) { var state = GetEntityState(sel); if (!state.unitAI || !state.unitAI.isGuarding) continue; var ent = state.unitAI.isGuarding; if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1) entsAdd.push(ent); } } // flag the entities to remove (from the previously added) from this additional highlight for each (var ent in g_AdditionalHighlight) if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1) entsRemove.push(ent); _setHighlight(entsAdd , HIGHLIGHTED_ALPHA, true ); _setHighlight(entsRemove, 0 , false); g_AdditionalHighlight = entsAdd; } // Temporarily adding this here const AMBIENT_TEMPERATE = "temperate"; var currentAmbient; function playRandomAmbient(type) { switch (type) { case AMBIENT_TEMPERATE: // 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 14417) +++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 14418) @@ -1,1329 +1,1333 @@