Ticket #3934: resource_agnostic-v8.patch

File resource_agnostic-v8.patch, 121.8 KB (added by s0600204, 3 years ago)

Rebased, and shifting of barter icons to the trade page

  • new file inaries/data/mods/public/globalscripts/Resources.js

    diff --git a/binaries/data/mods/public/globalscripts/Resources.js b/binaries/data/mods/public/globalscripts/Resources.js
    new file mode 100644
    index 0000000..6287897
    - +  
     1/**
     2 * Since the AI context can't access JSON functions, it gets passed an object
     3 * containing the information from `GuiInterface.js::GetSimulationState()`.
     4 */
     5function Resources()
     6{
     7    let jsonFiles = [];
     8    // Simulation context
     9    if (Engine.FindJSONFiles)
     10    {
     11        jsonFiles = Engine.FindJSONFiles("resources", false);
     12        for (let file in jsonFiles)
     13            jsonFiles[file] = "resources/" + jsonFiles[file] + ".json";
     14    }
     15    // GUI context
     16    else if (Engine.BuildDirEntList)
     17        jsonFiles = Engine.BuildDirEntList("simulation/data/resources/", "*.json", false);
     18    else
     19    {
     20        error("Resources: JSON functions are not available");
     21        return;
     22    }
     23
     24    this.resourceData = [];
     25    this.resourceCodes = [];
     26
     27    for (let filename of jsonFiles)
     28    {
     29        let data = Engine.ReadJSONFile(filename);
     30        if (!data)
     31            continue;
     32
     33        this.resourceData.push(data);
     34        if (data.enabled)
     35            this.resourceCodes.push(data.code);
     36    }
     37
     38    let resSort = (a, b) =>
     39        a.order < b.order ? -1 :
     40        a.order > b.order ? +1 : 0;
     41
     42    this.resourceData.sort(resSort);
     43    this.resourceCodes.sort((a, b) => resSort(
     44        this.resourceData.find(resource => resource.code == a),
     45        this.resourceData.find(resource => resource.code == b)
     46    ));
     47};
     48
     49Resources.prototype.GetData = function()
     50{
     51    return this.resourceData.filter(resource => resource.enabled);
     52};
     53
     54Resources.prototype.GetResource = function(type)
     55{
     56    let lType = type.toLowerCase();
     57    return this.GetData().find(resource => resource.code == lType);
     58};
     59
     60Resources.prototype.GetCodes = function()
     61{
     62    return this.resourceCodes;
     63};
     64
     65/**
     66 * Returns an object containing untranslated resource names mapped to
     67 * resource codes. Includes subtypes.
     68 */
     69Resources.prototype.GetNames = function()
     70{
     71    let names = {};
     72    for (let res of this.GetData())
     73    {
     74        names[res.code] = res.name;
     75        for (let subres in res.subtypes)
     76            names[subres] = res.subtypes[subres]
     77    }
     78    return names;
     79};
  • binaries/data/mods/public/gui/common/functions_utility.js

    diff --git a/binaries/data/mods/public/gui/common/functions_utility.js b/binaries/data/mods/public/gui/common/functions_utility.js
    index 12380bb..9f99b2c 100644
    a b function notifyUser(userName, msgText) 
    245245
    246246    g_LastNickNotification = timeNow;
    247247}
     248
     249/**
     250 * Horizontally spaces objects within a parent
     251 *
     252 * @param margin The gap, in px, between the objects
     253 */
     254function horizontallySpaceObjects(parentName, margin=0)
     255{
     256    let objects = Engine.GetGUIObjectByName(parentName).children;
     257    for (let i = 0; i < objects.length; ++i)
     258    {
     259        let size = objects[i].size;
     260        let width = size.right - size.left;
     261        size.left = i * (width + margin) + margin;
     262        size.right = (i + 1) * (width + margin);
     263        objects[i].size = size;
     264    }
     265}
     266
     267/**
     268 * Hide all children after a certain index
     269 *
     270 * @param idx - The index from which to start
     271 */
     272function hideRemaining(parentName, start = 0)
     273{
     274    let objects = Engine.GetGUIObjectByName(parentName).children;
     275
     276    for (let i = start; i < objects.length; ++i)
     277        objects[i].hidden = true;
     278}
  • 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/tooltips.js

    diff --git a/binaries/data/mods/public/gui/common/tooltips.js b/binaries/data/mods/public/gui/common/tooltips.js
    index 6cebc6d..dcca7b6 100644
    a b function getEntityCostComponentsTooltipString(template, trainNum, entity) 
    339339
    340340    return costs;
    341341}
     342
    342343function getGatherTooltip(template)
    343344{
    344345    if (!template.gather)
  • 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 1890513..b03a570 100644
    a b  
    11<?xml version="1.0" encoding="utf-8"?>
    22
    33<object name="diplomacyDialogPanel"
    4     size="50%-300 50%-200 50%+300 50%+150"
     4    size="50%-260 50%-200 50%+260 50%+150"
    55    type="image"
    66    hidden="true"
    77    sprite="ModernDialog"
     
    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
    5555                <!-- 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"/>
     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 83abfc8..c42d1bb 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
    3128// Shown in the trade dialog.
    3229const g_IdleTraderTextColor = "orange";
    3330
     31const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
     32const BARTER_BUNCH_MULTIPLIER = 5;
     33const BARTER_ACTIONS = ["Sell", "Buy"];
     34var g_BarterSell;
     35
    3436var g_IsMenuOpen = false;
    3537
    3638var g_IsDiplomacyOpen = false;
    function closeChat() 
    228230    Engine.GetGUIObjectByName("chatDialogPanel").hidden = true;
    229231}
    230232
     233function resizeDiplomacyDialog()
     234{
     235    let dialog = Engine.GetGUIObjectByName("diplomacyDialogPanel");
     236    let size = dialog.size;
     237    let width = size.right - size.left;
     238
     239    let tribSize = Engine.GetGUIObjectByName("diplomacyPlayer[0]_tribute[0]").size;
     240    width += g_ResourceData.GetCodes().length * (tribSize.right - tribSize.left);
     241
     242    size.left = -width / 2;
     243    size.right = width / 2;
     244    dialog.size = size;
     245}
     246
    231247function openDiplomacy()
    232248{
    233249    closeOpenDialogs();
    function openDiplomacy() 
    255271        diplomacyFormatTributeButtons(i, myself || playerInactive);
    256272        diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]);
    257273    }
    258 
    259274    Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
    260275}
    261276
    function diplomacyFormatStanceButtons(i, hidden) 
    306321
    307322function diplomacyFormatTributeButtons(i, hidden)
    308323{
    309     for (let resource of RESOURCES)
     324    let resNames = g_ResourceData.GetNames();
     325    let resCodes = g_ResourceData.GetCodes();
     326    let r = 0;
     327    for (let resCode of resCodes)
    310328    {
    311         let button = Engine.GetGUIObjectByName("diplomacyPlayerTribute"+resource[0].toUpperCase()+resource.substring(1)+"["+(i-1)+"]");
     329        let button = Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]");
     330        if (!button)
     331            break;
     332        Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]_image").sprite = "stretched:session/icons/resources/"+resCode+".png";
    312333        button.hidden = hidden;
     334        setPanelObjectPosition(button, r, 8, 0);
     335        ++r;
    313336        if (hidden)
    314337            continue;
    315338
    316339        button.enabled = controlsPlayer(g_ViewedPlayer);
    317         button.tooltip = formatTributeTooltip(i, resource, 100);
    318         button.onpress = (function(i, resource, button) {
     340        button.tooltip = formatTributeTooltip(i, resNames[resCode], 100);
     341        button.onPress = (function(i, resCode, button) {
    319342            // Shift+click to send 500, shift+click+click to send 1000, etc.
    320343            // See INPUT_MASSTRIBUTING in input.js
    321344            let multiplier = 1;
    function diplomacyFormatTributeButtons(i, hidden) 
    328351                }
    329352
    330353                let amounts = {};
    331                 for (let type of RESOURCES)
    332                     amounts[type] = 0;
    333                 amounts[resource] = 100 * multiplier;
     354                for (let res of resCodes)
     355                    amounts[res] = 0;
     356                amounts[resCode] = 100 * multiplier;
    334357
    335                 button.tooltip = formatTributeTooltip(i, resource, amounts[resource]);
     358                button.tooltip = formatTributeTooltip(i, resNames[resCode], amounts[resCode]);
    336359
    337360                // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some
    338361                // evil global variable hackery.
    339362                g_FlushTributing = function() {
    340363                    Engine.PostNetworkCommand({ "type": "tribute", "player": i, "amounts":  amounts });
    341364                    multiplier = 1;
    342                     button.tooltip = formatTributeTooltip(i, resource, 100);
     365                    button.tooltip = formatTributeTooltip(i, resNames[resCode], 100);
    343366                };
    344367
    345368                if (!isBatchTrainPressed)
    346369                    g_FlushTributing();
    347370            };
    348         })(i, resource, button);
     371        })(i, resCode, button);
    349372    }
    350373}
    351374
    function toggleDiplomacy() 
    378401        openDiplomacy();
    379402}
    380403
     404function resizeTradeDialog()
     405{
     406    let dialog = Engine.GetGUIObjectByName("tradeDialogPanel");
     407    let size = dialog.size;
     408    let width = size.right - size.left;
     409
     410    let tradeSize = Engine.GetGUIObjectByName("tradeResource[0]").size;
     411    width += g_ResourceData.GetCodes().length * (tradeSize.right - tradeSize.left);
     412
     413    size.left = -width / 2;
     414    size.right = width / 2;
     415    dialog.size = size;
     416}
     417
    381418function openTrade()
    382419{
    383420    closeOpenDialogs();
    function openTrade() 
    387424
    388425    g_IsTradeOpen = true;
    389426
    390     var updateButtons = function()
     427    let updateTradeButtons = function()
    391428    {
    392         for (var res in button)
     429        for (let res in button)
    393430        {
    394431            button[res].label.caption = proba[res] + "%";
    395432
    function openTrade() 
    399436        }
    400437    };
    401438
    402     var proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
    403     var button = {};
    404     var selec = RESOURCES[0];
    405     for (var i = 0; i < RESOURCES.length; ++i)
     439    let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
     440    let button = {};
     441    let resCodes = g_ResourceData.GetCodes();
     442    let selec = resCodes[0];
     443    hideRemaining("tradeResources", resCodes.length);
     444    Engine.GetGUIObjectByName("tradeHelp").hidden = false;
     445
     446    let maxTradeResources = Engine.GetGUIObjectByName("tradeResources").children.length;
     447    let maxBarterResources = Engine.GetGUIObjectByName("barterResources").children.length;
     448
     449    for (let i = 0; i < resCodes.length; ++i)
    406450    {
    407         var buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
    408         if (i > 0)
    409         {
    410             var size = Engine.GetGUIObjectByName("tradeResource["+(i-1)+"]").size;
    411             var width = size.right - size.left;
    412             size.left += width;
    413             size.right += width;
    414             Engine.GetGUIObjectByName("tradeResource["+i+"]").size = size;
    415         }
    416         var resource = RESOURCES[i];
    417         proba[resource] = (proba[resource] ? proba[resource] : 0);
    418         var buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
    419         var icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
    420         icon.sprite = "stretched:session/icons/resources/" + resource + ".png";
    421         var label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]");
    422         var buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
    423         var buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
    424         var iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
    425         button[resource] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
     451        let resCode = resCodes[i];
     452
     453        // Barter
     454        let barterButton = {};
     455        for (let a of ["Buy", "Sell"])
     456            barterButton[a] = Engine.GetGUIObjectByName("barter" + a + "Button[" + i + "]");
     457
     458        if (!g_BarterSell)
     459            g_BarterSell = g_ResourceData.GetCodes()[0];
     460
     461        let resource = getLocalizedResourceName(g_ResourceData.GetNames()[resCode], "withinSentence");
     462        barterButton.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource });
     463        barterButton.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource });
     464
     465        barterButton.Sell.onPress = function() {
     466            g_BarterSell = resCode;
     467        };
     468
     469        setPanelObjectPosition(Engine.GetGUIObjectByName("barterResource[" + i + "]"), i, maxBarterResources);
     470
     471        // Trade
     472        let tradeResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
     473        if (!tradeResource)
     474            break;
     475
     476        setPanelObjectPosition(tradeResource, i, maxTradeResources);
     477
     478        let icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
     479        icon.sprite = "stretched:session/icons/resources/" + resCode + ".png";
    426480
     481        let buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
     482        let buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
     483
     484        button[resCode] = {
     485            "up": buttonUp,
     486            "dn": buttonDn,
     487            "label": Engine.GetGUIObjectByName("tradeResourceText["+i+"]"),
     488            "sel": Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]")
     489        };
     490
     491        proba[resCode] = proba[resCode] || 0;
     492
     493        let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
    427494        buttonResource.enabled = controlsPlayer(g_ViewedPlayer);
    428495        buttonResource.onpress = (function(resource){
    429496            return function() {
    430497                if (Engine.HotkeyIsPressed("session.fulltradeswap"))
    431498                {
    432                     for (var ress of RESOURCES)
    433                         proba[ress] = 0;
     499                    for (let res of resCodes)
     500                        proba[res] = 0;
    434501                    proba[resource] = 100;
    435502                    Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    436503                }
    437504                selec = resource;
    438                 updateButtons();
     505                updateTradeButtons();
    439506            };
    440         })(resource);
     507        })(resCode);
    441508
    442509        buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
    443510        buttonUp.onpress = (function(resource){
    function openTrade() 
    445512                proba[resource] += Math.min(STEP, proba[selec]);
    446513                proba[selec]    -= Math.min(STEP, proba[selec]);
    447514                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    448                 updateButtons();
     515                updateTradeButtons();
    449516            };
    450         })(resource);
     517        })(resCode);
    451518
    452519        buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
    453520        buttonDn.onpress = (function(resource){
    function openTrade() 
    455522                proba[selec]    += Math.min(STEP, proba[resource]);
    456523                proba[resource] -= Math.min(STEP, proba[resource]);
    457524                Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
    458                 updateButtons();
     525                updateTradeButtons();
    459526            };
    460         })(resource);
     527        })(resCode);
    461528    }
    462     updateButtons();
     529    updateTradeButtons();
    463530
    464531    let traderNumber = Engine.GuiInterfaceCall("GetTraderNumber", g_ViewedPlayer);
    465     Engine.GetGUIObjectByName("landTraders").caption = getIdleLandTradersText(traderNumber);
    466     Engine.GetGUIObjectByName("shipTraders").caption = getIdleShipTradersText(traderNumber);
    467 
     532    Engine.GetGUIObjectByName("traders").caption = getIdleLandTradersText(traderNumber) + "\n\n" + getIdleShipTradersText(traderNumber);
    468533    Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false;
    469534}
    470535
     536function updateBarterButtons()
     537{
     538    let resCodes = g_ResourceData.GetCodes();
     539    if (!g_BarterSell)
     540        g_BarterSell = resCodes[0];
     541
     542    let canBarter = Engine.GuiInterfaceCall("PlayerCanBarter", g_ViewedPlayer);
     543    Engine.GetGUIObjectByName("barterNoMarketsMessage").hidden = canBarter;
     544    Engine.GetGUIObjectByName("barterResources").hidden = !canBarter;
     545    if (!canBarter)
     546        return;
     547
     548    for (let i = 0; i < resCodes.length; ++i)
     549    {
     550        let resCode = resCodes[i];
     551        let barterButton = {};
     552        let barterIcon = {};
     553        let barterAmount = {};
     554        for (let a of ["Buy", "Sell"])
     555        {
     556            barterButton[a] = Engine.GetGUIObjectByName("barter" + a + "Button[" + i + "]");
     557            barterIcon[a] = Engine.GetGUIObjectByName("barter" + a + "Icon[" + i + "]");
     558            barterAmount[a] = Engine.GetGUIObjectByName("barter" + a + "Amount[" + i + "]");
     559        }
     560        let selectionIcon = Engine.GetGUIObjectByName("barterSellSelection[" + i + "]");
     561
     562
     563        let amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
     564        if (Engine.HotkeyIsPressed("session.massbarter"))
     565            amountToSell *= BARTER_BUNCH_MULTIPLIER;
     566
     567        let isSelected = resCode == g_BarterSell;
     568        let grayscale = isSelected ? "color: 0 0 0 100:grayscale:" : "";
     569
     570        // do we have enough of this resource to sell?
     571        let neededRes = {};
     572        neededRes[resCode] = amountToSell;
     573        let canSellCurrent = Engine.GuiInterfaceCall("GetNeededResources", {
     574            "cost": neededRes,
     575            "player": g_ViewedPlayer,
     576        }) ? "color:255 0 0 80:" : "";
     577
     578        // Let's see if we have enough resources to barter.
     579        neededRes = {};
     580        neededRes[g_BarterSell] = amountToSell;
     581        let canBuyAny = Engine.GuiInterfaceCall("GetNeededResources", {
     582            "cost": neededRes,
     583            "player": g_ViewedPlayer,
     584        }) ? "color:255 0 0 80:" : "";
     585
     586        barterIcon.Sell.sprite = canSellCurrent + "stretched:" + grayscale + "session/icons/resources/" + resCode + ".png";
     587        barterIcon.Buy.sprite = canBuyAny + "stretched:" + grayscale + "session/icons/resources/" + resCode + ".png";
     588
     589        barterAmount.Sell.caption = "-" + amountToSell;
     590        let prices = Engine.GuiInterfaceCall("GetBarterPrices");
     591        barterAmount.Buy.caption = "+" + Math.round(prices.sell[g_BarterSell] / prices.buy[resCode] * amountToSell);
     592
     593        barterButton.Buy.onPress = function() {
     594            Engine.PostNetworkCommand({
     595                "type": "barter",
     596                "sell": g_BarterSell,
     597                "buy": resCode,
     598                "amount": amountToSell
     599            });
     600        };
     601
     602        barterButton.Buy.hidden = isSelected;
     603        barterButton.Buy.enabled = controlsPlayer(g_ViewedPlayer);
     604        barterButton.Sell.hidden = false;
     605        selectionIcon.hidden = !isSelected;
     606    }
     607};
     608
    471609function getIdleLandTradersText(traderNumber)
    472610{
    473611    let active = traderNumber.landTrader.trading;
  • 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 a288fe6..9915232 100644
    a b function layoutSelectionMultiple() 
    1212
    1313function getResourceTypeDisplayName(resourceType)
    1414{
    15     let resourceCode = resourceType.generic;
    16     if (resourceCode == "treasure")
    17         return getLocalizedResourceName(resourceType.specific, "firstWord");
    18     else
    19         return getLocalizedResourceName(resourceCode, "firstWord");
     15    return getLocalizedResourceName(
     16        g_ResourceData.GetNames()[
     17            resourceType.generic == "treasure" ?
     18                resourceType.specific :
     19                resourceType.generic
     20        ],
     21        "firstWord");
    2022}
    2123
    2224// 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 fb12b00..487f5b9 100644
    a b g_SelectionPanels.Alert = { 
    8484    }
    8585};
    8686
    87 g_SelectionPanels.Barter = {
    88     "getMaxNumberOfItems": function()
    89     {
    90         return 4;
    91     },
    92     "rowLength": 4,
    93     "getItems": function(unitEntState, selection)
    94     {
    95         if (!unitEntState.barterMarket)
    96             return [];
    97         // ["food", "wood", "stone", "metal"]
    98         return BARTER_RESOURCES;
    99     },
    100     "setupButton": function(data)
    101     {
    102         // data.item is the resource name in this case
    103         let button = {};
    104         let icon = {};
    105         let amount = {};
    106         for (let a of BARTER_ACTIONS)
    107         {
    108             button[a] = Engine.GetGUIObjectByName("unitBarter" + a + "Button[" + data.i + "]");
    109             icon[a] = Engine.GetGUIObjectByName("unitBarter" + a + "Icon[" + data.i + "]");
    110             amount[a] = Engine.GetGUIObjectByName("unitBarter" + a + "Amount[" + data.i + "]");
    111         }
    112         let selectionIcon = Engine.GetGUIObjectByName("unitBarterSellSelection[" + data.i + "]");
    113 
    114         let amountToSell = BARTER_RESOURCE_AMOUNT_TO_SELL;
    115         if (Engine.HotkeyIsPressed("session.massbarter"))
    116             amountToSell *= BARTER_BUNCH_MULTIPLIER;
    117 
    118         amount.Sell.caption = "-" + amountToSell;
    119         let prices = data.unitEntState.barterMarket.prices;
    120         amount.Buy.caption = "+" + Math.round(prices.sell[g_BarterSell] / prices.buy[data.item] * amountToSell);
    121 
    122         let resource = getLocalizedResourceName(data.item, "withinSentence");
    123         button.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource });
    124         button.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource });
    125 
    126         button.Sell.onPress = function() {
    127             g_BarterSell = data.item;
    128             updateSelectionDetails();
    129         };
    130 
    131         button.Buy.onPress = function() {
    132             Engine.PostNetworkCommand({
    133                 "type": "barter",
    134                 "sell": g_BarterSell,
    135                 "buy": data.item,
    136                 "amount": amountToSell
    137             });
    138         };
    139 
    140         let isSelected = data.item == g_BarterSell;
    141         let grayscale = isSelected ? "color: 0 0 0 100:grayscale:" : "";
    142 
    143         // do we have enough of this resource to sell?
    144         let neededRes = {};
    145         neededRes[data.item] = amountToSell;
    146         let canSellCurrent = Engine.GuiInterfaceCall("GetNeededResources", {
    147             "cost": neededRes,
    148             "player": data.unitEntState.player
    149         }) ? "color:255 0 0 80:" : "";
    150 
    151         // Let's see if we have enough resources to barter.
    152         neededRes = {};
    153         neededRes[g_BarterSell] = amountToSell;
    154         let canBuyAny = Engine.GuiInterfaceCall("GetNeededResources", {
    155             "cost": neededRes,
    156             "player": data.unitEntState.player
    157         }) ? "color:255 0 0 80:" : "";
    158 
    159         icon.Sell.sprite = canSellCurrent + "stretched:" + grayscale + "session/icons/resources/" + data.item + ".png";
    160         icon.Buy.sprite = canBuyAny + "stretched:" + grayscale + "session/icons/resources/" + data.item + ".png";
    161 
    162         button.Buy.hidden = isSelected;
    163         button.Buy.enabled = controlsPlayer(data.unitEntState.player);
    164         button.Sell.hidden = false;
    165         selectionIcon.hidden = !isSelected;
    166 
    167         setPanelObjectPosition(button.Sell, data.i, data.rowLength);
    168         setPanelObjectPosition(button.Buy, data.i + data.rowLength, data.rowLength);
    169         return true;
    170     }
    171 };
    172 
    17387g_SelectionPanels.Command = {
    17488    "getMaxNumberOfItems": function()
    17589    {
    g_SelectionPanels.Upgrade = { 
    11441058 */
    11451059let g_PanelsOrder = [
    11461060    // LEFT PANE
    1147     "Barter", // Must always be visible on markets
    11481061    "Garrison", // More important than Formation, as you want to see the garrisoned units in ships
    11491062    "Alert",
    11501063    "Formation",
  • 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 c224597..e5b1f03 100644
    a b  
    1 const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
    2 const BARTER_BUNCH_MULTIPLIER = 5;
    3 const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
    4 const BARTER_ACTIONS = ["Sell", "Buy"];
    51const GATE_ACTIONS = ["lock", "unlock"];
    62
    7 // upgrade constants
    83const UPGRADING_NOT_STARTED = -2;
    94const UPGRADING_CHOSEN_OTHER = -1;
    105
    11 // ==============================================
    12 // BARTER HELPERS
    13 // Resources to sell on barter panel
    14 var g_BarterSell = "food";
    15 
    166function canMoveSelectionIntoFormation(formationTemplate)
    177{
    188    if (!(formationTemplate in g_canMoveIntoFormation))
  • deleted file 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
    deleted file mode 100644
    index 46b8b7b..0000000
    + -  
    1 <?xml version="1.0" encoding="utf-8"?>
    2 <object name="unitBarterPanel"
    3     size="6 36 100% 100%"
    4     hidden="true"
    5 >
    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>
    24 </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 d82312e..5548b2d 100644
    a b var g_EntityStates = {}; 
    130130var g_TemplateData = {};
    131131var g_TemplateDataWithoutLocalization = {};
    132132var g_TechnologyData = {};
     133var g_ResourceData = new Resources();
    133134
    134135/**
    135136 * Top coordinate of the research list.
    function init(initData, hotloadData) 
    268269    let gameSpeedIdx = g_GameSpeeds.Speed.indexOf(Engine.GetSimRate());
    269270    gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : g_GameSpeeds.Default;
    270271    gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); };
     272
    271273    initMenuPosition();
     274    resizeDiplomacyDialog();
     275    resizeTradeDialog();
    272276
    273277    for (let slot in Engine.GetGUIObjectByName("unitHeroPanel").children)
    274278        initGUIHeroes(slot);
    function updateTopPanel() 
    474478    let viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
    475479    viewPlayer.hidden = !g_IsObserver && !g_DevSettings.changePerspective;
    476480
    477     Engine.GetGUIObjectByName("food").hidden = !isPlayer;
    478     Engine.GetGUIObjectByName("wood").hidden = !isPlayer;
    479     Engine.GetGUIObjectByName("stone").hidden = !isPlayer;
    480     Engine.GetGUIObjectByName("metal").hidden = !isPlayer;
     481    let resCodes = g_ResourceData.GetCodes();
     482    let r = 0;
     483    for (let res of resCodes)
     484    {
     485        if (!Engine.GetGUIObjectByName("resource["+r+"]"))
     486        {
     487            warn("Current GUI limits prevent displaying more than " + r + " resources at the top of the screen");
     488            break;
     489        }
     490        Engine.GetGUIObjectByName("resource["+r+"]_icon").sprite = "stretched:session/icons/resources/" + res + ".png";
     491        Engine.GetGUIObjectByName("resource["+r+"]").hidden = !isPlayer;
     492        ++r;
     493    }
     494    horizontallySpaceObjects("resourceCounts", 0);
     495    hideRemaining("resourceCounts", r);
     496
     497    let resPop = Engine.GetGUIObjectByName("population");
     498    let resPopSize = resPop.size;
     499    resPopSize.left = Engine.GetGUIObjectByName("resource["+ (r-1) +"]").size.right;
     500    resPop.size = resPopSize;
     501
    481502    Engine.GetGUIObjectByName("population").hidden = !isPlayer;
    482503    Engine.GetGUIObjectByName("diplomacyButton1").hidden = !isPlayer;
    483504    Engine.GetGUIObjectByName("tradeButton1").hidden = !isPlayer;
    function leaveGame(willRejoin) 
    547568            "disconnected": g_Disconnected,
    548569            "isReplay": g_IsReplay,
    549570            "replayDirectory": !g_HasRejoined && replayDirectory,
    550             "replaySelectionData": g_ReplaySelectionData
     571            "replaySelectionData": g_ReplaySelectionData,
    551572        }
    552573    });
    553574}
    function updateGUIObjects() 
    714735    updateBuildingPlacementPreview();
    715736    updateTimeNotifications();
    716737    updateIdleWorkerButton();
     738    updateBarterButtons();
    717739
    718740    if (g_ViewedPlayer > 0)
    719741    {
    function updatePlayerDisplay() 
    948970    if (!playerState)
    949971        return;
    950972
    951     for (let res of RESOURCES)
     973    let resCodes = g_ResourceData.GetCodes();
     974    let resNames = g_ResourceData.GetNames();
     975    for (let r = 0; r < resCodes.length; ++r)
    952976    {
    953         Engine.GetGUIObjectByName("resource_" + res).caption = Math.floor(playerState.resourceCounts[res]);
    954         Engine.GetGUIObjectByName(res).tooltip = getLocalizedResourceName(res, "firstWord") + getAllyStatTooltip(res);
     977        if (!Engine.GetGUIObjectByName("resource["+r+"]"))
     978            break;
     979        let res = resCodes[r];
     980        Engine.GetGUIObjectByName("resource["+r+"]").tooltip = getLocalizedResourceName(resNames[res], "firstWord") + getAllyStatTooltip(res);
     981        Engine.GetGUIObjectByName("resource["+r+"]_count").caption = Math.floor(playerState.resourceCounts[res]);
    955982    }
    956983
    957984    Engine.GetGUIObjectByName("resourcePop").caption = sprintf(translate("%(popCount)s/%(popLimit)s"), playerState);
  • 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 7355a6a..9b1ce7a 100644
    a b  
    111111
    112112        <!-- Supplemental Details Panel (Left of Selection Details) -->
    113113        <object
    114             size="50%-304 100%-170 50%-110 100%"
     114            size="50%-304 100%-172 50%-110 100%"
    115115            name="supplementalSelectionDetails"
    116116            type="image"
    117117            sprite="supplementalDetailsPanel"
  • binaries/data/mods/public/gui/session/top_panel/button_trade.xml

    diff --git a/binaries/data/mods/public/gui/session/top_panel/button_trade.xml b/binaries/data/mods/public/gui/session/top_panel/button_trade.xml
    index 787c1f7..6f59e92 100644
    a b  
    77>
    88    <!-- TODO make the button less ugly -->
    99    <object size="0 0 100% 100%" name="tradeButtonImage" type="image" sprite="stretched:session/icons/economics.png" ghost="true"/>
    10     <translatableAttribute id="tooltip">Trade</translatableAttribute>
     10    <translatableAttribute id="tooltip">Barter &amp; Trade</translatableAttribute>
    1111    <action on="Press">
    1212        toggleTrade();
    1313    </action>
  • 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 4d84ca7..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         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
    4         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_food"/>
    5 </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 4edba79..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         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
    4         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_metal"/>
    5 </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..ffffe6b 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">
    3     <object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
     2
     3<object name="population" size="0 0 50%-52 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     4    <object size="0 -2 40 38" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
    45    <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
    56</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 6133acc..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         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
    4         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_stone"/>
    5 </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 f020979..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         <object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
    4         <object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_wood"/>
    5 </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..190c20e
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2
     3<object size="0 0 50%-90-52 100%" name="resourceCounts">
     4    <repeat count="5">
     5        <object name="resource[n]" size="0 0 75.6 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
     6            <object size="0 -2 40 38" type="image" name="resource[n]_icon" ghost="true"/>
     7            <object size="28 0 100%-2 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 ec9ed10..14f6905 100644
    a b  
    11<?xml version="1.0" encoding="utf-8"?>
    22<object name="tradeDialogPanel"
    3     size="50%-250 50%-130 50%+250 50%+100"
     3    size="50%-80 50%-280 50%+80 50%+136"
    44    type="image"
    55    hidden="true"
    66    sprite="ModernDialog"
     7    z="100"
    78>
    89    <object type="text" style="TitleText" size="50%-96 -16 50%+96 16">
    9         <translatableAttribute id="caption">Trade</translatableAttribute>
     10        <translatableAttribute id="caption">Barter &amp; Trade Goods</translatableAttribute>
    1011    </object>
    1112
    12     <!-- Trading goods -->
    13     <object name="tradeGoods" size="20 50 100%-20 82">
    14         <object name="tradeHeader" size="0 0 180 100%" type="text" style="ModernLabelText" text_align="left" ghost="true">
    15             <translatableAttribute id="caption">Trading goods selection:</translatableAttribute>
     13
     14    <!-- Barter Goods -->
     15    <object size="24 24 100%-24 33%">
     16
     17        <object name="barterHeader" size="8 0 100% 32" type="text" style="ModernLeftLabelText">
     18            <translatableAttribute id="caption">Barter</translatableAttribute>
    1619        </object>
     20        <object size="0 28 100% 29" type="image" sprite="ModernGoldLine"/>
     21
     22        <object size="0 38 100% 38+84">
     23
     24            <object size="0 0 60 41" type="text" style="ModernRightLabelText">
     25                <translatableAttribute id="caption">Sell:</translatableAttribute>
     26            </object>
     27
     28            <object size="0 100%-41 60 100%" type="text" style="ModernRightLabelText">
     29                <translatableAttribute id="caption">Buy:</translatableAttribute>
     30            </object>
     31
     32            <object size="72 0 100% 100%" type="text" style="ModernLabelText" name="barterNoMarketsMessage">
     33                <translatableAttribute id="caption">No Markets Available</translatableAttribute>
     34            </object>
     35
     36            <object name="barterResources" size="72 0 100% 100%">
     37                <repeat count="8">
     38                    <object name="barterResource[n]" size="0 0 58 100%">
     39
     40                        <!-- Sell -->
     41                        <object name="barterSellButton[n]" style="iconButton" type="button" size="0 0 41 41" tooltip_style="sessionToolTipBottomBold" hidden="true">
     42                            <object name="barterSellIcon[n]" type="image" ghost="true" size="3 3 100%-3 100%-3"/>
     43                            <object name="barterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
     44                            <object name="barterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 100%-3 100%-3" sprite="stretched:session/icons/corners.png"/>
     45                        </object>
     46
     47                        <!-- Buy -->
     48                        <object name="barterBuyButton[n]" style="iconButton" type="button" size="0 100%-41 41 100%" tooltip_style="sessionToolTipBottomBold" hidden="true">
     49                            <object name="barterBuyIcon[n]" type="image" ghost="true" size="3 3 100%-3 100%-3"/>
     50                            <object name="barterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
     51                        </object>
    1752
    18         <object size="180 0 100% 100%">
    19             <repeat count="4">
    20                 <object name="tradeResource[n]" size="0 0 58 32">
    21                     <object name="tradeResourceButton[n]" size="4 0 36 100%" type="button" style="StoneButton">
    22                         <object name="tradeResourceIcon[n]" type="image" ghost="true"/>
    23                         <object name="tradeResourceSelection[n]" type="image" sprite="stretched:session/icons/corners.png" ghost="true"/>
    24                         <object name="tradeResourceText[n]" type="text" style="ModernLabelText" ghost="true"/>
    25                     </object>
    26                     <object name="tradeArrowUp[n]" size="36 0 52 50%" type="button" style="iconButton">
    27                         <object type="image" ghost="true" sprite="StoneArrowUp"/>
    2853                    </object>
    29                     <object name="tradeArrowDn[n]" size="36 50% 52 100%" type="button" style="iconButton">
    30                         <object type="image" ghost="true" sprite="StoneArrowDn"/>
     54                </repeat>
     55            </object>
     56
     57        </object>
     58    </object>
     59
     60    <!-- Trading goods -->
     61    <object size="24 33%+32 100%-24 100%-64">
     62
     63        <object name="tradeHeader" size="8 0 100% 32" type="text" style="ModernLeftLabelText">
     64            <translatableAttribute id="caption">Trade</translatableAttribute>
     65        </object>
     66        <object size="0 28 100% 29" type="image" sprite="ModernGoldLine"/>
     67
     68        <object name="tradeGoods" size="0 38 100% 38+32">
     69
     70            <object size="0 0 60 100%" type="text" style="ModernRightLabelText">
     71                <translatableAttribute id="caption">Goods:</translatableAttribute>
     72            </object>
     73
     74            <object size="72 0 100% 100%" name="tradeResources">
     75                <repeat count="8">
     76                    <object name="tradeResource[n]" size="0 0 58 32">
     77
     78                        <object name="tradeResourceButton[n]" size="4 0 36 100%" type="button" style="StoneButton">
     79                            <object name="tradeResourceIcon[n]" type="image" ghost="true"/>
     80                            <object name="tradeResourceSelection[n]" type="image" sprite="stretched:session/icons/corners.png" ghost="true"/>
     81                            <object name="tradeResourceText[n]" type="text" style="ModernLabelText" ghost="true"/>
     82                        </object>
     83                        <object name="tradeArrowUp[n]" size="36 0 52 50%" type="button" style="iconButton">
     84                            <object type="image" ghost="true" sprite="StoneArrowUp"/>
     85                        </object>
     86                        <object name="tradeArrowDn[n]" size="36 50% 52 100%" type="button" style="iconButton">
     87                            <object type="image" ghost="true" sprite="StoneArrowDn"/>
     88                        </object>
     89
    3190                    </object>
    32                 </object>
    33             </repeat>
     91                </repeat>
     92            </object>
     93
    3494            <object name="tradeHelp" size="100%-24 4 100% 28" enabled="false" type="button" style="StoneButton" tooltip_style="sessionToolTipBold">
    3595                <object size="20% 15% 80% 75%" type="image" ghost="true" sprite="iconInfoWhite"/>
    3696            </object>
     97
    3798        </object>
    38     </object>
    3999
    40     <object name="tradeStatistics" size="20 90 100%-20 168">
    41         <object name="landTraders" size="0 0 100% 50%" type="text" style="ModernLabelText" text_align="left" ghost="true" />
    42         <object name="shipTraders" size="0 50% 100% 100%" type="text" style="ModernLabelText" text_align="left" ghost="true" />
     100        <object name="traders" size="8 88 100% 100%" type="text" style="ModernLeftTabLabelText"/>
     101
    43102    </object>
    44103
    45104    <object size="50%-64 100%-50 50%+64 100%-22" type="button" style="StoneButton">
  • binaries/data/mods/public/gui/session/unit_actions.js

    diff --git a/binaries/data/mods/public/gui/session/unit_actions.js b/binaries/data/mods/public/gui/session/unit_actions.js
    index 03e9765..13948c6 100644
    a b var g_EntityCommands = 
    12081208                return false;
    12091209
    12101210            return {
    1211                 "tooltip": translate("Select trading goods"),
     1211                "tooltip": translate("Barter & Trade"),
    12121212                "icon": "economics.png"
    12131213            };
    12141214        },
  • binaries/data/mods/public/gui/structree/draw.js

    diff --git a/binaries/data/mods/public/gui/structree/draw.js b/binaries/data/mods/public/gui/structree/draw.js
    index 06f1e39..7d3fc8b 100644
    a b function draw() 
    112112                if (p>c)
    113113                    c = p;
    114114
    115                 hideRemaining("phase["+i+"]_struct["+s+"]_row["+r+"]_prod[", p, "]");
     115                hideRemaining("phase["+i+"]_struct["+s+"]_row["+r+"]", p);
    116116            }
    117117
    118118            let size = thisEle.size;
    function draw() 
    132132                phaEle.size = size;
    133133            }
    134134            ++r;
    135             hideRemaining("phase["+i+"]_struct["+s+"]_row[", r, "]");
     135            hideRemaining("phase["+i+"]_struct["+s+"]_rows", r);
    136136            ++s;
    137137        }
    138         hideRemaining("phase["+i+"]_struct[", s, "]");
     138        hideRemaining("phase["+i+"]", s);
    139139        ++i;
    140140    }
    141141
    function draw() 
    179179                ++p;
    180180            }
    181181        }
    182         hideRemaining("trainer["+t+"]_prod[", p, "]");
     182        hideRemaining("trainer["+t+"]_row", p);
    183183
    184184        let size = thisEle.size;
    185185        size.right = size.left + Math.max(p*24, defWidth) + 4;
    function draw() 
    193193        phaEle.size = size;
    194194        ++t;
    195195    }
    196     hideRemaining("trainer[", t, "]");
     196    hideRemaining("trainers", t);
    197197
    198198    let size = Engine.GetGUIObjectByName("display_tree").size;
    199199    size.right = t > 0 ? -124 : -4;
    function getPositionOffset(idx) 
    235235    return size;
    236236}
    237237
    238 function hideRemaining(prefix, idx, suffix)
    239 {
    240     let obj = Engine.GetGUIObjectByName(prefix + idx + suffix);
    241     while (obj)
    242     {
    243         obj.hidden = true;
    244         ++idx;
    245         obj = Engine.GetGUIObjectByName(prefix + idx + suffix);
    246     }
    247 }
    248 
    249 
    250238/**
    251239 * Positions certain elements that only need to be positioned once
    252240 * (as <repeat> does not reposition automatically).
    function predraw() 
    283271            prodBarIcon.sprite = "stretched:session/portraits/"+g_ParsedData.phases[phaseList[i+j]].icon;
    284272        }
    285273        // Hide remaining prod bars
    286         hideRemaining("phase["+i+"]_bar[", j-1, "]");
     274        hideRemaining("phase["+i+"]_bars", j-1);
    287275
    288276        let s = 0;
    289277        let ele = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
    function predraw() 
    334322        g_DrawLimits[pha].structQuant = s;
    335323        ++i;
    336324    }
    337     hideRemaining("phase[", i, "]");
    338     hideRemaining("phase[", i, "]_bar");
     325    hideRemaining("phase_rows", i);
     326    hideRemaining("phase_ident", i);
    339327
    340328    let t = 0;
    341329    let ele = Engine.GetGUIObjectByName("trainer["+t+"]");
  • binaries/data/mods/public/gui/structree/load.js

    diff --git a/binaries/data/mods/public/gui/structree/load.js b/binaries/data/mods/public/gui/structree/load.js
    index 1dfd332..6b52fdf 100644
    a b  
    55 */
    66function getGatherRates(templateName)
    77{
    8     // TODO: It would be nice to use the gather rates present in the templates
    9     // instead of hard-coding the possible rates here.
    10 
    11     // We ignore ruins here, as those are not that common and would skew the results
    12     var types = {
    13         "food": ["food", "food.fish", "food.fruit", "food.grain", "food.meat", "food.milk"],
    14         "wood": ["wood", "wood.tree"],
    15         "stone": ["stone", "stone.rock"],
    16         "metal": ["metal", "metal.ore"]
    17     };
    18     var rates = {};
     8    let rates = {};
    199
    20     for (let type in types)
     10    for (let resource of g_ResourceData.GetData())
    2111    {
     12        let types = [resource.code];
     13        for (let subtype in resource.subtypes)
     14            // We ignore ruins as those are not that common and skew the results
     15            if (subtype !== "ruins")
     16                types.push(resource.code + "." + subtype);
     17
    2218        let count, rate;
    23         [rate, count] = types[type].reduce(function(sum, t) {
     19        [rate, count] = types.reduce((sum, t) => {
    2420                let r = +fetchValue(templateName, "ResourceGatherer/Rates/"+t);
    2521                return [sum[0] + (r > 0 ? r : 0), sum[1] + (r > 0 ? 1 : 0)];
    2622            }, [0, 0]);
    2723
    2824        if (rate > 0)
    29             rates[type] = Math.round(rate / count * 100) / 100;
     25            rates[resource.code] = +(rate / count).toFixed(1);
    3026    }
    3127
    3228    if (!Object.keys(rates).length)
  • binaries/data/mods/public/gui/structree/rows.xml

    diff --git a/binaries/data/mods/public/gui/structree/rows.xml b/binaries/data/mods/public/gui/structree/rows.xml
    index 4e53293..fdffe3c 100644
    a b  
    99                    <object type="image" style="StructIcon" name="phase[k]_struct[s]_icon"
    1010                        sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
    1111                    />
    12                     <repeat count="4" var="r">
    13                         <object name="phase[k]_struct[s]_row[r]">
    14                             <repeat count="24" var="p">
    15                                 <object type="image" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
    16                             </repeat>
    17                         </object>
    18                     </repeat>
     12                    <object name="phase[k]_struct[s]_rows">
     13                        <repeat count="4" var="r">
     14                            <object name="phase[k]_struct[s]_row[r]">
     15                                <repeat count="24" var="p">
     16                                    <object type="image" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
     17                                </repeat>
     18                            </object>
     19                        </repeat>
     20                    </object>
    1921                </object>
    2022            </repeat>
    2123        </object>
  • binaries/data/mods/public/gui/structree/structree.js

    diff --git a/binaries/data/mods/public/gui/structree/structree.js b/binaries/data/mods/public/gui/structree/structree.js
    index 65bfd9e..fcb6a9c 100644
    a b var g_Lists = {}; 
    99var g_CivData = {};
    1010var g_SelectedCiv = "";
    1111var g_CallbackSet = false;
     12var g_ResourceData = new Resources();
    1213
    1314/**
    1415 * Initialize the dropdown containing all the available civs
  • binaries/data/mods/public/gui/structree/structree.xml

    diff --git a/binaries/data/mods/public/gui/structree/structree.xml b/binaries/data/mods/public/gui/structree/structree.xml
    index b53bd93..f33d12e 100644
    a b  
    6464
    6565        <!-- Structure Tree display -->
    6666        <object size="0 54+64 100%-124 100%-54" name="display_tree">
    67             <repeat count="4" var="n">
    68                 <object name="phase[n]_phase" type="image"/>
    69                 <object name="phase[n]_bar">
    70                     <repeat count="4" var="k">
    71                         <object name="phase[n]_bar[k]" type="image" sprite="ProdBar">
    72                             <object name="phase[n]_bar[k]_icon" type="image" size="2 2 20 20"/>
     67            <object name="phase_ident">
     68                <repeat count="4" var="n">
     69                    <object>
     70                        <object name="phase[n]_phase" type="image"/>
     71                        <object name="phase[n]_bars">
     72                            <repeat count="4" var="k">
     73                                <object name="phase[n]_bar[k]" type="image" sprite="ProdBar">
     74                                    <object name="phase[n]_bar[k]_icon" type="image" size="2 2 20 20"/>
     75                                </object>
     76                            </repeat>
    7377                        </object>
    74                     </repeat>
    75                 </object>
    76             </repeat>
     78                    </object>
     79                </repeat>
     80            </object>
    7781
    7882            <object type="image" style="TreeDisplay" size="48+16+8 0 100%-12 100%">
    7983                <include file="gui/structree/rows.xml"/>
     
    9397                <translatableAttribute id="caption">Trainer Units</translatableAttribute>
    9498            </object>
    9599
    96             <object type="image" style="TreeDisplay" size="0 24 100% 100%">
     100            <object type="image" style="TreeDisplay" size="0 24 100% 100%" name="trainers">
    97101                <repeat count="3" var="t">
    98102                    <object type="image" style="StructBox" name="trainer[t]">
    99103                        <object type="text" style="StructNameSpecific" name="trainer[t]_name"/>
  • 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 562e3bb..36b1812 100644
    a b function updateCountersPlayer(playerState, counters, idGUI) 
    6363{
    6464    for (let w in counters)
    6565    {
     66        if (!Engine.GetGUIObjectByName(idGUI + "[" + w + "]"))
     67            break;
    6668        let fn = counters[w].fn;
    6769        Engine.GetGUIObjectByName(idGUI + "[" + w + "]").caption = fn && fn(playerState, w);
    6870    }
    function calculateUnits(playerState, position) 
    250252
    251253function calculateResources(playerState, position)
    252254{
    253     let type = g_ResourcesTypes[position];
     255    let type = g_ResourceData.GetCodes()[position];
    254256
    255257    return formatIncome(
    256258        playerState.statistics.resourcesGathered[type],
    function calculateTotalResources(playerState) 
    262264    let totalGathered = 0;
    263265    let totalUsed = 0;
    264266
    265     for (let type of g_ResourcesTypes)
     267    for (let type of g_ResourceData.GetCodes())
    266268    {
    267269        totalGathered += playerState.statistics.resourcesGathered[type];
    268270        totalUsed += playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type];
    function calculateResourcesTeam(counters) 
    330332
    331333function calculateResourceExchanged(playerState, position)
    332334{
    333     let type = g_ResourcesTypes[position];
     335    let type = g_ResourceData.GetCodes()[position];
    334336
    335337    return formatIncome(
    336338        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 7feeb45..efb20e9 100644
    a b var g_ScorePanelsData = { 
    9292    "resources": {
    9393        "headings": [
    9494            { "caption": translate("Player name"), "yStart": 26, "width": 200 },
    95             { "caption": translate("Food"), "yStart": 34, "width": 100 },
    96             { "caption": translate("Wood"), "yStart": 34, "width": 100 },
    97             { "caption": translate("Stone"), "yStart": 34, "width": 100 },
    98             { "caption": translate("Metal"), "yStart": 34, "width": 100 },
    9995            { "caption": translate("Total"), "yStart": 34, "width": 110 },
    10096            {
    10197                "caption": sprintf(translate("Tributes \n(%(sent)s / %(received)s)"),
    var g_ScorePanelsData = { 
    121117            }, // width = 510
    122118        ],
    123119        "counters": [
    124             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    125             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    126             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    127             { "width": 100, "fn": calculateResources, "verticalOffset": 12 },
    128120            { "width": 110, "fn": calculateTotalResources, "verticalOffset": 12 },
    129121            { "width": 121, "fn": calculateTributeSent, "verticalOffset": 12 },
    130122            { "width": 100, "fn": calculateTreasureCollected, "verticalOffset": 12 },
    var g_ScorePanelsData = { 
    135127    "market": {
    136128        "headings": [
    137129            { "caption": translate("Player name"), "yStart": 26, "width": 200 },
    138             { "caption": translate("Food exchanged"), "yStart": 16, "width": 100 },
    139             { "caption": translate("Wood exchanged"), "yStart": 16, "width": 100 },
    140             { "caption": translate("Stone exchanged"), "yStart": 16, "width": 100 },
    141             { "caption": translate("Metal exchanged"), "yStart": 16, "width": 100 },
    142130            { "caption": translate("Barter efficiency"), "yStart": 16, "width": 100 },
    143131            { "caption": translate("Trade income"), "yStart": 16, "width": 100 }
    144132        ],
    145133        "titleHeadings": [],
    146134        "counters": [
    147             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    148             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    149             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    150             { "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
    151135            { "width": 100, "fn": calculateBarterEfficiency, "verticalOffset": 12 },
    152136            { "width": 100, "fn": calculateTradeIncome, "verticalOffset": 12 }
    153137        ],
    function updateGeneralPanelHeadings(headings) 
    206190            headerGUIName = "Heading[" + (h - 1) + "]";
    207191
    208192        let headerGUI = Engine.GetGUIObjectByName(headerGUIName);
     193        if (!headerGUI)
     194            break;
    209195        headerGUI.caption = headings[h].caption;
    210196        headerGUI.size = left + " " + headings[h].yStart + " " + (left + headings[h].width) + " 100%";
    211197        headerGUI.hidden = false;
    function updateGeneralPanelCounter(counters) 
    249235        for (let w in counters)
    250236        {
    251237            counterObject = Engine.GetGUIObjectByName("valueData[" + p + "][" + w + "]");
     238            if (!counterObject)
     239                break;
    252240            counterObject.size = left + " " + counters[w].verticalOffset + " " + (left + counters[w].width) + " 100%";
    253241            counterObject.hidden = false;
    254242            left += counters[w].width;
    function updateGeneralPanelCounter(counters) 
    264252            for (let w in counters)
    265253            {
    266254                counterObject = Engine.GetGUIObjectByName("valueDataTeam[" + t + "][" + p + "][" + w + "]");
     255                if (!counterObject)
     256                    break;
    267257                counterObject.size = left + " " + counters[w].verticalOffset + " " + (left + counters[w].width) + " 100%";
    268258                counterObject.hidden = false;
    269259
  • 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 51e5577..7849b1f 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_CapturedColor = '[color="255 255 157"]'; 
    1717
    1818const g_BuildingsTypes = [ "total", "House", "Economic", "Outpost", "Military", "Fortress", "CivCentre", "Wonder" ];
    1919const g_UnitsTypes = [ "total", "Infantry", "Worker", "Cavalry", "Champion", "Hero", "Ship", "Trader" ];
    20 const g_ResourcesTypes = [ "food", "wood", "stone", "metal" ];
    2120
    2221// Colors used for gathered and traded resources
    2322const g_IncomeColor = '[color="201 255 200"]';
    var g_PlayerCount = 0; 
    3433// Count players without team (or all if teams are not displayed)
    3534var g_WithoutTeam = 0;
    3635var g_GameData;
     36var g_ResourceData = new Resources();
    3737
    3838function selectPanel(panel)
    3939{
    function init(data) 
    242242    else
    243243        g_Teams = false;
    244244
     245    // Resource names and counters
     246    let resHeads = [];
     247    let tradeHeads = [];
     248    let resPanel = g_ScorePanelsData.resources;
     249    let tradePanel = g_ScorePanelsData.market;
     250    let resNames = g_ResourceData.GetNames();
     251    let resCodes = g_ResourceData.GetCodes();
     252    for (let code of resCodes)
     253    {
     254        resHeads.push({
     255            "caption": translateWithContext("firstWord", resNames[code]),
     256            "yStart": 34,
     257            "width": 100
     258        });
     259
     260        resPanel.counters.unshift({
     261            "width": 100,
     262            "fn": calculateResources,
     263            "verticalOffset": 12
     264        });
     265
     266        tradeHeads.push({
     267            "caption": sprintf(
     268                translate("%(resource)s exchanged"), {
     269                    "resource": translateWithContext("withinSentence", resNames[code])
     270                }),
     271            "yStart": 16,
     272            "width": 100
     273        });
     274
     275        tradePanel.counters.unshift({
     276            "width": 100,
     277            "fn": calculateResourceExchanged,
     278            "verticalOffset": 12
     279        });
     280    }
     281    resPanel.headings.splice.apply(resPanel.headings, [1, 0].concat(resHeads));
     282    resPanel.titleHeadings[0].width = 100 * resCodes.length + 110;
     283    tradePanel.headings.splice.apply(tradePanel.headings, [1, 0].concat(tradeHeads));
     284
    245285    // Erase teams data if teams are not displayed
    246286    if (!g_Teams)
    247287    {
  • 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 7d27a47..47bdb4d 100644
    a b  
    103103                <object name="playerNameHeading" type="text" style="ModernLeftTabLabelText">
    104104                    <translatableAttribute id="caption">Player name</translatableAttribute>
    105105                </object>
    106                 <repeat var="x" count="8">
     106                <repeat var="x" count="12">
    107107                    <object name="titleHeading[x]" type="text" style="ModernTabLabelText">
    108108                    </object>
    109109                </repeat>
    110                 <repeat var="x" count="8">
     110                <repeat var="x" count="12">
    111111                    <object name="Heading[x]" type="text" style="ModernTabLabelText">
    112112                    </object>
    113113                </repeat>
     
    124124                                </object>
    125125                                <object name="playerNamet[i][n]" type="text" size="40 2 208 100%" style="ModernLeftLabelText"/>
    126126                                <object name="civIcont[i][n]" type="image" size="208 5 240 37" />
    127                                 <repeat var="x" count="8">
     127                                <repeat var="x" count="12">
    128128                                    <object name="valueDataTeam[i][n][x]" type="text" style="ModernTabLabelText">
    129129                                    </object>
    130130                                </repeat>
     
    132132                        </repeat>
    133133                    </object>
    134134                    <object name="teamHeadingt[i]" type="text" style="ModernLeftTabLabelText"/>
    135                     <repeat var="x" count="8">
     135                    <repeat var="x" count="12">
    136136                        <object name="valueDataTeam[i][x]" type="text" style="ModernTabLabelText">
    137137                        </object>
    138138                    </repeat>
     
    147147                        </object>
    148148                        <object name="playerName[n]" type="text" size="40 2 208 100%" style="ModernLeftLabelText"/>
    149149                        <object name="civIcon[n]" type="image" size="208 5 240 37"/>
    150                         <repeat var="x" count="8">
     150                        <repeat var="x" count="12">
    151151                            <object name="valueData[n][x]" type="text" style="ModernTabLabelText">
    152152                            </object>
    153153                        </repeat>
  • 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 4c98928..431065c 100644
    a b  
    289289                    }
    290290                }
    291291            },
    292             {
     292            {
    293293                "extractor": "json",
    294294                "filemasks": [
    295295                    "gui/credits/texts/**.json"
     
    559559                        "description"
    560560                    ]
    561561                }
     562            },
     563            {
     564                "extractor": "json",
     565                "filemasks": [
     566                    "simulation/data/resources/**.json"
     567                ],
     568                "options": {
     569                    "keywords": [
     570                        "name",
     571                        "subtypes"
     572                    ],
     573                    "context": "firstWord"
     574                }
     575            },
     576            {
     577                "extractor": "json",
     578                "filemasks": [
     579                    "simulation/data/resources/**.json"
     580                ],
     581                "options": {
     582                    "keywords": [
     583                        "name",
     584                        "subtypes"
     585                    ],
     586                    "context": "withinSentence"
     587                }
    562588            }
    563589        ]
    564590    },
  • 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 8130676..b6fe84b 100644
    a b m.Resources = function(amounts = {}, population = 0) 
    99    this.population = population > 0 ? population : 0;
    1010};
    1111
    12 m.Resources.prototype.types = []; // This array will be filled in SharedScript.init
     12// This array will be filled in SharedScript.init
     13m.Resources.prototype.types = [];
    1314
    1415m.Resources.prototype.reset = function()
    1516{
  • 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 68ab45d..0e7b6c5 100644
    a b m.SharedScript.prototype.init = function(state, deserialization) 
    180180    this.accessibility.init(state, this.terrainAnalyzer);
    181181
    182182    // Setup resources
    183     this.resourceTypes = { "food": 0, "wood": 1, "stone": 2, "metal": 2 };
    184     this.resourceList = [];
    185     for (let res in this.resourceTypes)
    186         this.resourceList.push(res);
    187     m.Resources.prototype.types = this.resourceList;
     183    this.resourceInfo = state.resources;
     184    m.Resources.prototype.types = state.resources.codes;
    188185    // Resource types: 0 = not used for resource maps
    189     //                 1 = abondant resource with small amount each
     186    //                 1 = abundant resource with small amount each
    190187    //                 2 = spare resource, but huge amount each
    191188    // The following maps are defined in TerrainAnalysis.js and are used for some building placement (cc, dropsites)
    192189    // They are updated by checking for create and destroy events for all resources
    m.SharedScript.prototype.init = function(state, deserialization) 
    197194    this.ccResourceMaps = {}; // Contains maps showing the density of resources, optimized for CC placement.
    198195    this.createResourceMaps();
    199196
    200     /** Keep in sync with gui/common/l10n.js */
    201     this.resourceNames = {
    202         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    203         "food": markForTranslationWithContext("withinSentence", "Food"),
    204         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    205         "wood": markForTranslationWithContext("withinSentence", "Wood"),
    206         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    207         "metal": markForTranslationWithContext("withinSentence", "Metal"),
    208         // Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
    209         "stone": markForTranslationWithContext("withinSentence", "Stone"),
    210     };
    211 
    212197    this.gameState = {};
    213198    for (let i in this._players)
    214199    {
  • 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 04633dc..0639911 100644
    a b m.Accessibility.prototype.floodFill = function(startIndex, value, onWater) 
    383383/** creates a map of resource density */
    384384m.SharedScript.prototype.createResourceMaps = function()
    385385{
    386     for (let resource of this.resourceList)
     386    for (let resource of this.resourceInfo.codes)
    387387    {
    388         if (this.resourceTypes[resource] !== 1 && this.resourceTypes[resource] !== 2)
     388        if (this.resourceInfo.aiInfluenceGroups[resource] === 0)
    389389            continue;
    390390        // if there is no resourceMap create one with an influence for everything with that resource
    391391        if (this.resourceMaps[resource])
    m.SharedScript.prototype.createResourceMaps = function() 
    405405        let cellSize = this.resourceMaps[resource].cellSize;
    406406        let x = Math.floor(ent.position()[0] / cellSize);
    407407        let z = Math.floor(ent.position()[1] / cellSize);
    408         let type = this.resourceTypes[resource];
    409         let strength = Math.floor(ent.resourceSupplyMax()/this.normalizationFactor[type]);
    410         this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2, "constant");
    411         this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2);
    412         this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[type]/cellSize, strength, "constant");
     408        let grp = this.resourceInfo.aiInfluenceGroups[resource];
     409        let strength = Math.floor(ent.resourceSupplyMax() / this.normalizationFactor[grp]);
     410        this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant");
     411        this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2);
     412        this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
    413413    }
    414414};
    415415
    m.SharedScript.prototype.createResourceMaps = function() 
    420420 */
    421421m.SharedScript.prototype.updateResourceMaps = function(events)
    422422{
    423     for (let resource of this.resourceList)
     423    for (let resource of this.resourceInfo.codes)
    424424    {
    425         if (this.resourceTypes[resource] !== 1 && this.resourceTypes[resource] !== 2)
     425        if (this.resourceInfo.aiInfluenceGroups[resource] === 0)
    426426            continue;
    427427        // if there is no resourceMap create one with an influence for everything with that resource
    428428        if (this.resourceMaps[resource])
    m.SharedScript.prototype.updateResourceMaps = function(events) 
    447447        let cellSize = this.resourceMaps[resource].cellSize;
    448448        let x = Math.floor(ent.position()[0] / cellSize);
    449449        let z = Math.floor(ent.position()[1] / cellSize);
    450         let type = this.resourceTypes[resource];
    451         let strength = -Math.floor(ent.resourceSupplyMax()/this.normalizationFactor[type]);
    452         this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2, "constant");
    453         this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2);
    454         this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[type]/cellSize, strength, "constant");
     450        let grp = this.resourceInfo.aiInfluenceGroups[resource];
     451        let strength = -Math.floor(ent.resourceSupplyMax() / this.normalizationFactor[grp]);
     452        this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant");
     453        this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2);
     454        this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
    455455    }
    456456    for (let e of events.Create)
    457457    {
    m.SharedScript.prototype.updateResourceMaps = function(events) 
    466466        let cellSize = this.resourceMaps[resource].cellSize;
    467467        let x = Math.floor(ent.position()[0] / cellSize);
    468468        let z = Math.floor(ent.position()[1] / cellSize);
    469         let type = this.resourceTypes[resource];
    470         let strength = Math.floor(ent.resourceSupplyMax()/this.normalizationFactor[type]);
    471         this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2, "constant");
    472         this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2);
    473         this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[type]/cellSize, strength, "constant");
     469        let grp = this.resourceInfo.aiInfluenceGroups[resource];
     470        let strength = Math.floor(ent.resourceSupplyMax() / this.normalizationFactor[grp]);
     471        this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant");
     472        this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2);
     473        this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
    474474    }
    475475};
    476476
  • binaries/data/mods/public/simulation/ai/petra/baseManager.js

    diff --git a/binaries/data/mods/public/simulation/ai/petra/baseManager.js b/binaries/data/mods/public/simulation/ai/petra/baseManager.js
    index f1b338f..1c823c7 100644
    a b m.BaseManager.prototype.init = function(gameState, state) 
    5555    this.dropsites = {};
    5656    this.dropsiteSupplies = {};
    5757    this.gatherers = {};
    58     for (let res of gameState.sharedScript.resourceList)
     58    for (let res of gameState.sharedScript.resourceInfo.codes)
    5959    {
    6060        this.dropsiteSupplies[res] = { "nearby": [], "medium": [], "faraway": [] };
    6161        this.gatherers[res] = { "nextCheck": 0, "used": 0, "lost": 0 };
    m.BaseManager.prototype.getResourceLevel = function (gameState, type, nearbyOnly 
    434434/** check our resource levels and react accordingly */
    435435m.BaseManager.prototype.checkResourceLevels = function (gameState, queues)
    436436{
    437     for (let type of gameState.sharedScript.resourceList)
     437    for (let type of gameState.sharedScript.resourceInfo.codes)
    438438    {
    439439        if (type === "food")
    440440        {
  • binaries/data/mods/public/simulation/ai/petra/chatHelper.js

    diff --git a/binaries/data/mods/public/simulation/ai/petra/chatHelper.js b/binaries/data/mods/public/simulation/ai/petra/chatHelper.js
    index 60999e8..1ef89be 100644
    a b m.chatLaunchAttack = function(gameState, player, type) 
    1717        "message": message,
    1818        "translateMessage": true,
    1919        "translateParameters": ["_player_"],
    20         "parameters": {"_player_": player}
     20        "parameters": { "_player_": player }
    2121    });
    2222};
    2323
    m.chatAnswerRequestAttack = function(gameState, player, answer, other) 
    4545        "message": message,
    4646        "translateMessage": true,
    4747        "translateParameters": ["_player_"],
    48         "parameters": {"_player_": player}
     48        "parameters": { "_player_": player }
    4949    };
    5050    if (other !== undefined)
    5151    {
    m.chatSentTribute = function(gameState, player) 
    7171        "message": message,
    7272        "translateMessage": true,
    7373        "translateParameters": ["_player_"],
    74         "parameters": {"_player_": player}
     74        "parameters": { "_player_": player }
    7575    });
    7676};
    7777
    m.chatRequestTribute = function(gameState, resource) 
    9090        "type": "aichat",
    9191        "message": message,
    9292        "translateMessage": true,
    93         "translateParameters": {"resource": "withinSentence"},
    94         "parameters": {"resource": gameState.sharedScript.resourceNames[resource]}
     93        "translateParameters": { "resource": "withinSentence" },
     94        "parameters": { "resource": gameState.sharedScript.resourceInfo.names[resource] }
    9595    });
    9696};
    9797
    m.chatNewTradeRoute = function(gameState, player) 
    109109        "message": message,
    110110        "translateMessage": true,
    111111        "translateParameters": ["_player_"],
    112         "parameters": {"_player_": player}
     112        "parameters": { "_player_": player }
    113113    });
    114114};
    115115
  • binaries/data/mods/public/simulation/ai/petra/config.js

    diff --git a/binaries/data/mods/public/simulation/ai/petra/config.js b/binaries/data/mods/public/simulation/ai/petra/config.js
    index d5dc6b3..b0f5e7f 100644
    a b m.Config = function(difficulty) 
    103103        "defensive": 0.5
    104104    };
    105105
    106     this.resources = ["food", "wood", "stone", "metal"];
     106    // See m.QueueManager.prototype.wantedGatherRates()
     107    this.queues =
     108    {
     109        "firstTurn": {
     110            "food": 10,
     111            "wood": 10,
     112            "default": 0
     113        },
     114        "short": {
     115            "food": 200,
     116            "wood": 200,
     117            "default": 100
     118        },
     119        "medium": {
     120            "default": 0
     121        },
     122        "long": {
     123            "default": 0
     124        }
     125    };
    107126};
    108127
    109128m.Config.prototype.setConfig = function(gameState)
  • binaries/data/mods/public/simulation/ai/petra/headquarters.js

    diff --git a/binaries/data/mods/public/simulation/ai/petra/headquarters.js b/binaries/data/mods/public/simulation/ai/petra/headquarters.js
    index 3e67da9..871035f 100644
    a b m.HQ.prototype.init = function(gameState, queues) 
    6969    this.navalMap = false;
    7070    this.navalRegions = {};
    7171
    72     for (let res of gameState.sharedScript.resourceList)
     72    for (let res of gameState.sharedScript.resourceInfo.codes)
    7373    {
    7474        this.wantedRates[res] = 0;
    7575        this.currentRates[res] = 0;
    m.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number) 
    653653m.HQ.prototype.getTotalResourceLevel = function(gameState)
    654654{
    655655    let total = {};
    656     for (let res of gameState.sharedScript.resourceList)
     656    for (let res of gameState.sharedScript.resourceInfo.codes)
    657657        total[res] = 0;
    658658    for (let base of this.baseManagers)
    659659        for (let res in total)
  • binaries/data/mods/public/simulation/ai/petra/queueManager.js

    diff --git a/binaries/data/mods/public/simulation/ai/petra/queueManager.js b/binaries/data/mods/public/simulation/ai/petra/queueManager.js
    index 4691d11..671131d 100644
    a b m.QueueManager.prototype.wantedGatherRates = function(gameState) 
    8484    if (gameState.ai.playedTurn === 0)
    8585    {
    8686        let ret = {};
    87         for (let res of gameState.sharedScript.resourceList)
    88             ret[res] = (res === "food" || res === "wood" ) ? 10 : 0;
     87        for (let res of gameState.sharedScript.resourceInfo.codes)
     88            ret[res] = this.Config.queues.firstTurn[res] || this.Config.queues.firstTurn.default;
    8989        return ret;
    9090    }
    9191
    m.QueueManager.prototype.wantedGatherRates = function(gameState) 
    9797    let totalShort = {};
    9898    let totalMedium = {};
    9999    let totalLong = {};
    100     for (let res of gameState.sharedScript.resourceList)
     100    for (let res of gameState.sharedScript.resourceInfo.codes)
    101101    {
    102         totalShort[res] = (res === "food" || res === "wood" ) ? 200 : 100;
    103         totalMedium[res] = 0;
    104         totalLong[res] = 0;
     102        totalShort[res] = this.Config.queues.short[res] || this.Config.queues.short.default;
     103        totalMedium[res] = this.Config.queues.medium[res] || this.Config.queues.medium.default;
     104        totalLong[res] = this.Config.queues.long[res] || this.Config.queues.long.default;
    105105    }
    106106    let total;
    107107    //queueArrays because it's faster.
    m.QueueManager.prototype.wantedGatherRates = function(gameState) 
    133133    // global rates
    134134    let rates = {};
    135135    let diff;
    136     for (let res of gameState.sharedScript.resourceList)
     136    for (let res of gameState.sharedScript.resourceInfo.codes)
    137137    {
    138138        if (current[res] > 0)
    139139        {
  • binaries/data/mods/public/simulation/ai/petra/researchManager.js

    diff --git a/binaries/data/mods/public/simulation/ai/petra/researchManager.js b/binaries/data/mods/public/simulation/ai/petra/researchManager.js
    index f169b39..8e409a1 100644
    a b m.ResearchManager.prototype.researchWantedTechs = function(gameState, techs) 
    100100            let cost = template.cost;
    101101            let costMax = 0;
    102102            for (let res in cost)
    103                 costMax = Math.max(costMax, Math.max(cost[res]-available[res], 0));
     103                if (gameState.sharedScript.resourceInfo.codes.indexOf(res))
     104                    costMax = Math.max(costMax, Math.max(cost[res]-available[res], 0));
    104105            if (10*numWorkers < costMax)
    105106                continue;
    106107        }
  • 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 6a70192..0673536 100644
    a b Cost.prototype.Schema = 
    1919    "<element name='PopulationBonus' a:help='Population cap increase while this entity exists'>" +
    2020        "<data type='nonNegativeInteger'/>" +
    2121    "</element>" +
    22     "<element name='BuildTime' a:help='Time taken to construct/train this unit (in seconds)'>" +
     22    "<element name='BuildTime' a:help='Time taken to construct/train this entity (in seconds)'>" +
    2323        "<ref name='nonNegativeDecimal'/>" +
    2424    "</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>" +
     25    "<element name='Resources' a:help='Resource costs to construct/train this entity'>" +
     26        Resources.BuildSchema("nonNegativeDecimal") +
    3227    "</element>";
    3328
    3429Cost.prototype.Init = function()
    Cost.prototype.GetResourceCosts = function(owner) 
    7065    let entityTemplate = cmpTemplateManager.GetTemplate(entityTemplateName);
    7166
    7267    let costs = {};
    73     for (let r in this.template.Resources)
    74         costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +this.template.Resources[r], owner, entityTemplate);
     68    let resCodes = Resources.GetCodes();
     69
     70    for (let res in this.template.Resources)
     71    {
     72        if (resCodes.indexOf(res) < 0)
     73            continue;
     74        costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, +this.template.Resources[res], owner, entityTemplate);
     75    }
     76
    7577    return costs;
    7678};
    7779
  • 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 533c891..a0923b1 100644
    a b GuiInterface.prototype.GetSimulationState = function() 
    152152    let cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
    153153    ret.barterPrices = cmpBarter.GetPrices();
    154154
     155    // Add Resource Codes, untranslated names and AI Analysis
     156    ret.resources = {
     157        "codes": Resources.GetCodes(),
     158        "names": Resources.GetNames(),
     159        "aiInfluenceGroups": {}
     160    };
     161    for (let res of ret.resources.codes)
     162        ret.resources.aiInfluenceGroups[res] = Resources.GetResource(res).aiAnalysisInfluenceGroup || 0;
     163
    155164    // Add basic statistics to each player
    156165    for (let i = 0; i < numPlayers; ++i)
    157166    {
    GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) 
    12691278
    12701279    let result = {
    12711280        "pieces": [],
    1272         "cost": { "food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0 },
     1281        "cost": { "population": 0, "populationBonus": 0, "time": 0 },
    12731282    };
     1283    for (let res of Resources.GetCodes())
     1284        result.cost[res] = 0;
    12741285
    12751286    let previewEntities = [];
    12761287    if (end.pos)
    GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd) 
    15451556            // copied over, so we need to fetch it from the template instead).
    15461557            // TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
    15471558            // boilerplate that's probably duplicated in tons of places.
    1548             result.cost.food += tplData.cost.food;
    1549             result.cost.wood += tplData.cost.wood;
    1550             result.cost.stone += tplData.cost.stone;
    1551             result.cost.metal += tplData.cost.metal;
    1552             result.cost.population += tplData.cost.population;
    1553             result.cost.populationBonus += tplData.cost.populationBonus;
    1554             result.cost.time += tplData.cost.time;
     1559            for (let res of Resources.GetCodes().concat("population", "populationBonus", "time"))
     1560                result.cost[res] = tplData.cost[res];
    15551561        }
    15561562
    15571563        let canAfford = true;
    GuiInterface.prototype.GetTradingGoods = function(player) 
    19321938    return QueryPlayerIDInterface(player).GetTradingGoods();
    19331939};
    19341940
     1941GuiInterface.prototype.GetBarterPrices = function()
     1942{
     1943    return Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter).GetPrices();
     1944};
     1945
     1946GuiInterface.prototype.PlayerCanBarter = function(player)
     1947{
     1948    let playerEnt = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(player);
     1949    return Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter).PlayerHasMarket(playerEnt);
     1950};
     1951
    19351952GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
    19361953{
    19371954    this.renamedEntities.push(msg);
    let exposedFunctions = { 
    19962013
    19972014    "GetTraderNumber": 1,
    19982015    "GetTradingGoods": 1,
     2016    "GetBarterPrices": 1,
     2017    "PlayerCanBarter": 1,
    19992018};
    20002019
    20012020GuiInterface.prototype.ScriptCall = function(player, name, args)
  • 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..8ce865e 100644
    a b  
    11function Loot() {}
    22
    33Loot.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>";
     4    "<a:help>Specifies the loot credited when this entity is killed.</a:help>" +
     5    "<a:example>" +
     6        "<xp>35</xp>" +
     7        "<metal>10</metal>" +
     8    "</a:example>" +
     9    Resources.BuildSchema("nonNegativeInteger", ["xp"]);
    1910
    2011Loot.prototype.Serialize = null; // we have no dynamic state to save
    2112
    Loot.prototype.GetXp = function() 
    2617
    2718Loot.prototype.GetResources = function()
    2819{
    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     };
     20    let ret = {};
     21    for (let res of Resources.GetCodes())
     22        ret[res] = +(this.template[res] || 0);
     23
     24    return ret;
    3525};
    3626
    3727Engine.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 89986af..796a420 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    // Initial resources and trading goods probability in steps of 5
     43    let resCodes = Resources.GetCodes();
     44    let quotient = Math.floor(20 / resCodes.length);
     45    let remainder = 20 % resCodes.length;
     46    for (let i in resCodes)
     47    {
     48        let res = resCodes[i];
     49        this.resourceCount[res] = 300;
     50        this.resourceNames[res] = Resources.GetResource(res).name;
     51        this.tradingGoods.push({
     52            "goods":  res,
     53            "proba": 5 * (quotient + (+i < remainder ? 1 : 0))
     54        });
     55    }
    5656};
    5757
    5858Player.prototype.SetPlayerID = function(id)
    Player.prototype.UnBlockTraining = function() 
    197197
    198198Player.prototype.SetResourceCounts = function(resources)
    199199{
    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;
     200    for (let res in resources)
     201        if (this.resourceCount[res])
     202            this.resourceCount[res] = resources[res];
    208203};
    209204
    210205Player.prototype.GetResourceCounts = function()
    Player.prototype.SubtractResourcesOrNotify = function(amounts) 
    297292
    298293    // Subtract the resources
    299294    for (var type in amounts)
    300         this.resourceCount[type] -= amounts[type];
     295        if (this.resourceCount[type])
     296            this.resourceCount[type] -= amounts[type];
    301297
    302298    return true;
    303299};
    Player.prototype.GetTradingGoods = function() 
    340336
    341337Player.prototype.SetTradingGoods = function(tradingGoods)
    342338{
    343     var sumProba = 0;
    344     for (var resource in tradingGoods)
     339    let resCodes = Resources.GetCodes();
     340    let sumProba = 0;
     341    for (let resource in tradingGoods)
     342    {
     343        if (resCodes.indexOf(resource) == -1)
     344        {
     345            error("Invalid trading goods: " + uneval(tradingGoods));
     346            return;
     347        }
    345348        sumProba += tradingGoods[resource];
    346     if (sumProba != 100)    // consistency check
     349    }
     350
     351    if (sumProba != 100)
    347352    {
    348         error("Player.js SetTradingGoods: " + uneval(tradingGoods));
    349         tradingGoods = { "food": 20, "wood":20, "stone":30, "metal":30 };
     353        error("Invalid trading goods: " + uneval(tradingGoods));
     354        return;
    350355    }
    351356
    352357    this.tradingGoods = [];
    353     for (var resource in tradingGoods)
    354         this.tradingGoods.push( {"goods": resource, "proba": tradingGoods[resource]} );
     358    for (let resource in tradingGoods)
     359        this.tradingGoods.push({
     360            "goods": resource,
     361            "proba": tradingGoods[resource]
     362        });
    355363};
    356364
    357365Player.prototype.GetState = function()
  • 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 6c72202..e110ab1 100644
    a b ProductionQueue.prototype.Schema = 
    3131        "</element>" +
    3232    "</optional>" +
    3333    "<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>" +
     34        Resources.BuildSchema("nonNegativeDecimal", ["time"]) +
    4135    "</element>";
    4236
    4337ProductionQueue.prototype.Init = function()
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    260254    // TODO: there should probably be a limit on the number of queued batches
    261255    // TODO: there should be a way for the GUI to determine whether it's going
    262256    // to be possible to add a batch (based on resource costs and length limits)
    263     var cmpPlayer = QueryOwnerInterface(this.entity);
     257    let cmpPlayer = QueryOwnerInterface(this.entity);
     258    let resCodes = Resources.GetCodes();
    264259
    265260    if (this.queue.length < MAX_QUEUE_SIZE)
    266261    {
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    293288            var buildTime = ApplyValueModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, cmpPlayer.GetPlayerID(), template);
    294289            var time = timeMult * buildTime;
    295290
    296             for (var r in template.Cost.Resources)
     291            for (let res in template.Cost.Resources)
    297292            {
    298                 costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +template.Cost.Resources[r], cmpPlayer.GetPlayerID(), template);
    299                 totalCosts[r] = Math.floor(count * costs[r]);
     293                if (resCodes.indexOf(res) < 0)
     294                    continue;
     295                costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, +template.Cost.Resources[res], cmpPlayer.GetPlayerID(), template);
     296                totalCosts[res] = Math.floor(count * costs[res]);
    300297            }
    301298
    302299            var population = ApplyValueModificationsToTemplate("Cost/Population",  +template.Cost.Population, cmpPlayer.GetPlayerID(), template);
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    337334            var template = cmpDataTemplateManager.GetTechnologyTemplate(templateName);
    338335            if (!template)
    339336                return;
    340             var cmpPlayer = QueryOwnerInterface(this.entity);
    341337            let techCostMultiplier = this.GetTechCostMultiplier();
    342338            let time =  techCostMultiplier.time * template.researchTime * cmpPlayer.GetCheatTimeMultiplier();
    343339
    344             var cost = {};
     340            let cost = {};
    345341            for (let res in template.cost)
    346                 cost[res] = Math.floor(techCostMultiplier[res] * template.cost[res]);
     342            {
     343                if (resCodes.indexOf(res) < 0)
     344                    continue;
     345                cost[res] = Math.floor((techCostMultiplier[res] || 1) * template.cost[res]);
     346            }
    347347
    348348            // TrySubtractResources should report error to player (they ran out of resources)
    349349            if (!cmpPlayer.TrySubtractResources(cost))
    ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat 
    361361                "player": cmpPlayer.GetPlayerID(),
    362362                "count": 1,
    363363                "technologyTemplate": templateName,
    364                 "resources": deepcopy(template.cost), // need to copy to avoid serialization problems
     364                "resources": cost,
    365365                "productionStarted": false,
    366366                "timeTotal": time*1000,
    367367                "timeRemaining": time*1000,
    ProductionQueue.prototype.RemoveBatch = function(id) 
    433433        // Refund the resource cost for this batch
    434434        var totalCosts = {};
    435435        var cmpStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker);
    436         for each (var r in ["food", "wood", "stone", "metal"])
     436        for (let r of Resources.GetCodes())
    437437        {
     438            if (!item.resources[r])
     439                continue;
    438440            totalCosts[r] = Math.floor(item.count * item.resources[r]);
    439441            if (cmpStatisticsTracker)
    440442                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 819807c..a8c70f7 100644
    a b ResourceDropsite.prototype.Schema = 
    44    "<element name='Types'>" +
    55        "<list>" +
    66            "<zeroOrMore>" +
    7                 "<choice>" +
    8                     "<value>food</value>" +
    9                     "<value>wood</value>" +
    10                     "<value>stone</value>" +
    11                     "<value>metal</value>" +
    12                 "</choice>" +
     7                Resources.BuildChoicesSchema() +
    138            "</zeroOrMore>" +
    149        "</list>" +
    1510    "</element>" +
    ResourceDropsite.prototype.Init = function() 
    2419};
    2520
    2621/**
    27  * Returns the list of resource types accepted by this dropsite.
     22 * Returns the list of resource types accepted by this dropsite,
     23 * as defined by it being referred to in the template and the resource being enabled.
    2824 */
    2925ResourceDropsite.prototype.GetTypes = function()
    3026{
    3127    let types = ApplyValueModificationsToEntity("ResourceDropsite/Types", this.template.Types, this.entity);
    32     return types ? types.split(/\s+/) : [];
     28    let resources = Resources.GetCodes();
     29    return types.split(/\s+/).filter(type => resources.indexOf(type.toLowerCase()) != -1);
    3330};
    3431
    3532/**
  • 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..3a40376 100644
    a b ResourceGatherer.prototype.Schema = 
    2525        "<ref name='positiveDecimal'/>" +
    2626    "</element>" +
    2727    "<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>" +
     28        Resources.BuildSchema("positiveDecimal", ["treasure"], true) +
    4929    "</element>" +
    5030    "<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>" +
     31        Resources.BuildSchema("positiveDecimal") +
    5732    "</element>";
    5833
    5934ResourceGatherer.prototype.Init = function()
    ResourceGatherer.prototype.RecalculateGatherRatesAndCapacities = function() 
    137112    this.rates = {};
    138113    for (let r in this.template.Rates)
    139114    {
     115        let type = r.split(".");
     116        let res = Resources.GetResource(type[0]);
     117
     118        if (!res && type[0] !== "treasure" || type.length > 1 && !res.subtypes[type[1]])
     119            continue;
     120
    140121        let rate = ApplyValueModificationsToEntity("ResourceGatherer/Rates/" + r, +this.template.Rates[r], this.entity);
    141122        this.rates[r] = rate * this.baseSpeed;
    142123    }
    ResourceGatherer.prototype.GetRange = function() 
    174155
    175156/**
    176157 * Try to gather treasure
    177  * @return 'true' if treasure is successfully gathered and 'false' in the other case
     158 * @return 'true' if treasure is successfully gathered, otherwise 'false'
    178159 */
    179160ResourceGatherer.prototype.TryInstantGather = function(target)
    180161{
  • 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..367810d 100644
    a b ResourceSupply.prototype.Schema = 
    1212    "<element name='Amount' a:help='Amount of resources available from this entity'>" +
    1313        "<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" +
    1414    "</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>" +
     15    "<element name='Type' a:help='Type and Subtype of resource available from this entity'>" +
     16        Resources.BuildChoicesSchema(true, true) +
    3217    "</element>" +
    3318    "<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
    3419        "<data type='nonNegativeInteger'/>" +
    ResourceSupply.prototype.Init = function() 
    4530    this.amount = this.GetMaxAmount();
    4631
    4732    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.
     33    let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); // system component so that's safe.
     34    let numPlayers = cmpPlayerManager.GetNumPlayers();
     35    for (let i = 0; i <= numPlayers; ++i)   // use "<=" because we want Gaia too.
    5136        this.gatherers.push([]);
    5237
    5338    this.infinite = !isFinite(+this.template.Amount);
    5439
    55     [this.type,this.subType] = this.template.Type.split('.');
    56     this.cachedType = { "generic" : this.type, "specific" : this.subType };
     40    [this.type, this.subtype] = this.template.Type.split('.');
     41    let resData = Resources.GetResource(this.type);
     42    if (this.type === "treasure")
     43        resData = { "subtypes": Resources.GetNames() };
    5744
     45    // Remove entity from gameworld if the resource supplied by this entity is disabled or not valid.
     46    if (!resData || !resData.subtypes[this.subtype])
     47        Engine.DestroyEntity(this.entity);
     48
     49    this.cachedType = { "generic": this.type, "specific": this.subtype };
    5850};
    5951
    6052ResourceSupply.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..f194a1f 100644
    a b function ResourceTrickle() {} 
    33ResourceTrickle.prototype.Schema =
    44    "<a:help>Controls the resource trickle ability of the unit.</a:help>" +
    55    "<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>" +
     6        Resources.BuildSchema("nonNegativeDecimal") +
    287    "</element>" +
    298    "<element name='Interval' a:help='Number of miliseconds must pass for the player to gain the next trickle.'>" +
    309        "<ref name='nonNegativeDecimal'/>" +
    ResourceTrickle.prototype.GetTimer = function() 
    4524
    4625ResourceTrickle.prototype.GetRates = function()
    4726{
    48     var rates = {};
    49     for (var resource in this.template.Rates)
     27    let rates = {};
     28    let resCodes = Resources.GetCodes();
     29    for (let resource in this.template.Rates)
     30    {
     31        if (resCodes.indexOf(resource) < 0)
     32            continue;
    5033        rates[resource] = ApplyValueModificationsToEntity("ResourceTrickle/Rates/"+resource, +this.template.Rates[resource], this.entity);
     34    }
    5135
    5236    return rates;
    5337};
    ResourceTrickle.prototype.GetRates = function() 
    5539// Do the actual work here
    5640ResourceTrickle.prototype.Trickle = function(data, lateness)
    5741{
    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]);
     42    let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     43    if (cmpPlayer)
     44        cmpPlayer.AddResources(this.GetRates());
    6545};
    6646
    6747Engine.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 3bc1f79..482502d 100644
    a b StatisticsTracker.prototype.Init = function() 
    105105    this.buildingsCapturedValue = 0;
    106106
    107107    this.resourcesGathered = {
    108         "food": 0,
    109         "wood": 0,
    110         "metal": 0,
    111         "stone": 0,
    112108        "vegetarianFood": 0
    113109    };
    114     this.resourcesUsed = {
    115         "food": 0,
    116         "wood": 0,
    117         "metal": 0,
    118         "stone": 0
    119     };
    120     this.resourcesSold = {
    121         "food": 0,
    122         "wood": 0,
    123         "metal": 0,
    124         "stone": 0
    125     };
    126     this.resourcesBought = {
    127         "food": 0,
    128         "wood": 0,
    129         "metal": 0,
    130         "stone": 0
    131     };
     110    this.resourcesUsed = {};
     111    this.resourcesSold = {};
     112    this.resourcesBought = {};
     113    for (let res of Resources.GetCodes())
     114    {
     115        this.resourcesGathered[res] = 0;
     116        this.resourcesUsed[res] = 0;
     117        this.resourcesSold[res] = 0;
     118        this.resourcesBought[res] = 0;
     119    }
    132120
    133121    this.tributesSent = 0;
    134122    this.tributesReceived = 0;
    StatisticsTracker.prototype.IncreaseResourceGatheredCounter = function(type, amo 
    347335 */
    348336StatisticsTracker.prototype.IncreaseResourceUsedCounter = function(type, amount)
    349337{
    350     this.resourcesUsed[type] += amount;
     338    if (typeof this.resourcesUsed[type] === "number")
     339        this.resourcesUsed[type] += amount;
    351340};
    352341
    353342StatisticsTracker.prototype.IncreaseTreasuresCollectedCounter = function()
  • 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 735778d..771a814 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 =
  • binaries/data/mods/public/simulation/components/Upgrade.js

    diff --git a/binaries/data/mods/public/simulation/components/Upgrade.js b/binaries/data/mods/public/simulation/components/Upgrade.js
    index 6179076..f185bf0 100644
    a b Upgrade.prototype.Schema = 
    2929                    "<element name='Cost' a:help='Resource cost to upgrade this unit'>" +
    3030                        "<oneOrMore>" +
    3131                            "<choice>" +
    32                                 "<element name='food'><data type='nonNegativeInteger'/></element>" +
    33                                 "<element name='wood'><data type='nonNegativeInteger'/></element>" +
    34                                 "<element name='stone'><data type='nonNegativeInteger'/></element>" +
    35                                 "<element name='metal'><data type='nonNegativeInteger'/></element>" +
     32                                Resources.BuildSchema("nonNegativeInteger") +
    3633                            "</choice>" +
    3734                        "</oneOrMore>" +
    3835                    "</element>" +
  • binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js

    diff --git a/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js b/binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
    index 6ae026e..e90d4b6 100644
    a b Engine.LoadComponentScript("interfaces/Upgrade.js"); 
    3333Engine.LoadComponentScript("interfaces/BuildingAI.js");
    3434Engine.LoadComponentScript("GuiInterface.js");
    3535
     36Resources = {
     37    "GetCodes": () => ["food", "metal", "stone", "wood"],
     38    "GetNames": () => ({
     39        "food": "Food",
     40        "metal": "Metal",
     41        "stone": "Stone",
     42        "wood": "Wood"
     43    }),
     44    "GetResource": () => ({}),
     45};
     46
    3647var cmp = ConstructComponent(SYSTEM_ENTITY, "GuiInterface");
    3748
    3849
    TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), { 
    330341    circularMap: false,
    331342    timeElapsed: 0,
    332343    gameType: "conquest",
    333     barterPrices: {buy: {food: 150}, sell: {food: 25}}
     344    "barterPrices": {
     345        "buy": { "food": 150 },
     346        "sell": { "food": 25 }
     347    },
     348    "resources": {
     349        "codes": ["food", "metal", "stone", "wood"],
     350        "names": {
     351            "food": "Food",
     352            "metal": "Metal",
     353            "stone": "Stone",
     354            "wood": "Wood",
     355        },
     356        "aiInfluenceGroups": {
     357            "food": 0,
     358            "metal": 0,
     359            "stone": 0,
     360            "wood": 0,
     361        }
     362    },
    334363});
    335364
    336365TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
    TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), { 
    449478    circularMap: false,
    450479    timeElapsed: 0,
    451480    gameType: "conquest",
    452     barterPrices: {buy: {food: 150}, sell: {food: 25}}
     481    "barterPrices": {
     482        "buy": { "food": 150 },
     483        "sell": { "food": 25 }
     484    },
     485    resources: {
     486        codes: ["food", "metal", "stone", "wood"],
     487        names: {
     488            "food": "Food",
     489            "metal": "Metal",
     490            "stone": "Stone",
     491            "wood": "Wood",
     492        },
     493        aiInfluenceGroups: {
     494            "food": 0,
     495            "metal": 0,
     496            "stone": 0,
     497            "wood": 0,
     498        }
     499    },
    453500});
    454501
    455502
  • binaries/data/mods/public/simulation/components/tests/test_Player.js

    diff --git a/binaries/data/mods/public/simulation/components/tests/test_Player.js b/binaries/data/mods/public/simulation/components/tests/test_Player.js
    index abb2d73..3d8136e 100644
    a b Engine.LoadComponentScript("interfaces/Player.js"); 
    33Engine.LoadComponentScript("interfaces/TechnologyManager.js");
    44Engine.LoadComponentScript("Player.js");
    55
     6Resources = {
     7    "GetCodes": () => ["food", "metal", "stone", "wood"],
     8    "GetResource": () => ({}),
     9};
     10
    611var cmpPlayer = ConstructComponent(10, "Player");
    712
    813TS_ASSERT_EQUALS(cmpPlayer.GetPopulationCount(), 0);
  • 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..5d4461b
    - +  
     1{
     2    "code": "food",
     3    "name": "Food",
     4    "order": 1,
     5    "subtypes": {
     6        "fish": "Fish",
     7        "fruit": "Fruit",
     8        "grain": "Grain",
     9        "meat": "Meat",
     10        "milk": "Milk"
     11    },
     12    "truePrice": 100,
     13    "aiAnalysisInfluenceGroup": 0,
     14    "enabled": true
     15}
  • 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..286fda4
    - +  
     1{
     2    "code": "metal",
     3    "name": "Metal",
     4    "order": 4,
     5    "subtypes": {
     6        "ore": "Ore"
     7    },
     8    "truePrice": 100,
     9    "aiAnalysisInfluenceGroup": 2,
     10    "enabled": true
     11}
  • 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..e78272e
    - +  
     1{
     2    "code": "stone",
     3    "name": "Stone",
     4    "order": 3,
     5    "subtypes": {
     6        "rock": "Rock",
     7        "ruins": "Ruins"
     8    },
     9    "truePrice": 100,
     10    "aiAnalysisInfluenceGroup": 2,
     11    "enabled": true
     12}
  • 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..25f90a1
    - +  
     1{
     2    "code": "wood",
     3    "name": "Wood",
     4    "order": 2,
     5    "subtypes": {
     6        "tree": "Tree",
     7        "ruins": "Ruins"
     8    },
     9    "truePrice": 100,
     10    "aiAnalysisInfluenceGroup": 1,
     11    "enabled": true
     12}
  • 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..fa5cac0
    - +  
     1/**
     2 * Builds a RelaxRNG schema based on currently valid elements.
     3 *
     4 * To prevent validation errors, disabled resources are included in the schema.
     5 *
     6 * @param datatype - The datatype of the element
     7 * @param additional - Array of additional data elements. Time, xp, treasure, etc.
     8 * @param subtypes - If true, resource subtypes will be included as well.
     9 * @return RelaxNG schema string
     10 */
     11Resources.prototype.BuildSchema = function(datatype, additional = [], subtypes = false)
     12{
     13    if (!datatype)
     14        return "";
     15
     16    switch (datatype)
     17    {
     18    case "decimal":
     19    case "nonNegativeDecimal":
     20    case "positiveDecimal":
     21        datatype = "<ref name='" + datatype + "'/>";
     22        break;
     23
     24    default:
     25        datatype = "<data type='" + datatype + "'/>";
     26    }
     27
     28    let resCodes = this.resourceData.map(resource => resource.code);
     29    let schema = "";
     30    for (let res of resCodes.concat(additional))
     31        schema +=
     32            "<optional>" +
     33                "<element name='" + res + "'>" +
     34                    datatype +
     35                "</element>" +
     36            "</optional>";
     37
     38    if (!subtypes)
     39        return "<interleave>" + schema + "</interleave>";
     40
     41    for (let res of this.resourceData)
     42        for (let subtype in res.subtypes)
     43            schema +=
     44                "<optional>" +
     45                    "<element name='" + res.code + "." + subtype + "'>" +
     46                        datatype +
     47                    "</element>" +
     48                "</optional>";
     49
     50    if (additional.indexOf("treasure") !== -1)
     51        for (let res of resCodes)
     52            schema +=
     53                "<optional>" +
     54                    "<element name='" + "treasure." + res + "'>" +
     55                        datatype +
     56                    "</element>" +
     57                "</optional>";
     58
     59    return "<interleave>" + schema + "</interleave>";
     60}
     61
     62/**
     63 * Builds the value choices for a RelaxNG `<choice></choice>` object, based on currently valid resources.
     64 *
     65 * @oaram subtypes - If set to true, the choices returned will be resource subtypes, rather than main types
     66 * @param treasure - If set to true, the pseudo resource 'treasure' (or its subtypes) will be included
     67 * @return String of RelaxNG Schema `<choice/>` values.
     68 */
     69Resources.prototype.BuildChoicesSchema = function(subtypes = false, treasure = false)
     70{
     71    let schema = "";
     72
     73    if (!subtypes)
     74    {
     75        let resCodes = this.resourceData.map(resource => resource.code);
     76        if (treasure)
     77            resCodes.push("treasure");
     78        for (let res of resCodes)
     79            schema += "<value>" + res + "</value>";
     80    }
     81    else
     82        for (let res of this.resourceData)
     83        {
     84            for (let subtype in res.subtypes)
     85                schema += "<value>" + res.code + "." + subtype + "</value>";
     86            if (treasure)
     87                schema += "<value>" + "treasure." + res.code + "</value>";
     88        }
     89
     90    return "<choice>" + schema + "</choice>";
     91}
     92
     93Resources = new Resources();