Move damage types definition to a new helper similar to how resources are handled.

This should make adding new damage types a little easier, however such
an
extension would still need changes to the AI, and to all templates that
have Armour, Attack, or DeathDamage.

Reviewed By: elexis
Differential Revision: https://code.wildfiregames.com/D866
This was SVN commit r20203.
This commit is contained in:
leper 2017-09-18 16:33:56 +00:00
parent 2e02c88d2c
commit 725aa8a686
16 changed files with 116 additions and 106 deletions

View file

@ -0,0 +1,22 @@
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

@ -117,11 +117,12 @@ function GetModifiedTemplateDataValue(template, value_path, mod_key, player, mod
* of properties.
* @param {object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
* @param {object} resources - An instance of the Resources prototype.
* @param {object} damageTypes - An instance of the DamageTypes prototype.
* @param {object} modifiers - Modifications from auto-researched techs, unit upgrades
* etc. Optional as only used if there's no player
* id provided.
*/
function GetTemplateDataHelper(template, player, auraTemplates, resources, modifiers={})
function GetTemplateDataHelper(template, player, auraTemplates, resources, damageTypes, modifiers={})
{
// Return data either from template (in tech tree) or sim state (ingame).
// @param {string} value_path - Route to the value within the template.
@ -133,11 +134,11 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, modif
let ret = {};
if (template.Armour)
ret.armour = {
"hack": getEntityValue("Armour/Hack"),
"pierce": getEntityValue("Armour/Pierce"),
"crush": getEntityValue("Armour/Crush")
};
{
ret.armour = {};
for (let damageType of damageTypes.GetTypes())
ret.armour[damageType] = getEntityValue("Armour/" + damageType);
}
if (template.Attack)
{
@ -155,38 +156,38 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, modif
else
{
ret.attack[type] = {
"hack": getAttackStat("Hack"),
"pierce": getAttackStat("Pierce"),
"crush": getAttackStat("Crush"),
"minRange": getAttackStat("MinRange"),
"maxRange": getAttackStat("MaxRange"),
"elevationBonus": getAttackStat("ElevationBonus")
};
for (let damageType of damageTypes.GetTypes())
ret.attack[type][damageType] = getAttackStat(damageType);
ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
(2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange));
}
ret.attack[type].repeatTime = getAttackStat("RepeatTime");
if (template.Attack[type].Splash)
{
ret.attack[type].splash = {
"hack": getAttackStat("Splash/Hack"),
"pierce": getAttackStat("Splash/Pierce"),
"crush": getAttackStat("Splash/Crush"),
// true if undefined
"friendlyFire": template.Attack[type].Splash.FriendlyFire != "false",
"shape": template.Attack[type].Splash.Shape
};
for (let damageType of damageTypes.GetTypes())
ret.attack[type].splash[damageType] = getAttackStat("Splash/" + damageType);
}
}
}
if (template.DeathDamage)
{
ret.deathDamage = {
"hack": getEntityValue("DeathDamage/Hack"),
"pierce": getEntityValue("DeathDamage/Pierce"),
"crush": getEntityValue("DeathDamage/Crush"),
"friendlyFire": template.DeathDamage.FriendlyFire != "false"
};
for (let damageType of damageTypes.GetTypes())
ret.deathDamage[damageType] = getEntityValue("DeathDamage/" + damageType);
}
if (template.Auras)

View file

@ -11,11 +11,7 @@ var g_AttackTypes = {
"Capture": translate("Capture Attack:")
};
var g_DamageTypes = {
"hack": translate("Hack"),
"pierce": translate("Pierce"),
"crush": translate("Crush"),
};
var g_DamageTypes = new DamageTypes();
var g_SplashDamageTypes = {
"Circular": translate("Circular Splash Damage"),
@ -188,7 +184,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(g_DamageTypes[dmgType]),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypes.GetNames()[dmgType])),
"armorPercentage":
'[font="sans-10"]' +
sprintf(translate("(%(armorPercentage)s)"), {
@ -204,11 +200,11 @@ function damageTypesToText(dmg)
if (!dmg)
return '[font="sans-12"]' + translate("(None)") + '[/font]';
return Object.keys(g_DamageTypes).filter(
return g_DamageTypes.GetTypes().filter(
dmgType => dmg[dmgType]).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s"), {
"damage": dmg[dmgType].toFixed(1),
"damageType": unitFont(g_DamageTypes[dmgType])
"damageType": unitFont(translateWithContext("damage type", g_DamageTypes.GetNames()[dmgType]))
})).join(commaFont(translate(", ")));
}

View file

@ -23,7 +23,7 @@ function getActualUpgradeData(upgradesInfo)
{
upgrade.entity = upgrade.entity.replace("{civ}", g_SelectedCiv);
let data = GetTemplateDataHelper(loadTemplate(upgrade.entity), null, g_AuraData, g_ResourceData);
let data = GetTemplateDataHelper(loadTemplate(upgrade.entity), null, g_AuraData, g_ResourceData, g_DamageTypes);
data.cost = upgrade.cost;
data.icon = upgrade.icon || data.icon;
data.tooltip = upgrade.tooltip || data.tooltip;
@ -177,7 +177,7 @@ function unravelPhases(techs)
function GetTemplateData(templateName)
{
var template = loadTemplate(templateName);
return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_CurrentModifiers);
return GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers);
}
function isPairTech(technologyCode)

View file

@ -17,6 +17,7 @@ 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();
@ -101,7 +102,7 @@ function loadUnit(templateName)
return null;
let template = loadTemplate(templateName);
let unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_CurrentModifiers);
let unit = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers);
if (template.ProductionQueue)
{
@ -157,7 +158,7 @@ function loadStructure(templateName)
return null;
let template = loadTemplate(templateName);
let structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_CurrentModifiers);
let structure = GetTemplateDataHelper(template, null, g_AuraData, g_ResourceData, g_DamageTypes, g_CurrentModifiers);
structure.production = {
"technology": [],

View file

@ -214,14 +214,14 @@ m.Template = m.Class({
return +this.get("Cost/PopulationBonus");
},
armourStrengths: function() {
"armourStrengths": function() {
if (!this.get("Armour"))
return undefined;
return {
hack: +this.get("Armour/Hack"),
pierce: +this.get("Armour/Pierce"),
crush: +this.get("Armour/Crush")
"Hack": +this.get("Armour/Hack"),
"Pierce": +this.get("Armour/Pierce"),
"Crush": +this.get("Armour/Crush")
};
},
@ -246,14 +246,14 @@ m.Template = m.Class({
};
},
attackStrengths: function(type) {
"attackStrengths": function(type) {
if (!this.get("Attack/" + type +""))
return undefined;
return {
hack: +(this.get("Attack/" + type + "/Hack") || 0),
pierce: +(this.get("Attack/" + type + "/Pierce") || 0),
crush: +(this.get("Attack/" + type + "/Crush") || 0)
"Hack": +(this.get("Attack/" + type + "/Hack") || 0),
"Pierce": +(this.get("Attack/" + type + "/Pierce") || 0),
"Crush": +(this.get("Attack/" + type + "/Crush") || 0)
};
},

View file

@ -28,13 +28,13 @@ m.getMaxStrength = function(ent, againstClass)
val *= ent.getMultiplierAgainst(type, againstClass);
switch (str)
{
case "crush":
case "Crush":
strength += val * 0.085 / 3;
break;
case "hack":
case "Hack":
strength += val * 0.075 / 3;
break;
case "pierce":
case "Pierce":
strength += val * 0.065 / 3;
break;
default:
@ -70,13 +70,13 @@ m.getMaxStrength = function(ent, againstClass)
let val = parseFloat(armourStrength[str]);
switch (str)
{
case "crush":
case "Crush":
strength += val * 0.085 / 3;
break;
case "hack":
case "Hack":
strength += val * 0.075 / 3;
break;
case "pierce":
case "Pierce":
strength += val * 0.065 / 3;
break;
default:

View file

@ -7,27 +7,11 @@ Armour.prototype.Schema =
"<Pierce>0.0</Pierce>" +
"<Crush>5.0</Crush>" +
"</a:example>" +
"<element name='Hack' a:help='Hack damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Pierce' a:help='Pierce damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Crush' a:help='Crush damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
DamageTypes.BuildSchema("damage protection") +
"<optional>" +
"<element name='Foundation' a:help='Armour given to building foundations'>" +
"<interleave>" +
"<element name='Hack' a:help='Hack damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Pierce' a:help='Pierce damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<element name='Crush' a:help='Crush damage protection'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
DamageTypes.BuildSchema("damage protection") +
"</interleave>" +
"</element>" +
"</optional>";
@ -85,11 +69,11 @@ Armour.prototype.GetArmourStrengths = function()
var foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
return {
"hack": applyMods("Hack", foundation),
"pierce": applyMods("Pierce", foundation),
"crush": applyMods("Crush", foundation)
};
let ret = {};
for (let damageType of DamageTypes.GetTypes())
ret[damageType] = applyMods(damageType, foundation);
return ret;
};
Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour);

View file

@ -100,9 +100,7 @@ Attack.prototype.Schema =
"<optional>" +
"<element name='Melee'>" +
"<interleave>" +
"<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
DamageTypes.BuildSchema("damage strength") +
"<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'>" +
"<data type='nonNegativeInteger'/>" +
@ -119,9 +117,7 @@ Attack.prototype.Schema =
"<optional>" +
"<element name='Ranged'>" +
"<interleave>" +
"<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
DamageTypes.BuildSchema("damage strength") +
"<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>" +
"<optional>"+
@ -149,9 +145,7 @@ Attack.prototype.Schema =
"<element name='Shape' a:help='Shape of the splash damage, can be circular or linear'><text/></element>" +
"<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='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
DamageTypes.BuildSchema("damage strength") +
Attack.prototype.bonusesSchema +
"</interleave>" +
"</element>" +
@ -176,9 +170,7 @@ Attack.prototype.Schema =
"<optional>" +
"<element name='Slaughter' a:help='A special attack to kill domestic animals'>" +
"<interleave>" +
"<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
DamageTypes.BuildSchema("damage strength") +
"<element name='MaxRange'><ref name='nonNegativeDecimal'/></element>" + // TODO: how do these work?
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
@ -403,11 +395,11 @@ Attack.prototype.GetAttackStrengths = function(type)
if (type == "Capture")
return { "value": applyMods("Value") };
return {
"hack": applyMods("Hack"),
"pierce": applyMods("Pierce"),
"crush": applyMods("Crush")
};
let ret = {};
for (let damageType of DamageTypes.GetTypes())
ret[damageType] = applyMods(damageType);
return ret;
};
Attack.prototype.GetSplashDamage = function(type)
@ -502,7 +494,6 @@ Attack.prototype.PerformAttack = function(type, target)
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
let data = {
"type": type,
"attacker": this.entity,

View file

@ -31,9 +31,7 @@ DeathDamage.prototype.Schema =
"<element name='Shape' a:help='Shape of the splash damage, can be circular'><text/></element>" +
"<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='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Pierce' a:help='Pierce damage strength'><ref name='nonNegativeDecimal'/></element>" +
"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
DamageTypes.BuildSchema("damage strength") +
DeathDamage.prototype.bonusesSchema;
DeathDamage.prototype.Init = function()
@ -48,11 +46,11 @@ DeathDamage.prototype.GetDeathDamageStrengths = function()
let applyMods = damageType =>
ApplyValueModificationsToEntity("DeathDamage/" + damageType, +(this.template[damageType] || 0), this.entity);
return {
"hack": applyMods("Hack"),
"pierce": applyMods("Pierce"),
"crush": applyMods("Crush")
};
let ret = {};
for (let damageType of DamageTypes.GetTypes())
ret[damageType] = applyMods(damageType);
return ret;
};
DeathDamage.prototype.GetBonusTemplate = function()

View file

@ -656,14 +656,14 @@ GuiInterface.prototype.GetTemplateData = function(player, name)
let aurasTemplate = {};
if (!template.Auras)
return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources, DamageTypes);
// Add aura name and description loaded from JSON file
let auraNames = template.Auras._string.split(/\s+/);
let cmpDataTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_DataTemplateManager);
for (let name of auraNames)
aurasTemplate[name] = cmpDataTemplateManager.GetAuraTemplate(name);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources);
return GetTemplateDataHelper(template, player, aurasTemplate, Resources, DamageTypes);
};
GuiInterface.prototype.GetTechnologyData = function(player, data)

View file

@ -1,4 +1,5 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("DamageTypes.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Auras.js");
@ -139,15 +140,15 @@ attackComponentTest(undefined, true ,(attacker, cmpAttack, defender) => {
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Capture"), { "value": 8 });
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged"), {
"hack": 0,
"pierce": 10,
"crush": 0
"Hack": 0,
"Pierce": 10,
"Crush": 0
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged.Splash"), {
"hack": 0.0,
"pierce": 15.0,
"crush": 35.0
"Hack": 0.0,
"Pierce": 15.0,
"Crush": 35.0
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Ranged"), {
@ -160,7 +161,13 @@ attackComponentTest(undefined, true ,(attacker, cmpAttack, defender) => {
"repeat": 1000
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashDamage("Ranged"), { "hack": 0, "pierce": 15, "crush": 35, "friendlyFire": false, "shape": "Circular" });
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashDamage("Ranged"), {
"Hack": 0,
"Pierce": 15,
"Crush": 35,
"friendlyFire": false,
"shape": "Circular"
});
});
for (let className of ["Infantry", "Cavalry"])

View file

@ -1,4 +1,5 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("DamageTypes.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Sound.js");
Engine.LoadHelperScript("ValueModification.js");

View file

@ -1,4 +1,5 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("DamageTypes.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Damage.js");
@ -26,9 +27,9 @@ let template = {
};
let modifiedDamage = {
"hack": 0.0,
"pierce": 215.0,
"crush": 35.0
"Hack": 0.0,
"Pierce": 215.0,
"Crush": 35.0
};
let cmpDeathDamage = ConstructComponent(deadEnt, "DeathDamage", template);

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);
let parsed_template = GetTemplateDataHelper(template, null, {}, Resources, DamageTypes);
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);
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources, DamageTypes);
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);
parsed_template = GetTemplateDataHelper(template, null, {}, Resources, DamageTypes);
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);
parsed_template = GetTemplateDataHelper(template, playerID, {}, Resources, DamageTypes);
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

@ -0,0 +1,8 @@
DamageTypes.prototype.BuildSchema = function(helptext = "")
{
return this.GetTypes().reduce((schema, type) =>
schema + "<element name='"+type+"' a:help='"+type+" "+helptext+"'><ref name='nonNegativeDecimal'/></element>",
"");
};
DamageTypes = new DamageTypes();