Index: ps/trunk/binaries/data/mods/public/gui/common/styles.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/styles.xml (revision 19598)
+++ ps/trunk/binaries/data/mods/public/gui/common/styles.xml (revision 19599)
@@ -1,207 +1,220 @@
+
+
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 19598)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 19599)
@@ -1,2152 +1,2172 @@
const g_MatchSettings_SP = "config/matchsettings.json";
const g_MatchSettings_MP = "config/matchsettings.mp.json";
const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire);
const g_GameSpeeds = prepareForDropdown(g_Settings && g_Settings.GameSpeeds.filter(speed => !speed.ReplayOnly));
const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities);
const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources);
const g_VictoryConditions = prepareForDropdown(g_Settings && g_Settings.VictoryConditions);
const g_VictoryDurations = prepareForDropdown(g_Settings && g_Settings.VictoryDurations);
/**
* Highlight the "random" dropdownlist item.
*/
var g_ColorRandom = "orange";
/**
* Color for regular dropdownlist items.
*/
var g_ColorRegular = "white";
/**
* Color for "Unassigned"-placeholder item in the dropdownlist.
*/
var g_PlayerAssignmentColors = {
"player": g_ColorRegular,
"observer": "170 170 250",
"unassigned": "140 140 140",
"AI": "70 150 70"
};
/**
* Used for highlighting the sender of chat messages.
*/
var g_SenderFont = "sans-bold-13";
/**
* This yields [1, 2, ..., MaxPlayers].
*/
var g_NumPlayersList = Array(g_MaxPlayers).fill(0).map((v, i) => i + 1);
/**
* Used for generating the botnames.
*/
var g_RomanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
var g_PlayerTeamList = prepareForDropdown([{
"label": translateWithContext("team", "None"),
"id": -1
}].concat(
Array(g_MaxTeams).fill(0).map((v, i) => ({
"label": i + 1,
"id": i
}))
)
);
/**
* Offer users to select playable civs only.
* Load unselectable civs as they could appear in scenario maps.
*/
var g_CivData = loadCivData();
/**
* Number of relics: [1, ..., NumCivs]
*/
var g_RelicCountList = Object.keys(g_CivData).map((civ, i) => i + 1);
var g_PlayerCivList = g_CivData && prepareForDropdown([{
"name": translateWithContext("civilization", "Random"),
"color": g_ColorRandom,
"code": "random"
}].concat(
Object.keys(g_CivData).filter(
civ => g_CivData[civ].SelectableInGameSetup
).map(civ => ({
"name": g_CivData[civ].Name,
"color": g_ColorRegular,
"code": civ
})).sort(sortNameIgnoreCase)
)
);
/**
* All selectable playercolors except gaia.
*/
var g_PlayerColorPickerList = g_Settings && g_Settings.PlayerDefaults.slice(1).map(pData => pData.Color);
/**
* Directory containing all maps of the given type.
*/
var g_MapPath = {
"random": "maps/random/",
"scenario": "maps/scenarios/",
"skirmish": "maps/skirmishes/"
};
/**
* Containing the colors to highlight the ready status of players,
* the chat ready messages and
* the tooltips and captions for the ready button
*/
var g_ReadyData = [
{
"color": g_ColorRegular,
"chat": translate("* %(username)s is not ready."),
"caption": translate("I'm ready"),
"tooltip": translate("State that you are ready to play.")
},
{
"color": "green",
"chat": translate("* %(username)s is ready!"),
"caption": translate("Stay ready"),
"tooltip": translate("Stay ready even when the game settings change.")
},
{
"color": "150 150 250",
"chat": "",
"caption": translate("I'm not ready!"),
"tooltip": translate("State that you are not ready to play.")
}
];
/**
* Processes a CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
*/
var g_NetMessageTypes = {
"netstatus": msg => handleNetStatusMessage(msg),
"netwarn": msg => addNetworkWarning(msg),
"gamesetup": msg => handleGamesetupMessage(msg),
"players": msg => handlePlayerAssignmentMessage(msg),
"ready": msg => handleReadyMessage(msg),
"start": msg => handleGamestartMessage(msg),
"kicked": msg => addChatMessage({
"type": msg.banned ? "banned" : "kicked",
"username": msg.username
}),
"chat": msg => addChatMessage({ "type": "chat", "guid": msg.guid, "text": msg.text }),
};
var g_FormatChatMessage = {
"system": (msg, user) => systemMessage(msg.text),
"settings": (msg, user) => systemMessage(translate('Game settings have been changed')),
"connect": (msg, user) => systemMessage(sprintf(translate("%(username)s has joined"), { "username": user })),
"disconnect": (msg, user) => systemMessage(sprintf(translate("%(username)s has left"), { "username": user })),
"kicked": (msg, user) => systemMessage(sprintf(translate("%(username)s has been kicked"), { "username": user })),
"banned": (msg, user) => systemMessage(sprintf(translate("%(username)s has been banned"), { "username": user })),
"chat": (msg, user) => sprintf(translate("%(username)s %(message)s"), {
"username": senderFont(sprintf(translate("<%(username)s>"), { "username": user })),
"message": escapeText(msg.text || "")
}),
"ready": (msg, user) => sprintf(g_ReadyData[msg.status].chat, { "username": user }),
"clientlist": (msg, user) => getUsernameList(),
};
var g_MapFilterList = prepareForDropdown([
{
"id": "default",
"name": translateWithContext("map filter", "Default"),
"filter": mapKeywords => mapKeywords.every(keyword => ["naval", "demo", "hidden"].indexOf(keyword) == -1),
"Default": true
},
{
"id": "naval",
"name": translate("Naval Maps"),
"filter": mapKeywords => mapKeywords.indexOf("naval") != -1
},
{
"id": "demo",
"name": translate("Demo Maps"),
"filter": mapKeywords => mapKeywords.indexOf("demo") != -1
},
{
"id": "new",
"name": translate("New Maps"),
"filter": mapKeywords => mapKeywords.indexOf("new") != -1
},
{
"id": "trigger",
"name": translate("Trigger Maps"),
"filter": mapKeywords => mapKeywords.indexOf("trigger") != -1
},
{
"id": "all",
"name": translate("All Maps"),
"filter": mapKeywords => true
},
]);
/**
* Whether this is a single- or multiplayer match.
*/
var g_IsNetworked;
/**
* Is this user in control of game settings (i.e. singleplayer or host of a multiplayergame).
*/
var g_IsController;
/**
+ * Whether this is a tutorial.
+ */
+var g_IsTutorial;
+
+/**
* To report the game to the lobby bot.
*/
var g_ServerName;
var g_ServerPort;
/**
* States whether the GUI is currently updated in response to network messages instead of user input
* and therefore shouldn't send further messages to the network.
*/
var g_IsInGuiUpdate = false;
/**
* Whether the current player is ready to start the game.
* 0 - not ready
* 1 - ready
* 2 - stay ready
*/
var g_IsReady = 0;
/**
* Ignore duplicate ready commands on init.
*/
var g_ReadyInit = true;
/**
* If noone has changed the ready status, we have no need to spam the settings changed message.
*
* <=0 - Suppressed settings message
* 1 - Will show settings message
* 2 - Host's initial ready, suppressed settings message
*/
var g_ReadyChanged = 2;
/**
* Used to prevent calling resetReadyData when starting a game.
*/
var g_GameStarted = false;
/**
* Selectable options (player, AI, unassigned) in the player assignment dropdowns and
* their colorized, textual representation.
*/
var g_PlayerAssignmentList = {};
/**
* Remembers which clients are assigned to which player slots and whether they are ready.
* The keys are guids or "local" in Singleplayer.
*/
var g_PlayerAssignments = {};
var g_DefaultPlayerData = [];
var g_GameAttributes = { "settings": {} };
/**
* List of translated words that can be used to autocomplete titles of settings
* and their values (for example playernames).
*/
var g_Autocomplete = [];
/**
* Array of strings formatted as displayed, including playername.
*/
var g_ChatMessages = [];
/**
* Filename and translated title of all maps, given the currently selected
* maptype and filter. Sorted by title, shown in the dropdown.
*/
var g_MapSelectionList = [];
/**
* Cache containing the mapsettings. Just-in-time loading.
*/
var g_MapData = {};
/**
* Wait one tick before initializing the GUI objects and
* don't process netmessages prior to that.
*/
var g_LoadingState = 0;
/**
* Only send a lobby update if something actually changed.
*/
var g_LastGameStanza;
/**
* Remembers if the current player viewed the AI settings of some playerslot.
*/
var g_LastViewedAIPlayer = -1;
/**
* Order in which the GUI elements will be shown.
* All valid options are required to appear here.
* The ones under "map" are shown in the map selection panel,
* the others in the "more options" dialog.
*/
var g_OptionOrderGUI = {
"map": {
"Dropdown": [
"mapType",
"mapFilter",
"mapSelection",
"numPlayers",
"mapSize",
],
"Checkbox": [
],
},
"more": {
"Dropdown": [
"gameSpeed",
"victoryCondition",
"relicCount",
"victoryDuration",
"populationCap",
"startingResources",
"ceasefire",
],
"Checkbox": [
"exploreMap",
"revealMap",
"disableTreasures",
"disableSpies",
"lockTeams",
"lastManStanding",
"enableCheats",
"enableRating",
]
}
};
/**
* These options must be initialized first, in the given order.
*/
var g_OptionOrderInit = {
"dropdowns": [
"mapType",
"mapFilter",
"mapSelection"
],
"checkboxes": [
]
};
/**
* Contains the logic of all multiple-choice gamesettings.
*
* Logic
* ids - Array of identifier strings that indicate the selected value.
* default - Returns the index of the default value (not the value itself).
* defined - Whether a value for the setting is actually specified.
* get - The identifier of the currently selected value.
* select - Saves and processes the value of the selected index of the ids array.
*
* GUI
* title - The caption shown in the label.
* tooltip - A description shown when hovering the dropdown or a specific item.
* labels - Array of translated strings selectable for this dropdown.
* colors - Optional array of colors to tint the according dropdown items with.
* hidden - If hidden, both the label and dropdown won't be visible. (default: false)
* enabled - Only the label will be shown if the setting is disabled. (default: true)
* autocomplete - Whether to autocomplete translated values of the string. (default: false)
* If disabled, still autocompletes the translated title of the setting.
*/
var g_Dropdowns = {
"mapType": {
"title": () => translate("Map Type"),
"tooltip": (hoverIdx) => g_MapTypes.Description[hoverIdx] || translate("Select a map type."),
"labels": () => g_MapTypes.Title,
"ids": () => g_MapTypes.Name,
"default": () => g_MapTypes.Default,
"defined": () => g_GameAttributes.mapType !== undefined,
"get": () => g_GameAttributes.mapType,
"select": (idx) => {
g_MapData = {};
g_GameAttributes.mapType = g_MapTypes.Name[idx];
g_GameAttributes.mapPath = g_MapPath[g_GameAttributes.mapType];
delete g_GameAttributes.map;
if (g_GameAttributes.mapType != "scenario")
g_GameAttributes.settings = {
"PlayerData": g_DefaultPlayerData.slice(0, 4)
};
reloadMapList();
},
"autocomplete": true,
},
"mapFilter": {
"title": () => translate("Map Filter"),
"tooltip": (hoverIdx) => translate("Select a map filter."),
"labels": () => g_MapFilterList.name,
"ids": () => g_MapFilterList.id,
"default": () => g_MapFilterList.Default,
"defined": () => g_GameAttributes.mapFilter !== undefined,
"get": () => g_GameAttributes.mapFilter,
"select": (idx) => {
g_GameAttributes.mapFilter = g_MapFilterList.id[idx];
delete g_GameAttributes.map;
reloadMapList();
},
"autocomplete": true,
},
"mapSelection": {
"title": () => translate("Select Map"),
"tooltip": (hoverIdx) => translate("Select a map to play on."),
"labels": () => g_MapSelectionList.name,
"colors": () => g_MapSelectionList.color,
"ids": () => g_MapSelectionList.file,
"default": () => 0,
"defined": () => g_GameAttributes.map !== undefined,
"get": () => g_GameAttributes.map,
"select": (idx) => {
selectMap(g_MapSelectionList.file[idx]);
},
"autocomplete": true,
},
"mapSize": {
"title": () => translate("Map Size"),
"tooltip": (hoverIdx) => translate("Select map size. (Larger sizes may reduce performance.)"),
"labels": () => g_MapSizes.Name,
"ids": () => g_MapSizes.Tiles,
"default": () => g_MapSizes.Default,
"defined": () => g_GameAttributes.settings.Size !== undefined,
"get": () => g_GameAttributes.settings.Size,
"select": (idx) => {
g_GameAttributes.settings.Size = g_MapSizes.Tiles[idx];
},
"hidden": () => g_GameAttributes.mapType != "random",
"autocomplete": true,
},
"numPlayers": {
"title": () => translate("Number of Players"),
"tooltip": (hoverIdx) => translate("Select number of players."),
"labels": () => g_NumPlayersList,
"ids": () => g_NumPlayersList,
"default": () => g_MaxPlayers - 1,
"defined": () => g_GameAttributes.settings.PlayerData !== undefined,
"get": () => g_GameAttributes.settings.PlayerData.length,
"enabled": () => g_GameAttributes.mapType == "random",
"select": (idx) => {
let num = idx + 1;
let pData = g_GameAttributes.settings.PlayerData;
g_GameAttributes.settings.PlayerData =
num > pData.length ?
pData.concat(g_DefaultPlayerData.slice(pData.length, num)) :
pData.slice(0, num);
unassignInvalidPlayers(num);
sanitizePlayerData(g_GameAttributes.settings.PlayerData);
},
},
"populationCap": {
"title": () => translate("Population Cap"),
"tooltip": (hoverIdx) => translate("Select population cap."),
"labels": () => g_PopulationCapacities.Title,
"ids": () => g_PopulationCapacities.Population,
"default": () => g_PopulationCapacities.Default,
"defined": () => g_GameAttributes.settings.PopulationCap !== undefined,
"get": () => g_GameAttributes.settings.PopulationCap,
"select": (idx) => {
g_GameAttributes.settings.PopulationCap = g_PopulationCapacities.Population[idx];
},
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"startingResources": {
"title": () => translate("Starting Resources"),
"tooltip": (hoverIdx) => {
return hoverIdx >= 0 ?
sprintf(translate("Initial amount of each resource: %(resources)s."), {
"resources": g_StartingResources.Resources[hoverIdx]
}) :
translate("Select the game's starting resources.");
},
"labels": () => g_StartingResources.Title,
"ids": () => g_StartingResources.Resources,
"default": () => g_StartingResources.Default,
"defined": () => g_GameAttributes.settings.StartingResources !== undefined,
"get": () => g_GameAttributes.settings.StartingResources,
"select": (idx) => {
g_GameAttributes.settings.StartingResources = g_StartingResources.Resources[idx];
},
"hidden": () => g_GameAttributes.mapType == "scenario",
"autocomplete": true,
},
"ceasefire": {
"title": () => translate("Ceasefire"),
"tooltip": (hoverIdx) => translate("Set time where no attacks are possible."),
"labels": () => g_Ceasefire.Title,
"ids": () => g_Ceasefire.Duration,
"default": () => g_Ceasefire.Default,
"defined": () => g_GameAttributes.settings.Ceasefire !== undefined,
"get": () => g_GameAttributes.settings.Ceasefire,
"select": (idx) => {
g_GameAttributes.settings.Ceasefire = g_Ceasefire.Duration[idx];
},
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"victoryCondition": {
"title": () => translate("Victory Condition"),
"tooltip": (hoverIdx) => g_VictoryConditions.Description[hoverIdx] || translate("Select victory condition."),
"labels": () => g_VictoryConditions.Title,
"ids": () => g_VictoryConditions.Name,
"default": () => g_VictoryConditions.Default,
"defined": () => g_GameAttributes.settings.GameType !== undefined,
"get": () => g_GameAttributes.settings.GameType,
"select": (idx) => {
g_GameAttributes.settings.GameType = g_VictoryConditions.Name[idx];
g_GameAttributes.settings.VictoryScripts = g_VictoryConditions.Scripts[idx];
},
"enabled": () => g_GameAttributes.mapType != "scenario",
"autocomplete": true,
},
"relicCount": {
"title": () => translate("Relic Count"),
"tooltip": (hoverIdx) => translate("Total number of relics spawned on the map."),
"labels": () => g_RelicCountList,
"ids": () => g_RelicCountList,
"default": () => g_RelicCountList.indexOf(5),
"defined": () => g_GameAttributes.settings.RelicCount !== undefined,
"get": () => g_GameAttributes.settings.RelicCount,
"select": (idx) => {
g_GameAttributes.settings.RelicCount = g_RelicCountList[idx];
},
"hidden": () => g_GameAttributes.settings.GameType != "capture_the_relic",
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"victoryDuration": {
"title": () => translate("Victory Duration"),
"tooltip": (hoverIdx) => translate("Number of minutes until the player has won."),
"labels": () => g_VictoryDurations.Title,
"ids": () => g_VictoryDurations.Duration,
"default": () => g_VictoryDurations.Default,
"defined": () => g_GameAttributes.settings.VictoryDuration !== undefined,
"get": () => g_GameAttributes.settings.VictoryDuration,
"select": (idx) => {
g_GameAttributes.settings.VictoryDuration = g_VictoryDurations.Duration[idx];
},
"hidden": () =>
g_GameAttributes.settings.GameType != "wonder" &&
g_GameAttributes.settings.GameType != "capture_the_relic",
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"gameSpeed": {
"title": () => translate("Game Speed"),
"tooltip": (hoverIdx) => translate("Select game speed."),
"labels": () => g_GameSpeeds.Title,
"ids": () => g_GameSpeeds.Speed,
"default": () => g_GameSpeeds.Default,
"defined": () => g_GameAttributes.gameSpeed !== undefined,
"get": () => g_GameAttributes.gameSpeed,
"select": (idx) => {
g_GameAttributes.gameSpeed = g_GameSpeeds.Speed[idx];
},
},
};
/**
* These dropdowns provide a setting that is repeated once for each player
* (where idx is the playerID starting from 0 for player 1).
*/
var g_PlayerDropdowns = {
"playerAssignment": {
"labels": (idx) => g_PlayerAssignmentList.Name || [],
"colors": (idx) => g_PlayerAssignmentList.Color || [],
"ids": (idx) => g_PlayerAssignmentList.Choice || [],
"default": (idx) => "ai:petra",
"defined": (idx) => idx < g_GameAttributes.settings.PlayerData.length,
"get": (idx) => {
for (let guid in g_PlayerAssignments)
if (g_PlayerAssignments[guid].player == idx + 1)
return "guid:" + guid;
for (let ai of g_Settings.AIDescriptions)
if (g_GameAttributes.settings.PlayerData[idx].AI == ai.id)
return "ai:" + ai.id;
return "unassigned";
},
"select": (selectedIdx, idx) => {
let choice = g_PlayerAssignmentList.Choice[selectedIdx];
if (choice == "unassigned" || choice.startsWith("ai:"))
{
if (g_IsNetworked)
Engine.AssignNetworkPlayer(idx+1, "");
else if (g_PlayerAssignments.local.player == idx+1)
g_PlayerAssignments.local.player = -1;
g_GameAttributes.settings.PlayerData[idx].AI = choice.startsWith("ai:") ? choice.substr(3) : "";
}
else
swapPlayers(choice.substr("guid:".length), idx);
},
"autocomplete": true,
},
"playerTeam": {
"labels": (idx) => g_PlayerTeamList.label,
"ids": (idx) => g_PlayerTeamList.id,
"default": (idx) => 0,
"defined": (idx) => g_GameAttributes.settings.PlayerData[idx].Team !== undefined,
"get": (idx) => g_GameAttributes.settings.PlayerData[idx].Team,
"select": (selectedIdx, idx) => {
g_GameAttributes.settings.PlayerData[idx].Team = selectedIdx - 1;
},
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"playerCiv": {
"labels": (idx) => g_PlayerCivList.name,
"colors": (idx) => g_PlayerCivList.color,
"ids": (idx) => g_PlayerCivList.code,
"default": (idx) => 0,
"defined": (idx) => g_GameAttributes.settings.PlayerData[idx].Civ !== undefined,
"get": (idx) => g_GameAttributes.settings.PlayerData[idx].Civ,
"select": (selectedIdx, idx) => {
g_GameAttributes.settings.PlayerData[idx].Civ = g_PlayerCivList.code[selectedIdx];
},
"enabled": () => g_GameAttributes.mapType != "scenario",
"autocomplete": true,
},
"playerColorPicker": {
"labels": (idx) => g_PlayerColorPickerList.map(color => "■"),
"colors": (idx) => g_PlayerColorPickerList.map(color => rgbToGuiColor(color)),
"ids": (idx) => g_PlayerColorPickerList.map((color, index) => index),
"default": (idx) => idx,
"defined": (idx) => g_GameAttributes.settings.PlayerData[idx].Color !== undefined,
"get": (idx) => g_GameAttributes.settings.PlayerData[idx].Color,
"select": (selectedIdx, idx) => {
let playerData = g_GameAttributes.settings.PlayerData;
// If someone else has that color, give that player the old color
let pData = playerData.find(pData => sameColor(g_PlayerColorPickerList[selectedIdx], pData.Color));
if (pData)
pData.Color = playerData[idx].Color;
playerData[idx].Color = g_PlayerColorPickerList[selectedIdx];
ensureUniquePlayerColors(playerData);
},
"enabled": () => g_GameAttributes.mapType != "scenario",
},
};
/**
* Contains the logic of all boolean gamesettings.
*/
var g_Checkboxes = {
"revealMap": {
"title": () =>
// Translation: Make sure to differentiate between the revealed map and explored map options!
translate("Revealed Map"),
"tooltip":
// Translation: Make sure to differentiate between the revealed map and explored map options!
() => translate("Toggle revealed map (see everything)."),
"default": () => false,
"defined": () => g_GameAttributes.settings.RevealMap !== undefined,
"get": () => g_GameAttributes.settings.RevealMap,
"set": checked => {
g_GameAttributes.settings.RevealMap = checked;
if (checked)
g_Checkboxes.exploreMap.set(true);
},
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"exploreMap": {
"title":
// Translation: Make sure to differentiate between the revealed map and explored map options!
() => translate("Explored Map"),
"tooltip":
// Translation: Make sure to differentiate between the revealed map and explored map options!
() => translate("Toggle explored map (see initial map)."),
"default": () => false,
"defined": () => g_GameAttributes.settings.ExploreMap !== undefined,
"get": () => g_GameAttributes.settings.ExploreMap,
"set": checked => {
g_GameAttributes.settings.ExploreMap = checked;
},
"enabled": () => g_GameAttributes.mapType != "scenario" && !g_GameAttributes.settings.RevealMap,
},
"disableTreasures": {
"title": () => translate("Disable Treasures"),
"tooltip": () => translate("Disable all treasures on the map."),
"default": () => false,
"defined": () => g_GameAttributes.settings.DisableTreasures !== undefined,
"get": () => g_GameAttributes.settings.DisableTreasures,
"set": checked => {
g_GameAttributes.settings.DisableTreasures = checked;
},
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"disableSpies": {
"title": () => translate("Disable Spies"),
"tooltip": () => translate("Disable spies during the game."),
"default": () => false,
"defined": () => g_GameAttributes.settings.DisableSpies !== undefined,
"get": () => g_GameAttributes.settings.DisableSpies,
"set": checked => {
g_GameAttributes.settings.DisableSpies = checked;
},
"enabled": () => g_GameAttributes.mapType != "scenario",
},
"lockTeams": {
"title": () => translate("Teams Locked"),
"tooltip": () => translate("Toggle locked teams."),
"default": () => Engine.HasXmppClient(),
"defined": () => g_GameAttributes.settings.LockTeams !== undefined,
"get": () => g_GameAttributes.settings.LockTeams,
"set": checked => {
g_GameAttributes.settings.LockTeams = checked;
g_GameAttributes.settings.LastManStanding = false;
},
"enabled": () =>
g_GameAttributes.mapType != "scenario" &&
!g_GameAttributes.settings.RatingEnabled,
},
"lastManStanding": {
"title": () => translate("Last Man Standing"),
"tooltip": () => translate("Toggle whether the last remaining player or the last remaining set of allies wins."),
"default": () => false,
"defined": () => g_GameAttributes.settings.LastManStanding !== undefined,
"get": () => g_GameAttributes.settings.LastManStanding,
"set": checked => {
g_GameAttributes.settings.LastManStanding = checked;
},
"enabled": () =>
g_GameAttributes.mapType != "scenario" &&
!g_GameAttributes.settings.LockTeams,
},
"enableCheats": {
"title": () => translate("Cheats"),
"tooltip": () => translate("Toggle the usability of cheats."),
"default": () => !g_IsNetworked,
"hidden": () => !g_IsNetworked,
"defined": () => g_GameAttributes.settings.CheatsEnabled !== undefined,
"get": () => g_GameAttributes.settings.CheatsEnabled,
"set": checked => {
g_GameAttributes.settings.CheatsEnabled = !g_IsNetworked ||
checked && !g_GameAttributes.settings.RatingEnabled;
},
"enabled": () => !g_GameAttributes.settings.RatingEnabled,
},
"enableRating": {
"title": () => translate("Rated Game"),
"tooltip": () => translate("Toggle if this game will be rated for the leaderboard."),
"default": () => Engine.HasXmppClient(),
"hidden": () => !Engine.HasXmppClient(),
"defined": () => g_GameAttributes.settings.RatingEnabled !== undefined,
"get": () => !!g_GameAttributes.settings.RatingEnabled,
"set": checked => {
g_GameAttributes.settings.RatingEnabled = Engine.HasXmppClient() ? checked : undefined;
Engine.SetRankedGame(!!g_GameAttributes.settings.RatingEnabled);
if (checked)
{
g_Checkboxes.lockTeams.set(true);
g_Checkboxes.enableCheats.set(false);
}
},
},
};
/**
* For setting up arbitrary GUI objects.
*/
var g_MiscControls = {
"chatPanel": {
"hidden": () => !g_IsNetworked,
},
"chatInput": {
"tooltip": () => colorizeAutocompleteHotkey(translate("Press %(hotkey)s to autocomplete playernames or settings.")),
},
"cheatWarningText": {
"hidden": () => !g_IsNetworked || !g_GameAttributes.settings.CheatsEnabled,
},
"cancelGame": {
"tooltip": () =>
Engine.HasXmppClient() ?
translate("Return to the lobby.") :
translate("Return to the main menu."),
},
"startGame": {
"caption": () =>
g_IsController ? translate("Start game!") : g_ReadyData[g_IsReady].caption,
"tooltip": (hoverIdx) =>
!g_IsController ?
g_ReadyData[g_IsReady].tooltip :
!g_IsNetworked || Object.keys(g_PlayerAssignments).every(guid =>
g_PlayerAssignments[guid].status || g_PlayerAssignments[guid].player == -1) ?
translate("Start a new game with the current settings.") :
translate("Start a new game with the current settings (disabled until all players are ready)"),
"enabled": () => !g_IsController ||
Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].status ||
g_PlayerAssignments[guid].player == -1 ||
guid == Engine.GetPlayerGUID() && g_IsController),
"hidden": () =>
!g_PlayerAssignments[Engine.GetPlayerGUID()] ||
g_PlayerAssignments[Engine.GetPlayerGUID()].player == -1 && !g_IsController,
},
"civResetButton": {
"hidden": () => g_GameAttributes.mapType == "scenario" || !g_IsController,
},
"teamResetButton": {
"hidden": () => g_GameAttributes.mapType == "scenario" || !g_IsController,
},
// Display these after having hidden every GUI object in the "More Options" dialog
"moreOptionsLabel": {
"hidden": () => false,
},
"hideMoreOptions": {
"hidden": () => false,
},
};
/**
* Contains options that are repeated for every player.
*/
var g_PlayerMiscControls = {
"playerBox": {
"size": (idx) => ["0", 32 * idx, "100%", 32 * (idx + 1)].join(" "),
},
"playerName": {
"caption": (idx) => {
let pData = g_GameAttributes.settings.PlayerData[idx];
let assignedGUID = Object.keys(g_PlayerAssignments).find(
guid => g_PlayerAssignments[guid].player == idx + 1);
let name = translate(pData.Name || g_DefaultPlayerData[idx].Name);
if (g_IsNetworked)
name =
'[color="' +
g_ReadyData[assignedGUID ? g_PlayerAssignments[assignedGUID].status : 2].color +
'"]' + name + '[/color]';
return name;
},
},
"playerColor": {
"sprite": (idx) => "color:" + rgbToGuiColor(g_GameAttributes.settings.PlayerData[idx].Color) + " 100",
},
"playerConfig": {
"hidden": (idx) => !g_GameAttributes.settings.PlayerData[idx].AI,
"onPress": (idx) => function() {
openAIConfig(idx);
},
},
};
/**
* Initializes some globals without touching the GUI.
*
* @param {Object} attribs - context data sent by the lobby / mainmenu
*/
function init(attribs)
{
if (!g_Settings)
{
cancelSetup();
return;
}
if (["offline", "server", "client"].indexOf(attribs.type) == -1)
{
error("Unexpected 'type' in gamesetup init: " + attribs.type);
cancelSetup();
return;
}
g_IsNetworked = attribs.type != "offline";
g_IsController = attribs.type != "client";
+ g_IsTutorial = attribs.tutorial && attribs.tutorial == true;
g_ServerName = attribs.serverName;
g_ServerPort = attribs.serverPort;
if (!g_IsNetworked)
g_PlayerAssignments = {
"local": {
"name": singleplayerName(),
"player": 1
}
};
// Replace empty playername when entering a singleplayermatch for the first time
if (!g_IsNetworked)
{
Engine.ConfigDB_CreateValue("user", "playername.singleplayer", singleplayerName());
Engine.ConfigDB_WriteValueToFile("user", "playername.singleplayer", singleplayerName(), "config/user.cfg");
}
initDefaults();
supplementDefaults();
setTimeout(displayGamestateNotifications, 1000);
}
function initDefaults()
{
// Remove gaia from both arrays
g_DefaultPlayerData = g_Settings.PlayerDefaults;
g_DefaultPlayerData.shift();
// Don't change the underlying defaults file, as Atlas uses that file too
for (let i in g_DefaultPlayerData)
{
g_DefaultPlayerData[i].Civ = "random";
g_DefaultPlayerData[i].Teams = -1;
}
}
/**
* Sets default values for all g_GameAttribute settings which don't have a value set.
*/
function supplementDefaults()
{
for (let dropdown in g_Dropdowns)
if (!g_Dropdowns[dropdown].defined())
g_Dropdowns[dropdown].select(g_Dropdowns[dropdown].default());
for (let checkbox in g_Checkboxes)
if (!g_Checkboxes[checkbox].defined())
g_Checkboxes[checkbox].set(g_Checkboxes[checkbox].default());
for (let dropdown in g_PlayerDropdowns)
for (let i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i)
if (!isControlArrayElementHidden(i) && !g_PlayerDropdowns[dropdown].defined(i))
g_PlayerDropdowns[dropdown].select(g_PlayerDropdowns[dropdown].default(i), i);
}
/**
* Called after the first tick.
*/
function initGUIObjects()
{
for (let dropdown of g_OptionOrderInit.dropdowns)
initDropdown(dropdown);
for (let dropdown in g_Dropdowns)
if (g_OptionOrderInit.dropdowns.indexOf(dropdown) == -1)
initDropdown(dropdown);
for (let checkbox of g_OptionOrderInit.checkboxes)
initCheckbox(checkbox);
for (let checkbox in g_Checkboxes)
if (g_OptionOrderInit.checkboxes.indexOf(checkbox) == -1)
initCheckbox(checkbox);
for (let dropdown in g_PlayerDropdowns)
initPlayerDropdowns(dropdown);
resizeMoreOptionsWindow();
initSPTips();
loadPersistMatchSettings();
updateGameAttributes();
+ if (g_IsTutorial)
+ {
+ launchTutorial();
+ return;
+ }
+
Engine.GetGUIObjectByName("loadingWindow").hidden = true;
Engine.GetGUIObjectByName("setupWindow").hidden = false;
if (g_IsNetworked)
Engine.GetGUIObjectByName("chatInput").focus();
}
/**
* The main options (like map selection) and player arrays have specific names.
* Options in the "More Options" dialog use a generic name.
*/
function getGUIObjectNameFromSetting(name)
{
for (let panel in g_OptionOrderGUI)
for (let type in g_OptionOrderGUI[panel])
{
let idx = g_OptionOrderGUI[panel][type].indexOf(name);
if (idx != -1)
- return [panel + "Option" + type, "[" + idx + "]"]
+ return [panel + "Option" + type, "[" + idx + "]"];
}
// Assume there is a GUI object with exactly that setting name
return [name, ""];
}
function initDropdown(name, idx)
{
let [guiName, guiIdx] = getGUIObjectNameFromSetting(name);
let idxName = idx === undefined ? "": "[" + idx + "]";
let data = (idx === undefined ? g_Dropdowns : g_PlayerDropdowns)[name];
let dropdown = Engine.GetGUIObjectByName(guiName + guiIdx + idxName);
dropdown.list = data.labels(idx).map((label, id) =>
data.colors && data.colors(idx) ?
'[color="' + data.colors(idx)[id] + '"]' + label + "[/color]" :
label);
dropdown.list_data = data.ids(idx);
dropdown.onSelectionChange = function() {
if (!g_IsController ||
g_IsInGuiUpdate ||
!this.list_data[this.selected] ||
data.hidden && data.hidden(idx) ||
data.enabled && !data.enabled(idx))
return;
data.select(this.selected, idx);
supplementDefaults();
updateGameAttributes();
};
if (data.tooltip)
dropdown.onHoverChange = function() {
this.tooltip = data.tooltip(this.hovered);
};
}
function initPlayerDropdowns(name)
{
for (let i = 0; i < g_MaxPlayers; ++i)
initDropdown(name, i);
}
function initCheckbox(name)
{
let [guiName, guiIdx] = getGUIObjectNameFromSetting(name);
Engine.GetGUIObjectByName(guiName + guiIdx).onPress = function() {
let obj = g_Checkboxes[name];
if (!g_IsController ||
g_IsInGuiUpdate ||
obj.enabled && !obj.enabled() ||
obj.hidden && obj.hidden())
return;
obj.set(this.checked);
supplementDefaults();
updateGameAttributes();
};
}
function initSPTips()
{
if (g_IsNetworked || Engine.ConfigDB_GetValue("user", "gui.gamesetup.enabletips") !== "true")
return;
Engine.GetGUIObjectByName("spTips").hidden = false;
Engine.GetGUIObjectByName("displaySPTips").checked = true;
Engine.GetGUIObjectByName("aiTips").caption = Engine.TranslateLines(Engine.ReadFile("gui/gamesetup/ai.txt"));
}
function saveSPTipsSetting()
{
let enabled = String(Engine.GetGUIObjectByName("displaySPTips").checked);
Engine.ConfigDB_CreateValue("user", "gui.gamesetup.enabletips", enabled);
Engine.ConfigDB_WriteValueToFile("user", "gui.gamesetup.enabletips", enabled, "config/user.cfg");
}
function verticallyDistributeGUIObjects(parent, objectHeight, ignore)
{
let yPos = undefined;
let parentObject = Engine.GetGUIObjectByName(parent);
for (let child of parentObject.children)
{
if (ignore.indexOf(child.name) != -1)
continue;
let childSize = child.size;
yPos = yPos || childSize.top;
if (child.hidden)
continue;
childSize.top = yPos;
childSize.bottom = yPos + objectHeight - 2;
child.size = childSize;
yPos += objectHeight;
}
return yPos;
}
/**
* Remove empty space in case of hidden options (like cheats, rating or victory duration)
*/
function resizeMoreOptionsWindow()
{
verticallyDistributeGUIObjects("mapOptions", 32, []);
let yPos = verticallyDistributeGUIObjects("moreOptions", 32, ["moreOptionsLabel"]);
// Resize the vertically centered window containing the options
let moreOptions = Engine.GetGUIObjectByName("moreOptions");
let mSize = moreOptions.size;
mSize.bottom = mSize.top + yPos + 20;
moreOptions.size = mSize;
}
/**
* Called when the client disconnects.
* The other cases from NetClient should never occur in the gamesetup.
*/
function handleNetStatusMessage(message)
{
if (message.status != "disconnected")
{
error("Unrecognised netstatus type " + message.status);
return;
}
cancelSetup();
reportDisconnect(message.reason, true);
}
/**
* Called whenever a client clicks on ready (or not ready).
*/
function handleReadyMessage(message)
{
--g_ReadyChanged;
if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1)
addChatMessage({
"type": "ready",
"status": message.status,
"guid": message.guid
});
g_PlayerAssignments[message.guid].status = message.status;
updateGUIObjects();
}
/**
* Called after every player is ready and the host decided to finally start the game.
*/
function handleGamestartMessage(message)
{
// Immediately inform the lobby server instead of waiting for the load to finish
if (g_IsController && Engine.HasXmppClient())
{
let clients = formatClientsForStanza();
Engine.SendChangeStateGame(clients.connectedPlayers, clients.list);
}
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
"isNetworked" : g_IsNetworked,
"playerAssignments": g_PlayerAssignments,
"isController": g_IsController
});
}
/**
* Called whenever the host changed any setting.
*/
function handleGamesetupMessage(message)
{
if (!message.data)
return;
g_GameAttributes = message.data;
if (!!g_GameAttributes.settings.RatingEnabled)
{
g_GameAttributes.settings.CheatsEnabled = false;
g_GameAttributes.settings.LockTeams = true;
g_GameAttributes.settings.LastManStanding = false;
}
Engine.SetRankedGame(!!g_GameAttributes.settings.RatingEnabled);
resetReadyData();
updateGUIObjects();
}
/**
* Called whenever a client joins/leaves or any gamesetting is changed.
*/
function handlePlayerAssignmentMessage(message)
{
for (let guid in message.newAssignments)
if (!g_PlayerAssignments[guid])
onClientJoin(guid, message.newAssignments);
for (let guid in g_PlayerAssignments)
if (!message.newAssignments[guid])
onClientLeave(guid);
g_PlayerAssignments = message.newAssignments;
updateGUIObjects();
sendRegisterGameStanza();
}
function onClientJoin(newGUID, newAssignments)
{
addChatMessage({
"type": "connect",
"guid": newGUID,
"username": newAssignments[newGUID].name
});
let freeSlot = g_GameAttributes.settings.PlayerData.findIndex((v,i) =>
Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player != i+1)
);
// Client is not and cannot become assigned as player
if (newAssignments[newGUID].player == -1 && freeSlot == -1)
return;
// Assign the joining client to the free slot
if (g_IsController && newAssignments[newGUID].player == -1)
Engine.AssignNetworkPlayer(freeSlot + 1, newGUID);
g_GameAttributes.settings.PlayerData[freeSlot].AI = "";
g_GameAttributes.settings.PlayerData[freeSlot].AIDiff = g_DefaultPlayerData[freeSlot].AIDiff;
resetReadyData();
}
function onClientLeave(guid)
{
addChatMessage({
"type": "disconnect",
"guid": guid
});
if (g_PlayerAssignments[guid].player != -1)
resetReadyData();
}
/**
* Doesn't translate, so that lobby clients can do that locally
* (even if they don't have that map).
*/
function getMapDisplayName(map)
{
if (map == "random")
return map;
let mapData = loadMapData(map);
if (!mapData || !mapData.settings || !mapData.settings.Name)
return map;
return mapData.settings.Name;
}
function getMapPreview(map)
{
let mapData = loadMapData(map);
if (!mapData || !mapData.settings || !mapData.settings.Preview)
return "nopreview.png";
return mapData.settings.Preview;
}
/**
* Initialize the dropdown containing all maps for the selected maptype and mapfilter.
*/
function reloadMapList()
{
if (!g_MapPath[g_GameAttributes.mapType])
{
error("Unexpected map type: " + g_GameAttributes.mapType);
return;
}
let mapFiles = g_GameAttributes.mapType == "random" ?
getJSONFileList(g_GameAttributes.mapPath) :
getXMLFileList(g_GameAttributes.mapPath);
// Apply map filter, if any defined
let mapList = [];
// TODO: Should verify these are valid maps before adding to list
for (let mapFile of mapFiles)
{
let file = g_GameAttributes.mapPath + mapFile;
let mapData = loadMapData(file);
let filterID = g_MapFilterList.id.findIndex(id => id == g_GameAttributes.mapFilter);
let mapFilter = g_MapFilterList.filter[filterID] || undefined;
if (!mapData.settings || mapFilter && !mapFilter(mapData.settings.Keywords || []))
continue;
mapList.push({
"file": file,
"color": g_ColorRegular,
"name": translate(getMapDisplayName(file))
});
}
- mapList = mapList.sort(sortNameIgnoreCase)
+ mapList = mapList.sort(sortNameIgnoreCase);
if (g_GameAttributes.mapType == "random")
mapList.unshift({
"file": "random",
"name": translateWithContext("map selection", "Random"),
"color": g_ColorRandom
});
g_MapSelectionList = prepareForDropdown(mapList);
initDropdown("mapSelection");
}
function loadMapData(name)
{
if (!name || !g_MapPath[g_GameAttributes.mapType])
return undefined;
if (name == "random")
return { "settings": { "Name": "", "Description": "" } };
if (!g_MapData[name])
g_MapData[name] = g_GameAttributes.mapType == "random" ?
Engine.ReadJSONFile(name + ".json") :
Engine.LoadMapSettings(name);
return g_MapData[name];
}
/**
* Sets the gameattributes the way they were the last time the user left the gamesetup.
*/
function loadPersistMatchSettings()
{
- if (!g_IsController || Engine.ConfigDB_GetValue("user", "persistmatchsettings") != "true")
+ if (!g_IsController || Engine.ConfigDB_GetValue("user", "persistmatchsettings") != "true" || g_IsTutorial)
return;
let settingsFile = g_IsNetworked ? g_MatchSettings_MP : g_MatchSettings_SP;
if (!Engine.FileExists(settingsFile))
return;
let attrs = Engine.ReadJSONFile(settingsFile);
if (!attrs || !attrs.settings)
return;
g_IsInGuiUpdate = true;
let mapName = attrs.map || "";
let mapSettings = attrs.settings;
g_GameAttributes = attrs;
if (!g_IsNetworked)
mapSettings.CheatsEnabled = true;
// Replace unselectable civs with random civ
let playerData = mapSettings.PlayerData;
if (playerData && g_GameAttributes.mapType != "scenario")
for (let i in playerData)
if (!g_CivData[playerData[i].Civ] || !g_CivData[playerData[i].Civ].SelectableInGameSetup)
playerData[i].Civ = "random";
// Apply map settings
let newMapData = loadMapData(mapName);
if (newMapData && newMapData.settings)
{
for (let prop in newMapData.settings)
mapSettings[prop] = newMapData.settings[prop];
if (playerData)
mapSettings.PlayerData = playerData;
}
if (mapSettings.PlayerData)
sanitizePlayerData(mapSettings.PlayerData);
// Reload, as the maptype or mapfilter might have changed
reloadMapList();
g_GameAttributes.settings.RatingEnabled = Engine.HasXmppClient();
Engine.SetRankedGame(g_GameAttributes.settings.RatingEnabled);
supplementDefaults();
g_IsInGuiUpdate = false;
}
function savePersistMatchSettings()
{
+ if (g_IsTutorial)
+ return;
let attributes = Engine.ConfigDB_GetValue("user", "persistmatchsettings") == "true" ? g_GameAttributes : {};
Engine.WriteJSONFile(g_IsNetworked ? g_MatchSettings_MP : g_MatchSettings_SP, attributes);
}
function sanitizePlayerData(playerData)
{
// Remove gaia
if (playerData.length && !playerData[0])
playerData.shift();
playerData.forEach((pData, index) => {
// Use defaults if the map doesn't specify a value
for (let prop in g_DefaultPlayerData[index])
if (!(prop in pData))
pData[prop] = g_DefaultPlayerData[index][prop];
// Replace colors with the best matching color of PlayerDefaults
if (g_GameAttributes.mapType != "scenario")
{
let colorDistances = g_PlayerColorPickerList.map(color => colorDistance(color, pData.Color));
let smallestDistance = colorDistances.find(distance => colorDistances.every(distance2 => (distance2 >= distance)));
pData.Color = g_PlayerColorPickerList.find(color => colorDistance(color, pData.Color) == smallestDistance);
}
});
ensureUniquePlayerColors(playerData);
}
function cancelSetup()
{
if (g_IsController)
savePersistMatchSettings();
Engine.DisconnectNetworkGame();
if (Engine.HasXmppClient())
{
Engine.LobbySetPlayerPresence("available");
if (g_IsController)
Engine.SendUnregisterGame();
Engine.SwitchGuiPage("page_lobby.xml");
}
else
Engine.SwitchGuiPage("page_pregame.xml");
}
/**
* Can't init the GUI before the first tick.
* Process netmessages afterwards.
*/
function onTick()
{
if (!g_Settings)
return;
// First tick happens before first render, so don't load yet
if (g_LoadingState == 0)
++g_LoadingState;
else if (g_LoadingState == 1)
{
initGUIObjects();
++g_LoadingState;
}
else if (g_LoadingState == 2)
handleNetMessages();
updateTimers();
}
/**
* Handles all pending messages sent by the net client.
*/
function handleNetMessages()
{
while (g_IsNetworked)
{
let message = Engine.PollNetworkClient();
if (!message)
break;
log("Net message: " + uneval(message));
if (g_NetMessageTypes[message.type])
g_NetMessageTypes[message.type](message);
else
error("Unrecognised net message type " + message.type);
}
}
/**
* Called when the map or the number of players changes.
*/
function unassignInvalidPlayers(maxPlayers)
{
if (g_IsNetworked)
{
// Remove invalid playerIDs from the servers playerassignments copy
for (let playerID = +maxPlayers + 1; playerID <= g_MaxPlayers; ++playerID)
Engine.AssignNetworkPlayer(playerID, "");
}
else if (g_PlayerAssignments.local.player > maxPlayers)
g_PlayerAssignments.local.player = -1;
}
function ensureUniquePlayerColors(playerData)
{
for (let i = playerData.length - 1; i >= 0; --i)
// If someone else has that color, assign an unused color
if (playerData.some((pData, j) => i != j && sameColor(playerData[i].Color, pData.Color)))
playerData[i].Color = g_PlayerColorPickerList.find(color => playerData.every(pData => !sameColor(color, pData.Color)));
}
function selectMap(name)
{
// Reset some map specific properties which are not necessarily redefined on each map
for (let prop of ["TriggerScripts", "CircularMap", "Garrison", "DisabledTemplates"])
g_GameAttributes.settings[prop] = undefined;
let mapData = loadMapData(name);
let mapSettings = mapData && mapData.settings ? deepcopy(mapData.settings) : {};
// Reset victory conditions
if (g_GameAttributes.mapType != "random")
{
let victoryIdx = g_VictoryConditions.Name.indexOf(mapSettings.GameType || "") != -1 ? g_VictoryConditions.Name.indexOf(mapSettings.GameType) : g_VictoryConditions.Default;
g_GameAttributes.settings.GameType = g_VictoryConditions.Name[victoryIdx];
g_GameAttributes.settings.VictoryScripts = g_VictoryConditions.Scripts[victoryIdx];
}
if (g_GameAttributes.mapType == "scenario")
{
delete g_GameAttributes.settings.VictoryDuration;
delete g_GameAttributes.settings.LastManStanding;
}
if (mapSettings.PlayerData)
sanitizePlayerData(mapSettings.PlayerData);
// Copy any new settings
g_GameAttributes.map = name;
g_GameAttributes.script = mapSettings.Script;
if (g_GameAttributes.map !== "random")
for (let prop in mapSettings)
g_GameAttributes.settings[prop] = mapSettings[prop];
unassignInvalidPlayers(g_GameAttributes.settings.PlayerData.length);
supplementDefaults();
}
function isControlArrayElementHidden(idx)
{
return idx !== undefined && idx >= g_GameAttributes.settings.PlayerData.length;
}
/**
* @param idx - Only specified for dropdown arrays.
*/
function updateGUIDropdown(name, idx = undefined)
{
let [guiName, guiIdx] = getGUIObjectNameFromSetting(name);
let idxName = idx === undefined ? "": "[" + idx + "]";
let dropdown = Engine.GetGUIObjectByName(guiName + guiIdx + idxName);
let label = Engine.GetGUIObjectByName(guiName + "Text" + guiIdx + idxName);
let frame = Engine.GetGUIObjectByName(guiName + "Frame" + guiIdx + idxName);
let title = Engine.GetGUIObjectByName(guiName + "Title" + guiIdx + idxName);
let indexHidden = isControlArrayElementHidden(idx);
let obj = (idx === undefined ? g_Dropdowns : g_PlayerDropdowns)[name];
let selected = indexHidden ? -1 : dropdown.list_data.indexOf(String(obj.get(idx)));
let enabled = !indexHidden && (!obj.enabled || obj.enabled(idx));
let hidden = indexHidden || obj.hidden && obj.hidden(idx);
dropdown.hidden = !g_IsController || !enabled || hidden;
dropdown.selected = indexHidden ? -1 : selected;
dropdown.tooltip = !indexHidden && obj.tooltip ? obj.tooltip(idx) : "";
if (frame)
frame.hidden = hidden;
if (title && obj.title && !indexHidden)
title.caption = sprintf(translate("%(option)s:"), { "option": obj.title(idx) });
if (label && !indexHidden)
{
label.hidden = g_IsController && enabled || hidden;
label.caption = selected == -1 ? translateWithContext("option value", "Unknown") : dropdown.list[selected];
}
}
/**
* Not used for the player assignments, so playerCheckboxes are not implemented,
* hence no index.
*/
function updateGUICheckbox(name)
{
let obj = g_Checkboxes[name];
let checked = obj.get();
let hidden = obj.hidden && obj.hidden();
let enabled = !obj.enabled || obj.enabled();
let [guiName, guiIdx] = getGUIObjectNameFromSetting(name);
let checkbox = Engine.GetGUIObjectByName(guiName + guiIdx);
let label = Engine.GetGUIObjectByName(guiName + "Text" + guiIdx);
let frame = Engine.GetGUIObjectByName(guiName + "Frame" + guiIdx);
let title = Engine.GetGUIObjectByName(guiName + "Title" + guiIdx);
checkbox.checked = checked;
checkbox.enabled = enabled;
checkbox.hidden = hidden || !g_IsController;
checkbox.tooltip = obj.tooltip ? obj.tooltip() : "";
label.caption = checked ? translate("Yes") : translate("No");
label.hidden = hidden || g_IsController;
if (frame)
frame.hidden = hidden;
if (title && obj.title)
title.caption = sprintf(translate("%(option)s:"), { "option": obj.title() });
}
function updateGUIMiscControl(name, idx)
{
let idxName = idx === undefined ? "": "[" + idx + "]";
let obj = (idx === undefined ? g_MiscControls : g_PlayerMiscControls)[name];
let control = Engine.GetGUIObjectByName(name + idxName);
if (!control)
warn("No GUI object with name '" + name + "'");
let hide = isControlArrayElementHidden(idx);
control.hidden = hide;
if (hide)
return;
for (let property in obj)
control[property] = obj[property](idx);
}
function launchGame()
{
if (!g_IsController)
{
error("Only host can start game");
return;
}
if (!g_GameAttributes.map)
return;
savePersistMatchSettings();
// Select random map
if (g_GameAttributes.map == "random")
{
let victoryScriptsSelected = g_GameAttributes.settings.VictoryScripts;
let gameTypeSelected = g_GameAttributes.settings.GameType;
selectMap(pickRandom(g_Dropdowns.mapSelection.ids().slice(1)));
g_GameAttributes.settings.VictoryScripts = victoryScriptsSelected;
g_GameAttributes.settings.GameType = gameTypeSelected;
}
g_GameAttributes.settings.TriggerScripts = g_GameAttributes.settings.VictoryScripts.concat(g_GameAttributes.settings.TriggerScripts || []);
// Prevent reseting the readystate
g_GameStarted = true;
g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
// Get a unique array of selectable cultures
let cultures = Object.keys(g_CivData).filter(civ => g_CivData[civ].SelectableInGameSetup).map(civ => g_CivData[civ].Culture);
cultures = cultures.filter((culture, index) => cultures.indexOf(culture) === index);
// Determine random civs and botnames
for (let i in g_GameAttributes.settings.PlayerData)
{
// Pick a random civ of a random culture
let chosenCiv = g_GameAttributes.settings.PlayerData[i].Civ || "random";
if (chosenCiv == "random")
{
let culture = pickRandom(cultures);
chosenCiv = pickRandom(Object.keys(g_CivData).filter(civ => g_CivData[civ].Culture == culture));
}
g_GameAttributes.settings.PlayerData[i].Civ = chosenCiv;
// Pick one of the available botnames for the chosen civ
if (g_GameAttributes.mapType === "scenario" || !g_GameAttributes.settings.PlayerData[i].AI)
continue;
let chosenName = pickRandom(g_CivData[chosenCiv].AINames);
if (!g_IsNetworked)
chosenName = translate(chosenName);
// Count how many players use the chosenName
let usedName = g_GameAttributes.settings.PlayerData.filter(pData => pData.Name && pData.Name.indexOf(chosenName) !== -1).length;
g_GameAttributes.settings.PlayerData[i].Name = !usedName ? chosenName : sprintf(translate("%(playerName)s %(romanNumber)s"), { "playerName": chosenName, "romanNumber": g_RomanNumbers[usedName+1] });
}
// Copy playernames for the purpose of replays
for (let guid in g_PlayerAssignments)
{
let player = g_PlayerAssignments[guid];
if (player.player > 0) // not observer or GAIA
g_GameAttributes.settings.PlayerData[player.player - 1].Name = player.name;
}
// Seed used for both map generation and simulation
g_GameAttributes.settings.Seed = randIntExclusive(0, Math.pow(2, 32));
g_GameAttributes.settings.AISeed = randIntExclusive(0, Math.pow(2, 32));
// Used for identifying rated game reports for the lobby
g_GameAttributes.matchID = Engine.GetMatchID();
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
Engine.StartNetworkGame();
}
else
{
// Find the player ID which the user has been assigned to
let playerID = -1;
for (let i in g_GameAttributes.settings.PlayerData)
{
let assignBox = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
if (assignBox.list_data[assignBox.selected] == "guid:local")
playerID = +i+1;
}
Engine.StartGame(g_GameAttributes, playerID);
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
- "isNetworked" : g_IsNetworked,
+ "isNetworked": g_IsNetworked,
"playerAssignments": g_PlayerAssignments
});
}
}
+function launchTutorial()
+{
+ selectMap("maps/tutorials/starting_economy_walkthrough");
+ launchGame();
+}
+
/**
* Don't set any attributes here, just show the changes in the GUI.
*
* Unless the mapsettings don't specify a property and the user didn't set it in g_GameAttributes previously.
*/
function updateGUIObjects()
{
g_IsInGuiUpdate = true;
updatePlayerAssignmentChoices();
// Hide exceeding dropdowns and checkboxes
for (let panel in g_OptionOrderGUI)
for (let child of Engine.GetGUIObjectByName(panel + "Options").children)
child.hidden = true;
// Show the relevant ones
for (let name in g_Dropdowns)
updateGUIDropdown(name);
for (let name in g_Checkboxes)
updateGUICheckbox(name);
for (let i = 0; i < g_MaxPlayers; ++i)
{
for (let name in g_PlayerDropdowns)
updateGUIDropdown(name, i);
for (let name in g_PlayerMiscControls)
updateGUIMiscControl(name, i);
}
for (let name in g_MiscControls)
updateGUIMiscControl(name);
updateGameDescription();
resizeMoreOptionsWindow();
rightAlignCancelButton();
updateAutocompleteEntries();
g_IsInGuiUpdate = false;
// Refresh AI config page
if (g_LastViewedAIPlayer != -1)
{
Engine.PopGuiPage();
openAIConfig(g_LastViewedAIPlayer);
}
}
function rightAlignCancelButton()
{
const offset = 10;
let startGame = Engine.GetGUIObjectByName("startGame");
let right = startGame.hidden ? startGame.size.right : startGame.size.left - offset;
let cancelGame = Engine.GetGUIObjectByName("cancelGame");
let cancelGameSize = cancelGame.size;
let buttonWidth = cancelGameSize.right - cancelGameSize.left;
cancelGameSize.right = right;
right -= buttonWidth;
for (let element of ["cheatWarningText", "onscreenToolTip"])
{
let elementSize = Engine.GetGUIObjectByName(element).size;
elementSize.right = right - (cancelGameSize.left - elementSize.right);
Engine.GetGUIObjectByName(element).size = elementSize;
}
cancelGameSize.left = right;
cancelGame.size = cancelGameSize;
}
function updateGameDescription()
{
setMapPreviewImage("mapPreview", getMapPreview(g_GameAttributes.map));
Engine.GetGUIObjectByName("mapInfoName").caption =
translateMapTitle(getMapDisplayName(g_GameAttributes.map));
Engine.GetGUIObjectByName("mapInfoDescription").caption = getGameDescription();
}
/**
* Broadcast the changed settings to all clients and the lobbybot.
*/
function updateGameAttributes()
{
if (g_IsInGuiUpdate || !g_IsController)
return;
if (g_IsNetworked)
{
Engine.SetNetworkGameAttributes(g_GameAttributes);
if (g_LoadingState >= 2)
sendRegisterGameStanza();
resetReadyData();
}
else
updateGUIObjects();
}
function openAIConfig(playerSlot)
{
g_LastViewedAIPlayer = playerSlot;
Engine.PushGuiPage("page_aiconfig.xml", {
"callback": "AIConfigCallback",
"isController": g_IsController,
"playerSlot": playerSlot,
"id": g_GameAttributes.settings.PlayerData[playerSlot].AI,
"difficulty": g_GameAttributes.settings.PlayerData[playerSlot].AIDiff
});
}
/**
* Called after closing the dialog.
*/
function AIConfigCallback(ai)
{
g_LastViewedAIPlayer = -1;
if (!ai.save || !g_IsController)
return;
g_GameAttributes.settings.PlayerData[ai.playerSlot].AI = ai.id;
g_GameAttributes.settings.PlayerData[ai.playerSlot].AIDiff = ai.difficulty;
updateGameAttributes();
}
function updatePlayerAssignmentChoices()
{
let playerChoices = sortGUIDsByPlayerID().map(guid => ({
"Choice": "guid:" + guid,
"Color": g_PlayerAssignments[guid].player == -1 ? g_PlayerAssignmentColors.observer : g_PlayerAssignmentColors.player,
"Name": g_PlayerAssignments[guid].name
}));
// Only display hidden AIs if the map preselects them
let aiChoices = g_Settings.AIDescriptions
.filter(ai => !ai.data.hidden || g_GameAttributes.settings.PlayerData.some(pData => pData.AI == ai.id))
.map(ai => ({
"Choice": "ai:" + ai.id,
"Name": sprintf(translate("AI: %(ai)s"), {
"ai": translate(ai.data.name)
}),
"Color": g_PlayerAssignmentColors.AI
}));
let unassignedSlot = [{
"Choice": "unassigned",
"Name": translate("Unassigned"),
"Color": g_PlayerAssignmentColors.unassigned
}];
g_PlayerAssignmentList = prepareForDropdown(playerChoices.concat(aiChoices).concat(unassignedSlot));
initPlayerDropdowns("playerAssignment");
}
function swapPlayers(guid, newSlot)
{
// Player slots are indexed from 0 as Gaia is omitted.
let newPlayerID = newSlot + 1;
let playerID = g_PlayerAssignments[guid].player;
// Attempt to swap the player or AI occupying the target slot,
// if any, into the slot this player is currently in.
if (playerID != -1)
{
for (let guid in g_PlayerAssignments)
{
// Move the player in the destination slot into the current slot.
if (g_PlayerAssignments[guid].player != newPlayerID)
continue;
if (g_IsNetworked)
Engine.AssignNetworkPlayer(playerID, guid);
else
g_PlayerAssignments[guid].player = playerID;
break;
}
// Transfer the AI from the target slot to the current slot.
g_GameAttributes.settings.PlayerData[playerID - 1].AI = g_GameAttributes.settings.PlayerData[newSlot].AI;
g_GameAttributes.settings.PlayerData[playerID - 1].AIDiff = g_GameAttributes.settings.PlayerData[newSlot].AIDiff;
// Swap civilizations if they aren't fixed
if (g_GameAttributes.mapType != "scenario")
{
[g_GameAttributes.settings.PlayerData[playerID - 1].Civ, g_GameAttributes.settings.PlayerData[newSlot].Civ] =
[g_GameAttributes.settings.PlayerData[newSlot].Civ, g_GameAttributes.settings.PlayerData[playerID - 1].Civ];
}
}
if (g_IsNetworked)
Engine.AssignNetworkPlayer(newPlayerID, guid);
else
g_PlayerAssignments[guid].player = newPlayerID;
g_GameAttributes.settings.PlayerData[newSlot].AI = "";
}
function submitChatInput()
{
let input = Engine.GetGUIObjectByName("chatInput");
let text = input.caption;
if (!text.length)
return;
input.caption = "";
if (executeNetworkCommand(text))
return;
Engine.SendNetworkChat(text);
}
function senderFont(text)
{
return '[font="' + g_SenderFont + '"]' + text + '[/font]';
}
function systemMessage(message)
{
return senderFont(sprintf(translate("== %(message)s"), { "message": message }));
}
function colorizePlayernameByGUID(guid, username = "")
{
// TODO: Maybe the host should have the moderator-prefix?
if (!username)
username = g_PlayerAssignments[guid] ? escapeText(g_PlayerAssignments[guid].name) : translate("Unknown Player");
let playerID = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].player : -1;
let color = g_ColorRegular;
if (playerID > 0)
{
color = g_GameAttributes.settings.PlayerData[playerID - 1].Color;
// Enlighten playercolor to improve readability
let [h, s, l] = rgbToHsl(color.r, color.g, color.b);
let [r, g, b] = hslToRgb(h, s, Math.max(0.6, l));
color = rgbToGuiColor({ "r": r, "g": g, "b": b });
}
return '[color="'+ color +'"]' + username + '[/color]';
}
function addChatMessage(msg)
{
if (!g_FormatChatMessage[msg.type])
return;
if (msg.type == "chat")
{
let userName = g_PlayerAssignments[Engine.GetPlayerGUID()].name;
if (userName != g_PlayerAssignments[msg.guid].name)
notifyUser(userName, msg.text);
}
let user = colorizePlayernameByGUID(msg.guid || -1, msg.username || "");
let text = g_FormatChatMessage[msg.type](msg, user);
if (!text)
return;
if (Engine.ConfigDB_GetValue("user", "chat.timestamp") == "true")
text = sprintf(translate("%(time)s %(message)s"), {
"time": sprintf(translate("\\[%(time)s]"), {
"time": Engine.FormatMillisecondsIntoDateStringLocal(new Date().getTime(), translate("HH:mm"))
}),
"message": text
});
g_ChatMessages.push(text);
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
function showMoreOptions(show)
{
Engine.GetGUIObjectByName("moreOptionsFade").hidden = !show;
Engine.GetGUIObjectByName("moreOptions").hidden = !show;
}
function resetCivilizations()
{
for (let i in g_GameAttributes.settings.PlayerData)
g_GameAttributes.settings.PlayerData[i].Civ = "random";
updateGameAttributes();
}
function resetTeams()
{
for (let i in g_GameAttributes.settings.PlayerData)
g_GameAttributes.settings.PlayerData[i].Team = -1;
updateGameAttributes();
}
function toggleReady()
{
setReady((g_IsReady + 1) % 3, true);
}
function setReady(ready, sendMessage)
{
g_IsReady = ready;
if (sendMessage)
Engine.SendNetworkReady(g_IsReady);
updateGUIObjects();
}
function resetReadyData()
{
if (g_GameStarted)
return;
if (g_ReadyChanged < 1)
addChatMessage({ "type": "settings" });
else if (g_ReadyChanged == 2 && !g_ReadyInit)
return; // duplicate calls on init
else
g_ReadyInit = false;
g_ReadyChanged = 2;
if (!g_IsNetworked)
g_IsReady = 2;
else if (g_IsController)
{
Engine.ClearAllPlayerReady();
setReady(2, true);
}
else if (g_IsReady != 2)
setReady(0, false);
}
/**
* Send a list of playernames and distinct between players and observers.
* Don't send teams, AIs or anything else until the game was started.
* The playerData format from g_GameAttributes is kept to reuse the GUI function presenting the data.
*/
function formatClientsForStanza()
{
let connectedPlayers = 0;
let playerData = [];
for (let guid in g_PlayerAssignments)
{
let pData = { "Name": g_PlayerAssignments[guid].name };
if (g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1])
++connectedPlayers;
else
pData.Team = "observer";
playerData.push(pData);
}
return {
"list": playerDataToStringifiedTeamList(playerData),
"connectedPlayers": connectedPlayers
};
}
/**
* Send the relevant gamesettings to the lobbybot.
*/
function sendRegisterGameStanza()
{
if (!g_IsController || !Engine.HasXmppClient())
return;
let clients = formatClientsForStanza();
let stanza = {
"name": g_ServerName,
"port": g_ServerPort,
"mapName": g_GameAttributes.map,
"niceMapName": getMapDisplayName(g_GameAttributes.map),
"mapSize": g_GameAttributes.mapType == "random" ? g_GameAttributes.settings.Size : "Default",
"mapType": g_GameAttributes.mapType,
"victoryCondition": g_VictoryConditions.Title[g_VictoryConditions.Name.indexOf(g_GameAttributes.settings.GameType)],
"nbp": clients.connectedPlayers,
"maxnbp": g_GameAttributes.settings.PlayerData.length,
"players": clients.list,
};
// Only send the stanza if the relevant settings actually changed
if (g_LastGameStanza && Object.keys(stanza).every(prop => g_LastGameStanza[prop] == stanza[prop]))
return;
g_LastGameStanza = stanza;
Engine.SendRegisterGame(stanza);
}
function updateAutocompleteEntries()
{
g_Autocomplete = [];
for (let control of [g_Dropdowns, g_Checkboxes])
for (let name in control)
g_Autocomplete = g_Autocomplete.concat(control[name].title());
for (let dropdown of [g_Dropdowns, g_PlayerDropdowns])
for (let name in dropdown)
if (dropdown[name].autocomplete)
g_Autocomplete = g_Autocomplete.concat(dropdown[name].labels());
}
Index: ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 19598)
+++ ps/trunk/binaries/data/mods/public/gui/pregame/mainmenu.xml (revision 19599)
@@ -1,633 +1,647 @@
Index: ps/trunk/binaries/data/mods/public/gui/session/messages.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 19598)
+++ ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 19599)
@@ -1,1234 +1,1276 @@
/**
* All known cheat commands.
* @type {Object}
*/
const g_Cheats = getCheatsData();
/**
* Number of seconds after which chatmessages will disappear.
*/
const g_ChatTimeout = 30;
/**
* Maximum number of lines to display simultaneously.
*/
const g_ChatLines = 20;
/**
* The currently displayed strings, limited by the given timeframe and limit above.
*/
var g_ChatMessages = [];
/**
* All unparsed chat messages received since connect, including timestamp.
*/
var g_ChatHistory = [];
/**
* Holds the timer-IDs used for hiding the chat after g_ChatTimeout seconds.
*/
var g_ChatTimers = [];
/**
* Command to send to the previously selected private chat partner.
*/
var g_LastChatAddressee = "";
/**
* Handle all netmessage types that can occur.
*/
var g_NetMessageTypes = {
"netstatus": msg => {
handleNetStatusMessage(msg);
},
"netwarn": msg => {
addNetworkWarning(msg);
},
"out-of-sync": msg => {
onNetworkOutOfSync(msg);
},
"players": msg => {
handlePlayerAssignmentsMessage(msg);
},
"paused": msg => {
setClientPauseState(msg.guid, msg.pause);
},
"clients-loading": msg => {
handleClientsLoadingMessage(msg.guids);
},
"rejoined": msg => {
addChatMessage({
"type": "rejoined",
"guid": msg.guid
});
},
"kicked": msg => {
addChatMessage({
"type": "kicked",
"username": msg.username,
"banned": msg.banned
});
},
"chat": msg => {
addChatMessage({
"type": "message",
"guid": msg.guid,
"text": msg.text
});
},
"aichat": msg => {
addChatMessage({
"type": "message",
"guid": msg.guid,
"text": msg.text,
"translate": true
});
},
"gamesetup": msg => {}, // Needed for autostart
"start": msg => {}
};
var g_FormatChatMessage = {
"system": msg => msg.text,
"connect": msg =>
sprintf(
g_PlayerAssignments[msg.guid].player != -1 ?
// Translation: A player that left the game joins again
translate("%(player)s is starting to rejoin the game.") :
// Translation: A player joins the game for the first time
translate("%(player)s is starting to join the game."),
{ "player": colorizePlayernameByGUID(msg.guid) }
),
"disconnect": msg =>
sprintf(translate("%(player)s has left the game."), {
"player": colorizePlayernameByGUID(msg.guid)
}),
"rejoined": msg =>
sprintf(
g_PlayerAssignments[msg.guid].player != -1 ?
// Translation: A player that left the game joins again
translate("%(player)s has rejoined the game.") :
// Translation: A player joins the game for the first time
translate("%(player)s has joined the game."),
{ "player": colorizePlayernameByGUID(msg.guid) }
),
"kicked": msg =>
sprintf(
msg.banned ?
translate("%(username)s has been banned") :
translate("%(username)s has been kicked"),
{
"username": colorizePlayernameHelper(
msg.username,
g_Players.findIndex(p => p.name == msg.username)
)
}
),
"clientlist": msg => getUsernameList(),
"message": msg => formatChatCommand(msg),
"defeat": msg => formatDefeatMessage(msg),
"won": msg => formatWinMessage(msg),
"diplomacy": msg => formatDiplomacyMessage(msg),
"tribute": msg => formatTributeMessage(msg),
"barter": msg => formatBarterMessage(msg),
"attack": msg => formatAttackMessage(msg),
"phase": msg => formatPhaseMessage(msg)
};
/**
* Show a label and grey overlay or hide both on connection change.
*/
var g_StatusMessageTypes = {
"authenticated": msg => translate("Connection to the server has been authenticated."),
"connected": msg => translate("Connected to the server."),
"disconnected": msg => translate("Connection to the server has been lost.") + "\n" +
// Translation: States the reason why the client disconnected from the server.
sprintf(translate("Reason: %(reason)s."), {
"reason": getDisconnectReason(msg.reason, true)
}),
"waiting_for_players": msg => translate("Waiting for players to connect:"),
"join_syncing": msg => translate("Synchronising gameplay with other players..."),
"active": msg => ""
};
/**
* Chatmessage shown after commands like /me or /enemies.
*/
var g_ChatCommands = {
"regular": {
"context": translate("(%(context)s) %(userTag)s %(message)s"),
"no-context": translate("%(userTag)s %(message)s")
},
"me": {
"context": translate("(%(context)s) * %(user)s %(message)s"),
"no-context": translate("* %(user)s %(message)s")
}
};
var g_ChatAddresseeContext = {
"/team": translate("Team"),
"/allies": translate("Ally"),
"/enemies": translate("Enemy"),
"/observers": translate("Observer"),
"/msg": translate("Private")
};
/**
* Returns true if the current player is an addressee, given the chat message type and sender.
*/
var g_IsChatAddressee = {
"/team": senderID =>
g_Players[senderID] &&
g_Players[Engine.GetPlayerID()] &&
g_Players[Engine.GetPlayerID()].team != -1 &&
g_Players[Engine.GetPlayerID()].team == g_Players[senderID].team,
"/allies": senderID =>
g_Players[senderID] &&
g_Players[Engine.GetPlayerID()] &&
g_Players[senderID].isMutualAlly[Engine.GetPlayerID()],
"/enemies": senderID =>
g_Players[senderID] &&
g_Players[Engine.GetPlayerID()] &&
g_Players[senderID].isEnemy[Engine.GetPlayerID()],
"/observers": senderID =>
g_IsObserver,
"/msg": (senderID, addresseeGUID) =>
addresseeGUID == Engine.GetPlayerGUID()
};
/**
* Notice only messages will be filtered that are visible to the player in the first place.
*/
var g_ChatHistoryFilters = [
{
"key": "all",
"text": translateWithContext("chat history filter", "Chat and notifications"),
"filter": (msg, senderID) => true
},
{
"key": "chat",
"text": translateWithContext("chat history filter", "Chat messages"),
"filter": (msg, senderID) => msg.type == "message"
},
{
"key": "player",
"text": translateWithContext("chat history filter", "Players chat"),
"filter": (msg, senderID) =>
msg.type == "message" &&
senderID > 0 && !isPlayerObserver(senderID)
},
{
"key": "ally",
"text": translateWithContext("chat history filter", "Ally chat"),
"filter": (msg, senderID) =>
msg.type == "message" &&
msg.cmd && msg.cmd == "/allies"
},
{
"key": "enemy",
"text": translateWithContext("chat history filter", "Enemy chat"),
"filter": (msg, senderID) =>
msg.type == "message" &&
msg.cmd && msg.cmd == "/enemies"
},
{
"key": "observer",
"text": translateWithContext("chat history filter", "Observer chat"),
"filter": (msg, senderID) =>
msg.type == "message" &&
msg.cmd && msg.cmd == "/observers"
},
{
"key": "private",
"text": translateWithContext("chat history filter", "Private chat"),
"filter": (msg, senderID) => !!msg.isVisiblePM
}
];
var g_PlayerStateMessages = {
"won": translate("You have won!"),
"defeated": translate("You have been defeated!")
};
/**
* Chatmessage shown on diplomacy change.
*/
var g_DiplomacyMessages = {
"active": {
"ally": translate("You are now allied with %(player)s."),
"enemy": translate("You are now at war with %(player)s."),
"neutral": translate("You are now neutral with %(player)s.")
},
"passive": {
"ally": translate("%(player)s is now allied with you."),
"enemy": translate("%(player)s is now at war with you."),
"neutral": translate("%(player)s is now neutral with you.")
},
"observer": {
"ally": translate("%(player)s is now allied with %(player2)s."),
"enemy": translate("%(player)s is now at war with %(player2)s."),
"neutral": translate("%(player)s is now neutral with %(player2)s.")
}
};
/**
* Defines how the GUI reacts to notifications that are sent by the simulation.
* Don't open new pages (message boxes) here! Otherwise further notifications
* handled in the same turn can't access the GUI objects anymore.
*/
var g_NotificationsTypes =
{
"chat": function(notification, player)
{
let message = {
"type": "message",
"guid": findGuidForPlayerID(player) || -1,
"text": notification.message
};
if (message.guid == -1)
message.player = player;
addChatMessage(message);
},
"aichat": function(notification, player)
{
let message = {
"type": "message",
"text": notification.message,
"guid": findGuidForPlayerID(player) || -1,
"player": player,
"translate": true
};
if (notification.translateParameters)
{
message.translateParameters = notification.translateParameters;
message.parameters = notification.parameters;
colorizePlayernameParameters(notification.parameters);
}
addChatMessage(message);
},
"defeat": function(notification, player)
{
addChatMessage({
"type": "defeat",
"guid": findGuidForPlayerID(player),
"player": player,
"resign": !!notification.resign
});
playerFinished(player, false);
sendLobbyPlayerlistUpdate();
},
"won": function(notification, player)
{
addChatMessage({
"type": "won",
"guid": findGuidForPlayerID(player),
"player": player
});
playerFinished(player, true);
sendLobbyPlayerlistUpdate();
},
"diplomacy": function(notification, player)
{
addChatMessage({
"type": "diplomacy",
"sourcePlayer": player,
"targetPlayer": notification.targetPlayer,
"status": notification.status
});
updatePlayerData();
},
"ceasefire-ended": function(notification, player)
{
updatePlayerData();
},
+ "tutorial": function(notification, player)
+ {
+ updateTutorial(notification);
+ },
"tribute": function(notification, player)
{
addChatMessage({
"type": "tribute",
"sourcePlayer": notification.donator,
"targetPlayer": player,
"amounts": notification.amounts
});
},
"barter": function(notification, player)
{
addChatMessage({
"type": "barter",
"player": player,
"amountsSold": notification.amountsSold,
"amountsBought": notification.amountsBought,
"resourceSold": notification.resourceSold,
"resourceBought": notification.resourceBought
});
},
"spy-response": function(notification, player)
{
if (g_ViewedPlayer == player)
setCameraFollow(notification.entity);
},
"attack": function(notification, player)
{
if (player != g_ViewedPlayer)
return;
// Focus camera on attacks
if (g_FollowPlayer)
{
setCameraFollow(notification.target);
g_Selection.reset();
if (notification.target)
g_Selection.addList([notification.target]);
}
if (Engine.ConfigDB_GetValue("user", "gui.session.notifications.attack") !== "true")
return;
addChatMessage({
"type": "attack",
"player": player,
"attacker": notification.attacker,
"targetIsDomesticAnimal": notification.targetIsDomesticAnimal
});
},
"phase": function(notification, player)
{
addChatMessage({
"type": "phase",
"player": player,
"phaseName": notification.phaseName,
"phaseState": notification.phaseState
});
},
"dialog": function(notification, player)
{
if (player == Engine.GetPlayerID())
openDialog(notification.dialogName, notification.data, player);
},
"resetselectionpannel": function(notification, player)
{
if (player != Engine.GetPlayerID())
return;
g_Selection.rebuildSelection({});
},
"playercommand": function(notification, player)
{
// For observers, focus the camera on units commanded by the selected player
if (!g_FollowPlayer || player != g_ViewedPlayer)
return;
let cmd = notification.cmd;
// Ignore boring animals
let entState = cmd.entities && cmd.entities[0] && GetEntityState(cmd.entities[0]);
if (entState && entState.identity && entState.identity.classes &&
entState.identity.classes.indexOf("Animal") != -1)
return;
// Focus the building to construct
if (cmd.type == "repair")
{
let targetState = GetEntityState(cmd.target);
if (targetState)
Engine.CameraMoveTo(targetState.position.x, targetState.position.z);
}
// Focus commanded entities, but don't lose previous focus when training units
else if (cmd.type != "train" && cmd.type != "research" && entState)
setCameraFollow(cmd.entities[0]);
// Select units affected by that command
let selection = [];
if (cmd.entities)
selection = cmd.entities;
if (cmd.target)
selection.push(cmd.target);
// Allow gaia in selection when gathering
g_Selection.reset();
g_Selection.addList(selection, false, cmd.type == "gather");
}
};
/**
* Loads all known cheat commands.
*
* @returns {Object}
*/
function getCheatsData()
{
let cheats = {};
for (let fileName of getJSONFileList("simulation/data/cheats/"))
{
let currentCheat = Engine.ReadJSONFile("simulation/data/cheats/"+fileName+".json");
if (!currentCheat)
continue;
if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1)
warn("Cheat name '" + currentCheat.Name + "' is already present");
else
cheats[currentCheat.Name] = currentCheat.Data;
}
return cheats;
}
/**
* Reads userinput from the chat and sends a simulation command in case it is a known cheat.
*
* @returns {boolean} - True if a cheat was executed.
*/
function executeCheat(text)
{
if (!controlsPlayer(Engine.GetPlayerID()) ||
!g_Players[Engine.GetPlayerID()].cheatsEnabled)
return false;
// Find the cheat code that is a prefix of the user input
let cheatCode = Object.keys(g_Cheats).find(cheatCode => text.indexOf(cheatCode) == 0);
if (!cheatCode)
return false;
let cheat = g_Cheats[cheatCode];
let parameter = text.substr(cheatCode.length);
if (cheat.isNumeric)
parameter = +parameter;
if (cheat.DefaultParameter && (isNaN(parameter) || parameter <= 0))
parameter = cheat.DefaultParameter;
Engine.PostNetworkCommand({
"type": "cheat",
"action": cheat.Action,
"text": cheat.Type,
"player": Engine.GetPlayerID(),
"parameter": parameter,
"templates": cheat.Templates,
"selected": g_Selection.toList()
});
return true;
}
function findGuidForPlayerID(playerID)
{
return Object.keys(g_PlayerAssignments).find(guid => g_PlayerAssignments[guid].player == playerID);
}
/**
* Processes all pending notifications sent from the GUIInterface simulation component.
*/
function handleNotifications()
{
for (let notification of Engine.GuiInterfaceCall("GetNotifications"))
{
if (!notification.players || !notification.type || !g_NotificationsTypes[notification.type])
{
error("Invalid GUI notification: " + uneval(notification));
continue;
}
for (let player of notification.players)
g_NotificationsTypes[notification.type](notification, player);
}
}
/**
+ * Updates the tutorial panel when a new goal.
+ */
+function updateTutorial(notification)
+{
+ // Show the tutorial panel if not yet done
+ Engine.GetGUIObjectByName("tutorialPanel").hidden = false;
+
+ if (notification.warning)
+ {
+ Engine.GetGUIObjectByName("tutorialWarning").caption = "[color=\"orange\"]" + notification.message + "[/color]";
+ return;
+ }
+
+ let tutorialText = Engine.GetGUIObjectByName("tutorialText");
+ let caption = tutorialText.caption.replace('[color=\"yellow\"]', '').replace('[/color]', '');
+ if (caption)
+ caption += "\n";
+ tutorialText.caption = caption + "[color=\"yellow\"]" + notification.message + "[/color]";
+ if (notification.readyButton)
+ {
+ Engine.GetGUIObjectByName("tutorialReady").hidden = false;
+ if (notification.leave)
+ {
+ Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click to quit this tutorial.");
+ Engine.GetGUIObjectByName("tutorialReady").caption = translate("Quit");
+ Engine.GetGUIObjectByName("tutorialReady").onPress = leaveGame;
+ }
+ else
+ Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click when ready.");
+ }
+ else
+ {
+ Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Follow the instructions.");
+ Engine.GetGUIObjectByName("tutorialReady").hidden = true;
+ }
+}
+
+/**
* Displays all active counters (messages showing the remaining time) for wonder-victory, ceasefire etc.
*/
function updateTimeNotifications()
{
let notifications = Engine.GuiInterfaceCall("GetTimeNotifications", g_ViewedPlayer);
let notificationText = "";
for (let n of notifications)
{
let message = n.message;
if (n.translateMessage)
message = translate(message);
let parameters = n.parameters || {};
if (n.translateParameters)
translateObjectKeys(parameters, n.translateParameters);
parameters.time = timeToString(n.endTime - g_SimState.timeElapsed);
colorizePlayernameParameters(parameters);
notificationText += sprintf(message, parameters) + "\n";
}
Engine.GetGUIObjectByName("notificationText").caption = notificationText;
}
/**
* Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
* Saves the received object to mainlog.html.
*/
function handleNetMessages()
{
while (true)
{
let msg = Engine.PollNetworkClient();
if (!msg)
return;
log("Net message: " + uneval(msg));
if (g_NetMessageTypes[msg.type])
g_NetMessageTypes[msg.type](msg);
else
error("Unrecognised net message type '" + msg.type + "'");
}
}
/**
* @param {Object} message
*/
function handleNetStatusMessage(message)
{
if (g_Disconnected)
return;
if (!g_StatusMessageTypes[message.status])
{
error("Unrecognised netstatus type '" + message.status + "'");
return;
}
g_IsNetworkedActive = message.status == "active";
let netStatus = Engine.GetGUIObjectByName("netStatus");
let statusMessage = g_StatusMessageTypes[message.status](message);
netStatus.caption = statusMessage;
netStatus.hidden = !statusMessage;
let loadingClientsText = Engine.GetGUIObjectByName("loadingClientsText");
loadingClientsText.hidden = message.status != "waiting_for_players";
if (message.status == "disconnected")
{
// Hide the pause overlay, and pause animations.
Engine.GetGUIObjectByName("pauseOverlay").hidden = true;
Engine.SetPaused(true, false);
g_Disconnected = true;
closeOpenDialogs();
}
}
function handleClientsLoadingMessage(guids)
{
let loadingClientsText = Engine.GetGUIObjectByName("loadingClientsText");
loadingClientsText.caption = guids.map(guid => colorizePlayernameByGUID(guid)).join(translate(", "));
}
function onNetworkOutOfSync(msg)
{
let txt = [
sprintf(translate("Out-Of-Sync error on turn %(turn)s."), {
"turn": msg.turn
}),
sprintf(translateWithContext("Out-Of-Sync", "Players: %(players)s"), {
"players": msg.players.join(translate(", "))
}),
msg.hash == msg.expectedHash ?
translateWithContext("Out-Of-Sync", "Your game state is identical to the hosts game state.") :
translateWithContext("Out-Of-Sync", "Your game state differs from the hosts game state."),
""
];
if (msg.turn > 1 && g_GameAttributes.settings.PlayerData.some(pData => pData && pData.AI))
txt.push(translateWithContext("Out-Of-Sync", "Rejoining Multiplayer games with AIs is not supported yet!"));
else
{
txt.push(
translateWithContext("Out-Of-Sync", "Ensure all players use the same mods."),
translateWithContext("Out-Of-Sync", 'Click on "Report Bugs" in the main menu to help fix this.'),
sprintf(translateWithContext("Out-Of-Sync", "Replay saved to %(filepath)s"), {
"filepath": escapeText(msg.path_replay)
}),
sprintf(translateWithContext("Out-Of-Sync", "Dumping current state to %(filepath)s"), {
"filepath": escapeText(msg.path_oos_dump)
})
);
}
messageBox(
600, 280,
txt.join("\n"),
translate("Out of Sync")
);
}
function onReplayOutOfSync()
{
messageBox(
500, 140,
translate("Out-Of-Sync error!") + "\n" +
// Translation: This is shown if replay is out of sync
translateWithContext("Out-Of-Sync", "The current game state is different from the original game state."),
translate("Out of Sync")
);
}
function handlePlayerAssignmentsMessage(message)
{
for (let guid in g_PlayerAssignments)
if (!message.newAssignments[guid])
onClientLeave(guid);
let joins = Object.keys(message.newAssignments).filter(guid => !g_PlayerAssignments[guid]);
g_PlayerAssignments = message.newAssignments;
joins.forEach(guid => {
onClientJoin(guid);
});
updateGUIObjects();
updateChatAddressees();
sendLobbyPlayerlistUpdate();
}
function onClientJoin(guid)
{
let playerID = g_PlayerAssignments[guid].player;
if (g_Players[playerID])
{
g_Players[playerID].guid = guid;
g_Players[playerID].name = g_PlayerAssignments[guid].name;
g_Players[playerID].offline = false;
}
addChatMessage({
"type": "connect",
"guid": guid
});
}
function onClientLeave(guid)
{
setClientPauseState(guid, false);
for (let id in g_Players)
if (g_Players[id].guid == guid)
g_Players[id].offline = true;
addChatMessage({
"type": "disconnect",
"guid": guid
});
}
function updateChatAddressees()
{
// Remember previously selected item
let chatAddressee = Engine.GetGUIObjectByName("chatAddressee");
let selectedName = chatAddressee.list_data[chatAddressee.selected] || "";
selectedName = selectedName.substr(0, 4) == "/msg" && selectedName.substr(5);
let addressees = [
{
"label": translateWithContext("chat addressee", "Everyone"),
"cmd": ""
}
];
if (!g_IsObserver)
{
addressees.push({
"label": translateWithContext("chat addressee", "Allies"),
"cmd": "/allies"
});
addressees.push({
"label": translateWithContext("chat addressee", "Enemies"),
"cmd": "/enemies"
});
}
addressees.push({
"label": translateWithContext("chat addressee", "Observers"),
"cmd": "/observers"
});
// Add playernames for private messages
let guids = sortGUIDsByPlayerID();
for (let guid of guids)
{
if (guid == Engine.GetPlayerGUID())
continue;
let playerID = g_PlayerAssignments[guid].player;
// Don't provide option for PM from observer to player
if (g_IsObserver && !isPlayerObserver(playerID))
continue;
let colorBox = isPlayerObserver(playerID) ? "" : colorizePlayernameHelper("■", playerID) + " ";
addressees.push({
"cmd": "/msg " + g_PlayerAssignments[guid].name,
"label": colorBox + g_PlayerAssignments[guid].name
});
}
// Select mock item if the selected addressee went offline
if (selectedName && guids.every(guid => g_PlayerAssignments[guid].name != selectedName))
addressees.push({
"cmd": "/msg " + selectedName,
"label": sprintf(translate("\\[OFFLINE] %(player)s"), { "player": selectedName })
});
let oldChatAddressee = chatAddressee.list_data[chatAddressee.selected];
chatAddressee.list = addressees.map(adressee => adressee.label);
chatAddressee.list_data = addressees.map(adressee => adressee.cmd);
chatAddressee.selected = Math.max(0, chatAddressee.list_data.indexOf(oldChatAddressee));
}
/**
* Send text as chat. Don't look for commands.
*
* @param {string} text
*/
function submitChatDirectly(text)
{
if (!text.length)
return;
if (g_IsNetworked)
Engine.SendNetworkChat(text);
else
addChatMessage({ "type": "message", "guid": "local", "text": text });
}
/**
* Loads the text from the GUI window, checks if it is a local command
* or cheat and executes it. Otherwise sends it as chat.
*/
function submitChatInput()
{
let text = Engine.GetGUIObjectByName("chatInput").caption;
closeChat();
if (!text.length)
return;
if (executeNetworkCommand(text))
return;
if (executeCheat(text))
return;
let chatAddressee = Engine.GetGUIObjectByName("chatAddressee");
if (chatAddressee.selected > 0 && (text.indexOf("/") != 0 || text.indexOf("/me ") == 0))
text = chatAddressee.list_data[chatAddressee.selected] + " " + text;
let selectedChat = chatAddressee.list_data[chatAddressee.selected];
if (selectedChat.startsWith("/msg"))
g_LastChatAddressee = selectedChat;
submitChatDirectly(text);
}
/**
* Displays the prepared chatmessage.
*
* @param msg {Object}
*/
function addChatMessage(msg)
{
if (!g_FormatChatMessage[msg.type])
return;
let formatted = g_FormatChatMessage[msg.type](msg);
if (!formatted)
return;
// Update chat overlay
g_ChatMessages.push(formatted);
g_ChatTimers.push(setTimeout(removeOldChatMessage, g_ChatTimeout * 1000));
if (g_ChatMessages.length > g_ChatLines)
removeOldChatMessage();
else
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
// Save to chat history
let historical = {
"txt": formatted,
"timePrefix": sprintf(translate("\\[%(time)s]"), {
"time": Engine.FormatMillisecondsIntoDateStringLocal(new Date().getTime(), translate("HH:mm"))
}),
"filter": {}
};
// Apply the filters now before diplomacies or playerstates change
let senderID = msg.guid && g_PlayerAssignments[msg.guid] ? g_PlayerAssignments[msg.guid].player : 0;
for (let filter of g_ChatHistoryFilters)
historical.filter[filter.key] = filter.filter(msg, senderID);
g_ChatHistory.push(historical);
updateChatHistory();
}
/**
* Called when the timer has run out for the oldest chatmessage or when the message limit is reached.
*/
function removeOldChatMessage()
{
clearTimeout(g_ChatTimers[0]);
g_ChatTimers.shift();
g_ChatMessages.shift();
Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
/**
* This function is used for AIs, whose names don't exist in g_PlayerAssignments.
*/
function colorizePlayernameByID(playerID)
{
let username = g_Players[playerID] && escapeText(g_Players[playerID].name);
return colorizePlayernameHelper(username, playerID);
}
function colorizePlayernameByGUID(guid)
{
let username = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].name : "";
let playerID = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].player : -1;
return colorizePlayernameHelper(username, playerID);
}
function colorizePlayernameHelper(username, playerID)
{
let playerColor = playerID > -1 ? rgbToGuiColor(g_Players[playerID].color) : "white";
return '[color="' + playerColor + '"]' + (username || translate("Unknown Player")) + "[/color]";
}
/**
* Insert the colorized playername to chat messages sent by the AI and time notifications.
*/
function colorizePlayernameParameters(parameters)
{
for (let param in parameters)
if (param.startsWith("_player_"))
parameters[param] = colorizePlayernameByID(parameters[param]);
}
function formatDefeatMessage(msg)
{
return sprintf(
msg.resign ?
translate("%(player)s has resigned.") :
translate("%(player)s has been defeated."),
{ "player": colorizePlayernameByID(msg.player) }
);
}
function formatWinMessage(msg)
{
return sprintf(translate("%(player)s has won."), {
"player": colorizePlayernameByID(msg.player)
});
}
function formatDiplomacyMessage(msg)
{
let messageType;
if (g_IsObserver)
messageType = "observer";
else if (Engine.GetPlayerID() == msg.sourcePlayer)
messageType = "active";
else if (Engine.GetPlayerID() == msg.targetPlayer)
messageType = "passive";
else
return "";
return sprintf(g_DiplomacyMessages[messageType][msg.status], {
"player": colorizePlayernameByID(messageType == "active" ? msg.targetPlayer : msg.sourcePlayer),
"player2": colorizePlayernameByID(messageType == "active" ? msg.sourcePlayer : msg.targetPlayer)
});
}
/**
* Optionally show all tributes sent in observer mode and tributes sent between allied players.
* Otherwise, only show tributes sent directly to us, and tributes that we send.
*/
function formatTributeMessage(msg)
{
let message = "";
if (msg.targetPlayer == Engine.GetPlayerID())
message = translate("%(player)s has sent you %(amounts)s.");
else if (msg.sourcePlayer == Engine.GetPlayerID())
message = translate("You have sent %(player2)s %(amounts)s.");
else if (Engine.ConfigDB_GetValue("user", "gui.session.notifications.tribute") == "true" &&
(g_IsObserver || g_GameAttributes.settings.LockTeams &&
g_Players[msg.sourcePlayer].isMutualAlly[Engine.GetPlayerID()] &&
g_Players[msg.targetPlayer].isMutualAlly[Engine.GetPlayerID()]))
message = translate("%(player)s has sent %(player2)s %(amounts)s.");
return sprintf(message, {
"player": colorizePlayernameByID(msg.sourcePlayer),
"player2": colorizePlayernameByID(msg.targetPlayer),
"amounts": getLocalizedResourceAmounts(msg.amounts)
});
}
function formatBarterMessage(msg)
{
if (!g_IsObserver || Engine.ConfigDB_GetValue("user", "gui.session.notifications.barter") != "true")
return "";
let amountsSold = {};
amountsSold[msg.resourceSold] = msg.amountsSold;
let amountsBought = {};
amountsBought[msg.resourceBought] = msg.amountsBought;
return sprintf(translate("%(player)s bartered %(amountsBought)s for %(amountsSold)s."), {
"player": colorizePlayernameByID(msg.player),
"amountsBought": getLocalizedResourceAmounts(amountsBought),
"amountsSold": getLocalizedResourceAmounts(amountsSold)
});
}
function formatAttackMessage(msg)
{
if (msg.player != g_ViewedPlayer)
return "";
let message = msg.targetIsDomesticAnimal ?
translate("Your livestock has been attacked by %(attacker)s!") :
translate("You have been attacked by %(attacker)s!");
return sprintf(message, {
"attacker": colorizePlayernameByID(msg.attacker)
});
}
function formatPhaseMessage(msg)
{
let notifyPhase = Engine.ConfigDB_GetValue("user", "gui.session.notifications.phase");
if (notifyPhase == "none" || msg.player != g_ViewedPlayer && !g_IsObserver && !g_Players[msg.player].isMutualAlly[g_ViewedPlayer])
return "";
let message = "";
if (notifyPhase == "all")
{
if (msg.phaseState == "started")
message = translate("%(player)s is advancing to the %(phaseName)s.");
else if (msg.phaseState == "aborted")
message = translate("The %(phaseName)s of %(player)s has been aborted.");
}
if (msg.phaseState == "completed")
message = translate("%(player)s has reached the %(phaseName)s.");
return sprintf(message, {
"player": colorizePlayernameByID(msg.player),
"phaseName": getEntityNames(GetTechnologyData(msg.phaseName, g_Players[msg.player].civ))
});
}
function formatChatCommand(msg)
{
if (!msg.text)
return "";
let isMe = msg.text.indexOf("/me ") == 0;
if (!isMe && !parseChatAddressee(msg))
return "";
isMe = msg.text.indexOf("/me ") == 0;
if (isMe)
msg.text = msg.text.substr("/me ".length);
// Translate or escape text
if (!msg.text)
return "";
if (msg.translate)
{
msg.text = translate(msg.text);
if (msg.translateParameters)
{
let parameters = msg.parameters || {};
translateObjectKeys(parameters, msg.translateParameters);
msg.text = sprintf(msg.text, parameters);
}
}
else
{
msg.text = escapeText(msg.text);
let userName = g_PlayerAssignments[Engine.GetPlayerGUID()].name;
if (userName != g_PlayerAssignments[msg.guid].name)
notifyUser(userName, msg.text);
}
// GUID for players, playerID for AIs
let coloredUsername = msg.guid != -1 ? colorizePlayernameByGUID(msg.guid) : colorizePlayernameByID(msg.player);
return sprintf(g_ChatCommands[isMe ? "me" : "regular"][msg.context ? "context" : "no-context"], {
"message": msg.text,
"context": msg.context || undefined,
"user": coloredUsername,
"userTag": sprintf(translate("<%(user)s>"), { "user": coloredUsername })
});
}
/**
* Checks if the current user is an addressee of the chatmessage sent by another player.
* Sets the context and potentially addresseeGUID of that message.
* Returns true if the message should be displayed.
*
* @param {Object} msg
*/
function parseChatAddressee(msg)
{
if (msg.text[0] != '/')
return true;
// Split addressee command and message-text
msg.cmd = msg.text.split(/\s/)[0];
msg.text = msg.text.substr(msg.cmd.length + 1);
// GUID is "local" in singleplayer, some string in multiplayer.
// Chat messages sent by the simulation (AI) come with the playerID.
let senderID = msg.player ? msg.player : (g_PlayerAssignments[msg.guid] || msg).player;
let isSender = msg.guid ?
msg.guid == Engine.GetPlayerGUID() :
senderID == Engine.GetPlayerID();
// Parse private message
let isPM = msg.cmd == "/msg";
let addresseeGUID;
let addresseeIndex;
if (isPM)
{
addresseeGUID = matchUsername(msg.text);
let addressee = g_PlayerAssignments[addresseeGUID];
if (!addressee)
{
if (isSender)
warn("Couldn't match username: " + msg.text);
return false;
}
// Prohibit PM if addressee and sender are identical
if (isSender && addresseeGUID == Engine.GetPlayerGUID())
return false;
msg.text = msg.text.substr(addressee.name.length + 1);
addresseeIndex = addressee.player;
}
// Set context string
if (!g_ChatAddresseeContext[msg.cmd])
{
if (isSender)
warn("Unknown chat command: " + msg.cmd);
return false;
}
msg.context = g_ChatAddresseeContext[msg.cmd];
// For observers only permit public- and observer-chat and PM to observers
if (isPlayerObserver(senderID) &&
(isPM && !isPlayerObserver(addresseeIndex) || !isPM && msg.cmd != "/observers"))
return false;
let visible = isSender || g_IsChatAddressee[msg.cmd](senderID, addresseeGUID);
msg.isVisiblePM = isPM && visible;
return visible;
}
/**
* Returns the guid of the user with the longest name that is a prefix of the given string.
*/
function matchUsername(text)
{
if (!text)
return "";
let match = "";
let playerGUID = "";
for (let guid in g_PlayerAssignments)
{
let pName = g_PlayerAssignments[guid].name;
if (text.indexOf(pName + " ") == 0 && pName.length > match.length)
{
match = pName;
playerGUID = guid;
}
}
return playerGUID;
}
/**
* Custom dialog response handling, usable by trigger maps.
*/
function sendDialogAnswer(guiObject, dialogName)
{
Engine.GetGUIObjectByName(dialogName+"-dialog").hidden = true;
Engine.PostNetworkCommand({
"type": "dialog-answer",
"dialog": dialogName,
"answer": guiObject.name.split("-").pop(),
});
resumeGame();
}
/**
* Custom dialog opening, usable by trigger maps.
*/
function openDialog(dialogName, data, player)
{
let dialog = Engine.GetGUIObjectByName(dialogName + "-dialog");
if (!dialog)
{
warn("messages.js: Unknow dialog with name " + dialogName);
return;
}
dialog.hidden = false;
for (let objName in data)
{
let obj = Engine.GetGUIObjectByName(dialogName + "-dialog-" + objName);
if (!obj)
{
warn("messages.js: Key '" + objName + "' not found in '" + dialogName + "' dialog.");
continue;
}
for (let key in data[objName])
{
let n = data[objName][key];
if (typeof n == "object" && n.message)
{
let message = n.message;
if (n.translateMessage)
message = translate(message);
let parameters = n.parameters || {};
if (n.translateParameters)
translateObjectKeys(parameters, n.translateParameters);
obj[key] = sprintf(message, parameters);
}
else
obj[key] = n;
}
}
pauseGame();
}
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 19598)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 19599)
@@ -1,169 +1,170 @@
onTick();
onSimulationUpdate();
onReplayFinished();
onReplayOutOfSync();
this.hidden = !this.hidden;
Engine.ConfigDB_CreateValue("user", "gui.session.timeelapsedcounter", String(Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") != "true"));
Engine.ConfigDB_CreateValue("user", "gui.session.ceasefirecounter", String(Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") != "true"));
Game Paused
Click to Resume Game
togglePause();
-
+
+
Exit
leaveGame();
Index: ps/trunk/binaries/data/mods/public/gui/session/tutorial_panel.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/tutorial_panel.xml (nonexistent)
+++ ps/trunk/binaries/data/mods/public/gui/session/tutorial_panel.xml (revision 19599)
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+ Ready
+ Engine.PostNetworkCommand({"type": "dialog-answer", "tutorial": "ready"});
+
+
Property changes on: ps/trunk/binaries/data/mods/public/gui/session/tutorial_panel.xml
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/l10n/messages.json
===================================================================
--- ps/trunk/binaries/data/mods/public/l10n/messages.json (revision 19598)
+++ ps/trunk/binaries/data/mods/public/l10n/messages.json (revision 19599)
@@ -1,665 +1,684 @@
[
{
"output": "public-civilizations.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "json",
"filemasks": [
"simulation/data/civs/**.json"
],
"options": {
"keywords": [
"Name",
"Description",
"History",
"Special",
"AINames"
]
}
}
]
},
{
"output": "public-gui-ingame.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "javascript",
"filemasks": [
"gui/session/**.js"
],
"options": {
"format": "javascript-format",
"keywords": {
"translate": [1],
"translatePlural": [1, 2],
"translateWithContext": [[1], 2],
"translatePluralWithContext": [[1], 2, 3],
"markForTranslation": [1],
"markForTranslationWithContext": [[1], 2],
"markForPluralTranslation": [1, 2]
},
"commentTags": [
"Translation:"
]
}
},
{
"extractor": "xml",
"filemasks": [
"gui/session/**.xml"
],
"options": {
"keywords": {
"translatableAttribute": {
"locationAttributes": ["id"]
},
"translate": {}
}
}
}
]
},
{
"output": "public-gui-gamesetup.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "javascript",
"filemasks": [
"gui/aiconfig/**.js",
"gui/gamesetup/**.js",
"gui/loading/**.js"
],
"options": {
"format": "javascript-format",
"keywords": {
"translate": [1],
"translatePlural": [1, 2],
"translateWithContext": [[1], 2],
"translatePluralWithContext": [[1], 2, 3],
"markForTranslation": [1],
"markForTranslationWithContext": [[1], 2],
"markForPluralTranslation": [1, 2]
},
"commentTags": [
"Translation:"
]
}
},
{
"extractor": "xml",
"filemasks": [
"gui/aiconfig/**.xml",
"gui/gamesetup/**.xml",
"gui/loading/**.xml"
],
"options": {
"keywords": {
"translatableAttribute": {
"locationAttributes": ["id"]
},
"translate": {}
}
}
},
{
"extractor": "txt",
"filemasks": [
"gui/text/quotes.txt"
],
"options": {
}
}
]
},
{
"output": "public-gui-lobby.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "javascript",
"filemasks": [
"gui/lobby/**.js"
],
"options": {
"format": "javascript-format",
"keywords": {
"translate": [1],
"translatePlural": [1, 2],
"translateWithContext": [[1], 2],
"translatePluralWithContext": [[1], 2, 3],
"markForTranslation": [1],
"markForTranslationWithContext": [[1], 2],
"markForPluralTranslation": [1, 2]
},
"commentTags": [
"Translation:"
]
}
},
{
"extractor": "xml",
"filemasks": [
"gui/lobby/**.xml"
],
"options": {
"keywords": {
"translatableAttribute": {
"locationAttributes": ["id"]
},
"translate": {}
}
}
},
{
"extractor": "txt",
"filemasks": [
"gui/lobby/Terms_of_Service.txt",
"gui/lobby/Terms_of_Use.txt"
],
"options": {
}
}
]
},
{
"output": "public-gui-manual.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "javascript",
"filemasks": [
"gui/manual/**.js"
],
"options": {
"format": "javascript-format",
"keywords": {
"translate": [1],
"translatePlural": [1, 2],
"translateWithContext": [[1], 2],
"translatePluralWithContext": [[1], 2, 3],
"markForTranslation": [1],
"markForTranslationWithContext": [[1], 2],
"markForPluralTranslation": [1, 2]
},
"commentTags": [
"Translation:"
]
}
},
{
"extractor": "xml",
"filemasks": [
"gui/manual/**.xml"
],
"options": {
"keywords": {
"translatableAttribute": {
"locationAttributes": ["id"]
},
"translate": {}
}
}
},
{
"extractor": "txt",
"filemasks": [
"gui/manual/intro.txt",
"gui/manual/userreport.txt"
],
"options": {
}
}
]
},
{
"output": "public-gui-other.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "javascript",
"filemasks": [
"globalscripts/**.js",
"gui/civinfo/**.js",
"gui/common/**.js",
"gui/credits/**.js",
"gui/locale/**.js",
"gui/msgbox/**.js",
"gui/options/**.js",
"gui/pregame/**.js",
"gui/replaymenu/**.js",
"gui/savedgames/**.js",
"gui/splashscreen/**.js",
"gui/structree/**.js",
"gui/summary/**.js"
],
"options": {
"format": "javascript-format",
"keywords": {
"translate": [1],
"translatePlural": [1, 2],
"translateWithContext": [[1], 2],
"translatePluralWithContext": [[1], 2, 3],
"markForTranslation": [1],
"markForTranslationWithContext": [[1], 2],
"markForPluralTranslation": [1, 2]
},
"commentTags": [
"Translation:"
]
}
},
{
"extractor": "xml",
"filemasks": [
"globalscripts/**.xml",
"gui/civinfo/**.xml",
"gui/common/**.xml",
"gui/credits/**.xml",
"gui/locale/**.xml",
"gui/msgbox/**.xml",
"gui/options/**.xml",
"gui/pregame/**.xml",
"gui/replaymenu/**.xml",
"gui/savedgames/**.xml",
"gui/splashscreen/**.xml",
"gui/structree/**.xml",
"gui/summary/**.xml"
],
"options": {
"keywords": {
"translatableAttribute": {
"locationAttributes": ["id"]
},
"translate": {}
}
}
},
{
"extractor": "json",
"filemasks": [
"gui/credits/texts/**.json"
],
"options": {
"keywords": [
"Title",
"Subtitle"
]
}
},
{
"extractor": "json",
"filemasks": [
"gui/options/**.json"
],
"options": {
"keywords": [
"label",
"tooltip"
]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/resources/**.json"
],
"options": {
"keywords": [
"description"
]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/resources/**.json"
],
"options": {
"keywords": [
"name",
"subtypes"
],
"comments": [
"Translation: Word as used at the beginning of a sentence or as a single-word sentence."
],
"context": "firstWord"
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/resources/**.json"
],
"options": {
"keywords": [
"name",
"subtypes"
],
"comments": [
"Translation: Word as used in the middle of a sentence (which may require using lowercase for your language)."
],
"context": "withinSentence"
}
},
{
"extractor": "txt",
"filemasks": [
"gui/gamesetup/**.txt",
"gui/splashscreen/splashscreen.txt",
"gui/text/tips/**.txt"
],
"options": {
}
}
]
},
{
"output": "public-templates-units.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "xml",
"filemasks": [
"simulation/templates/template_unit_*.xml",
"simulation/templates/units/**.xml"
],
"options": {
"keywords": {
"GenericName": {},
"SpecificName": {},
"VisibleClasses": {
"splitOnWhitespace": true
},
"Tooltip": {},
"DisabledTooltip": {},
"FormationName": {},
"FromClass": {},
"Rank": {
"tagAsContext": true
}
}
}
}
]
},
{
"output": "public-templates-buildings.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "xml",
"filemasks": [
"simulation/templates/template_structure_*.xml",
"simulation/templates/structures/**.xml"
],
"options": {
"keywords": {
"GenericName": {},
"SpecificName": {},
"VisibleClasses": {
"splitOnWhitespace": true
},
"Tooltip": {},
"DisabledTooltip": {},
"FormationName": {},
"FromClass": {},
"Rank": {
"tagAsContext": true
}
}
}
}
]
},
{
"output": "public-templates-other.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "xml",
"filemasks": {
"includeMasks": [
"simulation/templates/**.xml"
],
"excludeMasks": [
"simulation/templates/structures/**.xml",
"simulation/templates/template_structure_*.xml",
"simulation/templates/template_unit_*.xml",
"simulation/templates/units/**.xml"
]
},
"options": {
"keywords": {
"GenericName": {},
"SpecificName": {},
"VisibleClasses": {
"splitOnWhitespace": true
},
"Tooltip": {},
"DisabledTooltip": {},
"FormationName": {},
"FromClass": {},
"Rank": {
"tagAsContext": true
}
}
}
}
]
},
{
"output": "public-simulation-auras.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "json",
"filemasks": [
"simulation/data/auras/**.json"
],
"options": {
"keywords": [
"auraName",
"auraDescription"
]
}
}
]
},
{
"output": "public-simulation-technologies.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "json",
"filemasks": [
"simulation/data/technologies/**.json"
],
"options": {
"keywords": [
"specificName",
"genericName",
"description",
"tooltip",
"requirementsTooltip"
]
}
}
]
},
{
"output": "public-simulation-other.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "javascript",
"filemasks": [
"simulation/ai/**.js",
"simulation/components/**.js",
"simulation/helpers/**.js"
],
"options": {
"format": "javascript-format",
"keywords": {
"translate": [1],
"translatePlural": [1, 2],
"translateWithContext": [[1], 2],
"translatePluralWithContext": [[1], 2, 3],
"markForTranslation": [1],
"markForTranslationWithContext": [[1], 2],
"markForPluralTranslation": [1, 2]
},
"commentTags": [
"Translation:"
]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/settings/player_defaults.json"
],
"options": {
"keywords": [
"Name"
]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/settings/game_speeds.json"
],
"options": {
"keywords": ["Title"]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/settings/victory_conditions/*.json"
],
"options": {
"keywords": ["Title", "Description"]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/settings/starting_resources.json"
],
"options": {
"keywords": ["Title"],
"context": "startingResources"
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/settings/map_sizes.json"
],
"options": {
"keywords": [
"Name"
]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/ai/**.json"
],
"options": {
"keywords": [
"name",
"description"
]
}
}
]
},
{
"output": "public-maps.pot",
"inputRoot": "..",
"project": "0 A.D. — Empires Ascendant",
"copyrightHolder": "Wildfire Games",
"rules": [
{
"extractor": "json",
"filemasks": [
"maps/random/**.json"
],
"options": {
"keywords": [
"Name",
"Description"
]
}
},
{
"extractor": "javascript",
"filemasks": [
"maps/scenarios/**.js",
"maps/skirmishes/**.js",
"maps/random/**.js"
],
"options": {
"format": "javascript-format",
"keywords": {
"markForTranslation": [1],
"markForTranslationWithContext": [[1], 2],
"markForPluralTranslation": [1, 2]
},
"commentTags": [
"Translation:"
]
}
},
{
"extractor": "xml",
"filemasks": [
"maps/scenarios/**.xml",
"maps/skirmishes/**.xml"
],
"options": {
"keywords": {
"ScriptSettings": {
"extractJson": {
"keywords": [
"Name",
"Description"
]
}
}
}
}
}
]
+ },
+ {
+ "output": "public-tutorials.pot",
+ "inputRoot": "..",
+ "project": "0 A.D. — Empires Ascendant",
+ "copyrightHolder": "Wildfire Games",
+ "rules": [
+ {
+ "extractor": "javascript",
+ "filemasks": [
+ "maps/tutorials/**.js"
+ ],
+ "options": {
+ "keywords": [
+ "instructions"
+ ]
+ }
+ }
+ ]
}
]
Index: ps/trunk/binaries/data/mods/public/maps/scripts/Tutorial.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scripts/Tutorial.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/maps/scripts/Tutorial.js (revision 19599)
@@ -0,0 +1,114 @@
+Trigger.prototype.InitTutorial = function(data)
+{
+ this.count = 0;
+ this.index = 0;
+ this.fullText = "";
+ this.tutorialEvents = [];
+
+ // Register needed triggers
+ this.RegisterTrigger("OnDeserialized", "OnDeserializedTrigger", { "enabled": true });
+ this.RegisterTrigger("OnPlayerCommand", "OnPlayerCommandTrigger", { "enabled": false });
+ this.tutorialEvents.push("OnPlayerCommand");
+
+ for (let goal of this.tutorialGoals)
+ {
+ for (let key in goal)
+ {
+ if (typeof goal[key] !== "function" || this.tutorialEvents.indexOf(key) != -1)
+ continue;
+ let action = key + "Trigger";
+ this.RegisterTrigger(key, action, { "enabled": false });
+ this.tutorialEvents.push(key);
+ }
+ }
+
+ this.NextGoal();
+};
+
+Trigger.prototype.NextGoal = function(deserializing = false)
+{
+ if (this.index > this.tutorialGoals.length)
+ return;
+ let goal = this.tutorialGoals[this.index];
+ let needDelay = true;
+ let readyButton = false;
+ for (let event of this.tutorialEvents)
+ {
+ let action = event + "Trigger";
+ if (goal[event])
+ {
+ Trigger.prototype[action] = goal[event];
+ this.EnableTrigger(event, action);
+ needDelay = false;
+ if (!deserializing)
+ this.count = 0;
+ }
+ else
+ this.DisableTrigger(event, action);
+ }
+ if (needDelay) // no actions for the next goal
+ {
+ if (goal.delay)
+ this.DoAfterDelay(+goal.delay, "NextGoal", {});
+ else
+ {
+ this.EnableTrigger("OnPlayerCommand", "OnPlayerCommandTrigger");
+ Trigger.prototype.OnPlayerCommandTrigger = function(msg)
+ {
+ if (msg.cmd.type == "dialog-answer" && msg.cmd.tutorial && msg.cmd.tutorial == "ready")
+ this.NextGoal();
+ };
+ readyButton = true;
+ }
+ }
+
+ this.GoalMessage(goal.instructions, readyButton, ++this.index == this.tutorialGoals.length);
+};
+
+Trigger.prototype.GoalMessage = function(text, readyButton=false, leave=false)
+{
+ let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGUIInterface.PushNotification({
+ "type": "tutorial",
+ "players": [1],
+ "message": text,
+ "translateMessage": true,
+ "readyButton": readyButton,
+ "leave": leave
+ });
+};
+
+Trigger.prototype.WarningMessage = function(txt)
+{
+ let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGUIInterface.PushNotification({
+ "type": "tutorial",
+ "players": [1],
+ "message": txt,
+ "translateMessage": true,
+ "warning": true
+ });
+};
+
+Trigger.prototype.OnPlayerCommandTrigger = function() {};
+
+Trigger.prototype.OnResearchQueuedTrigger = function() {};
+
+Trigger.prototype.OnResearchFinishedTrigger = function() {};
+
+Trigger.prototype.OnStructureBuiltTrigger = function() {};
+
+Trigger.prototype.OnTrainingQueuedTrigger = function() {};
+
+Trigger.prototype.OnTrainingFinishedTrigger = function() {};
+
+Trigger.prototype.OnDeserializedTrigger = function()
+{
+ this.index = Math.max(0, this.index - 1);
+
+ // Display messages from already processed goals
+ for (let i = 0; i < this.index; ++i)
+ this.GoalMessage(this.tutorialGoals[i].instructions, false, false);
+
+ this.NextGoal(true);
+};
Property changes on: ps/trunk/binaries/data/mods/public/maps/scripts/Tutorial.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js (revision 19599)
@@ -0,0 +1,279 @@
+let tutorialGoals = [
+ {
+ "instructions": markForTranslation("This tutorial will teach the basics of developing your economy. Typically, you will start with a Civic Center and a couple units in 'Village Phase' and ultimately, your goal will be to develop and expand your empire, often by evolving to 'Town Phase' and 'City Phase' afterward.\n\nBefore starting, you can toggle between fullscreen and windowed mode using Alt+Enter. You can also change the level of zoom using the mouse wheel and the camera view using any of your keyboard's arrow keys.\nAdjust the game window to your preferences.\n"),
+ },
+ {
+ "instructions": markForTranslation("To start off, select your building, the Civic Center, by clicking on it. A selection ring in the color of your civilization will be displayed after clicking."),
+ },
+ {
+ "instructions": markForTranslation("Now that the Civic Center is selected, you will notice that a production panel will appear on the lower right of your screen detailing the actions that the buildings support. For the production panel, available actions are not masked in any color, while an icon masked in either grey or red indicates that the action has not been unlocked or you do not have sufficient resources to perform that action, respectively. Additionally, you can hover your mouse over icon to show a tooltip with more details.\nThe top row of buttons contains portraits of units that may be trained at the building while the bottom one or two rows will have researchable technologies. Hover your mouse over the 'II' icon. The tooltip will tell us that advancing to 'Town Phase' requires both more constructed structures as well as more Food and Wood resources."),
+ },
+ {
+ "instructions": markForTranslation("You have two main types of starting units: Female Citizens and Citizen Soldiers. Female Citizens are purely economic units; they have low HP, no armour, and little to no attack. Citizen Soldiers are workers by default, but in times of need, can utilizing a weapon to fight. You have two categories of Citizen Soldiers: Infantry and Cavalry. Female Citizens and Infantry Citizen Soldiers can gather any land resources while Cavalry Citizen Soldiers can only gather meat from hunted animals.\n"),
+ },
+ {
+ "instructions": markForTranslation("As a general rule of thumb, left-clicking represents selection while right-clicking with an entity selected represents an order (gather, build, fight, etc.).\n"),
+ },
+ {
+ "instructions": markForTranslation("At this point, Food and Wood are the most important resources for developing your economy, so let's start with gathering food. Females gather non-meat food faster than their male counterparts.\nThere are primarily three ways to select units:\n1) Hold the left mouse button and drag a selection rectangle that encloses the units you want to select.\n2) Click on one of them and then add additional units to your selection by shift-clicking each additional unit (or also via the above selection rectangle).\n3) Double-click on a unit. This will select every unit of the same type as the specified unit in your visible window. Triple-click will select all units of the same type on the entire map.\n You can click on an empty space on the map to reset the selection. Try each of these methods before tasking all of your Female Citizens to gather the grapes to the southeast of your Civic Center by right-clicking on the grapes when you have all the Female Citizens selected."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "gather" && msg.cmd.target &&
+ TriggerHelper.GetResourceType(msg.cmd.target).specific == "fruit")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("Now, let's gather some Wood with your Infantry Citizen Soldiers. Select your Infantry Citizen Soldiers and order them to gather Wood by right-clicking on the nearest tree."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "gather" && msg.cmd.target &&
+ TriggerHelper.GetResourceType(msg.cmd.target).specific == "tree")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("Cavalry Citizen Soldiers are good for hunting. Select your cavalry and order him to hunt the chicken around your Civic Center in similar fashion."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "gather" && msg.cmd.target &&
+ TriggerHelper.GetResourceType(msg.cmd.target).specific == "meat")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("All your units are now gathering resources. We should train more units!\nFirst, let's set a rally-point. Setting a rally point on a building that can train units will automatically designate a task to the new unit upon completion of training. We want to send the newly trained units to gather Wood on the group of trees to the south of the Civic Center. To do so, select the Civic Center by clicking on it and then right-click on one of the trees.\nRally-Points are indicated by a small flag at the end of the blue line."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type != "set-rallypoint" || !msg.cmd.data ||
+ !msg.cmd.data.command || msg.cmd.data.command != "gather" ||
+ !msg.cmd.data.resourceType || msg.cmd.data.resourceType.specific != "tree")
+ {
+ this.WarningMessage(markForTranslation("Select the Civic Center, then hover your mouse over the tree and right-click when you see your cursor change into a Wood icon."));
+ return;
+ }
+ this.NextGoal();
+ }
+
+ },
+ {
+ "instructions": markForTranslation("Now that the rally-point is set, we can produce additional units and they will do their assigned task automatically.\nAs Citizen Soldiers are better than Female Citizens for gathering Wood, select the Civic Center and shift-click on the second unit icon, the hoplites (shift-clicking produces a batch of five units). You can also train units individually by simply clicking, but training 5 units together takes less time than training 5 units individually."),
+ "OnTrainingQueued": function(msg)
+ {
+ if (msg.unitTemplate != "units/athen_infantry_spearman_b" || +msg.count == 1)
+ {
+ let entity = msg.trainerEntity;
+ let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
+ cmpProductionQueue.ResetQueue();
+ let txt = +msg.count == 1 ?
+ markForTranslation("Do not forget to shift-click to produce several units.") :
+ markForTranslation("Shift-click on the HOPLITE icon.");
+ this.WarningMessage(txt);
+ return;
+ }
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("Let's wait for the units to be trained.\nWhile waiting, direct your attention to the panel at the top of your screen. On the upper left, you will see your current resource supply (Food, Wood, Stone, and Metal). As each worker brings resources back to the Civic Center (or another dropsite), you will see the amount of the corresponding resource increase. This is a very important concept to keep in mind: gathered resources have to be brought back to a dropsite to be accounted, and you should always try to minimize the distance between resource and nearest dropsite to improve your gathering efficiency."),
+ "OnTrainingFinished": function(msg)
+ {
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("The newly trained units automatically go to the trees and start gathering Wood.\nBut as they have to bring it back to the Civic Center to deposit it, their gathering efficiency suffers from the distance. To fix that, we can build a storehouse, a dropsite for Wood, Stone, and Metal, close to the trees. To do so, select your five newly trained Citizen Solders and look for the construction panel on the bottom right, click on the storehouse icon, move the mouse as close as possible to the trees you want to collect and click on a valid place to build the dropsite.\nInvalid (obstructed) positions will show the building preview overlay in red."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "construct" && msg.cmd.template == "structures/athen_storehouse")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("The selected workers will automatically start constructing the building once you place the foundation."),
+ "OnStructureBuilt": function(msg)
+ {
+ let cmpResourceDropsite = Engine.QueryInterface(msg.building, IID_ResourceDropsite);
+ if (cmpResourceDropsite && cmpResourceDropsite.AcceptsType("wood"))
+ this.NextGoal();
+ },
+ },
+ {
+ "instructions": markForTranslation("When construction finishes, the builders default to gathering Wood automatically.\nLet's train some female workers to gather more food. Select the Civic Center and shift-click on the female citizen icon to train 5."),
+ "OnTrainingQueued": function(msg)
+ {
+ if (msg.unitTemplate != "units/athen_support_female_citizen" || +msg.count == 1)
+ {
+ let entity = msg.trainerEntity;
+ let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
+ cmpProductionQueue.ResetQueue();
+ let txt = +msg.count == 1 ?
+ markForTranslation("Do not forget to shift-click to produce several units.") :
+ markForTranslation("Shift-click on the FEMALE CITIZEN icon.");
+ this.WarningMessage(txt);
+ return;
+ }
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("Let's wait for the units to be trained.\nIn the meantime, we seem to have enough workers gathering Wood. We should remove the current rally-point of the Civic Center away from gathering Wood. For that purpose, right-click on the Civic Center when it is selected (and the flag icon indicating the rally-point is crossed out)."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "unset-rallypoint")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("The units should be ready soon.\nIn the meantime, direct your attention to your population count on the top panel. It is the fifth item from the left, after the resources. It would be prudent to keep an eye on it. It indicates your current population (including those being trained) and the current population limit, which is determined by your built structures."),
+ "OnTrainingFinished": function(msg)
+ {
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("As you have nearly reached the population limit, you must increase it by building some new structures if you want to train more units. The most cost effective structure to increase your population limit is the house.\nNow that the units are ready, let's build two houses."),
+ },
+ {
+ "instructions": markForTranslation("Select two of your newly trained Female Citizens and ask them to build these houses in the empty space to the east of the Civic Center. To do so, after selecting the Female Citizens, click on the house icon in the bottom right panel and shift-click on the position in the map where you want to build the first house followed by a shift-click on the position of the second house (when giving orders, shift-click put the order in the queue and the units will automatically switch to the next order in their queue when they finish their work). Press Escape to get rid of the house cursor so you don't spam houses all over the map.\nReminder: to select only two Female Citizens, click on the first one and then shift-click on the second one to add her to the selection."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "construct" && msg.cmd.template == "structures/athen_house" &&
+ ++this.count == 2)
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("You may notice that berries are a finite supply of food. We will need a more lasting food source. Fields produce an unlimited food resource, but are slower to gather than forageable fruits.\nBut to minimize the distance between a farm and its corresponding food dropsite, we will first build a farmstead."),
+ },
+ {
+ "instructions": markForTranslation("Select the three remaining (idle) Female Citizens and order them to build a farmstead in the center of the large open area to the west of the Civic Center.\nWe will need a decent chunk of space around the farmstead to build fields. In addition, we can see goats on the west side to further improve our food gathering efficiency should we ever decide to hunt them.\nIf you try to select the three idle Female Citizens by selection rectangle them, there is a high chance that one additional gatherers are included in your selection rectangle. To prevent that, hold the 'i' key while grabing: only idle units are then selected. If during your selection you select the cavalry which may now be idle, you can remove it by pressing the control key while clicking on its corresponding portrait in the selection panel on the bottom."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "construct" && msg.cmd.template == "structures/athen_farmstead")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("When the farmstead construction is finished, its builders will automatically look for food, and in this case, they will go after the nearby goats.\nBut your house builders will only look for something else to build and, if nothing found, become idle. Let's wait for them to build the houses."),
+ "OnStructureBuilt": function(msg)
+ {
+ if (TriggerHelper.EntityHasClass(msg.building, "House") && ++this.count == 2)
+ this.NextGoal();
+ },
+ },
+ {
+ "name": "buildField",
+ "instructions": markForTranslation("When both houses are built, select your two Female Citizens and order them to build a field as close as possible to the farmstead, which is a dropsite for all types of food."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "construct" && msg.cmd.template == "structures/athen_field")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("When the field is constructed, the builders will automatically start gathering it.\nThe cavalry unit should have slaughtered all chicken by now. Select it and right-click on one of the goats in the west to start hunting them for food."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "gather" && msg.cmd.target &&
+ TriggerHelper.GetResourceType(msg.cmd.target).specific == "meat")
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("A field can have up to five farmers working on it. To add additional gathereres, select the Civic Center and setup a rally-point on a field by right-clicking on it. As long as the field is not yet build, new workers sent by a rally-point will help building it, while they will gather it when built."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type != "set-rallypoint" || !msg.cmd.data || !msg.cmd.data.command ||
+ (msg.cmd.data.command != "build" || !msg.cmd.data.target || !TriggerHelper.EntityHasClass(msg.cmd.data.target, "Field")) &&
+ (msg.cmd.data.command != "gather" || !msg.cmd.data.resourceType || msg.cmd.data.resourceType.specific != "grain"))
+ {
+ this.WarningMessage(markForTranslation("Select the Civic Center and right-click on the field."));
+ return;
+ }
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("Now click three times on the female icon in the bottom right panel to train three additional farmers."),
+ "OnTrainingQueued": function(msg)
+ {
+ if (msg.unitTemplate != "units/athen_support_female_citizen" || +msg.count != 1)
+ {
+ let entity = msg.trainerEntity;
+ let cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue);
+ cmpProductionQueue.ResetQueue();
+ let txt = +msg.count == 1 ?
+ markForTranslation("Do a simple left-click to produce a single unit.") :
+ markForTranslation("Click on the FEMALE CITIZEN icon.");
+ this.WarningMessage(txt);
+ return;
+ }
+ if (++this.count < 3)
+ return;
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("You can increase the gather rates of your workers by researching new technologies available in some buildings.\nThe farming rate, for example, can be improved with a researchable technology in the farmstead. Select the farmstead and look at its production panel on the bottom right. You will see several researchable technologies. Hover the mouse over them to see their costs and effects and click on the one you want to research."),
+ "OnResearchQueued": function(msg)
+ {
+ if (msg.technologyTemplate && TriggerHelper.EntityHasClass(msg.researcherEntity, "Farmstead"))
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("We should start preparing to phase up into 'Town Phase', which will unlock many more units and buildings. Select the Civic Center and hover the mouse over the 'Town Phase' icon to see what is still needed.\nWe now have enough resources, but one structure is missing. Although this is an economic tutorial, it is nonetheless useful to be prepared for defense in case of attack, so let's build barracks.\nSelect four of your soldiers and ask them to build a barracks: as before, start selecting the soldiers, click on the barracks icon in the production panel and then lay down a foundation not far from your Civic Center where you want to build."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "construct" && msg.cmd.template == "structures/athen_barracks")
+ this.NextGoal();
+ }
+
+ },
+ {
+ "instructions": markForTranslation("Let's wait for the barracks to be built. As this construction is lengthy, you can add two soldiers to build it faster. To do so, select your Civic Center and set up a rally-point on the barracks foundation by right-clicking on it (you should see a hammer icon), and then produce two more builders by clicking on the hoplite icon twice."),
+ "OnStructureBuilt": function(msg)
+ {
+ if (TriggerHelper.EntityHasClass(msg.building, "Barracks"))
+ this.NextGoal();
+ },
+ },
+ {
+ "instructions": markForTranslation("You should now be able research 'Town Phase'. Select the Civic Center and click on the technology icon.\nIf you still miss some resources (icon with red overlay), wait for them to be gathered by your workers."),
+ "OnResearchQueued": function(msg)
+ {
+ if (msg.technologyTemplate && TriggerHelper.EntityHasClass(msg.researcherEntity, "CivilCentre"))
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("In later phases, you need usually Stone and Metal to build bigger structures and train better soldiers. Hence, while waiting for the research to be done, you will send half of your idle Citizen Soldiers (who have finished building the Barracks) to gather Stone and the other half to gather Metal.\nTo do so, we could select three Citizen Soldiers and right-click on the Stone mine on the west of the Civic Center (a Stone cursor is shown when you hover the mouse over it while your soldiers are selected). However, these soldiers were gathering Wood, so they may still carry some Wood which would be lost when starting to gather another resource."),
+ },
+ {
+ "instructions": markForTranslation("Thus, we should order them to deposit their Wood in the Civic Center along the way. To do so, we will queue orders with shift-click: select your soldiers, shift-right-click on the Civic Center to deposit their Wood and then shift-right-click on the Stone mine to gather it.\nPerform a similar order queue with the remaining soldiers and the Metal mine in the west."),
+ "OnPlayerCommand": function(msg)
+ {
+ if (msg.cmd.type == "gather" && msg.cmd.target &&
+ TriggerHelper.GetResourceType(msg.cmd.target).generic == "stone" &&
+ ++this.count == 3)
+ this.NextGoal();
+ if (msg.cmd.type == "gather" && msg.cmd.target &&
+ TriggerHelper.GetResourceType(msg.cmd.target).generic == "metal" &&
+ ++this.count == 3)
+ this.NextGoal();
+ },
+ "OnResearchFinished": function(msg)
+ {
+ if (msg.tech == "phase_town_athen" && ++this.count == 3)
+ this.NextGoal();
+ }
+ },
+ {
+ "instructions": markForTranslation("This is the end of the walkthrough. This should give you a good idea of the basics of setting up your economy.")
+ }
+];
+
+Trigger.prototype.tutorialGoals = tutorialGoals;
+var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+cmpTrigger.RegisterTrigger("OnInitGame", "InitTutorial", { "enabled": true });
Property changes on: ps/trunk/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.xml (revision 19598)
+++ ps/trunk/binaries/data/mods/public/maps/tutorials/starting_economy_walkthrough.xml (revision 19599)
@@ -1,8780 +1,8704 @@
default
0
0.5
lake
19.9
4.0
0.45
0
1
0.99
0.1999
default
-
- structures/cart_civil_centre
- 2
-
-
-
-
- units/cart_support_female_citizen
- 2
-
-
-
-
- units/cart_support_female_citizen
- 2
-
-
-
-
- units/cart_support_female_citizen
- 2
-
-
-
-
- units/cart_support_female_citizen
- 2
-
-
-
-
- units/cart_infantry_spearman_b
- 2
-
-
-
-
- units/cart_infantry_spearman_b
- 2
-
-
-
-
- units/cart_infantry_spearman_b
- 2
-
-
-
-
- units/cart_infantry_spearman_b
- 2
-
-
-
-
- units/cart_cavalry_javelinist_b
- 2
-
-
-
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_dry_a.xml
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/geology_metal_desert_slabs
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/plant_desert_a.xml
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
structures/athen_civil_centre
1
units/athen_support_female_citizen
1
units/athen_support_female_citizen
1
units/athen_support_female_citizen
1
units/athen_support_female_citizen
1
units/athen_infantry_spearman_b
1
units/athen_infantry_spearman_b
1
units/athen_infantry_spearman_b
1
units/athen_infantry_spearman_b
1
units/athen_cavalry_javelinist_b
1
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
-
- gaia/fauna_chicken
- 0
-
-
-
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/fauna_chicken
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/flora_bush_grapes
0
gaia/geology_metal_desert_slabs
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/plant_desert_a.xml
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stonemine_desert_badlands_quarry
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/plant_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/plant_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/plant_desert_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
gaia/geology_stone_desert_small
0
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_dry_a.xml
actor|props/flora/bush_dry_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/plant_desert_a.xml
actor|props/flora/bush_desert_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/plant_desert_a.xml
gaia/geology_metal_desert_slabs
0
actor|props/flora/bush_dry_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|props/flora/plant_desert_a.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|geology/stone_desert_med.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_dry_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
actor|props/flora/bush_desert_a.xml
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_goat
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_sheep
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/fauna_camel
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_tamarix
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
gaia/flora_tree_oak
0
Index: ps/trunk/binaries/data/mods/public/simulation/components/Trigger.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Trigger.js (revision 19598)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Trigger.js (revision 19599)
@@ -1,327 +1,333 @@
function Trigger() {}
Trigger.prototype.Schema =
"";
/**
* Events we're able to receive and call handlers for.
*/
Trigger.prototype.eventNames =
[
"CinemaPathEnded",
"CinemaQueueEnded",
"ConstructionStarted",
"DiplomacyChanged",
+ "Deserialized",
"InitGame",
"Interval",
"OwnershipChanged",
"PlayerCommand",
"PlayerDefeated",
"PlayerWon",
"Range",
"ResearchFinished",
"ResearchQueued",
"StructureBuilt",
"TrainingFinished",
"TrainingQueued",
"TreasureCollected"
];
Trigger.prototype.Init = function()
{
this.triggerPoints = {};
// Each event has its own set of actions determined by the map maker.
for (let eventName of this.eventNames)
this["On" + eventName + "Actions"] = {};
};
Trigger.prototype.RegisterTriggerPoint = function(ref, ent)
{
if (!this.triggerPoints[ref])
this.triggerPoints[ref] = [];
this.triggerPoints[ref].push(ent);
};
Trigger.prototype.RemoveRegisteredTriggerPoint = function(ref, ent)
{
if (!this.triggerPoints[ref])
{
warn("no trigger points found with ref "+ref);
return;
}
let i = this.triggerPoints[ref].indexOf(ent);
if (i == -1)
{
warn("entity " + ent + " wasn't found under the trigger points with ref "+ref);
return;
}
this.triggerPoints[ref].splice(i, 1);
};
Trigger.prototype.GetTriggerPoints = function(ref)
{
return this.triggerPoints[ref] || [];
};
/**
* Binds a function to the specified event.
*
* @param {string} event - One of eventNames
* @param {string} action - Name of a function available to this object
* @param {Object} data - f.e. enabled or not, delay for timers, range for range triggers
*
* @example
* data = { enabled: true, interval: 1000, delay: 500 }
*
* Range trigger:
* data.entities = [id1, id2] * Ids of the source
* data.players = [1,2,3,...] * list of player ids
* data.minRange = 0 * Minimum range for the query
* data.maxRange = -1 * Maximum range for the query (-1 = no maximum)
* data.requiredComponent = 0 * Required component id the entities will have
* data.enabled = false * If the query is enabled by default
*/
Trigger.prototype.RegisterTrigger = function(event, action, data)
{
let eventString = event + "Actions";
if (!this[eventString])
{
warn("Trigger.js: Invalid trigger event \"" + event + "\".");
return;
}
if (this[eventString][action])
{
warn("Trigger.js: Trigger \"" + action + "\" has been registered before. Aborting...");
return;
}
// clone the data to be sure it's only modified locally
// We could run into triggers overwriting each other's data otherwise.
// F.e. getting the wrong timer tag
data = clone(data) || { "enabled": false };
this[eventString][action] = data;
// setup range query
if (event == "OnRange")
{
if (!data.entities)
{
warn("Trigger.js: Range triggers should carry extra data");
return;
}
data.queries = [];
for (let ent of data.entities)
{
let cmpTriggerPoint = Engine.QueryInterface(ent, IID_TriggerPoint);
if (!cmpTriggerPoint)
{
warn("Trigger.js: Range triggers must be defined on trigger points");
continue;
}
data.queries.push(cmpTriggerPoint.RegisterRangeTrigger(action, data));
}
}
if (data.enabled)
this.EnableTrigger(event, action);
};
Trigger.prototype.DisableTrigger = function(event, action)
{
let eventString = event + "Actions";
if (!this[eventString][action])
{
warn("Trigger.js: Disabling unknown trigger");
return;
}
let data = this[eventString][action];
// special casing interval and range triggers for performance
if (event == "OnInterval")
{
if (!data.timer) // don't disable it a second time
return;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(data.timer);
data.timer = null;
}
else if (event == "OnRange")
{
if (!data.queries)
{
warn("Trigger.js: Range query wasn't set up before trying to disable it.");
return;
}
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
for (let query of data.queries)
cmpRangeManager.DisableActiveQuery(query);
}
data.enabled = false;
};
Trigger.prototype.EnableTrigger = function(event, action)
{
let eventString = event + "Actions";
if (!this[eventString][action])
{
warn("Trigger.js: Enabling unknown trigger");
return;
}
let data = this[eventString][action];
// special casing interval and range triggers for performance
if (event == "OnInterval")
{
if (data.timer) // don't enable it a second time
return;
if (!data.interval)
{
warn("Trigger.js: An interval trigger should have an intervel in its data");
return;
}
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
data.timer = cmpTimer.SetInterval(this.entity, IID_Trigger, "DoAction",
data.delay || 0, data.interval, { "action" : action });
}
else if (event == "OnRange")
{
if (!data.queries)
{
warn("Trigger.js: Range query wasn't set up before");
return;
}
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
for (let query of data.queries)
cmpRangeManager.EnableActiveQuery(query);
}
data.enabled = true;
};
/**
* This function executes the actions bound to the events.
* It's either called directlty from other simulation scripts,
* or from message listeners in this file
*
* @param {string} event - One of eventNames
* @param {Object} data - will be passed to the actions
*/
Trigger.prototype.CallEvent = function(event, data)
{
let eventString = "On" + event + "Actions";
if (!this[eventString])
{
warn("Trigger.js: Unknown trigger event called:\"" + event + "\".");
return;
}
for (let action in this[eventString])
if (this[eventString][action].enabled)
this.DoAction({ "action": action, "data":data });
};
Trigger.prototype.OnGlobalInitGame = function(msg)
{
this.CallEvent("InitGame", {});
};
Trigger.prototype.OnGlobalConstructionFinished = function(msg)
{
this.CallEvent("StructureBuilt", { "building": msg.newentity });
};
Trigger.prototype.OnGlobalTrainingFinished = function(msg)
{
this.CallEvent("TrainingFinished", msg);
// The data for this one is {"entities": createdEnts,
// "owner": cmpOwnership.GetOwner(),
// "metadata": metadata}
// See function "SpawnUnits" in ProductionQueue for more details
};
Trigger.prototype.OnGlobalResearchFinished = function(msg)
{
this.CallEvent("ResearchFinished", msg);
// The data for this one is { "player": playerID, "tech": tech }
};
Trigger.prototype.OnGlobalCinemaPathEnded = function(msg)
{
this.CallEvent("CinemaPathEnded", msg);
};
Trigger.prototype.OnGlobalCinemaQueueEnded = function(msg)
{
this.CallEvent("CinemaQueueEnded", msg);
};
+Trigger.prototype.OnGlobalDeserialized = function(msg)
+{
+ this.CallEvent("Deserialized", msg);
+};
+
Trigger.prototype.OnGlobalOwnershipChanged = function(msg)
{
this.CallEvent("OwnershipChanged", msg);
// data is {"entity": ent, "from": playerId, "to": playerId}
};
Trigger.prototype.OnGlobalPlayerDefeated = function(msg)
{
this.CallEvent("PlayerDefeated", msg);
};
Trigger.prototype.OnGlobalPlayerWon = function(msg)
{
this.CallEvent("PlayerWon", msg);
};
Trigger.prototype.OnGlobalDiplomacyChanged = function(msg)
{
this.CallEvent("DiplomacyChanged", msg);
};
/**
* Execute a function after a certain delay.
*
* @param {Number} time - Delay in milliseconds.
* @param {String} action - Name of the action function.
* @param {Object} data - Arbitrary object that will be passed to the action function.
* @return {Number} The ID of the timer, so it can be stopped later.
*/
Trigger.prototype.DoAfterDelay = function(time, action, data)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
return cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Trigger, "DoAction", time, {
"action": action,
"data": data
});
};
/**
* Execute a function each time a certain delay has passed.
*
* @param {Number} interval - Interval in milleseconds between consecutive calls.
* @param {String} action - Name of the action function.
* @param {Object} data - Arbitrary object that will be passed to the action function.
* @param {Number} [start] - Optional initial delay in milleseconds before starting the calls.
* If not given, interval will be used.
* @return {Number} the ID of the timer, so it can be stopped later.
*/
Trigger.prototype.DoRepeatedly = function(time, action, data, start)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
return cmpTimer.SetInterval(SYSTEM_ENTITY, IID_Trigger, "DoAction", start !== undefined ? start : time, time, {
"action": action,
"data": data
});
};
/**
* Called by the trigger listeners to exucute the actual action. Including sanity checks.
*/
Trigger.prototype.DoAction = function(msg)
{
if (this[msg.action])
this[msg.action](msg.data || null);
else
warn("Trigger.js: called a trigger action '" + msg.action + "' that wasn't found");
};
Engine.RegisterSystemComponentType(IID_Trigger, "Trigger", Trigger);
Index: ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp
===================================================================
--- ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp (revision 19598)
+++ ps/trunk/source/simulation2/scripting/MessageTypeConversions.cpp (revision 19599)
@@ -1,537 +1,536 @@
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
-
#include "precompiled.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/MessageTypes.h"
#define TOJSVAL_SETUP() \
JSContext* cx = scriptInterface.GetContext(); \
JSAutoRequest rq(cx); \
JS::RootedObject obj(cx, JS_NewPlainObject(cx)); \
if (!obj) \
return JS::UndefinedValue();
#define SET_MSG_PROPERTY(name) \
do { \
JS::RootedValue prop(cx);\
ScriptInterface::ToJSVal(cx, &prop, this->name); \
if (! JS_SetProperty(cx, obj, #name, prop)) \
return JS::UndefinedValue(); \
} while (0);
#define FROMJSVAL_SETUP() \
JSContext* cx = scriptInterface.GetContext(); \
JSAutoRequest rq(cx); \
if (val.isPrimitive()) \
return NULL; \
JS::RootedObject obj(cx, &val.toObject()); \
JS::RootedValue prop(cx);
#define GET_MSG_PROPERTY(type, name) \
type name; \
{ \
if (! JS_GetProperty(cx, obj, #name, &prop)) \
return NULL; \
if (! ScriptInterface::FromJSVal(cx, prop, name)) \
return NULL; \
}
JS::Value CMessage::ToJSValCached(ScriptInterface& scriptInterface) const
{
if (!m_Cached)
m_Cached.reset(new JS::PersistentRootedValue(scriptInterface.GetJSRuntime(), ToJSVal(scriptInterface)));
return m_Cached->get();
}
////////////////////////////////
JS::Value CMessageTurnStart::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
return JS::ObjectValue(*obj);
}
CMessage* CMessageTurnStart::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
return new CMessageTurnStart();
}
////////////////////////////////
#define MESSAGE_1(name, t0, a0) \
JS::Value CMessage##name::ToJSVal(ScriptInterface& scriptInterface) const \
{ \
TOJSVAL_SETUP(); \
SET_MSG_PROPERTY(a0); \
return JS::ObjectValue(*obj); \
} \
CMessage* CMessage##name::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val) \
{ \
FROMJSVAL_SETUP(); \
GET_MSG_PROPERTY(t0, a0); \
return new CMessage##name(a0); \
}
MESSAGE_1(Update, fixed, turnLength)
MESSAGE_1(Update_MotionFormation, fixed, turnLength)
MESSAGE_1(Update_MotionUnit, fixed, turnLength)
MESSAGE_1(Update_Final, fixed, turnLength)
////////////////////////////////
JS::Value CMessageInterpolate::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(deltaSimTime);
SET_MSG_PROPERTY(offset);
SET_MSG_PROPERTY(deltaRealTime);
return JS::ObjectValue(*obj);
}
CMessage* CMessageInterpolate::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(float, deltaSimTime);
GET_MSG_PROPERTY(float, offset);
GET_MSG_PROPERTY(float, deltaRealTime);
return new CMessageInterpolate(deltaSimTime, offset, deltaRealTime);
}
////////////////////////////////
JS::Value CMessageRenderSubmit::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
LOGWARNING("CMessageRenderSubmit::ToJSVal not implemented");
return JS::UndefinedValue();
}
CMessage* CMessageRenderSubmit::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
LOGWARNING("CMessageRenderSubmit::FromJSVal not implemented");
return NULL;
}
////////////////////////////////
JS::Value CMessageProgressiveLoad::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
LOGWARNING("CMessageProgressiveLoad::ToJSVal not implemented");
return JS::UndefinedValue();
}
CMessage* CMessageProgressiveLoad::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
LOGWARNING("CMessageProgressiveLoad::FromJSVal not implemented");
return NULL;
}
////////////////////////////////
-JS::Value CMessageDeserialized::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
+JS::Value CMessageDeserialized::ToJSVal(ScriptInterface& scriptInterface) const
{
- LOGWARNING("CMessageDeserialized::ToJSVal not implemented");
- return JS::UndefinedValue();
+ TOJSVAL_SETUP();
+ return JS::ObjectValue(*obj);
}
-CMessage* CMessageDeserialized::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
+CMessage* CMessageDeserialized::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
- LOGWARNING("CMessageDeserialized::FromJSVal not implemented");
- return NULL;
+ FROMJSVAL_SETUP();
+ return new CMessageDeserialized();
}
////////////////////////////////
JS::Value CMessageCreate::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
return JS::ObjectValue(*obj);
}
CMessage* CMessageCreate::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
return new CMessageCreate(entity);
}
////////////////////////////////
JS::Value CMessageDestroy::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
return JS::ObjectValue(*obj);
}
CMessage* CMessageDestroy::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
return new CMessageDestroy(entity);
}
////////////////////////////////
JS::Value CMessageOwnershipChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
SET_MSG_PROPERTY(from);
SET_MSG_PROPERTY(to);
return JS::ObjectValue(*obj);
}
CMessage* CMessageOwnershipChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
GET_MSG_PROPERTY(player_id_t, from);
GET_MSG_PROPERTY(player_id_t, to);
return new CMessageOwnershipChanged(entity, from, to);
}
////////////////////////////////
JS::Value CMessagePositionChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
SET_MSG_PROPERTY(inWorld);
SET_MSG_PROPERTY(x);
SET_MSG_PROPERTY(z);
SET_MSG_PROPERTY(a);
return JS::ObjectValue(*obj);
}
CMessage* CMessagePositionChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
GET_MSG_PROPERTY(bool, inWorld);
GET_MSG_PROPERTY(entity_pos_t, x);
GET_MSG_PROPERTY(entity_pos_t, z);
GET_MSG_PROPERTY(entity_angle_t, a);
return new CMessagePositionChanged(entity, inWorld, x, z, a);
}
////////////////////////////////
JS::Value CMessageInterpolatedPositionChanged::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
LOGWARNING("CMessageInterpolatedPositionChanged::ToJSVal not implemented");
return JS::UndefinedValue();
}
CMessage* CMessageInterpolatedPositionChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
LOGWARNING("CMessageInterpolatedPositionChanged::FromJSVal not implemented");
return NULL;
}
////////////////////////////////
JS::Value CMessageTerritoryPositionChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
SET_MSG_PROPERTY(newTerritory);
return JS::ObjectValue(*obj);
}
CMessage* CMessageTerritoryPositionChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
GET_MSG_PROPERTY(player_id_t, newTerritory);
return new CMessageTerritoryPositionChanged(entity, newTerritory);
}
////////////////////////////////
JS::Value CMessageMotionChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(starting);
SET_MSG_PROPERTY(error);
return JS::ObjectValue(*obj);
}
CMessage* CMessageMotionChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(bool, starting);
GET_MSG_PROPERTY(bool, error);
return new CMessageMotionChanged(starting, error);
}
////////////////////////////////
JS::Value CMessageTerrainChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(i0);
SET_MSG_PROPERTY(j0);
SET_MSG_PROPERTY(i1);
SET_MSG_PROPERTY(j1);
return OBJECT_TO_JSVAL(obj);
}
CMessage* CMessageTerrainChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(int32_t, i0);
GET_MSG_PROPERTY(int32_t, j0);
GET_MSG_PROPERTY(int32_t, i1);
GET_MSG_PROPERTY(int32_t, j1);
return new CMessageTerrainChanged(i0, i1, j0, j1);
}
////////////////////////////////
JS::Value CMessageVisibilityChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(player);
SET_MSG_PROPERTY(ent);
SET_MSG_PROPERTY(oldVisibility);
SET_MSG_PROPERTY(newVisibility);
return JS::ObjectValue(*obj);
}
CMessage* CMessageVisibilityChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(player_id_t, player);
GET_MSG_PROPERTY(entity_id_t, ent);
GET_MSG_PROPERTY(int, oldVisibility);
GET_MSG_PROPERTY(int, newVisibility);
return new CMessageVisibilityChanged(player, ent, oldVisibility, newVisibility);
}
////////////////////////////////
JS::Value CMessageWaterChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
return OBJECT_TO_JSVAL(obj);
}
CMessage* CMessageWaterChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
return new CMessageWaterChanged();
}
////////////////////////////////
JS::Value CMessageObstructionMapShapeChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
return JS::ObjectValue(*obj);
}
CMessage* CMessageObstructionMapShapeChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
return new CMessageObstructionMapShapeChanged();
}
////////////////////////////////
JS::Value CMessageTerritoriesChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
return JS::ObjectValue(*obj);
}
CMessage* CMessageTerritoriesChanged::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
return new CMessageTerritoriesChanged();
}
////////////////////////////////
JS::Value CMessageRangeUpdate::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(tag);
SET_MSG_PROPERTY(added);
SET_MSG_PROPERTY(removed);
return JS::ObjectValue(*obj);
}
CMessage* CMessageRangeUpdate::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
LOGWARNING("CMessageRangeUpdate::FromJSVal not implemented");
return NULL;
}
////////////////////////////////
JS::Value CMessagePathResult::ToJSVal(ScriptInterface& UNUSED(scriptInterface)) const
{
LOGWARNING("CMessagePathResult::ToJSVal not implemented");
return JS::UndefinedValue();
}
CMessage* CMessagePathResult::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
LOGWARNING("CMessagePathResult::FromJSVal not implemented");
return NULL;
}
////////////////////////////////
JS::Value CMessageValueModification::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entities);
SET_MSG_PROPERTY(component);
SET_MSG_PROPERTY(valueNames);
return JS::ObjectValue(*obj);
}
CMessage* CMessageValueModification::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(std::vector, entities);
GET_MSG_PROPERTY(std::wstring, component);
GET_MSG_PROPERTY(std::vector, valueNames);
return new CMessageValueModification(entities, component, valueNames);
}
////////////////////////////////
JS::Value CMessageTemplateModification::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(player);
SET_MSG_PROPERTY(component);
SET_MSG_PROPERTY(valueNames);
return JS::ObjectValue(*obj);
}
CMessage* CMessageTemplateModification::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(player_id_t, player);
GET_MSG_PROPERTY(std::wstring, component);
GET_MSG_PROPERTY(std::vector, valueNames);
return new CMessageTemplateModification(player, component, valueNames);
}
////////////////////////////////
JS::Value CMessageVisionRangeChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
SET_MSG_PROPERTY(oldRange);
SET_MSG_PROPERTY(newRange);
return JS::ObjectValue(*obj);
}
CMessage* CMessageVisionRangeChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
GET_MSG_PROPERTY(entity_pos_t, oldRange);
GET_MSG_PROPERTY(entity_pos_t, newRange);
return new CMessageVisionRangeChanged(entity, oldRange, newRange);
}
JS::Value CMessageVisionSharingChanged::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(entity);
SET_MSG_PROPERTY(player);
SET_MSG_PROPERTY(add);
return JS::ObjectValue(*obj);
}
CMessage* CMessageVisionSharingChanged::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(entity_id_t, entity);
GET_MSG_PROPERTY(player_id_t, player);
GET_MSG_PROPERTY(bool, add);
return new CMessageVisionSharingChanged(entity, player, add);
}
////////////////////////////////
JS::Value CMessageMinimapPing::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
return JS::ObjectValue(*obj);
}
CMessage* CMessageMinimapPing::FromJSVal(ScriptInterface& UNUSED(scriptInterface), JS::HandleValue UNUSED(val))
{
return new CMessageMinimapPing();
}
////////////////////////////////
JS::Value CMessageCinemaPathEnded::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
SET_MSG_PROPERTY(name);
return JS::ObjectValue(*obj);
}
CMessage* CMessageCinemaPathEnded::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
GET_MSG_PROPERTY(CStrW, name);
return new CMessageCinemaPathEnded(name);
}
////////////////////////////////
JS::Value CMessageCinemaQueueEnded::ToJSVal(ScriptInterface& scriptInterface) const
{
TOJSVAL_SETUP();
return JS::ObjectValue(*obj);
}
CMessage* CMessageCinemaQueueEnded::FromJSVal(ScriptInterface& scriptInterface, JS::HandleValue val)
{
FROMJSVAL_SETUP();
return new CMessageCinemaQueueEnded();
}
////////////////////////////////////////////////////////////////
CMessage* CMessageFromJSVal(int mtid, ScriptInterface& scriptingInterface, JS::HandleValue val)
{
switch (mtid)
{
#define MESSAGE(name) case MT_##name: return CMessage##name::FromJSVal(scriptingInterface, val);
#define INTERFACE(name)
#define COMPONENT(name)
#include "simulation2/TypeList.h"
#undef COMPONENT
#undef INTERFACE
#undef MESSAGE
}
return NULL;
}