mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-08-15 14:43:32 -07:00
This patch is primarily meant as preparation in order to be able to reuse this logic for GetEntityState calls in the future. Key changes: - Clearer API by providing different functions for different purposes. - Slight performance optimisation by storing parameters in a context object and pulling the info from there instead of redeclaring a lambda each time, which it seems Spidermonkey struggled to optimise well. - Allow computing only partial template data, specified by passing a list of desired components. (to be used more in the future) - Make it so the naming and structure of template data and matches 1:1 with the data in entity states. This makes accessing values from them more consistent and would also allow e.g. adopting data between the two in the future.
742 lines
22 KiB
JavaScript
742 lines
22 KiB
JavaScript
/**
|
|
* Loads history and gameplay data of all civs.
|
|
*
|
|
* @param selectableOnly {boolean} - Only load civs that can be selected
|
|
* in the gamesetup. Scenario maps might set non-selectable civs.
|
|
*/
|
|
function loadCivFiles(selectableOnly)
|
|
{
|
|
const propertyNames = [
|
|
"Code", "Culture", "Music", "CivBonuses", "StartEntities",
|
|
"AINames", "SkirmishReplacements", "SelectableInGameSetup"];
|
|
|
|
const civData = {};
|
|
|
|
for (const filename of Engine.ListDirectoryFiles("simulation/data/civs/", "*.json", false))
|
|
{
|
|
const data = Engine.ReadJSONFile(filename);
|
|
|
|
for (const prop of propertyNames)
|
|
if (data[prop] === undefined)
|
|
throw new Error(filename + " doesn't contain " + prop);
|
|
|
|
if (selectableOnly && !data.SelectableInGameSetup)
|
|
continue;
|
|
|
|
const template = Engine.GetTemplate("special/players/" + data.Code);
|
|
data.Name = template.Identity.GenericName;
|
|
data.Emblem = "session/portraits/" + template.Identity.Icon;
|
|
data.History = template.Identity.History;
|
|
|
|
civData[data.Code] = data;
|
|
}
|
|
|
|
return civData;
|
|
}
|
|
|
|
/**
|
|
* @return {string[]} - All the classes for this identity template.
|
|
*/
|
|
function GetIdentityClasses(template)
|
|
{
|
|
let classString = "";
|
|
|
|
if (template.Classes && template.Classes._string)
|
|
classString += " " + template.Classes._string;
|
|
|
|
if (template.VisibleClasses && template.VisibleClasses._string)
|
|
classString += " " + template.VisibleClasses._string;
|
|
|
|
if (template.Rank)
|
|
classString += " " + template.Rank;
|
|
|
|
return classString.length > 1 ? classString.substring(1).split(" ") : [];
|
|
}
|
|
|
|
/**
|
|
* Gets an array with all classes for this identity template
|
|
* that should be shown in the GUI
|
|
*/
|
|
function GetVisibleIdentityClasses(template)
|
|
{
|
|
return template.VisibleClasses && template.VisibleClasses._string ? template.VisibleClasses._string.split(" ") : [];
|
|
}
|
|
|
|
/**
|
|
* Check if a given list of classes matches another list of classes.
|
|
* Useful f.e. for checking identity classes.
|
|
*
|
|
* @param classes - List of the classes to check against.
|
|
* @param match - Either a string in the form
|
|
* "Class1 Class2+Class3"
|
|
* where spaces are handled as OR and '+'-signs as AND,
|
|
* and ! is handled as NOT, thus Class1+!Class2 = Class1 AND NOT Class2.
|
|
* Or a list in the form
|
|
* [["Class1"], ["Class2", "Class3"]]
|
|
* where the outer list is combined as OR, and the inner lists are AND-ed.
|
|
* Or a hybrid format containing a list of strings, where the list is
|
|
* combined as OR, and the strings are split by space and '+' and AND-ed.
|
|
*
|
|
* @return undefined if there are no classes or no match object
|
|
* true if the the logical combination in the match object matches the classes
|
|
* false otherwise.
|
|
*/
|
|
function MatchesClassList(classes, match)
|
|
{
|
|
if (!match || !classes)
|
|
return undefined;
|
|
// Transform the string to an array
|
|
if (typeof match === "string")
|
|
match = match.split(/\s+/);
|
|
|
|
for (let sublist of match)
|
|
{
|
|
// If the elements are still strings, split them by space or by '+'
|
|
if (typeof sublist === "string")
|
|
sublist = sublist.split(/[+\s]+/);
|
|
if (sublist.every(c => (c[0] === "!" && classes.indexOf(c.substr(1)) === -1) ||
|
|
(c[0] !== "!" && classes.indexOf(c) !== -1)))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get basic information about a technology template.
|
|
* @param {Object} template - A valid template as obtained by loading the tech JSON file.
|
|
* @param {string} civ - Civilization for which the tech requirements should be calculated.
|
|
*/
|
|
function GetTechnologyBasicDataHelper(template, civ)
|
|
{
|
|
return {
|
|
"name": {
|
|
"generic": template.genericName
|
|
},
|
|
"icon": template.icon ? "technologies/" + template.icon : undefined,
|
|
"description": template.description,
|
|
"reqs": DeriveTechnologyRequirements(template, civ),
|
|
"modifications": template.modifications,
|
|
"affects": template.affects,
|
|
"replaces": template.replaces
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get information about a technology template.
|
|
* @param {Object} template - A valid template as obtained by loading the tech JSON file.
|
|
* @param {string} civ - Civilization for which the specific name and tech requirements should be returned.
|
|
* @param {Object} resources - An instance of the Resources class.
|
|
*/
|
|
function GetTechnologyDataHelper(template, civ, resources)
|
|
{
|
|
const ret = GetTechnologyBasicDataHelper(template, civ);
|
|
|
|
if (template.specificName)
|
|
ret.name.specific = template.specificName[civ] || template.specificName.generic;
|
|
|
|
ret.cost = { "time": template.researchTime ? +template.researchTime : 0 };
|
|
for (const type of resources.GetCodes())
|
|
ret.cost[type] = +(template.cost && template.cost[type] || 0);
|
|
|
|
ret.tooltip = template.tooltip;
|
|
ret.requirementsTooltip = template.requirementsTooltip || "";
|
|
if (template.placeBelow)
|
|
ret.placeBelow = template.placeBelow;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Get information about an aura template.
|
|
* @param {object} template - A valid template as obtained by loading the aura JSON file.
|
|
*/
|
|
function GetAuraDataHelper(template)
|
|
{
|
|
return {
|
|
"name": {
|
|
"generic": template.auraName,
|
|
},
|
|
"description": template.auraDescription || null,
|
|
"modifications": template.modifications,
|
|
"radius": template.radius || null,
|
|
};
|
|
}
|
|
|
|
function calculateCarriedResources(carriedResources, tradingGoods)
|
|
{
|
|
var resources = {};
|
|
|
|
if (carriedResources)
|
|
for (const resource of carriedResources)
|
|
resources[resource.type] = (resources[resource.type] || 0) + resource.amount;
|
|
|
|
if (tradingGoods && tradingGoods.amount)
|
|
resources[tradingGoods.type] =
|
|
(resources[tradingGoods.type] || 0) +
|
|
(tradingGoods.amount.traderGain || 0) +
|
|
(tradingGoods.amount.market1Gain || 0) +
|
|
(tradingGoods.amount.market2Gain || 0);
|
|
|
|
return resources;
|
|
}
|
|
|
|
/**
|
|
* Remove filter prefix (mirage, corpse, etc) from template name.
|
|
*
|
|
* ie. filter|dir/to/template -> dir/to/template
|
|
*/
|
|
function removeFiltersFromTemplateName(templateName)
|
|
{
|
|
return templateName.split("|").pop();
|
|
}
|
|
|
|
/**
|
|
* Helper providing several options to compute an already loaded template's "data", i.e. interesting values with
|
|
* dynamic modifications (from e.g. auras, techs, or civ bonuses) applied.
|
|
*/
|
|
class TemplateHelper
|
|
{
|
|
/**
|
|
* Stores some temporary data from the current query to avoid having to pass it around all the time
|
|
* and to keep the computations clearer and cleaner.
|
|
*/
|
|
_context = Object.seal({
|
|
"template": {},
|
|
"player": null, // null or a player id
|
|
"entity": null, // null or an entity id
|
|
"auraTemplates": {},
|
|
"resources": {},
|
|
"customModifiers": null,
|
|
"applyValueModifications": () => 0
|
|
});
|
|
|
|
/**
|
|
* Pull basic information from a template without applying any modifiers whatsoever.
|
|
*
|
|
* @param {Object} template - A valid template as returned by the template loader.
|
|
* @param {Object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
|
|
* @param {Object} resources - An instance of the Resources class.
|
|
* @param {string[]} [components] - An array of components to process, if undefined all components are processed.
|
|
*/
|
|
getBasicData(template, auraTemplates, resources, components)
|
|
{
|
|
return this.computeDataFromModifiers(template, auraTemplates, resources, {}, components);
|
|
}
|
|
|
|
/**
|
|
* Pull basic information from a template and apply a given set of modifiers to it.
|
|
*
|
|
* @param {Object} template - A valid template as returned by the template loader.
|
|
* @param {Object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
|
|
* @param {Object} resources - An instance of the Resources class.
|
|
* @param {Object} modifiers - Modifications to apply to the template, e.g. from auto-researched techs or unit upgrades
|
|
* @param {string[]} [components] - An array of components to process, if undefined all components are processed.
|
|
*/
|
|
computeDataFromModifiers(template, auraTemplates, resources, modifiers, components)
|
|
{
|
|
this._context.template = template;
|
|
this._context.player = null;
|
|
this._context.entity = null;
|
|
this._context.auraTemplates = auraTemplates;
|
|
this._context.resources = resources;
|
|
this._context.customModifiers = modifiers;
|
|
this._context.applyValueModifications = this._applyCustomModifications.bind(this);
|
|
|
|
return this._computeModifiedTemplateData(template, components);
|
|
}
|
|
|
|
/**
|
|
* Pull basic information from a template and to it apply all modifiers registered (in the simulation) to a given player.
|
|
* Must only be called in simulation context.
|
|
*
|
|
* @param {Object} template - A valid template as returned by the template loader.
|
|
* @param {Object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
|
|
* @param {Object} resources - An instance of the Resources class.
|
|
* @param {number} player - ID of the target player.
|
|
* @param {string[]} [components] - An array of components to process, if undefined all components are processed.
|
|
*/
|
|
computeDataFromPlayer(template, auraTemplates, resources, player, components)
|
|
{
|
|
this._context.template = template;
|
|
this._context.player = player;
|
|
this._context.entity = null;
|
|
this._context.auraTemplates = auraTemplates;
|
|
this._context.resources = resources;
|
|
this._context.customModifiers = null;
|
|
this._context.applyValueModifications = this._applyPlayerModifications.bind(this);
|
|
|
|
return this._computeModifiedTemplateData(template, components);
|
|
}
|
|
|
|
/**
|
|
* Apply the manually-passed ("custom") modifiers to a given value.
|
|
*/
|
|
_applyCustomModifications(currentValue, modKey)
|
|
{
|
|
if (this._context.modifiers?.[modKey])
|
|
return GetTechModifiedProperty(
|
|
this._context.modifiers[modKey], GetIdentityClasses(this._context.template.Identity), currentValue
|
|
);
|
|
return currentValue;
|
|
}
|
|
|
|
/**
|
|
* Apply the modifiers of the current player to a given value.
|
|
*/
|
|
_applyPlayerModifications(currentValue, modKey)
|
|
{
|
|
return ApplyValueModificationsToTemplate(modKey, currentValue, this._context.player, this._context.template);
|
|
}
|
|
|
|
/**
|
|
* Read a given value from the current template, and apply the current modifications to it.
|
|
* Only call this for values that can theoretically be modified (i.e. the simulation supports it), like attack damge or max HP.
|
|
* Static, unmodifiable values (like turret points or footprint size) are better read from the template directly.
|
|
*/
|
|
_getModifiedValue(valuePath, modKey, defaultValue = 0)
|
|
{
|
|
let currentValue = this._context.template;
|
|
for (const property of valuePath.split("/"))
|
|
currentValue = currentValue[property] || defaultValue;
|
|
|
|
// Using .toFixed() to get around spidermonkey's treatment of numbers (3 * 1.1 = 3.3000000000000003 for instance).
|
|
return +this._context.applyValueModifications(+currentValue, modKey || valuePath).toFixed(8);
|
|
}
|
|
|
|
/**
|
|
* Pull the desired information from a template while accounting for modifications of the current context.
|
|
*/
|
|
_computeModifiedTemplateData(template, components)
|
|
{
|
|
const ret = {};
|
|
for (const component of (components || Object.keys(template)))
|
|
{
|
|
const handler = this._componentHandlers[component];
|
|
if (!handler)
|
|
// There doesn't need to be a handler for each component, so just skip it silently.
|
|
continue;
|
|
|
|
handler(template[component], ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Simple helper for the component handlers.
|
|
*/
|
|
_getAttackEffects(template, path)
|
|
{
|
|
const ret = {};
|
|
if (template.Capture)
|
|
ret.Capture = this._getModifiedValue(path + "/Capture");
|
|
|
|
if (template.Damage)
|
|
{
|
|
ret.Damage = {};
|
|
for (const damageType in template.Damage)
|
|
ret.Damage[damageType] = this._getModifiedValue(path + "/Damage/" + damageType);
|
|
}
|
|
|
|
if (template.ApplyStatus)
|
|
ret.ApplyStatus = template.ApplyStatus;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* The methods performing the core logic, pulling and processing the values of the template's individual
|
|
* components.
|
|
*/
|
|
_componentHandlers = {
|
|
"Attack": (attack, ret) =>
|
|
{
|
|
ret.attack = {};
|
|
|
|
for (const type in attack)
|
|
{
|
|
const attackType = attack[type];
|
|
const data = this._getAttackEffects(attackType, "Attack/" + type);
|
|
|
|
const getAttackStat = stat =>
|
|
this._getModifiedValue("Attack/" + type + "/" + stat);
|
|
|
|
data.attackName = {
|
|
"name": attackType.AttackName._string || attackType.AttackName
|
|
};
|
|
const context = attackType.AttackName["@context"];
|
|
if (context)
|
|
data.attackName.context = context;
|
|
|
|
data.minRange = getAttackStat("MinRange");
|
|
data.maxRange = getAttackStat("MaxRange");
|
|
data.yOrigin = getAttackStat("Origin/Y");
|
|
|
|
data.elevationAdaptedRange =
|
|
Math.sqrt(data.maxRange *
|
|
(2 * data.yOrigin + data.maxRange));
|
|
|
|
data.repeatTime = getAttackStat("RepeatTime");
|
|
|
|
if (attackType.Projectile)
|
|
data.projectileCount = +attackType.Projectile.Count || 1;
|
|
|
|
if (attackType.Projectile)
|
|
data.friendlyFire = attackType.Projectile.FriendlyFire === "true";
|
|
|
|
if (attackType.Splash)
|
|
{
|
|
data.splash = this._getAttackEffects(
|
|
attackType.Splash,
|
|
"Attack/" + type + "/Splash"
|
|
);
|
|
data.splash.friendlyFire = attackType.Splash.FriendlyFire !== "false";
|
|
data.splash.shape = attackType.Splash.Shape;
|
|
}
|
|
|
|
ret.attack[type] = data;
|
|
}
|
|
},
|
|
"Auras": (auras, ret) =>
|
|
{
|
|
ret.auras = {};
|
|
|
|
if (this._context.auraTemplates)
|
|
for (const auraID of auras._string.split(/\s+/))
|
|
ret.auras[auraID] =
|
|
GetAuraDataHelper(this._context.auraTemplates[auraID]);
|
|
},
|
|
"BuildRestrictions": (buildRestrictions, ret) =>
|
|
{
|
|
ret.buildRestrictions = {
|
|
"placementType": buildRestrictions.PlacementType,
|
|
"territory": buildRestrictions.Territory,
|
|
"category": buildRestrictions.Category
|
|
};
|
|
|
|
if (buildRestrictions.Distance)
|
|
{
|
|
ret.buildRestrictions.distance = {
|
|
"fromClass": buildRestrictions.Distance.FromClass
|
|
};
|
|
|
|
if (buildRestrictions.Distance.MinDistance)
|
|
ret.buildRestrictions.distance.min =
|
|
this._getModifiedValue("BuildRestrictions/Distance/MinDistance");
|
|
|
|
if (buildRestrictions.Distance.MaxDistance)
|
|
ret.buildRestrictions.distance.max =
|
|
this._getModifiedValue("BuildRestrictions/Distance/MaxDistance");
|
|
}
|
|
},
|
|
"BuildingAI": (buildingAI, ret) =>
|
|
{
|
|
ret.buildingAI = {
|
|
"defaultArrowCount": Math.round(this._getModifiedValue("BuildingAI/DefaultArrowCount")),
|
|
"garrisonArrowMultiplier": this._getModifiedValue("BuildingAI/GarrisonArrowMultiplier"),
|
|
"maxArrowCount": Math.round(this._getModifiedValue("BuildingAI/MaxArrowCount"))
|
|
};
|
|
},
|
|
"Cost": (cost, ret) =>
|
|
{
|
|
ret.cost = {};
|
|
|
|
for (const resCode in cost.Resources)
|
|
ret.cost[resCode] =
|
|
this._getModifiedValue("Cost/Resources/" + resCode);
|
|
|
|
if (cost.Population)
|
|
ret.cost.population = this._getModifiedValue("Cost/Population");
|
|
|
|
if (cost.BuildTime)
|
|
ret.cost.time = this._getModifiedValue("Cost/BuildTime");
|
|
},
|
|
"DeathDamage": (deathDamage, ret) =>
|
|
{
|
|
ret.deathDamage = this._getAttackEffects(deathDamage, "DeathDamage");
|
|
ret.deathDamage.friendlyFire = deathDamage.FriendlyFire != "false";
|
|
},
|
|
"Footprint": (footprint, ret) =>
|
|
{
|
|
ret.footprint = { "height": footprint.Height };
|
|
|
|
if (footprint.Square)
|
|
ret.footprint.square = {
|
|
"width": +footprint.Square["@width"],
|
|
"depth": +footprint.Square["@depth"]
|
|
};
|
|
else if (footprint.Circle)
|
|
ret.footprint.circle = {
|
|
"radius": +footprint.Circle["@radius"]
|
|
};
|
|
else
|
|
warn("TemplateHelper: Unrecognized Footprint type");
|
|
},
|
|
"Garrisonable": (garrisonable, ret) =>
|
|
{
|
|
ret.garrisonable = {
|
|
"size": this._getModifiedValue("Garrisonable/Size")
|
|
};
|
|
},
|
|
"GarrisonHolder": (garrisonHolder, ret) =>
|
|
{
|
|
ret.garrisonHolder = {
|
|
"buffHeal": this._getModifiedValue("GarrisonHolder/BuffHeal"),
|
|
"capacity": this._getModifiedValue("GarrisonHolder/Max")
|
|
};
|
|
},
|
|
"Heal": (heal, ret) =>
|
|
{
|
|
ret.heal = {
|
|
"health": this._getModifiedValue("Heal/Health"),
|
|
"range": this._getModifiedValue("Heal/Range"),
|
|
"interval": this._getModifiedValue("Heal/Interval")
|
|
};
|
|
},
|
|
"Health": (health, ret) =>
|
|
{
|
|
ret.maxHitpoints = Math.round(this._getModifiedValue("Health/Max"));
|
|
},
|
|
"Identity": (identity, ret) =>
|
|
{
|
|
ret.selectionGroupName = identity.SelectionGroupName;
|
|
ret.name = {
|
|
"specific": (identity.SpecificName || identity.GenericName),
|
|
"generic": identity.GenericName
|
|
};
|
|
ret.icon = identity.Icon;
|
|
ret.tooltip = identity.Tooltip;
|
|
ret.visibleIdentityClasses = GetVisibleIdentityClasses(identity);
|
|
ret.nativeCiv = identity.Civ;
|
|
ret.requirements = identity.Requirements;
|
|
ret.rank = identity.Rank;
|
|
},
|
|
"Loot": (loot, ret) =>
|
|
{
|
|
ret.loot = {};
|
|
|
|
for (const type in loot)
|
|
ret.loot[type] =
|
|
this._getModifiedValue("Loot/" + type);
|
|
},
|
|
"Obstruction": (obstruction, ret) =>
|
|
{
|
|
ret.obstruction = {
|
|
"active": ("" + obstruction.Active === "true"),
|
|
"blockMovement": ("" + obstruction.BlockMovement === "true"),
|
|
"blockPathfinding": ("" + obstruction.BlockPathfinding === "true"),
|
|
"blockFoundation": ("" + obstruction.BlockFoundation === "true"),
|
|
"blockConstruction": ("" + obstruction.BlockConstruction === "true"),
|
|
"disableBlockMovement": ("" + obstruction.DisableBlockMovement === "true"),
|
|
"disableBlockPathfinding": ("" + obstruction.DisableBlockPathfinding === "true"),
|
|
"shape": {}
|
|
};
|
|
|
|
if (obstruction.Static)
|
|
{
|
|
ret.obstruction.shape.type = "static";
|
|
ret.obstruction.shape.width = +obstruction.Static["@width"];
|
|
ret.obstruction.shape.depth = +obstruction.Static["@depth"];
|
|
}
|
|
else if (obstruction.Unit)
|
|
{
|
|
ret.obstruction.shape.type = "unit";
|
|
ret.obstruction.shape.radius = +obstruction.Unit["@radius"];
|
|
}
|
|
else
|
|
{
|
|
ret.obstruction.shape.type = "cluster";
|
|
}
|
|
},
|
|
"Pack": (pack, ret) =>
|
|
{
|
|
ret.pack = {
|
|
"state": pack.State,
|
|
"time": this._getModifiedValue("Pack/Time")
|
|
};
|
|
},
|
|
"Population": (population, ret) =>
|
|
{
|
|
if (!population.Bonus)
|
|
return;
|
|
|
|
ret.population = {
|
|
"bonus": this._getModifiedValue("Population/Bonus")
|
|
};
|
|
},
|
|
"Researcher": (researcher, ret) =>
|
|
{
|
|
ret.researcher = {
|
|
"techCostMultiplier": {}
|
|
};
|
|
|
|
for (const res of this._context.resources.GetCodes().concat(["time"]))
|
|
ret.researcher.techCostMultiplier[res] =
|
|
this._getModifiedValue("Researcher/TechCostMultiplier/" + res, null, 1);
|
|
},
|
|
"Resistance": (resistance, ret) =>
|
|
{
|
|
const form = this._context.template.Foundation ? "Foundation" : "Entity";
|
|
const resistanceForm = resistance?.[form];
|
|
if (!resistanceForm)
|
|
return;
|
|
|
|
ret.resistance = {};
|
|
|
|
if (resistanceForm.Damage)
|
|
{
|
|
ret.resistance.Damage = {};
|
|
for (const damageType in resistanceForm.Damage)
|
|
ret.resistance.Damage[damageType] =
|
|
this._getModifiedValue("Resistance/Entity/Damage/" + damageType);
|
|
}
|
|
|
|
if (resistanceForm.Capture)
|
|
ret.resistance.Capture = this._getModifiedValue("Resistance/Entity/Capture");
|
|
|
|
if (resistanceForm.ApplyStatus)
|
|
{
|
|
ret.resistance.ApplyStatus = {};
|
|
for (const statusEffect in resistanceForm.ApplyStatus)
|
|
ret.resistance.ApplyStatus[statusEffect] = {
|
|
"blockChance": this._getModifiedValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/BlockChance"),
|
|
"duration": this._getModifiedValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/Duration")
|
|
};
|
|
}
|
|
},
|
|
"ResourceDropsite": (resourceDropsite, ret) =>
|
|
{
|
|
ret.resourceDropsite = {
|
|
"types": resourceDropsite.Types.split(" ")
|
|
};
|
|
},
|
|
"ResourceGatherer": (resourceGatherer, ret) =>
|
|
{
|
|
ret.resourceGatherRates = {};
|
|
|
|
const baseSpeed = this._getModifiedValue("ResourceGatherer/BaseSpeed");
|
|
|
|
for (const type in resourceGatherer.Rates)
|
|
ret.resourceGatherRates[type] =
|
|
this._getModifiedValue("ResourceGatherer/Rates/" + type) * baseSpeed;
|
|
},
|
|
"ResourceTrickle": (resourceTrickle, ret) =>
|
|
{
|
|
ret.resourceTrickle = {
|
|
"interval": +resourceTrickle.Interval,
|
|
"rates": {}
|
|
};
|
|
|
|
for (const type in resourceTrickle.Rates)
|
|
ret.resourceTrickle.rates[type] =
|
|
this._getModifiedValue("ResourceTrickle/Rates/" + type);
|
|
},
|
|
"Trader": (trader, ret) =>
|
|
{
|
|
ret.trader = {
|
|
"GainMultiplier": this._getModifiedValue("Trader/GainMultiplier")
|
|
};
|
|
},
|
|
"Treasure": (treasure, ret) =>
|
|
{
|
|
ret.treasure = {
|
|
"collectTime": this._getModifiedValue("Treasure/CollectTime"),
|
|
"resources": {}
|
|
};
|
|
|
|
for (const resource in treasure.Resources)
|
|
ret.treasure.resources[resource] =
|
|
this._getModifiedValue("Treasure/Resources/" + resource);
|
|
},
|
|
"TurretHolder": (turretHolder, ret) =>
|
|
{
|
|
ret.turretHolder = {
|
|
"turretPoints": turretHolder.TurretPoints
|
|
};
|
|
},
|
|
"UnitMotion": (unitMotion, ret) =>
|
|
{
|
|
const walkSpeed = this._getModifiedValue("UnitMotion/WalkSpeed");
|
|
|
|
ret.unitMotion = {
|
|
"walk": walkSpeed,
|
|
"run": walkSpeed,
|
|
"acceleration": this._getModifiedValue("UnitMotion/Acceleration")
|
|
};
|
|
|
|
if (unitMotion.RunMultiplier)
|
|
ret.unitMotion.run *= this._getModifiedValue("UnitMotion/RunMultiplier");
|
|
},
|
|
"Upkeep": (upkeep, ret) =>
|
|
{
|
|
ret.upkeep = {
|
|
"interval": +upkeep.Interval,
|
|
"rates": {}
|
|
};
|
|
|
|
for (const type in upkeep.Rates)
|
|
ret.upkeep.rates[type] =
|
|
this._getModifiedValue("Upkeep/Rates/" + type);
|
|
},
|
|
"Upgrade": (upgrade, ret) =>
|
|
{
|
|
ret.upgrades = [];
|
|
|
|
for (const upgradeName in upgrade)
|
|
{
|
|
const upgr = upgrade[upgradeName];
|
|
|
|
const cost = {};
|
|
if (upgr.Cost)
|
|
for (const res in upgr.Cost)
|
|
cost[res] =
|
|
this._getModifiedValue("Upgrade/" + upgradeName + "/Cost/" + res, "Upgrade/Cost/" + res);
|
|
|
|
if (upgr.Time)
|
|
cost.time =
|
|
this._getModifiedValue("Upgrade/" + upgradeName + "/Time", "Upgrade/Time");
|
|
|
|
ret.upgrades.push({
|
|
"entity": upgr.Entity,
|
|
"tooltip": upgr.Tooltip,
|
|
cost,
|
|
"icon": upgr.Icon,
|
|
"requirements": upgr.Requirements
|
|
});
|
|
}
|
|
},
|
|
"WallPiece": (wallPiece, ret) =>
|
|
{
|
|
ret.wallPiece = {
|
|
"length": +wallPiece.Length,
|
|
"angle": +(wallPiece.Orientation || 1) * Math.PI,
|
|
"indent": +(wallPiece.Indent || 0),
|
|
"bend": +(wallPiece.Bend || 0) * Math.PI
|
|
};
|
|
},
|
|
"WallSet": (wallSet, ret) =>
|
|
{
|
|
ret.wallSet = {
|
|
"templates": {
|
|
"tower": wallSet.Templates.Tower,
|
|
"gate": wallSet.Templates.Gate,
|
|
"fort": wallSet.Templates.Fort || "structures/" + this._context.template.Identity.Civ + "/fortress",
|
|
"long": wallSet.Templates.WallLong,
|
|
"medium": wallSet.Templates.WallMedium,
|
|
"short": wallSet.Templates.WallShort
|
|
},
|
|
"maxTowerOverlap": +wallSet.MaxTowerOverlap,
|
|
"minTowerOverlap": +wallSet.MinTowerOverlap
|
|
};
|
|
|
|
if (wallSet.Templates.WallEnd)
|
|
ret.wallSet.templates.end = wallSet.Templates.WallEnd;
|
|
|
|
if (wallSet.Templates.WallCurves)
|
|
ret.wallSet.templates.curves = wallSet.Templates.WallCurves.split(/\s+/);
|
|
}
|
|
};
|
|
}
|
|
|
|
var g_TemplateHelper = new TemplateHelper();
|