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,7 @@ this.resourceDataObj = {}; this.resourceCodes = []; this.resourceNames = {}; + this.resourceCodesByProperty = {}; for (let filename of Engine.ListDirectoryFiles("simulation/data/resources/", "*.json", false)) { @@ -30,27 +31,36 @@ this.resourceNames[data.code] = data.name; for (let subres in data.subtypes) this.resourceNames[subres] = data.subtypes[subres]; + + for (let property in data.properties) + this.resourceCodesByProperty[data.properties[property]] ? + this.resourceCodesByProperty[data.properties[property]].push(data.code) : + this.resourceCodesByProperty[data.properties[property]] = [data.code]; } // Sort arrays by specified order - let resSort = (a, b) => + let resDataSort = (a, b) => a.order < b.order ? -1 : a.order > b.order ? +1 : 0; - - this.resourceData.sort(resSort); - this.resourceCodes.sort((a, b) => resSort( + let resSort = (a, b) => resDataSort( this.resourceData.find(resource => resource.code == a), this.resourceData.find(resource => resource.code == b) - )); + ); + + this.resourceData.sort(resDataSort); + this.resourceCodes.sort(resSort); + for (let property in this.resourceCodesByProperty) + this.resourceCodesByProperty[property].sort(resSort); deepfreeze(this.resourceData); deepfreeze(this.resourceDataObj); deepfreeze(this.resourceCodes); deepfreeze(this.resourceNames); + deepfreeze(this.resourceCodesByProperty); } /** - * Returns the objects defined in the JSON files for all availbale resources, + * Returns the objects defined in the JSON files for all available resources, * ordered as defined in these files. */ Resources.prototype.GetResources = function() @@ -68,11 +78,12 @@ /** * Returns an array containing all resource codes ordered as defined in the resource files. - * For example ["food", "wood", "stone", "metal"]. + * @param {string} property - the property e.g. ("tradable", "tributable" etc.) that the resource ought to have + * @return {string[]} - data of the form [ "food", "wood", ... ] */ -Resources.prototype.GetCodes = function() +Resources.prototype.GetCodes = function(property) { - return this.resourceCodes; + return Object.keys(this.resourceCodesByProperty).some(x => x == property) ? this.resourceCodesByProperty[property] : this.resourceCodes; }; /** 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.GetCodes("tributable").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.GetCodes("tributable"); 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.GetCodes("tradable").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.GetCodes("tradable"); + let resBarterCodes = g_ResourceData.GetCodes("barterable"); + 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) + + 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); + } + + 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.GetCodes("barterable")[0]; } /** @@ -856,6 +861,7 @@ }; barterButton.Buy.hidden = isSelected; + barterButton.Buy.enabled = controlsPlayer(player); barterButton.Sell.hidden = false; selectionIcon.hidden = !isSelected; @@ -873,7 +879,9 @@ Engine.GetGUIObjectByName("barterHelp").hidden = !canBarter; if (canBarter) - g_ResourceData.GetCodes().forEach((resCode, i) => { barterUpdateCommon(resCode, i, "barter", g_ViewedPlayer) }); + g_ResourceData.GetCodes("barterable").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.GetCodes("barterable").length > this.rowLength) return []; - return g_ResourceData.GetCodes(); + return g_ResourceData.GetCodes("barterable"); }, "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.GetCodes("tributable").indexOf(res) == -1) + continue; if (donor && availableResources[res] > 200 && allyResources[res] < 0.2 * availableResources[res]) { tribute[res] = Math.floor(0.3*availableResources[res] - allyResources[res]); @@ -402,17 +405,29 @@ } else { - response = "acceptWithTribute"; - requiredTribute = gameState.ai.HQ.pickMostNeededResources(gameState)[0]; - requiredTribute.wanted = Math.max(1000, gameState.getOwnUnits().length * (requestType === "ally" ? 10 : 5)); - this.receivedDiplomacyRequests.set(player, { - "status": "waitingForTribute", - "wanted": requiredTribute.wanted, - "type": requiredTribute.type, - "warnTime": gameState.ai.elapsedTime + 60, - "sentWarning": false, - "requestType": requestType - }); + // Try to request a tribute. + // If a resource is not tributable, do not request it. + // If no resources are tributable, decline. + let tributableResources = Resources.GetCodes("tributable"); + requiredTribute = gameState.ai.HQ.pickMostNeededResources(gameState).find(res => tributableResources.indexOf(res.type) != -1)); + if (requiredTribute) + { + response = "acceptWithTribute"; + requiredTribute.wanted = Math.max(1000, gameState.getOwnUnits().length * (requestType === "ally" ? 10 : 5)); + this.receivedDiplomacyRequests.set(player, { + "status": "waitingForTribute", + "wanted": requiredTribute.wanted, + "type": requiredTribute.type, + "warnTime": gameState.ai.elapsedTime + 60, + "sentWarning": false, + "requestType": requestType + }); + } + else + { + this.receivedDiplomacyRequests.set(player, { "requestType": requestType, "status": "declinedRequest" }); + response = "decline"; + } } m.chatAnswerRequestDiplomacy(gameState, player, requestType, response, requiredTribute); }; 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.GetCodes("tradable")) 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.GetCodes("barterable")) { // 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.GetCodes("barterable")) { 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.GetCodes("barterable")) { 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.GetCodes("barterable")) 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.GetCodes("barterable")) { 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.GetCodes("barterable"); 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.GetCodes("barterable")) { // 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,20 @@ "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) + for (let res of 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.GetCodes("tradable"); + 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)) @@ -420,7 +425,7 @@ Player.prototype.SetTradingGoods = function(tradingGoods) { - let resCodes = Resources.GetCodes(); + let resCodes = Resources.GetCodes("tradable"); let sumProba = 0; for (let resource in tradingGoods) { @@ -434,7 +439,7 @@ if (sumProba != 100) { - error("Invalid trading goods: " + uneval(tradingGoods)); + error("Invalid trading goods probability: " + uneval(sumProba)); return; } @@ -866,7 +871,7 @@ return; for (let resCode in amounts) - if (Resources.GetCodes().indexOf(resCode) == -1 || + if (Resources.GetCodes("tributable").indexOf(resCode) == -1 || !Number.isInteger(amounts[resCode]) || amounts[resCode] < 0) { 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 @@ -9,6 +9,7 @@ "grain": "Grain", "meat": "Meat" }, + "properties": ["barterable", "tradable", "tributable"], "truePrice": 100, "aiAnalysisInfluenceGroup": "ignore" } 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 @@ -6,6 +6,7 @@ "subtypes": { "ore": "Ore" }, + "properties": ["barterable", "tradable", "tributable"], "truePrice": 100, "aiAnalysisInfluenceGroup": "sparse" } 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 @@ -7,6 +7,7 @@ "rock": "Rock", "ruins": "Ruins" }, + "properties": ["barterable", "tradable", "tributable"], "truePrice": 100, "aiAnalysisInfluenceGroup": "sparse" } 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 @@ -7,6 +7,7 @@ "tree": "Tree", "ruins": "Ruins" }, + "properties": ["barterable", "tradable", "tributable"], "truePrice": 100, "aiAnalysisInfluenceGroup": "abundant" }