Index: binaries/data/mods/public/globalscripts/Resources.js =================================================================== --- binaries/data/mods/public/globalscripts/Resources.js +++ binaries/data/mods/public/globalscripts/Resources.js @@ -7,6 +7,12 @@ this.resourceDataObj = {}; this.resourceCodes = []; this.resourceNames = {}; + this.resourceTradable = {}; + this.resourceTradables = []; + this.resourceBarterable = {}; + this.resourceBarterables = []; + this.resourceTributable = {}; + this.resourceTributables = []; for (let filename of Engine.ListDirectoryFiles("simulation/data/resources/", "*.json", false)) { @@ -28,6 +34,26 @@ this.resourceDataObj[data.code] = data; this.resourceCodes.push(data.code); this.resourceNames[data.code] = data.name; + + // Specify whether resources can be exchanged + this.resourceTradable[data.code] = data.tradable; + if (data.tradable == true) + { + this.resourceTradables.push(data.code); + } + + this.resourceBarterable[data.code] = data.barterable; + if (data.barterable == true) + { + this.resourceBarterables.push(data.code); + } + + this.resourceTributable[data.code] = data.tributable; + if (data.tributable == true) + { + this.resourceTributables.push(data.code); + } + for (let subres in data.subtypes) this.resourceNames[subres] = data.subtypes[subres]; } @@ -43,10 +69,31 @@ this.resourceData.find(resource => resource.code == b) )); + this.resourceTradables.sort((a, b) => resSort( + this.resourceData.find(resource => resource.code == a), + this.resourceData.find(resource => resource.code == b) + )); + + this.resourceBarterables.sort((a, b) => resSort( + this.resourceData.find(resource => resource.code == a), + this.resourceData.find(resource => resource.code == b) + )); + + this.resourceTributables.sort((a, b) => resSort( + this.resourceData.find(resource => resource.code == a), + this.resourceData.find(resource => resource.code == b) + )); + deepfreeze(this.resourceData); deepfreeze(this.resourceDataObj); deepfreeze(this.resourceCodes); deepfreeze(this.resourceNames); + deepfreeze(this.resourceTradables); + deepfreeze(this.resourceTradable); + deepfreeze(this.resourceBarterables); + deepfreeze(this.resourceBarterable); + deepfreeze(this.resourceTributables); + deepfreeze(this.resourceTributable); } /** @@ -83,3 +130,57 @@ { return this.resourceNames; }; + +/** + * Returns an array with tradable resource codes. + * @return {string[]} data of the form [ "food", "metal", ... ] + */ +Resources.prototype.GetTradables = function() +{ + return this.resourceTradables; +}; + +/** + * Returns an object mapping resource codes whether it is tradable or not. + * @return {object} data of the form { "food": "true", "metal": "false", ... } + */ +Resources.prototype.GetTradable = function() +{ + return this.resourceTradable; +}; + +/** + * Returns an array with barterable resource codes. + * @return {string[]} data of the form [ "food", "metal", ... ] + */ +Resources.prototype.GetBarterables = function() +{ + return this.resourceBarterables; +}; + +/** + * Returns an object mapping resource codes whether it is barterable or not. + * @return {object} data of the form { "food": "true", "metal": "false", ... } + */ +Resources.prototype.GetBarterable = function() +{ + return this.resourceBarterable; +}; + +/** + * Returns an array with tributable resource codes. + * @return {string[]} data of the form [ "food", "metal", ... ] + */ +Resources.prototype.GetTributables = function() +{ + return this.resourceTributables; +}; + +/** + * Returns an object mapping resource codes whether it is tributable or not. + * @return {object} data of the form { "food": "true", "metal": "false", ... } + */ +Resources.prototype.GetTributable = function() +{ + return this.resourceTributable; +}; Index: binaries/data/mods/public/gui/session/menu.js =================================================================== --- binaries/data/mods/public/gui/session/menu.js +++ binaries/data/mods/public/gui/session/menu.js @@ -290,7 +290,7 @@ let size = dialog.size; let tribSize = Engine.GetGUIObjectByName("diplomacyPlayer[0]_tribute[0]").size; - let widthOffset = g_ResourceData.GetCodes().length * (tribSize.right - tribSize.left) / 2; + let widthOffset = g_ResourceData.GetTributables().length * (tribSize.right - tribSize.left) / 2; size.left -= widthOffset; size.right += widthOffset; @@ -491,7 +491,7 @@ function diplomacyFormatTributeButtons(i, hidden) { - let resCodes = g_ResourceData.GetCodes(); + let resCodes = g_ResourceData.GetTributables(); let r = 0; for (let resCode of resCodes) { @@ -638,7 +638,7 @@ let width = size.right - size.left; let tradeSize = Engine.GetGUIObjectByName("tradeResource[0]").size; - width += g_ResourceData.GetCodes().length * (tradeSize.right - tradeSize.left); + width += g_ResourceData.GetTradables().length * (tradeSize.right - tradeSize.left); size.left = -width / 2; size.right = width / 2; @@ -656,8 +656,9 @@ let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer); let button = {}; - let resCodes = g_ResourceData.GetCodes(); - let currTradeSelection = resCodes[0]; + let resTradeCodes = g_ResourceData.GetTradables(); + let resBarterCodes = g_ResourceData.GetBarterables(); + let currTradeSelection = resTradeCodes[0]; let updateTradeButtons = function() { @@ -671,12 +672,13 @@ } }; - hideRemaining("tradeResources", resCodes.length); + hideRemaining("tradeResources", resTradeCodes.length); Engine.GetGUIObjectByName("tradeHelp").hidden = false; - for (let i = 0; i < resCodes.length; ++i) + // Barter: + for (let i = 0; i < resBarterCodes.length; ++i) { - let resCode = resCodes[i]; + let resBarterCode = resBarterCodes[i]; let barterResource = Engine.GetGUIObjectByName("barterResource[" + i + "]"); if (!barterResource) @@ -685,11 +687,14 @@ break; } - // Barter: - barterOpenCommon(resCode, i, "barter"); + barterOpenCommon(resBarterCode, i, "barter"); setPanelObjectPosition(barterResource, i, i + 1); + } + // Trade: + for (let i = 0; i < resTradeCodes.length; ++i) + { + let resTradeCode = resTradeCodes[i]; - // Trade: let tradeResource = Engine.GetGUIObjectByName("tradeResource[" + i + "]"); if (!tradeResource) { @@ -700,19 +705,19 @@ setPanelObjectPosition(tradeResource, i, i + 1); let icon = Engine.GetGUIObjectByName("tradeResourceIcon[" + i + "]"); - icon.sprite = "stretched:session/icons/resources/" + resCode + ".png"; + icon.sprite = "stretched:session/icons/resources/" + resTradeCode + ".png"; let buttonUp = Engine.GetGUIObjectByName("tradeArrowUp[" + i + "]"); let buttonDn = Engine.GetGUIObjectByName("tradeArrowDn[" + i + "]"); - button[resCode] = { + button[resTradeCode] = { "up": buttonUp, "dn": buttonDn, "label": Engine.GetGUIObjectByName("tradeResourceText[" + i + "]"), "sel": Engine.GetGUIObjectByName("tradeResourceSelection[" + i + "]") }; - proba[resCode] = proba[resCode] || 0; + proba[resTradeCode] = proba[resTradeCode] || 0; let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton[" + i + "]"); buttonResource.enabled = controlsPlayer(g_ViewedPlayer); @@ -720,7 +725,7 @@ return () => { if (Engine.HotkeyIsPressed("session.fulltradeswap")) { - for (let res of resCodes) + for (let res of resTradeCodes) proba[res] = 0; proba[resource] = 100; Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); @@ -728,7 +733,7 @@ currTradeSelection = resource; updateTradeButtons(); }; - })(resCode); + })(resTradeCode); buttonUp.enabled = controlsPlayer(g_ViewedPlayer); buttonUp.onPress = (resource => { @@ -738,7 +743,7 @@ Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); updateTradeButtons(); }; - })(resCode); + })(resTradeCode); buttonDn.enabled = controlsPlayer(g_ViewedPlayer); buttonDn.onPress = (resource => { @@ -748,7 +753,7 @@ Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); updateTradeButtons(); }; - })(resCode); + })(resTradeCode); } updateTradeButtons(); @@ -765,7 +770,7 @@ function initBarterButtons() { - g_BarterSell = g_ResourceData.GetCodes()[0]; + g_BarterSell = g_ResourceData.GetBarterables()[0]; } /** @@ -873,7 +878,9 @@ Engine.GetGUIObjectByName("barterHelp").hidden = !canBarter; if (canBarter) - g_ResourceData.GetCodes().forEach((resCode, i) => { barterUpdateCommon(resCode, i, "barter", g_ViewedPlayer) }); + g_ResourceData.GetBarterables().forEach((resCode, i) => { + barterUpdateCommon(resCode, i, "barter", g_ViewedPlayer); + }); } function getIdleLandTradersText(traderNumber) Index: binaries/data/mods/public/gui/session/selection_panels.js =================================================================== --- binaries/data/mods/public/gui/session/selection_panels.js +++ binaries/data/mods/public/gui/session/selection_panels.js @@ -84,9 +84,9 @@ "getItems": function(unitEntStates) { // If more than `rowLength` resources, don't display icons. - if (unitEntStates.every(state => !state.isBarterMarket) || g_ResourceData.GetCodes().length > this.rowLength) + if (unitEntStates.every(state => !state.isBarterMarket) || g_ResourceData.GetBarterables().length > this.rowLength) return []; - return g_ResourceData.GetCodes(); + return g_ResourceData.GetBarterables(); }, "setupButton": function(data) { Index: binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js +++ binaries/data/mods/public/simulation/ai/petra/diplomacyManager.js @@ -80,6 +80,9 @@ let toSend = false; for (let res in allyResources) { + // Do not send resources which are untributable + if (!Resources.GetTributable[res]) + continue; if (donor && availableResources[res] > 200 && allyResources[res] < 0.2 * availableResources[res]) { tribute[res] = Math.floor(0.3*availableResources[res] - allyResources[res]); @@ -403,7 +406,17 @@ else { response = "acceptWithTribute"; - requiredTribute = gameState.ai.HQ.pickMostNeededResources(gameState)[0]; + + // If a resource is untributable, do not request it. + // TODO: What to do when no resources are tributable? + let i = 0; + while (i < Resources.GetCodes().length) + { + requiredTribute = gameState.ai.HQ.pickMostNeededResources(gameState)[i]; + if (Resources.GetTributable[requiredTribute]) + break; + i++; + } requiredTribute.wanted = Math.max(1000, gameState.getOwnUnits().length * (requestType === "ally" ? 10 : 5)); this.receivedDiplomacyRequests.set(player, { "status": "waitingForTribute", Index: binaries/data/mods/public/simulation/ai/petra/tradeManager.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/tradeManager.js +++ binaries/data/mods/public/simulation/ai/petra/tradeManager.js @@ -162,7 +162,7 @@ m.TradeManager.prototype.setTradingGoods = function(gameState) { let tradingGoods = {}; - for (let res of Resources.GetCodes()) + for (let res of Resources.GetTradables()) tradingGoods[res] = 0; // first, try to anticipate future needs let stocks = gameState.ai.HQ.getTotalResourceLevel(gameState); @@ -230,7 +230,7 @@ let getBarterRate = (prices, buy, sell) => Math.round(100 * prices.sell[sell] / prices.buy[buy]); // loop through each missing resource checking if we could barter and help finishing a queue quickly. - for (let buy of Resources.GetCodes()) + for (let buy of Resources.GetBarterables()) { // Check if our rate allows to gather it fast enough if (needs[buy] == 0 || needs[buy] < rates[buy] * 30) @@ -239,7 +239,7 @@ // Pick the best resource to barter. let bestToSell; let bestRate = 0; - for (let sell of Resources.GetCodes()) + for (let sell of Resources.GetBarterables()) { if (sell == buy) continue; @@ -295,7 +295,7 @@ return false; let bestToBuy; let bestChoice = 0; - for (let buy of Resources.GetCodes()) + for (let buy of Resources.GetBarterables()) { if (buy == "food") continue; Index: binaries/data/mods/public/simulation/components/Barter.js =================================================================== --- binaries/data/mods/public/simulation/components/Barter.js +++ binaries/data/mods/public/simulation/components/Barter.js @@ -29,7 +29,7 @@ Barter.prototype.Init = function() { this.priceDifferences = {}; - for (let resource of Resources.GetCodes()) + for (let resource of Resources.GetBarterables()) this.priceDifferences[resource] = 0; this.restoreTimer = undefined; }; @@ -38,7 +38,7 @@ { var prices = { "buy": {}, "sell": {} }; let multiplier = QueryPlayerIDInterface(playerID).GetBarterMultiplier(); - for (let resource of Resources.GetCodes()) + for (let resource of Resources.GetBarterables()) { let truePrice = Resources.GetResource(resource).truePrice; prices.buy[resource] = truePrice * (100 + this.CONSTANT_DIFFERENCE + this.priceDifferences[resource]) * multiplier.buy[resource] / 100; @@ -69,7 +69,7 @@ return; } - let availResources = Resources.GetCodes(); + let availResources = Resources.GetBarterables(); if (availResources.indexOf(resourceToSell) == -1) { warn("ExchangeResources: incorrect resource to sell: " + uneval(resourceToSell)); @@ -136,7 +136,7 @@ Barter.prototype.ProgressTimeout = function(data) { var needRestore = false; - for (let resource of Resources.GetCodes()) + for (let resource of Resources.GetBarterables()) { // Calculate value to restore, it should be limited to [-DIFFERENCE_RESTORE; DIFFERENCE_RESTORE] interval var differenceRestore = Math.min(this.DIFFERENCE_RESTORE, Math.max(-this.DIFFERENCE_RESTORE, this.priceDifferences[resource])); Index: binaries/data/mods/public/simulation/components/Player.js =================================================================== --- binaries/data/mods/public/simulation/components/Player.js +++ binaries/data/mods/public/simulation/components/Player.js @@ -80,15 +80,21 @@ "sell": clone(this.template.BarterMultiplier.Sell) }; - // Initial resources and trading goods probability in steps of 5 + // Initial resources let resCodes = Resources.GetCodes(); - let quotient = Math.floor(20 / resCodes.length); - let remainder = 20 % resCodes.length; for (let i in resCodes) { let res = resCodes[i]; this.resourceCount[res] = 300; this.resourceNames[res] = Resources.GetResource(res).name; + } + // Trading goods probability in steps of 5 + let resTradeCodes = Resources.GetTradables(); + let quotient = Math.floor(20 / resTradeCodes.length); + let remainder = 20 % resTradeCodes.length; + for (let i in resTradeCodes) + { + let res = resTradeCodes[i]; this.tradingGoods.push({ "goods": res, "proba": 5 * (quotient + (+i < remainder ? 1 : 0)) @@ -411,16 +417,24 @@ Player.prototype.GetTradingGoods = function() { - var tradingGoods = {}; + let resTradable = Resources.GetTradable(); + let tradingGoods = {}; for (let resource of this.tradingGoods) + { + if (!resTradable[resource.goods]) + { + warn("Not allowed to trade: " + uneval(resource.goods)); + continue; + } tradingGoods[resource.goods] = resource.proba; + } return tradingGoods; }; Player.prototype.SetTradingGoods = function(tradingGoods) { - let resCodes = Resources.GetCodes(); + let resCodes = Resources.GetTradables(); let sumProba = 0; for (let resource in tradingGoods) { @@ -434,7 +448,7 @@ if (sumProba != 100) { - error("Invalid trading goods: " + uneval(tradingGoods)); + error("Invalid trading goods probability: " + uneval(sumProba)); return; } @@ -866,7 +880,7 @@ return; for (let resCode in amounts) - if (Resources.GetCodes().indexOf(resCode) == -1 || + if (Resources.GetTributables().indexOf(resCode) == -1 || !Number.isInteger(amounts[resCode]) || amounts[resCode] < 0) { Index: binaries/data/mods/public/simulation/components/tests/test_Barter.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Barter.js +++ binaries/data/mods/public/simulation/components/tests/test_Barter.js @@ -10,7 +10,7 @@ const truePrice = 110; Resources = { - "GetCodes": () => ["wood", "stone", "metal"], + "GetBarterables": () => ["wood", "stone", "metal"], "GetResource": (resource) => ({ "truePrice": truePrice }) }; Index: binaries/data/mods/public/simulation/components/tests/test_Player.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Player.js +++ binaries/data/mods/public/simulation/components/tests/test_Player.js @@ -1,8 +1,20 @@ -Resources = { - "GetCodes": () => ["food", "metal", "stone", "wood"], +var Resources = { + "GetTradables": () => ["food", "metal", "stone", "wood"], "GetResource": () => ({}), "BuildSchema": (type) => { let schema = ""; + for (let res of Resources.GetTradables()) + schema += + "" + + "" + + "" + + "" + + ""; + return "" + schema + ""; + }, + "GetCodes": () => ["food", "metal", "stone", "wood"], + "BuildSchema": (type) => { + let schema = ""; for (let res of Resources.GetCodes()) schema += "" + @@ -11,6 +23,30 @@ "" + ""; return "" + schema + ""; + }, + "GetBarterables": () => ["food", "metal", "stone", "wood"], + "BuildSchema": (type) => { + let schema = ""; + for (let res of Resources.GetBarterables()) + schema += + "" + + "" + + "" + + "" + + ""; + return "" + schema + ""; + }, + "GetTributables": () => ["food", "metal", "stone", "wood"], + "BuildSchema": (type) => { + let schema = ""; + for (let res of Resources.GetTributables()) + schema += + "" + + "" + + "" + + "" + + ""; + return "" + schema + ""; } }; Index: binaries/data/mods/public/simulation/data/resources/food.json =================================================================== --- binaries/data/mods/public/simulation/data/resources/food.json +++ binaries/data/mods/public/simulation/data/resources/food.json @@ -10,5 +10,8 @@ "meat": "Meat" }, "truePrice": 100, - "aiAnalysisInfluenceGroup": "ignore" + "aiAnalysisInfluenceGroup": "ignore", + "tradable": true, + "barterable": true, + "tributable": true } Index: binaries/data/mods/public/simulation/data/resources/metal.json =================================================================== --- binaries/data/mods/public/simulation/data/resources/metal.json +++ binaries/data/mods/public/simulation/data/resources/metal.json @@ -7,5 +7,8 @@ "ore": "Ore" }, "truePrice": 100, - "aiAnalysisInfluenceGroup": "sparse" + "aiAnalysisInfluenceGroup": "sparse", + "tradable": true, + "barterable": true, + "tributable": true } Index: binaries/data/mods/public/simulation/data/resources/stone.json =================================================================== --- binaries/data/mods/public/simulation/data/resources/stone.json +++ binaries/data/mods/public/simulation/data/resources/stone.json @@ -8,5 +8,8 @@ "ruins": "Ruins" }, "truePrice": 100, - "aiAnalysisInfluenceGroup": "sparse" + "aiAnalysisInfluenceGroup": "sparse", + "tradable": true, + "barterable": true, + "tributable": true } Index: binaries/data/mods/public/simulation/data/resources/wood.json =================================================================== --- binaries/data/mods/public/simulation/data/resources/wood.json +++ binaries/data/mods/public/simulation/data/resources/wood.json @@ -8,5 +8,8 @@ "ruins": "Ruins" }, "truePrice": 100, - "aiAnalysisInfluenceGroup": "abundant" + "aiAnalysisInfluenceGroup": "abundant", + "tradable": true, + "barterable": true, + "tributable": true }