Ticket #3934: resource_agnostic-v5.patch

File resource_agnostic-v5.patch, 89.9 KB (added by elexis, 3 years ago)

rebased

  • binaries/data/mods/public/gui/common/functions_repeat_positioning.js

     
     1/**
     2 * Horizontally fit objects repeated with the `<repeat>` tag within a parent object
     3 *
     4 * @param basename The base name of the object, such as "object[n]" or "object[a]_sub[b]"
     5 * @param splitvar The var identifying the repeat count, without the square brackets
     6 * @param margin The gap, in px, between the repeated objects
     7 * @param limit The number of elements to fit
     8 * @return The number of elements affected
     9 */
     10function horizFitRepeatedObjects(basename, splitvar="n", margin=0, limit=0)
     11{
     12    basename = basename.split("["+splitvar+"]", 2);
     13
     14    let objObj;
     15    if (limit == 0)
     16        do
     17        {
     18            objObj = Engine.GetGUIObjectByName(basename.join("["+ ++limit +"]"));
     19        }
     20        while (objObj)
     21
     22    for (let c = 0; c < limit; ++c)
     23    {
     24        objObj = Engine.GetGUIObjectByName(basename.join("["+ c +"]"));
     25        let objSize = objObj.size;
     26        objSize.rleft = c * (100/limit);
     27        objSize.rright = (c+1) * (100/limit);
     28        objSize.right = -margin;
     29        objObj.size = objSize;
     30    }
     31
     32    return limit;
     33}
     34
     35/**
     36 * Hide all repeated elements after a certain index
     37 *
     38 * @param prefix The part of the element name preceeding the index
     39 * @param idx The index from which to start
     40 * @param prefix The part of the element name after the index
     41 */
     42function hideRemaining(prefix, idx, suffix)
     43{
     44    while (true)
     45    {
     46        let obj = Engine.GetGUIObjectByName(prefix + idx + suffix);
     47        if (!obj)
     48            return;
     49        obj.hidden = true;
     50        ++idx;
     51    }
     52}
  • binaries/data/mods/public/gui/common/l10n.js

     
    1 const localisedResourceNames = {
    2     "firstWord": {
    3         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    4         "food": translateWithContext("firstWord", "Food"),
    5         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    6         "meat": translateWithContext("firstWord", "Meat"),
    7         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    8         "metal": translateWithContext("firstWord", "Metal"),
    9         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    10         "ore": translateWithContext("firstWord", "Ore"),
    11         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    12         "rock": translateWithContext("firstWord", "Rock"),
    13         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    14         "ruins": translateWithContext("firstWord", "Ruins"),
    15         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    16         "stone": translateWithContext("firstWord", "Stone"),
    17         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    18         "treasure": translateWithContext("firstWord", "Treasure"),
    19         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    20         "tree": translateWithContext("firstWord", "Tree"),
    21         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    22         "wood": translateWithContext("firstWord", "Wood"),
    23         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    24         "fruit": translateWithContext("firstWord", "Fruit"),
    25         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    26         "grain": translateWithContext("firstWord", "Grain"),
    27         // Translation: Word as used at the beginning of a sentence or as a single-word sentence.
    28         "fish": translateWithContext("firstWord", "Fish"),
    29     },
    30     "withinSentence": {
    31         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    32         "food": translateWithContext("withinSentence", "Food"),
    33         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    34         "meat": translateWithContext("withinSentence", "Meat"),
    35         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    36         "metal": translateWithContext("withinSentence", "Metal"),
    37         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    38         "ore": translateWithContext("withinSentence", "Ore"),
    39         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    40         "rock": translateWithContext("withinSentence", "Rock"),
    41         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    42         "ruins": translateWithContext("withinSentence", "Ruins"),
    43         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    44         "stone": translateWithContext("withinSentence", "Stone"),
    45         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    46         "treasure": translateWithContext("withinSentence", "Treasure"),
    47         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    48         "tree": translateWithContext("withinSentence", "Tree"),
    49         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    50         "wood": translateWithContext("withinSentence", "Wood"),
    51         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    52         "fruit": translateWithContext("withinSentence", "Fruit"),
    53         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    54         "grain": translateWithContext("withinSentence", "Grain"),
    55         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    56         "fish": translateWithContext("withinSentence", "Fish"),
    57     }
    58 };
    591
    60 function getLocalizedResourceName(resourceCode, context)
     2function getLocalizedResourceName(resourceName, context)
    613{
    62     if (!localisedResourceNames[context])
    63     {
    64         warn("Internationalization: Unexpected context for resource type localization found: ‘" + context + "’. This context is not supported.");
    65         return resourceCode;
    66     }
    67     if (!localisedResourceNames[context][resourceCode])
    68     {
    69         warn("Internationalization: Unexpected resource type found with code ‘" + resourceCode + ". This resource type must be internationalized.");
    70         return resourceCode;
    71     }
    72     return localisedResourceNames[context][resourceCode];
     4    return translateWithContext(context, resourceName);
    735}
    746
    757/**
    768 * Format resource amounts to proper english and translate (for example: "200 food, 100 wood, and 300 metal").
    779 */
    function getLocalizedResourceAmounts(res 
    7911{
    8012    let amounts = Object.keys(resources)
    8113        .filter(type => resources[type] > 0)
    8214        .map(type => sprintf(translate("%(amount)s %(resourceType)s"), {
    8315            "amount": resources[type],
    84             "resourceType": getLocalizedResourceName(type, "withinSentence")
     16            "resourceType": translateWithContext("withinSentence", type)
    8517        }));
    8618
    8719    if (amounts.length > 1)
    8820    {
    8921        let lastAmount = amounts.pop();
  • binaries/data/mods/public/gui/common/setup_resources.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22
    33<setup>
    44    <!-- Icons -->
    5     <icon name="iconFood"
     5    <icon name="icon_food"
    66          sprite="stretched:session/icons/resources/food_small.png"
    77          size="16 16"
    88    />
    9     <icon name="iconMetal"
     9    <icon name="icon_metal"
    1010          sprite="stretched:session/icons/resources/metal_small.png"
    1111          size="16 16"
    1212    />
    13     <icon name="iconPopulation"
     13    <icon name="icon_population"
    1414          sprite="stretched:session/icons/resources/population_small.png"
    1515          size="16 16"
    1616    />
    17     <icon name="iconStone"
     17    <icon name="icon_stone"
    1818          sprite="stretched:session/icons/resources/stone_small.png"
    1919          size="16 16"
    2020    />
    21     <icon name="iconWood"
     21    <icon name="icon_wood"
    2222          sprite="stretched:session/icons/resources/wood_small.png"
    2323          size="16 16"
    2424    />
    25     <icon name="iconTime"
     25    <icon name="icon_time"
    2626          sprite="stretched:session/icons/resources/time_small.png"
    2727          size="16 16"
    2828    />
    2929</setup>
  • binaries/data/mods/public/gui/common/tooltips.js

     
    1 const g_CostDisplayIcons = {
    2     "food": '[icon="iconFood"]',
    3     "wood": '[icon="iconWood"]',
    4     "stone": '[icon="iconStone"]',
    5     "metal": '[icon="iconMetal"]',
    6     "population": '[icon="iconPopulation"]',
    7     "time": '[icon="iconTime"]'
    8 };
    9 
    101const g_TooltipTextFormats = {
    112    "unit": ['[font="sans-10"][color="orange"]', '[/color][/font]'],
    123    "header": ['[font="sans-bold-13"]', '[/font]'],
    134    "body": ['[font="sans-13"]', '[/font]'],
    145    "comma": ['[font="sans-12"]', '[/font]']
    const g_DamageTypes = { 
    2516    "hack": translate("Hack"),
    2617    "pierce": translate("Pierce"),
    2718    "crush": translate("Crush"),
    2819};
    2920
     21function costIcon(resource)
     22{
     23    return '[icon="icon_' + resource + "]';
     24}
     25
    3026function bodyFont(text)
    3127{
    3228    return g_TooltipTextFormats.body[0] + text + g_TooltipTextFormats.body[1];
    3329}
    3430
    function getEntityCostComponentsTooltipS 
    321317    if (template.cost.time)
    322318        totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
    323319
    324320    let costs = [];
    325321
    326     for (let type in g_CostDisplayIcons)
    327         if (totalCosts[type])
     322    for (let c in template.cost)
     323    {
     324        if (c == "populationBonus" || !totalCosts[c])
     325            continue;
     326
     327        if (typeof GetSimState === "undefined" || GetSimState().resources.codes.indexOf(c) > -1 || c === "time")
    328328            costs.push(sprintf(translate("%(component)s %(cost)s"), {
    329                 "component": g_CostDisplayIcons[type],
    330                 "cost": totalCosts[type]
     329                "component": costIcon(c),
     330                "cost": totalCosts[c]
    331331            }));
     332    }
    332333
    333334    return costs;
    334335}
    335336function getGatherTooltip(template)
    336337{
    function getGatherTooltip(template) 
    340341    return sprintf(translate("%(label)s %(details)s"), {
    341342        "label": headerFont(translate("Gather Rates:")),
    342343        "details":
    343344            Object.keys(template.gather).map(
    344345                type => sprintf(translate("%(resourceIcon)s %(rate)s"), {
    345                     "resourceIcon": g_CostDisplayIcons[type],
     346                    "resourceIcon": costIcon(type),
    346347                    "rate": template.gather[type]
    347348                })
    348349            ).join("  ")
    349350    });
    350351}
    function getWallPieceTooltip(wallTypes) 
    389390    if (sameTypes)
    390391        for (let resource in resourceCount)
    391392            // Translation: This string is part of the resources cost string on
    392393            // the tooltip for wall structures.
    393394            out.push(sprintf(translate("%(resourceIcon)s %(minimum)s to %(resourceIcon)s %(maximum)s"), {
    394                 "resourceIcon": g_CostDisplayIcons[resource],
     395                "resourceIcon": costIcon(resource),
    395396                "minimum": Math.min.apply(Math, resourceCount[resource]),
    396397                "maximum": Math.max.apply(Math, resourceCount[resource])
    397398            }));
    398399    else
    399400        for (let i = 0; i < wallTypes.length; ++i)
    function getNeededResourcesTooltip(resou 
    452453        return "";
    453454
    454455    let formatted = [];
    455456    for (let resource in resources)
    456457        formatted.push(sprintf(translate("%(component)s %(cost)s"), {
    457             "component": '[font="sans-12"]' + g_CostDisplayIcons[resource] + '[/font]',
     458            "component": '[font="sans-12"]' + costIcon(resource) + '[/font]',
    458459            "cost": resources[resource]
    459460        }));
    460461
    461462    return '\n[font="sans-bold-13"][color="red"]' + translate("Insufficient resources:") + '[/color][/font]\n' + formatted.join("  ");
    462463}
  • binaries/data/mods/public/gui/session/diplomacy_window.xml

     
    99    <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">
    1010        <translatableAttribute id="caption">Diplomacy</translatableAttribute>
    1111    </object>
    1212
    1313    <object name="diplomacyHeader" size="32 32 100%-32 64">
    14         <object name="diplomacyHeaderName" size="0 0 150 100%" type="text" style="chatPanel" ghost="true">
     14        <object name="diplomacyHeaderName" size="0 0 140 100%" type="text" style="chatPanel" ghost="true" text_align="center">
    1515            <translatableAttribute id="caption">Name</translatableAttribute>
    1616        </object>
    1717        <object name="diplomacyHeaderCiv" size="150 0 250 100%" type="text" style="chatPanel" ghost="true">
    1818            <translatableAttribute id="caption">Civilization</translatableAttribute>
    1919        </object>
     
    2121            <translatableAttribute id="caption">Team</translatableAttribute>
    2222        </object>
    2323        <object name="diplomacyHeaderTheirs" size="300 0 360 100%" type="text" style="chatPanel" ghost="true">
    2424            <translatableAttribute id="caption">Theirs</translatableAttribute>
    2525        </object>
    26         <object name="diplomacyHeaderAlly" size="100%-180 0 100%-160 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     26        <object name="diplomacyHeaderAlly" size="360 0 380 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
    2727            <translatableAttribute id="caption">A</translatableAttribute>
    2828            <translatableAttribute id="tooltip">Ally</translatableAttribute>
    2929        </object>
    30         <object name="diplomacyHeaderNeutral" size="100%-160 0 100%-140 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     30        <object name="diplomacyHeaderNeutral" size="380 0 400 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
    3131            <translatableAttribute id="caption">N</translatableAttribute>
    3232            <translatableAttribute id="tooltip">Neutral</translatableAttribute>
    3333        </object>
    34         <object name="diplomacyHeaderEnemy" size="100%-140 0 100%-120 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
     34        <object name="diplomacyHeaderEnemy" size="400 0 420 100%" type="text" style="chatPanel" tooltip_style="sessionToolTipBold">
    3535            <translatableAttribute id="caption">E</translatableAttribute>
    3636            <translatableAttribute id="tooltip">Enemy</translatableAttribute>
    3737        </object>
    38         <object name="diplomacyHeaderTribute" size="100%-110 0 100% 100%" type="text" style="chatPanel">
     38        <object name="diplomacyHeaderTribute" size="430 0 100%-30 100%" type="text" style="chatPanel" text_align="center">
    3939            <translatableAttribute id="caption">Tribute</translatableAttribute>
    4040        </object>
    4141    </object>
    4242    <object size="32 64 100%-32 384">
    4343        <repeat count="16">
     
    4646                <object name="diplomacyPlayerCiv[n]" size="150 0 250 100%" type="text" style="chatPanel" ghost="true"/>
    4747                <object name="diplomacyPlayerTeam[n]" size="250 0 300 100%" type="text" style="chatPanel" ghost="true"/>
    4848                <object name="diplomacyPlayerTheirs[n]" size="300 0 360 100%" type="text" style="chatPanel" ghost="true"/>
    4949
    5050                <!-- Diplomatic stance - selection -->
    51                 <object name="diplomacyPlayerAlly[n]" size="100%-180 0 100%-160 100%" type="button" style="StoneButton" hidden="true"/>
    52                 <object name="diplomacyPlayerNeutral[n]" size="100%-160 0 100%-140 100%" type="button" style="StoneButton" hidden="true"/>
    53                 <object name="diplomacyPlayerEnemy[n]" size="100%-140 0 100%-120 100%" type="button" style="StoneButton" hidden="true"/>
     51                <object name="diplomacyPlayerAlly[n]" size="360 0 380 100%" type="button" style="StoneButton" hidden="true"/>
     52                <object name="diplomacyPlayerNeutral[n]" size="380 0 400 100%" type="button" style="StoneButton" hidden="true"/>
     53                <object name="diplomacyPlayerEnemy[n]" size="400 0 420 100%" type="button" style="StoneButton" hidden="true"/>
    5454
    55                 <!-- Tribute -->
    56                 <object name="diplomacyPlayerTributeFood[n]" size="100%-110 0 100%-90 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
    57                     <object name="diplomacyPlayerTributeFoodImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
    58                 </object>
    59                 <object name="diplomacyPlayerTributeWood[n]" size="100%-90 0 100%-70 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
    60                     <object name="diplomacyPlayerTributeWoodImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
    61                 </object>
    62                 <object name="diplomacyPlayerTributeStone[n]" size="100%-70 0 100%-50 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
    63                     <object name="diplomacyPlayerTributeStoneImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
    64                 </object>
    65                 <object name="diplomacyPlayerTributeMetal[n]" size="100%-50 0 100%-30 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
    66                     <object name="diplomacyPlayerTributeMetalImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
     55                <!-- Tribute -->
     56                <object size="430 0 100%-40 100%">
     57                    <repeat count="8" var="r">
     58                        <object name="diplomacyPlayer[n]_tribute[r]" size="0 0 20 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
     59                            <object name="diplomacyPlayer[n]_tribute[r]_image" type="image" size="0 0 100% 100%" ghost="true"/>
     60                        </object>
     61                    </repeat>
    6762                </object>
    6863
    6964                <object name="diplomacyAttackRequest[n]" size="100%-20 0 100% 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
    7065                    <object name="diplomacyAttackRequestImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/attack-request.png" ghost="true"/>
    7166                </object>
  • binaries/data/mods/public/gui/session/menu.js

    const MENU_TOP = MENU_BOTTOM - END_MENU_ 
    2020const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
    2121
    2222// Number of pixels per millisecond to move
    2323const MENU_SPEED = 1.2;
    2424
    25 // Available resources in trade and tribute menu
    26 const RESOURCES = ["food", "wood", "stone", "metal"];
    27 
    2825// Trade menu: step for probability changes
    2926const STEP = 5;
    3027
    3128// Shown in the trade dialog.
    3229const g_IdleTraderTextColor = "orange";
    function openDiplomacy() 
    235232        return;
    236233
    237234    g_IsDiplomacyOpen = true;
    238235
    239236    let isCeasefireActive = GetSimState().ceasefireActive;
     237    let resCodes = GetSimState().resources.codes;
    240238
    241239    // Get offset for one line
    242240    let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size;
    243241    let rowsize = onesize.bottom - onesize.top;
    244242
    function openDiplomacy() 
    253251        diplomacyFormatStanceButtons(i, myself || playerInactive || isCeasefireActive || g_Players[g_ViewedPlayer].teamsLocked);
    254252        diplomacyFormatTributeButtons(i, myself || playerInactive);
    255253        diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]);
    256254    }
    257255
    258     Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
     256    let dialog = Engine.GetGUIObjectByName("diplomacyDialogPanel");
     257    let size = dialog.size;
     258    let wid = resCodes.length * 10;
     259    size.left = -(260 + wid);
     260    size.right = (260 + wid);
     261    dialog.size = size;
     262    dialog.hidden = false;
    259263}
    260264
    261265function diplomacySetupTexts(i, rowsize)
    262266{
    263267    // Apply offset
    function diplomacyFormatStanceButtons(i, 
    303307    }
    304308}
    305309
    306310function diplomacyFormatTributeButtons(i, hidden)
    307311{
    308     for (let resource of RESOURCES)
     312    let resources = GetSimState().resources;
     313    let r = 0;
     314    for (let resCode of resources.codes)
    309315    {
    310         let button = Engine.GetGUIObjectByName("diplomacyPlayerTribute"+resource[0].toUpperCase()+resource.substring(1)+"["+(i-1)+"]");
     316        let button = Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]");
     317        Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]_image").sprite = "stretched:session/icons/resources/"+resCode+".png";
    311318        button.hidden = hidden;
     319        setPanelObjectPosition(button, r, 8, 0);
     320        ++r;
    312321        if (hidden)
    313322            continue;
    314323
    315324        button.enabled = controlsPlayer(g_ViewedPlayer);
    316         button.tooltip = formatTributeTooltip(i, resource, 100);
    317         button.onpress = (function(i, resource, button) {
     325        button.tooltip = formatTributeTooltip(i, resources.names[resCode], 100);
     326        button.onpress = (function(i, resCode, button) {
    318327            // Shift+click to send 500, shift+click+click to send 1000, etc.
    319328            // See INPUT_MASSTRIBUTING in input.js
    320329            let multiplier = 1;
    321330            return function() {
    322331                let isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute");
    function diplomacyFormatTributeButtons(i 
    325334                    inputState = INPUT_MASSTRIBUTING;
    326335                    multiplier += multiplier == 1 ? 4 : 5;
    327336                }
    328337
    329338                let amounts = {};
    330                 for (let type of RESOURCES)
    331                     amounts[type] = 0;
    332                 amounts[resource] = 100 * multiplier;
     339                for (let res of resources.codes)
     340                    amounts[res] = 0;
     341                amounts[resCode] = 100 * multiplier,
    333342
    334                 button.tooltip = formatTributeTooltip(i, resource, amounts[resource]);
     343                button.tooltip = formatTributeTooltip(i, resources.names[resCode], amounts[resCode]);
    335344
    336345                // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some
    337346                // evil global variable hackery.
    338347                g_FlushTributing = function() {
    339348                    Engine.PostNetworkCommand({ "type": "tribute", "player": i, "amounts":  amounts });
    340349                    multiplier = 1;
    341                     button.tooltip = formatTributeTooltip(i, resource, 100);
     350                    button.tooltip = formatTributeTooltip(i, resources.names[resCode], 100);
    342351                };
    343352
    344353                if (!isBatchTrainPressed)
    345354                    g_FlushTributing();
    346355            };
    347         })(i, resource, button);
     356        })(i, resCode, button);
    348357    }
    349358}
    350359
    351360function diplomacyFormatAttackRequestButton(i, hidden)
    352361{
    function openTrade() 
    396405            button[res].up.hidden = !controlsPlayer(g_ViewedPlayer) || res == selec || proba[res] == 100 || proba[selec] == 0;
    397406            button[res].dn.hidden = !controlsPlayer(g_ViewedPlayer) || res == selec || proba[res] == 0 || proba[selec] == 100;
    398407        }
    399408    };
    400409
    401     var proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
    402     var button = {};
    403     var selec = RESOURCES[0];
    404     for (var i = 0; i < RESOURCES.length; ++i)
     410    let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
     411    let button = {};
     412    let resCodes = GetSimState().resources.codes;
     413    let selec = resCodes[0];
     414    hideRemaining("tradeResource[", resCodes.length, "]");
     415
     416    for (let i = 0; i < resCodes.length; ++i)
    405417    {
    406         var buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
    407         if (i > 0)
    408         {
    409             var size = Engine.GetGUIObjectByName("tradeResource["+(i-1)+"]").size;
    410             var width = size.right - size.left;
    411             size.left += width;
    412             size.right += width;
    413             Engine.GetGUIObjectByName("tradeResource["+i+"]").size = size;
    414         }
    415         var resource = RESOURCES[i];
    416         proba[resource] = (proba[resource] ? proba[resource] : 0);
    417         var buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
    418         var icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
    419         icon.sprite = "stretched:session/icons/resources/" + resource + ".png";
    420         var label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]");
    421         var buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
    422         var buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
    423         var iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
    424         button[resource] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
     418        let buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
     419        setPanelObjectPosition(buttonResource, i, 8);
     420        let resCode = resCodes[i];
     421        proba[resCode] = (proba[resCode] ? proba[resCode] : 0);
     422        buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
     423        let icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
     424        icon.sprite = "stretched:session/icons/resources/" + resCode + ".png";
     425        let label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]");
     426        let buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
     427        let buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
     428        let iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
     429        button[resCode] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
    425430
    426431        buttonResource.enabled = controlsPlayer(g_ViewedPlayer);
    427432        buttonResource.onpress = (function(resource){
    428433            return function() {
    429434                if (Engine.HotkeyIsPressed("session.fulltradeswap"))
    430435                {
    431                     for (var ress of RESOURCES)
     436                    for (let ress of resCodes)
    432437                        proba[ress] = 0;
    433438                    proba[resource] = 100;
    434439                    Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    435440                }
    436441                selec = resource;
    437442                updateButtons();
    438443            };
    439         })(resource);
     444        })(resCode);
    440445
    441446        buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
    442447        buttonUp.onpress = (function(resource){
    443448            return function() {
    444449                proba[resource] += Math.min(STEP, proba[selec]);
    445450                proba[selec]    -= Math.min(STEP, proba[selec]);
    446451                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    447452                updateButtons();
    448453            };
    449         })(resource);
     454        })(resCode);
    450455
    451456        buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
    452457        buttonDn.onpress = (function(resource){
    453458            return function() {
    454459                proba[selec]    += Math.min(STEP, proba[resource]);
    455460                proba[resource] -= Math.min(STEP, proba[resource]);
    456461                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    457462                updateButtons();
    458463            };
    459         })(resource);
     464        })(resCode);
    460465    }
    461466    updateButtons();
    462467
    463468    let traderNumber = Engine.GuiInterfaceCall("GetTraderNumber", g_ViewedPlayer);
    464469    Engine.GetGUIObjectByName("landTraders").caption = getIdleLandTradersText(traderNumber);
    465470    Engine.GetGUIObjectByName("shipTraders").caption = getIdleShipTradersText(traderNumber);
    466471
    467     Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false;
     472    let dialog = Engine.GetGUIObjectByName("tradeDialogPanel");
     473    let size = dialog.size;
     474    let wid = resCodes.length * (58/2);
     475    size.left = -(134 + wid);
     476    size.right = (134 + wid);
     477    dialog.size = size;
     478    dialog.hidden = false;
    468479}
    469480
    470481function getIdleLandTradersText(traderNumber)
    471482{
    472483    let active = traderNumber.landTrader.trading;
  • binaries/data/mods/public/gui/session/selection_details.js

    function layoutSelectionMultiple() 
    1111}
    1212
    1313function getResourceTypeDisplayName(resourceType)
    1414{
    1515    let resourceCode = resourceType.generic;
    16     if (resourceCode == "treasure")
    17         return getLocalizedResourceName(resourceType.specific, "firstWord");
    18     else
    19         return getLocalizedResourceName(resourceCode, "firstWord");
     16    let resourceName = GetSimState().resources.names[(resourceCode == "treasure" ? resourceType.specific : resourceCode)]
     17    return getLocalizedResourceName(resourceName, "firstWord");
    2018}
    2119
    2220// Updates the health bar of garrisoned units
    2321function updateGarrisionHealthBar(entState, selection)
    2422{
    function displayMultiple(selection) 
    393391    }
    394392
    395393    let numberOfUnits = Engine.GetGUIObjectByName("numberOfUnits");
    396394    numberOfUnits.caption = selection.length;
    397395    numberOfUnits.tooltip = Object.keys(totalResourcesCarried).map(res =>
    398         g_CostDisplayIcons[res] + totalResourcesCarried[res]
     396        costIcon(res) + totalResourcesCarried[res]
    399397    ).join(" ");
    400398
    401399    // Unhide Details Area
    402400    Engine.GetGUIObjectByName("detailsAreaMultiple").hidden = false;
    403401    Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
  • binaries/data/mods/public/gui/session/selection_panels.js

     
    3232let g_AvailableFormations = new Map();
    3333let g_FormationsInfo = new Map();
    3434
    3535let g_SelectionPanels = {};
    3636
     37let g_BarterSell;
     38
    3739g_SelectionPanels.Alert = {
    3840    "getMaxNumberOfItems": function()
    3941    {
    4042        return 2;
    4143    },
    g_SelectionPanels.Alert = { 
    8587};
    8688
    8789g_SelectionPanels.Barter = {
    8890    "getMaxNumberOfItems": function()
    8991    {
    90         return 4;
     92        return 8;
    9193    },
    9294    "rowLength": 4,
    9395    "getItems": function(unitEntState, selection)
    9496    {
    9597        if (!unitEntState.barterMarket)
    9698            return [];
    97         // ["food", "wood", "stone", "metal"]
    98         return BARTER_RESOURCES;
     99        return GetSimState().resources.codes;
    99100    },
    100101    "setupButton": function(data)
    101102    {
    102103        // data.item is the resource name in this case
    103104        let button = {};
    g_SelectionPanels.Barter = { 
    113114
    114115        let amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
    115116        if (Engine.HotkeyIsPressed("session.massbarter"))
    116117            amountToSell *= BARTER_BUNCH_MULTIPLIER;
    117118
     119        if (!g_BarterSell)
     120            g_BarterSell = GetSimState().resources.codes[0];
     121
    118122        amount.Sell.caption = "-" + amountToSell;
    119123        let prices = data.unitEntState.barterMarket.prices;
    120124        amount.Buy.caption = "+" + Math.round(prices.sell[g_BarterSell] / prices.buy[data.item] * amountToSell);
    121125
    122         let resource = getLocalizedResourceName(data.item, "withinSentence");
     126        let resource = getLocalizedResourceName(GetSimState().resources.names[data.item], "firstWord");
    123127        button.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource });
    124128        button.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource });
    125129
    126130        button.Sell.onPress = function() {
    127131            g_BarterSell = data.item;
    g_SelectionPanels.Barter = { 
    162166        button.Buy.hidden = isSelected;
    163167        button.Buy.enabled = controlsPlayer(data.unitEntState.player);
    164168        button.Sell.hidden = false;
    165169        selectionIcon.hidden = !isSelected;
    166170
    167         setPanelObjectPosition(button.Sell, data.i, data.rowLength);
    168         setPanelObjectPosition(button.Buy, data.i + data.rowLength, data.rowLength);
     171        let sellPos = data.i + (data.i >= data.rowLength ? data.rowLength : 0);
     172        let buyPos = data.i + data.rowLength * (data.i >= data.rowLength ? 2 : 1);
     173        setPanelObjectPosition(button.Sell, sellPos, data.rowLength);
     174        setPanelObjectPosition(button.Buy, buyPos, data.rowLength);
    169175        return true;
    170176    }
    171177};
    172178
    173179g_SelectionPanels.Command = {
    g_SelectionPanels.Queue = { 
    647653        let tooltip = getEntityNames(template);
    648654        if (data.item.neededSlots)
    649655        {
    650656            tooltip += "\n[color=\"red\"]" + translate("Insufficient population capacity:") + "\n[/color]";
    651657            tooltip += sprintf(translate("%(population)s %(neededSlots)s"), {
    652                 "population": g_CostDisplayIcons.population,
     658                "population": costIcon("population"),
    653659                "neededSlots": data.item.neededSlots
    654660            });
    655661        }
    656662        data.button.tooltip = tooltip;
    657663
    g_SelectionPanels.Selection = { 
    874880        }
    875881
    876882        let tooltip = getEntityNames(template);
    877883        if (data.carried)
    878884            tooltip += "\n" + Object.keys(data.carried).map(res =>
    879                 g_CostDisplayIcons[res] + data.carried[res]
     885                costIcon(res) + data.carried[res]
    880886            ).join(" ");
    881887        data.button.tooltip = tooltip;
    882888
    883889        data.countDisplay.caption = ents.length || "";
    884890
  • binaries/data/mods/public/gui/session/selection_panels_helpers.js

     
    11const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
    22const BARTER_BUNCH_MULTIPLIER = 5;
    3 const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
    43const BARTER_ACTIONS = ["Sell", "Buy"];
    54const GATE_ACTIONS = ["lock", "unlock"];
    65
    7 // upgrade constants
    86const UPGRADING_NOT_STARTED = -2;
    97const UPGRADING_CHOSEN_OTHER = -1;
    108
    11 // ==============================================
    12 // BARTER HELPERS
    13 // Resources to sell on barter panel
    14 var g_BarterSell = "food";
    15 
    169function canMoveSelectionIntoFormation(formationTemplate)
    1710{
    1811    if (!(formationTemplate in g_canMoveIntoFormation))
    1912    {
    2013        g_canMoveIntoFormation[formationTemplate] = Engine.GuiInterfaceCall("CanMoveEntsIntoFormation", {
  • binaries/data/mods/public/gui/session/selection_panels_left/barter_panel.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    22<object name="unitBarterPanel"
    3     size="6 36 100% 100%"
     3    size="24 12 100% 100%"
    44    hidden="true"
    55>
    6     <object ghost="true" style="resourceText" type="text" size="0 0 100% 20">
    7         <translatableAttribute id="tooltip">Exchange resources:</translatableAttribute>
    8     </object>
    9     <object size="0 32 100% 124">
    10         <repeat count="4">
    11             <!-- sell -->
    12             <object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
    13                 <object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
    14                 <object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
    15                 <object name="unitBarterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
    16             </object>
    17             <!-- buy -->
    18             <object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
    19                 <object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
    20                 <object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
    21             </object>
    22         </repeat>
    23     </object>
     6
     7    <repeat count="8">
     8
     9        <!-- sell -->
     10        <object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" hidden="true">
     11            <object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
     12            <object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
     13            <object name="unitBarterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 33 33" sprite="stretched:session/icons/corners.png"/>
     14        </object>
     15
     16        <!-- buy -->
     17        <object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 36 36" tooltip_style="sessionToolTipBottomBold" hidden="true">
     18            <object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 33 33"/>
     19            <object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
     20        </object>
     21
     22    </repeat>
     23
    2424</object>
  • binaries/data/mods/public/gui/session/session.js

    function updateTopPanel() 
    464464        let civName = g_CivData[g_Players[g_ViewedPlayer].civ].Name;
    465465        Engine.GetGUIObjectByName("civIcon").sprite = "stretched:" + g_CivData[g_Players[g_ViewedPlayer].civ].Emblem;
    466466        Engine.GetGUIObjectByName("civIconOverlay").tooltip = sprintf(translate("%(civ)s - Structure Tree"), { "civ": civName });
    467467    }
    468468
     469    let resources = GetSimState().resources;
     470    let r = 0;
     471    for (let resCode of resources.codes)
     472    {
     473        Engine.GetGUIObjectByName("resource["+r+"]").tooltip = getLocalizedResourceName(resources.names[resCode], "firstWord");
     474        Engine.GetGUIObjectByName("resource["+r+"]_icon").sprite = "stretched:session/icons/resources/" + resCode + ".png";
     475        ++r;
     476    }
     477    horizFitRepeatedObjects ("resource[n]", "n", 0, r);
     478    hideRemaining("resource[", r, "]");
     479
    469480    // Hide stuff gaia/observers don't use.
    470     Engine.GetGUIObjectByName("food").hidden = !isPlayer;
    471     Engine.GetGUIObjectByName("wood").hidden = !isPlayer;
    472     Engine.GetGUIObjectByName("stone").hidden = !isPlayer;
    473     Engine.GetGUIObjectByName("metal").hidden = !isPlayer;
     481    for (let r = 0; r < resources.codes.length; ++r)
     482        Engine.GetGUIObjectByName("resource["+r+"]").hidden = !isPlayer;
    474483    Engine.GetGUIObjectByName("population").hidden = !isPlayer;
    475484    Engine.GetGUIObjectByName("civIcon").hidden = !isPlayer;
    476485    Engine.GetGUIObjectByName("diplomacyButton1").hidden = !isPlayer;
    477486    Engine.GetGUIObjectByName("tradeButton1").hidden = !isPlayer;
    478487    Engine.GetGUIObjectByName("observerText").hidden = isPlayer;
    function updateDebug() 
    933942    debug.caption = text.replace(/\[/g, "\\[");
    934943}
    935944
    936945function updatePlayerDisplay()
    937946{
    938     let playerState = GetSimState().players[g_ViewedPlayer];
     947    let simState = GetSimState();
     948    let playerState = simState.players[g_ViewedPlayer];
    939949    if (!playerState)
    940950        return;
    941951
    942     Engine.GetGUIObjectByName("resourceFood").caption = Math.floor(playerState.resourceCounts.food);
    943     Engine.GetGUIObjectByName("resourceWood").caption = Math.floor(playerState.resourceCounts.wood);
    944     Engine.GetGUIObjectByName("resourceStone").caption = Math.floor(playerState.resourceCounts.stone);
    945     Engine.GetGUIObjectByName("resourceMetal").caption = Math.floor(playerState.resourceCounts.metal);
     952    let resCodes = simState.resources.codes;
     953    for (let r = 0; r < resCodes.length; ++r)
     954        Engine.GetGUIObjectByName("resource["+r+"]_count").caption = Math.floor(playerState.resourceCounts[resCodes[r]]);
     955
    946956    Engine.GetGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
    947957    Engine.GetGUIObjectByName("population").tooltip = translate("Population (current / limit)") + "\n" +
    948958                    sprintf(translate("Maximum population: %(popCap)s"), { "popCap": playerState.popMax });
    949959
    950960    g_IsTrainingBlocked = playerState.trainingBlocked;
  • binaries/data/mods/public/gui/session/session.xml

     
    44
    55<script file="gui/common/color.js"/>
    66<script file="gui/common/colorFades.js"/>
    77<script file="gui/common/functions_civinfo.js"/>
    88<script file="gui/common/functions_global_object.js"/>
     9<script file="gui/common/functions_repeat_positioning.js"/>
    910<script file="gui/common/functions_utility.js"/>
    1011<script file="gui/common/l10n.js"/>
    1112<script file="gui/common/music.js"/>
    1213<script file="gui/common/network.js"/>
    1314<script file="gui/common/settings.js"/>
  • binaries/data/mods/public/gui/session/top_panel/resource_population.xml

     
    11<?xml version="1.0" encoding="utf-8"?>
    2 <object name="population" size="370 0 460 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     2<object name="population" size="50%-90-52 0 50%-52 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
    33    <object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
    44    <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
    55</object>
  • binaries/data/mods/public/gui/session/trade_window.xml

     
    1414        <object name="tradeHeader" size="0 0 180 100%" type="text" style="ModernLabelText" text_align="left" ghost="true">
    1515            <translatableAttribute id="caption">Trading goods selection:</translatableAttribute>
    1616        </object>
    1717
    1818        <object size="180 0 100% 100%">
    19             <repeat count="4">
     19            <repeat count="8">
    2020                <object name="tradeResource[n]" size="0 0 58 32">
    2121                    <object name="tradeResourceButton[n]" size="4 0 36 100%" type="button" style="StoneButton">
    2222                        <object name="tradeResourceIcon[n]" type="image" ghost="true"/>
    2323                        <object name="tradeResourceSelection[n]" type="image" sprite="stretched:session/icons/corners.png" ghost="true"/>
    2424                        <object name="tradeResourceText[n]" type="text" style="ModernLabelText" ghost="true"/>
  • binaries/data/mods/public/gui/summary/counters.js

    function calculateUnits(playerState, pos 
    248248        playerState.statistics.enemyUnitsKilled[type]);
    249249}
    250250
    251251function calculateResources(playerState, position)
    252252{
    253     let type = g_ResourcesTypes[position];
     253    let type = g_GameData.resources.codes[position];
    254254
    255255    return formatIncome(
    256256        playerState.statistics.resourcesGathered[type],
    257257        playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type]);
    258258}
    function calculateResources(playerState, 
    260260function calculateTotalResources(playerState)
    261261{
    262262    let totalGathered = 0;
    263263    let totalUsed = 0;
    264264
    265     for (let type of g_ResourcesTypes)
     265    for (let type of g_GameData.resources.codes)
    266266    {
    267267        totalGathered += playerState.statistics.resourcesGathered[type];
    268268        totalUsed += playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type];
    269269    }
    270270
    function calculateResourcesTeam(counters 
    328328    }
    329329}
    330330
    331331function calculateResourceExchanged(playerState, position)
    332332{
    333     let type = g_ResourcesTypes[position];
     333    let type = g_GameData.resources.codes[position];
    334334
    335335    return formatIncome(
    336336        playerState.statistics.resourcesBought[type],
    337337        playerState.statistics.resourcesSold[type]);
    338338}
  • binaries/data/mods/public/gui/summary/layout.js

    var g_ScorePanelsData = { 
    9090        "teamCounterFn": calculateUnitsTeam
    9191    },
    9292    "resources": {
    9393        "headings": [
    9494            { "caption": translate("Player name"), "yStart": 26, "width": 200 },
    95             { "caption": translate("Food"), "yStart": 34, "width": 100 },
    96             { "caption": translate("Wood"), "yStart": 34, "width": 100 },
    97             { "caption": translate("Stone"), "yStart": 34, "width": 100 },
    98             { "caption": translate("Metal"), "yStart": 34, "width": 100 },
    9995            { "caption": translate("Total"), "yStart": 34, "width": 110 },
    10096            {
    10197                "caption": sprintf(translate("Tributes \n(%(sent)s / %(received)s)"),
    10298                    {
    10399                        "sent": g_IncomeColor + translate("Sent") + '[/color]',
    var g_ScorePanelsData = { 
    119115                "yStart": 16,
    120116                "width": (100 * 4 + 110)
    121117            }, // width = 510
    122118        ],
    123119        "counters": [
    124             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    125             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    126             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    127             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    128120            { "width": 110, "fn": calculateTotalResources, "verticalOffset": 12 },
    129121            { "width": 121, "fn": calculateTributeSent, "verticalOffset": 12 },
    130122            { "width": 100, "fn": calculateTreasureCollected, "verticalOffset": 12 },
    131123            { "width": 100, "fn": calculateLootCollected, "verticalOffset": 12 }
    132124        ],
    133125        "teamCounterFn": calculateResourcesTeam
    134126    },
    135127    "market": {
    136128        "headings": [
    137129            { "caption": translate("Player name"), "yStart": 26, "width": 200 },
    138             { "caption": translate("Food exchanged"), "yStart": 16, "width": 100 },
    139             { "caption": translate("Wood exchanged"), "yStart": 16, "width": 100 },
    140             { "caption": translate("Stone exchanged"), "yStart": 16, "width": 100 },
    141             { "caption": translate("Metal exchanged"), "yStart": 16, "width": 100 },
    142130            { "caption": translate("Barter efficiency"), "yStart": 16, "width": 100 },
    143131            { "caption": translate("Trade income"), "yStart": 16, "width": 100 }
    144132        ],
    145133        "titleHeadings": [],
    146134        "counters": [
    147             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    148             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    149             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    150             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    151135            { "width": 100, "fn": calculateBarterEfficiency, "verticalOffset": 12 },
    152136            { "width": 100, "fn": calculateTradeIncome, "verticalOffset": 12 }
    153137        ],
    154138        "teamCounterFn": calculateMarketTeam
    155139    },
  • binaries/data/mods/public/gui/summary/summary.js

     
    1 const g_MaxHeadingTitle= 8;
     1const g_MaxHeadingTitle= 12;
    22
    33// const for filtering long collective headings
    44const g_LongHeadingWidth = 250;
    55
    66const g_PlayerBoxYSize = 40;
    const g_LostColor = '[color="255 213 213 
    1515const g_KilledColor = '[color="196 198 255"]';
    1616const g_CapturedColor = '[color="255 255 157"]';
    1717
    1818const g_BuildingsTypes = [ "total", "House", "Economic", "Outpost", "Military", "Fortress", "CivCentre", "Wonder" ];
    1919const g_UnitsTypes = [ "total", "Infantry", "Worker", "Cavalry", "Champion", "Hero", "Ship", "Trader" ];
    20 const g_ResourcesTypes = [ "food", "wood", "stone", "metal" ];
    2120
    2221// Colors used for gathered and traded resources
    2322const g_IncomeColor = '[color="201 255 200"]';
    2423const g_OutcomeColor = '[color="255 213 213"]';
    2524
    function init(data) 
    240239            g_Teams = false;    // Each player has his own team. Displaying teams makes no sense.
    241240    }
    242241    else
    243242        g_Teams = false;
    244243
     244    // Resource names and counters
     245    let resHeads = [];
     246    let tradeHeads = [];
     247    let resPanel = g_ScorePanelsData.resources;
     248    let tradePanel = g_ScorePanelsData.market;
     249    for (let code of g_GameData.resources.codes)
     250    {
     251        resHeads.push({
     252                "caption": translateWithContext("firstWord", g_GameData.resources.names[code]),
     253                "yStart": 34, "width": 100
     254            });
     255        resPanel.counters.unshift({"width": 100, "fn": calculateResources, "verticalOffset": 12});
     256       
     257        tradeHeads.push({
     258                "caption": sprintf(
     259                    translate("%(resource)s exchanged"), {
     260                        "resource": translateWithContext("withinSentence", g_GameData.resources.names[code])
     261                    }),
     262                "yStart": 16, "width": 100
     263            });
     264        tradePanel.counters.unshift({"width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12});
     265    }
     266    resPanel.headings.splice.apply(resPanel.headings, [1, 0].concat(resHeads));
     267    resPanel.titleHeadings[0].width = (100 * g_GameData.resources.codes.length) + 110;
     268    tradePanel.headings.splice.apply(tradePanel.headings, [1, 0].concat(tradeHeads));
     269
    245270    // Erase teams data if teams are not displayed
    246271    if (!g_Teams)
    247272    {
    248273        for (let p = 0; p < g_PlayerCount; ++p)
    249274            g_GameData.sim.playerStates[p+1].team = -1;
  • binaries/data/mods/public/gui/summary/summary.xml

     
    101101        <object name="generalPanel" type="image" sprite="ForegroundBody" size="20 120 100%-20 100%-54">
    102102            <object size="0 0 100% 100%-50">
    103103                <object name="playerNameHeading" type="text" style="ModernLeftTabLabelText">
    104104                    <translatableAttribute id="caption">Player name</translatableAttribute>
    105105                </object>
    106                 <repeat var="x" count="8">
     106                <repeat var="x" count="12">
    107107                    <object name="titleHeading[x]" type="text" style="ModernTabLabelText">
    108108                    </object>
    109109                </repeat>
    110                 <repeat var="x" count="8">
     110                <repeat var="x" count="12">
    111111                    <object name="Heading[x]" type="text" style="ModernTabLabelText">
    112112                    </object>
    113113                </repeat>
    114114            </object>
    115115
     
    122122                                <object type="image" sprite="ForegroundBox" size="10 9 34 33">
    123123                                    <object name="playerColorBoxt[i][n]" type="image" size="2 2 22 22"/>
    124124                                </object>
    125125                                <object name="playerNamet[i][n]" type="text" size="40 2 208 100%" style="ModernLeftLabelText"/>
    126126                                <object name="civIcont[i][n]" type="image" size="208 5 240 37" />
    127                                 <repeat var="x" count="8">
     127                                <repeat var="x" count="12">
    128128                                    <object name="valueDataTeam[i][n][x]" type="text" style="ModernTabLabelText">
    129129                                    </object>
    130130                                </repeat>
    131131                            </object>
    132132                        </repeat>
    133133                    </object>
    134134                    <object name="teamHeadingt[i]" type="text" style="ModernLeftTabLabelText"/>
    135                     <repeat var="x" count="8">
     135                    <repeat var="x" count="12">
    136136                        <object name="valueDataTeam[i][x]" type="text" style="ModernTabLabelText">
    137137                        </object>
    138138                    </repeat>
    139139                </object>
    140140            </repeat>
     
    145145                        <object type="image" sprite="ForegroundBox" size="10 9 34 33">
    146146                            <object name="playerColorBox[n]" type="image" size="2 2 22 22"/>
    147147                        </object>
    148148                        <object name="playerName[n]" type="text"  size="40 2 208 100%" style="ModernLeftLabelText"/>
    149149                        <object name="civIcon[n]" type="image" size="208 5 240 37"/>
    150                         <repeat var="x" count="8">
     150                        <repeat var="x" count="12">
    151151                            <object name="valueData[n][x]" type="text" style="ModernTabLabelText">
    152152                            </object>
    153153                        </repeat>
    154154                    </object>
    155155                </repeat>
  • binaries/data/mods/public/l10n/messages.json

     
    287287                        },
    288288                        "translate": {}
    289289                    }
    290290                }
    291291            },
    292             {
     292            {
    293293                "extractor": "json",
    294294                "filemasks": [
    295295                    "gui/credits/texts/**.json"
    296296                ],
    297297                "options": {
     
    560560                    "keywords": [
    561561                        "name",
    562562                        "description"
    563563                    ]
    564564                }
     565            },
     566            {
     567                "extractor": "json",
     568                "filemasks": [
     569                    "simulation/data/resources/**.json"
     570                ],
     571                "options": {
     572                    "keywords": [
     573                        "name",
     574                        "subtypes"
     575                    ],
     576                    "context": "firstWord"
     577                }
     578            },
     579            {
     580                "extractor": "json",
     581                "filemasks": [
     582                    "simulation/data/resources/**.json"
     583                ],
     584                "options": {
     585                    "keywords": [
     586                        "name",
     587                        "subtypes"
     588                    ],
     589                    "context": "withinSentence"
     590                }
    565591            }
    566592        ]
    567593    },
    568594    {
    569595        "output": "public-maps.pot",
  • binaries/data/mods/public/simulation/components/Cost.js

     
    11function Cost() {}
    22
     3Cost.prototype.ResourcesSchema = Resources.BuildSchema("nonNegativeInteger");
     4
    35Cost.prototype.Schema =
    46    "<a:help>Specifies the construction/training costs of this entity.</a:help>" +
    57    "<a:example>" +
    68        "<Population>1</Population>" +
    79        "<PopulationBonus>15</PopulationBonus>" +
    Cost.prototype.Schema = 
    1719        "<data type='nonNegativeInteger'/>" +
    1820    "</element>" +
    1921    "<element name='PopulationBonus' a:help='Population cap increase while this entity exists'>" +
    2022        "<data type='nonNegativeInteger'/>" +
    2123    "</element>" +
    22     "<element name='BuildTime' a:help='Time taken to construct/train this unit (in seconds)'>" +
     24    "<element name='BuildTime' a:help='Time taken to construct/train this entity (in seconds)'>" +
    2325        "<ref name='nonNegativeDecimal'/>" +
    2426    "</element>" +
    25     "<element name='Resources' a:help='Resource costs to construct/train this unit'>" +
    26         "<interleave>" +
    27             "<element name='food'><data type='nonNegativeInteger'/></element>" +
    28             "<element name='wood'><data type='nonNegativeInteger'/></element>" +
    29             "<element name='stone'><data type='nonNegativeInteger'/></element>" +
    30             "<element name='metal'><data type='nonNegativeInteger'/></element>" +
    31         "</interleave>" +
     27    "<element name='Resources' a:help='Resource costs to construct/train this entity'>" +
     28        Cost.prototype.ResourcesSchema +
    3229    "</element>";
    3330
    3431Cost.prototype.Init = function()
    3532{
    3633    this.populationCost = +this.template.Population;
    Cost.prototype.GetResourceCosts = functi 
    6865    let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
    6966    let entityTemplateName = cmpTemplateManager.GetCurrentTemplateName(this.entity);
    7067    let entityTemplate = cmpTemplateManager.GetTemplate(entityTemplateName);
    7168
    7269    let costs = {};
    73     for (let r in this.template.Resources)
    74         costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +this.template.Resources[r], owner, entityTemplate);
     70    let resCodes = Resources.GetCodes();
     71
     72    for (let res in this.template.Resources)
     73    {
     74        let cost = +this.template.Resources[res];
     75        if (resCodes.indexOf(res) < 0)
     76            continue;
     77        costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, cost, owner, entityTemplate);
     78    }
     79
    7580    return costs;
    7681};
    7782
    7883Cost.prototype.OnOwnershipChanged = function(msg)
    7984{
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    GuiInterface.prototype.GetSimulationStat 
    149149
    150150    // Add bartering prices
    151151    let cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
    152152    ret.barterPrices = cmpBarter.GetPrices();
    153153
     154    // Add Resource Codes, untranslated names and AI Analysis
     155    ret.resources = {
     156        "codes": Resources.GetCodes(),
     157        "names": Resources.GetNames()
     158    };
     159    ret.aiResourceAnalysis = {};
     160    for (let res of Resources.GetCodes())
     161        ret.aiResourceAnalysis[res] = Resources.GetResource(res).aiAnalysis || null;
     162
    154163    // Add basic statistics to each player
    155164    for (let i = 0; i < numPlayers; ++i)
    156165    {
    157166        let playerEnt = cmpPlayerManager.GetPlayerByID(i);
    158167        let cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
    GuiInterface.prototype.SetWallPlacementP 
    12651274    // --------------------------------------------------------------------------------
    12661275    // calculate wall placement and position preview entities
    12671276
    12681277    let result = {
    12691278        "pieces": [],
    1270         "cost": { "food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0 },
     1279        "cost": {"population": 0, "populationBonus": 0, "time": 0},
    12711280    };
    1272 
     1281    for (let res of Resources.GetCodes())
     1282        result.cost[res] = 0;
     1283   
    12731284    let previewEntities = [];
    12741285    if (end.pos)
    12751286        previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js
    12761287
    12771288    // For wall placement, we may (and usually do) need to have wall pieces overlap each other more than would
    GuiInterface.prototype.SetWallPlacementP 
    15411552
    15421553            // grab the cost of this wall piece and add it up (note; preview entities don't have their Cost components
    15431554            // copied over, so we need to fetch it from the template instead).
    15441555            // TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
    15451556            // boilerplate that's probably duplicated in tons of places.
    1546             result.cost.food += tplData.cost.food;
    1547             result.cost.wood += tplData.cost.wood;
    1548             result.cost.stone += tplData.cost.stone;
    1549             result.cost.metal += tplData.cost.metal;
    1550             result.cost.population += tplData.cost.population;
    1551             result.cost.populationBonus += tplData.cost.populationBonus;
    1552             result.cost.time += tplData.cost.time;
     1557            let entries = Resources.GetCodes().concat("population", "populationBonus", "time");
     1558            for (let res of entries)
     1559                result.cost[res] = tplData.cost[res];
    15531560        }
    15541561
    15551562        let canAfford = true;
    15561563        let cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
    15571564        if (cmpPlayer && cmpPlayer.GetNeededResources(result.cost))
  • binaries/data/mods/public/simulation/components/Loot.js

     
    11function Loot() {}
    22
     3Loot.prototype.ResourcesSchema = Resources.BuildSchema("nonNegativeInteger", [ "xp" ]);
     4
    35Loot.prototype.Schema =
    4     "<optional>" +
    5         "<element name='xp'><data type='nonNegativeInteger'/></element>" +
    6     "</optional>" +
    7     "<optional>" +
    8         "<element name='food'><data type='nonNegativeInteger'/></element>" +
    9     "</optional>" +
    10     "<optional>" +
    11         "<element name='wood'><data type='nonNegativeInteger'/></element>" +
    12     "</optional>" +
    13     "<optional>" +
    14         "<element name='stone'><data type='nonNegativeInteger'/></element>" +
    15     "</optional>" +
    16     "<optional>" +
    17         "<element name='metal'><data type='nonNegativeInteger'/></element>" +
    18     "</optional>";
     6    "<a:help>Specifies the loot credited when this entity is killed.</a:help>" +
     7    "<a:example>" +
     8        "<xp>35</xp>" +
     9        "<metal>10</metal>" +
     10    "</a:example>" +
     11    Loot.prototype.ResourcesSchema;
    1912
    2013Loot.prototype.Serialize = null; // we have no dynamic state to save
    2114
    2215Loot.prototype.GetXp = function()
    2316{
    2417    return +(this.template.xp || 0);
    2518};
    2619
    2720Loot.prototype.GetResources = function()
    2821{
    29     return {
    30         "food": +(this.template.food || 0),
    31         "wood": +(this.template.wood || 0),
    32         "metal": +(this.template.metal || 0),
    33         "stone": +(this.template.stone || 0)
    34     };
     22    let ret = {};
     23    for (let res of Resources.GetCodes())
     24        ret[res] = +(this.template[res] || 0);
     25   
     26    return ret;
    3527};
    3628
    3729Engine.RegisterComponentType(IID_Loot, "Loot", Loot);
  • binaries/data/mods/public/simulation/components/Player.js

    Player.prototype.Init = function() 
    1616    this.color = { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 };
    1717    this.popUsed = 0; // population of units owned or trained by this player
    1818    this.popBonuses = 0; // sum of population bonuses of player's entities
    1919    this.maxPop = 300; // maximum population
    2020    this.trainingBlocked = false; // indicates whether any training queue is currently blocked
    21     this.resourceCount = {
    22         "food": 300,
    23         "wood": 300,
    24         "metal": 300,
    25         "stone": 300
    26     };
    27     // goods for next trade-route and its proba in % (the sum of probas must be 100)
    28     this.tradingGoods = [
    29         { "goods":  "wood", "proba": 30 },
    30         { "goods": "stone", "proba": 35 },
    31         { "goods": "metal", "proba": 35 },
    32     ];
     21    this.resourceCount = {};
     22    this.tradingGoods = []; // goods for next trade-route and its proba in % (the sum of probas must be 100)
    3323    this.team = -1; // team number of the player, players on the same team will always have ally diplomatic status - also this is useful for team emblems, scoring, etc.
    3424    this.teamsLocked = false;
    3525    this.state = "active"; // game state - one of "active", "defeated", "won"
    3626    this.diplomacy = [];    // array of diplomatic stances for this player with respect to other players (including gaia and self)
    3727    this.sharedDropsites = false;
    Player.prototype.Init = function() 
    4232    this.gatherRateMultiplier = 1;
    4333    this.tradeRateMultiplier = 1;
    4434    this.cheatsEnabled = false;
    4535    this.cheatTimeMultiplier = 1;
    4636    this.heroes = [];
    47     this.resourceNames = {
    48         "food": markForTranslation("Food"),
    49         "wood": markForTranslation("Wood"),
    50         "metal": markForTranslation("Metal"),
    51         "stone": markForTranslation("Stone"),
    52     };
     37    this.resourceNames = {};
    5338    this.disabledTemplates = {};
    5439    this.disabledTechnologies = {};
    5540    this.startingTechnologies = [];
     41   
     42    let resCodes = Resources.GetCodes();
     43    let tradeProportions = [ 0, 0 ];
     44    tradeProportions[0] = Math.floor(20 / resCodes.length);
     45    tradeProportions[1] = 20 - resCodes.length * tradeProportions[0];
     46    let resPos = 0;
     47    for (let res of resCodes)
     48    {
     49        this.resourceCount[res] = 300;
     50        this.resourceNames[res] = Resources.GetResource(res).name;
     51        let proportion = tradeProportions[0] + ((resPos < tradeProportions[1]) ? 1 : 0);
     52        this.tradingGoods.push({ "goods":  res, "proba": (proportion * 5) });
     53        ++resPos;
     54    }
    5655};
    5756
    5857Player.prototype.SetPlayerID = function(id)
    5958{
    6059    this.playerID = id;
    Player.prototype.UnBlockTraining = funct 
    195194    this.trainingBlocked = false;
    196195};
    197196
    198197Player.prototype.SetResourceCounts = function(resources)
    199198{
    200     if (resources.food !== undefined)
    201         this.resourceCount.food = resources.food;
    202     if (resources.wood !== undefined)
    203         this.resourceCount.wood = resources.wood;
    204     if (resources.stone !== undefined)
    205         this.resourceCount.stone = resources.stone;
    206     if (resources.metal !== undefined)
    207         this.resourceCount.metal = resources.metal;
     199    for (let res in resources)
     200        if (this.resourceCount[res])
     201            this.resourceCount[res] = resources[res];
    208202};
    209203
    210204Player.prototype.GetResourceCounts = function()
    211205{
    212206    return this.resourceCount;
    Player.prototype.SubtractResourcesOrNoti 
    295289        return false;
    296290    }
    297291
    298292    // Subtract the resources
    299293    for (var type in amounts)
    300         this.resourceCount[type] -= amounts[type];
     294        if (this.resourceCount[type])
     295            this.resourceCount[type] -= amounts[type];
    301296
    302297    return true;
    303298};
    304299
    305300Player.prototype.TrySubtractResources = function(amounts)
    Player.prototype.SetTradingGoods = funct 
    344339    for (var resource in tradingGoods)
    345340        sumProba += tradingGoods[resource];
    346341    if (sumProba != 100)    // consistency check
    347342    {
    348343        error("Player.js SetTradingGoods: " + uneval(tradingGoods));
    349         tradingGoods = { "food": 20, "wood":20, "stone":30, "metal":30 };
     344        let first = true;
     345        for (let res of Resources.GetCodes())
     346            if (first)
     347            {
     348                tradingGoods[res] = 100;
     349                first = false;
     350            }
     351            else
     352                tradingGoods[res] = 0;
    350353    }
    351354
    352355    this.tradingGoods = [];
    353356    for (var resource in tradingGoods)
    354357        this.tradingGoods.push( {"goods": resource, "proba": tradingGoods[resource]} );
  • binaries/data/mods/public/simulation/components/ProductionQueue.js

     
    11var g_ProgressInterval = 1000;
    22const MAX_QUEUE_SIZE = 16;
    33
    44function ProductionQueue() {}
    55
     6ProductionQueue.prototype.ResourceSchema = Resources.BuildSchema("nonNegativeDecimal", [ "time" ]);
     7
    68ProductionQueue.prototype.Schema =
    79    "<a:help>Allows the building to train new units and research technologies</a:help>" +
    810    "<a:example>" +
    911        "<BatchTimeModifier>0.7</BatchTimeModifier>" +
    1012        "<Entities datatype='tokens'>" +
    ProductionQueue.prototype.Schema = 
    2931            "</attribute>" +
    3032            "<text/>" +
    3133        "</element>" +
    3234    "</optional>" +
    3335    "<element name='TechCostMultiplier' a:help='Multiplier to modify ressources cost and research time of technologies searched in this building.'>" +
    34         "<interleave>" +
    35             "<element name='food'><ref name='nonNegativeDecimal'/></element>" +
    36             "<element name='wood'><ref name='nonNegativeDecimal'/></element>" +
    37             "<element name='stone'><ref name='nonNegativeDecimal'/></element>" +
    38             "<element name='metal'><ref name='nonNegativeDecimal'/></element>" +
    39             "<element name='time'><ref name='nonNegativeDecimal'/></element>" +
    40         "</interleave>" +
     36        ProductionQueue.prototype.ResourceSchema +
    4137    "</element>";
    4238
    4339ProductionQueue.prototype.Init = function()
    4440{
    4541    this.nextID = 1;
    ProductionQueue.prototype.IsTechnologyRe 
    258254ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadata)
    259255{
    260256    // TODO: there should probably be a limit on the number of queued batches
    261257    // TODO: there should be a way for the GUI to determine whether it's going
    262258    // to be possible to add a batch (based on resource costs and length limits)
    263     var cmpPlayer = QueryOwnerInterface(this.entity);
     259    let cmpPlayer = QueryOwnerInterface(this.entity);
     260    let resCodes = Resources.GetCodes();
    264261
    265262    if (this.queue.length < MAX_QUEUE_SIZE)
    266263    {
    267264       
    268265        if (type == "unit")
    ProductionQueue.prototype.AddBatch = fun 
    291288            var costs = {};
    292289            var totalCosts = {};
    293290            var buildTime = ApplyValueModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, cmpPlayer.GetPlayerID(), template);
    294291            var time = timeMult * buildTime;
    295292
    296             for (var r in template.Cost.Resources)
     293            for (let res in template.Cost.Resources)
    297294            {
    298                 costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +template.Cost.Resources[r], cmpPlayer.GetPlayerID(), template);
    299                 totalCosts[r] = Math.floor(count * costs[r]);
     295                let cost = +template.Cost.Resources[res];
     296                if (resCodes.indexOf(res) < 0)
     297                    continue;
     298                costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, cost, cmpPlayer.GetPlayerID(), template);
     299                totalCosts[res] = Math.floor(count * costs[res]);
    300300            }
    301301
    302302            var population = ApplyValueModificationsToTemplate("Cost/Population",  +template.Cost.Population, cmpPlayer.GetPlayerID(), template);
    303303
    304304            // TrySubtractResources should report error to player (they ran out of resources)
    ProductionQueue.prototype.AddBatch = fun 
    339339                return;
    340340            var cmpPlayer = QueryOwnerInterface(this.entity);
    341341            let techCostMultiplier = this.GetTechCostMultiplier();
    342342            let time =  techCostMultiplier.time * template.researchTime * cmpPlayer.GetCheatTimeMultiplier();
    343343
    344             var cost = {};
     344            let cost = {};
    345345            for (let res in template.cost)
    346                 cost[res] = Math.floor(techCostMultiplier[res] * template.cost[res]);
     346            {
     347                if (resCodes.indexOf(res) < 0)
     348                    continue;
     349                cost[res] = Math.floor((techCostMultiplier[res] ? techCostMultiplier[res] : 1) * template.cost[res]);
     350            }
    347351
    348352            // TrySubtractResources should report error to player (they ran out of resources)
    349353            if (!cmpPlayer.TrySubtractResources(cost))
    350354                return;
    351355           
    ProductionQueue.prototype.AddBatch = fun 
    359363            this.queue.push({
    360364                "id": this.nextID++,
    361365                "player": cmpPlayer.GetPlayerID(),
    362366                "count": 1,
    363367                "technologyTemplate": templateName,
    364                 "resources": deepcopy(template.cost), // need to copy to avoid serialization problems
     368                "resources": cost,
    365369                "productionStarted": false,
    366370                "timeTotal": time*1000,
    367371                "timeRemaining": time*1000,
    368372            });
    369373           
    ProductionQueue.prototype.RemoveBatch = 
    431435        }
    432436
    433437        // Refund the resource cost for this batch
    434438        var totalCosts = {};
    435439        var cmpStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker);
    436         for each (var r in ["food", "wood", "stone", "metal"])
     440        for (let r of Resources.GetCodes())
    437441        {
     442            if (!item.resources[r])
     443                continue;
    438444            totalCosts[r] = Math.floor(item.count * item.resources[r]);
    439445            if (cmpStatisticsTracker)
    440446                cmpStatisticsTracker.IncreaseResourceUsedCounter(r, -totalCosts[r]);
    441447        }
    442448       
  • binaries/data/mods/public/simulation/components/ResourceDropsite.js

     
    11function ResourceDropsite() {}
    22
     3ResourceDropsite.prototype.ResourceChoiceSchema = Resources.BuildChoicesSchema();
     4
    35ResourceDropsite.prototype.Schema =
    46    "<element name='Types'>" +
    57        "<list>" +
    68            "<zeroOrMore>" +
    7                 "<choice>" +
    8                     "<value>food</value>" +
    9                     "<value>wood</value>" +
    10                     "<value>stone</value>" +
    11                     "<value>metal</value>" +
    12                 "</choice>" +
     9                ResourceDropsite.prototype.ResourceChoiceSchema +
    1310            "</zeroOrMore>" +
    1411        "</list>" +
    1512    "</element>" +
    1613    "<element name='Sharable' a:help='Allows allies to use this entity.'>" +
    1714        "<data type='boolean'/>" +
    ResourceDropsite.prototype.Init = functi 
    2219    this.sharable = this.template.Sharable == "true";
    2320    this.shared = this.sharable;
    2421};
    2522
    2623/**
    27  * Returns the list of resource types accepted by this dropsite.
     24 * Returns the list of resource types accepted by this dropsite,
     25 * as defined by it being referred to in the template and the resource being enabled.
    2826 */
    2927ResourceDropsite.prototype.GetTypes = function()
    3028{
    31     let types = ApplyValueModificationsToEntity("ResourceDropsite/Types", this.template.Types, this.entity);
    32     return types ? types.split(/\s+/) : [];
     29    let typesTok = ApplyValueModificationsToEntity("ResourceDropsite/Types", this.template.Types, this.entity);
     30    let typesArr = [];
     31    let resources = Resources.GetCodes();
     32
     33    for (let type of typesTok.split(/\s+/))
     34        if (resources.indexOf(type.toLowerCase()) > -1)
     35            typesArr.push(type);
     36
     37    return typesArr;
    3338};
    3439
    3540/**
    3641 * Returns whether this dropsite accepts the given generic type of resource.
    3742 */
  • binaries/data/mods/public/simulation/components/ResourceGatherer.js

     
    11function ResourceGatherer() {}
    22
     3ResourceGatherer.prototype.ResourcesSchema = Resources.BuildSchema("positiveDecimal", [ "treasure" ], true);
     4ResourceGatherer.prototype.CapacitiesSchema = Resources.BuildSchema("positiveDecimal");
     5
    36ResourceGatherer.prototype.Schema =
    47    "<a:help>Lets the unit gather resources from entities that have the ResourceSupply component.</a:help>" +
    58    "<a:example>" +
    69        "<MaxDistance>2.0</MaxDistance>" +
    710        "<BaseSpeed>1.0</BaseSpeed>" +
    ResourceGatherer.prototype.Schema = 
    2326    "</element>" +
    2427    "<element name='BaseSpeed' a:help='Base resource-gathering rate (in resource units per second)'>" +
    2528        "<ref name='positiveDecimal'/>" +
    2629    "</element>" +
    2730    "<element name='Rates' a:help='Per-resource-type gather rate multipliers. If a resource type is not specified then it cannot be gathered by this unit'>" +
    28         "<interleave>" +
    29             "<optional><element name='food' a:help='Food gather rate (may be overridden by \"food.*\" subtypes)'><ref name='positiveDecimal'/></element></optional>" +
    30             "<optional><element name='wood' a:help='Wood gather rate'><ref name='positiveDecimal'/></element></optional>" +
    31             "<optional><element name='stone' a:help='Stone gather rate'><ref name='positiveDecimal'/></element></optional>" +
    32             "<optional><element name='metal' a:help='Metal gather rate'><ref name='positiveDecimal'/></element></optional>" +
    33             "<optional><element name='treasure' a:help='Treasure gather rate (only presense on value makes sense, size is only used to determine the delay before gathering, so it should be set to 1)'><ref name='positiveDecimal'/></element></optional>" +
    34             "<optional><element name='food.fish' a:help='Fish gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
    35             "<optional><element name='food.fruit' a:help='Fruit gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
    36             "<optional><element name='food.grain' a:help='Grain gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
    37             "<optional><element name='food.meat' a:help='Meat gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
    38             "<optional><element name='food.milk' a:help='Milk gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
    39             "<optional><element name='wood.tree' a:help='Tree gather rate (overrides \"wood\")'><ref name='positiveDecimal'/></element></optional>" +
    40             "<optional><element name='wood.ruins' a:help='Tree gather rate (overrides \"wood\")'><ref name='positiveDecimal'/></element></optional>" +
    41             "<optional><element name='stone.rock' a:help='Rock gather rate (overrides \"stone\")'><ref name='positiveDecimal'/></element></optional>" +
    42             "<optional><element name='stone.ruins' a:help='Rock gather rate (overrides \"stone\")'><ref name='positiveDecimal'/></element></optional>" +
    43             "<optional><element name='metal.ore' a:help='Ore gather rate (overrides \"metal\")'><ref name='positiveDecimal'/></element></optional>" +
    44             "<optional><element name='treasure.food' a:help='Food treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
    45             "<optional><element name='treasure.wood' a:help='Wood treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
    46             "<optional><element name='treasure.stone' a:help='Stone treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
    47             "<optional><element name='treasure.metal' a:help='Metal treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
    48         "</interleave>" +
     31        ResourceGatherer.prototype.ResourcesSchema +
    4932    "</element>" +
    5033    "<element name='Capacities' a:help='Per-resource-type maximum carrying capacity'>" +
    51         "<interleave>" +
    52             "<element name='food' a:help='Food capacity'><ref name='positiveDecimal'/></element>" +
    53             "<element name='wood' a:help='Wood capacity'><ref name='positiveDecimal'/></element>" +
    54             "<element name='stone' a:help='Stone capacity'><ref name='positiveDecimal'/></element>" +
    55             "<element name='metal' a:help='Metal capacity'><ref name='positiveDecimal'/></element>" +
    56         "</interleave>" +
     34        ResourceGatherer.prototype.CapacitiesSchema +
    5735    "</element>";
    5836
    5937ResourceGatherer.prototype.Init = function()
    6038{
    6139    this.carrying = {}; // { generic type: integer amount currently carried }
    ResourceGatherer.prototype.RecalculateGa 
    135113    this.baseSpeed = multiplier * ApplyValueModificationsToEntity("ResourceGatherer/BaseSpeed", +this.template.BaseSpeed, this.entity);
    136114
    137115    this.rates = {};
    138116    for (let r in this.template.Rates)
    139117    {
     118        let type = r.split(".");
     119        let res = Resources.GetResource(type[0]);
     120       
     121        if (!res && type[0] !== "treasure" || (type.length > 1 && res.subtypes.indexOf(type[1]) < 0))
     122            continue;
     123       
    140124        let rate = ApplyValueModificationsToEntity("ResourceGatherer/Rates/" + r, +this.template.Rates[r], this.entity);
    141125        this.rates[r] = rate * this.baseSpeed;
    142126    }
    143127
    144128    this.capacities = {};
    ResourceGatherer.prototype.GetRange = fu 
    172156    // maybe this should depend on the unit or target or something?
    173157};
    174158
    175159/**
    176160 * Try to gather treasure
    177  * @return 'true' if treasure is successfully gathered and 'false' in the other case
     161 * @return 'true' if treasure is successfully gathered and 'false' if not
    178162 */
    179163ResourceGatherer.prototype.TryInstantGather = function(target)
    180164{
    181165    let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
    182166    let type = cmpResourceSupply.GetType();
  • binaries/data/mods/public/simulation/components/ResourceSupply.js

     
    11function ResourceSupply() {}
    22
     3ResourceSupply.prototype.ResourceChoiceSchema = Resources.BuildChoicesSchema(true, true);
     4
    35ResourceSupply.prototype.Schema =
    46    "<a:help>Provides a supply of one particular type of resource.</a:help>" +
    57    "<a:example>" +
    68        "<Amount>1000</Amount>" +
    79        "<Type>food.meat</Type>" +
    ResourceSupply.prototype.Schema = 
    1012        "<data type='boolean'/>" +
    1113    "</element>" +
    1214    "<element name='Amount' a:help='Amount of resources available from this entity'>" +
    1315        "<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" +
    1416    "</element>" +
    15     "<element name='Type' a:help='Type of resources'>" +
    16         "<choice>" +
    17             "<value>wood.tree</value>" +
    18             "<value>wood.ruins</value>" +
    19             "<value>stone.rock</value>" +
    20             "<value>stone.ruins</value>" +
    21             "<value>metal.ore</value>" +
    22             "<value>food.fish</value>" +
    23             "<value>food.fruit</value>" +
    24             "<value>food.grain</value>" +
    25             "<value>food.meat</value>" +
    26             "<value>food.milk</value>" +
    27             "<value>treasure.wood</value>" +
    28             "<value>treasure.stone</value>" +
    29             "<value>treasure.metal</value>" +
    30             "<value>treasure.food</value>" +
    31         "</choice>" +
     17    "<element name='Type' a:help='Type and Subtype of resource available from this entity'>" +
     18        ResourceSupply.prototype.ResourceChoiceSchema +
    3219    "</element>" +
    3320    "<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
    3421        "<data type='nonNegativeInteger'/>" +
    3522    "</element>" +
    3623    "<optional>" +
    ResourceSupply.prototype.Init = function 
    4330{
    4431    // Current resource amount (non-negative)
    4532    this.amount = this.GetMaxAmount();
    4633
    4734    this.gatherers = [];    // list of IDs for each players
    48     var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); // system component so that's safe.
    49     var numPlayers = cmpPlayerManager.GetNumPlayers();
    50     for (var i = 0; i <= numPlayers; ++i)   // use "<=" because we want Gaia too.
     35    let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); // system component so that's safe.
     36    let numPlayers = cmpPlayerManager.GetNumPlayers();
     37    for (let i = 0; i <= numPlayers; ++i)   // use "<=" because we want Gaia too.
    5138        this.gatherers.push([]);
    5239
    5340    this.infinite = !isFinite(+this.template.Amount);
    5441
    55     [this.type,this.subType] = this.template.Type.split('.');
    56     this.cachedType = { "generic" : this.type, "specific" : this.subType };
     42    [this.type, this.subtype] = this.template.Type.split('.');
     43    let resData = Resources.GetResource(this.type);
     44    if (this.type === "treasure")
     45        resData = { "subtypes": Resources.GetCodes() };
     46
     47    // Remove entity from gameworld if the resource supplied by this entity is disabled or not valid.
     48    if (!resData || resData.subtypes.indexOf(this.subtype) === -1)
     49        Engine.DestroyEntity(this.entity);
    5750
     51    this.cachedType = { "generic" : this.type, "specific" : this.subtype };
    5852};
    5953
    6054ResourceSupply.prototype.IsInfinite = function()
    6155{
    6256    return this.infinite;
  • binaries/data/mods/public/simulation/components/ResourceTrickle.js

     
    11function ResourceTrickle() {}
    22
     3ResourceTrickle.prototype.ResourcesSchema = Resources.BuildSchema("nonNegativeDecimal");
     4
    35ResourceTrickle.prototype.Schema =
    46    "<a:help>Controls the resource trickle ability of the unit.</a:help>" +
    57    "<element name='Rates' a:help='Trickle Rates'>" +
    6         "<interleave>" +
    7             "<optional>" +
    8                 "<element name='food' a:help='Food given to the player every interval'>" +
    9                     "<ref name='nonNegativeDecimal'/>" +
    10                 "</element>" +
    11             "</optional>" +
    12             "<optional>" +
    13                 "<element name='wood' a:help='Wood given to the player every interval'>" +
    14                     "<ref name='nonNegativeDecimal'/>" +
    15                 "</element>" +
    16             "</optional>" +
    17             "<optional>" +
    18                 "<element name='stone' a:help='Stone given to the player every interval'>" +
    19                     "<ref name='nonNegativeDecimal'/>" +
    20                 "</element>" +
    21             "</optional>" +
    22             "<optional>" +
    23                 "<element name='metal' a:help='Metal given to the player every interval'>" +
    24                     "<ref name='nonNegativeDecimal'/>" +
    25                 "</element>" +
    26             "</optional>" +
    27         "</interleave>" +
     8        ResourceTrickle.prototype.ResourcesSchema +
    289    "</element>" +
    2910    "<element name='Interval' a:help='Number of miliseconds must pass for the player to gain the next trickle.'>" +
    3011        "<ref name='nonNegativeDecimal'/>" +
    3112    "</element>";
    3213
    ResourceTrickle.prototype.GetTimer = fun 
    4324    return interval;
    4425};
    4526
    4627ResourceTrickle.prototype.GetRates = function()
    4728{
    48     var rates = {};
    49     for (var resource in this.template.Rates)
     29    let rates = {};
     30    let resCodes = Resources.GetCodes();
     31    for (let resource in this.template.Rates)
     32    {
     33        if (resCodes.indexOf(resource) < 0)
     34            continue;
    5035        rates[resource] = ApplyValueModificationsToEntity("ResourceTrickle/Rates/"+resource, +this.template.Rates[resource], this.entity);
     36    }
    5137
    5238    return rates;
    5339};
    5440
    5541// Do the actual work here
    5642ResourceTrickle.prototype.Trickle = function(data, lateness)
    5743{
    58     var cmpPlayer = QueryOwnerInterface(this.entity);
    59     if (!cmpPlayer)
    60         return;
    61 
    62     var rates = this.GetRates();
    63     for (var resource in rates)
    64         cmpPlayer.AddResource(resource, rates[resource]);
     44    let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     45    if (cmpPlayer)
     46        cmpPlayer.AddResources(this.GetRates());
    6547};
    6648
    6749Engine.RegisterComponentType(IID_ResourceTrickle, "ResourceTrickle", ResourceTrickle);
  • binaries/data/mods/public/simulation/components/StatisticsTracker.js

    StatisticsTracker.prototype.Init = funct 
    103103        "total": 0
    104104    };
    105105    this.buildingsCapturedValue = 0;
    106106
    107107    this.resourcesGathered = {
    108         "food": 0,
    109         "wood": 0,
    110         "metal": 0,
    111         "stone": 0,
    112108        "vegetarianFood": 0
    113109    };
    114     this.resourcesUsed = {
    115         "food": 0,
    116         "wood": 0,
    117         "metal": 0,
    118         "stone": 0
    119     };
    120     this.resourcesSold = {
    121         "food": 0,
    122         "wood": 0,
    123         "metal": 0,
    124         "stone": 0
    125     };
    126     this.resourcesBought = {
    127         "food": 0,
    128         "wood": 0,
    129         "metal": 0,
    130         "stone": 0
    131     };
     110    this.resourcesUsed = {};
     111    this.resourcesSold = {};
     112    this.resourcesBought = {};
     113    for (let res of Resources.GetCodes())
     114    {
     115        this.resourcesGathered[res] = 0;
     116        this.resourcesUsed[res] = 0;
     117        this.resourcesSold[res] = 0;
     118        this.resourcesBought[res] = 0;
     119    }
    132120
    133121    this.tributesSent = 0;
    134122    this.tributesReceived = 0;
    135123    this.tradeIncome = 0;
    136124    this.treasuresCollected = 0;
    StatisticsTracker.prototype.IncreaseReso 
    345333 * @param type Generic type of resource (string)
    346334 * @param amount Amount of resource, which should be added (integer)
    347335 */
    348336StatisticsTracker.prototype.IncreaseResourceUsedCounter = function(type, amount)
    349337{
    350     this.resourcesUsed[type] += amount;
     338    if (typeof this.resourcesUsed[type] === "number")
     339        this.resourcesUsed[type] += amount;
    351340};
    352341
    353342StatisticsTracker.prototype.IncreaseTreasuresCollectedCounter = function()
    354343{
    355344    ++this.treasuresCollected;
  • binaries/data/mods/public/simulation/components/Trader.js

     
    22// resources a trader gets
    33
    44// Additional gain for ships for each garrisoned trader, in percents
    55const GARRISONED_TRADER_ADDITION = 20;
    66
    7 // Array of resource names
    8 const RESOURCES = ["food", "wood", "stone", "metal"];
    9 
    107function Trader() {}
    118
    129Trader.prototype.Schema =
    1310    "<a:help>Lets the unit generate resouces while moving between markets (or docks in case of water trading).</a:help>" +
    1411    "<a:example>" +
  • binaries/data/mods/public/simulation/data/resources/food.json

     
     1{
     2    "code": "food",
     3    "name": "Food",
     4    "subtypes": {
     5        "fish": "Fish",
     6        "fruit": "Fruit",
     7        "grain": "Grain",
     8        "meat": "Meat",
     9        "milk": "Milk"
     10    },
     11    "truePrice": 100,
     12    "enabled": true
     13}
  • binaries/data/mods/public/simulation/data/resources/metal.json

     
     1{
     2    "code": "metal",
     3    "name": "Metal",
     4    "subtypes": {
     5        "ore": "Ore"
     6    },
     7    "truePrice": 100,
     8    "aiAnalysis": {
     9        "decreaseFactor": 90.0,
     10        "influenceMapGroup": 1
     11    },
     12    "enabled": true
     13}
  • binaries/data/mods/public/simulation/data/resources/stone.json

     
     1{
     2    "code": "stone",
     3    "name": "Stone",
     4    "subtypes": {
     5        "rock": "Rock",
     6        "ruins": "Ruins"
     7    },
     8    "truePrice": 100,
     9    "aiAnalysis": {
     10        "decreaseFactor": 90.0,
     11        "influenceMapGroup": 1
     12    },
     13    "enabled": true
     14}
  • binaries/data/mods/public/simulation/data/resources/wood.json

     
     1{
     2    "code": "wood",
     3    "name": "Wood",
     4    "subtypes": {
     5        "tree": "Tree",
     6        "ruins": "Ruins"
     7    },
     8    "truePrice": 100,
     9    "aiAnalysis": {
     10        "decreaseFactor": 50.0,
     11        "influenceMapGroup": 0
     12    },
     13    "enabled": true
     14}
  • binaries/data/mods/public/simulation/helpers/Resources.js

     
     1/**
     2 * Resource handling helper script
     3 *
     4 */
     5
     6var Resources = {};
     7
     8/**
     9 * Loads all readable resource data into internal stores
     10 */
     11Resources.LoadData = function()
     12{
     13    this.resourceData = [];
     14    this.resourceCodes = [];
     15
     16    let jsonFiles = Engine.FindJSONFiles("resources", false);
     17    for (let filename of jsonFiles)
     18    {
     19        let data = Engine.ReadJSONFile("resources/"+filename+".json");
     20        if (!data)
     21            continue;
     22
     23        data.subtypeNames = data.subtypes;
     24        data.subtypes = Object.keys(data.subtypes);
     25
     26        this.resourceData.push(data);
     27        if (data.enabled)
     28            this.resourceCodes.push(data.code);
     29    }
     30};
     31
     32/**
     33 * Returns all resource data
     34 */
     35Resources.GetData = function()
     36{
     37    if (!this.resourceData)
     38        this.LoadData();
     39
     40    return this.resourceData.filter((resource) => { return resource.enabled });
     41};
     42
     43/**
     44 * Returns data of a single resource. Only returns data about valid and enabled resources.
     45 *
     46 * @param type Resource generic type
     47 * @return The resource data if found, else false
     48 */
     49Resources.GetResource = function(type)
     50{
     51    let data = this.GetData();
     52    type = type.toLowerCase();
     53
     54    return data.find((resource) => { return resource.code == type; }) || false;
     55};
     56
     57/**
     58 * Returns an array of codes belonging to valid resources
     59 *
     60 * @return Array of generic resource type codes
     61 */
     62Resources.GetCodes = function()
     63{
     64    if (!this.resourceData)
     65        this.LoadData();
     66
     67    return this.resourceCodes;
     68};
     69
     70/**
     71 * Returns an object containing untranslated resource names mapped to
     72 * resource codes. Includes subtypes.
     73 */
     74Resources.GetNames = function()
     75{
     76    let names = {};
     77    for (let res of this.GetData())
     78    {
     79        names[res.code] = res.name;
     80        for (let subres of res.subtypes)
     81            names[subres] = res.subtypeNames[subres]
     82    }
     83    return names;
     84};
     85
     86/**
     87 * Builds a RelaxRNG schema based on currently valid elements.
     88 *
     89 * To prevent validation errors, disabled resources are included in the schema.
     90 *
     91 * @param datatype The datatype of the element
     92 * @param additional Array of additional data elements. Time, xp, treasure, etc.
     93 * @param subtypes If true, resource subtypes will be included as well.
     94 * @return RelaxNG schema string
     95 */
     96Resources.BuildSchema = function(datatype, additional = [], subtypes = false)
     97{
     98    if (!datatype)
     99        return "";
     100
     101    if (!this.resourceData)
     102        this.LoadData();
     103
     104    switch (datatype)
     105    {
     106    case "decimal":
     107    case "nonNegativeDecimal":
     108    case "positiveDecimal":
     109        datatype = "<ref name='" + datatype + "'/>";
     110        break;
     111
     112    default:
     113        datatype = "<data type='" + datatype + "'/>";
     114    }
     115
     116    let resCodes = this.resourceData.map((resource) => { return resource.code });
     117    let schema = "<interleave>";
     118    for (let res of resCodes.concat(additional))
     119        schema +=
     120            "<optional>" +
     121                "<element name='" + res + "'>" +
     122                    datatype +
     123                "</element>" +
     124            "</optional>";
     125
     126    if (!subtypes)
     127        return schema + "</interleave>";
     128
     129    for (let res of this.resourceData)
     130        for (let subtype of res.subtypes)
     131            schema +=
     132                "<optional>" +
     133                    "<element name='" + res.code + "." + subtype + "'>" +
     134                        datatype +
     135                    "</element>" +
     136                "</optional>";
     137
     138    if (additional.indexOf("treasure") !== -1)
     139        for (let res of resCodes)
     140            schema +=
     141                "<optional>" +
     142                    "<element name='" + "treasure." + res + "'>" +
     143                        datatype +
     144                    "</element>" +
     145                "</optional>";
     146
     147    return schema + "</interleave>";
     148}
     149
     150/**
     151 * Builds the value choices for a RelaxNG `<choice></choice>` object, based on currently valid resources.
     152 *
     153 * @oaram subtypes If set to true, the choices returned will be resource subtypes, rather than main types
     154 * @param treasure If set to true, the pseudo resource 'treasure' (or its subtypes) will be included
     155 * @return String of RelaxNG Schema `<choice/>` values.
     156 */
     157Resources.BuildChoicesSchema = function(subtypes = false, treasure = false)
     158{
     159    if (!this.resourceData)
     160        this.LoadData();
     161
     162    let schema = "<choice>";
     163
     164    if (!subtypes)
     165    {
     166        let resCodes = this.resourceData.map((resource) => { return resource.code });
     167        treasure = treasure ? [ "treasure" ] : [];
     168        for (let res of resCodes.concat(treasure))
     169            schema += "<value>" + res + "</value>";
     170    }
     171    else
     172        for (let res of this.resourceData)
     173        {
     174            for (let subtype of res.subtypes)
     175                schema += "<value>" + res.code + "." + subtype + "</value>";
     176            if (treasure)
     177                schema += "<value>" + "treasure." + res.code + "</value>";
     178        }
     179
     180    return schema + "</choice>";
     181}
     182
     183Engine.RegisterGlobal("Resources", Resources);