mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
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:
parent
7bb0f2ea69
commit
077c4f2576
18 changed files with 255 additions and 91 deletions
|
|
@ -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", ... }
|
||||
|
|
|
|||
46
binaries/data/mods/public/globalscripts/tests/test_resources.js
Executable file
46
binaries/data/mods/public/globalscripts/tests/test_resources.js
Executable 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"
|
||||
});
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Engine.LoadComponentScript("Barter.js");
|
|||
const truePrice = 110;
|
||||
|
||||
Resources = {
|
||||
"GetCodes": () => ["wood", "stone", "metal"],
|
||||
"GetBarterableCodes": () => ["wood", "stone", "metal"],
|
||||
"GetResource": (resource) => ({ "truePrice": truePrice })
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = "";
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
"grain": "Grain",
|
||||
"meat": "Meat"
|
||||
},
|
||||
"properties": ["barterable", "tradable", "tributable"],
|
||||
"truePrice": 100,
|
||||
"aiAnalysisInfluenceGroup": "ignore"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"subtypes": {
|
||||
"ore": "Ore"
|
||||
},
|
||||
"properties": ["barterable", "tradable", "tributable"],
|
||||
"truePrice": 100,
|
||||
"aiAnalysisInfluenceGroup": "sparse"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"rock": "Rock",
|
||||
"ruins": "Ruins"
|
||||
},
|
||||
"properties": ["barterable", "tradable", "tributable"],
|
||||
"truePrice": 100,
|
||||
"aiAnalysisInfluenceGroup": "sparse"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
"tree": "Tree",
|
||||
"ruins": "Ruins"
|
||||
},
|
||||
"properties": ["barterable", "tradable", "tributable"],
|
||||
"truePrice": 100,
|
||||
"aiAnalysisInfluenceGroup": "abundant"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
Loading…
Reference in a new issue