Ticket #3994: t3994_gamesetup_GUI_rewrite_v1.patch

File t3994_gamesetup_GUI_rewrite_v1.patch, 44.7 KB (added by elexis, 8 years ago)
  • binaries/data/mods/public/gui/gamesetup/gamesetup.js

     
    11const g_MatchSettings_SP = "config/matchsettings.json";
    22const g_MatchSettings_MP = "config/matchsettings.mp.json";
    33
     4const g_PlayerArray = Array(g_MaxPlayers).fill(0).map((v, i) => i + 1); // 1, 2, ..., MaxPlayers
    45const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire);
    56const g_GameSpeeds = prepareForDropdown(g_Settings && g_Settings.GameSpeeds.filter(speed => !speed.ReplayOnly));
    67const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes);
    78const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes);
    89const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities);
    const g_FormatChatMessage = {  
    5758        "username": user
    5859    }),
    5960    "clientlist": (msg, user) => getUsernameList()
    6061};
    6162
    62 /**
    63  * The dropdownlist items will appear in the order they are added.
    64  */
    65 const g_MapFilters = [
     63const g_MapFilters = prepareForDropdown([
    6664    {
    6765        "id": "default",
    6866        "name": translate("Default"),
    69         "filter": mapKeywords => mapKeywords.every(keyword => ["naval", "demo", "hidden"].indexOf(keyword) == -1)
     67        "filter": mapKeywords => mapKeywords.every(keyword => ["naval", "demo", "hidden"].indexOf(keyword) == -1),
     68        "Default": true
    7069    },
    7170    {
    7271        "id": "naval",
    7372        "name": translate("Naval Maps"),
    7473        "filter": mapKeywords => mapKeywords.indexOf("naval") != -1
    const g_MapFilters = [  
    8685    {
    8786        "id": "all",
    8887        "name": translate("All Maps"),
    8988        "filter": mapKeywords => true
    9089    }
    91 ];
     90]);
    9291
    9392/**
    9493 * Used for generating the botnames.
    9594 */
    9695const g_RomanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
    var g_DefaultPlayerData = [];  
    198197var g_GameAttributes = { "settings": {} };
    199198
    200199var g_ChatMessages = [];
    201200
    202201/**
    203  * Cache containing the mapsettings for scenario/skirmish maps. Just-in-time loading.
     202 * Filename and translated title of all maps, given the currently selected
     203 * maptype and filter. Sorted by title, shown in the dropdown.
     204 */
     205var g_MapList = [];
     206
     207/**
     208 * Cache containing the mapsettings. Just-in-time loading.
    204209 */
    205210var g_MapData = {};
    206211
    207212/**
    208213 * Wait one tick before initializing the GUI objects and
    var g_LastGameStanza;  
    219224 * Remembers if the current player viewed the AI settings of some playerslot.
    220225 */
    221226var g_LastViewedAIPlayer = -1;
    222227
    223228/**
     229 * Contains the logic of all multiple-choice gamesettings.
     230 *
     231 * Hidden - if so, both label and dropdown won't be visible.
     232 * Enabled - Only the label will be shown if it's disabled.
     233 * Default - returns the index of the default value (not the value itself).
     234 *
     235 * NOTICE: The first three elements need to be initialized first.
     236 * If the map is changed, missing values are supplemented with defaults.
     237 */
     238const g_Dropdowns = g_Settings && {
     239    "mapType": {
     240        "labels": () => g_MapTypes.Title,
     241        "ids": () => g_MapTypes.Name,
     242        "default": () => g_MapTypes.Default,
     243        "defined": () => g_GameAttributes.mapType !== undefined,
     244        "get": () => g_GameAttributes.mapType,
     245        "select": idx => {
     246
     247            g_MapData = {};
     248
     249            g_GameAttributes.mapType = g_MapTypes.Name[idx];
     250            g_GameAttributes.mapPath = g_MapPath[g_GameAttributes.mapType];
     251            delete g_GameAttributes.map;
     252
     253            if (g_GameAttributes.mapType != "scenario")
     254                g_GameAttributes.settings = {
     255                    "PlayerData": g_DefaultPlayerData.slice(0, 4)
     256                };
     257
     258            reloadMapList();
     259            supplementDefaults();
     260        }
     261    },
     262    "mapFilter": {
     263        "labels": () => g_MapFilters.name,
     264        "ids": () => g_MapFilters.id,
     265        "default": () => g_MapFilters.Default,
     266        "defined": () => g_GameAttributes.mapFilter !== undefined,
     267        "get": () => g_GameAttributes.mapFilter,
     268        "select": idx => {
     269            g_GameAttributes.mapFilter = g_MapFilters.id[idx];
     270            delete g_GameAttributes.map;
     271            reloadMapList();
     272            supplementDefaults();
     273        }
     274    },
     275    "mapSelection": {
     276        "labels": () => g_MapList.name,
     277        "ids": () => g_MapList.file,
     278        "default": () => 0,
     279        "defined": () => g_GameAttributes.map !== undefined,
     280        "get": () => g_GameAttributes.map,
     281        "select": idx => {
     282            selectMap(g_MapList.file[idx]);
     283            supplementDefaults();
     284        }
     285    },
     286    "mapSize": {
     287        "labels": () => g_MapSizes.LongName,
     288        "ids": () => g_MapSizes.Tiles,
     289        "default": () => g_MapSizes.Default,
     290        "defined": () => g_GameAttributes.settings.Size !== undefined,
     291        "get": () => g_GameAttributes.settings.Size,
     292        "select": idx => {
     293            g_GameAttributes.settings.Size = g_MapSizes.Tiles[idx];
     294        },
     295        "enabled": () => g_GameAttributes.mapType == "random"
     296    },
     297    "numPlayers": {
     298        "labels": () => g_PlayerArray,
     299        "ids": () => g_PlayerArray,
     300        "default": () => g_MaxPlayers - 1,
     301        "defined": () => g_GameAttributes.settings.PlayerData !== undefined,
     302        "get": () => g_GameAttributes.settings.PlayerData.length,
     303        "select": idx => {
     304            selectNumPlayers(idx + 1);
     305        },
     306        "enabled": () => g_GameAttributes.mapType == "random"
     307    },
     308    "populationCap": {
     309        "labels": () => g_PopulationCapacities.Title,
     310        "ids": () => g_PopulationCapacities.Population,
     311        "default": () => g_PopulationCapacities.Default,
     312        "defined": () => g_GameAttributes.settings.PopulationCap !== undefined,
     313        "get": () => g_GameAttributes.settings.PopulationCap,
     314        "select": idx => {
     315            g_GameAttributes.settings.PopulationCap = g_PopulationCapacities.Population[idx];
     316        },
     317        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     318    },
     319    "startingResources": {
     320        "labels": () => g_StartingResources.Title,
     321        "ids": () => g_StartingResources.Resources,
     322        "default": () => g_StartingResources.Default,
     323        "defined": () => g_GameAttributes.settings.StartingResources !== undefined,
     324        "get": () => g_GameAttributes.settings.StartingResources,
     325        "select": idx => {
     326            g_GameAttributes.settings.StartingResources = g_StartingResources.Resources[idx];
     327        },
     328        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     329    },
     330    "ceasefire": {
     331        "labels": () => g_Ceasefire.Title,
     332        "ids": () => g_Ceasefire.Duration,
     333        "default": () => g_Ceasefire.Default,
     334        "defined": () => g_GameAttributes.settings.Ceasefire !== undefined,
     335        "get": () => g_GameAttributes.settings.Ceasefire,
     336        "select": idx => {
     337            g_GameAttributes.settings.Ceasefire = g_Ceasefire.Duration[idx];
     338        },
     339        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     340    },
     341    "victoryCondition": {
     342        "labels": () => g_VictoryConditions.Title,
     343        "ids": () => g_VictoryConditions.Name,
     344        "default": () => g_VictoryConditions.Default,
     345        "defined": () => g_GameAttributes.settings.GameType !== undefined,
     346        "get": () => g_GameAttributes.settings.GameType,
     347        "select": idx => {
     348            g_GameAttributes.settings.GameType = g_VictoryConditions.Name[idx];
     349            g_GameAttributes.settings.VictoryScripts = g_VictoryConditions.Scripts[idx];
     350        },
     351        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     352    },
     353    "wonderDuration": {
     354        "labels": () => g_WonderDurations.Title,
     355        "ids": () => g_WonderDurations.Duration,
     356        "default": () => g_WonderDurations.Default,
     357        "defined": () => g_GameAttributes.settings.WonderDuration !== undefined,
     358        "get": () => g_GameAttributes.settings.WonderDuration,
     359        "select": idx => {
     360            g_GameAttributes.settings.WonderDuration = g_WonderDurations.Duration[idx];
     361        },
     362        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     363    },
     364    "gameSpeed": {
     365        "labels": () => g_GameSpeeds.Title,
     366        "ids": () => g_GameSpeeds.Speed,
     367        "default": () => g_GameSpeeds.Default,
     368        "defined": () => g_GameAttributes.gameSpeed !== undefined,
     369        "get": () => g_GameAttributes.gameSpeed,
     370        "select": idx => {
     371            g_GameAttributes.gameSpeed = g_GameSpeeds.Speed[idx];
     372        }
     373    }
     374};
     375
     376/**
     377 * Contains the logic of all boolean gamesettings.
     378 */
     379const g_Checkboxes = {
     380    "revealMap": {
     381        "default": () => false,
     382        "defined": () => g_GameAttributes.settings.RevealMap !== undefined,
     383        "get": () => g_GameAttributes.settings.RevealMap,
     384        "set": checked => {
     385            g_GameAttributes.settings.RevealMap = checked;
     386        },
     387        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     388    },
     389    "exploreMap": {
     390        "default": () => false,
     391        "defined": () => g_GameAttributes.settings.ExploreMap !== undefined,
     392        "get": () => g_GameAttributes.settings.ExploreMap,
     393        "set": checked => {
     394            g_GameAttributes.settings.ExploreMap = checked;
     395        },
     396        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     397    },
     398    "disableTreasures": {
     399        "default": () => false,
     400        "defined": () => g_GameAttributes.settings.DisableTreasures !== undefined,
     401        "get": () => g_GameAttributes.settings.DisableTreasures,
     402        "set": checked => {
     403            g_GameAttributes.settings.DisableTreasures = checked;
     404        },
     405        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1
     406    },
     407    "lockTeams":  {
     408        "default": () => Engine.HasXmppClient(),
     409        "defined": () => g_GameAttributes.settings.LockTeams !== undefined,
     410        "get": () => g_GameAttributes.settings.LockTeams,
     411        "set": checked => {
     412            g_GameAttributes.settings.LockTeams = checked;
     413        },
     414        "enabled": () => ["random", "skirmish"].indexOf(g_GameAttributes.mapType) != -1 &&
     415                         !g_GameAttributes.settings.RatingEnabled,
     416    },
     417    "enableCheats":  {
     418        "default": () => !g_IsNetworked,
     419        "hidden": () => !g_IsNetworked,
     420        "defined": () => g_GameAttributes.settings.CheatsEnabled !== undefined,
     421        "get": () => g_GameAttributes.settings.CheatsEnabled,
     422        "set": checked => {
     423            g_GameAttributes.settings.CheatsEnabled = !g_IsNetworked ||
     424                checked && !g_GameAttributes.settings.RatingEnabled;
     425        },
     426        "enabled": () => !g_GameAttributes.settings.RatingEnabled
     427    },
     428    "enableRating":  {
     429        "default": () => Engine.HasXmppClient(),
     430        "defined": () => g_GameAttributes.settings.RatingEnabled !== undefined,
     431        "get": () => !!g_GameAttributes.settings.RatingEnabled,
     432        "set": checked => {
     433            g_GameAttributes.settings.RatingEnabled = Engine.HasXmppClient() ? checked : undefined;
     434            Engine.SetRankedGame(!!g_GameAttributes.settings.RatingEnabled);
     435        }
     436    }
     437};
     438
     439/**
     440 * GUI objects in the order they should appear in the more options window.
     441 */
     442const g_MoreOptions = [
     443    "optionGameSpeed",
     444    "optionVictoryCondition",
     445    "optionWonderDuration",
     446    "optionPopulationCap",
     447    "optionStartingResources",
     448    "optionCeasefire",
     449    "optionRevealMap",
     450    "optionExploreMap",
     451    "optionDisableTreasures",
     452    "optionLockTeams",
     453    "optionCheats",
     454    "optionRating",
     455    "hideMoreOptions"
     456];
     457
     458/**
     459 * The size each GUI object in the more options dialog should have.
     460 */
     461const g_MoreOptionsEntryHeight = 30;
     462
     463/**
     464 * For setting up some additional GUI objects.
     465 */
     466const g_MiscControls = {
     467    "chatPanel": {
     468        "hidden": () => !g_IsNetworked
     469    },
     470    "optionCheats": {
     471        "hidden": () => !g_IsNetworked
     472    },
     473    "optionRating": {
     474        "hidden": () => !Engine.HasXmppClient()
     475    },
     476    "optionWonderDuration": {
     477        "hidden": () => g_GameAttributes.settings.GameType != "wonder"
     478    },
     479    "cheatWarningText": {
     480        "hidden": () => !g_IsNetworked || !g_GameAttributes.settings.CheatsEnabled
     481    },
     482    "mapSize": {
     483        "hidden": () => g_GameAttributes.mapType != "random" || !g_IsController
     484    },
     485    "mapSizeText": {
     486        "hidden": () => g_GameAttributes.mapType != "random" || g_IsController
     487    },
     488    "mapSizeDesc": {
     489        "hidden": () => g_GameAttributes.mapType != "random"
     490    },
     491    "cancelGame": {
     492        "tooltip": () => Engine.HasXmppClient() ?
     493            translate("Return to the lobby.") :
     494            translate("Return to the main menu.")
     495    }
     496};
     497
     498/**
    224499 * Initializes some globals without touching the GUI.
    225500 *
    226501 * @param {Object} attribs - context data sent by the lobby / mainmenu
    227502 */
    228503function init(attribs)
    function init(attribs)  
    255530    g_DefaultPlayerData = g_Settings.PlayerDefaults;
    256531    g_DefaultPlayerData.shift();
    257532    for (let i in g_DefaultPlayerData)
    258533        g_DefaultPlayerData[i].Civ = "random";
    259534
     535    supplementDefaults();
     536
    260537    setTimeout(displayGamestateNotifications, 1000);
    261538}
    262539
    263540/**
     541 * Sets default values for all g_GameAttribute settings which don't have a value set.
     542 */
     543function supplementDefaults()
     544{
     545    for (let dropdown in g_Dropdowns)
     546        if (!g_Dropdowns[dropdown].defined())
     547            g_Dropdowns[dropdown].select(g_Dropdowns[dropdown]["default"]());
     548
     549    for (let checkbox in g_Checkboxes)
     550        if (!g_Checkboxes[checkbox].defined())
     551            g_Checkboxes[checkbox].set(g_Checkboxes[checkbox]["default"]());
     552}
     553
     554/**
    264555 * Called after the first tick.
    265556 */
    266557function initGUIObjects()
    267558{
    268     Engine.GetGUIObjectByName("cancelGame").tooltip = Engine.HasXmppClient() ? translate("Return to the lobby.") : translate("Return to the main menu.");
     559    for (let dropdown in g_Dropdowns)
     560        initDropdown(dropdown);
    269561
    270     initCivNameList();
    271     initMapTypes();
    272     initMapFilters();
     562    for (let checkbox in g_Checkboxes)
     563        initCheckbox(checkbox);
    273564
    274     if (g_IsController)
    275     {
    276         // Unique ID to be used for identifying the same game reports for the lobby
    277         g_GameAttributes.matchID = Engine.GetMatchID();
    278         g_GameAttributes.settings.CheatsEnabled = !g_IsNetworked;
    279         g_GameAttributes.settings.RatingEnabled = Engine.IsRankedGame() || undefined;
    280 
    281         initMapNameList();
    282         initNumberOfPlayers();
    283         initGameSpeed();
    284         initPopulationCaps();
    285         initStartingResources();
    286         initCeasefire();
    287         initWonderDurations();
    288         initVictoryConditions();
    289         initMapSizes();
    290         initRadioButtons();
    291     }
    292     else
    293         hideControls();
    294 
    295     initMultiplayerSettings();
     565    // TODO: move to g_DropdownArrays
     566    initCivNameList();
    296567    initPlayerAssignments();
     568    // TODO unify with dupe calls and make dependent of g_DropdownArrays
     569    Engine.GetGUIObjectByName("startGame").enabled = !g_IsController;
    297570
    298571    resizeMoreOptionsWindow();
    299572
    300573    if (g_IsNetworked)
    301574        Engine.GetGUIObjectByName("chatInput").focus();
    function initGUIObjects()  
    307580            warn("initGUIObjects() called while in GUI update");
    308581        updateGameAttributes();
    309582    }
    310583}
    311584
    312 function initMapTypes()
     585function initDropdown(name)
    313586{
    314     let mapTypes = Engine.GetGUIObjectByName("mapType");
    315     mapTypes.list = g_MapTypes.Title;
    316     mapTypes.list_data = g_MapTypes.Name;
    317     mapTypes.onSelectionChange = function() {
    318         if (this.selected != -1)
    319             selectMapType(this.list_data[this.selected]);
     587    let ctrl = Engine.GetGUIObjectByName(name);
     588    ctrl.list = g_Dropdowns[name].labels();
     589    ctrl.list_data = g_Dropdowns[name].ids();
     590    ctrl.onSelectionChange = function() {
     591
     592        if (!g_IsController ||
     593            g_IsInGuiUpdate ||
     594            !ctrl.list_data[this.selected] ||
     595            g_Dropdowns[this.name].enabled && !g_Dropdowns[this.name].enabled() ||
     596            g_Dropdowns[this.name].hidden && g_Dropdowns[this.name].hidden())
     597            return;
     598
     599        g_Dropdowns[this.name].select(this.selected);
     600        updateGameAttributes();
    320601    };
    321     if (g_IsController)
    322         mapTypes.selected = g_MapTypes.Default;
    323602}
    324603
    325 function initMapFilters()
     604function initCheckbox(name)
    326605{
    327     let mapFilters = Engine.GetGUIObjectByName("mapFilter");
    328     mapFilters.list = g_MapFilters.map(mapFilter => mapFilter.name);
    329     mapFilters.list_data = g_MapFilters.map(mapFilter => mapFilter.id);
    330     mapFilters.onSelectionChange = function() {
    331         if (this.selected != -1)
    332             selectMapFilter(this.list_data[this.selected]);
     606    let ctrl = Engine.GetGUIObjectByName(name);
     607    ctrl.onPress = function() {
     608
     609        if (!g_IsController ||
     610            g_IsInGuiUpdate ||
     611            g_Checkboxes[this.name].enabled && !g_Checkboxes[this.name].enabled() ||
     612            g_Checkboxes[this.name].hidden && g_Checkboxes[this.name].hidden())
     613            return;
     614
     615        g_Checkboxes[this.name].set(this.checked);
     616        updateGameAttributes();
    333617    };
    334     if (g_IsController)
    335         mapFilters.selected = 0;
    336     g_GameAttributes.mapFilter = "default";
    337618}
    338619
    339620/**
    340621 * Remove empty space in case of hidden options (like cheats, rating or wonder duration)
    341622 */
    342623function resizeMoreOptionsWindow()
    343624{
    344     const elementHeight = 30;
    345     const elements = [
    346         "optionGameSpeed",
    347         "optionVictoryCondition",
    348         "optionWonderDuration",
    349         "optionPopulationCap",
    350         "optionStartingResources",
    351         "optionCeasefire",
    352         "optionRevealMap",
    353         "optionExploreMap",
    354         "optionDisableTreasures",
    355         "optionLockTeams",
    356         "optionCheats",
    357         "optionRating",
    358         "hideMoreOptions"
    359     ];
    360 
    361625    let yPos = undefined;
    362     for (let element of elements)
     626    for (let element of g_MoreOptions)
    363627    {
    364628        let guiOption = Engine.GetGUIObjectByName(element);
    365629        let gSize = guiOption.size;
    366630        yPos = yPos || gSize.top;
    367631
    368632        if (guiOption.hidden)
    369633            continue;
    370634
    371635        gSize.top = yPos;
    372         gSize.bottom = yPos + elementHeight - 2;
     636        gSize.bottom = yPos + g_MoreOptionsEntryHeight - 2;
    373637        guiOption.size = gSize;
    374638
    375         yPos += elementHeight;
     639        yPos += g_MoreOptionsEntryHeight;
    376640    }
    377641
    378642    // Resize the vertically centered window containing the options
    379643    let moreOptions = Engine.GetGUIObjectByName("moreOptions");
    380644    let mSize = moreOptions.size;
    381645    mSize.bottom = mSize.top + yPos + 20;
    382646    moreOptions.size = mSize;
    383647}
    384648
    385 function initNumberOfPlayers()
    386 {
    387     let playersArray = Array(g_MaxPlayers).fill(0).map((v, i) => i + 1); // 1, 2, ..., MaxPlayers
    388     let numPlayers = Engine.GetGUIObjectByName("numPlayers");
    389     numPlayers.list = playersArray;
    390     numPlayers.list_data = playersArray;
    391     numPlayers.onSelectionChange = function() {
    392         if (this.selected != -1)
    393             selectNumPlayers(this.list_data[this.selected]);
    394     };
    395     numPlayers.selected = g_MaxPlayers - 1;
    396 }
    397 
    398 function initGameSpeed()
    399 {
    400     let gameSpeed = Engine.GetGUIObjectByName("gameSpeed");
    401     gameSpeed.hidden = false;
    402     Engine.GetGUIObjectByName("gameSpeedText").hidden = true;
    403     gameSpeed.list = g_GameSpeeds.Title;
    404     gameSpeed.list_data = g_GameSpeeds.Speed;
    405     gameSpeed.onSelectionChange = function() {
    406         if (this.selected != -1)
    407             g_GameAttributes.gameSpeed = g_GameSpeeds.Speed[this.selected];
    408 
    409         updateGameAttributes();
    410     };
    411     gameSpeed.selected = g_GameSpeeds.Default;
    412 }
    413 
    414 function initPopulationCaps()
    415 {
    416     let populationCaps = Engine.GetGUIObjectByName("populationCap");
    417     populationCaps.list = g_PopulationCapacities.Title;
    418     populationCaps.list_data = g_PopulationCapacities.Population;
    419     populationCaps.selected = g_PopulationCapacities.Default;
    420     populationCaps.onSelectionChange = function() {
    421         if (this.selected != -1)
    422             g_GameAttributes.settings.PopulationCap = g_PopulationCapacities.Population[this.selected];
    423 
    424         updateGameAttributes();
    425     };
    426 }
    427 
    428 function initStartingResources()
    429 {
    430     let startingResourcesL = Engine.GetGUIObjectByName("startingResources");
    431     startingResourcesL.list = g_StartingResources.Title;
    432     startingResourcesL.list_data = g_StartingResources.Resources;
    433     startingResourcesL.selected = g_StartingResources.Default;
    434     startingResourcesL.onSelectionChange = function() {
    435         if (this.selected != -1)
    436             g_GameAttributes.settings.StartingResources = g_StartingResources.Resources[this.selected];
    437 
    438         updateGameAttributes();
    439     };
    440 }
    441 
    442 function initCeasefire()
    443 {
    444     let ceasefireL = Engine.GetGUIObjectByName("ceasefire");
    445     ceasefireL.list = g_Ceasefire.Title;
    446     ceasefireL.list_data = g_Ceasefire.Duration;
    447     ceasefireL.selected = g_Ceasefire.Default;
    448     ceasefireL.onSelectionChange = function() {
    449         if (this.selected != -1)
    450             g_GameAttributes.settings.Ceasefire = g_Ceasefire.Duration[this.selected];
    451 
    452         updateGameAttributes();
    453     };
    454 }
    455 
    456 function initVictoryConditions()
    457 {
    458     let victoryConditions = Engine.GetGUIObjectByName("victoryCondition");
    459     victoryConditions.list = g_VictoryConditions.Title;
    460     victoryConditions.list_data = g_VictoryConditions.Name;
    461     victoryConditions.onSelectionChange = function() {
    462         if (this.selected != -1)
    463         {
    464             g_GameAttributes.settings.GameType = g_VictoryConditions.Name[this.selected];
    465             g_GameAttributes.settings.VictoryScripts = g_VictoryConditions.Scripts[this.selected];
    466         }
    467 
    468         updateGameAttributes();
    469     };
    470     victoryConditions.selected = g_VictoryConditions.Default;
    471 }
    472 
    473 function initWonderDurations()
    474 {
    475     let wonderConditions = Engine.GetGUIObjectByName("wonderDuration");
    476     wonderConditions.list = g_WonderDurations.Title;
    477     wonderConditions.list_data = g_WonderDurations.Duration;
    478     wonderConditions.onSelectionChange = function()
    479     {
    480         if (this.selected != -1)
    481             g_GameAttributes.settings.WonderDuration = g_WonderDurations.Duration[this.selected];
    482 
    483         updateGameAttributes();
    484     };
    485     wonderConditions.selected = g_WonderDurations.Default;
    486 }
    487 
    488 function initMapSizes()
    489 {
    490     let mapSize = Engine.GetGUIObjectByName("mapSize");
    491     mapSize.list = g_MapSizes.LongName;
    492     mapSize.list_data = g_MapSizes.Tiles;
    493     mapSize.onSelectionChange = function() {
    494         if (this.selected != -1)
    495             g_GameAttributes.settings.Size = g_MapSizes.Tiles[this.selected];
    496         updateGameAttributes();
    497     };
    498     mapSize.selected = 0;
    499 }
    500 
    501 /**
    502  * Assign update-functions to all checkboxes.
    503  */
    504 function initRadioButtons()
    505 {
    506     let options = {
    507         "RevealMap": "revealMap",
    508         "ExploreMap": "exploreMap",
    509         "DisableTreasures": "disableTreasures",
    510         "LockTeams": "lockTeams",
    511         "CheatsEnabled": "enableCheats"
    512     };
    513 
    514     Object.keys(options).forEach(attribute => {
    515         Engine.GetGUIObjectByName(options[attribute]).onPress = function() {
    516             g_GameAttributes.settings[attribute] = this.checked;
    517             updateGameAttributes();
    518         };
    519     });
    520 
    521     Engine.GetGUIObjectByName("enableRating").onPress = function() {
    522         g_GameAttributes.settings.RatingEnabled = this.checked;
    523         Engine.SetRankedGame(this.checked);
    524         Engine.GetGUIObjectByName("enableCheats").enabled = !this.checked;
    525         Engine.GetGUIObjectByName("lockTeams").enabled = !this.checked;
    526         updateGameAttributes();
    527     };
    528 }
    529 
    530 /**
    531  * If we're a network client, hide the controls and show the text instead.
    532  */
    533 function hideControls()
    534 {
    535     for (let ctrl of ["mapType", "mapFilter", "mapSelection", "victoryCondition", "gameSpeed", "numPlayers"])
    536         hideControl(ctrl, ctrl + "Text");
    537 
    538     // TODO: Shouldn't players be able to choose their own assignment?
    539     for (let i = 0; i < g_MaxPlayers; ++i)
    540     {
    541         Engine.GetGUIObjectByName("playerAssignment["+i+"]").hidden = true;
    542         Engine.GetGUIObjectByName("playerCiv["+i+"]").hidden = true;
    543         Engine.GetGUIObjectByName("playerTeam["+i+"]").hidden = true;
    544     }
    545 
    546     Engine.GetGUIObjectByName("startGame").enabled = true;
    547 }
    548 
    549649/**
    550650 * Hides the GUI controls for clients and shows the read-only label instead.
    551651 *
    552652 * @param {string} control - name of the GUI object able to change a setting
    553653 * @param {string} label - name of the GUI object displaying a setting
    function hideControl(control, label, all  
    557657{
    558658    Engine.GetGUIObjectByName(control).hidden = !allowControl;
    559659    Engine.GetGUIObjectByName(label).hidden = allowControl;
    560660}
    561661
    562 /**
    563  * Checks a boolean checkbox for the host and sets the text of the label for the client.
    564  *
    565  * @param {string} control - name of the GUI object able to change a setting
    566  * @param {string} label - name of the GUI object displaying a setting
    567  * @param {boolean} checked - Whether the setting is active / enabled.
    568  */
    569 function setGUIBoolean(control, label, checked)
    570 {
    571     Engine.GetGUIObjectByName(control).checked = checked;
    572     Engine.GetGUIObjectByName(label).caption = checked ? translate("Yes") : translate("No");
    573 }
    574 
    575 /**
    576  * Hide and set some elements depending on whether we play single- or multiplayer.
    577  */
    578 function initMultiplayerSettings()
    579 {
    580     Engine.GetGUIObjectByName("chatPanel").hidden = !g_IsNetworked;
    581     Engine.GetGUIObjectByName("optionCheats").hidden = !g_IsNetworked;
    582     Engine.GetGUIObjectByName("optionRating").hidden = !Engine.HasXmppClient();
    583 
    584     Engine.GetGUIObjectByName("enableCheats").enabled = !Engine.IsRankedGame();
    585     Engine.GetGUIObjectByName("lockTeams").enabled = !Engine.IsRankedGame();
    586 
    587     Engine.GetGUIObjectByName("enableCheats").checked = g_GameAttributes.settings.CheatsEnabled;
    588     Engine.GetGUIObjectByName("enableRating").checked = !!g_GameAttributes.settings.RatingEnabled;
    589 
    590     for (let ctrl of ["enableCheats", "enableRating"])
    591         hideControl(ctrl, ctrl + "Text");
    592 }
    593 
     662// TODO: move to g_DropdownArrays
    594663/**
    595664 * Populate team-, color- and civ-dropdowns.
    596665 */
    597666function initPlayerAssignments()
    598667{
    function initPlayerAssignments()  
    625694        colorPicker.list_data = g_PlayerColors.map((color, index) => index);
    626695        colorPicker.selected = -1;
    627696        colorPicker.onSelectionChange = function() { selectPlayerColor(playerSlot, this.selected); };
    628697
    629698        Engine.GetGUIObjectByName("playerCiv["+i+"]").onSelectionChange = function() {
    630             if ((this.selected != -1)&&(g_GameAttributes.mapType !== "scenario"))
     699            if (this.selected != -1 && g_GameAttributes.mapType !== "scenario")
    631700                g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected];
    632701
    633702            updateGameAttributes();
    634703        };
    635704    }
    function handlePlayerAssignmentMessage(m  
    775844function getMapDisplayName(map)
    776845{
    777846    let mapData = loadMapData(map);
    778847    if (!mapData || !mapData.settings || !mapData.settings.Name)
    779848    {
    780         log("Map data missing in scenario '" + map + "' - likely unsupported format");
     849        log("Map data missing in map '" + map + "' - likely unsupported format");
    781850        return map;
    782851    }
    783852
    784853    return mapData.settings.Name;
    785854}
    function getSetting(settings, defaults,  
    808877}
    809878
    810879/**
    811880 * Initialize the dropdowns containing all selectable civs (including random).
    812881 */
     882// TODO: move to g_DropdownArrays
    813883function initCivNameList()
    814884{
    815885    let civList = Object.keys(g_CivData).filter(civ => g_CivData[civ].SelectableInGameSetup).map(civ => ({ "name": g_CivData[civ].Name, "code": civ })).sort(sortNameIgnoreCase);
    816886    let civListNames = [g_RandomCiv].concat(civList.map(civ => civ.name));
    817887    let civListCodes = ["random"].concat(civList.map(civ => civ.code));
    function initCivNameList()  
    824894        civ.selected = 0;
    825895    }
    826896}
    827897
    828898/**
    829  * Initialize the dropdown containing all maps for the selected maptype and mapfilter.
     899 * Called when the maptype or mapfilter changed.
    830900 */
    831 function initMapNameList()
     901function reloadMapList()
    832902{
    833903    if (!g_MapPath[g_GameAttributes.mapType])
    834904    {
    835         error("initMapNameList: Unexpected map type " + g_GameAttributes.mapType);
     905        error("loadMaps: Unexpected map type " + g_GameAttributes.mapType);
    836906        return;
    837907    }
    838908
    839     let mapFiles = g_GameAttributes.mapType == "random" ? getJSONFileList(g_GameAttributes.mapPath) : getXMLFileList(g_GameAttributes.mapPath);
     909    let mapFiles = g_GameAttributes.mapType == "random" ?
     910        getJSONFileList(g_GameAttributes.mapPath) :
     911        getXMLFileList(g_GameAttributes.mapPath);
    840912
    841     // Apply map filter, if any defined
    842     // TODO: Should verify these are valid maps before adding to list
    843913    let mapList = [];
     914
     915    if (g_GameAttributes.mapType == "random")
     916        mapList.push({
     917            "file": "random",
     918            "name": g_RandomMap
     919        });
     920
     921    // TODO: Should verify these are valid maps before adding to list
    844922    for (let mapFile of mapFiles)
    845923    {
    846924        let file = g_GameAttributes.mapPath + mapFile;
    847925        let mapData = loadMapData(file);
    848         let mapFilter = g_MapFilters.find(mapFilter => mapFilter.id == (g_GameAttributes.mapFilter || "all"));
    849 
    850         if (!!mapData.settings && mapFilter && mapFilter.filter(mapData.settings.Keywords || []))
    851             mapList.push({ "name": getMapDisplayName(file), "file": file });
    852     }
    853 
    854     translateObjectKeys(mapList, ["name"]);
    855     mapList.sort(sortNameIgnoreCase);
     926        let mapFilter = g_MapFilters.id.find(filter => filter == g_GameAttributes.mapFilter);
    856927
    857     let mapListNames = mapList.map(map => map.name);
    858     let mapListFiles = mapList.map(map => map.file);
     928        if (!mapData.settings || mapFilter && !mapFilter.filter(mapData.settings.Keywords || []))
     929            continue;
    859930
    860     // Scenario/skirmish maps have a fixed playercount
    861     if (g_GameAttributes.mapType == "random")
    862     {
    863         mapListNames.unshift(g_RandomMap);
    864         mapListFiles.unshift("random");
     931        mapList.push({
     932            "file": file,
     933            "name": translate(getMapDisplayName(file))
     934        });
    865935    }
    866936
    867     let mapSelectionBox = Engine.GetGUIObjectByName("mapSelection");
    868     mapSelectionBox.list = mapListNames;
    869     mapSelectionBox.list_data = mapListFiles;
    870     mapSelectionBox.onSelectionChange = function() {
    871         if (this.selected != -1)
    872             selectMap(this.list_data[this.selected]);
    873     };
    874     mapSelectionBox.selected = Math.max(0, mapListFiles.indexOf(g_GameAttributes.map || ""));
     937    g_MapList = prepareForDropdown(mapList.sort(sortNameIgnoreCase));
     938    initDropdown("mapSelection");
    875939}
    876940
    877941function loadMapData(name)
    878942{
    879943    if (!name || !g_MapPath[g_GameAttributes.mapType])
    function loadMapData(name)  
    881945
    882946    if (name == "random")
    883947        return { "settings": { "Name": "", "Description": "" } };
    884948
    885949    if (!g_MapData[name])
    886         g_MapData[name] = g_GameAttributes.mapType == "random" ? Engine.ReadJSONFile(name + ".json") : Engine.LoadMapSettings(name);
     950        g_MapData[name] =
     951            g_GameAttributes.mapType == "random" ?
     952            Engine.ReadJSONFile(name + ".json") :
     953            Engine.LoadMapSettings(name);
    887954
    888955    return g_MapData[name];
    889956}
    890957
    891958/**
    function loadPersistMatchSettings()  
    908975
    909976    let mapName = attrs.map || "";
    910977    let mapSettings = attrs.settings;
    911978
    912979    g_GameAttributes = attrs;
    913     g_GameAttributes.matchID = Engine.GetMatchID();
    914     mapSettings.Seed = Math.floor(Math.random() * 65536);
    915     mapSettings.AISeed = Math.floor(Math.random() * 65536);
    916980
    917981    if (!g_IsNetworked)
    918982        mapSettings.CheatsEnabled = true;
    919983
    920984    // Replace unselectable civs with random civ
    function loadPersistMatchSettings()  
    9371001
    9381002    if (mapSettings.PlayerData)
    9391003        sanitizePlayerData(mapSettings.PlayerData);
    9401004
    9411005    // Reload, as the maptype or mapfilter might have changed
    942     initMapNameList();
     1006    reloadMapList();
    9431007
    9441008    g_GameAttributes.settings.RatingEnabled = Engine.HasXmppClient();
    9451009    Engine.SetRankedGame(g_GameAttributes.settings.RatingEnabled);
    9461010
    9471011    updateGUIObjects();
    function onTick()  
    10391103    updateTimers();
    10401104}
    10411105
    10421106/**
    10431107 * Called when the host choses the number of players on a random map.
     1108 *
    10441109 * @param {Number} num
    10451110 */
    10461111function selectNumPlayers(num)
    10471112{
    1048     // Avoid recursion
    1049     if (g_IsInGuiUpdate || !g_IsController || g_GameAttributes.mapType != "random")
     1113    if (g_GameAttributes.mapType != "random")
    10501114        return;
    10511115
    10521116    // Unassign players from nonexistent slots
    10531117    if (g_IsNetworked)
    10541118    {
    function selectNumPlayers(num)  
    10631127    if (num < pData.length)
    10641128        g_GameAttributes.settings.PlayerData = pData.slice(0, num);
    10651129    else
    10661130        for (let i = pData.length; i < num; ++i)
    10671131            g_GameAttributes.settings.PlayerData.push(g_DefaultPlayerData[i]);
    1068 
    1069     updateGameAttributes();
    10701132}
    10711133
    10721134/**
    10731135 * Assigns the given color to that player.
    10741136 */
    function ensureUniquePlayerColors(player  
    11001162        // If someone else has that color, assign an unused color
    11011163        if (playerData.some((pData, j) => i != j && sameColor(playerData[i].Color, pData.Color)))
    11021164            playerData[i].Color = g_PlayerColors.find(color => playerData.every(pData => !sameColor(color, pData.Color)));
    11031165}
    11041166
    1105 /**
    1106  * Called when the user selects a map type from the list.
    1107  *
    1108  * @param {string} type - scenario, skirmish or random
    1109  */
    1110 function selectMapType(type)
    1111 {
    1112     // Avoid recursion
    1113     if (g_IsInGuiUpdate || !g_IsController)
    1114         return;
    1115 
    1116     if (!g_MapPath[type])
    1117     {
    1118         error("selectMapType: Unexpected map type " + type);
    1119         return;
    1120     }
    1121 
    1122     g_MapData = {};
    1123     g_GameAttributes.map = "";
    1124     g_GameAttributes.mapType = type;
    1125     g_GameAttributes.mapPath = g_MapPath[type];
    1126 
    1127     if (type != "scenario")
    1128         g_GameAttributes.settings = {
    1129             "PlayerData": g_DefaultPlayerData.slice(0, 4),
    1130             "Seed": Math.floor(Math.random() * 65536),
    1131             "CheatsEnabled": g_GameAttributes.settings.CheatsEnabled
    1132         };
    1133     g_GameAttributes.settings.AISeed = Math.floor(Math.random() * 65536);
    1134 
    1135     initMapNameList();
    1136 
    1137     updateGameAttributes();
    1138 }
    1139 
    1140 function selectMapFilter(id)
    1141 {
    1142     // Avoid recursion
    1143     if (g_IsInGuiUpdate || !g_IsController)
    1144         return;
    1145 
    1146     g_GameAttributes.mapFilter = id;
    1147 
    1148     initMapNameList();
    1149 
    1150     updateGameAttributes();
    1151 }
    1152 
    11531167function selectMap(name)
    11541168{
    1155     // Avoid recursion
    1156     if (g_IsInGuiUpdate || !g_IsController || !name)
    1157         return;
    1158 
    11591169    // Reset some map specific properties which are not necessarily redefined on each map
    11601170    for (let prop of ["TriggerScripts", "CircularMap", "Garrison"])
    11611171        g_GameAttributes.settings[prop] = undefined;
    11621172
    11631173    let mapData = loadMapData(name);
    function selectMap(name)  
    11851195            g_GameAttributes.settings[prop] = mapSettings[prop];
    11861196
    11871197    // Use default AI if the map doesn't specify any explicitly
    11881198    for (let i in g_GameAttributes.settings.PlayerData)
    11891199    {
    1190         if (!('AI' in g_GameAttributes.settings.PlayerData[i]))
     1200        if (!g_GameAttributes.settings.PlayerData[i].AI)
    11911201            g_GameAttributes.settings.PlayerData[i].AI = g_DefaultPlayerData[i].AI;
    1192         if (!('AIDiff' in g_GameAttributes.settings.PlayerData[i]))
     1202
     1203        if (!g_GameAttributes.settings.PlayerData[i].AIDiff)
    11931204            g_GameAttributes.settings.PlayerData[i].AIDiff = g_DefaultPlayerData[i].AIDiff;
    11941205    }
    11951206
    11961207    // Reset player assignments on map change
    11971208    if (!g_IsNetworked)
    11981209        g_PlayerAssignments = { "local": { "name": singleplayerName(), "player": 1, "civ": "", "team": -1, "ready": 0 } };
    11991210
    12001211    else
    12011212    {
    1202         let numPlayers = mapSettings.PlayerData ? mapSettings.PlayerData.length : g_GameAttributes.settings.PlayerData.length;
    1203 
    12041213        for (let guid in g_PlayerAssignments)
    12051214        {   // Unassign extra players
    12061215            let player = g_PlayerAssignments[guid].player;
    1207             if (player <= g_MaxPlayers && player > numPlayers)
     1216            if (player <= g_MaxPlayers && player > g_GameAttributes.settings.PlayerData.length)
    12081217                Engine.AssignNetworkPlayer(player, "");
    12091218        }
    12101219    }
    1211 
    1212     updateGameAttributes();
    12131220}
    12141221
    12151222function launchGame()
    12161223{
    12171224    if (!g_IsController)
    function launchGame()  
    12811288        let player = g_PlayerAssignments[guid];
    12821289        if (player.player > 0)  // not observer or GAIA
    12831290            g_GameAttributes.settings.PlayerData[player.player - 1].Name = player.name;
    12841291    }
    12851292
     1293    g_GameAttributes.mapSettings.Seed = Math.floor(Math.random() * 65536);
     1294    g_GameAttributes.mapSettings.AISeed = Math.floor(Math.random() * 65536);
     1295
     1296    // Unique ID to be used for identifying the same game reports for the lobby
     1297    g_GameAttributes.matchID = Engine.GetMatchID();
     1298
    12861299    if (g_IsNetworked)
    12871300    {
    12881301        Engine.SetNetworkGameAttributes(g_GameAttributes);
    12891302        Engine.StartNetworkGame();
    12901303    }
    function launchGame()  
    13151328 */
    13161329function updateGUIObjects()
    13171330{
    13181331    g_IsInGuiUpdate = true;
    13191332
    1320     let mapSettings = g_GameAttributes.settings;
     1333    for (let ctrl in g_Dropdowns)
     1334    {
     1335        let dropdown = Engine.GetGUIObjectByName(ctrl);
    13211336
    1322     // These dropdowns don't set values while g_IsInGuiUpdate
    1323     let mapName = g_GameAttributes.map || "";
    1324     let mapFilterIdx = g_MapFilters.findIndex(mapFilter => mapFilter.id == (g_GameAttributes.mapFilter || "default"));
    1325     let mapTypeIdx = g_GameAttributes.mapType !== undefined ? g_MapTypes.Name.indexOf(g_GameAttributes.mapType) : g_MapTypes.Default;
    1326     let gameSpeedIdx = g_GameAttributes.gameSpeed !== undefined ? g_GameSpeeds.Speed.indexOf(g_GameAttributes.gameSpeed) : g_GameSpeeds.Default;
    1327 
    1328     // These dropdowns might set the default (as they ignore g_IsInGuiUpdate)
    1329     let mapSizeIdx = mapSettings.Size !== undefined ? g_MapSizes.Tiles.indexOf(mapSettings.Size) : g_MapSizes.Default;
    1330     let victoryIdx = mapSettings.GameType !== undefined ? g_VictoryConditions.Name.indexOf(mapSettings.GameType) : g_VictoryConditions.Default;
    1331     let wonderDurationIdx = mapSettings.WonderDuration !== undefined ? g_WonderDurations.Duration.indexOf(mapSettings.WonderDuration) : g_WonderDurations.Default;
    1332     let popIdx = mapSettings.PopulationCap !== undefined ? g_PopulationCapacities.Population.indexOf(mapSettings.PopulationCap) : g_PopulationCapacities.Default;
    1333     let startingResIdx = mapSettings.StartingResources !== undefined ? g_StartingResources.Resources.indexOf(mapSettings.StartingResources) : g_StartingResources.Default;
    1334     let ceasefireIdx = mapSettings.Ceasefire !== undefined ? g_Ceasefire.Duration.indexOf(mapSettings.Ceasefire) : g_Ceasefire.Default;
    1335     let numPlayers = mapSettings.PlayerData ? mapSettings.PlayerData.length : g_MaxPlayers;
     1337        let idx = dropdown.list_data.indexOf("" + g_Dropdowns[ctrl].get());
     1338        let hidden = g_Dropdowns[ctrl].hidden && g_Dropdowns[ctrl].hidden();
     1339        let enabled = !g_Dropdowns[ctrl].enabled || g_Dropdowns[ctrl].enabled();
    13361340
    1337     if (g_IsController)
     1341        dropdown.selected = idx;
     1342        dropdown.hidden = !g_IsController || hidden || !enabled;
     1343
     1344        let label = Engine.GetGUIObjectByName(ctrl + "Text");
     1345        label.caption = idx == -1 ? translate("Unknown") : dropdown.list[idx];
     1346        label.hidden = g_IsController && enabled || hidden;
     1347    }
     1348
     1349    for (let ctrl in g_Checkboxes)
    13381350    {
    1339         Engine.GetGUIObjectByName("mapType").selected = mapTypeIdx;
    1340         Engine.GetGUIObjectByName("mapFilter").selected = mapFilterIdx;
    1341         Engine.GetGUIObjectByName("mapSelection").selected = Engine.GetGUIObjectByName("mapSelection").list_data.indexOf(mapName);
    1342         Engine.GetGUIObjectByName("mapSize").selected = mapSizeIdx;
    1343         Engine.GetGUIObjectByName("numPlayers").selected = numPlayers - 1;
    1344         Engine.GetGUIObjectByName("victoryCondition").selected = victoryIdx;
    1345         Engine.GetGUIObjectByName("wonderDuration").selected = wonderDurationIdx;
    1346         Engine.GetGUIObjectByName("populationCap").selected = popIdx;
    1347         Engine.GetGUIObjectByName("gameSpeed").selected = gameSpeedIdx;
    1348         Engine.GetGUIObjectByName("ceasefire").selected = ceasefireIdx;
    1349         Engine.GetGUIObjectByName("startingResources").selected = startingResIdx;
     1351        let obj = g_Checkboxes[ctrl];
     1352
     1353        let hidden = obj.hidden && obj.hidden();
     1354        let enabled = !obj.enabled || obj.enabled();
     1355
     1356        let checkbox = Engine.GetGUIObjectByName(ctrl);
     1357        checkbox.checked = obj.get();
     1358        checkbox.enabled = enabled;
     1359        checkbox.hidden = hidden || !g_IsController;
     1360
     1361        let label = Engine.GetGUIObjectByName(ctrl + "Text");
     1362        label.caption = obj.get() ? translate("Yes") : translate("No");
     1363        label.hidden = hidden || g_IsController;
    13501364    }
    1351     else
     1365
     1366    for (let ctrl in g_MiscControls)
    13521367    {
    1353         Engine.GetGUIObjectByName("mapTypeText").caption = g_MapTypes.Title[mapTypeIdx];
    1354         Engine.GetGUIObjectByName("mapFilterText").caption = g_MapFilters[mapFilterIdx].name;
    1355         Engine.GetGUIObjectByName("mapSelectionText").caption = mapName == "random" ? g_RandomMap : translate(getMapDisplayName(mapName));
    1356         initMapNameList();
    1357     }
    1358 
    1359     // Can be visible to both host and clients
    1360     Engine.GetGUIObjectByName("mapSizeText").caption = g_GameAttributes.mapType == "random" ? g_MapSizes.LongName[mapSizeIdx] : translate("Default");
    1361     Engine.GetGUIObjectByName("numPlayersText").caption = numPlayers;
    1362     Engine.GetGUIObjectByName("victoryConditionText").caption = g_VictoryConditions.Title[victoryIdx];
    1363     Engine.GetGUIObjectByName("wonderDurationText").caption = g_WonderDurations.Title[wonderDurationIdx];
    1364     Engine.GetGUIObjectByName("populationCapText").caption = g_PopulationCapacities.Title[popIdx];
    1365     Engine.GetGUIObjectByName("startingResourcesText").caption = g_StartingResources.Title[startingResIdx];
    1366     Engine.GetGUIObjectByName("ceasefireText").caption = g_Ceasefire.Title[ceasefireIdx];
    1367     Engine.GetGUIObjectByName("gameSpeedText").caption = g_GameSpeeds.Title[gameSpeedIdx];
    1368 
    1369     setGUIBoolean("enableCheats", "enableCheatsText", !!mapSettings.CheatsEnabled);
    1370     setGUIBoolean("disableTreasures", "disableTreasuresText", !!mapSettings.DisableTreasures);
    1371     setGUIBoolean("exploreMap", "exploreMapText", !!mapSettings.ExploreMap);
    1372     setGUIBoolean("revealMap", "revealMapText", !!mapSettings.RevealMap);
    1373     setGUIBoolean("lockTeams", "lockTeamsText", !!mapSettings.LockTeams);
    1374     setGUIBoolean("enableRating", "enableRatingText", !!mapSettings.RatingEnabled);
    1375 
    1376     Engine.GetGUIObjectByName("optionWonderDuration").hidden =
    1377         g_GameAttributes.settings.GameType &&
    1378         g_GameAttributes.settings.GameType != "wonder";
    1379 
    1380     Engine.GetGUIObjectByName("cheatWarningText").hidden = !g_IsNetworked || !mapSettings.CheatsEnabled;
    1381 
    1382     Engine.GetGUIObjectByName("enableCheats").enabled = !mapSettings.RatingEnabled;
    1383     Engine.GetGUIObjectByName("lockTeams").enabled = !mapSettings.RatingEnabled;
    1384 
    1385     // Mapsize completely hidden for non-random maps
    1386     let isRandom = g_GameAttributes.mapType == "random";
    1387     Engine.GetGUIObjectByName("mapSizeDesc").hidden = !isRandom;
    1388     Engine.GetGUIObjectByName("mapSize").hidden = !isRandom || !g_IsController;
    1389     Engine.GetGUIObjectByName("mapSizeText").hidden = !isRandom || g_IsController;
    1390     hideControl("numPlayers", "numPlayersText", isRandom && g_IsController);
    1391 
    1392     let notScenario = g_GameAttributes.mapType != "scenario" && g_IsController ;
    1393 
    1394     for (let ctrl of ["victoryCondition", "wonderDuration", "populationCap",
    1395                       "startingResources", "ceasefire", "revealMap",
    1396                       "exploreMap", "disableTreasures", "lockTeams"])
    1397         hideControl(ctrl, ctrl + "Text", notScenario);
     1368        let control = Engine.GetGUIObjectByName(ctrl);
     1369        control.hidden = g_MiscControls[ctrl].hidden && g_MiscControls[ctrl].hidden();
    13981370
    1399     setMapDescription();
     1371        if (g_MiscControls[ctrl].tooltip)
     1372            control.tooltip = g_MiscControls[ctrl].tooltip();
     1373    }
    14001374
     1375    // TODO: move to g_DropdownArrays
     1376    let mapSettings = g_GameAttributes.settings;
     1377    let notScenario = g_GameAttributes.mapType != "scenario";
    14011378    for (let i = 0; i < g_MaxPlayers; ++i)
    14021379    {
    1403         Engine.GetGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers);
     1380        Engine.GetGUIObjectByName("playerBox["+i+"]").hidden = (i >= mapSettings.PlayerData.length);
    14041381
    1405         if (i >= numPlayers)
     1382        if (i >= mapSettings.PlayerData.length)
    14061383            continue;
    14071384
    14081385        let pName = Engine.GetGUIObjectByName("playerName["+i+"]");
    14091386        let pAssignment = Engine.GetGUIObjectByName("playerAssignment["+i+"]");
    14101387        let pAssignmentText = Engine.GetGUIObjectByName("playerAssignmentText["+i+"]");
    function updateGUIObjects()  
    14431420        pColorPickerHeading.hidden = !canChangeColors;
    14441421        if (canChangeColors)
    14451422            pColorPicker.selected = g_PlayerColors.findIndex(col => sameColor(col, color));
    14461423    }
    14471424
     1425    updateGUIMapDescription();
    14481426    resizeMoreOptionsWindow();
    14491427
    14501428    g_IsInGuiUpdate = false;
    14511429
    14521430    // Game attributes include AI settings, so update the player list
    function updateGUIObjects()  
    14641442}
    14651443
    14661444/**
    14671445 * Sets an additional map label, map preview image and mapsettings description.
    14681446 */
    1469 function setMapDescription()
     1447function updateGUIMapDescription()
    14701448{
    1471     let numPlayers = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData.length : 0;
    1472     let mapName = g_GameAttributes.map || "";
     1449    setMapPreviewImage("mapPreview", getMapPreview(g_GameAttributes.map));
    14731450
    1474     let victoryIdx = Math.max(0, g_VictoryConditions.Name.indexOf(g_GameAttributes.settings.GameType || ""));
    1475     let victoryTitle;
     1451    Engine.GetGUIObjectByName("mapInfoName").caption =
     1452        g_GameAttributes.map == "random" ?
     1453            translateWithContext("map selection", "Random") :
     1454            translate(getMapDisplayName(g_GameAttributes.map));
     1455
     1456    let mapDescription =
     1457        g_GameAttributes.map == "random" ?
     1458            translate("Randomly selects a map from the list") :
     1459        g_GameAttributes.settings.Description ?
     1460            translate(g_GameAttributes.settings.Description) :
     1461            translate("Sorry, no description available.");
     1462
     1463    let numPlayers = sprintf(
     1464        translatePlural(
     1465            "%(number)s player. ",
     1466            "%(number)s players. ",
     1467            g_GameAttributes.settings.PlayerData.length
     1468        ),
     1469        { "number": g_GameAttributes.settings.PlayerData.length }
     1470    );
     1471
     1472    Engine.GetGUIObjectByName("mapInfoDescription").caption =
     1473        numPlayers +
     1474        getVictoryConditionDescription() + "\n\n" +
     1475        mapDescription;
     1476}
    14761477
     1478function getVictoryConditionDescription()
     1479{
     1480    let victoryIdx = g_VictoryConditions.Name.indexOf(g_GameAttributes.settings.GameType);
     1481
     1482    if (!g_VictoryConditions.Name[victoryIdx])
     1483        return "";
     1484
     1485    let victoryTitle;
    14771486    if (g_VictoryConditions.Name[victoryIdx] == "wonder")
    14781487        victoryTitle = sprintf(
    14791488            translatePluralWithContext(
    14801489                "victory condition",
    14811490                "Wonder (%(min)s minute)",
    function setMapDescription()  
    14881497        victoryTitle = g_VictoryConditions.Title[victoryIdx];
    14891498
    14901499    if (victoryIdx != g_VictoryConditions.Default)
    14911500        victoryTitle = "[color=\"" + g_VictoryColor + "\"]" + victoryTitle + "[/color]";
    14921501
    1493     let mapDescription = g_GameAttributes.settings.Description ? translate(g_GameAttributes.settings.Description) : translate("Sorry, no description available.");
    1494     if (mapName == "random")
    1495         mapDescription = translate("Randomly selects a map from the list");
    1496 
    1497     let gameDescription = sprintf(translatePlural("%(number)s player. ", "%(number)s players. ", numPlayers), { "number": numPlayers });
    1498     gameDescription += translate("Victory Condition:") + " " + victoryTitle + ".\n\n";
    1499     gameDescription += mapDescription;
    1500 
    1501     Engine.GetGUIObjectByName("mapInfoName").caption = mapName == "random" ? translateWithContext("map selection", "Random") : translate(getMapDisplayName(mapName));
    1502     Engine.GetGUIObjectByName("mapInfoDescription").caption = gameDescription;
    1503     setMapPreviewImage("mapPreview", getMapPreview(mapName));
     1502    return translate("Victory Condition:") + " " + victoryTitle + ".";
    15041503}
    15051504
    15061505/**
    15071506 * Broadcast the changed settings to all clients and the lobbybot.
    15081507 */
  • source/gui/IGUITextOwner.cpp

    void IGUITextOwner::DrawText(size_t inde  
    7373    {
    7474        SetupText();
    7575        m_GeneratedTextsValid = true;
    7676    }
    7777
    78     ENSURE(index < m_GeneratedTexts.size() && "Trying to draw a Text Index within a IGUITextOwner that doesn't exist");
     78    if (!m_GeneratedTexts[index])
     79        return;
    7980
    8081    if (GetGUI())
    8182        GetGUI()->DrawText(*m_GeneratedTexts[index], color, pos, z, clipping);
    8283}
    8384