Adds a "properties"-property to resources and let mods be able to prevent resources from being bartered, traded and/or tributed.

Patch By: Freagarach
Comments By: elexis, Stan`, Nescio
Reviewed By: wraitii, Angen
Fixes #4370

Differential Revision: https://code.wildfiregames.com/D1846
This was SVN commit r22970.
This commit is contained in:
wraitii 2019-09-22 14:53:47 +00:00
parent 7bb0f2ea69
commit 077c4f2576
18 changed files with 255 additions and 91 deletions

View file

@ -7,6 +7,7 @@ function Resources()
this.resourceDataObj = {};
this.resourceCodes = [];
this.resourceNames = {};
this.resourceCodesByProperty = {};
for (let filename of Engine.ListDirectoryFiles("simulation/data/resources/", "*.json", false))
{
@ -30,27 +31,36 @@ function Resources()
this.resourceNames[data.code] = data.name;
for (let subres in data.subtypes)
this.resourceNames[subres] = data.subtypes[subres];
for (let property in data.properties)
{
if (!this.resourceCodesByProperty[data.properties[property]])
this.resourceCodesByProperty[data.properties[property]] = [];
this.resourceCodesByProperty[data.properties[property]].push(data.code);
}
}
// Sort arrays by specified order
let resSort = (a, b) =>
a.order < b.order ? -1 :
a.order > b.order ? +1 : 0;
this.resourceData.sort(resSort);
this.resourceCodes.sort((a, b) => resSort(
let resDataSort = (a, b) => a.order < b.order ? -1 : a.order > b.order ? +1 : 0;
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,13 +78,40 @@ Resources.prototype.GetResource = function(type)
/**
* Returns an array containing all resource codes ordered as defined in the resource files.
* For example ["food", "wood", "stone", "metal"].
* @return {string[]} - Data of the form [ "food", "wood", ... ].
*/
Resources.prototype.GetCodes = function()
{
return this.resourceCodes;
};
/**
* Returns an array containing all barterable resource codes ordered as defined in the resource files.
* @return {string[]} - Data of the form [ "food", "wood", ... ].
*/
Resources.prototype.GetBarterableCodes = function()
{
return this.resourceCodesByProperty.barterable || [];
};
/**
* Returns an array containing all tradable resource codes ordered as defined in the resource files.
* @return {string[]} - Data of the form [ "food", "wood", ... ].
*/
Resources.prototype.GetTradableCodes = function()
{
return this.resourceCodesByProperty.tradable || [];
};
/**
* Returns an array containing all tributable resource codes ordered as defined in the resource files.
* @return {string[]} - Data of the form [ "food", "wood", ... ].
*/
Resources.prototype.GetTributableCodes = function()
{
return this.resourceCodesByProperty.tributable || [];
};
/**
* Returns an object mapping resource codes to translatable resource names. Includes subtypes.
* For example { "food": "Food", "fish": "Fish", "fruit": "Fruit", "metal": "Metal", ... }

View file

@ -0,0 +1,46 @@
let resources = {
"res_A": {
"code": "a",
"name": "A",
"subtypes": {
"aa": "AA",
"aaa": "AAA"
},
"order": 2,
"properties": ["barterable", "tributable"]
},
"res_B": {
"code": "b",
"name": "B",
"subtypes": {
"bb": "BB",
"bbb": "BBB"
},
"order": 1,
"properties": ["tributable"]
}
};
Engine.ListDirectoryFiles = () => Object.keys(resources);
Engine.ReadJSONFile = (file) => resources[file];
let res = new Resources();
TS_ASSERT_EQUALS(res.GetResources().length, 2);
TS_ASSERT_EQUALS(res.GetResources()[0].code, "b");
TS_ASSERT_EQUALS(res.GetResource("b").order, 1);
TS_ASSERT_UNEVAL_EQUALS(res.GetCodes(), ["b", "a"]);
TS_ASSERT_UNEVAL_EQUALS(res.GetTributableCodes(), ["b", "a"]);
TS_ASSERT_UNEVAL_EQUALS(res.GetBarterableCodes(), ["a"]);
TS_ASSERT_UNEVAL_EQUALS(res.GetTradableCodes(), []);
TS_ASSERT_UNEVAL_EQUALS(res.GetNames(), {
"a": "A",
"aa": "AA",
"aaa": "AAA",
"b": "B",
"bb": "BB",
"bbb": "BBB"
});

View file

@ -276,10 +276,16 @@ function resizeDiplomacyDialog()
let dialog = Engine.GetGUIObjectByName("diplomacyDialogPanel");
let size = dialog.size;
let tribSize = Engine.GetGUIObjectByName("diplomacyPlayer[0]_tribute[0]").size;
let widthOffset = g_ResourceData.GetCodes().length * (tribSize.right - tribSize.left) / 2;
size.left -= widthOffset;
size.right += widthOffset;
let resTribCodesLength = g_ResourceData.GetTributableCodes().length;
if (resTribCodesLength)
{
let tribSize = Engine.GetGUIObjectByName("diplomacyPlayer[0]_tribute[0]").size;
let widthOffset = resTribCodesLength * (tribSize.right - tribSize.left) / 2;
size.left -= widthOffset;
size.right += widthOffset;
}
else
Engine.GetGUIObjectByName("diplomacyHeaderTribute").hidden = true;
let firstRow = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size;
let heightOffset = (g_Players.length - 1) * (firstRow.bottom - firstRow.top) / 2;
@ -478,9 +484,9 @@ function diplomacyFormatStanceButtons(i, hidden)
function diplomacyFormatTributeButtons(i, hidden)
{
let resCodes = g_ResourceData.GetCodes();
let resTribCodes = g_ResourceData.GetTributableCodes();
let r = 0;
for (let resCode of resCodes)
for (let resCode of resTribCodes)
{
let button = Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]");
if (!button)
@ -503,15 +509,15 @@ function diplomacyFormatTributeButtons(i, hidden)
// See INPUT_MASSTRIBUTING in input.js
let multiplier = 1;
return function() {
let isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute");
if (isBatchTrainPressed)
let isMassTributePressed = Engine.HotkeyIsPressed("session.masstribute");
if (isMassTributePressed)
{
inputState = INPUT_MASSTRIBUTING;
multiplier += multiplier == 1 ? 4 : 5;
}
let amounts = {};
for (let res of resCodes)
for (let res of resTribCodes)
amounts[res] = 0;
amounts[resCode] = 100 * multiplier;
@ -525,7 +531,7 @@ function diplomacyFormatTributeButtons(i, hidden)
button.tooltip = formatTributeTooltip(i, resCode, 100);
};
if (!isBatchTrainPressed)
if (!isMassTributePressed)
g_FlushTributing();
};
})(i, resCode, button);
@ -624,8 +630,15 @@ function resizeTradeDialog()
let size = dialog.size;
let width = size.right - size.left;
let resTradCodesLength = g_ResourceData.GetTradableCodes().length;
Engine.GetGUIObjectByName("tradeDialogPanelTrade").hidden = !resTradCodesLength;
let resBarterCodesLength = g_ResourceData.GetBarterableCodes().length;
Engine.GetGUIObjectByName("tradeDialogPanelBarter").hidden = !resBarterCodesLength;
let tradeSize = Engine.GetGUIObjectByName("tradeResource[0]").size;
width += g_ResourceData.GetCodes().length * (tradeSize.right - tradeSize.left);
let length = Math.max(resTradCodesLength, resBarterCodesLength);
width += length * (tradeSize.right - tradeSize.left);
size.left = -width / 2;
size.right = width / 2;
@ -643,8 +656,9 @@ function openTrade()
let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
let button = {};
let resCodes = g_ResourceData.GetCodes();
let currTradeSelection = resCodes[0];
let resTradeCodes = g_ResourceData.GetTradableCodes();
let resBarterCodes = g_ResourceData.GetBarterableCodes();
let currTradeSelection = resTradeCodes[0];
let updateTradeButtons = function()
{
@ -658,12 +672,13 @@ function openTrade()
}
};
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)
@ -672,11 +687,14 @@ function openTrade()
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)
{
@ -687,19 +705,19 @@ function openTrade()
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);
@ -707,7 +725,7 @@ function openTrade()
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 });
@ -715,7 +733,7 @@ function openTrade()
currTradeSelection = resource;
updateTradeButtons();
};
})(resCode);
})(resTradeCode);
buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
buttonUp.onPress = (resource => {
@ -725,7 +743,7 @@ function openTrade()
Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba });
updateTradeButtons();
};
})(resCode);
})(resTradeCode);
buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
buttonDn.onPress = (resource => {
@ -735,7 +753,7 @@ function openTrade()
Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba });
updateTradeButtons();
};
})(resCode);
})(resTradeCode);
}
updateTradeButtons();
@ -752,7 +770,8 @@ function updateTraderTexts()
function initBarterButtons()
{
g_BarterSell = g_ResourceData.GetCodes()[0];
let resBartCodes = g_ResourceData.GetBarterableCodes();
g_BarterSell = resBartCodes.length ? resBartCodes[0] : undefined;
}
/**
@ -860,7 +879,9 @@ function updateBarterButtons()
Engine.GetGUIObjectByName("barterHelp").hidden = !canBarter;
if (canBarter)
g_ResourceData.GetCodes().forEach((resCode, i) => { barterUpdateCommon(resCode, i, "barter", g_ViewedPlayer); });
g_ResourceData.GetBarterableCodes().forEach((resCode, i) => {
barterUpdateCommon(resCode, i, "barter", g_ViewedPlayer);
});
}
function getIdleLandTradersText(traderNumber)

View file

@ -84,9 +84,9 @@ g_SelectionPanels.Barter = {
"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.GetBarterableCodes().length > this.rowLength)
return [];
return g_ResourceData.GetCodes();
return g_ResourceData.GetBarterableCodes();
},
"setupButton": function(data)
{

View file

@ -692,7 +692,8 @@ function updateTopPanel()
Engine.GetGUIObjectByName("population").hidden = !isPlayer;
Engine.GetGUIObjectByName("diplomacyButton").hidden = !isPlayer;
Engine.GetGUIObjectByName("tradeButton").hidden = !isPlayer;
Engine.GetGUIObjectByName("tradeButton").hidden = !isPlayer ||
(!g_ResourceData.GetTradableCodes().length && !g_ResourceData.GetBarterableCodes().length);
Engine.GetGUIObjectByName("observerText").hidden = isPlayer;
let alphaLabel = Engine.GetGUIObjectByName("alphaLabel");

View file

@ -10,7 +10,7 @@
</object>
<!-- Barter Goods -->
<object size="24 24 100%-24 33%">
<object size="24 24 100%-24 33%" name="tradeDialogPanelBarter">
<object type="text" size="8 0 100% 32" style="ModernLeftLabelText">
<translatableAttribute id="caption">Barter</translatableAttribute>
@ -60,7 +60,7 @@
</object>
<!-- Trading goods -->
<object size="24 33%+32 100%-24 100%-64">
<object size="24 33%+32 100%-24 100%-64" name="tradeDialogPanelTrade">
<object type="text" size="8 0 100% 32" style="ModernLeftLabelText">
<translatableAttribute id="caption">Trade</translatableAttribute>

View file

@ -63,6 +63,9 @@ PETRA.DiplomacyManager.prototype.init = function(gameState)
PETRA.DiplomacyManager.prototype.tributes = function(gameState)
{
this.nextTributeUpdate = gameState.ai.elapsedTime + 30;
let resTribCodes = Resources.GetTributableCodes();
if (!resTribCodes.length)
return;
let totalResources = gameState.getResources();
let availableResources = gameState.ai.queueManager.getAvailableResources(gameState);
let mostNeeded;
@ -75,7 +78,7 @@ PETRA.DiplomacyManager.prototype.tributes = function(gameState)
let allyPop = gameState.sharedScript.playersData[i].popCount;
let tribute = {};
let toSend = false;
for (let res in allyResources)
for (let res in resTribCodes)
{
if (donor && availableResources[res] > 200 && allyResources[res] < 0.2 * availableResources[res])
{
@ -94,8 +97,8 @@ PETRA.DiplomacyManager.prototype.tributes = function(gameState)
if (this.nextTributeRequest.has(res) && gameState.ai.elapsedTime < this.nextTributeRequest.get(res))
continue;
if (!mostNeeded)
mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState);
for (let k = 0; k < 2; ++k)
mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState, resTribCodes);
for (let k = 0; k < mostNeeded.length; ++k)
{
if (mostNeeded[k].type == res && mostNeeded[k].wanted > 0)
{
@ -399,17 +402,29 @@ PETRA.DiplomacyManager.prototype.handleDiplomacyRequest = function(gameState, pl
}
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 resTribCodes = Resources.GetTributableCodes();
if (resTribCodes.length)
{
requiredTribute = gameState.ai.HQ.pickMostNeededResources(gameState, resTribCodes)[0];
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";
}
}
PETRA.chatAnswerRequestDiplomacy(gameState, player, requestType, response, requiredTribute);
};
@ -508,7 +523,7 @@ PETRA.DiplomacyManager.prototype.update = function(gameState, events)
{
this.checkEvents(gameState, events);
if (!gameState.ai.HQ.saveResources && gameState.ai.elapsedTime > this.nextTributeUpdate)
if (Resources.GetTributableCodes().length && !gameState.ai.HQ.saveResources && gameState.ai.elapsedTime > this.nextTributeUpdate)
this.tributes(gameState);
if (this.waitingToBetray && gameState.ai.elapsedTime > this.betrayLapseTime)

View file

@ -898,13 +898,15 @@ PETRA.HQ.prototype.GetWantedGatherRates = function(gameState)
* We compare; we pick the one where the discrepancy is highest.
* Need to balance long-term needs and possible short-term needs.
*/
PETRA.HQ.prototype.pickMostNeededResources = function(gameState)
PETRA.HQ.prototype.pickMostNeededResources = function(gameState, allowedResources = [])
{
let wantedRates = this.GetWantedGatherRates(gameState);
let currentRates = this.GetCurrentGatherRates(gameState);
if (!allowedResources.length)
allowedResources = Resources.GetCodes();
let needed = [];
for (let res in wantedRates)
for (let res of allowedResources)
needed.push({ "type": res, "wanted": wantedRates[res], "current": currentRates[res] });
needed.sort((a, b) => {
@ -1286,6 +1288,10 @@ PETRA.HQ.prototype.findMarketLocation = function(gameState, template)
if (!markets.length) // this is the first market. For the time being, place it arbitrarily by the ConstructionPlan
return [-1, -1, -1, 0];
// No need for more than one market when we cannot trade.
if (!Resources.GetTradableCodes().length)
return false;
// obstruction map
let obstructions = PETRA.createObstructionMap(gameState, 0, template);
let halfSize = 0;

View file

@ -157,16 +157,19 @@ PETRA.TradeManager.prototype.updateTrader = function(gameState, ent)
PETRA.TradeManager.prototype.setTradingGoods = function(gameState)
{
let resTradeCodes = Resources.GetTradableCodes();
if (!resTradeCodes.length)
return;
let tradingGoods = {};
for (let res of Resources.GetCodes())
for (let res of resTradeCodes)
tradingGoods[res] = 0;
// first, try to anticipate future needs
let stocks = gameState.ai.HQ.getTotalResourceLevel(gameState);
let mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState);
let mostNeeded = gameState.ai.HQ.pickMostNeededResources(gameState, resTradeCodes);
let wantedRates = gameState.ai.HQ.GetWantedGatherRates(gameState);
let remaining = 100;
let targetNum = this.Config.Economy.targetNumTraders;
for (let res in stocks)
for (let res of resTradeCodes)
{
if (res == "food")
continue;
@ -196,7 +199,7 @@ PETRA.TradeManager.prototype.setTradingGoods = function(gameState)
let nextNeed = remaining - mainNeed;
tradingGoods[mostNeeded[0].type] += mainNeed;
if (mostNeeded[1].wanted > 0)
if (mostNeeded[1] && mostNeeded[1].wanted > 0)
tradingGoods[mostNeeded[1].type] += nextNeed;
else
tradingGoods[mostNeeded[0].type] += nextNeed;
@ -214,6 +217,9 @@ PETRA.TradeManager.prototype.performBarter = function(gameState)
let barterers = gameState.getOwnEntitiesByClass("BarterMarket", true).filter(API3.Filters.isBuilt()).toEntityArray();
if (barterers.length == 0)
return false;
let resBarterCodes = Resources.GetBarterableCodes();
if (!resBarterCodes.length)
return false;
// Available resources after account substraction
let available = gameState.ai.queueManager.getAvailableResources(gameState);
@ -226,7 +232,7 @@ PETRA.TradeManager.prototype.performBarter = function(gameState)
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 resBarterCodes)
{
// Check if our rate allows to gather it fast enough
if (needs[buy] == 0 || needs[buy] < rates[buy] * 30)
@ -235,7 +241,7 @@ PETRA.TradeManager.prototype.performBarter = function(gameState)
// Pick the best resource to barter.
let bestToSell;
let bestRate = 0;
for (let sell of Resources.GetCodes())
for (let sell of resBarterCodes)
{
if (sell == buy)
continue;
@ -287,11 +293,11 @@ PETRA.TradeManager.prototype.performBarter = function(gameState)
}
// now do contingency bartering, selling food to buy finite resources (and annoy our ennemies by increasing prices)
if (available.food < 1000 || needs.food > 0)
if (available.food < 1000 || needs.food > 0 || resBarterCodes.indexOf("food") == -1)
return false;
let bestToBuy;
let bestChoice = 0;
for (let buy of Resources.GetCodes())
for (let buy of resBarterCodes)
{
if (buy == "food")
continue;
@ -405,6 +411,14 @@ PETRA.TradeManager.prototype.activateProspection = function(gameState)
*/
PETRA.TradeManager.prototype.checkRoutes = function(gameState, accessIndex)
{
// If we cannot trade, do not bother checking routes.
if (!Resources.GetTradableCodes().length)
{
this.tradeRoute = undefined;
this.potentialTradeRoute = undefined;
return false;
}
let market1 = gameState.updatingCollection("OwnMarkets", API3.Filters.byClass("Market"), gameState.getOwnStructures());
let market2 = gameState.updatingCollection("diplo-ExclusiveAllyMarkets", API3.Filters.byClass("Market"), gameState.getExclusiveAllyEntities());
if (market1.length + market2.length < 2) // We have to wait ... markets will be built soon
@ -616,6 +630,8 @@ PETRA.TradeManager.prototype.prospectForNewMarket = function(gameState, queues)
PETRA.TradeManager.prototype.isNewMarketWorth = function(expectedGain)
{
if (!Resources.GetTradableCodes().length)
return false;
if (expectedGain < this.minimalGain)
return false;
if (this.potentialTradeRoute && expectedGain < 2*this.potentialTradeRoute.gain &&
@ -626,7 +642,7 @@ PETRA.TradeManager.prototype.isNewMarketWorth = function(expectedGain)
PETRA.TradeManager.prototype.update = function(gameState, events, queues)
{
if (gameState.ai.HQ.canBarter)
if (gameState.ai.HQ.canBarter && Resources.GetBarterableCodes().length)
this.performBarter(gameState);
if (this.Config.difficulty <= 1)

View file

@ -29,16 +29,16 @@ Barter.prototype.RESTORE_TIMER_INTERVAL = 5000;
Barter.prototype.Init = function()
{
this.priceDifferences = {};
for (let resource of Resources.GetCodes())
for (let resource of Resources.GetBarterableCodes())
this.priceDifferences[resource] = 0;
this.restoreTimer = undefined;
};
Barter.prototype.GetPrices = function(playerID)
{
var prices = { "buy": {}, "sell": {} };
let prices = { "buy": {}, "sell": {} };
let multiplier = QueryPlayerIDInterface(playerID).GetBarterMultiplier();
for (let resource of Resources.GetCodes())
for (let resource of Resources.GetBarterableCodes())
{
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 @@ Barter.prototype.ExchangeResources = function(playerID, resourceToSell, resource
return;
}
let availResources = Resources.GetCodes();
let availResources = Resources.GetBarterableCodes();
if (availResources.indexOf(resourceToSell) == -1)
{
warn("ExchangeResources: incorrect resource to sell: " + uneval(resourceToSell));
@ -135,11 +135,11 @@ Barter.prototype.ExchangeResources = function(playerID, resourceToSell, resource
Barter.prototype.ProgressTimeout = function(data)
{
var needRestore = false;
for (let resource of Resources.GetCodes())
let needRestore = false;
for (let resource of Resources.GetBarterableCodes())
{
// 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]));
let differenceRestore = Math.min(this.DIFFERENCE_RESTORE, Math.max(-this.DIFFERENCE_RESTORE, this.priceDifferences[resource]));
differenceRestore = -differenceRestore;
this.priceDifferences[resource] += differenceRestore;
// If price difference still exists then set flag to run timer again
@ -149,7 +149,7 @@ Barter.prototype.ProgressTimeout = function(data)
if (!needRestore)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.restoreTimer);
this.restoreTimer = undefined;
}

View file

@ -76,20 +76,22 @@ Player.prototype.Init = function()
"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.GetTradableCodes();
let quotient = Math.floor(20 / resTradeCodes.length);
let remainder = 20 % resTradeCodes.length;
for (let i in resTradeCodes)
this.tradingGoods.push({
"goods": res,
"goods": resTradeCodes[i],
"proba": 5 * (quotient + (+i < remainder ? 1 : 0))
});
}
};
Player.prototype.SetPlayerID = function(id)
@ -385,11 +387,11 @@ Player.prototype.GetTradingGoods = function()
Player.prototype.SetTradingGoods = function(tradingGoods)
{
let resCodes = Resources.GetCodes();
let resTradeCodes = Resources.GetTradableCodes();
let sumProba = 0;
for (let resource in tradingGoods)
{
if (resCodes.indexOf(resource) == -1 || tradingGoods[resource] < 0)
if (resTradeCodes.indexOf(resource) == -1 || tradingGoods[resource] < 0)
{
error("Invalid trading goods: " + uneval(tradingGoods));
return;
@ -399,7 +401,7 @@ Player.prototype.SetTradingGoods = function(tradingGoods)
if (sumProba != 100)
{
error("Invalid trading goods: " + uneval(tradingGoods));
error("Invalid trading goods probability: " + uneval(sumProba));
return;
}
@ -812,19 +814,20 @@ Player.prototype.GetCheatsEnabled = function()
Player.prototype.TributeResource = function(player, amounts)
{
var cmpPlayer = QueryPlayerIDInterface(player);
let cmpPlayer = QueryPlayerIDInterface(player);
if (!cmpPlayer)
return;
if (this.state != "active" || cmpPlayer.state != "active")
return;
let resTribCodes = Resources.GetTributableCodes();
for (let resCode in amounts)
if (Resources.GetCodes().indexOf(resCode) == -1 ||
if (resTribCodes.indexOf(resCode) == -1 ||
!Number.isInteger(amounts[resCode]) ||
amounts[resCode] < 0)
{
warn("Invalid tribute amounts: " + uneval(amounts));
warn("Invalid tribute amounts: " + uneval(resCode) + ": " + uneval(amounts));
return;
}

View file

@ -10,7 +10,7 @@ Engine.LoadComponentScript("Barter.js");
const truePrice = 110;
Resources = {
"GetCodes": () => ["wood", "stone", "metal"],
"GetBarterableCodes": () => ["wood", "stone", "metal"],
"GetResource": (resource) => ({ "truePrice": truePrice })
};

View file

@ -1,5 +1,7 @@
Resources = {
"GetCodes": () => ["food", "metal", "stone", "wood"],
"GetTradableCodes": () => ["food", "metal", "stone", "wood"],
"GetBarterableCodes": () => ["food", "metal", "stone", "wood"],
"GetResource": () => ({}),
"BuildSchema": (type) => {
let schema = "";

View file

@ -9,6 +9,7 @@
"grain": "Grain",
"meat": "Meat"
},
"properties": ["barterable", "tradable", "tributable"],
"truePrice": 100,
"aiAnalysisInfluenceGroup": "ignore"
}

View file

@ -6,6 +6,7 @@
"subtypes": {
"ore": "Ore"
},
"properties": ["barterable", "tradable", "tributable"],
"truePrice": 100,
"aiAnalysisInfluenceGroup": "sparse"
}

View file

@ -7,6 +7,7 @@
"rock": "Rock",
"ruins": "Ruins"
},
"properties": ["barterable", "tradable", "tributable"],
"truePrice": 100,
"aiAnalysisInfluenceGroup": "sparse"
}

View file

@ -7,6 +7,7 @@
"tree": "Tree",
"ruins": "Ruins"
},
"properties": ["barterable", "tradable", "tributable"],
"truePrice": 100,
"aiAnalysisInfluenceGroup": "abundant"
}

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2017 Wildfire Games.
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -56,6 +56,19 @@ public:
TS_ASSERT(componentManager->LoadScript(VfsPath(L"simulation/helpers") / pathname));
}
void test_global_scripts()
{
VfsPaths paths;
TS_ASSERT_OK(vfs::GetPathnames(g_VFS, L"globalscripts/tests/", L"test_*.js", paths));
for (const VfsPath& path : paths)
{
CSimContext context;
CComponentManager componentManager(context, g_ScriptRuntime, true);
ScriptTestSetup(componentManager.GetScriptInterface());
load_script(componentManager.GetScriptInterface(), path);
}
}
void test_scripts()
{
if (!VfsFileExists(L"simulation/components/tests/setup.js"))