Ticket #3934: resource_agnostic-v2.patch

File resource_agnostic-v2.patch, 93.5 KB (added by s0600204, 3 years ago)

Stricter schema and sorting out some L10n issues.

  • new file inaries/data/mods/public/gui/common/functions_repeat_positioning.js

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

    diff --git a/binaries/data/mods/public/gui/common/l10n.js b/binaries/data/mods/public/gui/common/l10n.js
    index 53c16c2..3fd4570 100644
    a b  
    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/**
    function getLocalizedResourceAmounts(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)
  • binaries/data/mods/public/gui/common/setup_resources.xml

    diff --git a/binaries/data/mods/public/gui/common/setup_resources.xml b/binaries/data/mods/public/gui/common/setup_resources.xml
    index ffc9c4d..afbabdd 100644
    a b  
    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    />
  • binaries/data/mods/public/gui/common/tooltips.js

    diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js
    index 6422947..dfc15d4 100644
    a b  
    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 };
    91
    102const g_TooltipTextFormats = {
    113    "unit": ['[font="sans-10"][color="orange"]', '[/color][/font]'],
    function getBuildRateTooltip(rate) 
    260252 */
    261253function getCostComponentDisplayIcon(costComponentName)
    262254{
    263     if (costComponentName in g_CostDisplayIcons)
    264         return g_CostDisplayIcons[costComponentName];
    265 
    266     warn(sprintf("The specified cost component, ‘%(component)s’, is not currently supported.", { "component": costComponentName }));
    267     return "";
     255    return "[icon=\"icon_"+costComponentName+"\"]";
    268256}
    269257
    270258/**
    function getEntityCostComponentsTooltipString(template, trainNum, entity) 
    291279    totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
    292280
    293281    let costs = [];
    294 
    295     for (let type in g_CostDisplayIcons)
    296         if (totalCosts[type])
    297             costs.push(sprintf(translate("%(component)s %(cost)s"), {
    298                 "component": getCostComponentDisplayIcon(type),
    299                 "cost": totalCosts[type]
    300             }));
     282   
     283    for (let c in template.cost)
     284        if (c === "populationBonus")
     285            continue;
     286        else if (totalCosts[c])
     287            if (typeof GetSimState === "undefined" || GetSimState().resources[c] || c === "time")
     288                costs.push(sprintf(translate("%(component)s %(cost)s"), {
     289                    "component": getCostComponentDisplayIcon(c),
     290                    "cost": totalCosts[c]
     291                }));
    301292
    302293    return costs;
    303294}
  • binaries/data/mods/public/gui/session/diplomacy_window.xml

    diff --git a/binaries/data/mods/public/gui/session/diplomacy_window.xml b/binaries/data/mods/public/gui/session/diplomacy_window.xml
    index 1708159..f2499d8 100644
    a b  
    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">
     
    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>
     
    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">
  • binaries/data/mods/public/gui/session/menu.js

    diff --git a/binaries/data/mods/public/gui/session/menu.js b/binaries/data/mods/public/gui/session/menu.js
    index f7b1de4..ec00633 100644
    a b const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM; 
    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
    function openDiplomacy() 
    265262    g_IsDiplomacyOpen = true;
    266263
    267264    let isCeasefireActive = GetSimState().ceasefireActive;
     265    let resCodes = Object.keys(GetSimState().resources);
    268266
    269267    // Get offset for one line
    270268    let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size;
    function openDiplomacy() 
    283281        diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]);
    284282    }
    285283
    286     Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
     284    let dialog = Engine.GetGUIObjectByName("diplomacyDialogPanel");
     285    let size = dialog.size;
     286    let wid = resCodes.length * 10;
     287    size.left = -(260 + wid);
     288    size.right = (260 + wid);
     289    dialog.size = size;
     290    dialog.hidden = false;
    287291}
    288292
    289293function diplomacySetupTexts(i, rowsize)
    function diplomacyFormatStanceButtons(i, hidden) 
    329333
    330334function diplomacyFormatTributeButtons(i, hidden)
    331335{
    332     for (let resource of RESOURCES)
     336    let resources = GetSimState().resources;
     337    let r = 0;
     338    for (let resCode in resources)
    333339    {
    334         let button = Engine.GetGUIObjectByName("diplomacyPlayerTribute"+resource[0].toUpperCase()+resource.substring(1)+"["+(i-1)+"]");
     340        let button = Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]");
     341            Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]_image").sprite = "stretched:session/icons/resources/"+resCode+".png";
    335342        button.hidden = hidden;
     343        setPanelObjectPosition(button, r, 8, 0);
    336344        if (hidden)
    337345            continue;
     346        ++r;
    338347
    339348        button.enabled = controlsPlayer(g_ViewedPlayer);
    340         button.tooltip = formatTributeTooltip(i, resource, 100);
    341         button.onpress = (function(i, resource, button) {
     349        button.tooltip = formatTributeTooltip(i, resources[resCode], 100);
     350        button.onpress = (function(i, resCode, button) {
    342351            // Shift+click to send 500, shift+click+click to send 1000, etc.
    343352            // See INPUT_MASSTRIBUTING in input.js
    344353            let multiplier = 1;
    function diplomacyFormatTributeButtons(i, hidden) 
    351360                }
    352361
    353362                let amounts = {};
    354                 for (let type of RESOURCES)
    355                     amounts[type] = 0;
    356                 amounts[resource] = 100 * multiplier;
     363                for (let res in GetSimState().resources)
     364                    amounts[res] = 0;
     365                amounts[resCode] = 100 * multiplier,
    357366
    358                 button.tooltip = formatTributeTooltip(i, resource, amounts[resource]);
     367                button.tooltip = formatTributeTooltip(i, resources[resCode], amounts[resCode]);
    359368
    360369                // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some
    361370                // evil global variable hackery.
    362371                g_FlushTributing = function() {
    363372                    Engine.PostNetworkCommand({ "type": "tribute", "player": i, "amounts":  amounts });
    364373                    multiplier = 1;
    365                     button.tooltip = formatTributeTooltip(i, resource, 100);
     374                    button.tooltip = formatTributeTooltip(i, resources[resCode], 100);
    366375                };
    367376
    368377                if (!isBatchTrainPressed)
    369378                    g_FlushTributing();
    370379            };
    371         })(i, resource, button);
     380        })(i, resCode, button);
    372381    }
    373382}
    374383
    function openTrade() 
    422431        }
    423432    };
    424433
    425     var proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
    426     var button = {};
    427     var selec = RESOURCES[0];
    428     for (var i = 0; i < RESOURCES.length; ++i)
     434    let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
     435    let button = {};
     436    let resCodes = Object.keys(GetSimState().resources);
     437    let selec = resCodes[0];
     438    hideRemaining("tradeResource[", resCodes.length, "]");
     439
     440    for (let i = 0; i < resCodes.length; ++i)
    429441    {
    430         var buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
    431         if (i > 0)
    432         {
    433             var size = Engine.GetGUIObjectByName("tradeResource["+(i-1)+"]").size;
    434             var width = size.right - size.left;
    435             size.left += width;
    436             size.right += width;
    437             Engine.GetGUIObjectByName("tradeResource["+i+"]").size = size;
    438         }
    439         var resource = RESOURCES[i];
    440         proba[resource] = (proba[resource] ? proba[resource] : 0);
    441         var buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
    442         var icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
    443         icon.sprite = "stretched:session/icons/resources/" + resource + ".png";
    444         var label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]");
    445         var buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
    446         var buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
    447         var iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
    448         button[resource] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
     442        let buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
     443        setPanelObjectPosition(buttonResource, i, 8);
     444        let resCode = resCodes[i];
     445        proba[resCode] = (proba[resCode] ? proba[resCode] : 0);
     446        buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
     447        let icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
     448        icon.sprite = "stretched:session/icons/resources/" + resCode + ".png";
     449        let label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]");
     450        let buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
     451        let buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
     452        let iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
     453        button[resCode] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
    449454
    450455        buttonResource.enabled = controlsPlayer(g_ViewedPlayer);
    451456        buttonResource.onpress = (function(resource){
    452457            return function() {
    453458                if (Engine.HotkeyIsPressed("session.fulltradeswap"))
    454459                {
    455                     for (var ress of RESOURCES)
     460                    for (let ress of resCodes)
    456461                        proba[ress] = 0;
    457462                    proba[resource] = 100;
    458463                    Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    function openTrade() 
    460465                selec = resource;
    461466                updateButtons();
    462467            };
    463         })(resource);
     468        })(resCode);
    464469
    465470        buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
    466471        buttonUp.onpress = (function(resource){
    function openTrade() 
    470475                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    471476                updateButtons();
    472477            };
    473         })(resource);
     478        })(resCode);
    474479
    475480        buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
    476481        buttonDn.onpress = (function(resource){
    function openTrade() 
    480485                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    481486                updateButtons();
    482487            };
    483         })(resource);
     488        })(resCode);
    484489    }
    485490    updateButtons();
    486491
    function openTrade() 
    488493    Engine.GetGUIObjectByName("landTraders").caption = getIdleLandTradersText(traderNumber);
    489494    Engine.GetGUIObjectByName("shipTraders").caption = getIdleShipTradersText(traderNumber);
    490495
    491     Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false;
     496    let dialog = Engine.GetGUIObjectByName("tradeDialogPanel");
     497    let size = dialog.size;
     498    let wid = resCodes.length * (58/2);
     499    size.left = -(134 + wid);
     500    size.right = (134 + wid);
     501    dialog.size = size;
     502    dialog.hidden = false;
    492503}
    493504
    494505function getIdleLandTradersText(traderNumber)
  • binaries/data/mods/public/gui/session/selection_details.js

    diff --git a/binaries/data/mods/public/gui/session/selection_details.js b/binaries/data/mods/public/gui/session/selection_details.js
    index 7948042..2bff4ba 100644
    a b function layoutSelectionMultiple() 
    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[(resourceCode == "treasure" ? resourceType.specific : resourceCode)]
     17    return getLocalizedResourceName(resourceName, "firstWord");
    2018}
    2119
    2220// Updates the health bar of garrisoned units
  • binaries/data/mods/public/gui/session/selection_panels.js

    diff --git a/binaries/data/mods/public/gui/session/selection_panels.js b/binaries/data/mods/public/gui/session/selection_panels.js
    index 387a5bb..2cff9ba 100644
    a b g_SelectionPanels.Alert = { 
    8787g_SelectionPanels.Barter = {
    8888    "getMaxNumberOfItems": function()
    8989    {
    90         return 4;
     90        return 8;
    9191    },
    9292    "rowLength": 4,
    9393    "getItems": function(unitEntState, selection)
    9494    {
    9595        if (!unitEntState.barterMarket)
    9696            return [];
    97         // ["food", "wood", "stone", "metal"]
    98         return BARTER_RESOURCES;
     97        return Object.keys(GetSimState().resources);
    9998    },
    10099    "setupButton": function(data)
    101100    {
    g_SelectionPanels.Barter = { 
    115114        if (Engine.HotkeyIsPressed("session.massbarter"))
    116115            amountToSell *= BARTER_BUNCH_MULTIPLIER;
    117116
     117        if (!g_BarterSell)
     118            g_BarterSell = Object.keys(GetSimState().resources)[0];
     119
    118120        amount.Sell.caption = "-" + amountToSell;
    119121        let prices = data.unitEntState.barterMarket.prices;
    120122        amount.Buy.caption = "+" + Math.round(prices.sell[g_BarterSell] / prices.buy[data.item] * amountToSell);
    121123
    122         let resource = getLocalizedResourceName(data.item, "withinSentence");
     124        let resource = getLocalizedResourceName(GetSimState().resources[data.item], "withinSentence");
    123125        button.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource });
    124126        button.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource });
    125127
    g_SelectionPanels.Barter = { 
    158160        button.Sell.hidden = false;
    159161        selectionIcon.hidden = !isSelected;
    160162
    161         setPanelObjectPosition(button.Sell, data.i, data.rowLength);
    162         setPanelObjectPosition(button.Buy, data.i + data.rowLength, data.rowLength);
     163        let sellPos = data.i + (data.i >= data.rowLength ? data.rowLength : 0);
     164        let buyPos = data.i + data.rowLength * (data.i >= data.rowLength ? 2 : 1);
     165        setPanelObjectPosition(button.Sell, sellPos, data.rowLength);
     166        setPanelObjectPosition(button.Buy, buyPos, data.rowLength);
    163167        return true;
    164168    }
    165169};
  • binaries/data/mods/public/gui/session/selection_panels_helpers.js

    diff --git a/binaries/data/mods/public/gui/session/selection_panels_helpers.js b/binaries/data/mods/public/gui/session/selection_panels_helpers.js
    index 681b65a..ae95910 100644
    a b  
    22// Barter constants
    33const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
    44const BARTER_BUNCH_MULTIPLIER = 5;
    5 const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
    65const BARTER_ACTIONS = ["Sell", "Buy"];
    76
    87// Gate constants
    const GATE_ACTIONS = ["lock", "unlock"]; 
    109
    1110// ==============================================
    1211// BARTER HELPERS
    13 // Resources to sell on barter panel
    14 var g_BarterSell = "food";
     12// Resource to sell by default on barter panel
     13// ! - cannot be set as we don't know the possible resources yet
     14var g_BarterSell = null;
    1515
     16// ==============================================
    1617// FORMATION HELPERS
    1718// Check if the selection can move into formation, and cache the result
    1819function canMoveSelectionIntoFormation(formationTemplate)
  • binaries/data/mods/public/gui/session/selection_panels_left/barter_panel.xml

    diff --git a/binaries/data/mods/public/gui/session/selection_panels_left/barter_panel.xml b/binaries/data/mods/public/gui/session/selection_panels_left/barter_panel.xml
    index f32e117..c28ef92 100644
    a b  
    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

    diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js
    index 93fbb16..4884267 100644
    a b function updateTopPanel() 
    419419        Engine.GetGUIObjectByName("civIconOverlay").tooltip = sprintf(translate("%(civ)s - Structure Tree"), { "civ": civName });
    420420    }
    421421
     422    let resources = GetSimState().resources;
     423    let r = 0;
     424    for (let resCode in resources)
     425    {
     426        Engine.GetGUIObjectByName("resource["+r+"]").tooltip = getLocalizedResourceName(resources[resCode], "firstWord");
     427        Engine.GetGUIObjectByName("resource["+r+"]_icon").sprite = "stretched:session/icons/resources/" + resCode + ".png";
     428        ++r;
     429    }
     430    horizFitRepeatedObjects ("resource[n]", "n", 0, r);
     431    hideRemaining("resource[", r, "]");
     432
    422433    // Hide stuff gaia/observers don't use.
    423     Engine.GetGUIObjectByName("food").hidden = !isPlayer;
    424     Engine.GetGUIObjectByName("wood").hidden = !isPlayer;
    425     Engine.GetGUIObjectByName("stone").hidden = !isPlayer;
    426     Engine.GetGUIObjectByName("metal").hidden = !isPlayer;
     434    for (let r = 0; r < resources.length; ++r)
     435        Engine.GetGUIObjectByName("resource["+r+"]").hidden = !isPlayer;
    427436    Engine.GetGUIObjectByName("population").hidden = !isPlayer;
    428437    Engine.GetGUIObjectByName("civIcon").hidden = !isPlayer;
    429438    Engine.GetGUIObjectByName("diplomacyButton1").hidden = !isPlayer;
    function leaveGame(willRejoin) 
    529538
    530539    summary.gameResult = gameResult;
    531540    summary.isReplay = g_IsReplay;
     541    summary.resources = GetSimState().resources;
    532542    Engine.SwitchGuiPage("page_summary.xml", summary);
    533543}
    534544
    function updateDebug() 
    920930
    921931function updatePlayerDisplay()
    922932{
    923     let playerState = GetSimState().players[g_ViewedPlayer];
     933    let simState = GetSimState();
     934    let playerState = simState.players[g_ViewedPlayer];
    924935    if (!playerState)
    925936        return;
    926937
    927     Engine.GetGUIObjectByName("resourceFood").caption = Math.floor(playerState.resourceCounts.food);
    928     Engine.GetGUIObjectByName("resourceWood").caption = Math.floor(playerState.resourceCounts.wood);
    929     Engine.GetGUIObjectByName("resourceStone").caption = Math.floor(playerState.resourceCounts.stone);
    930     Engine.GetGUIObjectByName("resourceMetal").caption = Math.floor(playerState.resourceCounts.metal);
     938    let resCodes = Object.keys(simState.resources);
     939    for (let r = 0; r < resCodes.length; ++r)
     940        Engine.GetGUIObjectByName("resource["+r+"]_count").caption = Math.floor(playerState.resourceCounts[resCodes[r]]);
     941
    931942    Engine.GetGUIObjectByName("resourcePop").caption = playerState.popCount + "/" + playerState.popLimit;
    932943    Engine.GetGUIObjectByName("population").tooltip = translate("Population (current / limit)") + "\n" +
    933944                    sprintf(translate("Maximum population: %(popCap)s"), { "popCap": playerState.popMax });
  • binaries/data/mods/public/gui/session/session.xml

    diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
    index 8f2db00..bf12835 100644
    a b  
    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"/>
  • deleted file binaries/data/mods/public/gui/session/top_panel/resource_food.xml

    diff --git a/binaries/data/mods/public/gui/session/top_panel/resource_food.xml b/binaries/data/mods/public/gui/session/top_panel/resource_food.xml
    deleted file mode 100644
    index c3f49a4..0000000
    + -  
    1 <?xml version="1.0" encoding="utf-8"?>
    2 <object name="food" size="10 0 100 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
    3         <translatableAttribute id="tooltip">Food</translatableAttribute>
    4         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
    5         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceFood"/>
    6 </object>
  • deleted file binaries/data/mods/public/gui/session/top_panel/resource_metal.xml

    diff --git a/binaries/data/mods/public/gui/session/top_panel/resource_metal.xml b/binaries/data/mods/public/gui/session/top_panel/resource_metal.xml
    deleted file mode 100644
    index 8dc01ec..0000000
    + -  
    1 <?xml version="1.0" encoding="utf-8"?>
    2 <object name="metal" size="280 0 370 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
    3         <translatableAttribute id="tooltip">Metal</translatableAttribute>
    4         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
    5         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceMetal"/>
    6 </object>
  • binaries/data/mods/public/gui/session/top_panel/resource_population.xml

    diff --git a/binaries/data/mods/public/gui/session/top_panel/resource_population.xml b/binaries/data/mods/public/gui/session/top_panel/resource_population.xml
    index 9c9dcc2..9d66e40 100644
    a b  
    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>
  • deleted file binaries/data/mods/public/gui/session/top_panel/resource_stone.xml

    diff --git a/binaries/data/mods/public/gui/session/top_panel/resource_stone.xml b/binaries/data/mods/public/gui/session/top_panel/resource_stone.xml
    deleted file mode 100644
    index 37d08e3..0000000
    + -  
    1 <?xml version="1.0" encoding="utf-8"?>
    2 <object name="stone" size="190 0 280 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
    3         <translatableAttribute id="tooltip">Stone</translatableAttribute>
    4         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
    5         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceStone"/>
    6 </object>
  • deleted file binaries/data/mods/public/gui/session/top_panel/resource_wood.xml

    diff --git a/binaries/data/mods/public/gui/session/top_panel/resource_wood.xml b/binaries/data/mods/public/gui/session/top_panel/resource_wood.xml
    deleted file mode 100644
    index 90558af..0000000
    + -  
    1 <?xml version="1.0" encoding="utf-8"?>
    2 <object name="wood" size="100 0 190 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
    3         <translatableAttribute id="tooltip">Wood</translatableAttribute>
    4         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
    5         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourceWood"/>
    6 </object>
  • new file inaries/data/mods/public/gui/session/top_panel/resources.xml

    diff --git a/binaries/data/mods/public/gui/session/top_panel/resources.xml b/binaries/data/mods/public/gui/session/top_panel/resources.xml
    new file mode 100644
    index 0000000..bcf5784
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2
     3<object size="10 0 50%-90-52 100%">
     4  <repeat count="8">
     5    <object name="resource[n]" size="0 0 90 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     6      <object size="0 -4 40 36" type="image" name="resource[n]_icon" ghost="true"/>
     7      <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource[n]_count"/>
     8    </object>
     9  </repeat>
     10</object>
  • binaries/data/mods/public/gui/session/trade_window.xml

    diff --git a/binaries/data/mods/public/gui/session/trade_window.xml b/binaries/data/mods/public/gui/session/trade_window.xml
    index 80b226c..0862bc1 100644
    a b  
    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"/>
  • binaries/data/mods/public/gui/summary/counters.js

    diff --git a/binaries/data/mods/public/gui/summary/counters.js b/binaries/data/mods/public/gui/summary/counters.js
    index f595863..ca20e80 100644
    a b function calculateUnits(playerState, position) 
    197197
    198198function calculateResources(playerState, position)
    199199{
    200     let type = g_ResourcesTypes[position];
     200    let type = Object.keys(g_GameData.resources)[position];
    201201
    202202    return formatIncome(
    203203        playerState.statistics.resourcesGathered[type],
    function calculateTotalResources(playerState) 
    209209    let totalGathered = 0;
    210210    let totalUsed = 0;
    211211
    212     for (let type of g_ResourcesTypes)
     212    for (let type of Object.keys(g_GameData.resources))
    213213    {
    214214        totalGathered += playerState.statistics.resourcesGathered[type];
    215215        totalUsed += playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type];
    function calculateResourcesTeam(counters) 
    279279
    280280function calculateResourceExchanged(playerState, position)
    281281{
    282     let type = g_ResourcesTypes[position];
     282    let type = Object.keys(g_GameData.resources)[position];
    283283
    284284    return formatIncome(
    285285        playerState.statistics.resourcesBought[type],
  • binaries/data/mods/public/gui/summary/layout.js

    diff --git a/binaries/data/mods/public/gui/summary/layout.js b/binaries/data/mods/public/gui/summary/layout.js
    index e987340..a45debf 100644
    a b var g_ScorePanelsData = { 
    7373    "resources": {
    7474        "headings": [
    7575            { "caption": translate("Player name"), "yStart": 26, "width": 200 },
    76             { "caption": translate("Food"), "yStart": 34, "width": 100 },
    77             { "caption": translate("Wood"), "yStart": 34, "width": 100 },
    78             { "caption": translate("Stone"), "yStart": 34, "width": 100 },
    79             { "caption": translate("Metal"), "yStart": 34, "width": 100 },
    8076            { "caption": translate("Total"), "yStart": 34, "width": 110 },
    8177            { "caption": translate("Tributes (Sent / Received)"), "yStart": 16, "width": 121 },
    8278            { "caption": translate("Treasures collected"), "yStart": 16, "width": 100 },
    var g_ScorePanelsData = { 
    8682            { "caption": translate("Resource Statistics (Gathered / Used)"), "yStart": 16, "width": (100 * 4 + 110) }, // width = 510
    8783        ],
    8884        "counters": [
    89             { "width": 100, "fn": calculateResources },
    90             { "width": 100, "fn": calculateResources },
    91             { "width": 100, "fn": calculateResources },
    92             { "width": 100, "fn": calculateResources },
    9385            { "width": 110, "fn": calculateTotalResources },
    9486            { "width": 121, "fn": calculateTributeSent },
    9587            { "width": 100, "fn": calculateTreasureCollected },
    var g_ScorePanelsData = { 
    10092    "market": {
    10193        "headings": [
    10294            { "caption": translate("Player name"), "yStart": 26, "width": 200 },
    103             { "caption": translate("Food exchanged"), "yStart": 16, "width": 100 },
    104             { "caption": translate("Wood exchanged"), "yStart": 16, "width": 100 },
    105             { "caption": translate("Stone exchanged"), "yStart": 16, "width": 100 },
    106             { "caption": translate("Metal exchanged"), "yStart": 16, "width": 100 },
    10795            { "caption": translate("Barter efficiency"), "yStart": 16, "width": 100 },
    10896            { "caption": translate("Trade income"), "yStart": 16, "width": 100 }
    10997        ],
    11098        "titleHeadings": [],
    11199        "counters": [
    112             { "width": 100, "fn": calculateResourceExchanged },
    113             { "width": 100, "fn": calculateResourceExchanged },
    114             { "width": 100, "fn": calculateResourceExchanged },
    115             { "width": 100, "fn": calculateResourceExchanged },
    116100            { "width": 100, "fn": calculateBarterEfficiency },
    117101            { "width": 100, "fn": calculateTradeIncome }
    118102        ],
  • binaries/data/mods/public/gui/summary/summary.js

    diff --git a/binaries/data/mods/public/gui/summary/summary.js b/binaries/data/mods/public/gui/summary/summary.js
    index 3a3a09e..34c06df 100644
    a b  
    1 const g_MaxHeadingTitle= 8;
     1const g_MaxHeadingTitle= 12;
    22
    33// const for filtering long collective headings
    44const g_LongHeadingWidth = 250;
    const g_KilledColor = '[color="196 198 255"]'; 
    1616
    1717const g_BuildingsTypes = [ "total", "House", "Economic", "Outpost", "Military", "Fortress", "CivCentre", "Wonder" ];
    1818const g_UnitsTypes = [ "total", "Infantry", "Worker", "Cavalry", "Champion", "Hero", "Ship", "Trader" ];
    19 const g_ResourcesTypes = [ "food", "wood", "stone", "metal" ];
    2019
    2120// Colors used for gathered and traded resources
    2221const g_IncomeColor = '[color="201 255 200"]';
    function init(data) 
    198197    else
    199198        g_Teams = false;
    200199
     200    // Resource names and counters
     201    let resHeads = [];
     202    let tradeHeads = [];
     203    let resPanel = g_ScorePanelsData.resources;
     204    let tradePanel = g_ScorePanelsData.market;
     205    for (let code of Object.keys(g_GameData.resources))
     206    {
     207        resHeads.push({
     208                "caption": translateWithContext("firstWord", g_GameData.resources[code]),
     209                "yStart": 34, "width": 100
     210            });
     211        resPanel.counters.unshift({"width": 100, "fn": calculateResources});
     212       
     213        tradeHeads.push({
     214                "caption": sprintf(
     215                    translate("%(resource)s exchanged"), {
     216                        "resource": translateWithContext("withinSentence", g_GameData.resources[code])
     217                    }),
     218                "yStart": 16, "width": 100,
     219            });
     220        tradePanel.counters.unshift({"width": 100, "fn": calculateResourceExchanged});
     221    }
     222    resPanel.headings.splice.apply(resPanel.headings, [1, 0].concat(resHeads));
     223    resPanel.titleHeadings[0].width = (100 * Object.keys(g_GameData.resources).length) + 110;
     224    tradePanel.headings.splice.apply(tradePanel.headings, [1, 0].concat(tradeHeads));
     225
    201226    // Erase teams data if teams are not displayed
    202227    if (!g_Teams)
    203228    {
  • binaries/data/mods/public/gui/summary/summary.xml

    diff --git a/binaries/data/mods/public/gui/summary/summary.xml b/binaries/data/mods/public/gui/summary/summary.xml
    index 4a77ea6..7bfd9cb 100644
    a b  
    107107                <object name="playerNameHeading" type="text" style="ModernLeftTabLabelText">
    108108                    <translatableAttribute id="caption">Player name</translatableAttribute>
    109109                </object>
    110                 <repeat var="x" count="8">
     110                <repeat var="x" count="12">
    111111                    <object name="titleHeading[x]" type="text" style="ModernTabLabelText">
    112112                    </object>
    113113                </repeat>
    114                 <repeat var="x" count="8">
     114                <repeat var="x" count="12">
    115115                    <object name="Heading[x]" type="text" style="ModernTabLabelText">
    116116                    </object>
    117117                </repeat>
     
    126126                                <object name="playerColorBoxt[i][n]" type="image" size="10 4 30 24"/>
    127127                                <object name="playerNamet[i][n]" type="text"  size="40 2 208 100%" style="ModernLeftLabelText" />
    128128                                <object name="civIcont[i][n]" type="image" size="208 0 240 32"/>
    129                                 <repeat var="x" count="8">
     129                                <repeat var="x" count="12">
    130130                                    <object name="valueDataTeam[i][n][x]" type="text" style="ModernTabLabelText">
    131131                                    </object>
    132132                                </repeat>
     
    134134                        </repeat>
    135135                    </object>
    136136                    <object name="teamHeadingt[i]" type="text" style="ModernLeftTabLabelText"/>
    137                     <repeat var="x" count="8">
     137                    <repeat var="x" count="12">
    138138                        <object name="valueDataTeam[i][x]" type="text" style="ModernTabLabelText">
    139139                        </object>
    140140                    </repeat>
     
    149149                        </object>
    150150                        <object name="playerName[n]" type="text"  size="40 2 208 100%" style="ModernLeftLabelText"/>
    151151                        <object name="civIcon[n]" type="image" size="208 0 240 32"/>
    152                         <repeat var="x" count="8">
     152                        <repeat var="x" count="12">
    153153                            <object name="valueData[n][x]" type="text" style="ModernTabLabelText">
    154154                            </object>
    155155                        </repeat>
  • binaries/data/mods/public/l10n/messages.json

    diff --git a/binaries/data/mods/public/l10n/messages.json b/binaries/data/mods/public/l10n/messages.json
    index 188e3cc..0b3315c 100644
    a b  
    289289                    }
    290290                }
    291291            },
    292             {
     292            {
    293293                "extractor": "json",
    294294                "filemasks": [
    295295                    "gui/credits/texts/**.json"
     
    562562                        "description"
    563563                    ]
    564564                }
    565             }
     565            },
     566            {
     567                "extractor": "json",
     568                "filemasks": [
     569                    "simulation/data/resources/**.json"
     570                ],
     571                "options": {
     572                    "keywords": [
     573                        "name"
     574                    ]
     575                }
    566576        ]
    567577    },
    568578    {
  • binaries/data/mods/public/simulation/ai/common-api/resources.js

    diff --git a/binaries/data/mods/public/simulation/ai/common-api/resources.js b/binaries/data/mods/public/simulation/ai/common-api/resources.js
    index 768106b..afc3cf0 100644
    a b m.Resources = function(amounts = {}, population = 0) 
    99    this.population = population > 0 ? population : 0;
    1010};
    1111
    12 m.Resources.prototype.types = [ "food", "wood", "stone", "metal" ];
     12m.Resources.prototype.types = [ ]; // Gets populated in SharedScript.init
    1313
    1414m.Resources.prototype.reset = function()
    1515{
  • binaries/data/mods/public/simulation/ai/common-api/shared.js

    diff --git a/binaries/data/mods/public/simulation/ai/common-api/shared.js b/binaries/data/mods/public/simulation/ai/common-api/shared.js
    index 57e2930..1c49d26 100644
    a b m.SharedScript = function(settings) 
    2727
    2828    // A few notes about these maps. They're updated by checking for "create" and "destroy" events for all resources
    2929    // TODO: change the map when the resource amounts change for at least stone and metal mines.
    30     this.resourceMaps = {}; // Contains maps showing the density of wood, stone and metal
    31     this.CCResourceMaps = {}; // Contains maps showing the density of wood, stone and metal, optimized for CC placement.
     30    this.resourceMaps = {}; // Contains maps showing the density of resources
     31    this.CCResourceMaps = {}; // Contains maps showing the density of resources, optimized for CC placement.
    3232    // Resource maps data.
    33     // By how much to divide the resource amount when filling the map (ie a tree having 200 wood is "4").
    34     this.decreaseFactor = {"wood": 50.0, "stone": 90.0, "metal": 90.0};
     33    this.decreaseFactor = {};
     34    this.influenceMapGroup = {};
    3535};
    3636
    3737//Return a simple object (using no classes etc) that will be serialized into saved games
    m.SharedScript.prototype.init = function(state, deserialization) 
    142142    this.mapSize = state.mapSize;
    143143    this.gameType = state.gameType;
    144144    this.barterPrices = state.barterPrices;
     145   
     146    m.Resources.prototype.types = state.resources;
    145147
    146148    this.passabilityMap = state.passabilityMap;
    147149    if (this.mapSize % this.passabilityMap.width !== 0)
    m.SharedScript.prototype.init = function(state, deserialization) 
    179181    this.accessibility = new m.Accessibility();
    180182    this.accessibility.init(state, this.terrainAnalyzer);
    181183   
    182     // defined in TerrainAnalysis.js
     184    // By how much to divide the resource amount when filling the map (ie a tree having 200 wood is "4").
     185    for (let res in state.aiResourceAnalysis)
     186    {
     187        if (!state.aiResourceAnalysis[res])
     188            continue;
     189        this.decreaseFactor[res] = state.aiResourceAnalysis[res].decreaseFactor;
     190        this.influenceMapGroup[res] = state.aiResourceAnalysis[res].influenceMapGroup;
     191    }
     192    // defined in terrain-analysis.js
    183193    this.createResourceMaps(this);
    184194
    185195    this.gameState = {};
  • binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js

    diff --git a/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js b/binaries/data/mods/public/simulation/ai/common-api/terrain-analysis.js
    index 68d8445..566bbbc 100644
    a b m.Accessibility.prototype.floodFill = function(startIndex, value, onWater) 
    382382
    383383// creates a map of resource density
    384384m.SharedScript.prototype.createResourceMaps = function(sharedScript)
    385 {   
     385{
    386386    for (let resource in this.decreaseFactor)
    387387    {
    388388        // if there is no resourceMap create one with an influence for everything with that resource
    m.SharedScript.prototype.createResourceMaps = function(sharedScript) 
    394394            this.CCResourceMaps[resource] = new m.Map(sharedScript, "resource");
    395395        }
    396396    }
    397     let cellSize = this.resourceMaps.wood.cellSize;
     397
    398398    for (let ent of sharedScript._entities.values())
    399399    {
    400400        if (ent && ent.position() && ent.resourceSupplyType() && ent.resourceSupplyType().generic !== "treasure") {
    401401            let resource = ent.resourceSupplyType().generic;
    402402            if (!this.resourceMaps[resource])
    403403                continue;
     404
     405            let cellSize = this.resourceMaps[resource].cellSize;
    404406            let x = Math.floor(ent.position()[0] / cellSize);
    405407            let z = Math.floor(ent.position()[1] / cellSize);
    406408            let strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
    407             if (resource === "wood")
     409            if (this.influenceMapGroup[resource] === 0)
    408410            {
    409411                this.CCResourceMaps[resource].addInfluence(x, z, 60/cellSize, strength, "constant");
    410412                this.resourceMaps[resource].addInfluence(x, z, 36/cellSize, strength/2, "constant");
    411413                this.resourceMaps[resource].addInfluence(x, z, 36/cellSize, strength/2);
    412414            }
    413             else if (resource === "stone" || resource === "metal")
     415            else if (this.influenceMapGroup[resource] === 1)
    414416            {
    415417                this.CCResourceMaps[resource].addInfluence(x, z, 120/cellSize, strength, "constant");
    416418                this.resourceMaps[resource].addInfluence(x, z, 48/cellSize, strength/2, "constant");
    m.SharedScript.prototype.updateResourceMaps = function(sharedScript, events) 
    438440            this.CCResourceMaps[resource] = new m.Map(sharedScript, "resource");
    439441        }
    440442    }
    441     let cellSize = this.resourceMaps.wood.cellSize;
     443
    442444    // Look for destroy events and subtract the entities original influence from the resourceMap
    443445    // TODO: perhaps do something when dropsites appear/disappear.
    444446    let destEvents = events.Destroy;
    m.SharedScript.prototype.updateResourceMaps = function(sharedScript, events) 
    454456            let resource = ent.resourceSupplyType().generic;
    455457            if (!this.resourceMaps[resource])
    456458                continue;
     459
     460            let cellSize = this.resourceMaps[resource].cellSize;
    457461            let x = Math.floor(ent.position()[0] / cellSize);
    458462            let z = Math.floor(ent.position()[1] / cellSize);
    459463            let strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
    460             if (resource === "wood")
     464            if (this.influenceMapGroup[resource] === 0)
    461465            {
    462466                this.CCResourceMaps[resource].addInfluence(x, z, 60/cellSize, -strength, "constant");
    463467                this.resourceMaps[resource].addInfluence(x, z, 36/cellSize, -strength/2, "constant");
    464468                this.resourceMaps[resource].addInfluence(x, z, 36/cellSize, -strength/2);
    465469            }
    466             else if (resource === "stone" || resource === "metal")
     470            else if (this.influenceMapGroup[resource] === 1)
    467471            {
    468472                this.CCResourceMaps[resource].addInfluence(x, z, 120/cellSize, -strength, "constant");
    469473                this.resourceMaps[resource].addInfluence(x, z, 48/cellSize, -strength/2, "constant");
    m.SharedScript.prototype.updateResourceMaps = function(sharedScript, events) 
    481485            let resource = ent.resourceSupplyType().generic;
    482486            if (!this.resourceMaps[resource])
    483487                continue;
     488
     489            let cellSize = this.resourceMaps[resource].cellSize;
    484490            let x = Math.floor(ent.position()[0] / cellSize);
    485491            let z = Math.floor(ent.position()[1] / cellSize);
    486492            let strength = Math.floor(ent.resourceSupplyMax()/this.decreaseFactor[resource]);
    487             if (resource === "wood")
     493            if (this.influenceMapGroup[resource] === 0)
    488494            {
    489495                this.CCResourceMaps[resource].addInfluence(x, z, 60/cellSize, strength, "constant");
    490496                this.resourceMaps[resource].addInfluence(x, z, 36/cellSize, strength/2, "constant");
    491497                this.resourceMaps[resource].addInfluence(x, z, 36/cellSize, strength/2);
    492498            }
    493             else if (resource === "stone" || resource === "metal")
     499            else if (this.influenceMapGroup[resource] === 1)
    494500            {
    495501                this.CCResourceMaps[resource].addInfluence(x, z, 120/cellSize, strength, "constant");
    496502                this.resourceMaps[resource].addInfluence(x, z, 48/cellSize, strength/2, "constant");
  • binaries/data/mods/public/simulation/components/Barter.js

    diff --git a/binaries/data/mods/public/simulation/components/Barter.js b/binaries/data/mods/public/simulation/components/Barter.js
    index 24c39a4..106d1e3 100644
    a b  
    1 // True price of 100 units of resource (for case if some resource is more worth).
     1// The "true price" is a base price of 100 units of resource (for the case of some resources being of more worth than others).
    22// With current bartering system only relative values makes sense
    33// so if for example stone is two times more expensive than wood,
    44// there will 2:1 exchange rate.
    5 const TRUE_PRICES = { "food": 100, "wood": 100, "stone": 100, "metal": 100 };
    6 
     5//
    76// Constant part of price difference between true price and buy/sell price.
    87// In percents.
    98// Buy price equal to true price plus constant difference.
    const DIFFERENCE_RESTORE = 0.5; 
    2120// Interval of timer which slowly restore prices after deals
    2221const RESTORE_TIMER_INTERVAL = 5000;
    2322
    24 // Array of resource names
    25 const RESOURCES = ["food", "wood", "stone", "metal"];
    26 
    2723function Barter() {}
    2824
    2925Barter.prototype.Schema =
    Barter.prototype.Schema = 
    3228Barter.prototype.Init = function()
    3329{
    3430    this.priceDifferences = {};
    35     for (var resource of RESOURCES)
     31    for (let resource of Resources.GetCodes())
    3632        this.priceDifferences[resource] = 0;
    3733    this.restoreTimer = undefined;
    3834};
    Barter.prototype.Init = function() 
    4036Barter.prototype.GetPrices = function()
    4137{
    4238    var prices = { "buy": {}, "sell": {} };
    43     for (var resource of RESOURCES)
     39    for (let resource of Resources.GetCodes())
    4440    {
    45         prices["buy"][resource] = TRUE_PRICES[resource] * (100 + CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
    46         prices["sell"][resource] = TRUE_PRICES[resource] * (100 - CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
     41        let truePrice = Resources.GetResource(resource).truePrice;
     42        prices.buy[resource] = truePrice * (100 + CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
     43        prices.sell[resource] = truePrice * (100 - CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
    4744    }
    4845    return prices;
    4946};
    Barter.prototype.ExchangeResources = function(playerEntity, resourceToSell, reso 
    7168        warn("ExchangeResources: incorrect amount: " + uneval(amount));
    7269        return;
    7370    }
    74     if (RESOURCES.indexOf(resourceToSell) == -1)
     71    let availResources = Resources.GetCodes();
     72    if (availResources.indexOf(resourceToSell) == -1)
    7573    {
    7674        warn("ExchangeResources: incorrect resource to sell: " + uneval(resourceToSell));
    7775        return;
    7876    }
    79     if (RESOURCES.indexOf(resourceToBuy) == -1)
     77    if (availResources.indexOf(resourceToBuy) == -1)
    8078    {
    8179        warn("ExchangeResources: incorrect resource to buy: " + uneval(resourceToBuy));
    8280        return;
    Barter.prototype.ExchangeResources = function(playerEntity, resourceToSell, reso 
    123121Barter.prototype.ProgressTimeout = function(data)
    124122{
    125123    var needRestore = false;
    126     for (var resource of RESOURCES)
     124    for (let resource of Resources.GetCodes())
    127125    {
    128126        // Calculate value to restore, it should be limited to [-DIFFERENCE_RESTORE; DIFFERENCE_RESTORE] interval
    129127        var differenceRestore = Math.min(DIFFERENCE_RESTORE, Math.max(-DIFFERENCE_RESTORE, this.priceDifferences[resource]));
  • binaries/data/mods/public/simulation/components/Cost.js

    diff --git a/binaries/data/mods/public/simulation/components/Cost.js b/binaries/data/mods/public/simulation/components/Cost.js
    index e5660ee..a9ae125 100644
    a b  
    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>" +
    Cost.prototype.Schema = 
    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()
    Cost.prototype.GetResourceCosts = function(owner) 
    7067    let entityTemplate = cmpTemplateManager.GetTemplate(entityTemplateName);
    7168
    7269    let costs = {};
     70    let resCodes = Resources.GetCodes();
     71
    7372    for (let r in this.template.Resources)
    74         costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +this.template.Resources[r], owner, entityTemplate);
     73    {
     74        let cost = +this.template.Resources[r];
     75        if (resCodes.indexOf(r.toLowerCase()) < 0)
     76        {
     77            if (cost > 0)
     78                warn("'"+r+"' has been specified as a required resource, but is not a valid resource.");
     79            continue;
     80        }
     81        costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, cost, owner, entityTemplate);
     82    }
     83
    7584    return costs;
    7685};
    7786
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
    index ffb28e3..d03acd2 100644
    a b GuiInterface.prototype.GetSimulationState = function() 
    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    for (let res of Resources.GetData())
     157        ret.resources[res.code] = res.name;
     158    ret.aiResourceAnalysis = {};
     159    for (let res of Resources.GetCodes())
     160        ret.aiResourceAnalysis[res] = Resources.GetResource(res).aiAnalysis || null;
     161
    154162    // Add basic statistics to each player
    155163    for (let i = 0; i < numPlayers; ++i)
    156164    {
    GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) 
    12461254
    12471255    let result = {
    12481256        "pieces": [],
    1249         "cost": { "food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0 },
     1257        "cost": {"population": 0, "populationBonus": 0, "time": 0},
    12501258    };
    1251 
     1259    for (let res of Resources.GetCodes())
     1260        result.cost[res] = 0;
     1261   
    12521262    let previewEntities = [];
    12531263    if (end.pos)
    12541264        previewEntities = GetWallPlacement(this.placementWallEntities, wallSet, start, end); // see helpers/Walls.js
    GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) 
    15221532            // copied over, so we need to fetch it from the template instead).
    15231533            // TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
    15241534            // boilerplate that's probably duplicated in tons of places.
    1525             result.cost.food += tplData.cost.food;
    1526             result.cost.wood += tplData.cost.wood;
    1527             result.cost.stone += tplData.cost.stone;
    1528             result.cost.metal += tplData.cost.metal;
    1529             result.cost.population += tplData.cost.population;
    1530             result.cost.populationBonus += tplData.cost.populationBonus;
    1531             result.cost.time += tplData.cost.time;
     1535            let entries = Resources.GetCodes().concat("population", "populationBonus", "time");
     1536            for (let res of entries)
     1537                result.cost[res] = tplData.cost[res];
    15321538        }
    15331539
    15341540        let canAfford = true;
  • binaries/data/mods/public/simulation/components/Loot.js

    diff --git a/binaries/data/mods/public/simulation/components/Loot.js b/binaries/data/mods/public/simulation/components/Loot.js
    index 3161340..772fb05 100644
    a b  
    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
    Loot.prototype.GetXp = function() 
    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

    diff --git a/binaries/data/mods/public/simulation/components/Player.js b/binaries/data/mods/public/simulation/components/Player.js
    index c097d27..31bf5ae 100644
    a b Player.prototype.Init = function() 
    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"
    Player.prototype.Init = function() 
    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)
    Player.prototype.UnBlockTraining = function() 
    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    {
     201        if (this.resourceCount[res] === undefined)
     202            warn("The "+res+" resourcehas been passed to cmpPlayer, but does not exist/has been disabled by mod");
     203        this.resourceCount[res] = resources[res];
     204    }
    208205};
    209206
    210207Player.prototype.GetResourceCounts = function()
    Player.prototype.SubtractResourcesOrNotify = function(amounts) 
    297294
    298295    // Subtract the resources
    299296    for (var type in amounts)
    300         this.resourceCount[type] -= amounts[type];
     297        if (this.resourceCount[type])
     298            this.resourceCount[type] -= amounts[type];
    301299
    302300    return true;
    303301};
    Player.prototype.SetTradingGoods = function(tradingGoods) 
    346344    if (sumProba != 100)    // consistency check
    347345    {
    348346        error("Player.js SetTradingGoods: " + uneval(tradingGoods));
    349         tradingGoods = { "food": 20, "wood":20, "stone":30, "metal":30 };
     347        let first = true;
     348        for (let res of Resources.GetCodes())
     349            if (first)
     350            {
     351                tradingGoods[res] = 100;
     352                first = false;
     353            }
     354            else
     355                tradingGoods[res] = 0;
    350356    }
    351357
    352358    this.tradingGoods = [];
  • binaries/data/mods/public/simulation/components/ProductionQueue.js

    diff --git a/binaries/data/mods/public/simulation/components/ProductionQueue.js b/binaries/data/mods/public/simulation/components/ProductionQueue.js
    index 185bdb6..8077473 100644
    a b const 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>" +
    ProductionQueue.prototype.Schema = 
    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()
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    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    {
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    295292
    296293            for (var r in template.Cost.Resources)
    297294            {
    298                 costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +template.Cost.Resources[r], cmpPlayer.GetPlayerID(), template);
     295                let cost = +template.Cost.Resources[r];
     296                if (resCodes.indexOf(r) < 0)
     297                {
     298                    if (cost > 0)
     299                        warn("'"+r+"' has been specified as a required resource, but is not a valid resource.");
     300                    continue;
     301                }
     302                costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, cost, cmpPlayer.GetPlayerID(), template);
    299303                totalCosts[r] = Math.floor(count * costs[r]);
    300304            }
    301305
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    341345            let techCostMultiplier = this.GetTechCostMultiplier();
    342346            let time =  techCostMultiplier.time * template.researchTime * cmpPlayer.GetCheatTimeMultiplier();
    343347
    344             var cost = {};
    345             for (let res in template.cost)
    346                 cost[res] = Math.floor(techCostMultiplier[res] * template.cost[res]);
     348            let cost = {};
     349            for (let r in template.cost)
     350            {
     351                if (resCodes.indexOf(r) < 0)
     352                {
     353                    if (Math.floor(template.cost[r]) > 0)
     354                        warn("'"+r+"' has been specified as a required resource, but is not a valid resource.");
     355                    continue;
     356                }
     357                cost[r] = Math.floor((techCostMultiplier[r] ? techCostMultiplier[r] : 1) * template.cost[r]);;
     358            }
    347359
    348360            // TrySubtractResources should report error to player (they ran out of resources)
    349361            if (!cmpPlayer.TrySubtractResources(cost))
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    361373                "player": cmpPlayer.GetPlayerID(),
    362374                "count": 1,
    363375                "technologyTemplate": templateName,
    364                 "resources": deepcopy(template.cost), // need to copy to avoid serialization problems
     376                "resources": cost,
    365377                "productionStarted": false,
    366378                "timeTotal": time*1000,
    367379                "timeRemaining": time*1000,
    ProductionQueue.prototype.RemoveBatch = function(id) 
    433445        // Refund the resource cost for this batch
    434446        var totalCosts = {};
    435447        var cmpStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker);
    436         for each (var r in ["food", "wood", "stone", "metal"])
     448        for (let r of Resources.GetCodes())
    437449        {
     450            if (!item.resources[r])
     451                continue;
    438452            totalCosts[r] = Math.floor(item.count * item.resources[r]);
    439453            if (cmpStatisticsTracker)
    440454                cmpStatisticsTracker.IncreaseResourceUsedCounter(r, -totalCosts[r]);
  • binaries/data/mods/public/simulation/components/ResourceDropsite.js

    diff --git a/binaries/data/mods/public/simulation/components/ResourceDropsite.js b/binaries/data/mods/public/simulation/components/ResourceDropsite.js
    index 44045c0..f192a4b 100644
    a b  
    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>" +
    ResourceDropsite.prototype.Init = function() 
    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/**
  • binaries/data/mods/public/simulation/components/ResourceGatherer.js

    diff --git a/binaries/data/mods/public/simulation/components/ResourceGatherer.js b/binaries/data/mods/public/simulation/components/ResourceGatherer.js
    index acd5fbd..eed10e0 100644
    a b  
    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>" +
    ResourceGatherer.prototype.Schema = 
    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()
    ResourceGatherer.prototype.RecalculateGatherRatesAndCapacities = function() 
    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    }
    ResourceGatherer.prototype.GetRange = function() 
    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{
  • binaries/data/mods/public/simulation/components/ResourceSupply.js

    diff --git a/binaries/data/mods/public/simulation/components/ResourceSupply.js b/binaries/data/mods/public/simulation/components/ResourceSupply.js
    index 04e95da..7cc580b 100644
    a b  
    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>" +
    ResourceSupply.prototype.Schema = 
    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'/>" +
    ResourceSupply.prototype.Init = function() 
    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()
  • binaries/data/mods/public/simulation/components/ResourceTrickle.js

    diff --git a/binaries/data/mods/public/simulation/components/ResourceTrickle.js b/binaries/data/mods/public/simulation/components/ResourceTrickle.js
    index 5c554e7..7bed918 100644
    a b  
    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'/>" +
    ResourceTrickle.prototype.GetTimer = function() 
    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};
    ResourceTrickle.prototype.GetRates = function() 
    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

    diff --git a/binaries/data/mods/public/simulation/components/StatisticsTracker.js b/binaries/data/mods/public/simulation/components/StatisticsTracker.js
    index a5a060d..c27c9ed 100644
    a b StatisticsTracker.prototype.Init = function() 
    9494    this.enemyBuildingsDestroyedValue = 0;
    9595
    9696    this.resourcesGathered = {
    97         "food": 0,
    98         "wood": 0,
    99         "metal": 0,
    100         "stone": 0,
    10197        "vegetarianFood": 0
    10298    };
    103     this.resourcesUsed = {
    104         "food": 0,
    105         "wood": 0,
    106         "metal": 0,
    107         "stone": 0
    108     };
    109     this.resourcesSold = {
    110         "food": 0,
    111         "wood": 0,
    112         "metal": 0,
    113         "stone": 0
    114     };
    115     this.resourcesBought = {
    116         "food": 0,
    117         "wood": 0,
    118         "metal": 0,
    119         "stone": 0
    120     };
     99    this.resourcesUsed = {};
     100    this.resourcesSold = {};
     101    this.resourcesBought = {};
     102    for (let res of Resources.GetCodes())
     103    {
     104        this.resourcesGathered[res] = 0;
     105        this.resourcesUsed[res] = 0;
     106        this.resourcesSold[res] = 0;
     107        this.resourcesBought[res] = 0;
     108    }
    121109
    122110    this.tributesSent = 0;
    123111    this.tributesReceived = 0;
    StatisticsTracker.prototype.IncreaseResourceGatheredCounter = function(type, amo 
    314302 */
    315303StatisticsTracker.prototype.IncreaseResourceUsedCounter = function(type, amount)
    316304{
    317     this.resourcesUsed[type] += amount;
     305    if (typeof this.resourcesUsed[type] === "number")
     306        this.resourcesUsed[type] += amount;
    318307};
    319308
    320309StatisticsTracker.prototype.IncreaseTreasuresCollectedCounter = function()
  • binaries/data/mods/public/simulation/components/Trader.js

    diff --git a/binaries/data/mods/public/simulation/components/Trader.js b/binaries/data/mods/public/simulation/components/Trader.js
    index f80096a..1faec9b 100644
    a b  
    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 =
    Trader.prototype.GetRequiredGoods = function() 
    149146Trader.prototype.SetRequiredGoods = function(requiredGoods)
    150147{
    151148    // Check that argument is a correct resource name
    152     if (!requiredGoods || RESOURCES.indexOf(requiredGoods) == -1)
     149    if (!requiredGoods || Resources.GetCodes().indexOf(requiredGoods) == -1)
    153150        this.requiredGoods = undefined;
    154151    else
    155152        this.requiredGoods = requiredGoods;
    Trader.prototype.PerformTrade = function(currentMarket) 
    229226    // otherwise choose one according to the player's trading priorities
    230227    // if still nothing (but should never happen), choose metal
    231228    // and recomputes the gain in case it has changed (for example by technology)
    232     var nextGoods = this.GetRequiredGoods();
    233     if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
     229    let nextGoods = this.GetRequiredGoods();
     230    let resCodes = Resources.GetCodes();
     231    if (!nextGoods || resCodes.indexOf(nextGoods) == -1)
    234232    {
    235233        let cmpPlayer = QueryOwnerInterface(this.entity);
    236234        if (cmpPlayer)
    237235            nextGoods = cmpPlayer.GetNextTradingGoods();
    238236
    239         if (!nextGoods || RESOURCES.indexOf(nextGoods) == -1)
     237        if (!nextGoods || resCodes.indexOf(nextGoods) == -1)
    240238            nextGoods = "metal";
    241239    }
    242240    this.goods.type = nextGoods;
  • new file inaries/data/mods/public/simulation/data/resources/food.json

    diff --git a/binaries/data/mods/public/simulation/data/resources/food.json b/binaries/data/mods/public/simulation/data/resources/food.json
    new file mode 100644
    index 0000000..f9a1e00
    - +  
     1{
     2    "code": "food",
     3    "name": "Food",
     4    "subtypes": [
     5        "fish",
     6        "fruit",
     7        "grain",
     8        "meat",
     9        "milk"
     10    ],
     11    "truePrice": 100,
     12    "enabled": true
     13}
  • new file inaries/data/mods/public/simulation/data/resources/metal.json

    diff --git a/binaries/data/mods/public/simulation/data/resources/metal.json b/binaries/data/mods/public/simulation/data/resources/metal.json
    new file mode 100644
    index 0000000..44fd856
    - +  
     1{
     2    "code": "metal",
     3    "name": "Metal",
     4    "subtypes": [
     5        "ore"
     6    ],
     7    "truePrice": 100,
     8    "aiAnalysis": {
     9        "decreaseFactor": 90.0,
     10        "influenceMapGroup": 1
     11    },
     12    "enabled": true
     13}
  • new file inaries/data/mods/public/simulation/data/resources/stone.json

    diff --git a/binaries/data/mods/public/simulation/data/resources/stone.json b/binaries/data/mods/public/simulation/data/resources/stone.json
    new file mode 100644
    index 0000000..d86be0e
    - +  
     1{
     2    "code": "stone",
     3    "name": "Stone",
     4    "subtypes": [
     5        "rock",
     6        "ruins"
     7    ],
     8    "truePrice": 100,
     9    "aiAnalysis": {
     10        "decreaseFactor": 90.0,
     11        "influenceMapGroup": 1
     12    },
     13    "enabled": true
     14}
  • new file inaries/data/mods/public/simulation/data/resources/wood.json

    diff --git a/binaries/data/mods/public/simulation/data/resources/wood.json b/binaries/data/mods/public/simulation/data/resources/wood.json
    new file mode 100644
    index 0000000..ec9b63d
    - +  
     1{
     2    "code": "wood",
     3    "name": "Wood",
     4    "subtypes": [
     5        "tree",
     6        "ruins"
     7    ],
     8    "truePrice": 100,
     9    "aiAnalysis": {
     10        "decreaseFactor": 50.0,
     11        "influenceMapGroup": 0
     12    },
     13    "enabled": true
     14}
  • new file inaries/data/mods/public/simulation/helpers/Resources.js

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