Line data Source code
1 : /**
2 : * Loads history and gameplay data of all civs.
3 : *
4 : * @param selectableOnly {boolean} - Only load civs that can be selected
5 : * in the gamesetup. Scenario maps might set non-selectable civs.
6 : */
7 : function loadCivFiles(selectableOnly)
8 : {
9 6 : let propertyNames = [
10 : "Code", "Culture", "Music", "CivBonuses", "StartEntities",
11 : "AINames", "SkirmishReplacements", "SelectableInGameSetup"];
12 :
13 6 : let civData = {};
14 :
15 6 : for (let filename of Engine.ListDirectoryFiles("simulation/data/civs/", "*.json", false))
16 : {
17 84 : let data = Engine.ReadJSONFile(filename);
18 :
19 84 : for (let prop of propertyNames)
20 672 : if (data[prop] === undefined)
21 0 : throw new Error(filename + " doesn't contain " + prop);
22 :
23 84 : if (selectableOnly && !data.SelectableInGameSetup)
24 0 : continue;
25 :
26 84 : const template = Engine.GetTemplate("special/players/" + data.Code);
27 84 : data.Name = template.Identity.GenericName;
28 84 : data.Emblem = "session/portraits/" + template.Identity.Icon;
29 84 : data.History = template.Identity.History;
30 :
31 84 : civData[data.Code] = data;
32 : }
33 :
34 6 : return civData;
35 : }
36 :
37 : /**
38 : * @return {string[]} - All the classes for this identity template.
39 : */
40 : function GetIdentityClasses(template)
41 : {
42 20 : let classString = "";
43 :
44 20 : if (template.Classes && template.Classes._string)
45 13 : classString += " " + template.Classes._string;
46 :
47 20 : if (template.VisibleClasses && template.VisibleClasses._string)
48 4 : classString += " " + template.VisibleClasses._string;
49 :
50 20 : if (template.Rank)
51 2 : classString += " " + template.Rank;
52 :
53 20 : return classString.length > 1 ? classString.substring(1).split(" ") : [];
54 : }
55 :
56 : /**
57 : * Gets an array with all classes for this identity template
58 : * that should be shown in the GUI
59 : */
60 : function GetVisibleIdentityClasses(template)
61 : {
62 14 : return template.VisibleClasses && template.VisibleClasses._string ? template.VisibleClasses._string.split(" ") : [];
63 : }
64 :
65 : /**
66 : * Check if a given list of classes matches another list of classes.
67 : * Useful f.e. for checking identity classes.
68 : *
69 : * @param classes - List of the classes to check against.
70 : * @param match - Either a string in the form
71 : * "Class1 Class2+Class3"
72 : * where spaces are handled as OR and '+'-signs as AND,
73 : * and ! is handled as NOT, thus Class1+!Class2 = Class1 AND NOT Class2.
74 : * Or a list in the form
75 : * [["Class1"], ["Class2", "Class3"]]
76 : * where the outer list is combined as OR, and the inner lists are AND-ed.
77 : * Or a hybrid format containing a list of strings, where the list is
78 : * combined as OR, and the strings are split by space and '+' and AND-ed.
79 : *
80 : * @return undefined if there are no classes or no match object
81 : * true if the the logical combination in the match object matches the classes
82 : * false otherwise.
83 : */
84 : function MatchesClassList(classes, match)
85 : {
86 262 : if (!match || !classes)
87 3 : return undefined;
88 : // Transform the string to an array
89 259 : if (typeof match == "string")
90 121 : match = match.split(/\s+/);
91 :
92 259 : for (let sublist of match)
93 : {
94 : // If the elements are still strings, split them by space or by '+'
95 305 : if (typeof sublist == "string")
96 292 : sublist = sublist.split(/[+\s]+/);
97 342 : if (sublist.every(c => (c[0] == "!" && classes.indexOf(c.substr(1)) == -1) ||
98 : (c[0] != "!" && classes.indexOf(c) != -1)))
99 146 : return true;
100 : }
101 :
102 113 : return false;
103 : }
104 :
105 : /**
106 : * Gets the value originating at the value_path as-is, with no modifiers applied.
107 : *
108 : * @param {Object} template - A valid template as returned from a template loader.
109 : * @param {string} value_path - Route to value within the xml template structure.
110 : * @param {number} default_value - A value to use if one is not specified in the template.
111 : * @return {number}
112 : */
113 : function GetBaseTemplateDataValue(template, value_path, default_value)
114 : {
115 12 : let current_value = template;
116 12 : for (let property of value_path.split("/"))
117 44 : current_value = current_value[property] || default_value;
118 12 : return +current_value;
119 : }
120 :
121 : /**
122 : * Gets the value originating at the value_path with the modifiers dictated by the mod_key applied.
123 : *
124 : * @param {Object} template - A valid template as returned from a template loader.
125 : * @param {string} value_path - Route to value within the xml template structure.
126 : * @param {string} mod_key - Tech modification key, if different from value_path.
127 : * @param {number} player - Optional player id.
128 : * @param {Object} modifiers - Value modifiers from auto-researched techs, unit upgrades,
129 : * etc. Optional as only used if no player id provided.
130 : * @param {number} default_value - A value to use if one is not specified in the template.
131 : * @return {number} Modifier altered value.
132 : */
133 : function GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers={}, default_value)
134 : {
135 12 : let current_value = GetBaseTemplateDataValue(template, value_path, default_value);
136 12 : mod_key = mod_key || value_path;
137 :
138 12 : if (player)
139 6 : current_value = ApplyValueModificationsToTemplate(mod_key, current_value, player, template);
140 6 : else if (modifiers && modifiers[mod_key])
141 0 : current_value = GetTechModifiedProperty(modifiers[mod_key], GetIdentityClasses(template.Identity), current_value);
142 :
143 : // Using .toFixed() to get around spidermonkey's treatment of numbers (3 * 1.1 = 3.3000000000000003 for instance).
144 12 : return +current_value.toFixed(8);
145 : }
146 :
147 : /**
148 : * Get information about a template with or without technology modifications.
149 : *
150 : * NOTICE: The data returned here should have the same structure as
151 : * the object returned by GetEntityState and GetExtendedEntityState!
152 : *
153 : * @param {Object} template - A valid template as returned by the template loader.
154 : * @param {number} player - An optional player id to get the technology modifications
155 : * of properties.
156 : * @param {Object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
157 : * @param {Object} resources - An instance of the Resources class.
158 : * @param {Object} modifiers - Modifications from auto-researched techs, unit upgrades
159 : * etc. Optional as only used if there's no player
160 : * id provided.
161 : */
162 : function GetTemplateDataHelper(template, player, auraTemplates, resources, modifiers = {})
163 : {
164 : // Return data either from template (in tech tree) or sim state (ingame).
165 : // @param {string} value_path - Route to the value within the template.
166 : // @param {string} mod_key - Modification key, if not the same as the value_path.
167 : // @param {number} default_value - A value to use if one is not specified in the template.
168 4 : const getEntityValue = function(value_path, mod_key, default_value = 0) {
169 12 : return GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers, default_value);
170 : };
171 :
172 4 : let ret = {};
173 :
174 4 : if (template.Resistance)
175 : {
176 : // Don't show Foundation resistance.
177 0 : ret.resistance = {};
178 0 : if (template.Resistance.Entity)
179 : {
180 0 : if (template.Resistance.Entity.Damage)
181 : {
182 0 : ret.resistance.Damage = {};
183 0 : for (let damageType in template.Resistance.Entity.Damage)
184 0 : ret.resistance.Damage[damageType] = getEntityValue("Resistance/Entity/Damage/" + damageType);
185 : }
186 0 : if (template.Resistance.Entity.Capture)
187 0 : ret.resistance.Capture = getEntityValue("Resistance/Entity/Capture");
188 0 : if (template.Resistance.Entity.ApplyStatus)
189 : {
190 0 : ret.resistance.ApplyStatus = {};
191 0 : for (let statusEffect in template.Resistance.Entity.ApplyStatus)
192 0 : ret.resistance.ApplyStatus[statusEffect] = {
193 : "blockChance": getEntityValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/BlockChance"),
194 : "duration": getEntityValue("Resistance/Entity/ApplyStatus/" + statusEffect + "/Duration")
195 : };
196 : }
197 : }
198 : }
199 :
200 4 : let getAttackEffects = (temp, path) => {
201 0 : let effects = {};
202 0 : if (temp.Capture)
203 0 : effects.Capture = getEntityValue(path + "/Capture");
204 :
205 0 : if (temp.Damage)
206 : {
207 0 : effects.Damage = {};
208 0 : for (let damageType in temp.Damage)
209 0 : effects.Damage[damageType] = getEntityValue(path + "/Damage/" + damageType);
210 : }
211 :
212 0 : if (temp.ApplyStatus)
213 0 : effects.ApplyStatus = temp.ApplyStatus;
214 :
215 0 : return effects;
216 : };
217 :
218 4 : if (template.Attack)
219 : {
220 0 : ret.attack = {};
221 0 : for (let type in template.Attack)
222 : {
223 0 : let getAttackStat = function(stat) {
224 0 : return getEntityValue("Attack/" + type + "/" + stat);
225 : };
226 :
227 0 : ret.attack[type] = {
228 : "attackName": {
229 : "name": template.Attack[type].AttackName._string || template.Attack[type].AttackName,
230 : "context": template.Attack[type].AttackName["@context"]
231 : },
232 : "minRange": getAttackStat("MinRange"),
233 : "maxRange": getAttackStat("MaxRange"),
234 : "yOrigin": getAttackStat("Origin/Y")
235 : };
236 :
237 0 : ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
238 : (2 * ret.attack[type].yOrigin + ret.attack[type].maxRange));
239 :
240 0 : ret.attack[type].repeatTime = getAttackStat("RepeatTime");
241 0 : if (template.Attack[type].Projectile)
242 0 : ret.attack[type].friendlyFire = template.Attack[type].Projectile.FriendlyFire == "true";
243 :
244 0 : Object.assign(ret.attack[type], getAttackEffects(template.Attack[type], "Attack/" + type));
245 :
246 0 : if (template.Attack[type].Splash)
247 : {
248 0 : ret.attack[type].splash = {
249 : "friendlyFire": template.Attack[type].Splash.FriendlyFire != "false",
250 : "shape": template.Attack[type].Splash.Shape,
251 : };
252 0 : Object.assign(ret.attack[type].splash, getAttackEffects(template.Attack[type].Splash, "Attack/" + type + "/Splash"));
253 : }
254 : }
255 : }
256 :
257 4 : if (template.DeathDamage)
258 : {
259 0 : ret.deathDamage = {
260 : "friendlyFire": template.DeathDamage.FriendlyFire != "false",
261 : };
262 :
263 0 : Object.assign(ret.deathDamage, getAttackEffects(template.DeathDamage, "DeathDamage"));
264 : }
265 :
266 4 : if (template.Auras && auraTemplates)
267 : {
268 0 : ret.auras = {};
269 0 : for (let auraID of template.Auras._string.split(/\s+/))
270 0 : ret.auras[auraID] = GetAuraDataHelper(auraTemplates[auraID]);
271 : }
272 :
273 4 : if (template.BuildingAI)
274 0 : ret.buildingAI = {
275 : "defaultArrowCount": Math.round(getEntityValue("BuildingAI/DefaultArrowCount")),
276 : "garrisonArrowMultiplier": getEntityValue("BuildingAI/GarrisonArrowMultiplier"),
277 : "maxArrowCount": Math.round(getEntityValue("BuildingAI/MaxArrowCount"))
278 : };
279 :
280 4 : if (template.BuildRestrictions)
281 : {
282 : // required properties
283 0 : ret.buildRestrictions = {
284 : "placementType": template.BuildRestrictions.PlacementType,
285 : "territory": template.BuildRestrictions.Territory,
286 : "category": template.BuildRestrictions.Category,
287 : };
288 :
289 : // optional properties
290 0 : if (template.BuildRestrictions.Distance)
291 : {
292 0 : ret.buildRestrictions.distance = {
293 : "fromClass": template.BuildRestrictions.Distance.FromClass,
294 : };
295 :
296 0 : if (template.BuildRestrictions.Distance.MinDistance)
297 0 : ret.buildRestrictions.distance.min = getEntityValue("BuildRestrictions/Distance/MinDistance");
298 :
299 0 : if (template.BuildRestrictions.Distance.MaxDistance)
300 0 : ret.buildRestrictions.distance.max = getEntityValue("BuildRestrictions/Distance/MaxDistance");
301 : }
302 : }
303 :
304 4 : if (template.TrainingRestrictions)
305 : {
306 0 : ret.trainingRestrictions = {
307 : "category": template.TrainingRestrictions.Category
308 : };
309 0 : if (template.TrainingRestrictions.MatchLimit)
310 0 : ret.trainingRestrictions.matchLimit = +template.TrainingRestrictions.MatchLimit;
311 : }
312 :
313 4 : if (template.Cost)
314 : {
315 0 : ret.cost = {};
316 0 : for (let resCode in template.Cost.Resources)
317 0 : ret.cost[resCode] = getEntityValue("Cost/Resources/" + resCode);
318 :
319 0 : if (template.Cost.Population)
320 0 : ret.cost.population = getEntityValue("Cost/Population");
321 :
322 0 : if (template.Cost.BuildTime)
323 0 : ret.cost.time = getEntityValue("Cost/BuildTime");
324 : }
325 :
326 4 : if (template.Footprint)
327 : {
328 0 : ret.footprint = { "height": template.Footprint.Height };
329 :
330 0 : if (template.Footprint.Square)
331 0 : ret.footprint.square = {
332 : "width": +template.Footprint.Square["@width"],
333 : "depth": +template.Footprint.Square["@depth"]
334 : };
335 0 : else if (template.Footprint.Circle)
336 0 : ret.footprint.circle = { "radius": +template.Footprint.Circle["@radius"] };
337 : else
338 0 : warn("GetTemplateDataHelper(): Unrecognized Footprint type");
339 : }
340 :
341 4 : if (template.Garrisonable)
342 0 : ret.garrisonable = {
343 : "size": getEntityValue("Garrisonable/Size")
344 : };
345 :
346 4 : if (template.GarrisonHolder)
347 : {
348 0 : ret.garrisonHolder = {
349 : "buffHeal": getEntityValue("GarrisonHolder/BuffHeal")
350 : };
351 :
352 0 : if (template.GarrisonHolder.Max)
353 0 : ret.garrisonHolder.capacity = getEntityValue("GarrisonHolder/Max");
354 : }
355 :
356 4 : if (template.Heal)
357 0 : ret.heal = {
358 : "health": getEntityValue("Heal/Health"),
359 : "range": getEntityValue("Heal/Range"),
360 : "interval": getEntityValue("Heal/Interval")
361 : };
362 :
363 4 : if (template.ResourceGatherer)
364 : {
365 0 : ret.resourceGatherRates = {};
366 0 : let baseSpeed = getEntityValue("ResourceGatherer/BaseSpeed");
367 0 : for (let type in template.ResourceGatherer.Rates)
368 0 : ret.resourceGatherRates[type] = getEntityValue("ResourceGatherer/Rates/"+ type) * baseSpeed;
369 : }
370 :
371 4 : if (template.ResourceDropsite)
372 0 : ret.resourceDropsite = {
373 : "types": template.ResourceDropsite.Types.split(" ")
374 : };
375 :
376 4 : if (template.ResourceTrickle)
377 : {
378 0 : ret.resourceTrickle = {
379 : "interval": +template.ResourceTrickle.Interval,
380 : "rates": {}
381 : };
382 0 : for (let type in template.ResourceTrickle.Rates)
383 0 : ret.resourceTrickle.rates[type] = getEntityValue("ResourceTrickle/Rates/" + type);
384 : }
385 :
386 4 : if (template.Loot)
387 : {
388 0 : ret.loot = {};
389 0 : for (let type in template.Loot)
390 0 : ret.loot[type] = getEntityValue("Loot/"+ type);
391 : }
392 :
393 4 : if (template.Obstruction)
394 : {
395 0 : ret.obstruction = {
396 : "active": ("" + template.Obstruction.Active == "true"),
397 : "blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
398 : "blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
399 : "blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
400 : "blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
401 : "disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
402 : "disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
403 : "shape": {}
404 : };
405 :
406 0 : if (template.Obstruction.Static)
407 : {
408 0 : ret.obstruction.shape.type = "static";
409 0 : ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
410 0 : ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
411 : }
412 0 : else if (template.Obstruction.Unit)
413 : {
414 0 : ret.obstruction.shape.type = "unit";
415 0 : ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
416 : }
417 : else
418 0 : ret.obstruction.shape.type = "cluster";
419 : }
420 :
421 4 : if (template.Pack)
422 0 : ret.pack = {
423 : "state": template.Pack.State,
424 : "time": getEntityValue("Pack/Time"),
425 : };
426 :
427 4 : if (template.Population && template.Population.Bonus)
428 0 : ret.population = {
429 : "bonus": getEntityValue("Population/Bonus")
430 : };
431 :
432 4 : if (template.Health)
433 0 : ret.health = Math.round(getEntityValue("Health/Max"));
434 :
435 4 : if (template.Identity)
436 : {
437 4 : ret.selectionGroupName = template.Identity.SelectionGroupName;
438 4 : ret.name = {
439 : "specific": (template.Identity.SpecificName || template.Identity.GenericName),
440 : "generic": template.Identity.GenericName
441 : };
442 4 : ret.icon = template.Identity.Icon;
443 4 : ret.tooltip = template.Identity.Tooltip;
444 4 : ret.requirements = template.Identity.Requirements;
445 4 : ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
446 4 : ret.nativeCiv = template.Identity.Civ;
447 : }
448 :
449 4 : if (template.UnitMotion)
450 : {
451 0 : const walkSpeed = getEntityValue("UnitMotion/WalkSpeed");
452 0 : ret.speed = {
453 : "walk": walkSpeed,
454 : "run": walkSpeed,
455 : "acceleration": getEntityValue("UnitMotion/Acceleration")
456 : };
457 0 : if (template.UnitMotion.RunMultiplier)
458 0 : ret.speed.run *= getEntityValue("UnitMotion/RunMultiplier");
459 : }
460 :
461 4 : if (template.Upgrade)
462 : {
463 4 : ret.upgrades = [];
464 4 : for (let upgradeName in template.Upgrade)
465 : {
466 4 : let upgrade = template.Upgrade[upgradeName];
467 :
468 4 : let cost = {};
469 4 : if (upgrade.Cost)
470 4 : for (let res in upgrade.Cost)
471 8 : cost[res] = getEntityValue("Upgrade/" + upgradeName + "/Cost/" + res, "Upgrade/Cost/" + res);
472 4 : if (upgrade.Time)
473 4 : cost.time = getEntityValue("Upgrade/" + upgradeName + "/Time", "Upgrade/Time");
474 :
475 4 : ret.upgrades.push({
476 : "entity": upgrade.Entity,
477 : "tooltip": upgrade.Tooltip,
478 : "cost": cost,
479 : "icon": upgrade.Icon,
480 : "requirements": upgrade.Requirements
481 : });
482 : }
483 : }
484 :
485 4 : if (template.Researcher)
486 : {
487 0 : ret.techCostMultiplier = {};
488 0 : for (const res of resources.GetCodes().concat(["time"]))
489 0 : ret.techCostMultiplier[res] = getEntityValue("Researcher/TechCostMultiplier/" + res, null, 1);
490 : }
491 :
492 4 : if (template.Trader)
493 0 : ret.trader = {
494 : "GainMultiplier": getEntityValue("Trader/GainMultiplier")
495 : };
496 :
497 4 : if (template.Treasure)
498 : {
499 0 : ret.treasure = {
500 : "collectTime": getEntityValue("Treasure/CollectTime"),
501 : "resources": {}
502 : };
503 0 : for (let resource in template.Treasure.Resources)
504 0 : ret.treasure.resources[resource] = getEntityValue("Treasure/Resources/" + resource);
505 : }
506 :
507 4 : if (template.TurretHolder)
508 0 : ret.turretHolder = {
509 : "turretPoints": template.TurretHolder.TurretPoints
510 : };
511 :
512 4 : if (template.Upkeep)
513 : {
514 0 : ret.upkeep = {
515 : "interval": +template.Upkeep.Interval,
516 : "rates": {}
517 : };
518 0 : for (let type in template.Upkeep.Rates)
519 0 : ret.upkeep.rates[type] = getEntityValue("Upkeep/Rates/" + type);
520 : }
521 :
522 4 : if (template.WallSet)
523 : {
524 0 : ret.wallSet = {
525 : "templates": {
526 : "tower": template.WallSet.Templates.Tower,
527 : "gate": template.WallSet.Templates.Gate,
528 : "fort": template.WallSet.Templates.Fort || "structures/" + template.Identity.Civ + "/fortress",
529 : "long": template.WallSet.Templates.WallLong,
530 : "medium": template.WallSet.Templates.WallMedium,
531 : "short": template.WallSet.Templates.WallShort
532 : },
533 : "maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
534 : "minTowerOverlap": +template.WallSet.MinTowerOverlap
535 : };
536 0 : if (template.WallSet.Templates.WallEnd)
537 0 : ret.wallSet.templates.end = template.WallSet.Templates.WallEnd;
538 0 : if (template.WallSet.Templates.WallCurves)
539 0 : ret.wallSet.templates.curves = template.WallSet.Templates.WallCurves.split(/\s+/);
540 : }
541 :
542 4 : if (template.WallPiece)
543 0 : ret.wallPiece = {
544 : "length": +template.WallPiece.Length,
545 : "angle": +(template.WallPiece.Orientation || 1) * Math.PI,
546 : "indent": +(template.WallPiece.Indent || 0),
547 : "bend": +(template.WallPiece.Bend || 0) * Math.PI
548 : };
549 :
550 4 : return ret;
551 : }
552 :
553 : /**
554 : * Get basic information about a technology template.
555 : * @param {Object} template - A valid template as obtained by loading the tech JSON file.
556 : * @param {string} civ - Civilization for which the tech requirements should be calculated.
557 : */
558 : function GetTechnologyBasicDataHelper(template, civ)
559 : {
560 0 : return {
561 : "name": {
562 : "generic": template.genericName
563 : },
564 : "icon": template.icon ? "technologies/" + template.icon : undefined,
565 : "description": template.description,
566 : "reqs": DeriveTechnologyRequirements(template, civ),
567 : "modifications": template.modifications,
568 : "affects": template.affects,
569 : "replaces": template.replaces
570 : };
571 : }
572 :
573 : /**
574 : * Get information about a technology template.
575 : * @param {Object} template - A valid template as obtained by loading the tech JSON file.
576 : * @param {string} civ - Civilization for which the specific name and tech requirements should be returned.
577 : * @param {Object} resources - An instance of the Resources class.
578 : */
579 : function GetTechnologyDataHelper(template, civ, resources)
580 : {
581 0 : let ret = GetTechnologyBasicDataHelper(template, civ);
582 :
583 0 : if (template.specificName)
584 0 : ret.name.specific = template.specificName[civ] || template.specificName.generic;
585 :
586 0 : ret.cost = { "time": template.researchTime ? +template.researchTime : 0 };
587 0 : for (let type of resources.GetCodes())
588 0 : ret.cost[type] = +(template.cost && template.cost[type] || 0);
589 :
590 0 : ret.tooltip = template.tooltip;
591 0 : ret.requirementsTooltip = template.requirementsTooltip || "";
592 :
593 0 : return ret;
594 : }
595 :
596 : /**
597 : * Get information about an aura template.
598 : * @param {object} template - A valid template as obtained by loading the aura JSON file.
599 : */
600 : function GetAuraDataHelper(template)
601 : {
602 0 : return {
603 : "name": {
604 : "generic": template.auraName,
605 : },
606 : "description": template.auraDescription || null,
607 : "modifications": template.modifications,
608 : "radius": template.radius || null,
609 : };
610 : }
611 :
612 : function calculateCarriedResources(carriedResources, tradingGoods)
613 : {
614 0 : var resources = {};
615 :
616 0 : if (carriedResources)
617 0 : for (let resource of carriedResources)
618 0 : resources[resource.type] = (resources[resource.type] || 0) + resource.amount;
619 :
620 0 : if (tradingGoods && tradingGoods.amount)
621 0 : resources[tradingGoods.type] =
622 : (resources[tradingGoods.type] || 0) +
623 : (tradingGoods.amount.traderGain || 0) +
624 : (tradingGoods.amount.market1Gain || 0) +
625 : (tradingGoods.amount.market2Gain || 0);
626 :
627 0 : return resources;
628 : }
629 :
630 : /**
631 : * Remove filter prefix (mirage, corpse, etc) from template name.
632 : *
633 : * ie. filter|dir/to/template -> dir/to/template
634 : */
635 : function removeFiltersFromTemplateName(templateName)
636 : {
637 0 : return templateName.split("|").pop();
638 : }
|