2017-11-26 05:30:57 -08:00
|
|
|
/**
|
|
|
|
|
* 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)
|
|
|
|
|
{
|
2025-05-06 05:16:13 -07:00
|
|
|
const propertyNames = [
|
2022-02-04 22:24:45 -08:00
|
|
|
"Code", "Culture", "Music", "CivBonuses", "StartEntities",
|
2022-02-04 22:39:53 -08:00
|
|
|
"AINames", "SkirmishReplacements", "SelectableInGameSetup"];
|
2017-11-26 05:30:57 -08:00
|
|
|
|
2025-05-06 05:16:13 -07:00
|
|
|
const civData = {};
|
2017-11-26 05:30:57 -08:00
|
|
|
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const filename of Engine.ListDirectoryFiles("simulation/data/civs/", "*.json", false))
|
2017-11-26 05:30:57 -08:00
|
|
|
{
|
2025-05-06 05:16:13 -07:00
|
|
|
const data = Engine.ReadJSONFile(filename);
|
2017-11-26 05:30:57 -08:00
|
|
|
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const prop of propertyNames)
|
2017-11-26 05:30:57 -08:00
|
|
|
if (data[prop] === undefined)
|
|
|
|
|
throw new Error(filename + " doesn't contain " + prop);
|
|
|
|
|
|
2022-02-04 22:24:45 -08:00
|
|
|
if (selectableOnly && !data.SelectableInGameSetup)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
const template = Engine.GetTemplate("special/players/" + data.Code);
|
|
|
|
|
data.Name = template.Identity.GenericName;
|
2022-02-07 21:55:01 -08:00
|
|
|
data.Emblem = "session/portraits/" + template.Identity.Icon;
|
2022-02-04 22:24:45 -08:00
|
|
|
data.History = template.Identity.History;
|
|
|
|
|
|
|
|
|
|
civData[data.Code] = data;
|
2017-11-26 05:30:57 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return civData;
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-07 09:56:14 -08:00
|
|
|
/**
|
2020-05-12 08:24:59 -07:00
|
|
|
* @return {string[]} - All the classes for this identity template.
|
2012-11-07 09:56:14 -08:00
|
|
|
*/
|
2014-05-22 03:20:02 -07:00
|
|
|
function GetIdentityClasses(template)
|
2012-11-07 09:56:14 -08:00
|
|
|
{
|
2020-05-12 08:24:59 -07:00
|
|
|
let classString = "";
|
|
|
|
|
|
2014-05-22 03:20:02 -07:00
|
|
|
if (template.Classes && template.Classes._string)
|
2020-05-12 08:24:59 -07:00
|
|
|
classString += " " + template.Classes._string;
|
2014-05-22 03:20:02 -07:00
|
|
|
|
|
|
|
|
if (template.VisibleClasses && template.VisibleClasses._string)
|
2020-05-12 08:24:59 -07:00
|
|
|
classString += " " + template.VisibleClasses._string;
|
2014-05-22 03:20:02 -07:00
|
|
|
|
|
|
|
|
if (template.Rank)
|
2020-05-12 08:24:59 -07:00
|
|
|
classString += " " + template.Rank;
|
|
|
|
|
|
|
|
|
|
return classString.length > 1 ? classString.substring(1).split(" ") : [];
|
2012-11-07 09:56:14 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2014-05-22 03:20:02 -07:00
|
|
|
* Gets an array with all classes for this identity template
|
|
|
|
|
* that should be shown in the GUI
|
2012-11-07 09:56:14 -08:00
|
|
|
*/
|
2014-05-22 03:20:02 -07:00
|
|
|
function GetVisibleIdentityClasses(template)
|
2012-11-07 09:56:14 -08:00
|
|
|
{
|
2020-05-12 08:24:59 -07:00
|
|
|
return template.VisibleClasses && template.VisibleClasses._string ? template.VisibleClasses._string.split(" ") : [];
|
2012-11-07 09:56:14 -08:00
|
|
|
}
|
|
|
|
|
|
2014-06-05 02:39:36 -07:00
|
|
|
/**
|
2017-09-15 09:07:04 -07:00
|
|
|
* 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
|
2014-06-05 02:39:36 -07:00
|
|
|
* "Class1 Class2+Class3"
|
|
|
|
|
* where spaces are handled as OR and '+'-signs as AND,
|
2017-09-15 09:07:04 -07:00
|
|
|
* and ! is handled as NOT, thus Class1+!Class2 = Class1 AND NOT Class2.
|
2014-06-05 02:39:36 -07:00
|
|
|
* Or a list in the form
|
|
|
|
|
* [["Class1"], ["Class2", "Class3"]]
|
2017-09-15 09:07:04 -07:00
|
|
|
* where the outer list is combined as OR, and the inner lists are AND-ed.
|
2014-06-05 02:39:36 -07:00
|
|
|
* Or a hybrid format containing a list of strings, where the list is
|
2017-09-15 09:07:04 -07:00
|
|
|
* combined as OR, and the strings are split by space and '+' and AND-ed.
|
2014-06-05 02:39:36 -07:00
|
|
|
*
|
|
|
|
|
* @return undefined if there are no classes or no match object
|
|
|
|
|
* true if the the logical combination in the match object matches the classes
|
2017-09-15 09:07:04 -07:00
|
|
|
* false otherwise.
|
2014-06-05 02:39:36 -07:00
|
|
|
*/
|
|
|
|
|
function MatchesClassList(classes, match)
|
|
|
|
|
{
|
|
|
|
|
if (!match || !classes)
|
|
|
|
|
return undefined;
|
2017-09-15 09:07:04 -07:00
|
|
|
// Transform the string to an array
|
2023-06-14 00:27:06 -07:00
|
|
|
if (typeof match === "string")
|
2014-06-05 02:39:36 -07:00
|
|
|
match = match.split(/\s+/);
|
2015-01-08 11:55:10 -08:00
|
|
|
|
2017-09-15 09:07:04 -07:00
|
|
|
for (let sublist of match)
|
2014-06-05 02:39:36 -07:00
|
|
|
{
|
2017-09-15 09:07:04 -07:00
|
|
|
// If the elements are still strings, split them by space or by '+'
|
2023-06-14 00:27:06 -07:00
|
|
|
if (typeof sublist === "string")
|
2014-06-05 02:39:36 -07:00
|
|
|
sublist = sublist.split(/[+\s]+/);
|
2023-06-14 00:27:06 -07:00
|
|
|
if (sublist.every(c => (c[0] === "!" && classes.indexOf(c.substr(1)) === -1) ||
|
|
|
|
|
(c[0] !== "!" && classes.indexOf(c) !== -1)))
|
2014-06-05 02:39:36 -07:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2012-11-07 09:56:14 -08:00
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
/**
|
2017-04-11 07:36:30 -07:00
|
|
|
* Gets the value originating at the value_path as-is, with no modifiers applied.
|
2016-07-04 15:16:35 -07:00
|
|
|
*
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} template - A valid template as returned from a template loader.
|
2017-04-11 07:36:30 -07:00
|
|
|
* @param {string} value_path - Route to value within the xml template structure.
|
2022-08-10 14:49:15 -07:00
|
|
|
* @param {number} default_value - A value to use if one is not specified in the template.
|
2017-04-11 07:36:30 -07:00
|
|
|
* @return {number}
|
|
|
|
|
*/
|
2022-08-10 14:49:15 -07:00
|
|
|
function GetBaseTemplateDataValue(template, value_path, default_value)
|
2017-04-11 07:36:30 -07:00
|
|
|
{
|
|
|
|
|
let current_value = template;
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const property of value_path.split("/"))
|
2022-08-10 14:49:15 -07:00
|
|
|
current_value = current_value[property] || default_value;
|
2017-04-11 07:36:30 -07:00
|
|
|
return +current_value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets the value originating at the value_path with the modifiers dictated by the mod_key applied.
|
2016-07-04 15:16:35 -07:00
|
|
|
*
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} template - A valid template as returned from a template loader.
|
2017-04-11 07:36:30 -07:00
|
|
|
* @param {string} value_path - Route to value within the xml template structure.
|
|
|
|
|
* @param {string} mod_key - Tech modification key, if different from value_path.
|
|
|
|
|
* @param {number} player - Optional player id.
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} modifiers - Value modifiers from auto-researched techs, unit upgrades,
|
2017-04-11 07:36:30 -07:00
|
|
|
* etc. Optional as only used if no player id provided.
|
2022-08-10 14:49:15 -07:00
|
|
|
* @param {number} default_value - A value to use if one is not specified in the template.
|
2017-04-11 07:36:30 -07:00
|
|
|
* @return {number} Modifier altered value.
|
2015-01-08 11:55:10 -08:00
|
|
|
*/
|
2022-08-10 14:49:15 -07:00
|
|
|
function GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers={}, default_value)
|
2015-01-08 11:55:10 -08:00
|
|
|
{
|
2022-08-10 14:49:15 -07:00
|
|
|
let current_value = GetBaseTemplateDataValue(template, value_path, default_value);
|
2017-04-11 07:36:30 -07:00
|
|
|
mod_key = mod_key || value_path;
|
2016-06-29 19:12:26 -07:00
|
|
|
|
2017-04-11 07:36:30 -07:00
|
|
|
if (player)
|
|
|
|
|
current_value = ApplyValueModificationsToTemplate(mod_key, current_value, player, template);
|
Add a system component to handle stat modifiers, make technologies and auras use this common interface.
The ModifiersManager system component provides an interface to add and
remove modifiers, and get modified stats.
The goal is to merge all the different stat-modifying systems 0 A.D. has
implemented over the years.
This commit makes technologies and auras use ModifiersManager. Some
cheats and AI bonuses also have a similar stat-modifying effect that
have not yet been updated.
Further, this system component makes it possible for e.g. triggers to
easily add modifiers, enabling the writing of Castle Blood Automatic,
RPG or Tower Defense maps without the need for mods or hacks.
The 'Modifier' name was preferred over 'Modification' as it is shorter
and more readable, along with the logic that 'modifiers' store
'modifications' and this stores modifiers. Renaming of other functions
and classes has been left for future work for now.
Internally, this uses a JS data structure. If performance issues arise
with it in the future, this data structure or the whole component could
be moved to C++.
The performance has been tested to be about as fast as the current
implementations (and specifically much faster for global auras with no
icons). Testing showed that sending value modification messages was by
far the slowest part.
Comments by: leper, Stan, elexis
Differential Revision: https://code.wildfiregames.com/D274
This was SVN commit r22767.
2019-08-24 00:37:07 -07:00
|
|
|
else if (modifiers && modifiers[mod_key])
|
|
|
|
|
current_value = GetTechModifiedProperty(modifiers[mod_key], GetIdentityClasses(template.Identity), current_value);
|
2016-06-29 19:12:26 -07:00
|
|
|
|
2017-04-11 07:36:30 -07:00
|
|
|
// Using .toFixed() to get around spidermonkey's treatment of numbers (3 * 1.1 = 3.3000000000000003 for instance).
|
|
|
|
|
return +current_value.toFixed(8);
|
|
|
|
|
}
|
2016-06-29 19:12:26 -07:00
|
|
|
|
2017-04-11 07:36:30 -07:00
|
|
|
/**
|
|
|
|
|
* Get information about a template with or without technology modifications.
|
|
|
|
|
*
|
|
|
|
|
* NOTICE: The data returned here should have the same structure as
|
|
|
|
|
* the object returned by GetEntityState and GetExtendedEntityState!
|
|
|
|
|
*
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} template - A valid template as returned by the template loader.
|
2017-04-11 07:36:30 -07:00
|
|
|
* @param {number} player - An optional player id to get the technology modifications
|
|
|
|
|
* of properties.
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
|
2022-08-10 14:49:15 -07:00
|
|
|
* @param {Object} resources - An instance of the Resources class.
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} modifiers - Modifications from auto-researched techs, unit upgrades
|
2017-04-11 07:36:30 -07:00
|
|
|
* etc. Optional as only used if there's no player
|
|
|
|
|
* id provided.
|
|
|
|
|
*/
|
2022-08-10 14:49:15 -07:00
|
|
|
function GetTemplateDataHelper(template, player, auraTemplates, resources, modifiers = {})
|
2017-04-11 07:36:30 -07:00
|
|
|
{
|
|
|
|
|
// Return data either from template (in tech tree) or sim state (ingame).
|
|
|
|
|
// @param {string} value_path - Route to the value within the template.
|
|
|
|
|
// @param {string} mod_key - Modification key, if not the same as the value_path.
|
2022-08-10 14:49:15 -07:00
|
|
|
// @param {number} default_value - A value to use if one is not specified in the template.
|
2025-12-30 00:57:37 -08:00
|
|
|
const getEntityValue = function(value_path, mod_key, default_value = 0)
|
|
|
|
|
{
|
2022-08-10 14:49:15 -07:00
|
|
|
return GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers, default_value);
|
2016-06-29 19:12:26 -07:00
|
|
|
};
|
2015-01-08 11:55:10 -08:00
|
|
|
|
2025-05-06 05:16:13 -07:00
|
|
|
const ret = {};
|
2015-01-08 11:55:10 -08:00
|
|
|
|
2020-08-27 03:24:59 -07:00
|
|
|
if (template.Resistance)
|
2017-09-18 09:33:56 -07:00
|
|
|
{
|
2020-08-27 03:24:59 -07:00
|
|
|
// Don't show Foundation resistance.
|
|
|
|
|
ret.resistance = {};
|
|
|
|
|
if (template.Resistance.Entity)
|
|
|
|
|
{
|
|
|
|
|
if (template.Resistance.Entity.Damage)
|
|
|
|
|
{
|
|
|
|
|
ret.resistance.Damage = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const damageType in template.Resistance.Entity.Damage)
|
2020-08-27 03:24:59 -07:00
|
|
|
ret.resistance.Damage[damageType] = getEntityValue("Resistance/Entity/Damage/" + damageType);
|
|
|
|
|
}
|
|
|
|
|
if (template.Resistance.Entity.Capture)
|
|
|
|
|
ret.resistance.Capture = getEntityValue("Resistance/Entity/Capture");
|
2020-11-11 12:07:30 -08:00
|
|
|
if (template.Resistance.Entity.ApplyStatus)
|
|
|
|
|
{
|
|
|
|
|
ret.resistance.ApplyStatus = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const statusEffect in template.Resistance.Entity.ApplyStatus)
|
2020-11-11 12:07:30 -08:00
|
|
|
ret.resistance.ApplyStatus[statusEffect] = {
|
|
|
|
|
"blockChance": getEntityValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/BlockChance"),
|
|
|
|
|
"duration": getEntityValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/Duration")
|
|
|
|
|
};
|
|
|
|
|
}
|
2020-08-27 03:24:59 -07:00
|
|
|
}
|
2017-09-18 09:33:56 -07:00
|
|
|
}
|
2015-01-08 11:55:10 -08:00
|
|
|
|
2025-12-30 00:57:37 -08:00
|
|
|
const getAttackEffects = (temp, path) =>
|
|
|
|
|
{
|
2025-05-06 05:16:13 -07:00
|
|
|
const effects = {};
|
2019-08-22 11:00:33 -07:00
|
|
|
if (temp.Capture)
|
|
|
|
|
effects.Capture = getEntityValue(path + "/Capture");
|
|
|
|
|
|
|
|
|
|
if (temp.Damage)
|
|
|
|
|
{
|
|
|
|
|
effects.Damage = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const damageType in temp.Damage)
|
2019-08-22 11:00:33 -07:00
|
|
|
effects.Damage[damageType] = getEntityValue(path + "/Damage/" + damageType);
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-27 08:51:25 -08:00
|
|
|
if (temp.ApplyStatus)
|
|
|
|
|
effects.ApplyStatus = temp.ApplyStatus;
|
|
|
|
|
|
2019-08-22 11:00:33 -07:00
|
|
|
return effects;
|
|
|
|
|
};
|
|
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
if (template.Attack)
|
|
|
|
|
{
|
|
|
|
|
ret.attack = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const type in template.Attack)
|
2015-01-08 11:55:10 -08:00
|
|
|
{
|
2025-12-30 00:57:37 -08:00
|
|
|
const getAttackStat = function(stat)
|
|
|
|
|
{
|
2016-07-02 21:08:52 -07:00
|
|
|
return getEntityValue("Attack/" + type + "/" + stat);
|
|
|
|
|
};
|
|
|
|
|
|
2019-08-22 11:00:33 -07:00
|
|
|
ret.attack[type] = {
|
2020-11-18 13:34:33 -08:00
|
|
|
"attackName": {
|
|
|
|
|
"name": template.Attack[type].AttackName._string || template.Attack[type].AttackName,
|
|
|
|
|
"context": template.Attack[type].AttackName["@context"]
|
|
|
|
|
},
|
2019-08-22 11:00:33 -07:00
|
|
|
"minRange": getAttackStat("MinRange"),
|
|
|
|
|
"maxRange": getAttackStat("MaxRange"),
|
Rename "ElevationBonus" and "Delay" to "Origin" and "EffectDelay", respectively.
`ElevationBonus` is vague, as discussions proved. Therefore it is
renamed to `Origin`, which, describes better what the value stands for.
`Delay` is also quite vague, so renamed to `EffectDelay`.
Differential revision: https://code.wildfiregames.com/D2016
Comments by: @bb, @nani, @Nescio, @Silier, @Stan, @wraitii
This was SVN commit r26074.
2021-12-14 23:42:06 -08:00
|
|
|
"yOrigin": getAttackStat("Origin/Y")
|
2019-08-22 11:00:33 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
|
Rename "ElevationBonus" and "Delay" to "Origin" and "EffectDelay", respectively.
`ElevationBonus` is vague, as discussions proved. Therefore it is
renamed to `Origin`, which, describes better what the value stands for.
`Delay` is also quite vague, so renamed to `EffectDelay`.
Differential revision: https://code.wildfiregames.com/D2016
Comments by: @bb, @nani, @Nescio, @Silier, @Stan, @wraitii
This was SVN commit r26074.
2021-12-14 23:42:06 -08:00
|
|
|
(2 * ret.attack[type].yOrigin + ret.attack[type].maxRange));
|
2017-09-18 09:33:56 -07:00
|
|
|
|
2016-07-02 21:08:52 -07:00
|
|
|
ret.attack[type].repeatTime = getAttackStat("RepeatTime");
|
2020-03-07 02:39:05 -08:00
|
|
|
if (template.Attack[type].Projectile)
|
|
|
|
|
ret.attack[type].friendlyFire = template.Attack[type].Projectile.FriendlyFire == "true";
|
2016-07-02 21:08:52 -07:00
|
|
|
|
2019-08-22 11:00:33 -07:00
|
|
|
Object.assign(ret.attack[type], getAttackEffects(template.Attack[type], "Attack/" + type));
|
|
|
|
|
|
2016-07-02 21:08:52 -07:00
|
|
|
if (template.Attack[type].Splash)
|
2017-09-18 09:33:56 -07:00
|
|
|
{
|
2016-07-02 21:08:52 -07:00
|
|
|
ret.attack[type].splash = {
|
2017-02-20 07:39:36 -08:00
|
|
|
"friendlyFire": template.Attack[type].Splash.FriendlyFire != "false",
|
2019-06-16 10:08:27 -07:00
|
|
|
"shape": template.Attack[type].Splash.Shape,
|
2015-04-20 00:45:45 -07:00
|
|
|
};
|
2019-08-22 11:00:33 -07:00
|
|
|
Object.assign(ret.attack[type].splash, getAttackEffects(template.Attack[type].Splash, "Attack/" + type + "/Splash"));
|
2017-09-18 09:33:56 -07:00
|
|
|
}
|
2015-01-08 11:55:10 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-07 05:38:57 -07:00
|
|
|
if (template.DeathDamage)
|
|
|
|
|
{
|
|
|
|
|
ret.deathDamage = {
|
2019-06-16 10:08:27 -07:00
|
|
|
"friendlyFire": template.DeathDamage.FriendlyFire != "false",
|
2017-08-07 05:38:57 -07:00
|
|
|
};
|
2019-08-22 11:00:33 -07:00
|
|
|
|
|
|
|
|
Object.assign(ret.deathDamage, getAttackEffects(template.DeathDamage, "DeathDamage"));
|
2017-08-07 05:38:57 -07:00
|
|
|
}
|
|
|
|
|
|
2017-12-09 19:12:54 -08:00
|
|
|
if (template.Auras && auraTemplates)
|
2015-01-08 11:55:10 -08:00
|
|
|
{
|
|
|
|
|
ret.auras = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const auraID of template.Auras._string.split(/\s+/))
|
2020-11-28 20:44:38 -08:00
|
|
|
ret.auras[auraID] = GetAuraDataHelper(auraTemplates[auraID]);
|
2015-01-08 11:55:10 -08:00
|
|
|
}
|
|
|
|
|
|
2016-06-29 18:31:40 -07:00
|
|
|
if (template.BuildingAI)
|
2016-07-04 15:16:35 -07:00
|
|
|
ret.buildingAI = {
|
2017-05-10 06:04:35 -07:00
|
|
|
"defaultArrowCount": Math.round(getEntityValue("BuildingAI/DefaultArrowCount")),
|
2016-07-04 15:16:35 -07:00
|
|
|
"garrisonArrowMultiplier": getEntityValue("BuildingAI/GarrisonArrowMultiplier"),
|
2017-05-10 06:04:35 -07:00
|
|
|
"maxArrowCount": Math.round(getEntityValue("BuildingAI/MaxArrowCount"))
|
2016-07-04 15:16:35 -07:00
|
|
|
};
|
2016-06-29 18:31:40 -07:00
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
if (template.BuildRestrictions)
|
|
|
|
|
{
|
|
|
|
|
// required properties
|
|
|
|
|
ret.buildRestrictions = {
|
|
|
|
|
"placementType": template.BuildRestrictions.PlacementType,
|
|
|
|
|
"territory": template.BuildRestrictions.Territory,
|
|
|
|
|
"category": template.BuildRestrictions.Category,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// optional properties
|
|
|
|
|
if (template.BuildRestrictions.Distance)
|
|
|
|
|
{
|
|
|
|
|
ret.buildRestrictions.distance = {
|
2016-05-11 02:35:52 -07:00
|
|
|
"fromClass": template.BuildRestrictions.Distance.FromClass,
|
2015-01-08 11:55:10 -08:00
|
|
|
};
|
2016-06-29 19:12:26 -07:00
|
|
|
|
|
|
|
|
if (template.BuildRestrictions.Distance.MinDistance)
|
2017-12-11 07:23:34 -08:00
|
|
|
ret.buildRestrictions.distance.min = getEntityValue("BuildRestrictions/Distance/MinDistance");
|
2016-06-29 19:12:26 -07:00
|
|
|
|
|
|
|
|
if (template.BuildRestrictions.Distance.MaxDistance)
|
2017-12-11 07:23:34 -08:00
|
|
|
ret.buildRestrictions.distance.max = getEntityValue("BuildRestrictions/Distance/MaxDistance");
|
2015-01-08 11:55:10 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (template.TrainingRestrictions)
|
2020-12-29 03:00:54 -08:00
|
|
|
{
|
2015-01-08 11:55:10 -08:00
|
|
|
ret.trainingRestrictions = {
|
2020-12-29 03:00:54 -08:00
|
|
|
"category": template.TrainingRestrictions.Category
|
2015-01-08 11:55:10 -08:00
|
|
|
};
|
2020-12-29 03:00:54 -08:00
|
|
|
if (template.TrainingRestrictions.MatchLimit)
|
|
|
|
|
ret.trainingRestrictions.matchLimit = +template.TrainingRestrictions.MatchLimit;
|
|
|
|
|
}
|
2015-01-08 11:55:10 -08:00
|
|
|
|
|
|
|
|
if (template.Cost)
|
|
|
|
|
{
|
|
|
|
|
ret.cost = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const resCode in template.Cost.Resources)
|
2016-11-21 09:32:17 -08:00
|
|
|
ret.cost[resCode] = getEntityValue("Cost/Resources/" + resCode);
|
2016-06-29 19:12:26 -07:00
|
|
|
|
|
|
|
|
if (template.Cost.Population)
|
|
|
|
|
ret.cost.population = getEntityValue("Cost/Population");
|
|
|
|
|
|
|
|
|
|
if (template.Cost.BuildTime)
|
|
|
|
|
ret.cost.time = getEntityValue("Cost/BuildTime");
|
2015-01-08 11:55:10 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (template.Footprint)
|
|
|
|
|
{
|
2016-06-29 19:12:26 -07:00
|
|
|
ret.footprint = { "height": template.Footprint.Height };
|
2015-01-08 11:55:10 -08:00
|
|
|
|
|
|
|
|
if (template.Footprint.Square)
|
2016-06-29 19:12:26 -07:00
|
|
|
ret.footprint.square = {
|
|
|
|
|
"width": +template.Footprint.Square["@width"],
|
|
|
|
|
"depth": +template.Footprint.Square["@depth"]
|
|
|
|
|
};
|
2015-01-08 11:55:10 -08:00
|
|
|
else if (template.Footprint.Circle)
|
2016-06-29 19:12:26 -07:00
|
|
|
ret.footprint.circle = { "radius": +template.Footprint.Circle["@radius"] };
|
2015-01-08 11:55:10 -08:00
|
|
|
else
|
|
|
|
|
warn("GetTemplateDataHelper(): Unrecognized Footprint type");
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-28 02:02:03 -08:00
|
|
|
if (template.Garrisonable)
|
|
|
|
|
ret.garrisonable = {
|
|
|
|
|
"size": getEntityValue("Garrisonable/Size")
|
|
|
|
|
};
|
|
|
|
|
|
2016-06-29 18:31:40 -07:00
|
|
|
if (template.GarrisonHolder)
|
|
|
|
|
{
|
2016-07-19 06:19:37 -07:00
|
|
|
ret.garrisonHolder = {
|
|
|
|
|
"buffHeal": getEntityValue("GarrisonHolder/BuffHeal")
|
|
|
|
|
};
|
|
|
|
|
|
2016-06-29 18:31:40 -07:00
|
|
|
if (template.GarrisonHolder.Max)
|
2016-10-09 08:53:23 -07:00
|
|
|
ret.garrisonHolder.capacity = getEntityValue("GarrisonHolder/Max");
|
2016-06-29 18:31:40 -07:00
|
|
|
}
|
|
|
|
|
|
2016-07-01 06:32:34 -07:00
|
|
|
if (template.Heal)
|
|
|
|
|
ret.heal = {
|
2020-07-21 09:28:29 -07:00
|
|
|
"health": getEntityValue("Heal/Health"),
|
2016-07-01 06:32:34 -07:00
|
|
|
"range": getEntityValue("Heal/Range"),
|
2020-07-21 09:28:29 -07:00
|
|
|
"interval": getEntityValue("Heal/Interval")
|
2016-07-01 06:32:34 -07:00
|
|
|
};
|
|
|
|
|
|
2016-11-19 06:34:02 -08:00
|
|
|
if (template.ResourceGatherer)
|
|
|
|
|
{
|
|
|
|
|
ret.resourceGatherRates = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
const baseSpeed = getEntityValue("ResourceGatherer/BaseSpeed");
|
|
|
|
|
for (const type in template.ResourceGatherer.Rates)
|
2017-04-11 07:36:30 -07:00
|
|
|
ret.resourceGatherRates[type] = getEntityValue("ResourceGatherer/Rates/"+ type) * baseSpeed;
|
2016-11-19 06:34:02 -08:00
|
|
|
}
|
|
|
|
|
|
2020-09-16 08:28:44 -07:00
|
|
|
if (template.ResourceDropsite)
|
|
|
|
|
ret.resourceDropsite = {
|
|
|
|
|
"types": template.ResourceDropsite.Types.split(" ")
|
|
|
|
|
};
|
|
|
|
|
|
2017-04-07 23:45:22 -07:00
|
|
|
if (template.ResourceTrickle)
|
|
|
|
|
{
|
|
|
|
|
ret.resourceTrickle = {
|
|
|
|
|
"interval": +template.ResourceTrickle.Interval,
|
|
|
|
|
"rates": {}
|
|
|
|
|
};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const type in template.ResourceTrickle.Rates)
|
2017-04-07 23:45:22 -07:00
|
|
|
ret.resourceTrickle.rates[type] = getEntityValue("ResourceTrickle/Rates/" + type);
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-18 18:21:09 -07:00
|
|
|
if (template.Loot)
|
|
|
|
|
{
|
|
|
|
|
ret.loot = {};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const type in template.Loot)
|
2016-09-18 18:21:09 -07:00
|
|
|
ret.loot[type] = getEntityValue("Loot/"+ type);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
if (template.Obstruction)
|
|
|
|
|
{
|
|
|
|
|
ret.obstruction = {
|
|
|
|
|
"active": ("" + template.Obstruction.Active == "true"),
|
|
|
|
|
"blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
|
|
|
|
|
"blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
|
|
|
|
|
"blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
|
|
|
|
|
"blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
|
|
|
|
|
"disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
|
|
|
|
|
"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
|
|
|
|
|
"shape": {}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (template.Obstruction.Static)
|
|
|
|
|
{
|
|
|
|
|
ret.obstruction.shape.type = "static";
|
|
|
|
|
ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
|
|
|
|
|
ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
|
|
|
|
|
}
|
|
|
|
|
else if (template.Obstruction.Unit)
|
|
|
|
|
{
|
|
|
|
|
ret.obstruction.shape.type = "unit";
|
|
|
|
|
ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
ret.obstruction.shape.type = "cluster";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (template.Pack)
|
|
|
|
|
ret.pack = {
|
|
|
|
|
"state": template.Pack.State,
|
2016-06-29 19:12:26 -07:00
|
|
|
"time": getEntityValue("Pack/Time"),
|
2015-01-08 11:55:10 -08:00
|
|
|
};
|
|
|
|
|
|
2020-12-14 10:17:59 -08:00
|
|
|
if (template.Population && template.Population.Bonus)
|
|
|
|
|
ret.population = {
|
|
|
|
|
"bonus": getEntityValue("Population/Bonus")
|
|
|
|
|
};
|
|
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
if (template.Health)
|
2016-06-29 19:12:26 -07:00
|
|
|
ret.health = Math.round(getEntityValue("Health/Max"));
|
2015-01-08 11:55:10 -08:00
|
|
|
|
|
|
|
|
if (template.Identity)
|
|
|
|
|
{
|
|
|
|
|
ret.selectionGroupName = template.Identity.SelectionGroupName;
|
|
|
|
|
ret.name = {
|
|
|
|
|
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
|
|
|
|
|
"generic": template.Identity.GenericName
|
|
|
|
|
};
|
|
|
|
|
ret.icon = template.Identity.Icon;
|
2017-04-11 07:36:30 -07:00
|
|
|
ret.tooltip = template.Identity.Tooltip;
|
2022-11-24 03:20:11 -08:00
|
|
|
ret.requirements = template.Identity.Requirements;
|
2015-01-08 11:55:10 -08:00
|
|
|
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
|
2018-02-21 13:39:00 -08:00
|
|
|
ret.nativeCiv = template.Identity.Civ;
|
2015-01-08 11:55:10 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (template.UnitMotion)
|
|
|
|
|
{
|
2021-10-09 14:31:11 -07:00
|
|
|
const walkSpeed = getEntityValue("UnitMotion/WalkSpeed");
|
2015-01-08 11:55:10 -08:00
|
|
|
ret.speed = {
|
2021-10-09 14:31:11 -07:00
|
|
|
"walk": walkSpeed,
|
|
|
|
|
"run": walkSpeed,
|
|
|
|
|
"acceleration": getEntityValue("UnitMotion/Acceleration")
|
2015-01-08 11:55:10 -08:00
|
|
|
};
|
2019-04-19 03:04:50 -07:00
|
|
|
if (template.UnitMotion.RunMultiplier)
|
|
|
|
|
ret.speed.run *= getEntityValue("UnitMotion/RunMultiplier");
|
2015-01-08 11:55:10 -08:00
|
|
|
}
|
|
|
|
|
|
2017-05-17 11:41:23 -07:00
|
|
|
if (template.Upgrade)
|
|
|
|
|
{
|
|
|
|
|
ret.upgrades = [];
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const upgradeName in template.Upgrade)
|
2017-05-17 11:41:23 -07:00
|
|
|
{
|
2025-05-06 05:16:13 -07:00
|
|
|
const upgrade = template.Upgrade[upgradeName];
|
2017-05-17 11:41:23 -07:00
|
|
|
|
2025-05-06 05:16:13 -07:00
|
|
|
const cost = {};
|
2017-06-01 11:28:49 -07:00
|
|
|
if (upgrade.Cost)
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const res in upgrade.Cost)
|
2017-06-01 11:28:49 -07:00
|
|
|
cost[res] = getEntityValue("Upgrade/" + upgradeName + "/Cost/" + res, "Upgrade/Cost/" + res);
|
2017-05-17 11:41:23 -07:00
|
|
|
if (upgrade.Time)
|
|
|
|
|
cost.time = getEntityValue("Upgrade/" + upgradeName + "/Time", "Upgrade/Time");
|
|
|
|
|
|
|
|
|
|
ret.upgrades.push({
|
|
|
|
|
"entity": upgrade.Entity,
|
|
|
|
|
"tooltip": upgrade.Tooltip,
|
|
|
|
|
"cost": cost,
|
2022-11-24 03:20:11 -08:00
|
|
|
"icon": upgrade.Icon,
|
|
|
|
|
"requirements": upgrade.Requirements
|
2017-05-17 11:41:23 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-15 23:08:39 -08:00
|
|
|
if (template.Researcher)
|
2016-05-25 23:28:23 -07:00
|
|
|
{
|
|
|
|
|
ret.techCostMultiplier = {};
|
2022-08-10 14:49:15 -07:00
|
|
|
for (const res of resources.GetCodes().concat(["time"]))
|
|
|
|
|
ret.techCostMultiplier[res] = getEntityValue("Researcher/TechCostMultiplier/" + res, null, 1);
|
2016-05-25 23:28:23 -07:00
|
|
|
}
|
|
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
if (template.Trader)
|
2015-07-17 12:27:15 -07:00
|
|
|
ret.trader = {
|
2016-06-29 19:12:26 -07:00
|
|
|
"GainMultiplier": getEntityValue("Trader/GainMultiplier")
|
2015-07-17 12:27:15 -07:00
|
|
|
};
|
2016-05-21 09:20:27 -07:00
|
|
|
|
2021-03-02 23:47:38 -08:00
|
|
|
if (template.Treasure)
|
|
|
|
|
{
|
|
|
|
|
ret.treasure = {
|
|
|
|
|
"collectTime": getEntityValue("Treasure/CollectTime"),
|
|
|
|
|
"resources": {}
|
|
|
|
|
};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const resource in template.Treasure.Resources)
|
2021-03-02 23:47:38 -08:00
|
|
|
ret.treasure.resources[resource] = getEntityValue("Treasure/Resources/" + resource);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-26 03:18:30 -07:00
|
|
|
if (template.TurretHolder)
|
|
|
|
|
ret.turretHolder = {
|
|
|
|
|
"turretPoints": template.TurretHolder.TurretPoints
|
|
|
|
|
};
|
|
|
|
|
|
2021-04-03 23:52:20 -07:00
|
|
|
if (template.Upkeep)
|
|
|
|
|
{
|
|
|
|
|
ret.upkeep = {
|
|
|
|
|
"interval": +template.Upkeep.Interval,
|
|
|
|
|
"rates": {}
|
|
|
|
|
};
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const type in template.Upkeep.Rates)
|
2021-04-03 23:52:20 -07:00
|
|
|
ret.upkeep.rates[type] = getEntityValue("Upkeep/Rates/" + type);
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
if (template.WallSet)
|
2017-12-09 19:12:54 -08:00
|
|
|
{
|
2015-01-08 11:55:10 -08:00
|
|
|
ret.wallSet = {
|
|
|
|
|
"templates": {
|
|
|
|
|
"tower": template.WallSet.Templates.Tower,
|
|
|
|
|
"gate": template.WallSet.Templates.Gate,
|
2020-11-19 02:20:25 -08:00
|
|
|
"fort": template.WallSet.Templates.Fort || "structures/" + template.Identity.Civ + "/fortress",
|
2015-01-08 11:55:10 -08:00
|
|
|
"long": template.WallSet.Templates.WallLong,
|
|
|
|
|
"medium": template.WallSet.Templates.WallMedium,
|
2017-12-09 19:12:54 -08:00
|
|
|
"short": template.WallSet.Templates.WallShort
|
2015-01-08 11:55:10 -08:00
|
|
|
},
|
|
|
|
|
"maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
|
2017-12-09 19:12:54 -08:00
|
|
|
"minTowerOverlap": +template.WallSet.MinTowerOverlap
|
2015-01-08 11:55:10 -08:00
|
|
|
};
|
2017-12-09 19:12:54 -08:00
|
|
|
if (template.WallSet.Templates.WallEnd)
|
|
|
|
|
ret.wallSet.templates.end = template.WallSet.Templates.WallEnd;
|
|
|
|
|
if (template.WallSet.Templates.WallCurves)
|
2020-05-20 21:34:13 -07:00
|
|
|
ret.wallSet.templates.curves = template.WallSet.Templates.WallCurves.split(/\s+/);
|
2017-12-09 19:12:54 -08:00
|
|
|
}
|
2015-01-08 11:55:10 -08:00
|
|
|
|
|
|
|
|
if (template.WallPiece)
|
2017-12-09 19:12:54 -08:00
|
|
|
ret.wallPiece = {
|
|
|
|
|
"length": +template.WallPiece.Length,
|
|
|
|
|
"angle": +(template.WallPiece.Orientation || 1) * Math.PI,
|
|
|
|
|
"indent": +(template.WallPiece.Indent || 0),
|
|
|
|
|
"bend": +(template.WallPiece.Bend || 0) * Math.PI
|
|
|
|
|
};
|
2015-01-08 11:55:10 -08:00
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-11 07:36:30 -07:00
|
|
|
/**
|
|
|
|
|
* Get basic information about a technology template.
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} template - A valid template as obtained by loading the tech JSON file.
|
2017-04-11 07:36:30 -07:00
|
|
|
* @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,
|
2018-01-01 22:38:09 -08:00
|
|
|
"affects": template.affects,
|
|
|
|
|
"replaces": template.replaces
|
2017-04-11 07:36:30 -07:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-08 11:55:10 -08:00
|
|
|
/**
|
|
|
|
|
* Get information about a technology template.
|
2020-08-24 04:01:25 -07:00
|
|
|
* @param {Object} template - A valid template as obtained by loading the tech JSON file.
|
2017-04-11 07:36:30 -07:00
|
|
|
* @param {string} civ - Civilization for which the specific name and tech requirements should be returned.
|
2022-08-10 14:49:15 -07:00
|
|
|
* @param {Object} resources - An instance of the Resources class.
|
2015-01-08 11:55:10 -08:00
|
|
|
*/
|
2016-11-19 06:29:45 -08:00
|
|
|
function GetTechnologyDataHelper(template, civ, resources)
|
2015-01-08 11:55:10 -08:00
|
|
|
{
|
2025-05-06 05:16:13 -07:00
|
|
|
const ret = GetTechnologyBasicDataHelper(template, civ);
|
2015-01-08 11:55:10 -08:00
|
|
|
|
|
|
|
|
if (template.specificName)
|
2017-04-11 07:36:30 -07:00
|
|
|
ret.name.specific = template.specificName[civ] || template.specificName.generic;
|
2016-06-29 19:12:26 -07:00
|
|
|
|
2016-12-07 10:06:12 -08:00
|
|
|
ret.cost = { "time": template.researchTime ? +template.researchTime : 0 };
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const type of resources.GetCodes())
|
2017-02-10 06:04:55 -08:00
|
|
|
ret.cost[type] = +(template.cost && template.cost[type] || 0);
|
2015-01-08 11:55:10 -08:00
|
|
|
|
2016-06-29 19:12:26 -07:00
|
|
|
ret.tooltip = template.tooltip;
|
|
|
|
|
ret.requirementsTooltip = template.requirementsTooltip || "";
|
2015-01-08 11:55:10 -08:00
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
2016-09-18 18:21:09 -07:00
|
|
|
|
2020-11-28 20:44:38 -08:00
|
|
|
/**
|
|
|
|
|
* 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,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-18 18:21:09 -07:00
|
|
|
function calculateCarriedResources(carriedResources, tradingGoods)
|
|
|
|
|
{
|
|
|
|
|
var resources = {};
|
|
|
|
|
|
|
|
|
|
if (carriedResources)
|
2025-05-06 05:16:13 -07:00
|
|
|
for (const resource of carriedResources)
|
2016-09-18 18:21:09 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-01 17:37:39 -08:00
|
|
|
/**
|
|
|
|
|
* Remove filter prefix (mirage, corpse, etc) from template name.
|
|
|
|
|
*
|
|
|
|
|
* ie. filter|dir/to/template -> dir/to/template
|
|
|
|
|
*/
|
|
|
|
|
function removeFiltersFromTemplateName(templateName)
|
|
|
|
|
{
|
|
|
|
|
return templateName.split("|").pop();
|
|
|
|
|
}
|