Merge resource agnostic branch by s0600204, fixes #3934.

Remove all occurances of hardcoded resources in the simulation, GUI and
AI code by
specifying resources as JSON files in a new simulation subdirectory and
accessing them through a globally defined prototype.

This was SVN commit r18964.
This commit is contained in:
elexis 2016-11-19 14:29:45 +00:00
parent 7ff7fcd240
commit 52f311da2b
60 changed files with 881 additions and 598 deletions

View file

@ -0,0 +1,98 @@
/**
* Since the AI context can't access JSON functions, it gets passed an object
* containing the information from `GuiInterface.js::GetSimulationState()`.
*/
function Resources()
{
let jsonFiles = [];
// Simulation context
if (Engine.FindJSONFiles)
{
jsonFiles = Engine.FindJSONFiles("resources", false);
for (let file in jsonFiles)
jsonFiles[file] = "resources/" + jsonFiles[file] + ".json";
}
// GUI context
else if (Engine.BuildDirEntList)
jsonFiles = Engine.BuildDirEntList("simulation/data/resources/", "*.json", false);
else
{
error("Resources: JSON functions are not available");
return;
}
this.resourceData = [];
this.resourceDataObj = {};
this.resourceCodes = [];
this.resourceNames = {};
for (let filename of jsonFiles)
{
let data = Engine.ReadJSONFile(filename);
if (!data)
continue;
if (data.code != data.code.toLowerCase())
warn("Resource codes should use lower case: " + data.code);
// Treasures are supported for every specified resource
if (data.code == "treasure")
{
error("Encountered resource with reserved keyword: " + data.code);
continue;
}
this.resourceData.push(data);
this.resourceDataObj[data.code] = data;
this.resourceCodes.push(data.code);
this.resourceNames[data.code] = data.name;
for (let subres in data.subtypes)
this.resourceNames[subres] = data.subtypes[subres]
}
// 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(
this.resourceData.find(resource => resource.code == a),
this.resourceData.find(resource => resource.code == b)
));
};
/**
* Returns the objects defined in the JSON files for all availbale resources,
* ordered as defined in these files.
*/
Resources.prototype.GetResources = function()
{
return this.resourceData;
};
/**
* Returns the object defined in the JSON file for the given resource.
*/
Resources.prototype.GetResource = function(type)
{
return this.resourceDataObj[type];
};
/**
* Returns an array containing all resource codes ordered as defined in the resource files.
* For example ["food", "wood", "stone", "metal"].
*/
Resources.prototype.GetCodes = function()
{
return this.resourceCodes;
};
/**
* Returns an object mapping resource codes to translatable resource names. Includes subtypes.
* For example { "food": "Food", "fish": "Fish", "fruit": "Fruit", "metal": "Metal", ... }
*/
Resources.prototype.GetNames = function()
{
return this.resourceNames;
};

View file

@ -75,8 +75,9 @@ function MatchesClassList(classes, match)
* @param player An optional player id to get the technology modifications
* of properties.
* @param auraTemplates An object in the form of {key: {auraName: "", auraDescription: ""}}
* @param resources An instance of the Resources prototype
*/
function GetTemplateDataHelper(template, player, auraTemplates)
function GetTemplateDataHelper(template, player, auraTemplates, resources)
{
// Return data either from template (in tech tree) or sim state (ingame)
let getEntityValue = function(tech_type) {
@ -193,17 +194,9 @@ function GetTemplateDataHelper(template, player, auraTemplates)
if (template.Cost)
{
ret.cost = {};
if (template.Cost.Resources.food)
ret.cost.food = getEntityValue("Cost/Resources/food");
if (template.Cost.Resources.wood)
ret.cost.wood = getEntityValue("Cost/Resources/wood");
if (template.Cost.Resources.stone)
ret.cost.stone = getEntityValue("Cost/Resources/stone");
if (template.Cost.Resources.metal)
ret.cost.metal = getEntityValue("Cost/Resources/metal");
for (let resCode of resources.GetCodes())
if (template.Cost.Resources[resCode])
ret.cost[resCode] = getEntityValue("Cost/Resources/" + resCode);
if (template.Cost.Population)
ret.cost.population = getEntityValue("Cost/Population");
@ -348,8 +341,9 @@ function GetTemplateDataHelper(template, player, auraTemplates)
* Get information about a technology template.
* @param template A valid template as obtained by loading the tech JSON file.
* @param civ Civilization for which the specific name should be returned.
* @param resources An instance of the Resources prototype.
*/
function GetTechnologyDataHelper(template, civ)
function GetTechnologyDataHelper(template, civ, resources)
{
var ret = {};
@ -370,13 +364,9 @@ function GetTechnologyDataHelper(template, civ)
ret.icon = template.icon ? "technologies/" + template.icon : null;
ret.cost = {
"food": template.cost ? +template.cost.food : 0,
"wood": template.cost ? +template.cost.wood : 0,
"metal": template.cost ? +template.cost.metal : 0,
"stone": template.cost ? +template.cost.stone : 0,
"time": template.researchTime ? +template.researchTime : 0,
}
ret.cost = { "time": template.researchTime ? +template.researchTime : 0 }
for (let type of resources.GetCodes())
ret.cost[type] = template.cost ? +template.cost[type] : 0;
ret.tooltip = template.tooltip;
ret.requirementsTooltip = template.requirementsTooltip || "";

View file

@ -245,3 +245,32 @@ function notifyUser(userName, msgText)
g_LastNickNotification = timeNow;
}
/**
* Horizontally spaces objects within a parent
*
* @param margin The gap, in px, between the objects
*/
function horizontallySpaceObjects(parentName, margin=0)
{
let objects = Engine.GetGUIObjectByName(parentName).children;
for (let i = 0; i < objects.length; ++i)
{
let size = objects[i].size;
let width = size.right - size.left;
size.left = i * (width + margin) + margin;
size.right = (i + 1) * (width + margin);
objects[i].size = size;
}
}
/**
* Hide all children after a certain index
*/
function hideRemaining(parentName, start = 0)
{
let objects = Engine.GetGUIObjectByName(parentName).children;
for (let i = start; i < objects.length; ++i)
objects[i].hidden = true;
}

View file

@ -1,75 +1,6 @@
const localisedResourceNames = {
"firstWord": {
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"food": translateWithContext("firstWord", "Food"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"meat": translateWithContext("firstWord", "Meat"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"metal": translateWithContext("firstWord", "Metal"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"ore": translateWithContext("firstWord", "Ore"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"rock": translateWithContext("firstWord", "Rock"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"ruins": translateWithContext("firstWord", "Ruins"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"stone": translateWithContext("firstWord", "Stone"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"treasure": translateWithContext("firstWord", "Treasure"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"tree": translateWithContext("firstWord", "Tree"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"wood": translateWithContext("firstWord", "Wood"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"fruit": translateWithContext("firstWord", "Fruit"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"grain": translateWithContext("firstWord", "Grain"),
// Translation: Word as used at the beginning of a sentence or as a single-word sentence.
"fish": translateWithContext("firstWord", "Fish"),
},
"withinSentence": {
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"food": translateWithContext("withinSentence", "Food"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"meat": translateWithContext("withinSentence", "Meat"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"metal": translateWithContext("withinSentence", "Metal"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"ore": translateWithContext("withinSentence", "Ore"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"rock": translateWithContext("withinSentence", "Rock"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"ruins": translateWithContext("withinSentence", "Ruins"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"stone": translateWithContext("withinSentence", "Stone"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"treasure": translateWithContext("withinSentence", "Treasure"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"tree": translateWithContext("withinSentence", "Tree"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"wood": translateWithContext("withinSentence", "Wood"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"fruit": translateWithContext("withinSentence", "Fruit"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"grain": translateWithContext("withinSentence", "Grain"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"fish": translateWithContext("withinSentence", "Fish"),
}
};
function getLocalizedResourceName(resourceCode, context)
function getLocalizedResourceName(resourceName, context)
{
if (!localisedResourceNames[context])
{
warn("Internationalization: Unexpected context for resource type localization found: " + context + ". This context is not supported.");
return resourceCode;
}
if (!localisedResourceNames[context][resourceCode])
{
warn("Internationalization: Unexpected resource type found with code " + resourceCode + ". This resource type must be internationalized.");
return resourceCode;
}
return localisedResourceNames[context][resourceCode];
return translateWithContext(context, resourceName);
}
/**
@ -81,7 +12,7 @@ function getLocalizedResourceAmounts(resources)
.filter(type => resources[type] > 0)
.map(type => sprintf(translate("%(amount)s %(resourceType)s"), {
"amount": resources[type],
"resourceType": getLocalizedResourceName(type, "withinSentence")
"resourceType": getLocalizedResourceName(g_ResourceData.GetResource(type).name, "withinSentence")
}));
if (amounts.length > 1)

View file

@ -603,7 +603,7 @@ function getLootTooltip(template)
template.trader && template.trader.goods
);
const lootTypes = ["xp", "food", "wood", "stone", "metal"];
const lootTypes = g_ResourceData.GetCodes().concat(["xp"]);
let lootLabels = [];
for (let type of lootTypes)
{

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="diplomacyDialogPanel"
size="50%-300 50%-200 50%+300 50%+150"
size="50%-260 50%-200 50%+260 50%+150"
type="image"
hidden="true"
sprite="ModernDialog"
@ -11,7 +11,7 @@
</object>
<object name="diplomacyHeader" size="32 32 100%-32 64">
<object name="diplomacyHeaderName" size="0 0 150 100%" type="text" style="DiplomacyText" ghost="true">
<object name="diplomacyHeaderName" size="0 0 140 100%" type="text" style="DiplomacyText" ghost="true" text_align="center">
<translatableAttribute id="caption">Name</translatableAttribute>
</object>
<object name="diplomacyHeaderCiv" size="150 0 250 100%" type="text" style="DiplomacyText" ghost="true">
@ -23,25 +23,25 @@
<object name="diplomacyHeaderTheirs" size="300 0 360 100%" type="text" style="DiplomacyText" ghost="true">
<translatableAttribute id="caption">Theirs</translatableAttribute>
</object>
<object name="diplomacyHeaderAlly" size="100%-180 0 100%-160 100%" type="text" style="DiplomacyText" tooltip_style="sessionToolTipBold">
<object name="diplomacyHeaderAlly" size="360 0 380 100%" type="text" style="DiplomacyText" tooltip_style="sessionToolTipBold">
<translatableAttribute id="caption">A</translatableAttribute>
<translatableAttribute id="tooltip">Ally</translatableAttribute>
</object>
<object name="diplomacyHeaderNeutral" size="100%-160 0 100%-140 100%" type="text" style="DiplomacyText" tooltip_style="sessionToolTipBold">
<object name="diplomacyHeaderNeutral" size="380 0 400 100%" type="text" style="DiplomacyText" tooltip_style="sessionToolTipBold">
<translatableAttribute id="caption">N</translatableAttribute>
<translatableAttribute id="tooltip">Neutral</translatableAttribute>
</object>
<object name="diplomacyHeaderEnemy" size="100%-140 0 100%-120 100%" type="text" style="DiplomacyText" tooltip_style="sessionToolTipBold">
<object name="diplomacyHeaderEnemy" size="400 0 420 100%" type="text" style="DiplomacyText" tooltip_style="sessionToolTipBold">
<translatableAttribute id="caption">E</translatableAttribute>
<translatableAttribute id="tooltip">Enemy</translatableAttribute>
</object>
<object name="diplomacyHeaderTribute" size="100%-110 0 100% 100%" type="text" style="DiplomacyText">
<object name="diplomacyHeaderTribute" size="430 0 100%-30 100%" type="text" style="DiplomacyText" text_align="center">
<translatableAttribute id="caption">Tribute</translatableAttribute>
</object>
</object>
<object size="32 64 100%-32 384">
<repeat count="16">
<repeat count="16" var="n">
<object name="diplomacyPlayer[n]" size="0 0 100% 20" type="image" hidden="false">
<object name="diplomacyPlayerName[n]" size="0 0 150 100%" type="text" style="DiplomacyText" ghost="true"/>
<object name="diplomacyPlayerCiv[n]" size="150 0 250 100%" type="text" style="DiplomacyText" ghost="true"/>
@ -49,22 +49,17 @@
<object name="diplomacyPlayerTheirs[n]" size="300 0 360 100%" type="text" style="DiplomacyText" ghost="true"/>
<!-- Diplomatic stance - selection -->
<object name="diplomacyPlayerAlly[n]" size="100%-180 0 100%-160 100%" type="button" style="StoneButton" hidden="true"/>
<object name="diplomacyPlayerNeutral[n]" size="100%-160 0 100%-140 100%" type="button" style="StoneButton" hidden="true"/>
<object name="diplomacyPlayerEnemy[n]" size="100%-140 0 100%-120 100%" type="button" style="StoneButton" hidden="true"/>
<object name="diplomacyPlayerAlly[n]" size="360 0 380 100%" type="button" style="StoneButton" hidden="true"/>
<object name="diplomacyPlayerNeutral[n]" size="380 0 400 100%" type="button" style="StoneButton" hidden="true"/>
<object name="diplomacyPlayerEnemy[n]" size="400 0 420 100%" type="button" style="StoneButton" hidden="true"/>
<!-- Tribute -->
<object name="diplomacyPlayerTributeFood[n]" size="100%-110 0 100%-90 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacyPlayerTributeFoodImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
</object>
<object name="diplomacyPlayerTributeWood[n]" size="100%-90 0 100%-70 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacyPlayerTributeWoodImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
</object>
<object name="diplomacyPlayerTributeStone[n]" size="100%-70 0 100%-50 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacyPlayerTributeStoneImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
</object>
<object name="diplomacyPlayerTributeMetal[n]" size="100%-50 0 100%-30 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacyPlayerTributeMetalImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
<object size="430 0 100%-40 100%">
<repeat count="8" var="r">
<object name="diplomacyPlayer[n]_tribute[r]" size="0 0 20 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacyPlayer[n]_tribute[r]_image" type="image" size="0 0 100% 100%" ghost="true"/>
</object>
</repeat>
</object>
<object name="diplomacyAttackRequest[n]" size="100%-20 0 100% 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">

View file

@ -22,9 +22,6 @@ const INITIAL_MENU_POSITION = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM;
// Number of pixels per millisecond to move
const MENU_SPEED = 1.2;
// Available resources in trade and tribute menu
const RESOURCES = ["food", "wood", "stone", "metal"];
// Trade menu: step for probability changes
const STEP = 5;
@ -230,6 +227,20 @@ function closeChat()
Engine.GetGUIObjectByName("chatDialogPanel").hidden = true;
}
function resizeDiplomacyDialog()
{
let dialog = Engine.GetGUIObjectByName("diplomacyDialogPanel");
let size = dialog.size;
let width = size.right - size.left;
let tribSize = Engine.GetGUIObjectByName("diplomacyPlayer[0]_tribute[0]").size;
width += g_ResourceData.GetCodes().length * (tribSize.right - tribSize.left);
size.left = -width / 2;
size.right = width / 2;
dialog.size = size;
}
function initChatWindow()
{
let filters = prepareForDropdown(g_ChatHistoryFilters);
@ -321,7 +332,6 @@ function openDiplomacy()
diplomacyFormatTributeButtons(i, myself || playerInactive);
diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]);
}
Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
}
@ -372,16 +382,28 @@ function diplomacyFormatStanceButtons(i, hidden)
function diplomacyFormatTributeButtons(i, hidden)
{
for (let resource of RESOURCES)
let resNames = g_ResourceData.GetNames();
let resCodes = g_ResourceData.GetCodes();
let r = 0;
for (let resCode of resCodes)
{
let button = Engine.GetGUIObjectByName("diplomacyPlayerTribute"+resource[0].toUpperCase()+resource.substring(1)+"["+(i-1)+"]");
let button = Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]");
if (!button)
{
warn("Current GUI limits prevent displaying more than " + r + " tribute buttons!");
break;
}
Engine.GetGUIObjectByName("diplomacyPlayer["+(i-1)+"]_tribute["+r+"]_image").sprite = "stretched:session/icons/resources/"+resCode+".png";
button.hidden = hidden;
setPanelObjectPosition(button, r, r+1, 0);
++r;
if (hidden)
continue;
button.enabled = controlsPlayer(g_ViewedPlayer);
button.tooltip = formatTributeTooltip(i, resource, 100);
button.onpress = (function(i, resource, button) {
button.tooltip = formatTributeTooltip(i, resNames[resCode], 100);
button.onPress = (function(i, resCode, button) {
// Shift+click to send 500, shift+click+click to send 1000, etc.
// See INPUT_MASSTRIBUTING in input.js
let multiplier = 1;
@ -394,24 +416,24 @@ function diplomacyFormatTributeButtons(i, hidden)
}
let amounts = {};
for (let type of RESOURCES)
amounts[type] = 0;
amounts[resource] = 100 * multiplier;
for (let res of resCodes)
amounts[res] = 0;
amounts[resCode] = 100 * multiplier;
button.tooltip = formatTributeTooltip(i, resource, amounts[resource]);
button.tooltip = formatTributeTooltip(i, resNames[resCode], amounts[resCode]);
// This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some
// evil global variable hackery.
g_FlushTributing = function() {
Engine.PostNetworkCommand({ "type": "tribute", "player": i, "amounts": amounts });
multiplier = 1;
button.tooltip = formatTributeTooltip(i, resource, 100);
button.tooltip = formatTributeTooltip(i, resNames[resCode], 100);
};
if (!isBatchTrainPressed)
g_FlushTributing();
};
})(i, resource, button);
})(i, resCode, button);
}
}
@ -444,6 +466,20 @@ function toggleDiplomacy()
openDiplomacy();
}
function resizeTradeDialog()
{
let dialog = Engine.GetGUIObjectByName("tradeDialogPanel");
let size = dialog.size;
let width = size.right - size.left;
let tradeSize = Engine.GetGUIObjectByName("tradeResource[0]").size;
width += g_ResourceData.GetCodes().length * (tradeSize.right - tradeSize.left);
size.left = -width / 2;
size.right = width / 2;
dialog.size = size;
}
function openTrade()
{
closeOpenDialogs();
@ -455,7 +491,7 @@ function openTrade()
var updateButtons = function()
{
for (var res in button)
for (let res in button)
{
button[res].label.caption = proba[res] + "%";
@ -465,45 +501,55 @@ function openTrade()
}
};
var proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
var button = {};
var selec = RESOURCES[0];
for (var i = 0; i < RESOURCES.length; ++i)
{
var buttonResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
if (i > 0)
{
var size = Engine.GetGUIObjectByName("tradeResource["+(i-1)+"]").size;
var width = size.right - size.left;
size.left += width;
size.right += width;
Engine.GetGUIObjectByName("tradeResource["+i+"]").size = size;
}
var resource = RESOURCES[i];
proba[resource] = (proba[resource] ? proba[resource] : 0);
var buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
var icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
icon.sprite = "stretched:session/icons/resources/" + resource + ".png";
var label = Engine.GetGUIObjectByName("tradeResourceText["+i+"]");
var buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
var buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
var iconSel = Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]");
button[resource] = { "up": buttonUp, "dn": buttonDn, "label": label, "sel": iconSel };
let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer);
let button = {};
let resCodes = g_ResourceData.GetCodes();
let selec = resCodes[0];
hideRemaining("tradeResources", resCodes.length);
Engine.GetGUIObjectByName("tradeHelp").hidden = false;
for (let i = 0; i < resCodes.length; ++i)
{
let tradeResource = Engine.GetGUIObjectByName("tradeResource["+i+"]");
if (!tradeResource)
{
warn("Current GUI limits prevent displaying more than " + r + " resources in the trading goods selection dialog!");
break;
}
setPanelObjectPosition(tradeResource, i, i+1);
let resCode = resCodes[i];
proba[resCode] = proba[resCode] || 0;
let icon = Engine.GetGUIObjectByName("tradeResourceIcon["+i+"]");
icon.sprite = "stretched:session/icons/resources/" + resCode + ".png";
let buttonUp = Engine.GetGUIObjectByName("tradeArrowUp["+i+"]");
let buttonDn = Engine.GetGUIObjectByName("tradeArrowDn["+i+"]");
button[resCode] = {
"up": buttonUp,
"dn": buttonDn,
"label": Engine.GetGUIObjectByName("tradeResourceText["+i+"]"),
"sel": Engine.GetGUIObjectByName("tradeResourceSelection["+i+"]")
};
let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
buttonResource.enabled = controlsPlayer(g_ViewedPlayer);
buttonResource.onpress = (function(resource){
return function() {
if (Engine.HotkeyIsPressed("session.fulltradeswap"))
{
for (var ress of RESOURCES)
proba[ress] = 0;
for (let res of resCodes)
proba[res] = 0;
proba[resource] = 100;
Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
}
selec = resource;
updateButtons();
};
})(resource);
})(resCode);
buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
buttonUp.onpress = (function(resource){
@ -513,7 +559,7 @@ function openTrade()
Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
updateButtons();
};
})(resource);
})(resCode);
buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
buttonDn.onpress = (function(resource){
@ -523,14 +569,13 @@ function openTrade()
Engine.PostNetworkCommand({"type": "set-trading-goods", "tradingGoods": proba});
updateButtons();
};
})(resource);
})(resCode);
}
updateButtons();
let traderNumber = Engine.GuiInterfaceCall("GetTraderNumber", g_ViewedPlayer);
Engine.GetGUIObjectByName("landTraders").caption = getIdleLandTradersText(traderNumber);
Engine.GetGUIObjectByName("shipTraders").caption = getIdleShipTradersText(traderNumber);
Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false;
}

View file

@ -12,11 +12,13 @@ function layoutSelectionMultiple()
function getResourceTypeDisplayName(resourceType)
{
let resourceCode = resourceType.generic;
if (resourceCode == "treasure")
return getLocalizedResourceName(resourceType.specific, "firstWord");
else
return getLocalizedResourceName(resourceCode, "firstWord");
return getLocalizedResourceName(
g_ResourceData.GetNames()[
resourceType.generic == "treasure" ?
resourceType.specific :
resourceType.generic
],
"firstWord");
}
// Updates the health bar of garrisoned units
@ -182,15 +184,16 @@ function displaySingle(entState)
"max": entState.resourceSupply.max
});
let resourceType = getResourceTypeDisplayName(entState.resourceSupply.type);
let unitResourceBar = Engine.GetGUIObjectByName("resourceBar");
let resourceSize = unitResourceBar.size;
resourceSize.rright = entState.resourceSupply.isInfinite ? 100 :
100 * Math.max(0, Math.min(1, +entState.resourceSupply.amount / +entState.resourceSupply.max));
unitResourceBar.size = resourceSize;
Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), { "resource": resourceType });
Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), {
"resource": getResourceTypeDisplayName(entState.resourceSupply.type)
});
Engine.GetGUIObjectByName("resourceStats").caption = resources;
if (entState.hitpoints)
@ -406,7 +409,8 @@ function displayMultiple(entStates)
if (Object.keys(totalCarrying).length)
numberOfUnits.tooltip = sprintf(translate("%(label)s %(details)s\n"), {
"label": headerFont(translate("Carrying:")),
"details": bodyFont(RESOURCES.filter(res => !!totalCarrying[res]).map(
"details": bodyFont(Object.keys(totalCarrying).filter(
res => totalCarrying[res] != 0).map(
res => sprintf(translate("%(type)s %(amount)s"),
{ "type": costIcon(res), "amount": totalCarrying[res] })).join(" "))
});
@ -414,7 +418,8 @@ function displayMultiple(entStates)
if (Object.keys(totalLoot).length)
numberOfUnits.tooltip += sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Loot:")),
"details": bodyFont(Object.keys(totalLoot).map(
"details": bodyFont(Object.keys(totalLoot).filter(
res => totalLoot[res] != 0).map(
res => sprintf(translate("%(type)s %(amount)s"),
{ "type": costIcon(res), "amount": totalLoot[res] })).join(" "))
});

View file

@ -33,6 +33,8 @@ let g_FormationsInfo = new Map();
let g_SelectionPanels = {};
let g_BarterSell;
g_SelectionPanels.Alert = {
"getMaxNumberOfItems": function()
{
@ -104,8 +106,7 @@ g_SelectionPanels.Barter = {
{
if (unitEntStates.every(state => !state.barterMarket))
return [];
// ["food", "wood", "stone", "metal"]
return BARTER_RESOURCES;
return g_ResourceData.GetCodes();
},
"setupButton": function(data)
{
@ -125,6 +126,9 @@ g_SelectionPanels.Barter = {
if (Engine.HotkeyIsPressed("session.massbarter"))
amountToSell *= BARTER_BUNCH_MULTIPLIER;
if (!g_BarterSell)
g_BarterSell = g_ResourceData.GetCodes()[0];
amount.Sell.caption = "-" + amountToSell;
let prices;
for (let state of data.unitEntStates)
@ -136,7 +140,7 @@ g_SelectionPanels.Barter = {
amount.Buy.caption = "+" + Math.round(prices.sell[g_BarterSell] / prices.buy[data.item] * amountToSell);
let resource = getLocalizedResourceName(data.item, "withinSentence");
let resource = getLocalizedResourceName(g_ResourceData.GetNames()[data.item], "withinSentence");
button.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource });
button.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource });

View file

@ -1,18 +1,11 @@
const BARTER_RESOURCE_AMOUNT_TO_SELL = 100;
const BARTER_BUNCH_MULTIPLIER = 5;
const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
const BARTER_ACTIONS = ["Sell", "Buy"];
const GATE_ACTIONS = ["lock", "unlock"];
// upgrade constants
const UPGRADING_NOT_STARTED = -2;
const UPGRADING_CHOSEN_OTHER = -1;
// ==============================================
// BARTER HELPERS
// Resources to sell on barter panel
var g_BarterSell = "food";
function canMoveSelectionIntoFormation(formationTemplate)
{
if (!(formationTemplate in g_canMoveIntoFormation))

View file

@ -1,24 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="unitBarterPanel"
size="6 36 100% 100%"
size="4 68 100% 100%"
hidden="true"
>
<object ghost="true" style="resourceText" type="text" size="0 0 100% 20">
<translatableAttribute id="tooltip">Exchange resources:</translatableAttribute>
</object>
<object size="0 32 100% 124">
<repeat count="4">
<!-- sell -->
<object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
<object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
<object name="unitBarterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
</object>
<!-- buy -->
<object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold">
<object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
<object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
</object>
</repeat>
</object>
<repeat count="4">
<!-- Sell -->
<object name="unitBarterSellButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold" hidden="true">
<object name="unitBarterSellIcon[n]" type="image" ghost="true" size="3 3 100%-3 100%-3"/>
<object name="unitBarterSellAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
<object name="unitBarterSellSelection[n]" hidden="true" type="image" ghost="true" size="3 3 100%-3 100%-3" sprite="stretched:session/icons/corners.png"/>
</object>
<!-- Buy -->
<object name="unitBarterBuyButton[n]" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottomBold" hidden="true">
<object name="unitBarterBuyIcon[n]" type="image" ghost="true" size="3 3 100%-3 100%-3"/>
<object name="unitBarterBuyAmount[n]" ghost="true" style="resourceText" type="text" size="0 0 100% 50%"/>
</object>
</repeat>
</object>

View file

@ -136,6 +136,7 @@ var g_EntityStates = {};
var g_TemplateData = {};
var g_TemplateDataWithoutLocalization = {};
var g_TechnologyData = {};
var g_ResourceData = new Resources();
/**
* Top coordinate of the research list.
@ -274,7 +275,10 @@ function init(initData, hotloadData)
let gameSpeedIdx = g_GameSpeeds.Speed.indexOf(Engine.GetSimRate());
gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : g_GameSpeeds.Default;
gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); };
initMenuPosition();
resizeDiplomacyDialog();
resizeTradeDialog();
for (let slot in Engine.GetGUIObjectByName("unitHeroPanel").children)
initGUIHeroes(slot);
@ -496,10 +500,27 @@ function updateTopPanel()
let viewPlayer = Engine.GetGUIObjectByName("viewPlayer");
viewPlayer.hidden = !g_IsObserver && !g_DevSettings.changePerspective;
Engine.GetGUIObjectByName("food").hidden = !isPlayer;
Engine.GetGUIObjectByName("wood").hidden = !isPlayer;
Engine.GetGUIObjectByName("stone").hidden = !isPlayer;
Engine.GetGUIObjectByName("metal").hidden = !isPlayer;
let resCodes = g_ResourceData.GetCodes();
let r = 0;
for (let res of resCodes)
{
if (!Engine.GetGUIObjectByName("resource["+r+"]"))
{
warn("Current GUI limits prevent displaying more than " + r + " resources in the top panel!");
break;
}
Engine.GetGUIObjectByName("resource["+r+"]_icon").sprite = "stretched:session/icons/resources/" + res + ".png";
Engine.GetGUIObjectByName("resource["+r+"]").hidden = !isPlayer;
++r;
}
horizontallySpaceObjects("resourceCounts", 5);
hideRemaining("resourceCounts", r);
let resPop = Engine.GetGUIObjectByName("population");
let resPopSize = resPop.size;
resPopSize.left = Engine.GetGUIObjectByName("resource["+ (r-1) +"]").size.right;
resPop.size = resPopSize;
Engine.GetGUIObjectByName("population").hidden = !isPlayer;
Engine.GetGUIObjectByName("diplomacyButton1").hidden = !isPlayer;
Engine.GetGUIObjectByName("tradeButton1").hidden = !isPlayer;
@ -994,10 +1015,15 @@ function updatePlayerDisplay()
if (!playerState)
return;
for (let res of RESOURCES)
let resCodes = g_ResourceData.GetCodes();
let resNames = g_ResourceData.GetNames();
for (let r = 0; r < resCodes.length; ++r)
{
Engine.GetGUIObjectByName("resource_" + res).caption = Math.floor(playerState.resourceCounts[res]);
Engine.GetGUIObjectByName(res).tooltip = getLocalizedResourceName(res, "firstWord") + getAllyStatTooltip(res);
if (!Engine.GetGUIObjectByName("resource["+r+"]"))
break;
let res = resCodes[r];
Engine.GetGUIObjectByName("resource["+r+"]").tooltip = getLocalizedResourceName(resNames[res], "firstWord") + getAllyStatTooltip(res);
Engine.GetGUIObjectByName("resource["+r+"]_count").caption = Math.floor(playerState.resourceCounts[res]);
}
Engine.GetGUIObjectByName("resourcePop").caption = sprintf(translate("%(popCount)s/%(popLimit)s"), playerState);

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="food" size="10 0 100 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/food.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_food"/>
</object>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="metal" size="280 0 370 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/metal.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_metal"/>
</object>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="population" size="370 0 460 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 34" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
<object name="population" size="0 0 50%-52 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -2 40 38" type="image" sprite="stretched:session/icons/resources/population.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resourcePop"/>
</object>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="stone" size="190 0 280 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/stone.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_stone"/>
</object>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="wood" size="100 0 190 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -4 40 36" type="image" sprite="stretched:session/icons/resources/wood.png" ghost="true"/>
<object size="32 0 100% 100%-2" type="text" style="resourceText" name="resource_wood"/>
</object>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<object size="0 0 50%-90-52 100%" name="resourceCounts">
<repeat count="4">
<object name="resource[n]" size="0 0 95 100%" type="image" style="resourceCounter" tooltip_style="sessionToolTipBold">
<object size="0 -2 40 38" type="image" name="resource[n]_icon" ghost="true"/>
<object size="34 0 100%-2 100%-2" type="text" style="resourceText" name="resource[n]_count"/>
</object>
</repeat>
</object>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<object name="tradeDialogPanel"
size="50%-250 50%-130 50%+250 50%+100"
size="50%-134 50%-130 50%+134 50%+100"
type="image"
hidden="true"
sprite="ModernDialog"
@ -15,8 +15,8 @@
<translatableAttribute id="caption">Trading goods selection:</translatableAttribute>
</object>
<object size="180 0 100% 100%">
<repeat count="4">
<object size="180 0 100% 100%" name="tradeResources">
<repeat count="8">
<object name="tradeResource[n]" size="0 0 58 32">
<object name="tradeResourceButton[n]" size="4 0 36 100%" type="button" style="StoneButton">
<object name="tradeResourceIcon[n]" type="image" ghost="true"/>

View file

@ -116,7 +116,7 @@ function draw()
if (p>c)
c = p;
hideRemaining("phase["+i+"]_struct["+s+"]_row["+r+"]_prod[", p, "]");
hideRemaining("phase["+i+"]_struct["+s+"]_row["+r+"]", p);
}
let size = thisEle.size;
@ -136,13 +136,14 @@ function draw()
phaEle.size = size;
}
++r;
hideRemaining("phase["+i+"]_struct["+s+"]_row[", r, "]");
hideRemaining("phase["+i+"]_struct["+s+"]_rows", r);
++s;
prodBarWidth += eleWidth + defMargin;
}
hideRemaining("phase["+i+"]_struct[", s, "]");
let offset = getPositionOffset(i);
hideRemaining("phase["+i+"]", s);
let offset = getPositionOffset(i);
// Resize phase bars
for (let j = 1; j < phaseList.length - i; ++j)
{
@ -195,7 +196,7 @@ function draw()
++p;
}
}
hideRemaining("trainer["+t+"]_prod[", p, "]");
hideRemaining("trainer["+t+"]_row", p);
let size = thisEle.size;
size.right = size.left + Math.max(p*24, defWidth) + 4;
@ -209,7 +210,7 @@ function draw()
phaEle.size = size;
++t;
}
hideRemaining("trainer[", t, "]");
hideRemaining("trainers", t);
let size = Engine.GetGUIObjectByName("display_tree").size;
size.right = t > 0 ? -124 : -4;
@ -251,18 +252,6 @@ function getPositionOffset(idx)
return size;
}
function hideRemaining(prefix, idx, suffix)
{
let obj = Engine.GetGUIObjectByName(prefix + idx + suffix);
while (obj)
{
obj.hidden = true;
++idx;
obj = Engine.GetGUIObjectByName(prefix + idx + suffix);
}
}
/**
* Positions certain elements that only need to be positioned once
* (as <repeat> does not reposition automatically).
@ -299,7 +288,7 @@ function predraw()
prodBarIcon.sprite = "stretched:session/portraits/"+g_ParsedData.phases[phaseList[i+j]].icon;
}
// Hide remaining prod bars
hideRemaining("phase["+i+"]_bar[", j-1, "]");
hideRemaining("phase["+i+"]_bars", j-1);
let s = 0;
let ele = Engine.GetGUIObjectByName("phase["+i+"]_struct["+s+"]");
@ -350,8 +339,8 @@ function predraw()
g_DrawLimits[pha].structQuant = s;
++i;
}
hideRemaining("phase[", i, "]");
hideRemaining("phase[", i, "]_bar");
hideRemaining("phase_rows", i);
hideRemaining("phase_ident", i);
let t = 0;
let ele = Engine.GetGUIObjectByName("trainer["+t+"]");

View file

@ -91,5 +91,5 @@ function depath(path)
function GetTemplateData(templateName)
{
var template = loadTemplate(templateName);
return GetTemplateDataHelper(template, null, g_AuraData);
return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
}

View file

@ -5,28 +5,24 @@
*/
function getGatherRates(templateName)
{
// TODO: It would be nice to use the gather rates present in the templates
// instead of hard-coding the possible rates here.
let rates = {};
// We ignore ruins here, as those are not that common and would skew the results
var types = {
"food": ["food", "food.fish", "food.fruit", "food.grain", "food.meat", "food.milk"],
"wood": ["wood", "wood.tree"],
"stone": ["stone", "stone.rock"],
"metal": ["metal", "metal.ore"]
};
var rates = {};
for (let type in types)
for (let resource of g_ResourceData.GetResources())
{
let types = [resource.code];
for (let subtype in resource.subtypes)
// We ignore ruins as those are not that common and skew the results
if (subtype !== "ruins")
types.push(resource.code + "." + subtype);
let count, rate;
[rate, count] = types[type].reduce(function(sum, t) {
[rate, count] = types.reduce((sum, t) => {
let r = +fetchValue(templateName, "ResourceGatherer/Rates/"+t);
return [sum[0] + (r > 0 ? r : 0), sum[1] + (r > 0 ? 1 : 0)];
}, [0, 0]);
if (rate > 0)
rates[type] = Math.round(rate / count * 100) / 100;
rates[resource.code] = +(rate / count).toFixed(1);
}
if (!Object.keys(rates).length)
@ -41,7 +37,7 @@ function loadUnit(templateName)
return null;
var template = loadTemplate(templateName);
var unit = GetTemplateDataHelper(template, null, g_AuraData);
var unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
unit.phase = false;
if (unit.requiredTechnology)
@ -94,7 +90,7 @@ function loadUnit(templateName)
function loadStructure(templateName)
{
var template = loadTemplate(templateName);
var structure = GetTemplateDataHelper(template, null, g_AuraData);
var structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData);
structure.phase = false;
if (structure.requiredTechnology)
@ -180,7 +176,7 @@ function loadStructure(templateName)
function loadTechnology(techName)
{
var template = loadTechData(techName);
var tech = GetTechnologyDataHelper(template, g_SelectedCiv);
var tech = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
tech.reqs = {};
if (template.pair !== undefined)
@ -243,7 +239,7 @@ function loadTechnology(techName)
function loadPhase(phaseCode)
{
var template = loadTechData(phaseCode);
var phase = GetTechnologyDataHelper(template, g_SelectedCiv);
var phase = GetTechnologyDataHelper(template, g_SelectedCiv, g_ResourceData);
phase.actualPhase = phaseCode;
if (template.replaces !== undefined)

View file

@ -9,13 +9,15 @@
<object type="image" style="StructIcon" name="phase[k]_struct[s]_icon"
sprite="stretched:pregame/shell/logo/wfg_logo_white.png"
/>
<repeat count="4" var="r">
<object name="phase[k]_struct[s]_row[r]">
<repeat count="24" var="p">
<object type="image" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
</repeat>
</object>
</repeat>
<object name="phase[k]_struct[s]_rows">
<repeat count="4" var="r">
<object name="phase[k]_struct[s]_row[r]">
<repeat count="24" var="p">
<object type="image" style="ProdBox" name="phase[k]_struct[s]_row[r]_prod[p]"/>
</repeat>
</object>
</repeat>
</object>
</object>
</repeat>
</object>

View file

@ -9,6 +9,7 @@ var g_Lists = {};
var g_CivData = {};
var g_SelectedCiv = "";
var g_CallbackSet = false;
var g_ResourceData = new Resources();
/**
* Initialize the dropdown containing all the available civs

View file

@ -64,16 +64,20 @@
<!-- Structure Tree display -->
<object size="0 54+64 100%-124 100%-54" name="display_tree">
<repeat count="4" var="n">
<object name="phase[n]_phase" type="image"/>
<object name="phase[n]_bar">
<repeat count="4" var="k">
<object name="phase[n]_bar[k]" type="image" sprite="ProdBar">
<object name="phase[n]_bar[k]_icon" type="image" size="2 2 20 20"/>
<object name="phase_ident">
<repeat count="4" var="n">
<object>
<object name="phase[n]_phase" type="image"/>
<object name="phase[n]_bars">
<repeat count="4" var="k">
<object name="phase[n]_bar[k]" type="image" sprite="ProdBar">
<object name="phase[n]_bar[k]_icon" type="image" size="2 2 20 20"/>
</object>
</repeat>
</object>
</repeat>
</object>
</repeat>
</object>
</repeat>
</object>
<object type="image" name="tree_display" style="TreeDisplay" size="48+16+8 0 100%-12 100%">
<include file="gui/structree/rows.xml"/>
@ -93,7 +97,7 @@
<translatableAttribute id="caption">Trainer Units</translatableAttribute>
</object>
<object type="image" style="TreeDisplay" size="0 24 100% 100%">
<object type="image" style="TreeDisplay" size="0 24 100% 100%" name="trainers">
<repeat count="3" var="t">
<object type="image" style="StructBox" name="trainer[t]">
<object type="text" style="StructNameSpecific" name="trainer[t]_name"/>

View file

@ -282,7 +282,7 @@ function calculateUnits(playerState, position)
function calculateResources(playerState, position)
{
let type = g_ResourcesTypes[position];
let type = g_ResourceData.GetCodes()[position];
return formatIncome(
playerState.statistics.resourcesGathered[type],
@ -294,7 +294,7 @@ function calculateTotalResources(playerState)
let totalGathered = 0;
let totalUsed = 0;
for (let type of g_ResourcesTypes)
for (let type of g_ResourceData.GetCodes())
{
totalGathered += playerState.statistics.resourcesGathered[type];
totalUsed += playerState.statistics.resourcesUsed[type] - playerState.statistics.resourcesSold[type];
@ -362,7 +362,7 @@ function calculateResourcesTeam(counters)
function calculateResourceExchanged(playerState, position)
{
let type = g_ResourcesTypes[position];
let type = g_ResourceData.GetCodes()[position];
return formatIncome(
playerState.statistics.resourcesBought[type],

View file

@ -95,10 +95,11 @@ var g_ScorePanelsData = {
"resources": {
"headings": [
{ "caption": translate("Player name"), "yStart": 26, "width": 200 },
{ "caption": translate("Food"), "yStart": 34, "width": 100 },
{ "caption": translate("Wood"), "yStart": 34, "width": 100 },
{ "caption": translate("Stone"), "yStart": 34, "width": 100 },
{ "caption": translate("Metal"), "yStart": 34, "width": 100 },
...g_ResourceData.GetResources().map(res => ({
"caption": translateWithContext("firstWord", res.name),
"yStart": 34,
"width": 100
})),
{ "caption": translate("Total"), "yStart": 34, "width": 110 },
{
"caption": sprintf(translate("Tributes \n(%(sent)s / %(received)s)"),
@ -120,14 +121,15 @@ var g_ScorePanelsData = {
"used": g_OutcomeColor + translate("Used") + '[/color]'
}),
"yStart": 16,
"width": (100 * 4 + 110)
}, // width = 510
"width": 100 * g_ResourceData.GetCodes().length + 110
},
],
"counters": [
{ "width": 100, "fn": calculateResources, "verticalOffset": 12 },
{ "width": 100, "fn": calculateResources, "verticalOffset": 12 },
{ "width": 100, "fn": calculateResources, "verticalOffset": 12 },
{ "width": 100, "fn": calculateResources, "verticalOffset": 12 },
...g_ResourceData.GetCodes().map(code => ({
"fn": calculateResources,
"verticalOffset": 12,
"width": 100
})),
{ "width": 110, "fn": calculateTotalResources, "verticalOffset": 12 },
{ "width": 121, "fn": calculateTributeSent, "verticalOffset": 12 },
{ "width": 100, "fn": calculateTreasureCollected, "verticalOffset": 12 },
@ -138,19 +140,28 @@ var g_ScorePanelsData = {
"market": {
"headings": [
{ "caption": translate("Player name"), "yStart": 26, "width": 200 },
{ "caption": translate("Food exchanged"), "yStart": 16, "width": 100 },
{ "caption": translate("Wood exchanged"), "yStart": 16, "width": 100 },
{ "caption": translate("Stone exchanged"), "yStart": 16, "width": 100 },
{ "caption": translate("Metal exchanged"), "yStart": 16, "width": 100 },
...g_ResourceData.GetResources().map(res => {
return {
"caption":
// Translation: use %(resourceWithinSentence)s if needed
sprintf(translate("%(resourceFirstWord)s exchanged"), {
"resourceFirstWord": translateWithContext("firstWord", res.name),
"resourceWithinSentence": translateWithContext("withinSentence", res.name)
}),
"yStart": 16,
"width": 100
};
}),
{ "caption": translate("Barter efficiency"), "yStart": 16, "width": 100 },
{ "caption": translate("Trade income"), "yStart": 16, "width": 100 }
],
"titleHeadings": [],
"counters": [
{ "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
{ "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
{ "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
{ "width": 100, "fn": calculateResourceExchanged, "verticalOffset": 12 },
...g_ResourceData.GetCodes().map(code => ({
"width": 100,
"fn": calculateResourceExchanged,
"verticalOffset": 12
})),
{ "width": 100, "fn": calculateBarterEfficiency, "verticalOffset": 12 },
{ "width": 100, "fn": calculateTradeIncome, "verticalOffset": 12 }
],

View file

@ -17,7 +17,6 @@ const g_CapturedColor = '[color="255 255 157"]';
const g_BuildingsTypes = [ "total", "House", "Economic", "Outpost", "Military", "Fortress", "CivCentre", "Wonder" ];
const g_UnitsTypes = [ "total", "Infantry", "Worker", "Cavalry", "Champion", "Hero", "Siege", "Ship", "Trader" ];
const g_ResourcesTypes = [ "food", "wood", "stone", "metal" ];
// Colors used for gathered and traded resources
const g_IncomeColor = '[color="201 255 200"]';
@ -34,6 +33,7 @@ var g_PlayerCount = 0;
// Count players without team (or all if teams are not displayed)
var g_WithoutTeam = 0;
var g_GameData;
var g_ResourceData = new Resources();
function selectPanel(panel)
{

View file

@ -289,7 +289,7 @@
}
}
},
{
{
"extractor": "json",
"filemasks": [
"gui/credits/texts/**.json"
@ -314,6 +314,38 @@
]
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/resources/**.json"
],
"options": {
"keywords": [
"name",
"subtypes"
],
"comments": [
"Translation: Word as used at the beginning of a sentence or as a single-word sentence."
],
"context": "firstWord"
}
},
{
"extractor": "json",
"filemasks": [
"simulation/data/resources/**.json"
],
"options": {
"keywords": [
"name",
"subtypes"
],
"comments": [
"Translation: Word as used in the middle of a sentence (which may require using lowercase for your language)."
],
"context": "withinSentence"
}
},
{
"extractor": "txt",
"filemasks": [

View file

@ -9,7 +9,8 @@ m.Resources = function(amounts = {}, population = 0)
this.population = population > 0 ? population : 0;
};
m.Resources.prototype.types = []; // This array will be filled in SharedScript.init
// This array will be filled in SharedScript.init
m.Resources.prototype.types = [];
m.Resources.prototype.reset = function()
{

View file

@ -182,13 +182,10 @@ m.SharedScript.prototype.init = function(state, deserialization)
this.accessibility.init(state, this.terrainAnalyzer);
// Setup resources
this.resourceTypes = { "food": 0, "wood": 1, "stone": 2, "metal": 2 };
this.resourceList = [];
for (let res in this.resourceTypes)
this.resourceList.push(res);
m.Resources.prototype.types = this.resourceList;
this.resourceInfo = state.resources;
m.Resources.prototype.types = state.resources.codes;
// Resource types: 0 = not used for resource maps
// 1 = abondant resource with small amount each
// 1 = abundant resource with small amount each
// 2 = spare resource, but huge amount each
// The following maps are defined in TerrainAnalysis.js and are used for some building placement (cc, dropsites)
// They are updated by checking for create and destroy events for all resources
@ -199,18 +196,6 @@ m.SharedScript.prototype.init = function(state, deserialization)
this.ccResourceMaps = {}; // Contains maps showing the density of resources, optimized for CC placement.
this.createResourceMaps();
/** Keep in sync with gui/common/l10n.js */
this.resourceNames = {
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"food": markForTranslationWithContext("withinSentence", "Food"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"wood": markForTranslationWithContext("withinSentence", "Wood"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"metal": markForTranslationWithContext("withinSentence", "Metal"),
// Translation: Word as used in the middle of a sentence (which may require using lowercase for your language).
"stone": markForTranslationWithContext("withinSentence", "Stone"),
};
this.gameState = {};
for (let i in this._players)
{

View file

@ -383,9 +383,9 @@ m.Accessibility.prototype.floodFill = function(startIndex, value, onWater)
/** creates a map of resource density */
m.SharedScript.prototype.createResourceMaps = function()
{
for (let resource of this.resourceList)
for (let resource of this.resourceInfo.codes)
{
if (this.resourceTypes[resource] !== 1 && this.resourceTypes[resource] !== 2)
if (this.resourceInfo.aiInfluenceGroups[resource] === 0)
continue;
// if there is no resourceMap create one with an influence for everything with that resource
if (this.resourceMaps[resource])
@ -405,11 +405,11 @@ m.SharedScript.prototype.createResourceMaps = function()
let cellSize = this.resourceMaps[resource].cellSize;
let x = Math.floor(ent.position()[0] / cellSize);
let z = Math.floor(ent.position()[1] / cellSize);
let type = this.resourceTypes[resource];
let strength = Math.floor(ent.resourceSupplyMax()/this.normalizationFactor[type]);
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2, "constant");
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2);
this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[type]/cellSize, strength, "constant");
let grp = this.resourceInfo.aiInfluenceGroups[resource];
let strength = Math.floor(ent.resourceSupplyMax() / this.normalizationFactor[grp]);
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant");
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2);
this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
}
};
@ -420,9 +420,9 @@ m.SharedScript.prototype.createResourceMaps = function()
*/
m.SharedScript.prototype.updateResourceMaps = function(events)
{
for (let resource of this.resourceList)
for (let resource of this.resourceInfo.codes)
{
if (this.resourceTypes[resource] !== 1 && this.resourceTypes[resource] !== 2)
if (this.resourceInfo.aiInfluenceGroups[resource] === 0)
continue;
// if there is no resourceMap create one with an influence for everything with that resource
if (this.resourceMaps[resource])
@ -447,11 +447,11 @@ m.SharedScript.prototype.updateResourceMaps = function(events)
let cellSize = this.resourceMaps[resource].cellSize;
let x = Math.floor(ent.position()[0] / cellSize);
let z = Math.floor(ent.position()[1] / cellSize);
let type = this.resourceTypes[resource];
let strength = -Math.floor(ent.resourceSupplyMax()/this.normalizationFactor[type]);
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2, "constant");
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2);
this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[type]/cellSize, strength, "constant");
let grp = this.resourceInfo.aiInfluenceGroups[resource];
let strength = -Math.floor(ent.resourceSupplyMax() / this.normalizationFactor[grp]);
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant");
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2);
this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
}
for (let e of events.Create)
{
@ -466,11 +466,11 @@ m.SharedScript.prototype.updateResourceMaps = function(events)
let cellSize = this.resourceMaps[resource].cellSize;
let x = Math.floor(ent.position()[0] / cellSize);
let z = Math.floor(ent.position()[1] / cellSize);
let type = this.resourceTypes[resource];
let strength = Math.floor(ent.resourceSupplyMax()/this.normalizationFactor[type]);
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2, "constant");
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[type]/cellSize, strength/2);
this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[type]/cellSize, strength, "constant");
let grp = this.resourceInfo.aiInfluenceGroups[resource];
let strength = Math.floor(ent.resourceSupplyMax() / this.normalizationFactor[grp]);
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2, "constant");
this.resourceMaps[resource].addInfluence(x, z, this.influenceRadius[grp] / cellSize, strength/2);
this.ccResourceMaps[resource].addInfluence(x, z, this.ccInfluenceRadius[grp] / cellSize, strength, "constant");
}
};

View file

@ -55,7 +55,7 @@ m.BaseManager.prototype.init = function(gameState, state)
this.dropsites = {};
this.dropsiteSupplies = {};
this.gatherers = {};
for (let res of gameState.sharedScript.resourceList)
for (let res of gameState.sharedScript.resourceInfo.codes)
{
this.dropsiteSupplies[res] = { "nearby": [], "medium": [], "faraway": [] };
this.gatherers[res] = { "nextCheck": 0, "used": 0, "lost": 0 };
@ -434,7 +434,7 @@ m.BaseManager.prototype.getResourceLevel = function (gameState, type, nearbyOnly
/** check our resource levels and react accordingly */
m.BaseManager.prototype.checkResourceLevels = function (gameState, queues)
{
for (let type of gameState.sharedScript.resourceList)
for (let type of gameState.sharedScript.resourceInfo.codes)
{
if (type === "food")
{

View file

@ -17,7 +17,7 @@ m.chatLaunchAttack = function(gameState, player, type)
"message": "/allies "+ message,
"translateMessage": true,
"translateParameters": ["_player_"],
"parameters": {"_player_": player}
"parameters": { "_player_": player }
});
};
@ -45,7 +45,7 @@ m.chatAnswerRequestAttack = function(gameState, player, answer, other)
"message": "/allies " + message,
"translateMessage": true,
"translateParameters": ["_player_"],
"parameters": {"_player_": player}
"parameters": { "_player_": player }
};
if (other !== undefined)
{
@ -71,7 +71,7 @@ m.chatSentTribute = function(gameState, player)
"message": "/allies " + message,
"translateMessage": true,
"translateParameters": ["_player_"],
"parameters": {"_player_": player}
"parameters": { "_player_": player }
});
};
@ -90,8 +90,8 @@ m.chatRequestTribute = function(gameState, resource)
"type": "aichat",
"message": "/allies " + message,
"translateMessage": true,
"translateParameters": {"resource": "withinSentence"},
"parameters": {"resource": gameState.sharedScript.resourceNames[resource]}
"translateParameters": { "resource": "withinSentence" },
"parameters": { "resource": gameState.sharedScript.resourceInfo.names[resource] }
});
};
@ -109,7 +109,7 @@ m.chatNewTradeRoute = function(gameState, player)
"message": "/allies " + message,
"translateMessage": true,
"translateParameters": ["_player_"],
"parameters": {"_player_": player}
"parameters": { "_player_": player }
});
};

View file

@ -103,7 +103,26 @@ m.Config = function(difficulty)
"defensive": 0.5
};
this.resources = ["food", "wood", "stone", "metal"];
// See m.QueueManager.prototype.wantedGatherRates()
this.queues =
{
"firstTurn": {
"food": 10,
"wood": 10,
"default": 0
},
"short": {
"food": 200,
"wood": 200,
"default": 100
},
"medium": {
"default": 0
},
"long": {
"default": 0
}
};
};
m.Config.prototype.setConfig = function(gameState)

View file

@ -69,7 +69,7 @@ m.HQ.prototype.init = function(gameState, queues)
this.navalMap = false;
this.navalRegions = {};
for (let res of gameState.sharedScript.resourceList)
for (let res of gameState.sharedScript.resourceInfo.codes)
{
this.wantedRates[res] = 0;
this.currentRates[res] = 0;
@ -654,7 +654,7 @@ m.HQ.prototype.bulkPickWorkers = function(gameState, baseRef, number)
m.HQ.prototype.getTotalResourceLevel = function(gameState)
{
let total = {};
for (let res of gameState.sharedScript.resourceList)
for (let res of gameState.sharedScript.resourceInfo.codes)
total[res] = 0;
for (let base of this.baseManagers)
for (let res in total)

View file

@ -84,8 +84,8 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState)
if (gameState.ai.playedTurn === 0)
{
let ret = {};
for (let res of gameState.sharedScript.resourceList)
ret[res] = (res === "food" || res === "wood" ) ? 10 : 0;
for (let res of gameState.sharedScript.resourceInfo.codes)
ret[res] = this.Config.queues.firstTurn[res] || this.Config.queues.firstTurn.default;
return ret;
}
@ -97,11 +97,11 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState)
let totalShort = {};
let totalMedium = {};
let totalLong = {};
for (let res of gameState.sharedScript.resourceList)
for (let res of gameState.sharedScript.resourceInfo.codes)
{
totalShort[res] = (res === "food" || res === "wood" ) ? 200 : 100;
totalMedium[res] = 0;
totalLong[res] = 0;
totalShort[res] = this.Config.queues.short[res] || this.Config.queues.short.default;
totalMedium[res] = this.Config.queues.medium[res] || this.Config.queues.medium.default;
totalLong[res] = this.Config.queues.long[res] || this.Config.queues.long.default;
}
let total;
//queueArrays because it's faster.
@ -133,7 +133,7 @@ m.QueueManager.prototype.wantedGatherRates = function(gameState)
// global rates
let rates = {};
let diff;
for (let res of gameState.sharedScript.resourceList)
for (let res of gameState.sharedScript.resourceInfo.codes)
{
if (current[res] > 0)
{

View file

@ -100,7 +100,8 @@ m.ResearchManager.prototype.researchWantedTechs = function(gameState, techs)
let cost = template.cost;
let costMax = 0;
for (let res in cost)
costMax = Math.max(costMax, Math.max(cost[res]-available[res], 0));
if (gameState.sharedScript.resourceInfo.codes.indexOf(res) != -1)
costMax = Math.max(costMax, Math.max(cost[res]-available[res], 0));
if (10*numWorkers < costMax)
continue;
}

View file

@ -1,9 +1,8 @@
// True price of 100 units of resource (for case if some resource is more worth).
// The "true price" is a base price of 100 units of resource (for the case of some resources being of more worth than others).
// With current bartering system only relative values makes sense
// so if for example stone is two times more expensive than wood,
// there will 2:1 exchange rate.
const TRUE_PRICES = { "food": 100, "wood": 100, "stone": 100, "metal": 100 };
//
// Constant part of price difference between true price and buy/sell price.
// In percents.
// Buy price equal to true price plus constant difference.
@ -21,9 +20,6 @@ const DIFFERENCE_RESTORE = 0.5;
// Interval of timer which slowly restore prices after deals
const RESTORE_TIMER_INTERVAL = 5000;
// Array of resource names
const RESOURCES = ["food", "wood", "stone", "metal"];
function Barter() {}
Barter.prototype.Schema =
@ -32,7 +28,7 @@ Barter.prototype.Schema =
Barter.prototype.Init = function()
{
this.priceDifferences = {};
for (var resource of RESOURCES)
for (let resource of Resources.GetCodes())
this.priceDifferences[resource] = 0;
this.restoreTimer = undefined;
};
@ -40,10 +36,11 @@ Barter.prototype.Init = function()
Barter.prototype.GetPrices = function()
{
var prices = { "buy": {}, "sell": {} };
for (var resource of RESOURCES)
for (let resource of Resources.GetCodes())
{
prices["buy"][resource] = TRUE_PRICES[resource] * (100 + CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
prices["sell"][resource] = TRUE_PRICES[resource] * (100 - CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
let truePrice = Resources.GetResource(resource).truePrice;
prices.buy[resource] = truePrice * (100 + CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
prices.sell[resource] = truePrice * (100 - CONSTANT_DIFFERENCE + this.priceDifferences[resource]) / 100;
}
return prices;
};
@ -71,12 +68,13 @@ Barter.prototype.ExchangeResources = function(playerEntity, resourceToSell, reso
warn("ExchangeResources: incorrect amount: " + uneval(amount));
return;
}
if (RESOURCES.indexOf(resourceToSell) == -1)
let availResources = Resources.GetCodes();
if (availResources.indexOf(resourceToSell) == -1)
{
warn("ExchangeResources: incorrect resource to sell: " + uneval(resourceToSell));
return;
}
if (RESOURCES.indexOf(resourceToBuy) == -1)
if (availResources.indexOf(resourceToBuy) == -1)
{
warn("ExchangeResources: incorrect resource to buy: " + uneval(resourceToBuy));
return;
@ -135,7 +133,7 @@ Barter.prototype.ExchangeResources = function(playerEntity, resourceToSell, reso
Barter.prototype.ProgressTimeout = function(data)
{
var needRestore = false;
for (var resource of RESOURCES)
for (let resource of Resources.GetCodes())
{
// Calculate value to restore, it should be limited to [-DIFFERENCE_RESTORE; DIFFERENCE_RESTORE] interval
var differenceRestore = Math.min(DIFFERENCE_RESTORE, Math.max(-DIFFERENCE_RESTORE, this.priceDifferences[resource]));

View file

@ -19,16 +19,11 @@ Cost.prototype.Schema =
"<element name='PopulationBonus' a:help='Population cap increase while this entity exists'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"<element name='BuildTime' a:help='Time taken to construct/train this unit (in seconds)'>" +
"<element name='BuildTime' a:help='Time taken to construct/train this entity (in seconds)'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Resources' a:help='Resource costs to construct/train this unit'>" +
"<interleave>" +
"<element name='food'><data type='nonNegativeInteger'/></element>" +
"<element name='wood'><data type='nonNegativeInteger'/></element>" +
"<element name='stone'><data type='nonNegativeInteger'/></element>" +
"<element name='metal'><data type='nonNegativeInteger'/></element>" +
"</interleave>" +
"<element name='Resources' a:help='Resource costs to construct/train this entity'>" +
Resources.BuildSchema("nonNegativeDecimal") +
"</element>";
Cost.prototype.Init = function()
@ -70,8 +65,15 @@ Cost.prototype.GetResourceCosts = function(owner)
let entityTemplate = cmpTemplateManager.GetTemplate(entityTemplateName);
let costs = {};
for (let r in this.template.Resources)
costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +this.template.Resources[r], owner, entityTemplate);
let resCodes = Resources.GetCodes();
for (let res in this.template.Resources)
{
if (resCodes.indexOf(res) == -1)
continue;
costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, +this.template.Resources[res], owner, entityTemplate);
}
return costs;
};

View file

@ -153,6 +153,15 @@ GuiInterface.prototype.GetSimulationState = function()
let cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
ret.barterPrices = cmpBarter.GetPrices();
// Add Resource Codes, untranslated names and AI Analysis
ret.resources = {
"codes": Resources.GetCodes(),
"names": Resources.GetNames(),
"aiInfluenceGroups": {}
};
for (let res of ret.resources.codes)
ret.resources.aiInfluenceGroups[res] = Resources.GetResource(res).aiAnalysisInfluenceGroup || 0;
// Add basic statistics to each player
for (let i = 0; i < numPlayers; ++i)
{
@ -628,7 +637,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
let aurasTemplate = {};
if (!template.Auras)
return GetTemplateDataHelper(template, player, aurasTemplate);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
// Add aura name and description loaded from JSON file
let auraNames = template.Auras._string.split(/\s+/);
@ -646,7 +655,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
aurasTemplate[name].auraName = auraTemplate.auraName || null;
aurasTemplate[name].auraDescription = auraTemplate.auraDescription || null;
}
return GetTemplateDataHelper(template, player, aurasTemplate);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
};
GuiInterface.prototype.GetTechnologyData = function(player, name)
@ -661,7 +670,7 @@ GuiInterface.prototype.GetTechnologyData = function(player, name)
}
let cmpPlayer = QueryPlayerIDInterface(player, IID_Player);
return GetTechnologyDataHelper(template, cmpPlayer.GetCiv());
return GetTechnologyDataHelper(template, cmpPlayer.GetCiv(), Resources);
};
GuiInterface.prototype.IsTechnologyResearched = function(player, data)
@ -1285,8 +1294,10 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
let result = {
"pieces": [],
"cost": { "food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0 },
"cost": { "population": 0, "populationBonus": 0, "time": 0 },
};
for (let res of Resources.GetCodes())
result.cost[res] = 0;
let previewEntities = [];
if (end.pos)
@ -1561,13 +1572,8 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
// copied over, so we need to fetch it from the template instead).
// TODO: we should really use a Cost object or at least some utility functions for this, this is mindless
// boilerplate that's probably duplicated in tons of places.
result.cost.food += tplData.cost.food;
result.cost.wood += tplData.cost.wood;
result.cost.stone += tplData.cost.stone;
result.cost.metal += tplData.cost.metal;
result.cost.population += tplData.cost.population;
result.cost.populationBonus += tplData.cost.populationBonus;
result.cost.time += tplData.cost.time;
for (let res of Resources.GetCodes().concat("population", "populationBonus", "time"))
result.cost[res] = tplData.cost[res];
}
let canAfford = true;

View file

@ -1,21 +1,12 @@
function Loot() {}
Loot.prototype.Schema =
"<optional>" +
"<element name='xp'><data type='nonNegativeInteger'/></element>" +
"</optional>" +
"<optional>" +
"<element name='food'><data type='nonNegativeInteger'/></element>" +
"</optional>" +
"<optional>" +
"<element name='wood'><data type='nonNegativeInteger'/></element>" +
"</optional>" +
"<optional>" +
"<element name='stone'><data type='nonNegativeInteger'/></element>" +
"</optional>" +
"<optional>" +
"<element name='metal'><data type='nonNegativeInteger'/></element>" +
"</optional>";
"<a:help>Specifies the loot credited when this entity is killed.</a:help>" +
"<a:example>" +
"<xp>35</xp>" +
"<metal>10</metal>" +
"</a:example>" +
Resources.BuildSchema("nonNegativeInteger", ["xp"]);
Loot.prototype.Serialize = null; // we have no dynamic state to save
@ -26,12 +17,11 @@ Loot.prototype.GetXp = function()
Loot.prototype.GetResources = function()
{
return {
"food": +(this.template.food || 0),
"wood": +(this.template.wood || 0),
"metal": +(this.template.metal || 0),
"stone": +(this.template.stone || 0)
};
let ret = {};
for (let res of Resources.GetCodes())
ret[res] = +(this.template[res] || 0);
return ret;
};
Engine.RegisterComponentType(IID_Loot, "Loot", Loot);

View file

@ -24,12 +24,13 @@ Looter.prototype.Collect = function(targetEntity)
);
// Loot resources as defined in the templates
var resources = cmpLoot.GetResources();
for (let type in resources)
resources[type] = ApplyValueModificationsToEntity("Looter/Resource/"+type, resources[type], this.entity)
+ (resourcesCarried[type] || 0);
// TODO: stop assuming that cmpLoot.GetResources() delivers all resource types (by defining them in a central location)
let lootTemplate = cmpLoot.GetResources();
let resources = {};
for (let type of Resources.GetCodes())
resources[type] =
ApplyValueModificationsToEntity(
"Looter/Resource/"+type, lootTemplate[type] || 0, this.entity) +
(resourcesCarried[type] || 0);
// Transfer resources
var cmpPlayer = QueryOwnerInterface(this.entity);

View file

@ -18,18 +18,8 @@ Player.prototype.Init = function()
this.popBonuses = 0; // sum of population bonuses of player's entities
this.maxPop = 300; // maximum population
this.trainingBlocked = false; // indicates whether any training queue is currently blocked
this.resourceCount = {
"food": 300,
"wood": 300,
"metal": 300,
"stone": 300
};
// goods for next trade-route and its proba in % (the sum of probas must be 100)
this.tradingGoods = [
{ "goods": "wood", "proba": 30 },
{ "goods": "stone", "proba": 35 },
{ "goods": "metal", "proba": 35 },
];
this.resourceCount = {};
this.tradingGoods = []; // goods for next trade-route and its proba in % (the sum of probas must be 100)
this.team = -1; // team number of the player, players on the same team will always have ally diplomatic status - also this is useful for team emblems, scoring, etc.
this.teamsLocked = false;
this.state = "active"; // game state - one of "active", "defeated", "won"
@ -44,15 +34,25 @@ Player.prototype.Init = function()
this.cheatsEnabled = false;
this.cheatTimeMultiplier = 1;
this.heroes = [];
this.resourceNames = {
"food": markForTranslation("Food"),
"wood": markForTranslation("Wood"),
"metal": markForTranslation("Metal"),
"stone": markForTranslation("Stone"),
};
this.resourceNames = {};
this.disabledTemplates = {};
this.disabledTechnologies = {};
this.startingTechnologies = [];
// Initial resources and trading goods probability in steps of 5
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;
this.tradingGoods.push({
"goods": res,
"proba": 5 * (quotient + (+i < remainder ? 1 : 0))
});
}
};
Player.prototype.SetPlayerID = function(id)
@ -197,14 +197,9 @@ Player.prototype.UnBlockTraining = function()
Player.prototype.SetResourceCounts = function(resources)
{
if (resources.food !== undefined)
this.resourceCount.food = resources.food;
if (resources.wood !== undefined)
this.resourceCount.wood = resources.wood;
if (resources.stone !== undefined)
this.resourceCount.stone = resources.stone;
if (resources.metal !== undefined)
this.resourceCount.metal = resources.metal;
for (let res in resources)
if (this.resourceCount[res])
this.resourceCount[res] = resources[res];
};
Player.prototype.GetResourceCounts = function()
@ -228,9 +223,7 @@ Player.prototype.AddResource = function(type, amount)
Player.prototype.AddResources = function(amounts)
{
for (var type in amounts)
{
this.resourceCount[type] += +amounts[type];
}
};
Player.prototype.GetNeededResources = function(amounts)
@ -297,7 +290,8 @@ Player.prototype.SubtractResourcesOrNotify = function(amounts)
// Subtract the resources
for (var type in amounts)
this.resourceCount[type] -= amounts[type];
if (this.resourceCount[type])
this.resourceCount[type] -= amounts[type];
return true;
};
@ -340,18 +334,30 @@ Player.prototype.GetTradingGoods = function()
Player.prototype.SetTradingGoods = function(tradingGoods)
{
var sumProba = 0;
for (var resource in tradingGoods)
sumProba += tradingGoods[resource];
if (sumProba != 100) // consistency check
let resCodes = Resources.GetCodes();
let sumProba = 0;
for (let resource in tradingGoods)
{
error("Player.js SetTradingGoods: " + uneval(tradingGoods));
tradingGoods = { "food": 20, "wood":20, "stone":30, "metal":30 };
if (resCodes.indexOf(resource) == -1)
{
error("Invalid trading goods: " + uneval(tradingGoods));
return;
}
sumProba += tradingGoods[resource];
}
if (sumProba != 100)
{
error("Invalid trading goods: " + uneval(tradingGoods));
return;
}
this.tradingGoods = [];
for (var resource in tradingGoods)
this.tradingGoods.push( {"goods": resource, "proba": tradingGoods[resource]} );
for (let resource in tradingGoods)
this.tradingGoods.push({
"goods": resource,
"proba": tradingGoods[resource]
});
};
Player.prototype.GetState = function()

View file

@ -31,13 +31,7 @@ ProductionQueue.prototype.Schema =
"</element>" +
"</optional>" +
"<element name='TechCostMultiplier' a:help='Multiplier to modify ressources cost and research time of technologies searched in this building.'>" +
"<interleave>" +
"<element name='food'><ref name='nonNegativeDecimal'/></element>" +
"<element name='wood'><ref name='nonNegativeDecimal'/></element>" +
"<element name='stone'><ref name='nonNegativeDecimal'/></element>" +
"<element name='metal'><ref name='nonNegativeDecimal'/></element>" +
"<element name='time'><ref name='nonNegativeDecimal'/></element>" +
"</interleave>" +
Resources.BuildSchema("nonNegativeDecimal", ["time"]) +
"</element>";
ProductionQueue.prototype.Init = function()
@ -260,7 +254,8 @@ ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat
// TODO: there should probably be a limit on the number of queued batches
// TODO: there should be a way for the GUI to determine whether it's going
// to be possible to add a batch (based on resource costs and length limits)
var cmpPlayer = QueryOwnerInterface(this.entity);
let cmpPlayer = QueryOwnerInterface(this.entity);
let resCodes = Resources.GetCodes();
if (this.queue.length < MAX_QUEUE_SIZE)
{
@ -293,10 +288,12 @@ ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat
var buildTime = ApplyValueModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, cmpPlayer.GetPlayerID(), template);
var time = timeMult * buildTime;
for (var r in template.Cost.Resources)
for (let res in template.Cost.Resources)
{
costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +template.Cost.Resources[r], cmpPlayer.GetPlayerID(), template);
totalCosts[r] = Math.floor(count * costs[r]);
if (resCodes.indexOf(res) == -1)
continue;
costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, +template.Cost.Resources[res], cmpPlayer.GetPlayerID(), template);
totalCosts[res] = Math.floor(count * costs[res]);
}
var population = ApplyValueModificationsToTemplate("Cost/Population", +template.Cost.Population, cmpPlayer.GetPlayerID(), template);
@ -337,6 +334,7 @@ ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat
var template = cmpDataTemplateManager.GetTechnologyTemplate(templateName);
if (!template)
return;
if (!this.GetTechnologiesList().some(tech =>
tech &&
(tech == templateName ||
@ -346,13 +344,17 @@ ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat
error("This entity cannot research " + templateName);
return;
}
var cmpPlayer = QueryOwnerInterface(this.entity);
let techCostMultiplier = this.GetTechCostMultiplier();
let time = techCostMultiplier.time * template.researchTime * cmpPlayer.GetCheatTimeMultiplier();
var cost = {};
let cost = {};
for (let res in template.cost)
cost[res] = Math.floor(techCostMultiplier[res] * template.cost[res]);
{
if (resCodes.indexOf(res) == -1)
continue;
cost[res] = Math.floor((techCostMultiplier[res] || 1) * template.cost[res]);
}
// TrySubtractResources should report error to player (they ran out of resources)
if (!cmpPlayer.TrySubtractResources(cost))
@ -370,7 +372,7 @@ ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat
"player": cmpPlayer.GetPlayerID(),
"count": 1,
"technologyTemplate": templateName,
"resources": deepcopy(template.cost), // need to copy to avoid serialization problems
"resources": cost,
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
@ -442,8 +444,10 @@ ProductionQueue.prototype.RemoveBatch = function(id)
// Refund the resource cost for this batch
var totalCosts = {};
var cmpStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker);
for each (var r in ["food", "wood", "stone", "metal"])
for (let r of Resources.GetCodes())
{
if (!item.resources[r])
continue;
totalCosts[r] = Math.floor(item.count * item.resources[r]);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseResourceUsedCounter(r, -totalCosts[r]);

View file

@ -4,12 +4,7 @@ ResourceDropsite.prototype.Schema =
"<element name='Types'>" +
"<list>" +
"<zeroOrMore>" +
"<choice>" +
"<value>food</value>" +
"<value>wood</value>" +
"<value>stone</value>" +
"<value>metal</value>" +
"</choice>" +
Resources.BuildChoicesSchema() +
"</zeroOrMore>" +
"</list>" +
"</element>" +
@ -24,12 +19,14 @@ ResourceDropsite.prototype.Init = function()
};
/**
* Returns the list of resource types accepted by this dropsite.
* Returns the list of resource types accepted by this dropsite,
* as defined by it being referred to in the template and the resource being enabled.
*/
ResourceDropsite.prototype.GetTypes = function()
{
let types = ApplyValueModificationsToEntity("ResourceDropsite/Types", this.template.Types, this.entity);
return types ? types.split(/\s+/) : [];
let resources = Resources.GetCodes();
return types.split(/\s+/).filter(type => resources.indexOf(type) != -1);
};
/**

View file

@ -25,35 +25,10 @@ ResourceGatherer.prototype.Schema =
"<ref name='positiveDecimal'/>" +
"</element>" +
"<element name='Rates' a:help='Per-resource-type gather rate multipliers. If a resource type is not specified then it cannot be gathered by this unit'>" +
"<interleave>" +
"<optional><element name='food' a:help='Food gather rate (may be overridden by \"food.*\" subtypes)'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='wood' a:help='Wood gather rate'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='stone' a:help='Stone gather rate'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='metal' a:help='Metal gather rate'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='treasure' a:help='Treasure gather rate (only presense on value makes sense, size is only used to determine the delay before gathering, so it should be set to 1)'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.fish' a:help='Fish gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.fruit' a:help='Fruit gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.grain' a:help='Grain gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.meat' a:help='Meat gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='food.milk' a:help='Milk gather rate (overrides \"food\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='wood.tree' a:help='Tree gather rate (overrides \"wood\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='wood.ruins' a:help='Tree gather rate (overrides \"wood\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='stone.rock' a:help='Rock gather rate (overrides \"stone\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='stone.ruins' a:help='Rock gather rate (overrides \"stone\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='metal.ore' a:help='Ore gather rate (overrides \"metal\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='treasure.food' a:help='Food treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='treasure.wood' a:help='Wood treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='treasure.stone' a:help='Stone treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
"<optional><element name='treasure.metal' a:help='Metal treasure gather rate (overrides \"treasure\")'><ref name='positiveDecimal'/></element></optional>" +
"</interleave>" +
Resources.BuildSchema("positiveDecimal", ["treasure"], true) +
"</element>" +
"<element name='Capacities' a:help='Per-resource-type maximum carrying capacity'>" +
"<interleave>" +
"<element name='food' a:help='Food capacity'><ref name='positiveDecimal'/></element>" +
"<element name='wood' a:help='Wood capacity'><ref name='positiveDecimal'/></element>" +
"<element name='stone' a:help='Stone capacity'><ref name='positiveDecimal'/></element>" +
"<element name='metal' a:help='Metal capacity'><ref name='positiveDecimal'/></element>" +
"</interleave>" +
Resources.BuildSchema("positiveDecimal") +
"</element>";
ResourceGatherer.prototype.Init = function()
@ -137,6 +112,14 @@ ResourceGatherer.prototype.RecalculateGatherRatesAndCapacities = function()
this.rates = {};
for (let r in this.template.Rates)
{
let type = r.split(".");
if (type[0] != "treasure" && type.length > 1 && !Resources.GetResource(type[0]).subtypes[type[1]])
{
error("Resource subtype not found: " + type[0] + "." + type[1]);
continue;
}
let rate = ApplyValueModificationsToEntity("ResourceGatherer/Rates/" + r, +this.template.Rates[r], this.entity);
this.rates[r] = rate * this.baseSpeed;
}
@ -174,7 +157,7 @@ ResourceGatherer.prototype.GetRange = function()
/**
* Try to gather treasure
* @return 'true' if treasure is successfully gathered and 'false' in the other case
* @return 'true' if treasure is successfully gathered, otherwise 'false'
*/
ResourceGatherer.prototype.TryInstantGather = function(target)
{

View file

@ -12,23 +12,8 @@ ResourceSupply.prototype.Schema =
"<element name='Amount' a:help='Amount of resources available from this entity'>" +
"<choice><data type='nonNegativeInteger'/><value>Infinity</value></choice>" +
"</element>" +
"<element name='Type' a:help='Type of resources'>" +
"<choice>" +
"<value>wood.tree</value>" +
"<value>wood.ruins</value>" +
"<value>stone.rock</value>" +
"<value>stone.ruins</value>" +
"<value>metal.ore</value>" +
"<value>food.fish</value>" +
"<value>food.fruit</value>" +
"<value>food.grain</value>" +
"<value>food.meat</value>" +
"<value>food.milk</value>" +
"<value>treasure.wood</value>" +
"<value>treasure.stone</value>" +
"<value>treasure.metal</value>" +
"<value>treasure.food</value>" +
"</choice>" +
"<element name='Type' a:help='Type and Subtype of resource available from this entity'>" +
Resources.BuildChoicesSchema(true, true) +
"</element>" +
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
"<data type='nonNegativeInteger'/>" +
@ -45,16 +30,23 @@ ResourceSupply.prototype.Init = function()
this.amount = this.GetMaxAmount();
this.gatherers = []; // list of IDs for each players
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); // system component so that's safe.
var numPlayers = cmpPlayerManager.GetNumPlayers();
for (var i = 0; i <= numPlayers; ++i) // use "<=" because we want Gaia too.
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); // system component so that's safe.
let numPlayers = cmpPlayerManager.GetNumPlayers();
for (let i = 0; i <= numPlayers; ++i) // use "<=" because we want Gaia too.
this.gatherers.push([]);
this.infinite = !isFinite(+this.template.Amount);
[this.type,this.subType] = this.template.Type.split('.');
this.cachedType = { "generic" : this.type, "specific" : this.subType };
let [type, subtype] = this.template.Type.split('.');
let resData = type === "treasure" ?
{ "subtypes": Resources.GetNames() } :
Resources.GetResource(type);
// Remove entity from gameworld if the resource supplied by this entity is disabled or not valid.
if (!resData || !resData.subtypes[subtype])
Engine.DestroyEntity(this.entity);
this.cachedType = { "generic": type, "specific": subtype };
};
ResourceSupply.prototype.IsInfinite = function()

View file

@ -3,28 +3,7 @@ function ResourceTrickle() {}
ResourceTrickle.prototype.Schema =
"<a:help>Controls the resource trickle ability of the unit.</a:help>" +
"<element name='Rates' a:help='Trickle Rates'>" +
"<interleave>" +
"<optional>" +
"<element name='food' a:help='Food given to the player every interval'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='wood' a:help='Wood given to the player every interval'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='stone' a:help='Stone given to the player every interval'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='metal' a:help='Metal given to the player every interval'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</optional>" +
"</interleave>" +
Resources.BuildSchema("nonNegativeDecimal") +
"</element>" +
"<element name='Interval' a:help='Number of miliseconds must pass for the player to gain the next trickle.'>" +
"<ref name='nonNegativeDecimal'/>" +
@ -33,7 +12,7 @@ ResourceTrickle.prototype.Schema =
ResourceTrickle.prototype.Init = function()
{
this.ComputeRates();
// Call the timer
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.SetInterval(this.entity, IID_ResourceTrickle, "Trickle", this.GetTimer(), this.GetTimer(), undefined);
};
@ -51,11 +30,16 @@ ResourceTrickle.prototype.GetRates = function()
ResourceTrickle.prototype.ComputeRates = function()
{
this.rates = {};
let resCodes = Resources.GetCodes();
for (let resource in this.template.Rates)
{
if (resCodes.indexOf(resource) == -1)
continue;
this.rates[resource] = ApplyValueModificationsToEntity("ResourceTrickle/Rates/"+resource, +this.template.Rates[resource], this.entity);
}
};
// Do the actual work here
ResourceTrickle.prototype.Trickle = function(data, lateness)
{
// The player entity may also have a ResourceTrickle component
@ -63,8 +47,7 @@ ResourceTrickle.prototype.Trickle = function(data, lateness)
if (!cmpPlayer)
return;
for (let resource in this.rates)
cmpPlayer.AddResource(resource, this.rates[resource]);
cmpPlayer.AddResources(this.rates);
};
ResourceTrickle.prototype.OnValueModification = function(msg)

View file

@ -122,30 +122,18 @@ StatisticsTracker.prototype.Init = function()
this.buildingsCapturedValue = 0;
this.resourcesGathered = {
"food": 0,
"wood": 0,
"metal": 0,
"stone": 0,
"vegetarianFood": 0
};
this.resourcesUsed = {
"food": 0,
"wood": 0,
"metal": 0,
"stone": 0
};
this.resourcesSold = {
"food": 0,
"wood": 0,
"metal": 0,
"stone": 0
};
this.resourcesBought = {
"food": 0,
"wood": 0,
"metal": 0,
"stone": 0
};
this.resourcesUsed = {};
this.resourcesSold = {};
this.resourcesBought = {};
for (let res of Resources.GetCodes())
{
this.resourcesGathered[res] = 0;
this.resourcesUsed[res] = 0;
this.resourcesSold[res] = 0;
this.resourcesBought[res] = 0;
}
this.tributesSent = 0;
this.tributesReceived = 0;
@ -385,7 +373,8 @@ StatisticsTracker.prototype.IncreaseResourceGatheredCounter = function(type, amo
*/
StatisticsTracker.prototype.IncreaseResourceUsedCounter = function(type, amount)
{
this.resourcesUsed[type] += amount;
if (typeof this.resourcesUsed[type] === "number")
this.resourcesUsed[type] += amount;
};
StatisticsTracker.prototype.IncreaseTreasuresCollectedCounter = function()

View file

@ -4,9 +4,6 @@
// Additional gain for ships for each garrisoned trader, in percents
const GARRISONED_TRADER_ADDITION = 20;
// Array of resource names
const RESOURCES = ["food", "wood", "stone", "metal"];
function Trader() {}
Trader.prototype.Schema =

View file

@ -29,10 +29,7 @@ Upgrade.prototype.Schema =
"<element name='Cost' a:help='Resource cost to upgrade this unit'>" +
"<oneOrMore>" +
"<choice>" +
"<element name='food'><data type='nonNegativeInteger'/></element>" +
"<element name='wood'><data type='nonNegativeInteger'/></element>" +
"<element name='stone'><data type='nonNegativeInteger'/></element>" +
"<element name='metal'><data type='nonNegativeInteger'/></element>" +
Resources.BuildSchema("nonNegativeInteger") +
"</choice>" +
"</oneOrMore>" +
"</element>" +

View file

@ -34,6 +34,17 @@ Engine.LoadComponentScript("interfaces/Upgrade.js");
Engine.LoadComponentScript("interfaces/BuildingAI.js");
Engine.LoadComponentScript("GuiInterface.js");
Resources = {
"GetCodes": () => ["food", "metal", "stone", "wood"],
"GetNames": () => ({
"food": "Food",
"metal": "Metal",
"stone": "Stone",
"wood": "Wood"
}),
"GetResource": () => ({}),
};
var cmp = ConstructComponent(SYSTEM_ENTITY, "GuiInterface");
@ -333,7 +344,25 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetSimulationState(), {
timeElapsed: 0,
gameType: "conquest",
alliedVictory: false,
barterPrices: {buy: {food: 150}, sell: {food: 25}}
"barterPrices": {
"buy": { "food": 150 },
"sell": { "food": 25 }
},
"resources": {
"codes": ["food", "metal", "stone", "wood"],
"names": {
"food": "Food",
"metal": "Metal",
"stone": "Stone",
"wood": "Wood",
},
"aiInfluenceGroups": {
"food": 0,
"metal": 0,
"stone": 0,
"wood": 0,
}
},
});
TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
@ -453,7 +482,25 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetExtendedSimulationState(), {
timeElapsed: 0,
gameType: "conquest",
alliedVictory: false,
barterPrices: {buy: {food: 150}, sell: {food: 25}}
"barterPrices": {
"buy": { "food": 150 },
"sell": { "food": 25 }
},
"resources": {
"codes": ["food", "metal", "stone", "wood"],
"names": {
"food": "Food",
"metal": "Metal",
"stone": "Stone",
"wood": "Wood",
},
"aiInfluenceGroups": {
"food": 0,
"metal": 0,
"stone": 0,
"wood": 0,
}
},
});

View file

@ -3,6 +3,11 @@ Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("Player.js");
Resources = {
"GetCodes": () => ["food", "metal", "stone", "wood"],
"GetResource": () => ({}),
};
var cmpPlayer = ConstructComponent(10, "Player");
TS_ASSERT_EQUALS(cmpPlayer.GetPopulationCount(), 0);

View file

@ -0,0 +1,14 @@
{
"code": "food",
"name": "Food",
"order": 1,
"subtypes": {
"fish": "Fish",
"fruit": "Fruit",
"grain": "Grain",
"meat": "Meat",
"milk": "Milk"
},
"truePrice": 100,
"aiAnalysisInfluenceGroup": 0
}

View file

@ -0,0 +1,10 @@
{
"code": "metal",
"name": "Metal",
"order": 4,
"subtypes": {
"ore": "Ore"
},
"truePrice": 100,
"aiAnalysisInfluenceGroup": 2
}

View file

@ -0,0 +1,11 @@
{
"code": "stone",
"name": "Stone",
"order": 3,
"subtypes": {
"rock": "Rock",
"ruins": "Ruins"
},
"truePrice": 100,
"aiAnalysisInfluenceGroup": 2
}

View file

@ -0,0 +1,11 @@
{
"code": "wood",
"name": "Wood",
"order": 2,
"subtypes": {
"tree": "Tree",
"ruins": "Ruins"
},
"truePrice": 100,
"aiAnalysisInfluenceGroup": 1
}

View file

@ -148,7 +148,7 @@ function Cheat(input)
cmpTechnologyManager.ResearchTechnology(techname);
return;
case "metaCheat":
for (let resource of ["food", "wood", "metal", "stone"])
for (let resource of Resources.GetCodes())
Cheat({ "player": input.player, "action": "addresource", "text": resource, "parameter": input.parameter });
Cheat({ "player": input.player, "action": "maxpopulation" });
Cheat({ "player": input.player, "action": "changemaxpopulation" });

View file

@ -0,0 +1,93 @@
/**
* Builds a RelaxRNG schema based on currently valid elements.
*
* To prevent validation errors, disabled resources are included in the schema.
*
* @param datatype - The datatype of the element
* @param additional - Array of additional data elements. Time, xp, treasure, etc.
* @param subtypes - If true, resource subtypes will be included as well.
* @return RelaxNG schema string
*/
Resources.prototype.BuildSchema = function(datatype, additional = [], subtypes = false)
{
if (!datatype)
return "";
switch (datatype)
{
case "decimal":
case "nonNegativeDecimal":
case "positiveDecimal":
datatype = "<ref name='" + datatype + "'/>";
break;
default:
datatype = "<data type='" + datatype + "'/>";
}
let resCodes = this.resourceData.map(resource => resource.code);
let schema = "";
for (let res of resCodes.concat(additional))
schema +=
"<optional>" +
"<element name='" + res + "'>" +
datatype +
"</element>" +
"</optional>";
if (!subtypes)
return "<interleave>" + schema + "</interleave>";
for (let res of this.resourceData)
for (let subtype in res.subtypes)
schema +=
"<optional>" +
"<element name='" + res.code + "." + subtype + "'>" +
datatype +
"</element>" +
"</optional>";
if (additional.indexOf("treasure") !== -1)
for (let res of resCodes)
schema +=
"<optional>" +
"<element name='" + "treasure." + res + "'>" +
datatype +
"</element>" +
"</optional>";
return "<interleave>" + schema + "</interleave>";
}
/**
* Builds the value choices for a RelaxNG `<choice></choice>` object, based on currently valid resources.
*
* @oaram subtypes - If set to true, the choices returned will be resource subtypes, rather than main types
* @param treasure - If set to true, the pseudo resource 'treasure' (or its subtypes) will be included
* @return String of RelaxNG Schema `<choice/>` values.
*/
Resources.prototype.BuildChoicesSchema = function(subtypes = false, treasure = false)
{
let schema = "";
if (!subtypes)
{
let resCodes = this.resourceData.map(resource => resource.code);
if (treasure)
resCodes.push("treasure");
for (let res of resCodes)
schema += "<value>" + res + "</value>";
}
else
for (let res of this.resourceData)
{
for (let subtype in res.subtypes)
schema += "<value>" + res.code + "." + subtype + "</value>";
if (treasure)
schema += "<value>" + "treasure." + res.code + "</value>";
}
return "<choice>" + schema + "</choice>";
}
Resources = new Resources();