Jump to content

FeXoR

WFG Retired
  • Posts

    1.426
  • Joined

  • Last visited

  • Days Won

    28

Posts posted by FeXoR

  1. ^^

    no problem. I’ll try to contribute where I can, and such things don’t need code knowledge.

    2 monitors and enough cpu cores to feed 6 parallel running atlas instances really helps speeding such tasks up ;)

    as a general rule: every map using the pathplacer js code needs at least 2 players, otherwise the code will fail with an exception.

    I only know of Canyon. maybe Dark Forest.

    If I don’t forget it, it’ll be done tomorrow.

    Thanks much!

    Deep Forests uses it's own path system that does not fail so no lower player cap needed here.

    I'll take a look at the pathPlacer function and the random maps where it is called. It should not fail in any case.

    I cannot even sleep... darn hangover...

    Where to find the GUI documentation? I want to hide the player number selection of the lower cap rather than remove them from the dropdown/ComboBox if possible to not worry about someone assuming the n-th entrance would be n players. (To be honest I do this myself in changeMaxPlayers() which ofc. is bad...)

    Yes, as I feared: if dropdown content index differs from the number of players it selects the number of player slots and the dropdown both have wrong values... Should be a bug in onGameAttributesChange() or updatePlayerList()...

    BTW: There is another file gamesetup_mp.js but it seams just to add some functions and override onTick() or so. So I assume there's nothing to change. Still network testing would be nice.

    Trying to change changeMaxPlayers() to not assume anything:


    // DEBUG added
    // Change maximum number of players
    function changeMaxPlayers(maxPlayers)
    {
    var numPlayersSelection = getGUIObjectByName("numPlayersSelection");
    var selectedNumPlayers = numPlayersSelection.list_data[numPlayersSelection.selected];
    numPlayersSelection.list = [];
    numPlayersSelection.list_data = [];
    for (var i = 1; i <= maxPlayers; ++i)
    {
    numPlayersSelection.list.push(i);
    numPlayersSelection.list_data.push(i);
    }
    if (selectedNumPlayers in numPlayersSelection.list_data)
    numPlayersSelection.selected = numPlayersSelection.list[numPlayersSelection.list_data.indexOf(selectedNumPlayers)];
    else
    numPlayersSelection.selected = numPlayersSelection.list[numPlayersSelection.list_data.indexOf(maxPlayers)];
    selectNumPlayers(numPlayersSelection.list_data[numPlayersSelection.selected]);
    }

    This raises an error:

    ERROR: JavaScript error: gui/gamesetup/gamesetup.js line 505 Error: Cannot convert value to int changeMaxPlayers(8)@gui/gamesetup/gamesetup.js:505 selectMap("Acropolis 01")@gui/gamesetup/gamesetup.js:878 __eventhandler31 (selectionchange)([object Object])@mapSelection selectionchange:0 initMapNameList()@gui/gamesetup/gamesetup.js:613 selectMapType("scenario")@gui/gamesetup/gamesetup.js:786 __eventhandler29 (selectionchange)([object Object])@mapTypeSelection selectionchange:0 initMain()@gui/gamesetup/gamesetup.js:131 onTick()@gui/gamesetup/gamesetup.js:669 __eventhandler26 (tick)([object Object])@setupWindow tick:0

    Any idea what is wrong here? OK, found it. I shouldn't use push on gui variables it seams...

    This one works in general but for some reason if 8 players are selected the combobox shows nothing:


    // DEBUG added
    // Change maximum number of players
    function changeMaxPlayers(maxPlayers)
    {
    var numPlayersSelection = getGUIObjectByName("numPlayersSelection");
    var selectedNumPlayers = deepcopy(numPlayersSelection.list_data[numPlayersSelection.selected]);
    var pList = [];
    var pListData = [];
    for (var i = 1; i <= maxPlayers; ++i)
    {
    pList.push(i);
    pListData.push(i);
    }
    numPlayersSelection.list = pList;
    numPlayersSelection.list_data = pListData;
    if (selectedNumPlayers in numPlayersSelection.list_data)
    numPlayersSelection.selected = numPlayersSelection.list_data.indexOf(selectedNumPlayers);
    else
    numPlayersSelection.selected = numPlayersSelection.list_data.indexOf(maxPlayers);
    selectNumPlayers(numPlayersSelection.list_data[numPlayersSelection.selected]);
    }

    Its the "if else" part that causes it but I don't get why...

    Never mind got it working in an other way.

    Fixed "assume" bug in onGameAttributesChange().

    Mainly working but a bit buggy when lower caps on newly selected map is bigger than the actually selected value:

    http://fexor.dyndns....up/gamesetup.js

    Searching... Fixed!

    Whow! The entrances of the combobox are strings though I gave it a list of integers. My dear. Took me an hour to find the reason why I can't use it in Array.indexOf() ...

    Trying to change the code to assume as little as possible about the content of the number of players combobox...

    ToDo (when the top is done):

    - Remove DEBUG notes and code... Done!

    - Change the RMS json files to match the tests of luziferius... Done!

    - Generate a patch... Done!

    - File a ticket... Done!

    The version with both caps working:

    http://fexor.dyndns....up/gamesetup.js

    (like always ^^)

    Still not working sane if combobox values skip some numbers e.g. [2, 4, 6, 8] but I gave up on this for the time being...

    In the added functions it's only assumed that combobox data with lower index contain lower values.

    Changed files, zip and diff:

    http://fexor.dyndns....ps%20for%20RMS/

    Ticket added: http://trac.wildfire...com/ticket/1834

    (Just noticed the link to "trac-post-commit-hook" in http://trac.wildfire...SpecialCommands is dead)

    While testing the maps I found that:

    - Guadalquivir River: On small maps with Iberians walls are placed for them but sometimes exceed the map border

    To reproduce: Guadalquivir River, Small, 1 Player (Iberian), Seed 0

    To fix: Move the start positions further away from the map border or avoid placing walls for Iberian players on small maps (Replace line 145 with:

    placeCivDefaultEntities(fx, fz, id, BUILDING_ANGlE, (mapSize/64 - 2 < 1) ? {"iberWall" : false} : undefined);

    )

    - Gulf of Bothnia: Needs at least 2 players.

    - In General: Iberian civ bonus if towers should be reduced to 5 instead of 7 mainly because of the population increase. Number of towers and average distance to CC (radius) should be added as keyword arguments to misk.js placeCivDefaultEntities(). Default is 15 for towers, 20 for walls (That's OK).

    - Phoenician Levant: On small maps with 6 players Iberian walls nearly touch each other.

    To fix: Reduce max. player caps to [4, 4, 8, 8, 8, 8, 8] or don't place Iberian walls in small maps (replace line 180 with:

    placeCivDefaultEntities(fx, fz, id, BUILDING_ANGlE, (mapSize/64 - 2 < 1) ? {"iberWall" : false} : undefined);

    )

    - Pyrenean Sierra: Players start positions are placed to close to the water. Iberian walls extend beyond the shore sometimes.

    To reproduce: Pyrenean Sierra, Small, 1 Player (Iberian), Seed 0

    To Fix: There is enough space on the map! Please move the starting locations so the walls have enough space to be placed.

    - Rhine Marshlands: Sometimes the Iberian Walls extend into water here as well. It's not that bad but I think the the amount of space granted for players bases could be slightly increased. Alternately to use towers instead of walls could work better here.

    - The Siege (Not yet added): Results in a quite unexpected fortress for one player but works ^^.

    - Snowflake Searocks: Min player cap has to be set to 2.

    - All the "Unknown" maps: I refuse to check them! However, I will try to add a function that enables loading from RMS inside other RMS.

    - All the "Unknown" maps (2nd): I forgot to add the max. player caps here, sorry.

    Fixing the list as far as I can (first only those connected to the player cap patch)...

    (OffTopic: I hat pasting things in post edits makes the field of view jump to the top. Same with refused, not shown or multiple added newlines, tabs and spaces. Please fix that!)

  2. Now that we have player number restriction per map size, I’ve looked into the available map scripts.(opened 3 atlas instances and generated every map in tiny+4players, small+6players and medium+8players)

    those maps are working well with the default [4, 6, 8, 8, 8, 8, 8]:


    Ardennes Forest
    Atlas Mountains
    Cantabrian Highlands
    Continent
    Corinthian Isthmus
    Cycladic Archipelago
    English Channel
    Fortress
    Gear
    Gulf of Bothnia (There are no islands in the gulf, but the map description says so)
    Hyrcanian Shores
    Kerala
    Lake
    Latium
    Lorraine Plain
    Neareastern Badlands
    New RMS Test (well…)
    Northern Lights
    Oasis
    Persian Highlands
    Pyrenean Sierra
    Rhine Marshlands
    Rivers
    Saharan Oases
    Sahel Watering Holes
    Syria
    The Nile
    Volcanic Lands

    those maps need tweaking:

    they had different flaws with the default, like beeing largely unfair or otherwise broken layout (esp Migration, where all islands are connected with default restrictions)


    Archipelago (with 4 players on a tiny map, thats not an archipelago at all…)
    [3, 6, 8, 8, 8, 8, 8]
    Belgian Uplands (largely unfair with 6 players on a small map)
    [4, 4, 8, 8, 8, 8, 8]
    Canyon
    [3, 6, 8, 8, 8, 8, 8]
    Corsica vs Sardinia (very crowded)
    [2, 4, 6, 8, 8, 8, 8]
    Deep Forest (not a real forest with 4 players on a tiny map)
    [3, 6, 8, 8, 8, 8, 8]
    Guadalquivir River (northern players very disadvantaged)
    [2, 4, 6, 6, 8, 8, 8]
    Islands (generaly generates very slowly)
    [2, 4, 6, 8, 8, 8, 8]
    Migration (restrictive, otherwise islands may be connected; even on giant with 8 players and seed 9560)
    [2, 3, 5, 5, 6, 7, 8]
    Snowflake Searocks
    [3, 4, 8, 8, 8, 8, 8]

    The Unknown scripts should get those, since some of the included variations may not work well with the default


    Unknown
    [2, 4, 6, 8, 8, 8, 8]
    Unknown Land
    [2, 4, 6, 8, 8, 8, 8]
    Unknown Nomad
    [2, 4, 6, 8, 8, 8, 8]

    Thx! I'll do this.

    I think on Belgian upland even [2, 4, 6, 8, 8, 8, 8] would be good. If to many players are in there map generation needs long (because with bad player derivation the map is totally rebuild and it happens more often with more players on smaller maps).

    I drunk to much yesterday... got a headache. I'll finish this tomorrow and file a ticket.

  3. That's not a bad idea. Until the game's performance is sufficiently improved, we should probably reduce the default random map setting to 2 players on medium

    Done: http://fexor.dyndns.org/files/0ad/gamesetup

    And a json file to test with Belgian Upland with differing caps: http://fexor.dyndns....an_uplands.json

    Not sure, but can't the lower player cap just be an integer instead of an array of integers?

  4. I would say change the constant MAX_PLAYERS_BY_MAP_SIZE_RMS to:

    const MAX_PLAYERS_BY_MAP_SIZE_RMS = [2, 2, 3, 4, 6, 8, 8];

    this way it exactly matches the number of players in the size description.

    with current settings, you can’t play with 8 players on a “Very Large (8 players)” map…

    or

    const MAX_PLAYERS_BY_MAP_SIZE_RMS = [2, 3, 4, 5, 7, 8, 8];

    this way it matches number of players in the size description +1

    or:

    const MAX_PLAYERS_BY_MAP_SIZE_RMS = [4, 6, 8, 8, 8, 8, 8];

    that one would be very permissive and might not work with many maps

    about minimal player count: some RMS might not work with 1 or 2 players…

    Canyon throws an exeption with only one player…

    or the layout may be broken (for whatever reason)

    but I think one of those is a reasonable default:

    const MIN_PLAYERS_BY_MAP_SIZE_RMS = [1, 1, 1, 1, 1, 1, 1];

    const MIN_PLAYERS_BY_MAP_SIZE_RMS = [2, 2, 2, 2, 2, 2, 2];

    it should only be set otherwise by a RMS that needs this

    Yes, the verion above already does that. Upper limit: [4, 6, 8, 8, 8, 8, 8] (The extreme values where for testing only)

    Lower caps will stay 1 by default and can then be changed by the map.

  5. this one is broken…

    you need the placement in onGameAttributesChange() to update the UI. your current version does not Update the UI. It just needs to additionally set the player count with selectNumPlayers(). I just don’t know a sane position for this one.

    I changed it perhaps after you checked.

    It works for me now. The changed code is marked with "DEBUG" comments to find it. There seams to be a failed by one though in changeMaxPlayers().

    EDIT: changeMaxPlayers() seams to be ok, must be somewhere else. Searching...

    EDIT2: Oh, was ofc the function changeMaxPlayers(). Fixed.

    Testing welcome.

    http://fexor.dyndns.org/files/0ad/gamesetup/

    Thanks for your help luziferius!

    Now checking with edited RMS json... Works perfectly AFAIK ^^ Yippie!

    Added the json for Belgian Uplands to test and set the default to less restrictive values.

    Put it to:

    AppData\Local\0ad\cache\mods\public\maps\random

  6. I really don’t know the code, where should that js file be placed?

    EDIT: found it. ~/.cache/0ad/mods/public/gui/gamesetup/gamesetup.js

    I’ll try what I can…

    I made a stupid mistake: I set the gui dropdown content in onGameAttributesChange() which is stupid. Should be done in selectMapType(), selectMap() and the point where the map size is changed (not sure where. Might still be onGameAttributesChange()...). Added changeMaxPlayers() to take care of the dropdown content..

    http://fexor.dyndns.org/files/0ad/gamesetup/

    Now i got it right. Just a fail by one somewhere (the selected number of players decrease when changing map size).

  7. you still include the old mapSizeToSizeIndex(size), but don’t call it at all…

    a minor typo in one of the first comments:

    'player linit' (just use search function to find it)

    Removed and fixed ^^

    Well it's a work in progress version.

    Can you see why the selected number of players (in the gui and maybe somewhere else too) is not lowered when the map size is lowered and results in less players possible than selected?

    To reproduce:

    - Select map type "Random"

    - Select map size Giant

    - Select number of players 8

    - Lower the map size The number of players available in number of players dropdown as well as the player-slots will be lowered and the maximum possible number of players selected as expected.

    - Raise the map size again: Now the number of players is automatically raised so I assume there are still 8 slots filled. Not sure...

    http://fexor.dyndns.org/files/0ad/gamesetup/gamesetup.js

  8. just a suggestion, I can’t code js, but a quick google query told me it’s possible

    change function mapSizeToSizeIndex(size) to:


    function mapSizeToSizeIndex(size)
    {
    var sizeIndex = undefined;
    switch (size)
    {
    case 128:
    sizeIndex = 0;
    break;
    case 192:
    sizeIndex = 1;
    break;
    case 256:
    sizeIndex = 2;
    break;
    case 320:
    sizeIndex = 3;
    break;
    case 384:
    sizeIndex = 4;
    break;
    case 448:
    sizeIndex = 5;
    break;
    case 512:
    sizeIndex = 6;
    break;
    default:
    //some error handling here, since invalid size passed
    }
    return sizeIndex;
    }

    or:

    (shorter version without a variable)


    function mapSizeToSizeIndex(size)
    {
    switch (size)
    {
    case 128:
    return 0;
    case 192:
    return 1;
    case 256:
    return 2;
    case 320:
    return 3;
    case 384:
    return 4;
    case 448:
    return 5;
    case 512:
    return 6;
    default:
    //some error handling here, since invalid size passed
    return undefined;
    }
    }

    or:

    the shortest version that uses the specific values 'size' can have

    this one is fragile, you may use a variable for the calculation and error-check before returning the value


    function mapSizeToSizeIndex(size)
    {
    return (size/64-2);
    }

    Good suggestion. Indeed with the last example I don't need any function but just use it as index right away.

    Actual code now:


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Constants
    const DEFAULT_NETWORKED_MAP = "Acropolis 1";
    const DEFAULT_OFFLINE_MAP = "Acropolis 1";

    // TODO: Move these somewhere like simulation\data\game_types.json, Atlas needs them too
    const VICTORY_TEXT = ["Conquest", "None"];
    const VICTORY_DATA = ["conquest", "endless"];
    const VICTORY_DEFAULTIDX = 0;
    const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", "Unlimited"];
    const POPULATION_CAP_DATA = [50, 100, 150, 200, 250, 300, 10000];
    const POPULATION_CAP_DEFAULTIDX = 5;
    const STARTING_RESOURCES = ["Very Low", "Low", "Medium", "High", "Very High", "Deathmatch"];
    const STARTING_RESOURCES_DATA = [100, 300, 500, 1000, 3000, 50000];
    const STARTING_RESOURCES_DEFAULTIDX = 1;
    // Max number of players for this mod (the entire game?)
    const MAX_PLAYERS = 8;
    // The default upper player linit for random maps by map size:
    // [Tiny, Small, Medium, Normal, Large, Very Large, Giant]
    // Ignored if explicitely defined in the RMS.
    // Still capped at MAX_PLAYERS if excessing it in getMaxPlayersRMS().
    const MAX_PLAYERS_BY_MAP_SIZE_RMS = [2, 3, 4, 5, 6, 7, 8];

    ////////////////////////////////////////////////////////////////////////////////////////////////

    // Is this is a networked game, or offline
    var g_IsNetworked;

    // Is this user in control of game settings (i.e. is a network server, or offline player)
    var g_IsController;

    // Are we currently updating the GUI in response to network messages instead of user input
    // (and therefore shouldn't send further messages to the network)
    var g_IsInGuiUpdate;

    var g_PlayerAssignments = {};

    // Default game setup attributes
    var g_DefaultPlayerData = [];
    var g_GameAttributes = {
    settings: {}
    };

    var g_MapSizes = {};

    var g_AIs = [];

    var g_ChatMessages = [];

    // Data caches
    var g_MapData = {};
    var g_CivData = {};

    var g_MapFilters = [];

    // Warn about the AI's nonexistent naval map support.
    var g_NavalWarning = "\n\n[font=\"serif-bold-12\"][color=\"orange\"]Warning:[/color][/font] \
    The AI does not support naval maps and may cause severe performance issues. \
    Naval maps are recommended to be played with human opponents only.";

    // To prevent the display locking up while we load the map metadata,
    // we'll start with a 'loading' message and switch to the main screen in the
    // tick handler
    var g_LoadingState = 0; // 0 = not started, 1 = loading, 2 = loaded

    ////////////////////////////////////////////////////////////////////////////////////////////////

    function init(attribs)
    {
    switch (attribs.type)
    {
    case "offline":
    g_IsNetworked = false;
    g_IsController = true;
    break;
    case "server":
    g_IsNetworked = true;
    g_IsController = true;
    break;
    case "client":
    g_IsNetworked = true;
    g_IsController = false;
    break;
    default:
    error("Unexpected 'type' in gamesetup init: "+attribs.type);
    }
    }

    // Called after the map data is loaded and cached
    function initMain()
    {
    // Load AI list and hide deprecated AIs
    g_AIs = Engine.GetAIs();

    // Sort AIs by displayed name
    g_AIs.sort(function (a, B) {
    return a.data.name < b.data.name ? -1 : b.data.name < a.data.name ? +1 : 0;
    });

    // Get default player data - remove gaia
    g_DefaultPlayerData = initPlayerDefaults();
    g_DefaultPlayerData.shift();
    for (var i = 0; i < g_DefaultPlayerData.length; i++)
    g_DefaultPlayerData[i].Civ = "random";

    g_MapSizes = initMapSizes();

    // Init civs
    initCivNameList();

    // Init map types
    var mapTypes = getGUIObjectByName("mapTypeSelection");
    mapTypes.list = ["Scenario","Random"];
    mapTypes.list_data = ["scenario","random"];

    // Setup map filters - will appear in order they are added
    addFilter("Default", function(settings) { return settings && !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"]); });
    addFilter("Naval Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["naval"]); });
    addFilter("Demo Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["demo"]); });
    addFilter("Old Maps", function(settings) { return !settings; });
    addFilter("All Maps", function(settings) { return true; });

    // Populate map filters dropdown
    var mapFilters = getGUIObjectByName("mapFilterSelection");
    mapFilters.list = getFilters();
    g_GameAttributes.mapFilter = "Default";

    // Setup controls for host only
    if (g_IsController)
    {
    mapTypes.selected = 0;
    mapFilters.selected = 0;

    initMapNameList();

    var numPlayersSelection = getGUIObjectByName("numPlayersSelection");
    var players = [];
    for (var i = 1; i <= MAX_PLAYERS; ++i)
    players.push(i);
    numPlayersSelection.list = players;
    numPlayersSelection.list_data = players;
    numPlayersSelection.selected = MAX_PLAYERS - 1;

    var populationCaps = getGUIObjectByName("populationCap");
    populationCaps.list = POPULATION_CAP;
    populationCaps.list_data = POPULATION_CAP_DATA;
    populationCaps.selected = POPULATION_CAP_DEFAULTIDX;
    populationCaps.onSelectionChange = function()
    {
    if (this.selected != -1)
    {
    g_GameAttributes.settings.PopulationCap = POPULATION_CAP_DATA[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    }

    var startingResourcesL = getGUIObjectByName("startingResources");
    startingResourcesL.list = STARTING_RESOURCES;
    startingResourcesL.list_data = STARTING_RESOURCES_DATA;
    startingResourcesL.selected = STARTING_RESOURCES_DEFAULTIDX;
    startingResourcesL.onSelectionChange = function()
    {
    if (this.selected != -1)
    {
    g_GameAttributes.settings.StartingResources = STARTING_RESOURCES_DATA[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    }

    var victoryConditions = getGUIObjectByName("victoryCondition");
    victoryConditions.list = VICTORY_TEXT;
    victoryConditions.list_data = VICTORY_DATA;
    victoryConditions.onSelectionChange = function()
    { // Update attributes so other players can see change
    if (this.selected != -1)
    {
    g_GameAttributes.settings.GameType = VICTORY_DATA[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };
    victoryConditions.selected = VICTORY_DEFAULTIDX;

    var mapSize = getGUIObjectByName("mapSize");
    mapSize.list = g_MapSizes.names;
    mapSize.list_data = g_MapSizes.tiles;
    mapSize.onSelectionChange = function()
    { // Update attributes so other players can see change
    if (this.selected != -1)
    {
    g_GameAttributes.settings.Size = g_MapSizes.tiles[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };

    getGUIObjectByName("revealMap").onPress = function()
    { // Update attributes so other players can see change
    g_GameAttributes.settings.RevealMap = this.checked;

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };

    getGUIObjectByName("lockTeams").onPress = function()
    { // Update attributes so other players can see change
    g_GameAttributes.settings.LockTeams = this.checked;

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };

    getGUIObjectByName("enableCheats").onPress = function()
    { // Update attributes so other players can see change
    g_GameAttributes.settings.CheatsEnabled = this.checked;

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };
    }
    else
    {
    // If we're a network client, disable all the map controls
    // TODO: make them look visually disabled so it's obvious why they don't work
    getGUIObjectByName("mapTypeSelection").hidden = true;
    getGUIObjectByName("mapTypeText").hidden = false;
    getGUIObjectByName("mapFilterSelection").hidden = true;
    getGUIObjectByName("mapFilterText").hidden = false;
    getGUIObjectByName("mapSelectionText").hidden = false;
    getGUIObjectByName("mapSelection").hidden = true;
    getGUIObjectByName("victoryConditionText").hidden = false;
    getGUIObjectByName("victoryCondition").hidden = true;

    // Disable player and game options controls
    // TODO: Shouldn't players be able to choose their own assignment?
    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    getGUIObjectByName("playerAssignment["+i+"]").enabled = false;
    getGUIObjectByName("playerCiv["+i+"]").hidden = true;
    getGUIObjectByName("playerTeam["+i+"]").hidden = true;
    }

    getGUIObjectByName("numPlayersSelection").hidden = true;
    }

    // Set up multiplayer/singleplayer bits:
    if (!g_IsNetworked)
    {
    getGUIObjectByName("chatPanel").hidden = true;
    getGUIObjectByName("enableCheats").checked = true;
    g_GameAttributes.settings.CheatsEnabled = true;
    }
    else
    {
    getGUIObjectByName("enableCheatsDesc").hidden = false;
    getGUIObjectByName("enableCheats").checked = false;
    g_GameAttributes.settings.CheatsEnabled = false;
    if (g_IsController)
    {
    getGUIObjectByName("enableCheats").hidden = false;
    }
    else
    {
    getGUIObjectByName("enableCheatsText").hidden = false;
    }
    }

    // Settings for all possible player slots
    var boxSpacing = 32;
    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    // Space player boxes
    var box = getGUIObjectByName("playerBox["+i+"]");
    var boxSize = box.size;
    var h = boxSize.bottom - boxSize.top;
    boxSize.top = i * boxSpacing;
    boxSize.bottom = i * boxSpacing + h;
    box.size = boxSize;

    // Populate team dropdowns
    var team = getGUIObjectByName("playerTeam["+i+"]");
    team.list = ["None", "1", "2", "3", "4"];
    team.list_data = [-1, 0, 1, 2, 3];
    team.selected = 0;

    let playerSlot = i; // declare for inner function use
    team.onSelectionChange = function()
    { // Update team
    if (this.selected != -1)
    {
    g_GameAttributes.settings.PlayerData[playerSlot].Team = this.selected - 1;
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };


    // Set events
    var civ = getGUIObjectByName("playerCiv["+i+"]");
    civ.onSelectionChange = function()
    { // Update civ
    if ((this.selected != -1)&&(g_GameAttributes.mapType !== "scenario"))
    {
    g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };
    }

    if (g_IsNetworked)
    {
    // For multiplayer, focus the chat input box by default
    getGUIObjectByName("chatInput").focus();
    }
    else
    {
    // For single-player, focus the map list by default,
    // to allow easy keyboard selection of maps
    getGUIObjectByName("mapSelection").focus();
    }
    }

    function handleNetMessage(message)
    {
    log("Net message: "+uneval(message));

    switch (message.type)
    {
    case "netstatus":
    switch (message.status)
    {
    case "disconnected":
    Engine.DisconnectNetworkGame();
    Engine.PopGuiPage();
    reportDisconnect(message.reason);
    break;

    default:
    error("Unrecognised netstatus type "+message.status);
    break;
    }
    break;

    case "gamesetup":
    if (message.data) // (the host gets undefined data on first connect, so skip that)
    {
    g_GameAttributes = message.data;
    }

    onGameAttributesChange();
    break;

    case "players":
    // Find and report all joinings/leavings
    for (var host in message.hosts)
    {
    if (! g_PlayerAssignments[host])
    {
    addChatMessage({ "type": "connect", "username": message.hosts[host].name });
    }
    }
    for (var host in g_PlayerAssignments)
    {
    if (! message.hosts[host])
    {
    addChatMessage({ "type": "disconnect", "guid": host });
    }
    }
    // Update the player list
    g_PlayerAssignments = message.hosts;
    updatePlayerList();
    break;

    case "start":
    Engine.SwitchGuiPage("page_loading.xml", {
    "attribs": g_GameAttributes,
    "isNetworked" : g_IsNetworked,
    "playerAssignments": g_PlayerAssignments
    });
    break;

    case "chat":
    addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
    break;

    default:
    error("Unrecognised net message type "+message.type);
    }
    }

    // Get display name from map data
    function getMapDisplayName(map)
    {
    var mapData = loadMapData(map);

    if (!mapData || !mapData.settings || !mapData.settings.Name)
    { // Give some msg that map format is unsupported
    log("Map data missing in scenario '"+map+"' - likely unsupported format");
    return map;
    }

    return mapData.settings.Name;
    }

    // Get display name from map data
    function getMapPreview(map)
    {
    var mapData = loadMapData(map);

    if (!mapData || !mapData.settings || !mapData.settings.Preview)
    { // Give some msg that map format is unsupported
    return "nopreview.png";
    }

    return mapData.settings.Preview;
    }

    // Get a setting if it exists or return default
    function getSetting(settings, defaults, property)
    {
    if (settings && (property in settings))
    {
    return settings[property];
    }

    // Use defaults
    if (defaults && (property in defaults))
    {
    return defaults[property];
    }

    return undefined;
    }

    // Takes a map size in tiles and returns the index of this size (0: Tiny, 1: Small, ...)
    function mapSizeToSizeIndex(size)
    {
    var sizeIndex = undefined;
    if (size == 128)
    sizeIndex = 0;
    if (size == 192)
    sizeIndex = 1;
    if (size == 256)
    sizeIndex = 2;
    if (size == 320)
    sizeIndex = 3;
    if (size == 384)
    sizeIndex = 4;
    if (size == 448)
    sizeIndex = 5;
    if (size == 512)
    sizeIndex = 6;

    return sizeIndex;
    }

    // Get the maximum number of players for this random map and map size
    // Include the scenario case?
    function getMaxPlayersRMS(mapName, mapSize, maxPlayersByMapSizeRMS)
    {
    log("g_GameAttributes.mapType = " + g_GameAttributes.mapType);
    // Make arguments optional

    // The map name as key for g_MapData
    mapName = (mapName || g_GameAttributes.map);
    // The map size to determin which index of the player cap array to use
    if (mapSize || g_GameAttributes.settings.Size)
    mapSize = (mapSize || g_GameAttributes.settings.Size);
    else
    {
    // Only a log to not show up at gui initialization
    log("getMaxPlayersRMS: g_GameAttributes.settings.Size is undefined! Returning MAX_PLAYERS = " + MAX_PLAYERS);
    return MAX_PLAYERS;
    }
    // The max number of players by map size array
    if (g_MapData[mapName].settings && g_MapData[mapName].settings.MaximumPlayersByMapSize)
    maxPlayersByMapSizeRMS = (maxPlayersByMapSizeRMS || g_MapData[mapName].settings.MaximumPlayersByMapSize);
    else
    maxPlayersByMapSizeRMS = (maxPlayersByMapSizeRMS || MAX_PLAYERS_BY_MAP_SIZE_RMS);

    // Getting the return value
    var maxPlayers = Math.min(maxPlayersByMapSizeRMS[Math.round(mapSize/64 - 2)], MAX_PLAYERS);

    return maxPlayers;
    }

    // Initialize the dropdowns containing all the available civs
    function initCivNameList()
    {
    // Cache civ data
    g_CivData = loadCivData();

    // Extract name/code, and skip civs that are explicitly disabled
    // (intended for unusable incomplete civs)
    var civList = [
    { "name": civ.Name, "code": civ.Code }
    for each (civ in g_CivData)
    if (civ.SelectableInGameSetup !== false)
    ];

    // Alphabetically sort the list, ignoring case
    civList.sort(sortNameIgnoreCase);

    var civListNames = [ civ.name for each (civ in civList) ];
    var civListCodes = [ civ.code for each (civ in civList) ];

    // Add random civ to beginning of list
    civListNames.unshift("[color=\"orange\"]Random");
    civListCodes.unshift("random");

    // Update the dropdowns
    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    var civ = getGUIObjectByName("playerCiv["+i+"]");
    civ.list = civListNames;
    civ.list_data = civListCodes;
    civ.selected = 0;
    }
    }

    // Initialise the list control containing all the available maps
    function initMapNameList()
    {
    // Get a list of map filenames
    // TODO: Should verify these are valid maps before adding to list
    var mapSelectionBox = getGUIObjectByName("mapSelection")
    var mapFiles;

    switch (g_GameAttributes.mapType)
    {
    case "scenario":
    mapFiles = getXMLFileList(g_GameAttributes.mapPath);
    break;

    case "random":
    mapFiles = getJSONFileList(g_GameAttributes.mapPath);
    break;

    default:
    error("initMapNameList: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return;
    }

    // Apply map filter, if any defined
    var mapList = [];
    for (var i = 0; i < mapFiles.length; ++i)
    {
    var file = mapFiles[i];
    var mapData = loadMapData(file);

    if (g_GameAttributes.mapFilter && mapData && testFilter(g_GameAttributes.mapFilter, mapData.settings))
    {
    mapList.push({ "name": getMapDisplayName(file), "file": file });
    }
    }

    // Alphabetically sort the list, ignoring case
    mapList.sort(sortNameIgnoreCase);
    if (g_GameAttributes.mapType == "random")
    mapList.unshift({ "name": "[color=\"orange\"]Random[/color]", "file": "random" });

    var mapListNames = [ map.name for each (map in mapList) ];
    var mapListFiles = [ map.file for each (map in mapList) ];

    // Select the default map
    var selected = mapListFiles.indexOf(g_GameAttributes.map);
    // Default to the first element if list is not empty and we can't find the one we searched for
    if (selected == -1 && mapList.length)
    {
    selected = 0;
    }

    // Update the list control
    mapSelectionBox.list = mapListNames;
    mapSelectionBox.list_data = mapListFiles;
    mapSelectionBox.selected = selected;
    }

    function loadMapData(name)
    {
    if (!name)
    {
    return undefined;
    }

    if (name == "random")
    {
    g_MapData[name] = {settings : {"Name" : "Random", "Description" : "Randomly selects a map from the list"}};
    return g_MapData[name];
    }

    if (!g_MapData[name])
    {
    switch (g_GameAttributes.mapType)
    {
    case "scenario":
    g_MapData[name] = Engine.LoadMapSettings(g_GameAttributes.mapPath+name);
    break;

    case "random":
    g_MapData[name] = parseJSONData(g_GameAttributes.mapPath+name+".json");
    break;

    default:
    error("loadMapData: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return undefined;
    }
    }

    return g_MapData[name];
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // GUI event handlers

    function cancelSetup()
    {
    Engine.DisconnectNetworkGame();
    }

    function onTick()
    {
    // First tick happens before first render, so don't load yet
    if (g_LoadingState == 0)
    {
    g_LoadingState++;
    }
    else if (g_LoadingState == 1)
    {
    getGUIObjectByName("loadingWindow").hidden = true;
    getGUIObjectByName("setupWindow").hidden = false;
    initMain();
    g_LoadingState++;
    }
    else if (g_LoadingState == 2)
    {
    while (true)
    {
    var message = Engine.PollNetworkClient();
    if (!message)
    {
    break;
    }
    handleNetMessage(message);
    }
    }
    }

    // Called when user selects number of players
    function selectNumPlayers(num)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change number of players
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    // Only meaningful for random maps
    if (g_GameAttributes.mapType != "random")
    {
    return;
    }

    // Update player data
    var pData = g_GameAttributes.settings.PlayerData;
    // DEBUG To change?
    if (pData && num < pData.length)
    {
    // Remove extra player data
    g_GameAttributes.settings.PlayerData = pData.slice(0, num);
    }
    else
    {
    // Add player data from defaults
    for (var i = pData.length; i < num; ++i)
    {
    g_GameAttributes.settings.PlayerData.push(g_DefaultPlayerData[i]);
    }
    }

    // Some players may have lost their assigned slot
    for (var guid in g_PlayerAssignments)
    {
    var player = g_PlayerAssignments[guid].player;
    if (player > num)
    {
    if (g_IsNetworked)
    Engine.AssignNetworkPlayer(player, "");
    else
    g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
    }
    }

    updateGameAttributes();
    }

    // Called when the user selects a map type from the list
    function selectMapType(type)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change map type
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    // Reset game attributes
    g_GameAttributes.map = "";
    g_GameAttributes.mapType = type;

    // Clear old map data
    g_MapData = {};

    // Select correct path
    switch (g_GameAttributes.mapType)
    {
    case "scenario":
    // Set a default map
    // TODO: This should be remembered from the last session
    g_GameAttributes.map = (g_IsNetworked ? DEFAULT_NETWORKED_MAP : DEFAULT_OFFLINE_MAP);
    g_GameAttributes.mapPath = "maps/scenarios/";
    break;

    case "random":
    g_GameAttributes.mapPath = "maps/random/";
    g_GameAttributes.settings = {
    // DEBUG To change?
    PlayerData: g_DefaultPlayerData.slice(0, 4),
    Seed: Math.floor(Math.random() * 65536),
    CheatsEnabled: g_GameAttributes.settings.CheatsEnabled
    };
    break;

    default:
    error("selectMapType: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return;
    }

    initMapNameList();

    updateGameAttributes();
    }

    function selectMapFilter(filterName)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change map filter
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    g_GameAttributes.mapFilter = filterName;

    initMapNameList();

    updateGameAttributes();
    }

    // Called when the user selects a map from the list
    function selectMap(name)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change map
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    // Return if we have no map
    if (!name)
    {
    return;
    }

    var mapData = loadMapData(name);
    var mapSettings = (mapData && mapData.settings ? deepcopy(mapData.settings) : {});

    // Copy any new settings
    g_GameAttributes.map = name;
    g_GameAttributes.script = mapSettings.Script;
    if (mapData !== "Random")
    for (var prop in mapSettings)
    g_GameAttributes.settings[prop] = mapSettings[prop];

    // Use default AI if the map doesn't specify any explicitly
    // DEBUG To change?
    for (var i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i)
    {
    if (!('AI' in g_GameAttributes.settings.PlayerData[i]))
    {
    g_GameAttributes.settings.PlayerData[i].AI = g_DefaultPlayerData[i].AI;
    }
    }

    // Reset player assignments on map change
    if (!g_IsNetworked)
    { // Slot 1
    g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
    }
    else
    {
    // DEBUG To change? BTW: if mapSettings.PlayerData does not exist g_GameAttributes.settings.PlayerData is not set properly either?!
    var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : g_GameAttributes.settings.PlayerData.length);

    for (var guid in g_PlayerAssignments)
    { // Unassign extra players
    var player = g_PlayerAssignments[guid].player;

    if (player <= MAX_PLAYERS && player > numPlayers)
    {
    Engine.AssignNetworkPlayer(player, "");
    }
    }
    }

    updateGameAttributes();
    }

    function launchGame()
    {
    if (g_IsNetworked && !g_IsController)
    {
    error("Only host can start game");
    return;
    }

    // Check that we have a map
    if (!g_GameAttributes.map)
    {
    return;
    }

    if (g_GameAttributes.map == "random")
    selectMap(getGUIObjectByName("mapSelection").list_data[Math.floor(Math.random() *
    (getGUIObjectByName("mapSelection").list.length - 1)) + 1]);

    g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
    var numPlayers = g_GameAttributes.settings.PlayerData.length;
    // Assign random civilizations to players with that choice
    // (this is synchronized because we're the host)
    var cultures = [];
    for each (civ in g_CivData)
    if ((civ.Culture !== undefined)&&(cultures.indexOf(civ.Culture) < 0)&&(civ.SelectableInGameSetup == undefined)||(civ.SelectableInGameSetup))
    cultures.push(civ.Culture);
    var allcivs = new Array(cultures.length);
    for (var i = 0; i < allcivs.length; ++i)
    allcivs[i] = [];
    for each (civ in g_CivData)
    if ((civ.Culture !== undefined)&&(civ.SelectableInGameSetup == undefined)||(civ.SelectableInGameSetup))
    allcivs[cultures.indexOf(civ.Culture)].push(civ.Code);

    const romanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
    for (var i = 0; i < numPlayers; ++i)
    {
    civs = allcivs[Math.floor(Math.random()*allcivs.length)];

    if (g_GameAttributes.settings.PlayerData[i].Civ == "random")
    g_GameAttributes.settings.PlayerData[i].Civ = civs[Math.floor(Math.random()*civs.length)];
    // Setting names for AI players. Check if the player is AI and the match is not a scenario
    if ((g_GameAttributes.mapType !== "scenario")&&(g_GameAttributes.settings.PlayerData[i].AI))
    {
    // Get the civ specific names
    if (g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames !== undefined)
    {
    var civAINames = shuffleArray(g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames);
    }
    else
    {
    var civAINames = [g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].Name];
    }
    // Choose the name
    var usedName = 0;
    if (i < civAINames.length)
    var chosenName = civAINames[i];
    else
    var chosenName = civAINames[Math.floor(Math.random() * civAINames.length)];
    for (var j = 0; j < numPlayers; ++j)
    if (g_GameAttributes.settings.PlayerData[j].Name.indexOf(chosenName) !== -1)
    usedName++;

    // Assign civ specific names to AI players
    if (usedName)
    g_GameAttributes.settings.PlayerData[i].Name = chosenName + " " + romanNumbers[usedName+1];
    else
    g_GameAttributes.settings.PlayerData[i].Name = chosenName;
    }
    }

    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    Engine.StartNetworkGame();
    }
    else
    {
    // Find the player ID which the user has been assigned to
    var numPlayers = g_GameAttributes.settings.PlayerData.length;
    var playerID = -1;
    for (var i = 0; i < numPlayers; ++i)
    {
    var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
    if (assignBox.list_data[assignBox.selected] == "local")
    {
    playerID = i+1;
    }
    }
    // Remove extra player data
    g_GameAttributes.settings.PlayerData = g_GameAttributes.settings.PlayerData.slice(0, numPlayers);

    Engine.StartGame(g_GameAttributes, playerID);
    Engine.SwitchGuiPage("page_loading.xml", {
    "attribs": g_GameAttributes,
    "isNetworked" : g_IsNetworked,
    "playerAssignments": g_PlayerAssignments
    });
    }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    function onGameAttributesChange()
    {
    g_IsInGuiUpdate = true;

    // Don't set any attributes here, just show the changes in GUI

    var mapName = g_GameAttributes.map || "";
    var mapSettings = g_GameAttributes.settings;
    // DEBUG To change?
    var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : MAX_PLAYERS);
    if (g_GameAttributes.mapType == "random")
    numPlayers = Math.min(numPlayers, getMaxPlayersRMS());

    // Update some controls for clients
    if (!g_IsController)
    {
    getGUIObjectByName("mapFilterText").caption = g_GameAttributes.mapFilter;
    var mapTypeSelection = getGUIObjectByName("mapTypeSelection");
    var idx = mapTypeSelection.list_data.indexOf(g_GameAttributes.mapType);
    getGUIObjectByName("mapTypeText").caption = mapTypeSelection.list[idx];
    var mapSelectionBox = getGUIObjectByName("mapSelection");
    mapSelectionBox.selected = mapSelectionBox.list_data.indexOf(mapName);
    getGUIObjectByName("mapSelectionText").caption = getMapDisplayName(mapName);
    var populationCapBox = getGUIObjectByName("populationCap");
    populationCapBox.selected = populationCapBox.list_data.indexOf(mapSettings.PopulationCap);
    var startingResourcesBox = getGUIObjectByName("startingResources");
    startingResourcesBox.selected = startingResourcesBox.list_data.indexOf(mapSettings.StartingResources);
    initMapNameList();
    }

    // Controls common to all map types
    // DEBUG To change? ToDo: Remove redundant players from g_GameAttributes.settings.playerData later! Where? When?
    var numPlayersSelection = getGUIObjectByName("numPlayersSelection");
    var players = [];
    for (var i = 1; i <= MAX_PLAYERS; ++i)
    {
    if (!(g_GameAttributes.mapType == "random" && i > getMaxPlayersRMS()))
    players.push(i);
    }
    numPlayersSelection.list = players;
    numPlayersSelection.list_data = players;
    numPlayersSelection.selected = players.length - 1;
    var revealMap = getGUIObjectByName("revealMap");
    var victoryCondition = getGUIObjectByName("victoryCondition");
    var lockTeams = getGUIObjectByName("lockTeams");
    var mapSize = getGUIObjectByName("mapSize");
    var enableCheats = getGUIObjectByName("enableCheats");
    var populationCap = getGUIObjectByName("populationCap");
    var startingResources = getGUIObjectByName("startingResources");

    var numPlayersText= getGUIObjectByName("numPlayersText");
    var mapSizeText = getGUIObjectByName("mapSizeText");
    var revealMapText = getGUIObjectByName("revealMapText");
    var victoryConditionText = getGUIObjectByName("victoryConditionText");
    var lockTeamsText = getGUIObjectByName("lockTeamsText");
    var enableCheatsText = getGUIObjectByName("enableCheatsText");
    var populationCapText = getGUIObjectByName("populationCapText");
    var startingResourcesText = getGUIObjectByName("startingResourcesText");

    var sizeIdx = (g_MapSizes.tiles.indexOf(mapSettings.Size) != -1 ? g_MapSizes.tiles.indexOf(mapSettings.Size) : g_MapSizes.default);
    var victoryIdx = (VICTORY_DATA.indexOf(mapSettings.GameType) != -1 ? VICTORY_DATA.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
    enableCheats.checked = (g_GameAttributes.settings.CheatsEnabled === undefined || !g_GameAttributes.settings.CheatsEnabled ? false : true)
    enableCheatsText.caption = (enableCheats.checked ? "Yes" : "No");
    populationCap.selected = (POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);
    populationCapText.caption = POPULATION_CAP[populationCap.selected];
    startingResources.selected = (STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) != -1 ? STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) : STARTING_RESOURCES_DEFAULTIDX);
    startingResourcesText.caption = STARTING_RESOURCES[startingResources.selected];
    // Handle map type specific logic
    switch (g_GameAttributes.mapType)
    {
    case "random":
    if (g_IsController)
    { //Host
    numPlayersSelection.selected = numPlayers - 1;
    numPlayersSelection.hidden = false;
    mapSize.hidden = false;
    revealMap.hidden = false;
    victoryCondition.hidden = false;
    lockTeams.hidden = false;
    populationCap.hidden = false;
    startingResources.hidden = false;

    numPlayersText.hidden = true;
    mapSizeText.hidden = true;
    revealMapText.hidden = true;
    victoryConditionText.hidden = true;
    lockTeamsText.hidden = true;
    populationCapText.hidden = true;
    startingResourcesText.hidden = true;

    // Update map preview
    getGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);

    mapSizeText.caption = "Map size:";
    mapSize.selected = sizeIdx;
    revealMapText.caption = "Reveal map:";
    revealMap.checked = (mapSettings.RevealMap ? true : false);

    victoryConditionText.caption = "Victory condition:";
    victoryCondition.selected = victoryIdx;
    lockTeamsText.caption = "Teams locked:";
    lockTeams.checked = (mapSettings.LockTeams ? true : false);
    }
    else
    {
    // Client
    numPlayersText.hidden = false;
    mapSizeText.hidden = false;
    revealMapText.hidden = false;
    victoryConditionText.hidden = false;
    lockTeamsText.hidden = false;
    populationCap.hidden = true;
    populationCapText.hidden = false;
    startingResources.hidden = true;
    startingResourcesText.hidden = false;
    // Update map preview
    getGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);

    numPlayersText.caption = numPlayers;
    mapSizeText.caption = g_MapSizes.names[sizeIdx];
    revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
    victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
    }

    break;

    case "scenario":
    // For scenario just reflect settings for the current map
    numPlayersSelection.hidden = true;
    mapSize.hidden = true;
    revealMap.hidden = true;
    victoryCondition.hidden = true;
    lockTeams.hidden = true;
    numPlayersText.hidden = false;
    mapSizeText.hidden = false;
    revealMapText.hidden = false;
    victoryConditionText.hidden = false;
    lockTeamsText.hidden = false;
    populationCap.hidden = true;
    populationCapText.hidden = false;
    startingResources.hidden = true;
    startingResourcesText.hidden = false;

    // Update map preview
    getGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);
    numPlayersText.caption = numPlayers;
    mapSizeText.caption = "Default";
    revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
    victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
    getGUIObjectByName("populationCap").selected = POPULATION_CAP_DEFAULTIDX;

    break;

    default:
    error("onGameAttributesChange: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return;
    }

    // Display map name
    getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);

    // Load the description from the map file, if there is one
    var description = mapSettings.Description || "Sorry, no description available.";

    if (g_GameAttributes.mapFilter == "Naval Maps")
    description += g_NavalWarning;

    // Describe the number of players
    var playerString = numPlayers + " " + (numPlayers == 1 ? "player" : "players") + ". ";

    // DEBUG To change?
    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    // Show only needed player slots
    getGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers);

    // Show player data or defaults as necessary
    if (i < numPlayers)
    {
    var pName = getGUIObjectByName("playerName["+i+"]");
    var pCiv = getGUIObjectByName("playerCiv["+i+"]");
    var pCivText = getGUIObjectByName("playerCivText["+i+"]");
    var pTeam = getGUIObjectByName("playerTeam["+i+"]");
    var pTeamText = getGUIObjectByName("playerTeamText["+i+"]");
    var pColor = getGUIObjectByName("playerColour["+i+"]");

    // Player data / defaults
    var pData = mapSettings.PlayerData ? mapSettings.PlayerData[i] : {};
    var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[i] : {};

    // Common to all game types
    var color = iColorToString(getSetting(pData, pDefs, "Colour"));
    pColor.sprite = "colour:"+color+" 100";
    pName.caption = getSetting(pData, pDefs, "Name");

    var team = getSetting(pData, pDefs, "Team");
    var civ = getSetting(pData, pDefs, "Civ");

    // For clients or scenarios, hide some player dropdowns
    if (!g_IsController || g_GameAttributes.mapType == "scenario")
    {
    pCivText.hidden = false;
    pCiv.hidden = true;
    pTeamText.hidden = false;
    pTeam.hidden = true;
    // Set text values
    if (civ == "random")
    {
    pCivText.caption = "[color=\"orange\"]Random";
    }
    else
    {
    pCivText.caption = g_CivData[civ].Name;
    }
    pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
    }
    else if (g_GameAttributes.mapType == "random")
    {
    pCivText.hidden = true;
    pCiv.hidden = false;
    pTeamText.hidden = true;
    pTeam.hidden = false;
    // Set dropdown values
    pCiv.selected = (civ ? pCiv.list_data.indexOf(civ) : 0);
    pTeam.selected = (team !== undefined && team >= 0) ? team+1 : 0;
    }
    }
    }

    getGUIObjectByName("mapInfoDescription").caption = playerString + description;

    g_IsInGuiUpdate = false;

    // Game attributes include AI settings, so update the player list
    updatePlayerList();
    }

    function updateGameAttributes()
    {
    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    else
    {
    onGameAttributesChange();
    }
    }

    function updatePlayerList()
    {
    g_IsInGuiUpdate = true;

    var hostNameList = [];
    var hostGuidList = [];
    var assignments = [];
    var aiAssignments = {};
    var noAssignment;
    var assignedCount = 0;

    for (var guid in g_PlayerAssignments)
    {
    var name = g_PlayerAssignments[guid].name;
    var hostID = hostNameList.length;
    var player = g_PlayerAssignments[guid].player;

    hostNameList.push(name);
    hostGuidList.push(guid);
    assignments[player] = hostID;

    if (player != 255)
    assignedCount++;
    }

    // Only enable start button if we have enough assigned players
    if (g_IsController)
    getGUIObjectByName("startGame").enabled = (assignedCount > 0);

    for each (var ai in g_AIs)
    {
    if (ai.data.hidden)
    {
    // If the map uses a hidden AI then don't hide it
    var usedByMap = false;
    for (var i = 0; i < MAX_PLAYERS; ++i) {
    if (i < g_GameAttributes.settings.PlayerData.length &&
    g_GameAttributes.settings.PlayerData[i].AI == ai.id)
    {
    usedByMap = true;
    }
    }
    if (!usedByMap)
    {
    continue;
    }
    }
    // Give AI a different color so it stands out
    aiAssignments[ai.id] = hostNameList.length;
    hostNameList.push("[color=\"70 150 70 255\"]AI: " + ai.data.name);
    hostGuidList.push("ai:" + ai.id);
    }

    noAssignment = hostNameList.length;
    hostNameList.push("[color=\"140 140 140 255\"]Unassigned");
    hostGuidList.push("");

    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    let playerSlot = i;
    let playerID = i+1; // we don't show Gaia, so first slot is ID 1

    var selection = assignments[playerID];

    var configButton = getGUIObjectByName("playerConfig["+i+"]");
    configButton.hidden = true;

    // Look for valid player slots
    if (playerSlot < g_GameAttributes.settings.PlayerData.length)
    {
    // If no human is assigned, look for an AI instead
    if (selection === undefined)
    {
    var aiId = g_GameAttributes.settings.PlayerData[playerSlot].AI;
    if (aiId)
    {
    // Check for a valid AI
    if (aiId in aiAssignments)
    selection = aiAssignments[aiId];
    else
    warn("AI \""+aiId+"\" not present. Defaulting to unassigned.");
    }

    if (!selection)
    selection = noAssignment;

    // Since no human is assigned, show the AI config button
    if (g_IsController)
    {
    configButton.hidden = false;
    configButton.onpress = function()
    {
    Engine.PushGuiPage("page_aiconfig.xml", {
    ais: g_AIs,
    id: g_GameAttributes.settings.PlayerData[playerSlot].AI,
    callback: function(ai) {
    g_GameAttributes.settings.PlayerData[playerSlot].AI = ai.id;

    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    else
    {
    updatePlayerList();
    }
    }
    });
    };
    }
    }
    else
    {
    // There was a human, so make sure we don't have any AI left
    // over in their slot, if we're in charge of the attributes
    if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
    {
    g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    }
    }

    var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
    assignBox.list = hostNameList;
    assignBox.list_data = hostGuidList;
    if (assignBox.selected != selection)
    {
    assignBox.selected = selection;
    }

    if (g_IsNetworked && g_IsController)
    {
    assignBox.onselectionchange = function ()
    {
    if (!g_IsInGuiUpdate)
    {
    var guid = hostGuidList[this.selected];
    if (guid == "")
    {
    // Unassign any host from this player slot
    Engine.AssignNetworkPlayer(playerID, "");
    // Remove AI from this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
    }
    else if (guid.substr(0, 3) == "ai:")
    {
    // Unassign any host from this player slot
    Engine.AssignNetworkPlayer(playerID, "");
    // Set the AI for this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
    }
    else
    swapPlayers(guid, playerSlot);

    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    };
    }
    else if (!g_IsNetworked)
    {
    assignBox.onselectionchange = function ()
    {
    if (!g_IsInGuiUpdate)
    {
    var guid = hostGuidList[this.selected];
    if (guid == "")
    {
    // Remove AI from this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
    }
    else if (guid.substr(0, 3) == "ai:")
    {
    // Set the AI for this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
    }
    else
    swapPlayers(guid, playerSlot);

    updatePlayerList();
    }
    };
    }
    }
    }

    g_IsInGuiUpdate = false;
    }

    function swapPlayers(guid, newSlot)
    {
    // Player slots are indexed from 0 as Gaia is omitted.
    var newPlayerID = newSlot + 1;
    var playerID = g_PlayerAssignments[guid].player;

    // Attempt to swap the player or AI occupying the target slot,
    // if any, into the slot this player is currently in.
    if (playerID != 255)
    {
    for (var i in g_PlayerAssignments)
    {
    // Move the player in the destination slot into the current slot.
    if (g_PlayerAssignments[i].player == newPlayerID)
    {
    if (g_IsNetworked)
    Engine.AssignNetworkPlayer(playerID, i);
    else
    g_PlayerAssignments[i].player = playerID;
    break;
    }
    }

    // Transfer the AI from the target slot to the current slot.
    g_GameAttributes.settings.PlayerData[playerID - 1].AI = g_GameAttributes.settings.PlayerData[newSlot].AI;
    }

    if (g_IsNetworked)
    Engine.AssignNetworkPlayer(newPlayerID, guid);
    else
    g_PlayerAssignments[guid].player = newPlayerID;

    // Remove AI from this player slot
    g_GameAttributes.settings.PlayerData[newSlot].AI = "";
    }

    function submitChatInput()
    {
    var input = getGUIObjectByName("chatInput");
    var text = input.caption;
    if (text.length)
    {
    Engine.SendNetworkChat(text);
    input.caption = "";
    }
    }

    function addChatMessage(msg)
    {
    var username = escapeText(msg.username || g_PlayerAssignments[msg.guid].name);
    var message = escapeText(msg.text);

    // TODO: Maybe host should have distinct font/color?
    var color = "white";

    if (g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != 255)
    { // Valid player who has been assigned - get player colour
    var player = g_PlayerAssignments[msg.guid].player - 1;
    var mapName = g_GameAttributes.map;
    var mapData = loadMapData(mapName);
    var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
    var pData = mapSettings.PlayerData ? mapSettings.PlayerData[player] : {};
    var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[player] : {};

    color = iColorToString(getSetting(pData, pDefs, "Colour"));
    }

    var formatted;
    switch (msg.type)
    {
    case "connect":
    formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has joined[/color]';
    break;

    case "disconnect":
    formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has left[/color]';
    break;

    case "message":
    formatted = '[font="serif-bold-13"]<[color="'+ color +'"]' + username + '[/color]>[/font] ' + message;
    break;

    default:
    error("Invalid chat message '" + uneval(msg) + "'");
    return;
    }

    g_ChatMessages.push(formatted);

    getGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
    }

    function toggleMoreOptions()
    {
    getGUIObjectByName("moreOptions").hidden = !getGUIObjectByName("moreOptions").hidden;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Basic map filters API

    // Add a new map list filter
    function addFilter(name, filterFunc)
    {
    if (filterFunc instanceof Object)
    { // Basic validity test
    var newFilter = {};
    newFilter.name = name;
    newFilter.filter = filterFunc;

    g_MapFilters.push(newFilter);
    }
    else
    {
    error("Invalid map filter: "+name);
    }
    }

    // Get array of map filter names
    function getFilters()
    {
    var filters = [];
    for (var i = 0; i < g_MapFilters.length; ++i)
    {
    filters.push(g_MapFilters[i].name);
    }

    return filters;
    }

    // Test map filter on given map settings object
    function testFilter(name, mapSettings)
    {
    for (var i = 0; i < g_MapFilters.length; ++i)
    {
    if (g_MapFilters[i].name == name)
    { // Found filter
    return g_MapFilters[i].filter(mapSettings);
    }
    }

    error("Invalid map filter: "+name);
    return false;
    }

    // Test an array of keywords against a match array using AND logic
    function keywordTestAND(keywords, matches)
    {
    if (!keywords || !matches)
    {
    return false;
    }

    for (var m = 0; m < matches.length; ++m)
    { // Fail on not match
    if (keywords.indexOf(matches[m]) == -1)
    {
    return false;
    }
    }
    return true;
    }

    // Test an array of keywords against a match array using OR logic
    function keywordTestOR(keywords, matches)
    {
    if (!keywords || !matches)
    {
    return false;
    }

    for (var m = 0; m < matches.length; ++m)
    { // Success on match
    if (keywords.indexOf(matches[m]) != -1)
    {
    return true;
    }
    }
    return false;
    }

    ToDo:

    - Drop redundant players from g_GameAttributes.settings.playerData (at select map/size? Or just call selectNumPlayers() if to many players there?)

  9. I find the last option more suitable. Another way could be not to choose a map in that the current player/size adjustment is unsuitable.

    Good idea! (The last one) (y)

    Analyzing the structure of g_MapData...

    Getting error: "onTick is not defined". Why ever. Searching... Fixed...

    Call order without clicking anything:

    initMain (g_MapData empty...)

    selectMapType (g_MapData empty...)

    selectMap (g_MapData loaded)

    onGameAttributesChange

    updatePlayerList

    onGameAttributesChange

    updatePlayerList

    selectMap

    onGameAttributesChange

    updatePlayerList

    onGameAttributesChange

    updatePlayerList

    selectMap

    onGameAttributesChange

    updatePlayerList

    onGameAttributesChange

    updatePlayerList

    Additionally called when switching map type to random map:

    selectMapType (g_MapData not updated yet)

    selectMap (g_MapData updated to "Aegean Sea")

    onGameAttributesChange

    updatePlayerList

    onGameAttributesChange

    updatePlayerList

    So nothing to change for the call order AFAIKS...

    Final g_MapData (unevaled):

    g_MapData: ({aegean_sea:{settings:{Name:"Aegean Sea", Script:"aegean_sea.js", Description:"Players start in two sides of a sea with scattered islands\n\nAegean Sea is a part of Mediterranean Sea between southern Balkan and Anatolia. There are more than 100 islands located in the region. By the game's timeline, nearly all of the inhabitants of the areas surrounding the sea were Greek. Many of the islands in the Aegean have safe harbours and bays. In ancient times, navigation through the sea was easier than travelling across the rough terrain of the Greek mainland (and to some extent the coastal areas of Anatolia). Many of the islands are volcanic, and marble and iron are mined on other islands. The larger islands have some fertile valleys and plains.", BaseTerrain:["medit_grass_shrubs", "medit_rocks_grass_shrubs", "medit_rocks_shrubs", "medit_rocks_grass", "medit_shrubs"], BaseHeight:1, Keywords:["naval"], CircularMap:true, Preview:"aegean_sea.png", XXXXXX:"Optionally define other things here, like we woul...

    Is there a way to avoid log() from being cut?

    Finalizing making getMaxPlayersRMS() arguments optional (I still need g_GameAttributes.map for the propper g_MapData key and there seams to be no way to avoid this. Same with map size: g_GameAttributes.settings.Size)... Done!

    This code additions should do (EDITED! The other was buggy. Still returning warnings at GUI initialization but returns correct values in any case):


    // The default upper player linit for random maps by map size:
    // [Tiny, Small, Medium, Normal, Large, Very Large, Giant]
    // Ignored if explicitely defined in the RMS.
    // Still capped at MAX_PLAYERS if excessing it in getMaxPlayersRMS().
    const MAX_PLAYERS_BY_MAP_SIZE_RMS = [4, 6, 8, 8, 8, 8, 8];

    (Bla blub...)

    // Takes a map size in tiles and returns the index of this size (0: Tiny, 1: Small, ...)
    function mapSizeToSizeIndex(size)
    {
    var sizeIndex = undefined;
    if (size == 128)
    sizeIndex = 0;
    if (size == 192)
    sizeIndex = 1;
    if (size == 256)
    sizeIndex = 2;
    if (size == 320)
    sizeIndex = 3;
    if (size == 384)
    sizeIndex = 4;
    if (size == 448)
    sizeIndex = 5;
    if (size == 512)
    sizeIndex = 6;

    return sizeIndex;
    }

    // Get the maximum number of players for this random map and map size
    function getMaxPlayersRMS(rms, mapSize, maxPlayersByMapSizeRMS)
    {
    // Make arguments optional

    // The map name as key for g_MapData
    if (g_GameAttributes.map || rms)
    rms = (rms || g_GameAttributes.map);
    else
    {
    warn("getMaxPlayersRMS: g_GameAttributes.map is undefined! Returning MAX_PLAYERS = " + MAX_PLAYERS);
    return MAX_PLAYERS;
    }

    // The map size to determin which index of the mlayer cap array to use
    if (mapSize || g_GameAttributes.settings.Size)
    mapSize = (mapSize || g_GameAttributes.settings.Size);
    else
    {
    warn("getMaxPlayersRMS: g_GameAttributes.settings.Size is undefined! Returning MAX_PLAYERS = " + MAX_PLAYERS);
    return MAX_PLAYERS;
    }

    // The max number of players by map size array
    if (g_MapData[rms].settings && g_MapData[rms].settings.MaximumPlayersByMapSize)
    maxPlayersByMapSizeRMS = (maxPlayersByMapSizeRMS || g_MapData[rms].settings.MaximumPlayersByMapSize);
    else
    maxPlayersByMapSizeRMS = (maxPlayersByMapSizeRMS || MAX_PLAYERS_BY_MAP_SIZE_RMS);

    // Getting the return value
    var maxPlayers = Math.min(maxPlayersByMapSizeRMS[mapSizeToSizeIndex(mapSize)], MAX_PLAYERS);

    return maxPlayers;
    }

    Grrr... again the error: "onTick is not defined". Why ever. Searching (again)... Fixed again!

    Searching where exactly to replace MAX_PLAYERS with getMaxPlayersRMS()... Oh, guessing always since if it's an scenario the scenario defines the number of players, not MAX_PLAYERS... Not really but I think I got it mainly right...

    Have to go now. I hope to finish this tomorrow (Likely without the "Random" random map case sanely implemented)...

    Comments/suggestions/criticism welcome!

    Seams to work somehow but the dropdown for number of players still contain 8 choices and lowering the map size does not result in lowering the number of player slots... I seam to need to change/update g_GameAttributes.settings.PlayerData as well. But doing this inside the function leads to problems when called from onGameAttributesChange (because it changes the game attribute again and run into an infinite loop)... Ah there's a g_IsInGuiUpdate variable (little blind FeXoR)...

    How to change the content of the number of players drop down? ...getGUIObjectByName("numPlayersSelection").list and list_data...

    It seams to be best to use the function for scenarios as well because setting PlayerData it not always mapType dependent(?)

    Work in progress...

  10. Trying to figure out what information to use: g_GameAttributes or g_MapData ?

    Trying to figure out in which order things are called (or if at all have to be taken into consideration): initMain() -> selectMapType() -> selectMap() -> onGameAttributesChange() -> updatePlayerList() ?

    What to do if a "random" random map is chosen (default after switching to random maps)? The default maximum number of players for RMS should override the caps for the (later chosen) specific random map in this case??? Or should the caps be defined seperately for this case (more restrictive)?

    General notes on gamesetup.js:

    - Indentation in otherwise empty lines is sometimes missing: Fixed...

    - Many empty lines (I'll leave them though double empty lines inside functions seam excessive to me).

    - Last settings for this game type (single-player or multi-player) for each mod should be saved and be used the next time this type of game in the specific mod is chosen (Still find the mod design ugly... IMO a civ-build-tree (containing the wanted civilizations) including units, buildings, techs and their dependencies should be determined and loaded for each map. The map could leave it empty so a default civ-build-tree is loaded. The map could also specify if a specific civ-build-tree is needed for this map of if it can be chosen in the game-setup-menu).

    Actual file:


    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Constants
    const DEFAULT_NETWORKED_MAP = "Acropolis 1";
    const DEFAULT_OFFLINE_MAP = "Acropolis 1";

    // TODO: Move these somewhere like simulation\data\game_types.json, Atlas needs them too
    const VICTORY_TEXT = ["Conquest", "None"];
    const VICTORY_DATA = ["conquest", "endless"];
    const VICTORY_DEFAULTIDX = 0;
    const POPULATION_CAP = ["50", "100", "150", "200", "250", "300", "Unlimited"];
    const POPULATION_CAP_DATA = [50, 100, 150, 200, 250, 300, 10000];
    const POPULATION_CAP_DEFAULTIDX = 5;
    const STARTING_RESOURCES = ["Very Low", "Low", "Medium", "High", "Very High", "Deathmatch"];
    const STARTING_RESOURCES_DATA = [100, 300, 500, 1000, 3000, 50000];
    const STARTING_RESOURCES_DEFAULTIDX = 1;
    // Max number of players for this mod (the entire game?)
    const MAX_PLAYERS = 8;
    // The default upper player linit for random maps by map size:
    // [Tiny, Small, Medium, Normal, Large, Very Large, Giant]
    // Ignored if explicitely defined in the RMS.
    // Still capped at MAX_PLAYERS if excessing it in getMaxPlayersRMS().
    const MAX_PLAYERS_BY_MAP_SIZE_RMS = [4, 6, 8, 8, 8, 8, 8];

    ////////////////////////////////////////////////////////////////////////////////////////////////

    // Is this is a networked game, or offline
    var g_IsNetworked;

    // Is this user in control of game settings (i.e. is a network server, or offline player)
    var g_IsController;

    // Are we currently updating the GUI in response to network messages instead of user input
    // (and therefore shouldn't send further messages to the network)
    var g_IsInGuiUpdate;

    var g_PlayerAssignments = {};

    // Default game setup attributes
    var g_DefaultPlayerData = [];
    var g_GameAttributes = {
    settings: {}
    };

    var g_MapSizes = {};

    var g_AIs = [];

    var g_ChatMessages = [];

    // Data caches
    var g_MapData = {};
    var g_CivData = {};

    var g_MapFilters = [];

    // Warn about the AI's nonexistent naval map support.
    var g_NavalWarning = "\n\n[font=\"serif-bold-12\"][color=\"orange\"]Warning:[/color][/font] \
    The AI does not support naval maps and may cause severe performance issues. \
    Naval maps are recommended to be played with human opponents only.";

    // To prevent the display locking up while we load the map metadata,
    // we'll start with a 'loading' message and switch to the main screen in the
    // tick handler
    var g_LoadingState = 0; // 0 = not started, 1 = loading, 2 = loaded

    ////////////////////////////////////////////////////////////////////////////////////////////////

    function init(attribs)
    {
    switch (attribs.type)
    {
    case "offline":
    g_IsNetworked = false;
    g_IsController = true;
    break;
    case "server":
    g_IsNetworked = true;
    g_IsController = true;
    break;
    case "client":
    g_IsNetworked = true;
    g_IsController = false;
    break;
    default:
    error("Unexpected 'type' in gamesetup init: "+attribs.type);
    }
    }

    // Called after the map data is loaded and cached
    function initMain()
    {
    // Load AI list and hide deprecated AIs
    g_AIs = Engine.GetAIs();

    // Sort AIs by displayed name
    g_AIs.sort(function (a, B) {
    return a.data.name < b.data.name ? -1 : b.data.name < a.data.name ? +1 : 0;
    });

    // Get default player data - remove gaia
    g_DefaultPlayerData = initPlayerDefaults();
    g_DefaultPlayerData.shift();
    for (var i = 0; i < g_DefaultPlayerData.length; i++)
    g_DefaultPlayerData[i].Civ = "random";

    g_MapSizes = initMapSizes();

    // Init civs
    initCivNameList();

    // Init map types
    var mapTypes = getGUIObjectByName("mapTypeSelection");
    mapTypes.list = ["Scenario","Random"];
    mapTypes.list_data = ["scenario","random"];

    // Setup map filters - will appear in order they are added
    addFilter("Default", function(settings) { return settings && !keywordTestOR(settings.Keywords, ["naval", "demo", "hidden"]); });
    addFilter("Naval Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["naval"]); });
    addFilter("Demo Maps", function(settings) { return settings && keywordTestAND(settings.Keywords, ["demo"]); });
    addFilter("Old Maps", function(settings) { return !settings; });
    addFilter("All Maps", function(settings) { return true; });

    // Populate map filters dropdown
    var mapFilters = getGUIObjectByName("mapFilterSelection");
    mapFilters.list = getFilters();
    g_GameAttributes.mapFilter = "Default";

    // Setup controls for host only
    if (g_IsController)
    {
    mapTypes.selected = 0;
    mapFilters.selected = 0;

    initMapNameList();

    var numPlayersSelection = getGUIObjectByName("numPlayersSelection");
    var players = [];
    for (var i = 1; i <= MAX_PLAYERS; ++i)
    players.push(i);
    numPlayersSelection.list = players;
    numPlayersSelection.list_data = players;
    numPlayersSelection.selected = MAX_PLAYERS - 1;

    var populationCaps = getGUIObjectByName("populationCap");
    populationCaps.list = POPULATION_CAP;
    populationCaps.list_data = POPULATION_CAP_DATA;
    populationCaps.selected = POPULATION_CAP_DEFAULTIDX;
    populationCaps.onSelectionChange = function()
    {
    if (this.selected != -1)
    {
    g_GameAttributes.settings.PopulationCap = POPULATION_CAP_DATA[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    }

    var startingResourcesL = getGUIObjectByName("startingResources");
    startingResourcesL.list = STARTING_RESOURCES;
    startingResourcesL.list_data = STARTING_RESOURCES_DATA;
    startingResourcesL.selected = STARTING_RESOURCES_DEFAULTIDX;
    startingResourcesL.onSelectionChange = function()
    {
    if (this.selected != -1)
    {
    g_GameAttributes.settings.StartingResources = STARTING_RESOURCES_DATA[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    }

    var victoryConditions = getGUIObjectByName("victoryCondition");
    victoryConditions.list = VICTORY_TEXT;
    victoryConditions.list_data = VICTORY_DATA;
    victoryConditions.onSelectionChange = function()
    { // Update attributes so other players can see change
    if (this.selected != -1)
    {
    g_GameAttributes.settings.GameType = VICTORY_DATA[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };
    victoryConditions.selected = VICTORY_DEFAULTIDX;

    var mapSize = getGUIObjectByName("mapSize");
    mapSize.list = g_MapSizes.names;
    mapSize.list_data = g_MapSizes.tiles;
    mapSize.onSelectionChange = function()
    { // Update attributes so other players can see change
    if (this.selected != -1)
    {
    g_GameAttributes.settings.Size = g_MapSizes.tiles[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };

    getGUIObjectByName("revealMap").onPress = function()
    { // Update attributes so other players can see change
    g_GameAttributes.settings.RevealMap = this.checked;

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };

    getGUIObjectByName("lockTeams").onPress = function()
    { // Update attributes so other players can see change
    g_GameAttributes.settings.LockTeams = this.checked;

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };

    getGUIObjectByName("enableCheats").onPress = function()
    { // Update attributes so other players can see change
    g_GameAttributes.settings.CheatsEnabled = this.checked;

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };
    }
    else
    {
    // If we're a network client, disable all the map controls
    // TODO: make them look visually disabled so it's obvious why they don't work
    getGUIObjectByName("mapTypeSelection").hidden = true;
    getGUIObjectByName("mapTypeText").hidden = false;
    getGUIObjectByName("mapFilterSelection").hidden = true;
    getGUIObjectByName("mapFilterText").hidden = false;
    getGUIObjectByName("mapSelectionText").hidden = false;
    getGUIObjectByName("mapSelection").hidden = true;
    getGUIObjectByName("victoryConditionText").hidden = false;
    getGUIObjectByName("victoryCondition").hidden = true;

    // Disable player and game options controls
    // TODO: Shouldn't players be able to choose their own assignment?
    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    getGUIObjectByName("playerAssignment["+i+"]").enabled = false;
    getGUIObjectByName("playerCiv["+i+"]").hidden = true;
    getGUIObjectByName("playerTeam["+i+"]").hidden = true;
    }

    getGUIObjectByName("numPlayersSelection").hidden = true;
    }

    // Set up multiplayer/singleplayer bits:
    if (!g_IsNetworked)
    {
    getGUIObjectByName("chatPanel").hidden = true;
    getGUIObjectByName("enableCheats").checked = true;
    g_GameAttributes.settings.CheatsEnabled = true;
    }
    else
    {
    getGUIObjectByName("enableCheatsDesc").hidden = false;
    getGUIObjectByName("enableCheats").checked = false;
    g_GameAttributes.settings.CheatsEnabled = false;
    if (g_IsController)
    {
    getGUIObjectByName("enableCheats").hidden = false;
    }
    else
    {
    getGUIObjectByName("enableCheatsText").hidden = false;
    }
    }

    // Settings for all possible player slots
    var boxSpacing = 32;
    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    // Space player boxes
    var box = getGUIObjectByName("playerBox["+i+"]");
    var boxSize = box.size;
    var h = boxSize.bottom - boxSize.top;
    boxSize.top = i * boxSpacing;
    boxSize.bottom = i * boxSpacing + h;
    box.size = boxSize;

    // Populate team dropdowns
    var team = getGUIObjectByName("playerTeam["+i+"]");
    team.list = ["None", "1", "2", "3", "4"];
    team.list_data = [-1, 0, 1, 2, 3];
    team.selected = 0;

    let playerSlot = i; // declare for inner function use
    team.onSelectionChange = function()
    { // Update team
    if (this.selected != -1)
    {
    g_GameAttributes.settings.PlayerData[playerSlot].Team = this.selected - 1;
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };


    // Set events
    var civ = getGUIObjectByName("playerCiv["+i+"]");
    civ.onSelectionChange = function()
    { // Update civ
    if ((this.selected != -1)&&(g_GameAttributes.mapType !== "scenario"))
    {
    g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected];
    }

    if (!g_IsInGuiUpdate)
    {
    updateGameAttributes();
    }
    };
    }

    if (g_IsNetworked)
    {
    // For multiplayer, focus the chat input box by default
    getGUIObjectByName("chatInput").focus();
    }
    else
    {
    // For single-player, focus the map list by default,
    // to allow easy keyboard selection of maps
    getGUIObjectByName("mapSelection").focus();
    }
    }

    function handleNetMessage(message)
    {
    log("Net message: "+uneval(message));

    switch (message.type)
    {
    case "netstatus":
    switch (message.status)
    {
    case "disconnected":
    Engine.DisconnectNetworkGame();
    Engine.PopGuiPage();
    reportDisconnect(message.reason);
    break;

    default:
    error("Unrecognised netstatus type "+message.status);
    break;
    }
    break;

    case "gamesetup":
    if (message.data) // (the host gets undefined data on first connect, so skip that)
    {
    g_GameAttributes = message.data;
    }

    onGameAttributesChange();
    break;

    case "players":
    // Find and report all joinings/leavings
    for (var host in message.hosts)
    {
    if (! g_PlayerAssignments[host])
    {
    addChatMessage({ "type": "connect", "username": message.hosts[host].name });
    }
    }
    for (var host in g_PlayerAssignments)
    {
    if (! message.hosts[host])
    {
    addChatMessage({ "type": "disconnect", "guid": host });
    }
    }
    // Update the player list
    g_PlayerAssignments = message.hosts;
    updatePlayerList();
    break;

    case "start":
    Engine.SwitchGuiPage("page_loading.xml", {
    "attribs": g_GameAttributes,
    "isNetworked" : g_IsNetworked,
    "playerAssignments": g_PlayerAssignments
    });
    break;

    case "chat":
    addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
    break;

    default:
    error("Unrecognised net message type "+message.type);
    }
    }

    // Get display name from map data
    function getMapDisplayName(map)
    {
    var mapData = loadMapData(map);

    if (!mapData || !mapData.settings || !mapData.settings.Name)
    { // Give some msg that map format is unsupported
    log("Map data missing in scenario '"+map+"' - likely unsupported format");
    return map;
    }

    return mapData.settings.Name;
    }

    // Get display name from map data
    function getMapPreview(map)
    {
    var mapData = loadMapData(map);

    if (!mapData || !mapData.settings || !mapData.settings.Preview)
    { // Give some msg that map format is unsupported
    return "nopreview.png";
    }

    return mapData.settings.Preview;
    }

    // Get a setting if it exists or return default
    function getSetting(settings, defaults, property)
    {
    if (settings && (property in settings))
    {
    return settings[property];
    }

    // Use defaults
    if (defaults && (property in defaults))
    {
    return defaults[property];
    }

    return undefined;
    }

    // Get the maximum number of players for this random map and map size
    /*
    What to use: g_GameAttributes or g_MapData ???
    */
    function getMaxPlayersRMS(rms, size, maxPlayersByMapSizeRMS)
    {
    // Make arguments optional
    if (g_GameAttributes.map):
    rms = (rms || g_GameAttributes.map);
    else
    warn("getMaxPlayersRMS: g_GameAttributes.map = " + uneval(g_GameAttributes.map));
    // size = ( || );
    if (g_MapData[rms][MaximumPlayersByMapSize])
    maxPlayersByMapSizeRMS = (maxPlayersByMapSizeRMS || g_MapData[rms][MaximumPlayersByMapSize]);
    else
    maxPlayersByMapSizeRMS = (maxPlayersByMapSizeRMS || MAX_PLAYERS_BY_MAP_SIZE_RMS);

    var maxPlayers = MAX_PLAYERS;
    return maxPlayers;
    }

    // Initialize the dropdowns containing all the available civs
    function initCivNameList()
    {
    // Cache civ data
    g_CivData = loadCivData();

    // Extract name/code, and skip civs that are explicitly disabled
    // (intended for unusable incomplete civs)
    var civList = [
    { "name": civ.Name, "code": civ.Code }
    for each (civ in g_CivData)
    if (civ.SelectableInGameSetup !== false)
    ];

    // Alphabetically sort the list, ignoring case
    civList.sort(sortNameIgnoreCase);

    var civListNames = [ civ.name for each (civ in civList) ];
    var civListCodes = [ civ.code for each (civ in civList) ];

    // Add random civ to beginning of list
    civListNames.unshift("[color=\"orange\"]Random");
    civListCodes.unshift("random");

    // Update the dropdowns
    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    var civ = getGUIObjectByName("playerCiv["+i+"]");
    civ.list = civListNames;
    civ.list_data = civListCodes;
    civ.selected = 0;
    }
    }

    // Initialise the list control containing all the available maps
    function initMapNameList()
    {
    // Get a list of map filenames
    // TODO: Should verify these are valid maps before adding to list
    var mapSelectionBox = getGUIObjectByName("mapSelection")
    var mapFiles;

    switch (g_GameAttributes.mapType)
    {
    case "scenario":
    mapFiles = getXMLFileList(g_GameAttributes.mapPath);
    break;

    case "random":
    mapFiles = getJSONFileList(g_GameAttributes.mapPath);
    break;

    default:
    error("initMapNameList: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return;
    }

    // Apply map filter, if any defined
    var mapList = [];
    for (var i = 0; i < mapFiles.length; ++i)
    {
    var file = mapFiles[i];
    var mapData = loadMapData(file);

    if (g_GameAttributes.mapFilter && mapData && testFilter(g_GameAttributes.mapFilter, mapData.settings))
    {
    mapList.push({ "name": getMapDisplayName(file), "file": file });
    }
    }

    // Alphabetically sort the list, ignoring case
    mapList.sort(sortNameIgnoreCase);
    if (g_GameAttributes.mapType == "random")
    mapList.unshift({ "name": "[color=\"orange\"]Random[/color]", "file": "random" });

    var mapListNames = [ map.name for each (map in mapList) ];
    var mapListFiles = [ map.file for each (map in mapList) ];

    // Select the default map
    var selected = mapListFiles.indexOf(g_GameAttributes.map);
    // Default to the first element if list is not empty and we can't find the one we searched for
    if (selected == -1 && mapList.length)
    {
    selected = 0;
    }

    // Update the list control
    mapSelectionBox.list = mapListNames;
    mapSelectionBox.list_data = mapListFiles;
    mapSelectionBox.selected = selected;
    }

    function loadMapData(name)
    {
    if (!name)
    {
    return undefined;
    }

    if (name == "random")
    {
    g_MapData[name] = {settings : {"Name" : "Random", "Description" : "Randomly selects a map from the list"}};
    return g_MapData[name];
    }

    if (!g_MapData[name])
    {
    switch (g_GameAttributes.mapType)
    {
    case "scenario":
    g_MapData[name] = Engine.LoadMapSettings(g_GameAttributes.mapPath+name);
    break;

    case "random":
    g_MapData[name] = parseJSONData(g_GameAttributes.mapPath+name+".json");
    break;

    default:
    error("loadMapData: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return undefined;
    }
    }

    return g_MapData[name];
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // GUI event handlers

    function cancelSetup()
    {
    Engine.DisconnectNetworkGame();
    }

    function onTick()
    {
    // First tick happens before first render, so don't load yet
    if (g_LoadingState == 0)
    {
    g_LoadingState++;
    }
    else if (g_LoadingState == 1)
    {
    getGUIObjectByName("loadingWindow").hidden = true;
    getGUIObjectByName("setupWindow").hidden = false;
    initMain();
    g_LoadingState++;
    }
    else if (g_LoadingState == 2)
    {
    while (true)
    {
    var message = Engine.PollNetworkClient();
    if (!message)
    {
    break;
    }
    handleNetMessage(message);
    }
    }
    }

    // Called when user selects number of players
    function selectNumPlayers(num)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change number of players
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    // Only meaningful for random maps
    if (g_GameAttributes.mapType != "random")
    {
    return;
    }

    // Update player data
    var pData = g_GameAttributes.settings.PlayerData;
    if (pData && num < pData.length)
    {
    // Remove extra player data
    g_GameAttributes.settings.PlayerData = pData.slice(0, num);
    }
    else
    {
    // Add player data from defaults
    for (var i = pData.length; i < num; ++i)
    {
    g_GameAttributes.settings.PlayerData.push(g_DefaultPlayerData[i]);
    }
    }

    // Some players may have lost their assigned slot
    for (var guid in g_PlayerAssignments)
    {
    var player = g_PlayerAssignments[guid].player;
    if (player > num)
    {
    if (g_IsNetworked)
    Engine.AssignNetworkPlayer(player, "");
    else
    g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
    }
    }

    updateGameAttributes();
    }

    // Called when the user selects a map type from the list
    function selectMapType(type)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change map type
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    // Reset game attributes
    g_GameAttributes.map = "";
    g_GameAttributes.mapType = type;

    // Clear old map data
    g_MapData = {};

    // Select correct path
    switch (g_GameAttributes.mapType)
    {
    case "scenario":
    // Set a default map
    // TODO: This should be remembered from the last session
    g_GameAttributes.map = (g_IsNetworked ? DEFAULT_NETWORKED_MAP : DEFAULT_OFFLINE_MAP);
    g_GameAttributes.mapPath = "maps/scenarios/";
    break;

    case "random":
    g_GameAttributes.mapPath = "maps/random/";
    g_GameAttributes.settings = {
    PlayerData: g_DefaultPlayerData.slice(0, 4),
    Seed: Math.floor(Math.random() * 65536),
    CheatsEnabled: g_GameAttributes.settings.CheatsEnabled
    };
    break;

    default:
    error("selectMapType: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return;
    }

    initMapNameList();

    updateGameAttributes();
    }

    function selectMapFilter(filterName)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change map filter
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    g_GameAttributes.mapFilter = filterName;

    initMapNameList();

    updateGameAttributes();
    }

    // Called when the user selects a map from the list
    function selectMap(name)
    {
    // Avoid recursion
    if (g_IsInGuiUpdate)
    {
    return;
    }

    // Network clients can't change map
    if (g_IsNetworked && !g_IsController)
    {
    return;
    }

    // Return if we have no map
    if (!name)
    {
    return;
    }

    var mapData = loadMapData(name);
    var mapSettings = (mapData && mapData.settings ? deepcopy(mapData.settings) : {});

    // Copy any new settings
    g_GameAttributes.map = name;
    g_GameAttributes.script = mapSettings.Script;
    if (mapData !== "Random")
    for (var prop in mapSettings)
    g_GameAttributes.settings[prop] = mapSettings[prop];

    // Use default AI if the map doesn't specify any explicitly
    for (var i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i)
    {
    if (!('AI' in g_GameAttributes.settings.PlayerData[i]))
    {
    g_GameAttributes.settings.PlayerData[i].AI = g_DefaultPlayerData[i].AI;
    }
    }

    // Reset player assignments on map change
    if (!g_IsNetworked)
    { // Slot 1
    g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
    }
    else
    {
    var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : g_GameAttributes.settings.PlayerData.length);

    for (var guid in g_PlayerAssignments)
    { // Unassign extra players
    var player = g_PlayerAssignments[guid].player;

    if (player <= MAX_PLAYERS && player > numPlayers)
    {
    Engine.AssignNetworkPlayer(player, "");
    }
    }
    }

    updateGameAttributes();
    }

    function launchGame()
    {
    if (g_IsNetworked && !g_IsController)
    {
    error("Only host can start game");
    return;
    }

    // Check that we have a map
    if (!g_GameAttributes.map)
    {
    return;
    }

    if (g_GameAttributes.map == "random")
    selectMap(getGUIObjectByName("mapSelection").list_data[Math.floor(Math.random() *
    (getGUIObjectByName("mapSelection").list.length - 1)) + 1]);

    g_GameAttributes.settings.mapType = g_GameAttributes.mapType;
    var numPlayers = g_GameAttributes.settings.PlayerData.length;
    // Assign random civilizations to players with that choice
    // (this is synchronized because we're the host)
    var cultures = [];
    for each (civ in g_CivData)
    if ((civ.Culture !== undefined)&&(cultures.indexOf(civ.Culture) < 0)&&(civ.SelectableInGameSetup == undefined)||(civ.SelectableInGameSetup))
    cultures.push(civ.Culture);
    var allcivs = new Array(cultures.length);
    for (var i = 0; i < allcivs.length; ++i)
    allcivs[i] = [];
    for each (civ in g_CivData)
    if ((civ.Culture !== undefined)&&(civ.SelectableInGameSetup == undefined)||(civ.SelectableInGameSetup))
    allcivs[cultures.indexOf(civ.Culture)].push(civ.Code);

    const romanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
    for (var i = 0; i < numPlayers; ++i)
    {
    civs = allcivs[Math.floor(Math.random()*allcivs.length)];

    if (g_GameAttributes.settings.PlayerData[i].Civ == "random")
    g_GameAttributes.settings.PlayerData[i].Civ = civs[Math.floor(Math.random()*civs.length)];
    // Setting names for AI players. Check if the player is AI and the match is not a scenario
    if ((g_GameAttributes.mapType !== "scenario")&&(g_GameAttributes.settings.PlayerData[i].AI))
    {
    // Get the civ specific names
    if (g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames !== undefined)
    {
    var civAINames = shuffleArray(g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].AINames);
    }
    else
    {
    var civAINames = [g_CivData[g_GameAttributes.settings.PlayerData[i].Civ].Name];
    }
    // Choose the name
    var usedName = 0;
    if (i < civAINames.length)
    var chosenName = civAINames[i];
    else
    var chosenName = civAINames[Math.floor(Math.random() * civAINames.length)];
    for (var j = 0; j < numPlayers; ++j)
    if (g_GameAttributes.settings.PlayerData[j].Name.indexOf(chosenName) !== -1)
    usedName++;

    // Assign civ specific names to AI players
    if (usedName)
    g_GameAttributes.settings.PlayerData[i].Name = chosenName + " " + romanNumbers[usedName+1];
    else
    g_GameAttributes.settings.PlayerData[i].Name = chosenName;
    }
    }

    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    Engine.StartNetworkGame();
    }
    else
    {
    // Find the player ID which the user has been assigned to
    var numPlayers = g_GameAttributes.settings.PlayerData.length;
    var playerID = -1;
    for (var i = 0; i < numPlayers; ++i)
    {
    var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
    if (assignBox.list_data[assignBox.selected] == "local")
    {
    playerID = i+1;
    }
    }
    // Remove extra player data
    g_GameAttributes.settings.PlayerData = g_GameAttributes.settings.PlayerData.slice(0, numPlayers);

    Engine.StartGame(g_GameAttributes, playerID);
    Engine.SwitchGuiPage("page_loading.xml", {
    "attribs": g_GameAttributes,
    "isNetworked" : g_IsNetworked,
    "playerAssignments": g_PlayerAssignments
    });
    }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    function onGameAttributesChange()
    {
    g_IsInGuiUpdate = true;

    // Don't set any attributes here, just show the changes in GUI

    var mapName = g_GameAttributes.map || "";
    var mapSettings = g_GameAttributes.settings;
    var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : MAX_PLAYERS);

    // Update some controls for clients
    if (!g_IsController)
    {
    getGUIObjectByName("mapFilterText").caption = g_GameAttributes.mapFilter;
    var mapTypeSelection = getGUIObjectByName("mapTypeSelection");
    var idx = mapTypeSelection.list_data.indexOf(g_GameAttributes.mapType);
    getGUIObjectByName("mapTypeText").caption = mapTypeSelection.list[idx];
    var mapSelectionBox = getGUIObjectByName("mapSelection");
    mapSelectionBox.selected = mapSelectionBox.list_data.indexOf(mapName);
    getGUIObjectByName("mapSelectionText").caption = getMapDisplayName(mapName);
    var populationCapBox = getGUIObjectByName("populationCap");
    populationCapBox.selected = populationCapBox.list_data.indexOf(mapSettings.PopulationCap);
    var startingResourcesBox = getGUIObjectByName("startingResources");
    startingResourcesBox.selected = startingResourcesBox.list_data.indexOf(mapSettings.StartingResources);
    initMapNameList();
    }

    // Controls common to all map types
    var numPlayersSelection = getGUIObjectByName("numPlayersSelection");
    var revealMap = getGUIObjectByName("revealMap");
    var victoryCondition = getGUIObjectByName("victoryCondition");
    var lockTeams = getGUIObjectByName("lockTeams");
    var mapSize = getGUIObjectByName("mapSize");
    var enableCheats = getGUIObjectByName("enableCheats");
    var populationCap = getGUIObjectByName("populationCap");
    var startingResources = getGUIObjectByName("startingResources");

    var numPlayersText= getGUIObjectByName("numPlayersText");
    var mapSizeText = getGUIObjectByName("mapSizeText");
    var revealMapText = getGUIObjectByName("revealMapText");
    var victoryConditionText = getGUIObjectByName("victoryConditionText");
    var lockTeamsText = getGUIObjectByName("lockTeamsText");
    var enableCheatsText = getGUIObjectByName("enableCheatsText");
    var populationCapText = getGUIObjectByName("populationCapText");
    var startingResourcesText = getGUIObjectByName("startingResourcesText");

    var sizeIdx = (g_MapSizes.tiles.indexOf(mapSettings.Size) != -1 ? g_MapSizes.tiles.indexOf(mapSettings.Size) : g_MapSizes.default);
    var victoryIdx = (VICTORY_DATA.indexOf(mapSettings.GameType) != -1 ? VICTORY_DATA.indexOf(mapSettings.GameType) : VICTORY_DEFAULTIDX);
    enableCheats.checked = (g_GameAttributes.settings.CheatsEnabled === undefined || !g_GameAttributes.settings.CheatsEnabled ? false : true)
    enableCheatsText.caption = (enableCheats.checked ? "Yes" : "No");
    populationCap.selected = (POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) != -1 ? POPULATION_CAP_DATA.indexOf(mapSettings.PopulationCap) : POPULATION_CAP_DEFAULTIDX);
    populationCapText.caption = POPULATION_CAP[populationCap.selected];
    startingResources.selected = (STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) != -1 ? STARTING_RESOURCES_DATA.indexOf(mapSettings.StartingResources) : STARTING_RESOURCES_DEFAULTIDX);
    startingResourcesText.caption = STARTING_RESOURCES[startingResources.selected];
    // Handle map type specific logic
    switch (g_GameAttributes.mapType)
    {
    case "random":
    if (g_IsController)
    { //Host
    numPlayersSelection.selected = numPlayers - 1;
    numPlayersSelection.hidden = false;
    mapSize.hidden = false;
    revealMap.hidden = false;
    victoryCondition.hidden = false;
    lockTeams.hidden = false;
    populationCap.hidden = false;
    startingResources.hidden = false;

    numPlayersText.hidden = true;
    mapSizeText.hidden = true;
    revealMapText.hidden = true;
    victoryConditionText.hidden = true;
    lockTeamsText.hidden = true;
    populationCapText.hidden = true;
    startingResourcesText.hidden = true;

    // Update map preview
    getGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);

    mapSizeText.caption = "Map size:";
    mapSize.selected = sizeIdx;
    revealMapText.caption = "Reveal map:";
    revealMap.checked = (mapSettings.RevealMap ? true : false);

    victoryConditionText.caption = "Victory condition:";
    victoryCondition.selected = victoryIdx;
    lockTeamsText.caption = "Teams locked:";
    lockTeams.checked = (mapSettings.LockTeams ? true : false);
    }
    else
    {
    // Client
    numPlayersText.hidden = false;
    mapSizeText.hidden = false;
    revealMapText.hidden = false;
    victoryConditionText.hidden = false;
    lockTeamsText.hidden = false;
    populationCap.hidden = true;
    populationCapText.hidden = false;
    startingResources.hidden = true;
    startingResourcesText.hidden = false;
    // Update map preview
    getGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);

    numPlayersText.caption = numPlayers;
    mapSizeText.caption = g_MapSizes.names[sizeIdx];
    revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
    victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
    }

    break;

    case "scenario":
    // For scenario just reflect settings for the current map
    numPlayersSelection.hidden = true;
    mapSize.hidden = true;
    revealMap.hidden = true;
    victoryCondition.hidden = true;
    lockTeams.hidden = true;
    numPlayersText.hidden = false;
    mapSizeText.hidden = false;
    revealMapText.hidden = false;
    victoryConditionText.hidden = false;
    lockTeamsText.hidden = false;
    populationCap.hidden = true;
    populationCapText.hidden = false;
    startingResources.hidden = true;
    startingResourcesText.hidden = false;

    // Update map preview
    getGUIObjectByName("mapPreview").sprite = "cropped:(0.78125,0.5859375)session/icons/mappreview/" + getMapPreview(mapName);
    numPlayersText.caption = numPlayers;
    mapSizeText.caption = "Default";
    revealMapText.caption = (mapSettings.RevealMap ? "Yes" : "No");
    victoryConditionText.caption = VICTORY_TEXT[victoryIdx];
    lockTeamsText.caption = (mapSettings.LockTeams ? "Yes" : "No");
    getGUIObjectByName("populationCap").selected = POPULATION_CAP_DEFAULTIDX;

    break;

    default:
    error("onGameAttributesChange: Unexpected map type '"+g_GameAttributes.mapType+"'");
    return;
    }

    // Display map name
    getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);

    // Load the description from the map file, if there is one
    var description = mapSettings.Description || "Sorry, no description available.";

    if (g_GameAttributes.mapFilter == "Naval Maps")
    description += g_NavalWarning;

    // Describe the number of players
    var playerString = numPlayers + " " + (numPlayers == 1 ? "player" : "players") + ". ";

    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    // Show only needed player slots
    getGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers);

    // Show player data or defaults as necessary
    if (i < numPlayers)
    {
    var pName = getGUIObjectByName("playerName["+i+"]");
    var pCiv = getGUIObjectByName("playerCiv["+i+"]");
    var pCivText = getGUIObjectByName("playerCivText["+i+"]");
    var pTeam = getGUIObjectByName("playerTeam["+i+"]");
    var pTeamText = getGUIObjectByName("playerTeamText["+i+"]");
    var pColor = getGUIObjectByName("playerColour["+i+"]");

    // Player data / defaults
    var pData = mapSettings.PlayerData ? mapSettings.PlayerData[i] : {};
    var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[i] : {};

    // Common to all game types
    var color = iColorToString(getSetting(pData, pDefs, "Colour"));
    pColor.sprite = "colour:"+color+" 100";
    pName.caption = getSetting(pData, pDefs, "Name");

    var team = getSetting(pData, pDefs, "Team");
    var civ = getSetting(pData, pDefs, "Civ");

    // For clients or scenarios, hide some player dropdowns
    if (!g_IsController || g_GameAttributes.mapType == "scenario")
    {
    pCivText.hidden = false;
    pCiv.hidden = true;
    pTeamText.hidden = false;
    pTeam.hidden = true;
    // Set text values
    if (civ == "random")
    {
    pCivText.caption = "[color=\"orange\"]Random";
    }
    else
    {
    pCivText.caption = g_CivData[civ].Name;
    }
    pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
    }
    else if (g_GameAttributes.mapType == "random")
    {
    pCivText.hidden = true;
    pCiv.hidden = false;
    pTeamText.hidden = true;
    pTeam.hidden = false;
    // Set dropdown values
    pCiv.selected = (civ ? pCiv.list_data.indexOf(civ) : 0);
    pTeam.selected = (team !== undefined && team >= 0) ? team+1 : 0;
    }
    }
    }

    getGUIObjectByName("mapInfoDescription").caption = playerString + description;

    g_IsInGuiUpdate = false;

    // Game attributes include AI settings, so update the player list
    updatePlayerList();
    }

    function updateGameAttributes()
    {
    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    else
    {
    onGameAttributesChange();
    }
    }

    function updatePlayerList()
    {
    g_IsInGuiUpdate = true;

    var hostNameList = [];
    var hostGuidList = [];
    var assignments = [];
    var aiAssignments = {};
    var noAssignment;
    var assignedCount = 0;

    for (var guid in g_PlayerAssignments)
    {
    var name = g_PlayerAssignments[guid].name;
    var hostID = hostNameList.length;
    var player = g_PlayerAssignments[guid].player;

    hostNameList.push(name);
    hostGuidList.push(guid);
    assignments[player] = hostID;

    if (player != 255)
    assignedCount++;
    }

    // Only enable start button if we have enough assigned players
    if (g_IsController)
    getGUIObjectByName("startGame").enabled = (assignedCount > 0);

    for each (var ai in g_AIs)
    {
    if (ai.data.hidden)
    {
    // If the map uses a hidden AI then don't hide it
    var usedByMap = false;
    for (var i = 0; i < MAX_PLAYERS; ++i) {
    if (i < g_GameAttributes.settings.PlayerData.length &&
    g_GameAttributes.settings.PlayerData[i].AI == ai.id)
    {
    usedByMap = true;
    }
    }
    if (!usedByMap)
    {
    continue;
    }
    }
    // Give AI a different color so it stands out
    aiAssignments[ai.id] = hostNameList.length;
    hostNameList.push("[color=\"70 150 70 255\"]AI: " + ai.data.name);
    hostGuidList.push("ai:" + ai.id);
    }

    noAssignment = hostNameList.length;
    hostNameList.push("[color=\"140 140 140 255\"]Unassigned");
    hostGuidList.push("");

    for (var i = 0; i < MAX_PLAYERS; ++i)
    {
    let playerSlot = i;
    let playerID = i+1; // we don't show Gaia, so first slot is ID 1

    var selection = assignments[playerID];

    var configButton = getGUIObjectByName("playerConfig["+i+"]");
    configButton.hidden = true;

    // Look for valid player slots
    if (playerSlot < g_GameAttributes.settings.PlayerData.length)
    {
    // If no human is assigned, look for an AI instead
    if (selection === undefined)
    {
    var aiId = g_GameAttributes.settings.PlayerData[playerSlot].AI;
    if (aiId)
    {
    // Check for a valid AI
    if (aiId in aiAssignments)
    selection = aiAssignments[aiId];
    else
    warn("AI \""+aiId+"\" not present. Defaulting to unassigned.");
    }

    if (!selection)
    selection = noAssignment;

    // Since no human is assigned, show the AI config button
    if (g_IsController)
    {
    configButton.hidden = false;
    configButton.onpress = function()
    {
    Engine.PushGuiPage("page_aiconfig.xml", {
    ais: g_AIs,
    id: g_GameAttributes.settings.PlayerData[playerSlot].AI,
    callback: function(ai) {
    g_GameAttributes.settings.PlayerData[playerSlot].AI = ai.id;

    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    else
    {
    updatePlayerList();
    }
    }
    });
    };
    }
    }
    else
    {
    // There was a human, so make sure we don't have any AI left
    // over in their slot, if we're in charge of the attributes
    if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
    {
    g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
    if (g_IsNetworked)
    {
    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    }
    }

    var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
    assignBox.list = hostNameList;
    assignBox.list_data = hostGuidList;
    if (assignBox.selected != selection)
    {
    assignBox.selected = selection;
    }

    if (g_IsNetworked && g_IsController)
    {
    assignBox.onselectionchange = function ()
    {
    if (!g_IsInGuiUpdate)
    {
    var guid = hostGuidList[this.selected];
    if (guid == "")
    {
    // Unassign any host from this player slot
    Engine.AssignNetworkPlayer(playerID, "");
    // Remove AI from this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
    }
    else if (guid.substr(0, 3) == "ai:")
    {
    // Unassign any host from this player slot
    Engine.AssignNetworkPlayer(playerID, "");
    // Set the AI for this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
    }
    else
    swapPlayers(guid, playerSlot);

    Engine.SetNetworkGameAttributes(g_GameAttributes);
    }
    };
    }
    else if (!g_IsNetworked)
    {
    assignBox.onselectionchange = function ()
    {
    if (!g_IsInGuiUpdate)
    {
    var guid = hostGuidList[this.selected];
    if (guid == "")
    {
    // Remove AI from this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
    }
    else if (guid.substr(0, 3) == "ai:")
    {
    // Set the AI for this player slot
    g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
    }
    else
    swapPlayers(guid, playerSlot);

    updatePlayerList();
    }
    };
    }
    }
    }

    g_IsInGuiUpdate = false;
    }

    function swapPlayers(guid, newSlot)
    {
    // Player slots are indexed from 0 as Gaia is omitted.
    var newPlayerID = newSlot + 1;
    var playerID = g_PlayerAssignments[guid].player;

    // Attempt to swap the player or AI occupying the target slot,
    // if any, into the slot this player is currently in.
    if (playerID != 255)
    {
    for (var i in g_PlayerAssignments)
    {
    // Move the player in the destination slot into the current slot.
    if (g_PlayerAssignments[i].player == newPlayerID)
    {
    if (g_IsNetworked)
    Engine.AssignNetworkPlayer(playerID, i);
    else
    g_PlayerAssignments[i].player = playerID;
    break;
    }
    }

    // Transfer the AI from the target slot to the current slot.
    g_GameAttributes.settings.PlayerData[playerID - 1].AI = g_GameAttributes.settings.PlayerData[newSlot].AI;
    }

    if (g_IsNetworked)
    Engine.AssignNetworkPlayer(newPlayerID, guid);
    else
    g_PlayerAssignments[guid].player = newPlayerID;

    // Remove AI from this player slot
    g_GameAttributes.settings.PlayerData[newSlot].AI = "";
    }

    function submitChatInput()
    {
    var input = getGUIObjectByName("chatInput");
    var text = input.caption;
    if (text.length)
    {
    Engine.SendNetworkChat(text);
    input.caption = "";
    }
    }

    function addChatMessage(msg)
    {
    var username = escapeText(msg.username || g_PlayerAssignments[msg.guid].name);
    var message = escapeText(msg.text);

    // TODO: Maybe host should have distinct font/color?
    var color = "white";

    if (g_PlayerAssignments[msg.guid] && g_PlayerAssignments[msg.guid].player != 255)
    { // Valid player who has been assigned - get player colour
    var player = g_PlayerAssignments[msg.guid].player - 1;
    var mapName = g_GameAttributes.map;
    var mapData = loadMapData(mapName);
    var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
    var pData = mapSettings.PlayerData ? mapSettings.PlayerData[player] : {};
    var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[player] : {};

    color = iColorToString(getSetting(pData, pDefs, "Colour"));
    }

    var formatted;
    switch (msg.type)
    {
    case "connect":
    formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has joined[/color]';
    break;

    case "disconnect":
    formatted = '[font="serif-bold-13"][color="'+ color +'"]' + username + '[/color][/font] [color="gold"]has left[/color]';
    break;

    case "message":
    formatted = '[font="serif-bold-13"]<[color="'+ color +'"]' + username + '[/color]>[/font] ' + message;
    break;

    default:
    error("Invalid chat message '" + uneval(msg) + "'");
    return;
    }

    g_ChatMessages.push(formatted);

    getGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
    }

    function toggleMoreOptions()
    {
    getGUIObjectByName("moreOptions").hidden = !getGUIObjectByName("moreOptions").hidden;
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // Basic map filters API

    // Add a new map list filter
    function addFilter(name, filterFunc)
    {
    if (filterFunc instanceof Object)
    { // Basic validity test
    var newFilter = {};
    newFilter.name = name;
    newFilter.filter = filterFunc;

    g_MapFilters.push(newFilter);
    }
    else
    {
    error("Invalid map filter: "+name);
    }
    }

    // Get array of map filter names
    function getFilters()
    {
    var filters = [];
    for (var i = 0; i < g_MapFilters.length; ++i)
    {
    filters.push(g_MapFilters[i].name);
    }

    return filters;
    }

    // Test map filter on given map settings object
    function testFilter(name, mapSettings)
    {
    for (var i = 0; i < g_MapFilters.length; ++i)
    {
    if (g_MapFilters[i].name == name)
    { // Found filter
    return g_MapFilters[i].filter(mapSettings);
    }
    }

    error("Invalid map filter: "+name);
    return false;
    }

    // Test an array of keywords against a match array using AND logic
    function keywordTestAND(keywords, matches)
    {
    if (!keywords || !matches)
    {
    return false;
    }

    for (var m = 0; m < matches.length; ++m)
    { // Fail on not match
    if (keywords.indexOf(matches[m]) == -1)
    {
    return false;
    }
    }
    return true;
    }

    // Test an array of keywords against a match array using OR logic
    function keywordTestOR(keywords, matches)
    {
    if (!keywords || !matches)
    {
    return false;
    }

    for (var m = 0; m < matches.length; ++m)
    { // Success on match
    if (keywords.indexOf(matches[m]) != -1)
    {
    return true;
    }
    }
    return false;
    }

  11. \binaries\data\mods\public\gui\gamesetup\gamesetup.js seams to be the file in question (function selectMap() line 745). Until now MAX_PLAYERS is a constant. Better to add another variable and do something like min(MAX_PLAYERS, max_players_rms) or change it to a variable entirely (I guess the first is best)?

    source\ps\scripting\JSInterface_Console.cpp handles autostart options but I'll leave them untouched (for testing player caps are not whanted anyway I guess).

    Still searching where the RMS.JSON file is loaded to add the "MaximumPlayers" and "MinimumPlayers" settings...

    EDIT: Should be binaries\data\mods\public\gui\common\functions_utility.js function parseJSONData() line 60. Seams just to load all data so no change needed here.

    Should be quite simple at first glance.

  12. Yes this will be possible, the restrictions can work both ways. It's up to the creator of the random map to assign the restrictions manually, or just leave it on default (if that's possible). We don't want to be too restrictive on playable options if there's no reason to be.

    Yep. And I think even [4, 6, 8, 8, 8, 8, 8] would do. Tiny maps give Iberians no walls anyway, the main problem on tiny maps. Lower limits should not be set by default but only by maps. Even number seam better to me (for 2 equally strong teams).

  13. I noticed that many PPL like formations and I think it could turn out to be a good thing. But at the moment it is pretty much destroying my style of play. I can't order a vast amount of troops even to go to a point on the map without having them stupidly running far into enemy territory not to mention the time lost by the units stupidly tangling around instead of attacking. So I really want something like a "no formation" formation that gives the order to each selected unit individually at least.

  14. (I've left a response on the ticket.)

    For some reason I can't log into track ATM. The function should take the entity type string e.g. athen_wall_long (The base name of the xml file) and return an associative array including the data. To pick the right mod it would be handy to have the mod path as a variable or constant (not sure here, see below) or another identifier that takes care the right xml from the right path is chosen.

    AFAIK it is planned to make a map enforce the use of a specific mod. So if the mod identifier should be a constant or a variable depends on when the RMGEN code is loaded (every time a RMS is loaded or only the first time?).

    I may not be able to do anything serious until early spring (in our calender) because university work is taking some time. So it would be good if FeXoR can do it if he has time.

    I didn't write the UI, just modified it (I think Brian was the main designer), but I agree that it should be implemented in the JSON files. Maybe an array like this:

    "MaximumPlayers" : [3, 4, 5, 6, 7, 8, 8],

    Should be added to the .json representing the maximum available players in each map size respectively (tiny, small, etc...). Gamesetup UI code will remove the extra options upon selecting size. Then all that remains is finding and adding the best max player choices for each map.

    Agreed. Where do I find the Gamesetup UI code and the code that loads the RMS JSON files?

  15. I'm not sure what you mean by 'engine' here? The engine is, AFAIK, generally considered to be the pyrogenesis executable. So it is already running at game setup and passing values from game setup to RMGEN should be quite trivial. Do we disagree on that?

    OK, maybe I mean the game engine (simulations?) that is not run when pyrogenesis is executed but after the map is loaded (or loads the map). And in case of a random map it is first generated by RMGEN and then loaded AFAIK. Not sure how to name it, I'm very bad of following naming conventions, sorry.

    If parsing data to RMGEN is trivial please tell me how to get the data for entities inside RMGEN.

  16. I found the core of it at last. The tileclass logic indeed has some problems as it can't handle decimal numbers as radius in "some" cases. Easy fix in the map itself.

    Which lines?

    Sorry edited my last post after you added your's but it's way OT anyway ^^.

    EDIT: Oh, didn't read the last one.

    But why call it triggers when everybody understands something completely different by that term? Why not just call it 'a set of common functions' or 'an API' or something equally meaningful? :)

    Not sure. Perhaps because I'm in love with them ^^ (See the ending of my previous post).

  17. That would be OK for me too but you'd have a great amount of data very close or even right inside the engine. I thought that would be not wanted and a layer between is already implemented (simulations). If it can be used easy to implement triggers there (or not even needed) we just have to change the map loading/random map generation part. But still that would be a lot of work. On the other hand it would still not work to restrict map/player settings dependent on the map used because simulations is started with the engine after game settings are chosen. So the engine has to run while map/player settings are chosen or a restriction for possible map settings has to be loaded before opening the settings screen (e.g. a settings file for all maps like for random maps it is JSON right now).

    So back to your question: Because then we would have only one layer of code to maintain and in it very general functions that can be used by more than one part of the game (e.g. RMGEN/AI) instead of parsing data to every part separately. Another example is the terrain analysis that could be done right at map generation and then can be used by the AIs or even pathfinding.

    You still might be right that I am overrating triggers. Perhaps because I loved how to manipulate maps in Warcraft III. But I'm not the only one. It was so simple and intuitive that a vast amount of maps came out of that some even followed by an own game for this map e.g. Defense of the Ancients: http://en.wikipedia....of_the_Ancients and here an in-game video:

    ...resulting in this game: http://blog.dota2.com/ and an in-game video:

    NOTE: Wikipedia states DotA would be a mod but actually it's just a map. WC3 just allows wide manipulation of maps.

    DotA 2 however is an entirely different game!

  18. Sure, as I said, they can be used to make interesting maps. But that's about it. They don't have any applicability to the tickets listed above.

    No, that concerned general game rules. I though I said that.

    Changing things ingame and having access to more things in RMGEN as well as AI API (so you load a set of triggers just into the map that manages the AI players rather than the current approach) could be done with triggers (If powerful enough). So all parts get all information (currently RMGEN has about zero information about anything outside the map like entity properties and cannot manipulate anything outside map generation e.g. the possible player settings or build restrictions or the chosen mod).

  19. This all would be so easy if everything was trigger based (Just dreaming I guess ^^).

    For example it would be nice to have an array "players" in RMGEN that has properties like e.g. "slotStatus" that shows if the player is a person or an AI.

    Not to mention the entity properties that would help a lot to have access to in RMGEN...

    Well, guess we should focus a bit and for now on performance:

    http://www.wildfireg...showtopic=16991

    ...and cleaning up the general game rules (that are pretty a mess right now IMO):

    http://www.wildfireg...showtopic=16986

    http://www.wildfireg...?showtopic=6352

    ...among others causing e.g.:

    http://trac.wildfire...com/ticket/1496

    http://trac.wildfire...com/ticket/1537

    http://trac.wildfire...com/ticket/1010

    ...among others.

    Still a wide variety of "features" (indeed most tickets issues) could be easily fixed by having triggers (which ofc. would take a huge amount of work and time to implement that widely/powerful in the first place) but would interfere with the actually implemented mod design (suggesting a much less powerful trigger approach like http://www.wildfireg...showtopic=16887).

×
×
  • Create New...