Easier introduction of new damage types.

725aa8a686 introduced a DamageTypes.js global script similar to the
resources one. However, we never actually need to refer to this script
since we can always use the damage types provided by the
template/context/object we are looping over/...
There is one exception to this for AI weighting of damage types.
However, since damage types are not stored in files, this is strictly
equivalent to hardcoding them in the global script and was deemed
acceptable.

Patch By: freagarach
Reviewed By: wraitii
Refs #4801 (by invalidating it for now, though such helper files might
be useful in the future if damage types require more metadata).

Differential Revision: https://code.wildfiregames.com/D1938
This was SVN commit r22527.
This commit is contained in:
wraitii 2019-07-22 18:37:18 +00:00
parent 32e8ed51aa
commit 6643613b54
19 changed files with 95 additions and 115 deletions

View file

@ -1,22 +0,0 @@
function DamageTypes()
{
// TODO: load these from files
this.names = {
"Hack": markForTranslationWithContext("damage type", "Hack"),
"Pierce": markForTranslationWithContext("damage type", "Pierce"),
"Crush": markForTranslationWithContext("damage type", "Crush"),
};
deepfreeze(this.names);
}
DamageTypes.prototype.GetNames = function()
{
return this.names;
};
DamageTypes.prototype.GetTypes = function()
{
return Object.keys(this.names);
};

View file

@ -165,8 +165,9 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, damag
if (template.Armour)
{
ret.armour = {};
for (let damageType of damageTypes.GetTypes())
ret.armour[damageType] = getEntityValue("Armour/" + damageType);
for (let damageType in template.Armour)
if (damageType != "Foundation")
ret.armour[damageType] = getEntityValue("Armour/" + damageType);
}
if (template.Attack)
@ -190,7 +191,7 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, damag
"elevationBonus": getAttackStat("ElevationBonus"),
"damage": {}
};
for (let damageType of damageTypes.GetTypes())
for (let damageType in template.Attack[type].Damage)
ret.attack[type].damage[damageType] = getAttackStat("Damage/" + damageType);
ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
@ -206,7 +207,7 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, damag
"shape": template.Attack[type].Splash.Shape,
"damage": {}
};
for (let damageType of damageTypes.GetTypes())
for (let damageType in template.Attack[type].Splash.Damage)
ret.attack[type].splash.damage[damageType] = getAttackStat("Splash/Damage/" + damageType);
}
}
@ -218,7 +219,7 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, damag
"friendlyFire": template.DeathDamage.FriendlyFire != "false",
"damage": {}
};
for (let damageType of damageTypes.GetTypes())
for (let damageType in template.DeathDamage.Damage)
ret.deathDamage.damage[damageType] = getEntityValue("DeathDamage/Damage/" + damageType);
}

View file

@ -14,8 +14,6 @@ var g_AttackTypes = {
"Capture": translate("Capture Attack:")
};
var g_DamageTypes = new DamageTypes();
var g_SplashDamageTypes = {
"Circular": translate("Circular Splash Damage"),
"Linear": translate("Linear Splash Damage")
@ -222,7 +220,7 @@ function getArmorTooltip(template)
Object.keys(template.armour).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
"damage": template.armour[dmgType].toFixed(1),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypes.GetNames()[dmgType])),
"damageType": unitFont(translateWithContext("damage type", dmgType)),
"armorPercentage":
'[font="sans-10"]' +
sprintf(translate("(%(armorPercentage)s)"), {
@ -238,11 +236,10 @@ function damageTypesToText(dmg)
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
return g_DamageTypes.GetTypes().filter(
dmgType => dmg[dmgType]).map(
return Object.keys(dmg).filter(dmgType => dmg[dmgType]).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg[dmgType].toFixed(1),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypes.GetNames()[dmgType]))
"damageType": unitFont(translateWithContext("damage type", dmgType))
})).join(commaFont(translate(", ")));
}

View file

@ -23,7 +23,7 @@ function getActualUpgradeData(upgradesInfo)
{
upgrade.entity = upgrade.entity.replace(/\{(civ|native)\}/g, g_SelectedCiv);
let data = GetTemplateDataHelper(loadTemplate(upgrade.entity), null, g_AuraData, g_ResourceData, g_DamageTypes);
let data = GetTemplateDataHelper(loadTemplate(upgrade.entity), null, g_AuraData, g_ResourceData);
data.name.internal = upgrade.entity;
data.cost = upgrade.cost;
data.icon = upgrade.icon || data.icon;
@ -124,8 +124,8 @@ function getPhaseOfTemplate(template)
*/
function GetTemplateData(templateName)
{
var template = loadTemplate(templateName);
return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers);
let template = loadTemplate(templateName);
return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_CurrentModifiers);
}
function isPairTech(technologyCode)

View file

@ -17,7 +17,6 @@ var g_CivData = loadCivData(true, false);
*/
var g_ParsedData = {};
var g_ResourceData = new Resources();
var g_DamageTypes = new DamageTypes();
// This must be defined after the g_TechnologyData cache object is declared.
var g_AutoResearchTechList = findAllAutoResearchedTechs();
@ -110,7 +109,7 @@ function loadEntityTemplate(templateName)
return null;
let template = loadTemplate(templateName);
let parsed = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers);
let parsed = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_CurrentModifiers);
parsed.name.internal = templateName;
parsed.history = template.Identity.History;

View file

@ -262,7 +262,7 @@ function getWallElement(element, style)
function readyWallElement(path, civCode)
{
path = path.replace(/\{civ\}/g, civCode);
let template = GetTemplateDataHelper(Engine.GetTemplate(path), null, null, {}, g_DamageTypes, {});
let template = GetTemplateDataHelper(Engine.GetTemplate(path), null, null, {}, {});
let length = template.wallPiece ? template.wallPiece.length : template.obstruction.shape.width;
return deepfreeze({

View file

@ -21,8 +21,6 @@ const TERRAIN_SEPARATOR = "|";
const SEA_LEVEL = 20.0;
const HEIGHT_UNITS_PER_METRE = 92;
const g_DamageTypes = new DamageTypes();
/**
* Constants needed for heightmap_manipulation.js
*/

View file

@ -198,14 +198,16 @@ m.Template = m.Class({
"getPopulationBonus": function() { return +this.get("Cost/PopulationBonus"); },
"armourStrengths": function() {
if (!this.get("Armour"))
let armourDamageTypes = this.get("Armour");
if (!armourDamageTypes)
return undefined;
return {
"Hack": +this.get("Armour/Hack"),
"Pierce": +this.get("Armour/Pierce"),
"Crush": +this.get("Armour/Crush")
};
let armour = {};
for (let damageType in armourDamageTypes)
if (damageType != "Foundation")
armour[damageType] = +armourDamageTypes[damageType];
return armour;
},
"attackTypes": function() {
@ -229,14 +231,15 @@ m.Template = m.Class({
},
"attackStrengths": function(type) {
if (!this.get("Attack/" + type +""))
let attackDamageTypes = this.get("Attack/" + type + "/Damage");
if (!attackDamageTypes)
return undefined;
return {
"Hack": +(this.get("Attack/" + type + "/Damage/Hack") || 0),
"Pierce": +(this.get("Attack/" + type + "/Damage/Pierce") || 0),
"Crush": +(this.get("Attack/" + type + "/Damage/Crush") || 0)
};
let damage = {};
for (let damageType in attackDamageTypes)
damage[damageType] = +attackDamageTypes[damageType];
return damage;
},
"captureStrength": function() {

View file

@ -24,6 +24,14 @@ m.Config = function(difficulty, behavior)
"popForBlacksmith": 65,
"numSentryTowers": 1
};
// Define damage type importance factors here.
this.DamageTypeImportance = {
"Hack": 0.085,
"Pierce": 0.075,
"Crush": 0.065
};
this.Economy = {
"popPhase2": 38, // How many units we want before aging to phase2.
"workPhase3": 65, // How many workers we want before aging to phase3.

View file

@ -488,7 +488,7 @@ m.DefenseArmy.prototype.evaluateStrength = function(ent, isOwn, remove)
entStrength = 2;
}
else
entStrength = m.getMaxStrength(ent);
entStrength = m.getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance);
// TODO adapt the getMaxStrength function for animals.
// For the time being, just increase it for elephants as the returned value is too small.

View file

@ -486,7 +486,7 @@ m.DefenseManager.prototype.assignDefenders = function(gameState)
else if (aMin === undefined)
continue;
armiesNeeding[aMin].need -= m.getMaxStrength(ent);
armiesNeeding[aMin].need -= m.getMaxStrength(ent, this.Config.debug, this.Config.DamageTypeImportance);
armiesNeeding[aMin].army.addOwn(gameState, potentialDefenders[i]);
armiesNeeding[aMin].army.assignUnit(gameState, potentialDefenders[i]);
potentialDefenders[i] = undefined;

View file

@ -8,10 +8,11 @@ m.isSiegeUnit = function(ent)
};
/** returns some sort of DPS * health factor. If you specify a class, it'll use the modifiers against that class too. */
m.getMaxStrength = function(ent, againstClass)
m.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstClass)
{
let strength = 0;
let attackTypes = ent.attackTypes();
let damageTypes = Object.keys(DamageTypeImportance);
if (!attackTypes)
return strength;
@ -26,20 +27,10 @@ m.getMaxStrength = function(ent, againstClass)
let val = parseFloat(attackStrength[str]);
if (againstClass)
val *= ent.getMultiplierAgainst(type, againstClass);
switch (str)
{
case "Crush":
strength += val * 0.085 / 3;
break;
case "Hack":
strength += val * 0.075 / 3;
break;
case "Pierce":
strength += val * 0.065 / 3;
break;
default:
API3.warn("Petra: " + str + " unknown attackStrength in getMaxStrength");
}
if (DamageTypeImportance[str])
strength += DamageTypeImportance[str] * val / damageTypes.length;
else if (debugLevel > 0)
API3.warn("Petra: " + str + " unknown attackStrength in getMaxStrength (please add " + str + " to config.js).");
}
let attackRange = ent.attackRange(type);
@ -68,20 +59,10 @@ m.getMaxStrength = function(ent, againstClass)
for (let str in armourStrength)
{
let val = parseFloat(armourStrength[str]);
switch (str)
{
case "Crush":
strength += val * 0.085 / 3;
break;
case "Hack":
strength += val * 0.075 / 3;
break;
case "Pierce":
strength += val * 0.065 / 3;
break;
default:
API3.warn("Petra: " + str + " unknown armourStrength in getMaxStrength");
}
if (DamageTypeImportance[str])
strength += DamageTypeImportance[str] * val / damageTypes.length;
else if (debugLevel > 0)
API3.warn("Petra: " + str + " unknown armourStrength in getMaxStrength (please add " + str + " to config.js).");
}
return strength * ent.maxHitpoints() / 100.0;

View file

@ -776,13 +776,13 @@ m.HQ.prototype.findBestTrainableUnit = function(gameState, classes, requirements
{
if (param[0] == "strength")
{
aValue += m.getMaxStrength(a[1]) * param[1];
bValue += m.getMaxStrength(b[1]) * param[1];
aValue += m.getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1];
bValue += m.getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance) * param[1];
}
else if (param[0] == "siegeStrength")
{
aValue += m.getMaxStrength(a[1], "Structure") * param[1];
bValue += m.getMaxStrength(b[1], "Structure") * param[1];
aValue += m.getMaxStrength(a[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1];
bValue += m.getMaxStrength(b[1], gameState.ai.Config.debug, gameState.ai.Config.DamageTypeImportance, "Structure") * param[1];
}
else if (param[0] == "speed")
{

View file

@ -7,11 +7,11 @@ Armour.prototype.Schema =
"<Pierce>0.0</Pierce>" +
"<Crush>5.0</Crush>" +
"</a:example>" +
DamageTypes.BuildSchema("damage protection") +
BuildDamageTypesSchema("damage protection") +
"<optional>" +
"<element name='Foundation' a:help='Armour given to building foundations'>" +
"<interleave>" +
DamageTypes.BuildSchema("damage protection") +
BuildDamageTypesSchema("damage protection") +
"</interleave>" +
"</element>" +
"</optional>";
@ -59,9 +59,9 @@ Armour.prototype.TakeDamage = function(strengths, multiplier = 1)
Armour.prototype.GetArmourStrengths = function()
{
// Work out the armour values with technology effects
var applyMods = (type, foundation) => {
var strength;
// Work out the armour values with technology effects.
let applyMods = (type, foundation) => {
let strength;
if (foundation)
{
strength = +this.template.Foundation[type];
@ -73,11 +73,12 @@ Armour.prototype.GetArmourStrengths = function()
return ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity);
};
var foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
let foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
let ret = {};
for (let damageType of DamageTypes.GetTypes())
ret[damageType] = applyMods(damageType, foundation);
for (let damageType in this.template)
if (damageType != "Foundation")
ret[damageType] = applyMods(damageType, foundation);
return ret;
};

View file

@ -131,7 +131,7 @@ Attack.prototype.Schema =
"<element name='Melee'>" +
"<interleave>" +
"<element name='Damage'>" +
DamageTypes.BuildSchema("damage strength") +
BuildDamageTypesSchema("damage strength") +
"</element>" +
"<element name='MaxRange' a:help='Maximum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
"<element name='PrepareTime' a:help='Time from the start of the attack command until the attack actually occurs (in milliseconds). This value relative to RepeatTime should closely match the \"event\" point in the actor&apos;s attack animation'>" +
@ -150,7 +150,7 @@ Attack.prototype.Schema =
"<element name='Ranged'>" +
"<interleave>" +
"<element name='Damage'>" +
DamageTypes.BuildSchema("damage strength") +
BuildDamageTypesSchema("damage strength") +
"</element>" +
"<element name='MaxRange' a:help='Maximum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
"<element name='MinRange' a:help='Minimum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
@ -180,7 +180,7 @@ Attack.prototype.Schema =
"<element name='Range' a:help='Size of the area affected by the splash'><ref name='nonNegativeDecimal'/></element>" +
"<element name='FriendlyFire' a:help='Whether the splash damage can hurt non enemy units'><data type='boolean'/></element>" +
"<element name='Damage'>" +
DamageTypes.BuildSchema("damage strength") +
BuildDamageTypesSchema("damage strength") +
"</element>" +
Attack.prototype.bonusesSchema +
"</interleave>" +
@ -242,7 +242,7 @@ Attack.prototype.Schema =
"<element name='Slaughter' a:help='A special attack to kill domestic animals'>" +
"<interleave>" +
"<element name='Damage'>" +
DamageTypes.BuildSchema("damage strength") +
BuildDamageTypesSchema("damage strength") +
"</element>" +
"<element name='MaxRange'><ref name='nonNegativeDecimal'/></element>" + // TODO: how do these work?
Attack.prototype.bonusesSchema +
@ -470,7 +470,7 @@ Attack.prototype.GetAttackStrengths = function(type)
return { "value": ApplyValueModificationsToEntity("Attack/Capture/Value", +(template.Value || 0), this.entity) };
let ret = {};
for (let damageType of DamageTypes.GetTypes())
for (let damageType in template.Damage)
ret[damageType] = applyMods(damageType);
return ret;

View file

@ -34,7 +34,7 @@ DeathDamage.prototype.Schema =
"<element name='Range' a:help='Size of the area affected by the splash'><ref name='nonNegativeDecimal'/></element>" +
"<element name='FriendlyFire' a:help='Whether the splash damage can hurt non enemy units'><data type='boolean'/></element>" +
"<element name='Damage'>" +
DamageTypes.BuildSchema("damage strength") +
BuildDamageTypesSchema("damage strength") +
"</element>" +
DeathDamage.prototype.bonusesSchema;
@ -51,7 +51,7 @@ DeathDamage.prototype.GetDeathDamageStrengths = function()
ApplyValueModificationsToEntity("DeathDamage/Damage/" + damageType, +(this.template.Damage[damageType] || 0), this.entity);
let ret = {};
for (let damageType of DamageTypes.GetTypes())
for (let damageType in this.template.Damage)
ret[damageType] = applyMods(damageType);
return ret;

View file

@ -547,14 +547,14 @@ GuiInterface.prototype.GetTemplateData = function(player, templateName)
let aurasTemplate = {};
if (!template.Auras)
return GetTemplateDataHelper(template, player, aurasTemplate, Resources, DamageTypes);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
let auraNames = template.Auras._string.split(/\s+/);
for (let name of auraNames)
aurasTemplate[name] = AuraTemplates.Get(name);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources, DamageTypes);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
};
GuiInterface.prototype.IsTechnologyResearched = function(player, data)

View file

@ -126,11 +126,11 @@ cmpUpgrade.owner = playerID;
* To start with, no techs are researched...
*/
// T1: Check the cost of the upgrade without a player value being passed (as it would be in the structree).
let parsed_template = GetTemplateDataHelper(template, null, {}, Resources, DamageTypes);
let parsed_template = GetTemplateDataHelper(template, null, {}, Resources);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T2: Check the value, with a player ID (as it would be in-session).
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources, DamageTypes);
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T3: Check that the value is correct within the Update Component.
@ -144,11 +144,11 @@ cmpUpgrade.Upgrade("structures/"+civCode+"_defense_tower");
isResearched = true;
// T4: Check that the player-less value hasn't increased...
parsed_template = GetTemplateDataHelper(template, null, {}, Resources, DamageTypes);
parsed_template = GetTemplateDataHelper(template, null, {}, Resources);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T5: ...but the player-backed value has.
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources, DamageTypes);
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources);
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 160, "wood": 25, "time": 90 });
// T6: The upgrade component should still be using the old resource cost (but new time cost) for the upgrade in progress...

View file

@ -1,8 +1,22 @@
DamageTypes.prototype.BuildSchema = function(helptext = "")
/**
* Builds a RelaxRNG schema based on currently valid elements.
*
* To prevent validation errors, disabled damage types are included in the schema.
*
* @param {string} helptext - Text displayed as help
* @return {string} - RelaxNG schema string
*/
function BuildDamageTypesSchema(helptext = "")
{
return "<interleave>" + this.GetTypes().reduce((schema, type) =>
schema + "<element name='"+type+"' a:help='"+type+" "+helptext+"'><ref name='nonNegativeDecimal'/></element>",
"") + "</interleave>";
};
return "<oneOrMore>" +
"<element a:help='" + helptext + "'>" +
"<anyName>" +
// Armour requires Foundation to not be a damage type.
"<except><name>Foundation</name></except>" +
"</anyName>" +
"<ref name='nonNegativeDecimal' />" +
"</element>" +
"</oneOrMore>";
}
DamageTypes = new DamageTypes();
Engine.RegisterGlobal("BuildDamageTypesSchema", BuildDamageTypesSchema);