Rename Armour to Resistance and change the way it is processed.

- Renames Armour-node to Resistance.
- Support resistance against Capture.
- Puts resistance against effects in separate nodes.
- Some cleaning.

Differential Revision: D2229
Reviewed by: @bb (accepted), @wraitii.
Comments by: @Stan, @Nescio.
This was SVN commit r24001.
This commit is contained in:
Freagarach 2020-08-27 10:24:59 +00:00
parent 37d31a5e3d
commit 0f91c5ac61
142 changed files with 1561 additions and 770 deletions

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity filtered=""> <Entity filtered="">
<AIProxy merge=""/> <AIProxy merge=""/>
<Armour merge=""/>
<BuildRestrictions merge=""/> <BuildRestrictions merge=""/>
<!-- Don't provide population bonuses yet (but still do take up population cost) --> <!-- Don't provide population bonuses yet (but still do take up population cost) -->
<Cost merge=""> <Cost merge="">
@ -27,6 +26,7 @@
<Position merge=""/> <Position merge=""/>
<RallyPoint merge=""/> <RallyPoint merge=""/>
<RallyPointRenderer merge=""/> <RallyPointRenderer merge=""/>
<Resistance merge=""/>
<Selectable merge=""/> <Selectable merge=""/>
<Sound merge=""/> <Sound merge=""/>
<StatusBars merge=""/> <StatusBars merge=""/>

View File

@ -160,12 +160,23 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
let ret = {}; let ret = {};
if (template.Armour) if (template.Resistance)
{ {
ret.armour = {}; // Don't show Foundation resistance.
for (let damageType in template.Armour) ret.resistance = {};
if (damageType != "Foundation") if (template.Resistance.Entity)
ret.armour[damageType] = getEntityValue("Armour/" + damageType); {
if (template.Resistance.Entity.Damage)
{
ret.resistance.Damage = {};
for (let damageType in template.Resistance.Entity.Damage)
ret.resistance.Damage[damageType] = getEntityValue("Resistance/Entity/Damage/" + damageType);
}
if (template.Resistance.Entity.Capture)
ret.resistance.Capture = getEntityValue("Resistance/Entity/Capture");
// ToDo: Resistance against StatusEffects.
}
} }
let getAttackEffects = (temp, path) => { let getAttackEffects = (temp, path) => {

View File

@ -155,37 +155,76 @@ function getCurrentHealthTooltip(entState, label)
} }
/** /**
* Converts an armor level into the actual reduction percentage * Converts an resistance level into the actual reduction percentage.
*/ */
function armorLevelToPercentageString(level) function resistanceLevelToPercentageString(level)
{ {
return sprintf(translate("%(percentage)s%%"), { return sprintf(translate("%(percentage)s%%"), {
"percentage": (100 - Math.round(Math.pow(0.9, level) * 100)) "percentage": (100 - Math.round(Math.pow(0.9, level) * 100))
}); });
} }
function getArmorTooltip(template) function getResistanceTooltip(template)
{ {
if (!template.armour) if (!template.resistance)
return "";
let details = [];
if (template.resistance.Damage)
details.push(getDamageResistanceTooltip(template.resistance.Damage));
if (template.resistance.Capture)
details.push(getCaptureResistanceTooltip(template.resistance.Capture));
// ToDo: Status effects resistance.
return sprintf(translate("%(label)s\n %(details)s"), {
"label": headerFont(translate("Resistance:")),
"details": details.join("\n ")
});
}
function getDamageResistanceTooltip(resistanceTypeTemplate)
{
if (!resistanceTypeTemplate)
return ""; return "";
return sprintf(translate("%(label)s %(details)s"), { return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Armor:")), "label": headerFont(translate("Damage Resistance:")),
"details": "details":
g_DamageTypesMetadata.sort(Object.keys(template.armour)).map( g_DamageTypesMetadata.sort(Object.keys(resistanceTypeTemplate)).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), { dmgType => sprintf(translate("%(damage)s %(damageType)s %(resistancePercentage)s"), {
"damage": template.armour[dmgType].toFixed(1), "damage": resistanceTypeTemplate[dmgType].toFixed(1),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypesMetadata.getName(dmgType))), "damageType": unitFont(translateWithContext("damage type", g_DamageTypesMetadata.getName(dmgType))),
"armorPercentage": "resistancePercentage":
'[font="sans-10"]' + '[font="sans-10"]' +
sprintf(translate("(%(armorPercentage)s)"), { sprintf(translate("(%(resistancePercentage)s)"), {
"armorPercentage": armorLevelToPercentageString(template.armour[dmgType]) "resistancePercentage": resistanceLevelToPercentageString(resistanceTypeTemplate[dmgType])
}) + '[/font]' }) + '[/font]'
}) })
).join(commaFont(translate(", "))) ).join(commaFont(translate(", ")))
}); });
} }
function getCaptureResistanceTooltip(resistanceTypeTemplate)
{
if (!resistanceTypeTemplate)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Capture Resistance:")),
"details":
sprintf(translate("%(damage)s %(damageType)s %(resistancePercentage)s"), {
"damage": resistanceTypeTemplate.toFixed(1),
"damageType": unitFont(translateWithContext("damage type", "Capture")),
"resistancePercentage":
'[font="sans-10"]' +
sprintf(translate("(%(resistancePercentage)s)"), {
"resistancePercentage": resistanceLevelToPercentageString(resistanceTypeTemplate)
}) + '[/font]'
})
});
}
function attackRateDetails(interval, projectiles) function attackRateDetails(interval, projectiles)
{ {
if (!interval) if (!interval)

View File

@ -55,7 +55,7 @@ ReferencePage.prototype.StatsFunctions = [
getHealerTooltip, getHealerTooltip,
getAttackTooltip, getAttackTooltip,
getSplashDamageTooltip, getSplashDamageTooltip,
getArmorTooltip, getResistanceTooltip,
getGarrisonTooltip, getGarrisonTooltip,
getProjectilesTooltip, getProjectilesTooltip,
getSpeedTooltip, getSpeedTooltip,

View File

@ -76,9 +76,9 @@ class TemplateParser
if (!parsed.upgrades) if (!parsed.upgrades)
parsed.upgrades = []; parsed.upgrades = [];
// Note: An assumption is made here that wall segments all have the same armor and auras // Note: An assumption is made here that wall segments all have the same resistance and auras
let struct = this.getEntity(parsed.wallSet.templates.long, civCode); let struct = this.getEntity(parsed.wallSet.templates.long, civCode);
parsed.armour = struct.armour; parsed.resistance = struct.resistance;
parsed.auras = struct.auras; parsed.auras = struct.auras;
// For technology cost multiplier, we need to use the tower // For technology cost multiplier, we need to use the tower

View File

@ -86,7 +86,7 @@ PanelEntity.prototype.PortraitDirectory = "session/portraits/";
PanelEntity.prototype.Tooltips = [ PanelEntity.prototype.Tooltips = [
getCurrentHealthTooltip, getCurrentHealthTooltip,
getAttackTooltip, getAttackTooltip,
getArmorTooltip, getResistanceTooltip,
getEntityTooltip, getEntityTooltip,
getAurasTooltip getAurasTooltip
]; ];

View File

@ -311,11 +311,11 @@ function displaySingle(entState)
showTemplateDetails(entState.template); showTemplateDetails(entState.template);
}; };
Engine.GetGUIObjectByName("attackAndArmorStats").tooltip = [ Engine.GetGUIObjectByName("attackAndResistanceStats").tooltip = [
getAttackTooltip, getAttackTooltip,
getSplashDamageTooltip, getSplashDamageTooltip,
getHealerTooltip, getHealerTooltip,
getArmorTooltip, getResistanceTooltip,
getGatherTooltip, getGatherTooltip,
getSpeedTooltip, getSpeedTooltip,
getGarrisonTooltip, getGarrisonTooltip,

View File

@ -980,7 +980,7 @@ g_SelectionPanels.Training = {
getAttackTooltip, getAttackTooltip,
getSplashDamageTooltip, getSplashDamageTooltip,
getHealerTooltip, getHealerTooltip,
getArmorTooltip, getResistanceTooltip,
getGarrisonTooltip, getGarrisonTooltip,
getProjectilesTooltip, getProjectilesTooltip,
getSpeedTooltip getSpeedTooltip

View File

@ -55,9 +55,9 @@
</object> </object>
<object size="0 63 100% 99" type="image" sprite="edgedPanelShader"> <object size="0 63 100% 99" type="image" sprite="edgedPanelShader">
<!-- Attack and Armor --> <!-- Attack and Resistance -->
<object size="90 -2 126 34" name="attackAndArmorStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip_style="sessionToolTipInstantly"> <object size="90 -2 126 34" name="attackAndResistanceStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip_style="sessionToolTipInstantly">
<translatableAttribute id="tooltip">Attack and Armor</translatableAttribute> <translatableAttribute id="tooltip">Attack and Resistance</translatableAttribute>
</object> </object>
<!-- Resource carrying icon/counter --> <!-- Resource carrying icon/counter -->
@ -94,7 +94,7 @@
</object> </object>
<!-- Names (this must come before the attack and armor icon to avoid clipping issues) --> <!-- Names (this must come before the attack and resistance icon to avoid clipping issues) -->
<object size="2 96 100%-2 100%-36" name="statsArea" type="image" sprite="edgedPanelShader"> <object size="2 96 100%-2 100%-36" name="statsArea" type="image" sprite="edgedPanelShader">
<!-- These images are used to clip off the top and bottom of the civ icon --> <!-- These images are used to clip off the top and bottom of the civ icon -->

View File

@ -196,17 +196,25 @@ m.Template = m.Class({
"getPopulationBonus": function() { return +this.get("Cost/PopulationBonus"); }, "getPopulationBonus": function() { return +this.get("Cost/PopulationBonus"); },
"armourStrengths": function() { "resistanceStrengths": function() {
let armourDamageTypes = this.get("Armour"); let resistanceTypes = this.get("Resistance");
if (!armourDamageTypes) if (!resistanceTypes || !resistanceTypes.Entity)
return undefined; return undefined;
let armour = {}; let resistance = {};
for (let damageType in armourDamageTypes) if (resistanceTypes.Entity.Capture)
if (damageType != "Foundation") resistance.Capture = +this.get("Resistance/Entity/Capture");
armour[damageType] = +armourDamageTypes[damageType];
return armour; if (resistanceTypes.Entity.Damage)
{
resistance.Damage = {};
for (let damageType in resistanceTypes.Entity.Damage)
resistance.Damage[damageType] = +this.get("Resistance/Entity/Damage/" + damageType);
}
// ToDo: Resistance to StatusEffects.
return resistance;
}, },
"attackTypes": function() { "attackTypes": function() {
@ -754,8 +762,8 @@ m.Entity = m.Class({
return false; return false;
let canCapture = allowCapture && this.canCapture(target); let canCapture = allowCapture && this.canCapture(target);
let armourStrengths = target.get("Armour"); let health = target.get("Health");
if (!armourStrengths) if (!health)
return canCapture; return canCapture;
for (let type in attackTypes) for (let type in attackTypes)

View File

@ -59,15 +59,19 @@ PETRA.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstCl
} }
} }
let armourStrength = ent.armourStrengths(); let resistanceStrength = ent.resistanceStrengths();
for (let str in armourStrength)
{ if (resistanceStrength.Damage)
let val = parseFloat(armourStrength[str]); for (let str in resistanceStrength.Damage)
if (DamageTypeImportance[str]) {
strength += DamageTypeImportance[str] * val / damageTypes.length; let val = +resistanceStrength.Damage[str];
else if (debugLevel > 0) if (DamageTypeImportance[str])
API3.warn("Petra: " + str + " unknown armourStrength in getMaxStrength (please add " + str + " to config.js)."); strength += DamageTypeImportance[str] * val / damageTypes.length;
} else if (debugLevel > 0)
API3.warn("Petra: " + str + " unknown resistanceStrength in getMaxStrength (please add " + str + " to config.js).");
}
// ToDo: Add support for StatusEffects and Capture.
return strength * ent.maxHitpoints() / 100.0; return strength * ent.maxHitpoints() / 100.0;
}; };

View File

@ -1,73 +0,0 @@
function Armour() {}
Armour.prototype.DamageResistanceSchema = "" +
"<oneOrMore>" +
"<element a:help='Resistance against any number of damage types'>" +
"<anyName>" +
"<except><name>Foundation</name></except>" +
"</anyName>" +
"<ref name='nonNegativeDecimal' />" +
"</element>" +
"</oneOrMore>";
Armour.prototype.Schema =
"<a:help>Controls the damage resistance of the unit.</a:help>" +
"<a:example>" +
"<Hack>10.0</Hack>" +
"<Pierce>0.0</Pierce>" +
"<Crush>5.0</Crush>" +
"</a:example>" +
Armour.prototype.DamageResistanceSchema +
"<optional>" +
"<element name='Foundation' a:help='Armour given to building foundations'>" +
Armour.prototype.DamageResistanceSchema +
"</element>" +
"</optional>";
Armour.prototype.Init = function()
{
this.invulnerable = false;
};
Armour.prototype.IsInvulnerable = function()
{
return this.invulnerable;
};
Armour.prototype.SetInvulnerability = function(invulnerability)
{
this.invulnerable = invulnerability;
Engine.PostMessage(this.entity, MT_InvulnerabilityChanged, { "entity": this.entity, "invulnerability": invulnerability });
};
Armour.prototype.GetArmourStrengths = function(effectType)
{
// Work out the armour values with technology effects.
let applyMods = (type, foundation) => {
let strength;
if (foundation)
{
strength = +this.template.Foundation[type];
type = "Foundation/" + type;
}
else
strength = +this.template[type];
return ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity);
};
let foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation;
let ret = {};
if (effectType != "Damage")
return ret;
for (let damageType in this.template)
if (damageType != "Foundation")
ret[damageType] = applyMods(damageType, foundation);
return ret;
};
Engine.RegisterComponentType(IID_Resistance, "Armour", Armour);

View File

@ -556,7 +556,7 @@ Attack.prototype.PerformAttack = function(type, target)
cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", +this.template[type].Delay + timeToTarget * 1000, data); cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", +this.template[type].Delay + timeToTarget * 1000, data);
} }
else else
Attacking.HandleAttackEffects(type, this.GetAttackEffectsData(type), target, this.entity, attackerOwner); Attacking.HandleAttackEffects(target, type, this.GetAttackEffectsData(type), this.entity, attackerOwner);
}; };
/** /**

View File

@ -52,28 +52,19 @@ Capturable.prototype.SetCapturePoints = function(capturePointsArray)
/** /**
* Compute the amount of capture points to be reduced and reduce them. * Compute the amount of capture points to be reduced and reduce them.
* @param {number} effectData - Base number of capture points to be taken. * @param {number} amount - Number of capture points to be taken.
* @param {number} captor - The entity capturing us. * @param {number} captor - The entity capturing us.
* @param {number} captorOwner - Owner of the captor. * @param {number} captorOwner - Owner of the captor.
* @param {number} bonusMultiplier - Multiplier to be multiplied with effectData.
* @return {Object} - Object of the form { "captureChange": number }, where number indicates the actual amount of capture points taken. * @return {Object} - Object of the form { "captureChange": number }, where number indicates the actual amount of capture points taken.
*/ */
Capturable.prototype.Capture = function(effectData, captor, captorOwner, bonusMultiplier) Capturable.prototype.Capture = function(amount, captor, captorOwner)
{ {
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); if (captorOwner == INVALID_PLAYER || !this.CanCapture(captorOwner))
let hitpoints = cmpHealth && cmpHealth.GetHitpoints();
if (captorOwner == INVALID_PLAYER || !this.CanCapture(captorOwner) || !hitpoints)
return {}; return {};
bonusMultiplier /= 0.1 + 0.9 * hitpoints / cmpHealth.GetMaxHitpoints();
let total = Attacking.GetTotalAttackEffects({ "Capture": effectData }, "Capture") * bonusMultiplier;
let change = this.Reduce(total, captorOwner);
// TODO: implement loot // TODO: implement loot
return { "captureChange": change }; return { "captureChange": this.Reduce(amount, captorOwner) };
}; };
/** /**

View File

@ -43,9 +43,8 @@ DelayedDamage.prototype.MissileHit = function(data, lateness)
if (cmpSoundManager && data.attackImpactSound) if (cmpSoundManager && data.attackImpactSound)
cmpSoundManager.PlaySoundGroupAtPosition(data.attackImpactSound, data.position); cmpSoundManager.PlaySoundGroupAtPosition(data.attackImpactSound, data.position);
// Do this first in case the direct hit kills the target // Do this first in case the direct hit kills the target.
if (data.splash) if (data.splash)
{
Attacking.CauseDamageOverArea({ Attacking.CauseDamageOverArea({
"type": data.type, "type": data.type,
"attackData": data.splash.attackData, "attackData": data.splash.attackData,
@ -57,17 +56,15 @@ DelayedDamage.prototype.MissileHit = function(data, lateness)
"direction": data.direction, "direction": data.direction,
"friendlyFire": data.splash.friendlyFire "friendlyFire": data.splash.friendlyFire
}); });
}
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
// Deal direct damage if we hit the main target // Deal direct damage if we hit the main target
// and if the target has Resistance (not the case for a mirage for example) // and we could handle the attack.
if (Attacking.TestCollision(data.target, data.position, lateness)) if (Attacking.TestCollision(data.target, data.position, lateness) &&
Attacking.HandleAttackEffects(data.target, data.type, data.attackData, data.attacker, data.attackerOwner))
{ {
cmpProjectileManager.RemoveProjectile(data.projectileId); cmpProjectileManager.RemoveProjectile(data.projectileId);
Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner);
return; return;
} }
@ -81,11 +78,10 @@ DelayedDamage.prototype.MissileHit = function(data, lateness)
for (let ent of ents) for (let ent of ents)
{ {
if (!Attacking.TestCollision(ent, data.position, lateness)) if (!Attacking.TestCollision(ent, data.position, lateness) ||
!Attacking.HandleAttackEffects(ent, data.type, data.attackData, data.attacker, data.attackerOwner))
continue; continue;
Attacking.HandleAttackEffects(data.type, data.attackData, ent, data.attacker, data.attackerOwner);
cmpProjectileManager.RemoveProjectile(data.projectileId); cmpProjectileManager.RemoveProjectile(data.projectileId);
break; break;
} }

View File

@ -454,9 +454,9 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
} }
} }
let cmpArmour = Engine.QueryInterface(ent, IID_Resistance); let cmpResistance = Engine.QueryInterface(ent, IID_Resistance);
if (cmpArmour) if (cmpResistance)
ret.armour = cmpArmour.GetArmourStrengths("Damage"); ret.resistance = cmpResistance.GetResistanceOfForm(cmpFoundation ? "Foundation" : "Entity");
let cmpBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI); let cmpBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI);
if (cmpBuildingAI) if (cmpBuildingAI)

View File

@ -178,22 +178,15 @@ Health.prototype.Kill = function()
}; };
/** /**
* Take damage according to the entity's resistance. * @param {number} amount - The amount of damage to be taken.
* @param {Object} strengths - { "hack": number, "pierce": number, "crush": number } or something like that. * @param {number} attacker - The entityID of the attacker.
* @param {number} bonusMultiplier - the damage multiplier. * @param {number} attackerOwner - The playerID of the owner of the attacker.
* Returns object of the form { "healthChange": -12 }. *
* @eturn {Object} - Object of the form { "healthChange": number }.
*/ */
Health.prototype.TakeDamage = function(effectData, attacker, attackerOwner, bonusMultiplier) Health.prototype.TakeDamage = function(amount, attacker, attackerOwner)
{ {
let cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance); let change = this.Reduce(amount);
if (!this.hitpoints || cmpResistance && cmpResistance.IsInvulnerable())
return { "healthChange": 0 };
let total = Attacking.GetTotalAttackEffects(effectData, "Damage", cmpResistance) * bonusMultiplier;
// Reduce health
let change = this.Reduce(total);
let cmpLoot = Engine.QueryInterface(this.entity, IID_Loot); let cmpLoot = Engine.QueryInterface(this.entity, IID_Loot);
if (cmpLoot && cmpLoot.GetXp() > 0 && change.healthChange < 0) if (cmpLoot && cmpLoot.GetXp() > 0 && change.healthChange < 0)

View File

@ -0,0 +1,128 @@
function Resistance() {}
/**
* Builds a RelaxRNG schema of possible attack effects.
* ToDo: Resistance to StatusEffects.
*
* @return {string} - RelaxNG schema string.
*/
Resistance.prototype.BuildResistanceSchema = function()
{
return "" +
"<oneOrMore>" +
"<choice>" +
"<element name='Damage'>" +
"<oneOrMore>" +
"<element a:help='Resistance against any number of damage types affecting health.'>" +
"<anyName/>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</oneOrMore>" +
"</element>" +
"<element name='Capture' a:help='Resistance against Capture attacks.'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"</choice>" +
"</oneOrMore>";
};
Resistance.prototype.Schema =
"<a:help>Controls the damage resistance of the unit.</a:help>" +
"<a:example>" +
"<Foundation>" +
"<Damage>" +
"<Hack>10.0</Hack>" +
"<Pierce>0.0</Pierce>" +
"<Crush>5.0</Crush>" +
"</Damage>" +
"<Capture>10</Capture>" +
"</Foundation>" +
"<Entity>" +
"<Damage>" +
"<Poison>5</Poison>" +
"</Damage>" +
"</Entity>" +
"</a:example>" +
"<zeroOrMore>" +
"<choice>" +
"<element name='Foundation' a:help='Resistance of an unfinished structure (i.e. a foundation).'>" +
Resistance.prototype.BuildResistanceSchema() +
"</element>" +
"<element name='Entity' a:help='Resistance of an entity.'>" +
Resistance.prototype.BuildResistanceSchema() +
"</element>" +
"</choice>" +
"</zeroOrMore>";
Resistance.prototype.Init = function()
{
this.invulnerable = false;
};
Resistance.prototype.IsInvulnerable = function()
{
return this.invulnerable;
};
Resistance.prototype.SetInvulnerability = function(invulnerability)
{
this.invulnerable = invulnerability;
Engine.PostMessage(this.entity, MT_InvulnerabilityChanged, { "entity": this.entity, "invulnerability": invulnerability });
};
/**
* Calculate the effective resistance of an entity to a particular effect.
* ToDo: Support resistance against status effects.
* @param {string} effectType - The type of attack effect the resistance has to be calculated for (e.g. "Damage", "Capture").
* @return {Object} - An object of the type { "Damage": { "Crush": number, "Hack": number }, "Capture": number }.
*/
Resistance.prototype.GetEffectiveResistanceAgainst = function(effectType)
{
let ret = {};
let template = this.GetResistanceOfForm(Engine.QueryInterface(this.entity, IID_Foundation) ? "Foundation" : "Entity");
if (template[effectType])
ret[effectType] = template[effectType];
return ret;
};
/**
* Get all separate resistances for showing in the GUI.
* @return {Object} - All resistances ordered by type.
*/
Resistance.prototype.GetFullResistance = function()
{
let ret = {};
for (let entityForm in this.template)
ret[entityForm] = this.GetResistanceOfForm(entityForm);
return ret;
};
/**
* Get the resistance of a particular type, i.e. Foundation or Entity.
* @param {string} entityForm - The form of the entity to query.
* @return {Object} - An object containing the resistances.
*/
Resistance.prototype.GetResistanceOfForm = function(entityForm)
{
let ret = {};
let template = this.template[entityForm];
if (!template)
return ret;
if (template.Damage)
{
ret.Damage = {};
for (let damageType in template.Damage)
ret.Damage[damageType] = ApplyValueModificationsToEntity("Resistance/" + entityForm + "/Damage/" + damageType, +this.template[entityForm].Damage[damageType], this.entity);
}
if (template.Capture)
ret.Capture = ApplyValueModificationsToEntity("Resistance/" + entityForm + "/Capture", +this.template[entityForm].Capture, this.entity);
return ret;
};
Engine.RegisterComponentType(IID_Resistance, "Resistance", Resistance);

View File

@ -30,7 +30,7 @@ StatusEffectsReceiver.prototype.GetActiveStatuses = function()
* *
* @return {Object} - The names of the status effects which were processed. * @return {Object} - The names of the status effects which were processed.
*/ */
StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, attackerOwner, bonusMultiplier) StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, attackerOwner)
{ {
let attackerData = { "entity": attacker, "owner": attackerOwner }; let attackerData = { "entity": attacker, "owner": attackerOwner };
for (let effect in effectData) for (let effect in effectData)

View File

@ -1,5 +0,0 @@
/**
* Message of the form { "attacker": number, "target": number, "type": string, "damage": number, "attackerOwner": number }
* sent from Attack component and by Damage component to the target entity, each time the target is attacked or damaged.
*/
Engine.RegisterMessageType("Attacked");

View File

@ -4,3 +4,17 @@ Engine.RegisterInterface("Resistance");
* Message of the form { "entity": entity, "invulnerability": true/false } * Message of the form { "entity": entity, "invulnerability": true/false }
*/ */
Engine.RegisterMessageType("InvulnerabilityChanged"); Engine.RegisterMessageType("InvulnerabilityChanged");
/**
* Message of the form {
* "type": string,
* "target": number,
* "attacker": attacker,
* "attackerOwner": number,
* "damage": number,
* "capture": number,
* "statusEffects": Object[],
* "fromStatusEffect": boolean }
* sent from Attacking.js-helper to the target entity, each time the target is damaged.
*/
Engine.RegisterMessageType("Attacked");

View File

@ -1,7 +1,6 @@
Engine.LoadHelperScript("Attacking.js"); Engine.LoadHelperScript("Attacking.js");
Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js"); Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js"); Engine.LoadComponentScript("interfaces/ModifiersManager.js");

View File

@ -1,17 +1,12 @@
Engine.LoadHelperScript("Attacking.js"); Engine.LoadHelperScript("Attacking.js");
Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Sound.js");
Engine.LoadHelperScript("ValueModification.js"); Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AttackDetection.js");
Engine.LoadComponentScript("interfaces/DelayedDamage.js"); Engine.LoadComponentScript("interfaces/DelayedDamage.js");
Engine.LoadComponentScript("interfaces/Resistance.js");
Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Loot.js"); Engine.LoadComponentScript("interfaces/Loot.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Promotion.js"); Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js"); Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Resistance.js");
Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Attack.js"); Engine.LoadComponentScript("Attack.js");
Engine.LoadComponentScript("DelayedDamage.js"); Engine.LoadComponentScript("DelayedDamage.js");
@ -89,9 +84,9 @@ function Test_Generic()
}); });
AddMock(target, IID_Health, { AddMock(target, IID_Health, {
"TakeDamage": (effectData, __, ___, bonusMultiplier) => { "TakeDamage": (amount, __, ___) => {
damageTaken = true; damageTaken = true;
return { "healthChange": -bonusMultiplier * effectData.Crush }; return { "healthChange": -amount };
}, },
}); });
@ -136,12 +131,12 @@ function Test_Generic()
damageTaken = false; damageTaken = false;
} }
Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner); Attacking.HandleAttackEffects(target, data.type, data.attackData, data.attacker, data.attackerOwner);
TestDamage(); TestDamage();
data.type = "Ranged"; data.type = "Ranged";
type = data.type; type = data.type;
Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner); Attacking.HandleAttackEffects(target, data.type, data.attackData, data.attacker, data.attackerOwner);
TestDamage(); TestDamage();
// Check for damage still being dealt if the attacker dies // Check for damage still being dealt if the attacker dies
@ -214,26 +209,26 @@ function TestLinearSplashDamage()
}); });
AddMock(60, IID_Health, { AddMock(60, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(60); hitEnts.add(60);
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(2.2, -0.4)); TS_ASSERT_EQUALS(amount, 100 * fallOff(2.2, -0.4));
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
AddMock(61, IID_Health, { AddMock(61, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(61); hitEnts.add(61);
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(0, 0)); TS_ASSERT_EQUALS(amount, 100 * fallOff(0, 0));
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
AddMock(62, IID_Health, { AddMock(62, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(62); hitEnts.add(62);
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 0); TS_ASSERT_EQUALS(amount, 0);
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -246,10 +241,10 @@ function TestLinearSplashDamage()
data.direction = new Vector3D(0.6, 747, 0.8); data.direction = new Vector3D(0.6, 747, 0.8);
AddMock(60, IID_Health, { AddMock(60, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(60); hitEnts.add(60);
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(1, 2)); TS_ASSERT_EQUALS(amount, 100 * fallOff(1, 2));
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -310,36 +305,36 @@ function TestCircularSplashDamage()
}); });
AddMock(60, IID_Health, { AddMock(60, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(0)); TS_ASSERT_EQUALS(amount, 100 * fallOff(0));
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
AddMock(61, IID_Health, { AddMock(61, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(5)); TS_ASSERT_EQUALS(amount, 100 * fallOff(5));
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
AddMock(62, IID_Health, { AddMock(62, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(1)); TS_ASSERT_EQUALS(amount, 100 * fallOff(1));
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
AddMock(63, IID_Health, { AddMock(63, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
TS_ASSERT(false); TS_ASSERT(false);
} }
}); });
let cmphealth = AddMock(64, IID_Health, { let cmphealth = AddMock(64, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 0); TS_ASSERT_EQUALS(amount, 0);
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
let spy = new Spy(cmphealth, "TakeDamage"); let spy = new Spy(cmphealth, "TakeDamage");
@ -408,10 +403,10 @@ function Test_MissileHit()
}); });
AddMock(60, IID_Health, { AddMock(60, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(60); hitEnts.add(60);
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100); TS_ASSERT_EQUALS(amount, 100);
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -447,9 +442,9 @@ function Test_MissileHit()
}); });
AddMock(60, IID_Health, { AddMock(60, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(false); TS_ASSERT_EQUALS(false);
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -465,10 +460,10 @@ function Test_MissileHit()
}); });
AddMock(61, IID_Health, { AddMock(61, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(61); hitEnts.add(61);
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100); TS_ASSERT_EQUALS(amount, 100);
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -493,10 +488,10 @@ function Test_MissileHit()
let dealtDamage = 0; let dealtDamage = 0;
AddMock(61, IID_Health, { AddMock(61, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(61); hitEnts.add(61);
dealtDamage += mult * (effectData.Hack + effectData.Pierce + effectData.Crush); dealtDamage += amount;
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -508,10 +503,10 @@ function Test_MissileHit()
}); });
AddMock(62, IID_Health, { AddMock(62, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(62); hitEnts.add(62);
TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 200 * 0.75); TS_ASSERT_EQUALS(amount, 200 * 0.75);
return { "healthChange": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -589,10 +584,10 @@ function Test_MissileHit()
dealtDamage = 0; dealtDamage = 0;
AddMock(61, IID_Health, { AddMock(61, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(61); hitEnts.add(61);
dealtDamage += mult * (effectData.Pierce + effectData.Crush); dealtDamage += amount;
return { "healthChange": -mult * (effectData.Pierce + effectData.Crush) }; return { "healthChange": -amount };
} }
}); });
@ -604,10 +599,10 @@ function Test_MissileHit()
}); });
AddMock(62, IID_Health, { AddMock(62, IID_Health, {
"TakeDamage": (effectData, __, ___, mult) => { "TakeDamage": (amount, __, ___) => {
hitEnts.add(62); hitEnts.add(62);
TS_ASSERT_EQUALS(mult * (effectData.Pierce + effectData.Crush), 200 * 0.75); TS_ASSERT_EQUALS(amount, 200 * 0.75);
return { "healtChange": -mult * (effectData.Pierce + effectData.Crush) }; return { "healtChange": -amount };
} }
}); });

View File

@ -1,6 +1,5 @@
Engine.LoadHelperScript("ObstructionSnap.js"); Engine.LoadHelperScript("ObstructionSnap.js");
Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AlertRaiser.js"); Engine.LoadComponentScript("interfaces/AlertRaiser.js");
Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Barter.js"); Engine.LoadComponentScript("interfaces/Barter.js");

View File

@ -0,0 +1,250 @@
Engine.LoadHelperScript("Attacking.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/Foundation.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Looter.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/PlayerManager.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/Resistance.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("Resistance.js");
class testResistance
{
constructor()
{
this.cmpResistance = null;
this.PlayerID = 1;
this.EnemyID = 2;
this.EntityID = 3;
this.AttackerID = 4;
}
Reset(schema = {})
{
this.cmpResistance = ConstructComponent(this.EntityID, "Resistance", schema);
DeleteMock(this.EntityID, IID_Health);
DeleteMock(this.EntityID, IID_Capturable);
DeleteMock(this.EntityID, IID_Identity);
}
TestInvulnerability()
{
this.Reset();
let damage = 5;
let attackData = { "Damage": { "Name": damage } };
let attackType = "Test";
TS_ASSERT(!this.cmpResistance.IsInvulnerable());
let cmpHealth = AddMock(this.EntityID, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, damage);
return { "healthChange": -amount };
}
});
let spy = new Spy(cmpHealth, "TakeDamage");
Attacking.HandleAttackEffects(this.EntityID, attackType, attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
this.cmpResistance.SetInvulnerability(true);
TS_ASSERT(this.cmpResistance.IsInvulnerable());
Attacking.HandleAttackEffects(this.EntityID, attackType, attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
}
TestBonus()
{
this.Reset();
let damage = 5;
let bonus = 2;
let classes = "Entity";
let attackData = {
"Damage": { "Name": damage },
"Bonuses": {
"bonus": {
"Classes": classes,
"Multiplier": bonus
}
}
};
AddMock(this.EntityID, IID_Identity, {
"GetClassesList": () => [classes],
"GetCiv": () => "civ"
});
let cmpHealth = AddMock(this.EntityID, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, damage * bonus);
return { "healthChange": -amount };
}
});
let spy = new Spy(cmpHealth, "TakeDamage");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
}
TestDamageResistanceApplies()
{
let resistanceValue = 2;
let damageType = "Name";
this.Reset({
"Entity": {
"Damage": {
[damageType]: resistanceValue
}
}
});
let damage = 5;
let attackData = {
"Damage": { "Name": damage }
};
let cmpHealth = AddMock(this.EntityID, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, damage * Math.pow(0.9, resistanceValue));
return { "healthChange": -amount };
}
});
let spy = new Spy(cmpHealth, "TakeDamage");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
}
TestCaptureResistanceApplies()
{
let resistanceValue = 2;
this.Reset({
"Entity": {
"Capture": resistanceValue
}
});
let damage = 5;
let attackData = {
"Capture": damage
};
let cmpCapturable = AddMock(this.EntityID, IID_Capturable, {
"Capture": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, damage * Math.pow(0.9, resistanceValue));
return { "captureChange": amount };
}
});
let spy = new Spy(cmpCapturable, "Capture");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
}
TestResistanceAndBonus()
{
let resistanceValue = 2;
let damageType = "Name";
this.Reset({
"Entity": {
"Damage": {
[damageType]: resistanceValue
}
}
});
let damage = 5;
let bonus = 2;
let classes = "Entity";
let attackData = {
"Damage": { "Name": damage },
"Bonuses": {
"bonus": {
"Classes": classes,
"Multiplier": bonus
}
}
};
AddMock(this.EntityID, IID_Identity, {
"GetClassesList": () => [classes],
"GetCiv": () => "civ"
});
let cmpHealth = AddMock(this.EntityID, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, damage * bonus * Math.pow(0.9, resistanceValue));
return { "healthChange": -amount };
}
});
let spy = new Spy(cmpHealth, "TakeDamage");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(spy._called, 1);
}
TestMultipleEffects()
{
let captureResistanceValue = 2;
this.Reset({
"Entity": {
"Capture": captureResistanceValue
}
});
let damage = 5;
let bonus = 2;
let classes = "Entity";
let attackData = {
"Damage": { "Name": damage },
"Capture": damage,
"Bonuses": {
"bonus": {
"Classes": classes,
"Multiplier": bonus
}
}
};
AddMock(this.EntityID, IID_Identity, {
"GetClassesList": () => [classes],
"GetCiv": () => "civ"
});
let cmpCapturable = AddMock(this.EntityID, IID_Capturable, {
"Capture": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, damage * bonus * Math.pow(0.9, captureResistanceValue));
return { "captureChange": amount };
}
});
let cmpHealth = AddMock(this.EntityID, IID_Health, {
"TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, damage * bonus);
return { "healthChange": -amount };
},
"GetHitpoints": () => 1,
"GetMaxHitpoints": () => 1
});
let healthSpy = new Spy(cmpHealth, "TakeDamage");
let captureSpy = new Spy(cmpCapturable, "Capture");
Attacking.HandleAttackEffects(this.EntityID, "Test", attackData, this.AttackerID, this.EnemyID);
TS_ASSERT_EQUALS(healthSpy._called, 1);
TS_ASSERT_EQUALS(captureSpy._called, 1);
}
}
let cmp = new testResistance();
cmp.TestInvulnerability();
cmp.TestBonus();
cmp.TestDamageResistanceApplies();
cmp.TestCaptureResistanceApplies();
cmp.TestResistanceAndBonus();
cmp.TestMultipleEffects();

View File

@ -2,7 +2,6 @@ Engine.LoadHelperScript("FSM.js");
Engine.LoadHelperScript("Entity.js"); Engine.LoadHelperScript("Entity.js");
Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Sound.js"); Engine.LoadHelperScript("Sound.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Auras.js"); Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Builder.js"); Engine.LoadComponentScript("interfaces/Builder.js");
Engine.LoadComponentScript("interfaces/BuildingAI.js"); Engine.LoadComponentScript("interfaces/BuildingAI.js");

View File

@ -3,9 +3,9 @@
"radius": 70, "radius": 70,
"affects": ["Soldier"], "affects": ["Soldier"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }, { "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Attack/Melee/Damage/Hack", "multiply": 1.1 }, { "value": "Attack/Melee/Damage/Hack", "multiply": 1.1 },
{ "value": "Attack/Melee/Damage/Pierce", "multiply": 1.1 }, { "value": "Attack/Melee/Damage/Pierce", "multiply": 1.1 },
{ "value": "Attack/Melee/Damage/Crush", "multiply": 1.1 }, { "value": "Attack/Melee/Damage/Crush", "multiply": 1.1 },

View File

@ -2,9 +2,9 @@
"type": "garrisonedUnits", "type": "garrisonedUnits",
"affects": ["Soldier"], "affects": ["Soldier"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 3 }, { "value": "Resistance/Entity/Damage/Hack", "add": 3 },
{ "value": "Armour/Pierce", "add": 3 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 3 },
{ "value": "Armour/Crush", "add": 3 }, { "value": "Resistance/Entity/Damage/Crush", "add": 3 },
{ "value": "Vision/Range", "add": 20 } { "value": "Vision/Range", "add": 20 }
], ],
"auraName": "Wall Protection", "auraName": "Wall Protection",

View File

@ -2,9 +2,9 @@
"type": "global", "type": "global",
"affects": ["Melee Cavalry"], "affects": ["Melee Cavalry"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }, { "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Health/Max", "multiply": 1.1 } { "value": "Health/Max", "multiply": 1.1 }
], ],
"auraName": "Commander of Heavy Cavalry", "auraName": "Commander of Heavy Cavalry",

View File

@ -2,9 +2,9 @@
"type": "global", "type": "global",
"affects": ["Human", "Siege"], "affects": ["Human", "Siege"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 } { "value": "Resistance/Entity/Damage/Crush", "add": 1 }
], ],
"auraName": "Founder and Defender of the Republic", "auraName": "Founder and Defender of the Republic",
"auraDescription": "Brutus was one of the key figures in the overthrow of the monarchy and the founding of the Roman Republic. Later, as consul he led a Roman army to victory against the Etruscan King Tarquinius who sought to retake the throne.\nHumans and Siege Engines +1 armor." "auraDescription": "Brutus was one of the key figures in the overthrow of the monarchy and the founding of the Roman Republic. Later, as consul he led a Roman army to victory against the Etruscan King Tarquinius who sought to retake the throne.\nHumans and Siege Engines +1 armor."

View File

@ -2,9 +2,9 @@
"type": "formation", "type": "formation",
"affects": ["Soldier"], "affects": ["Soldier"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 3 }, { "value": "Resistance/Entity/Damage/Hack", "add": 3 },
{ "value": "Armour/Pierce", "add": 3 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 3 },
{ "value": "Armour/Crush", "add": 3 }, { "value": "Resistance/Entity/Damage/Crush", "add": 3 },
{ "value": "UnitMotion/WalkSpeed", "multiply": 1.15 } { "value": "UnitMotion/WalkSpeed", "multiply": 1.15 }
], ],
"auraName": "Formation Reforms", "auraName": "Formation Reforms",

View File

@ -2,9 +2,9 @@
"type": "global", "type": "global",
"affects": ["Soldier", "Siege"], "affects": ["Soldier", "Siege"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }, { "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "UnitMotion/WalkSpeed", "multiply": 1.15 } { "value": "UnitMotion/WalkSpeed", "multiply": 1.15 }
], ],
"auraName": "Guerrilla Chief", "auraName": "Guerrilla Chief",

View File

@ -3,9 +3,9 @@
"radius": 50, "radius": 50,
"affects": ["Soldier"], "affects": ["Soldier"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 } { "value": "Resistance/Entity/Damage/Crush", "add": 1 }
], ],
"auraDescription": "Soldiers +1 armor.", "auraDescription": "Soldiers +1 armor.",
"auraName": "Battle Fervor", "auraName": "Battle Fervor",

View File

@ -3,9 +3,9 @@
"radius": 60, "radius": 60,
"affects": ["Siege"], "affects": ["Siege"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }, { "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Attack/Melee/Damage/Hack", "multiply": 1.2 }, { "value": "Attack/Melee/Damage/Hack", "multiply": 1.2 },
{ "value": "Attack/Melee/Damage/Pierce", "multiply": 1.2 }, { "value": "Attack/Melee/Damage/Pierce", "multiply": 1.2 },
{ "value": "Attack/Melee/Damage/Crush", "multiply": 1.2 }, { "value": "Attack/Melee/Damage/Crush", "multiply": 1.2 },

View File

@ -2,9 +2,9 @@
"type": "global", "type": "global",
"affects": ["Human", "Structure"], "affects": ["Human", "Structure"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 } { "value": "Resistance/Entity/Damage/Crush", "add": 1 }
], ],
"auraName": "Shield of Rome", "auraName": "Shield of Rome",
"auraDescription": "Humans and Structures +1 armor." "auraDescription": "Humans and Structures +1 armor."

View File

@ -3,9 +3,9 @@
"radius": 45, "radius": 45,
"affects": ["Cavalry"], "affects": ["Cavalry"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 }, { "value": "Resistance/Entity/Damage/Hack", "add": 2 },
{ "value": "Armour/Pierce", "add": 2 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 2 },
{ "value": "Armour/Crush", "add": 2 } { "value": "Resistance/Entity/Damage/Crush", "add": 2 }
], ],
"auraName": "Ilarchès", "auraName": "Ilarchès",
"auraDescription": "Cavalry +2 armor." "auraDescription": "Cavalry +2 armor."

View File

@ -3,9 +3,9 @@
"radius": 60, "radius": 60,
"affects": ["Citizen Infantry Javelineer"], "affects": ["Citizen Infantry Javelineer"],
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }, { "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.25 } { "value": "Attack/Ranged/Damage/Pierce", "multiply": 1.25 }
], ],
"auraName": "Helot Reforms", "auraName": "Helot Reforms",

View File

@ -15,8 +15,8 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Cavalry +1 hack and pierce armor.", "tooltip": "Cavalry +1 hack and pierce armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 } { "value": "Resistance/Entity/Damage/Pierce", "add": 1 }
], ],
"affects": ["Cavalry"], "affects": ["Cavalry"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -9,8 +9,8 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Cavalry +1 hack and pierce armor.", "tooltip": "Cavalry +1 hack and pierce armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 } { "value": "Resistance/Entity/Damage/Pierce", "add": 1 }
], ],
"affects": ["Cavalry"], "affects": ["Cavalry"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -16,8 +16,8 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Heroes +50 metal cost, +2 hack and pierce armor.", "tooltip": "Heroes +50 metal cost, +2 hack and pierce armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 }, { "value": "Resistance/Entity/Damage/Hack", "add": 2 },
{ "value": "Armour/Pierce", "add": 2 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 2 },
{ "value": "Cost/Resources/metal", "add": 50 } { "value": "Cost/Resources/metal", "add": 50 }
], ],
"affects": ["Hero"], "affects": ["Hero"],

View File

@ -21,8 +21,8 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Infantry +1 hack and pierce armor.", "tooltip": "Infantry +1 hack and pierce armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 } { "value": "Resistance/Entity/Damage/Pierce", "add": 1 }
], ],
"affects": ["Infantry"], "affects": ["Infantry"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -19,8 +19,8 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Infantry +1 hack and pierce armor.", "tooltip": "Infantry +1 hack and pierce armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 } { "value": "Resistance/Entity/Damage/Pierce", "add": 1 }
], ],
"affects": ["Infantry"], "affects": ["Infantry"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -9,9 +9,9 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Ships +2 armor.", "tooltip": "Ships +2 armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 }, { "value": "Resistance/Entity/Damage/Hack", "add": 2 },
{ "value": "Armour/Pierce", "add": 2 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 2 },
{ "value": "Armour/Crush", "add": 2 } { "value": "Resistance/Entity/Damage/Crush", "add": 2 }
], ],
"affects": ["Ship"], "affects": ["Ship"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -9,9 +9,9 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Ships +2 armor.", "tooltip": "Ships +2 armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 }, { "value": "Resistance/Entity/Damage/Hack", "add": 2 },
{ "value": "Armour/Pierce", "add": 2 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 2 },
{ "value": "Armour/Crush", "add": 2 } { "value": "Resistance/Entity/Damage/Crush", "add": 2 }
], ],
"affects": ["Ship"], "affects": ["Ship"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -8,9 +8,9 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Ships +2 armor.", "tooltip": "Ships +2 armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 }, { "value": "Resistance/Entity/Damage/Hack", "add": 2 },
{ "value": "Armour/Pierce", "add": 2 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 2 },
{ "value": "Armour/Crush", "add": 2 } { "value": "Resistance/Entity/Damage/Crush", "add": 2 }
], ],
"affects": ["Ship"], "affects": ["Ship"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -8,7 +8,7 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Siege Engines +2 hack armor.", "tooltip": "Siege Engines +2 hack armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 } { "value": "Resistance/Entity/Damage/Hack", "add": 2 }
], ],
"affects": ["Siege"], "affects": ["Siege"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -8,9 +8,9 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Towers +2 armor.", "tooltip": "Towers +2 armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 }, { "value": "Resistance/Entity/Damage/Hack", "add": 2 },
{ "value": "Armour/Pierce", "add": 2 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 2 },
{ "value": "Armour/Crush", "add": 2 } { "value": "Resistance/Entity/Damage/Crush", "add": 2 }
], ],
"affects": ["Tower"], "affects": ["Tower"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -9,8 +9,8 @@
"researchTime": 40, "researchTime": 40,
"tooltip": "Traders +2 hack and pierce armor.", "tooltip": "Traders +2 hack and pierce armor.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 2 }, { "value": "Resistance/Entity/Damage/Hack", "add": 2 },
{ "value": "Armour/Pierce", "add": 2 } { "value": "Resistance/Entity/Damage/Pierce", "add": 2 }
], ],
"affects": ["Trader"], "affects": ["Trader"],
"soundComplete": "interface/alarm/alarm_upgradearmory.xml" "soundComplete": "interface/alarm/alarm_upgradearmory.xml"

View File

@ -3,9 +3,9 @@
"icon": "upgrade_advanced.png", "icon": "upgrade_advanced.png",
"tooltip": "Advanced and Elite units +20% training time, +1 armor, +10% health, +0.7 capture attack strength, +20% loot, and −30% gather speed; Healers +5 healing strength and +3 healing range; Melee units +20% attack damage; Ranged units +4 attack range and −10% spread.", "tooltip": "Advanced and Elite units +20% training time, +1 armor, +10% health, +0.7 capture attack strength, +20% loot, and −30% gather speed; Healers +5 healing strength and +3 healing range; Melee units +20% attack damage; Ranged units +4 attack range and −10% spread.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }, { "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Attack/Capture/Capture", "add": 0.7 }, { "value": "Attack/Capture/Capture", "add": 0.7 },
{ "value": "Attack/Melee/Damage/Hack", "multiply": 1.2, "affects": "Melee" }, { "value": "Attack/Melee/Damage/Hack", "multiply": 1.2, "affects": "Melee" },
{ "value": "Attack/Melee/Damage/Pierce", "multiply": 1.2, "affects": "Melee" }, { "value": "Attack/Melee/Damage/Pierce", "multiply": 1.2, "affects": "Melee" },

View File

@ -3,9 +3,9 @@
"icon": "upgrade_elite.png", "icon": "upgrade_elite.png",
"tooltip": "Elite units +20% training time, +1 armor, +10% health, +0.8 capture attack strength, +20% loot, and −30% gather speed; Healers +5 healing strength and +3 healing range; Melee units +20% attack damage; Ranged units +4 attack range and −10% spread.", "tooltip": "Elite units +20% training time, +1 armor, +10% health, +0.8 capture attack strength, +20% loot, and −30% gather speed; Healers +5 healing strength and +3 healing range; Melee units +20% attack damage; Ranged units +4 attack range and −10% spread.",
"modifications": [ "modifications": [
{ "value": "Armour/Hack", "add": 1 }, { "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 }, { "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }, { "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Attack/Capture/Capture", "add": 0.8 }, { "value": "Attack/Capture/Capture", "add": 0.8 },
{ "value": "Attack/Melee/Damage/Hack", "multiply": 1.2, "affects": "Melee" }, { "value": "Attack/Melee/Damage/Hack", "multiply": 1.2, "affects": "Melee" },
{ "value": "Attack/Melee/Damage/Pierce", "multiply": 1.2, "affects": "Melee" }, { "value": "Attack/Melee/Damage/Pierce", "multiply": 1.2, "affects": "Melee" },

View File

@ -7,10 +7,7 @@ const DirectEffectsSchema =
"<element name='Damage'>" + "<element name='Damage'>" +
"<oneOrMore>" + "<oneOrMore>" +
"<element a:help='One or more elements describing damage types'>" + "<element a:help='One or more elements describing damage types'>" +
"<anyName>" + "<anyName/>" +
// Armour requires Foundation to not be a damage type.
"<except><name>Foundation</name></except>" +
"</anyName>" +
"<ref name='nonNegativeDecimal' />" + "<ref name='nonNegativeDecimal' />" +
"</element>" + "</element>" +
"</oneOrMore>" + "</oneOrMore>" +
@ -168,15 +165,41 @@ Attacking.prototype.GetStatusEffectsModifications = function(valueModifRoot, tem
return modifiers; return modifiers;
}; };
Attacking.prototype.GetTotalAttackEffects = function(effectData, effectType, cmpResistance) /**
* Calculate the total effect taking bonus and resistance into account.
*
* @param {number} target - The target of the attack.
* @param {Object} effectData - The effects calculate the effect for.
* @param {string} effectType - The type of effect to apply (e.g. Damage, Capture or StatusEffect).
* @param {number} bonusMultiplier - The factor to multiply the total effect with.
* @param {Object} cmpResistance - Optionally the resistance component of the target.
*
* @return {number} - The total value of the effect.
*/
Attacking.prototype.GetTotalAttackEffects = function(target, effectData, effectType, bonusMultiplier, cmpResistance)
{ {
let total = 0; let total = 0;
let armourStrengths = cmpResistance ? cmpResistance.GetArmourStrengths(effectType) : {}; if (!cmpResistance)
cmpResistance = Engine.QueryInterface(target, IID_Resistance);
for (let type in effectData) let resistanceStrengths = cmpResistance ? cmpResistance.GetEffectiveResistanceAgainst(effectType) : {};
total += effectData[type] * Math.pow(0.9, armourStrengths[type] || 0);
return total; if (effectType == "Damage")
for (let type in effectData.Damage)
total += effectData.Damage[type] * Math.pow(0.9, resistanceStrengths.Damage ? resistanceStrengths.Damage[type] || 0 : 0);
else if (effectType == "Capture")
{
total = effectData.Capture * Math.pow(0.9, resistanceStrengths.Capture || 0);
// If Health is lower we are more susceptible to capture attacks.
let cmpHealth = Engine.QueryInterface(target, IID_Health);
if (cmpHealth)
total /= 0.1 + 0.9 * cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints();
}
else if (effectType == "StatusEffect")
return effectData[effectType];
return total * bonusMultiplier;
}; };
/** /**
@ -302,12 +325,27 @@ Attacking.prototype.CauseDamageOverArea = function(data)
warn("The " + data.shape + " splash damage shape is not implemented!"); warn("The " + data.shape + " splash damage shape is not implemented!");
} }
this.HandleAttackEffects(data.type + ".Splash", data.attackData, ent, data.attacker, data.attackerOwner, damageMultiplier); this.HandleAttackEffects(ent, data.type + ".Splash", data.attackData, data.attacker, data.attackerOwner, damageMultiplier);
} }
}; };
/**
Attacking.prototype.HandleAttackEffects = function(attackType, attackData, target, attacker, attackerOwner, bonusMultiplier = 1) * Handle an attack peformed on an entity.
*
* @param {number} target - The targetted entityID.
* @param {string} attackType - The type of attack that was performed (e.g. "Melee" or "Capture").
* @param {Object} effectData - The effects use.
* @param {number} attacker - The entityID that attacked us.
* @param {number} attackerOwner - The playerID that owned the attacker when the attack was performed.
* @param {number} bonusMultiplier - The factor to multiply the total effect with, defaults to 1.
*
* @return {boolean} - Whether we handled the attack.
*/
Attacking.prototype.HandleAttackEffects = function(target, attackType, attackData, attacker, attackerOwner, bonusMultiplier = 1)
{ {
let cmpResistance = Engine.QueryInterface(target, IID_Resistance);
if (cmpResistance && cmpResistance.IsInvulnerable())
return false;
bonusMultiplier *= !attackData.Bonuses ? 1 : this.GetAttackBonus(attacker, target, attackType, attackData.Bonuses); bonusMultiplier *= !attackData.Bonuses ? 1 : this.GetAttackBonus(attacker, target, attackType, attackData.Bonuses);
let targetState = {}; let targetState = {};
@ -321,10 +359,11 @@ Attacking.prototype.HandleAttackEffects = function(attackType, attackData, targe
if (!cmpReceiver) if (!cmpReceiver)
continue; continue;
Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attacker, attackerOwner, bonusMultiplier)); Object.assign(targetState, cmpReceiver[receiver.method](this.GetTotalAttackEffects(target, attackData, effectType, bonusMultiplier, cmpResistance), attacker, attackerOwner));
} }
if (!Object.keys(targetState).length) if (!Object.keys(targetState).length)
return; return false;
Engine.PostMessage(target, MT_Attacked, { Engine.PostMessage(target, MT_Attacked, {
"type": attackType, "type": attackType,
@ -339,11 +378,13 @@ Attacking.prototype.HandleAttackEffects = function(attackType, attackData, targe
// We do not want an entity to get XP from active Status Effects. // We do not want an entity to get XP from active Status Effects.
if (!!attackData.StatusEffect) if (!!attackData.StatusEffect)
return; return true;
let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion); let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion);
if (cmpPromotion && targetState.xp) if (cmpPromotion && targetState.xp)
cmpPromotion.IncreaseXp(targetState.xp); cmpPromotion.IncreaseXp(targetState.xp);
return true;
}; };
/** /**

View File

@ -1,8 +1,8 @@
Engine.LoadHelperScript("Attacking.js"); Engine.LoadHelperScript("Attacking.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Capturable.js"); Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/Health.js"); Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Promotion.js"); Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/Resistance.js");
// Unit tests for the Attacking helper. // Unit tests for the Attacking helper.
// TODO: Some of it is tested in components/test_Damage.js, which should be spliced and moved. // TODO: Some of it is tested in components/test_Damage.js, which should be spliced and moved.
@ -13,8 +13,8 @@ class testHandleAttackEffects {
this.TESTED_ENTITY_ID = 5; this.TESTED_ENTITY_ID = 5;
this.attackData = { this.attackData = {
"Damage": "Uniquely Hashed Value", "Damage": "1",
"Capture": "Something Else Entirely", "Capture": "2"
}; };
} }
@ -24,13 +24,15 @@ class testHandleAttackEffects {
testMultipleEffects() { testMultipleEffects() {
AddMock(this.TESTED_ENTITY_ID, IID_Health, { AddMock(this.TESTED_ENTITY_ID, IID_Health, {
"TakeDamage": x => { this.resultString += x; }, "TakeDamage": x => { this.resultString += x; },
"GetHitpoints": () => 1,
"GetMaxHitpoints": () => 1,
}); });
AddMock(this.TESTED_ENTITY_ID, IID_Capturable, { AddMock(this.TESTED_ENTITY_ID, IID_Capturable, {
"Capture": x => { this.resultString += x; }, "Capture": x => { this.resultString += x; },
}); });
Attacking.HandleAttackEffects("Test", this.attackData, this.TESTED_ENTITY_ID, INVALID_ENTITY, INVALID_PLAYER); Attacking.HandleAttackEffects(this.TESTED_ENTITY_ID, "Test", this.attackData, INVALID_ENTITY, INVALID_PLAYER);
TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) !== -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) !== -1);
TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) !== -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) !== -1);
@ -44,7 +46,7 @@ class testHandleAttackEffects {
"Capture": x => { this.resultString += x; }, "Capture": x => { this.resultString += x; },
}); });
Attacking.HandleAttackEffects("Test", this.attackData, this.TESTED_ENTITY_ID, INVALID_ENTITY, INVALID_PLAYER); Attacking.HandleAttackEffects(this.TESTED_ENTITY_ID, "Test", this.attackData, INVALID_ENTITY, INVALID_PLAYER);
TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) === -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) === -1);
TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) !== -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) !== -1);
@ -52,9 +54,11 @@ class testHandleAttackEffects {
DeleteMock(this.TESTED_ENTITY_ID, IID_Capturable); DeleteMock(this.TESTED_ENTITY_ID, IID_Capturable);
AddMock(this.TESTED_ENTITY_ID, IID_Health, { AddMock(this.TESTED_ENTITY_ID, IID_Health, {
"TakeDamage": x => { this.resultString += x; }, "TakeDamage": x => { this.resultString += x; },
"GetHitpoints": () => 1,
"GetMaxHitpoints": () => 1,
}); });
Attacking.HandleAttackEffects("Test", this.attackData, this.TESTED_ENTITY_ID, INVALID_ENTITY, INVALID_PLAYER); Attacking.HandleAttackEffects(this.TESTED_ENTITY_ID, "Test", this.attackData, INVALID_ENTITY, INVALID_PLAYER);
TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) !== -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Damage) !== -1);
TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) === -1); TS_ASSERT(this.resultString.indexOf(this.attackData.Capture) === -1);
} }
@ -64,22 +68,24 @@ class testHandleAttackEffects {
*/ */
testAttackedMessage() { testAttackedMessage() {
Engine.PostMessage = () => TS_ASSERT(false); Engine.PostMessage = () => TS_ASSERT(false);
Attacking.HandleAttackEffects("Test", this.attackData, this.TESTED_ENTITY_ID, INVALID_ENTITY, INVALID_PLAYER); Attacking.HandleAttackEffects(this.TESTED_ENTITY_ID, "Test", this.attackData, INVALID_ENTITY, INVALID_PLAYER);
AddMock(this.TESTED_ENTITY_ID, IID_Capturable, { AddMock(this.TESTED_ENTITY_ID, IID_Capturable, {
"Capture": () => ({ "captureChange": 0 }), "Capture": () => ({ "captureChange": 0 }),
}); });
let count = 0; let count = 0;
Engine.PostMessage = () => count++; Engine.PostMessage = () => count++;
Attacking.HandleAttackEffects("Test", this.attackData, this.TESTED_ENTITY_ID, INVALID_ENTITY, INVALID_PLAYER); Attacking.HandleAttackEffects(this.TESTED_ENTITY_ID, "Test", this.attackData, INVALID_ENTITY, INVALID_PLAYER);
TS_ASSERT_EQUALS(count, 1); TS_ASSERT_EQUALS(count, 1);
AddMock(this.TESTED_ENTITY_ID, IID_Health, { AddMock(this.TESTED_ENTITY_ID, IID_Health, {
"TakeDamage": () => ({ "healthChange": 0 }), "TakeDamage": () => ({ "healthChange": 0 }),
"GetHitpoints": () => 1,
"GetMaxHitpoints": () => 1,
}); });
count = 0; count = 0;
Engine.PostMessage = () => count++; Engine.PostMessage = () => count++;
Attacking.HandleAttackEffects("Test", this.attackData, this.TESTED_ENTITY_ID, INVALID_ENTITY, INVALID_PLAYER); Attacking.HandleAttackEffects(this.TESTED_ENTITY_ID, "Test", this.attackData, INVALID_ENTITY, INVALID_PLAYER);
TS_ASSERT_EQUALS(count, 1); TS_ASSERT_EQUALS(count, 1);
} }
@ -88,13 +94,19 @@ class testHandleAttackEffects {
*/ */
testBonusMultiplier() { testBonusMultiplier() {
AddMock(this.TESTED_ENTITY_ID, IID_Health, { AddMock(this.TESTED_ENTITY_ID, IID_Health, {
"TakeDamage": (_, __, ___, mult) => { TS_ASSERT_EQUALS(mult, 2); }, "TakeDamage": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, this.attackData.Damage * 2);
},
"GetHitpoints": () => 1,
"GetMaxHitpoints": () => 1,
}); });
AddMock(this.TESTED_ENTITY_ID, IID_Capturable, { AddMock(this.TESTED_ENTITY_ID, IID_Capturable, {
"Capture": (_, __, ___, mult) => { TS_ASSERT_EQUALS(mult, 2); }, "Capture": (amount, __, ___) => {
TS_ASSERT_EQUALS(amount, this.attackData.Capture * 2);
},
}); });
Attacking.HandleAttackEffects("Test", this.attackData, this.TESTED_ENTITY_ID, INVALID_ENTITY, INVALID_PLAYER, 2); Attacking.HandleAttackEffects(this.TESTED_ENTITY_ID, "Test", this.attackData, INVALID_ENTITY, INVALID_PLAYER, 2);
} }
} }

View File

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_hero_infantry_swordsman"> <Entity parent="template_unit_hero_infantry_swordsman">
<Armour> <Resistance>
<Crush>20</Crush> <Entity>
</Armour> <Damage>
<Crush>20</Crush>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<MaxRange>7.0</MaxRange> <MaxRange>7.0</MaxRange>

View File

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_hero_infantry_swordsman"> <Entity parent="template_unit_hero_infantry_swordsman">
<Armour> <Resistance>
<Crush>20</Crush> <Entity>
</Armour> <Damage>
<Crush>20</Crush>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<MaxRange>7.0</MaxRange> <MaxRange>7.0</MaxRange>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_fauna_hunt_aggressive"> <Entity parent="template_unit_fauna_hunt_aggressive">
<Armour> <Resistance>
<Hack>3</Hack> <Entity>
<Pierce>4</Pierce> <Damage>
<Crush>5</Crush> <Hack>3</Hack>
</Armour> <Pierce>4</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_fauna_hunt_defensive"> <Entity parent="template_unit_fauna_hunt_defensive">
<Armour> <Resistance>
<Hack>3</Hack> <Entity>
<Pierce>4</Pierce> <Damage>
<Crush>5</Crush> <Hack>3</Hack>
</Armour> <Pierce>4</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>1</Crush> <Hack>1</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>2</Hack> <Entity>
<Pierce>5</Pierce> <Damage>
<Crush>2</Crush> <Hack>2</Hack>
</Armour> <Pierce>5</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Fence</Category> <Category>Fence</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>2</Hack> <Entity>
<Pierce>5</Pierce> <Damage>
<Crush>2</Crush> <Hack>2</Hack>
</Armour> <Pierce>5</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>2</Hack> <Entity>
<Pierce>5</Pierce> <Damage>
<Crush>2</Crush> <Hack>2</Hack>
</Armour> <Pierce>5</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>1</Crush> <Hack>1</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>1</Crush> <Hack>1</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>2</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>2</Crush> <Hack>2</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>2</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>2</Crush> <Hack>2</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>2</Hack> <Entity>
<Pierce>5</Pierce> <Damage>
<Crush>2</Crush> <Hack>2</Hack>
</Armour> <Pierce>5</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Fence</Category> <Category>Fence</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>1</Crush> <Hack>1</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>1</Crush> <Hack>1</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity filtered=""> <Entity filtered="">
<AIProxy merge=""/> <AIProxy merge=""/>
<Armour merge=""/> <Resistance merge=""/>
<AutoBuildable merge=""/> <AutoBuildable merge=""/>
<BuildRestrictions merge=""/> <BuildRestrictions merge=""/>
<!-- Don't provide population bonuses yet (but still do take up population cost) --> <!-- Don't provide population bonuses yet (but still do take up population cost) -->

View File

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_special"> <Entity parent="template_structure_special">
<Armour> <Resistance>
<Pierce>35</Pierce> <Entity>
</Armour> <Damage>
<Pierce>35</Pierce>
</Damage>
</Entity>
</Resistance>
<Auras datatype="tokens"> <Auras datatype="tokens">
structures/cart_super_dock_repair structures/cart_super_dock_repair
</Auras> </Auras>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_special"> <Entity parent="template_structure_special">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>25</Pierce> <Damage>
<Crush>2</Crush> <Hack>15</Hack>
<Pierce>25</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>5</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>5</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<Attack> <Attack>
<Ranged> <Ranged>
<Damage> <Damage>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive_wall_gate"> <Entity parent="template_structure_defensive_wall_gate">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>35</Pierce> <Damage>
<Crush>5</Crush> <Hack>15</Hack>
<Pierce>35</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>4</Hack> <Damage>
<Pierce>7</Pierce> <Hack>4</Hack>
<Crush>3</Crush> <Pierce>7</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Territory>own neutral enemy</Territory> <Territory>own neutral enemy</Territory>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive_wall_long"> <Entity parent="template_structure_defensive_wall_long">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>35</Pierce> <Damage>
<Crush>5</Crush> <Hack>15</Hack>
<Pierce>35</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>4</Hack> <Damage>
<Pierce>7</Pierce> <Hack>4</Hack>
<Crush>3</Crush> <Pierce>7</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Territory>own neutral enemy</Territory> <Territory>own neutral enemy</Territory>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive_wall_medium"> <Entity parent="template_structure_defensive_wall_medium">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>35</Pierce> <Damage>
<Crush>5</Crush> <Hack>15</Hack>
<Pierce>35</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>4</Hack> <Damage>
<Pierce>7</Pierce> <Hack>4</Hack>
<Crush>3</Crush> <Pierce>7</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Territory>own neutral enemy</Territory> <Territory>own neutral enemy</Territory>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive_wall_short"> <Entity parent="template_structure_defensive_wall_short">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>35</Pierce> <Damage>
<Crush>5</Crush> <Hack>15</Hack>
<Pierce>35</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>4</Hack> <Damage>
<Pierce>7</Pierce> <Hack>4</Hack>
<Crush>3</Crush> <Pierce>7</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Territory>own neutral enemy</Territory> <Territory>own neutral enemy</Territory>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive_wall_tower"> <Entity parent="template_structure_defensive_wall_tower">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>35</Pierce> <Damage>
<Crush>5</Crush> <Hack>15</Hack>
<Pierce>35</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>4</Hack> <Damage>
<Pierce>7</Pierce> <Hack>4</Hack>
<Crush>3</Crush> <Pierce>7</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Territory>own neutral enemy</Territory> <Territory>own neutral enemy</Territory>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,16 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity> <Entity>
<AIProxy/> <AIProxy/>
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>1</Pierce> <Damage>
<Crush>1</Crush> <Hack>1</Hack>
<Pierce>1</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>1</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>1</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildingAI> <BuildingAI>
<DefaultArrowCount>0</DefaultArrowCount> <DefaultArrowCount>0</DefaultArrowCount>
<GarrisonArrowMultiplier>0</GarrisonArrowMultiplier> <GarrisonArrowMultiplier>0</GarrisonArrowMultiplier>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>20</Hack> <Entity>
<Pierce>30</Pierce> <Damage>
<Crush>3</Crush> <Hack>20</Hack>
<Pierce>30</Pierce>
<Crush>3</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>5</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>5</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<Identity> <Identity>
<Classes datatype="tokens">ConquestCritical</Classes> <Classes datatype="tokens">ConquestCritical</Classes>
<GenericName>Civic Structure</GenericName> <GenericName>Civic Structure</GenericName>

View File

@ -6,15 +6,21 @@
<EndOfAlertRange>190</EndOfAlertRange> <EndOfAlertRange>190</EndOfAlertRange>
<SearchRange>100</SearchRange> <SearchRange>100</SearchRange>
</AlertRaiser> </AlertRaiser>
<Armour> <Resistance>
<Hack op="add">5</Hack> <Entity>
<Pierce op="add">5</Pierce> <Damage>
<Hack op="add">5</Hack>
<Pierce op="add">5</Pierce>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>5</Hack> <Damage>
<Pierce>15</Pierce> <Hack>5</Hack>
<Crush>3</Crush> <Pierce>15</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<Attack> <Attack>
<Ranged> <Ranged>
<Damage> <Damage>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>25</Hack> <Entity>
<Pierce>30</Pierce> <Damage>
<Crush>3</Crush> <Hack>25</Hack>
<Pierce>30</Pierce>
<Crush>3</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>5</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>5</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<Identity> <Identity>
<GenericName>Defensive Structure</GenericName> <GenericName>Defensive Structure</GenericName>
<VisibleClasses datatype="tokens">Defensive</VisibleClasses> <VisibleClasses datatype="tokens">Defensive</VisibleClasses>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive"> <Entity parent="template_structure_defensive">
<Armour> <Resistance>
<Hack>4</Hack> <Entity>
<Pierce>25</Pierce> <Damage>
<Crush>2</Crush> <Hack>4</Hack>
</Armour> <Pierce>25</Pierce>
<Crush>2</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<PlacementType>land-shore</PlacementType> <PlacementType>land-shore</PlacementType>
<Category>Wall</Category> <Category>Wall</Category>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive_tower"> <Entity parent="template_structure_defensive_tower">
<Armour> <Resistance>
<Hack>5</Hack> <Entity>
<Pierce>20</Pierce> <Damage>
<Crush>1</Crush> <Hack>5</Hack>
<Pierce>20</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>5</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>5</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<Attack> <Attack>
<Ranged> <Ranged>
<Damage> <Damage>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_defensive_tower"> <Entity parent="template_structure_defensive_tower">
<Armour> <Resistance>
<Hack op="add">-5</Hack> <Entity>
<Pierce op="add">-5</Pierce> <Damage>
<Crush op="add">-2</Crush> <Hack op="add">-5</Hack>
</Armour> <Pierce op="add">-5</Pierce>
<Crush op="add">-2</Crush>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Ranged> <Ranged>
<Damage> <Damage>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>5</Hack> <Entity>
<Pierce>20</Pierce> <Damage>
<Crush>1</Crush> <Hack>5</Hack>
<Pierce>20</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>5</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>5</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<Identity> <Identity>
<GenericName>Economic Structure</GenericName> <GenericName>Economic Structure</GenericName>
<VisibleClasses datatype="tokens">Economic</VisibleClasses> <VisibleClasses datatype="tokens">Economic</VisibleClasses>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>20</Hack> <Entity>
<Pierce>35</Pierce> <Damage>
<Crush>3</Crush> <Hack>20</Hack>
<Pierce>35</Pierce>
<Crush>3</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>5</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>5</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Military</Category> <Category>Military</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,13 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_military"> <Entity parent="template_structure_military">
<Armour> <Resistance>
<Pierce>30</Pierce> <Entity>
<Damage>
<Pierce>30</Pierce>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>3</Hack> <Damage>
<Pierce>10</Pierce> <Hack>3</Hack>
<Crush>3</Crush> <Pierce>10</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Embassy</Category> <Category>Embassy</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_military"> <Entity parent="template_structure_military">
<Armour> <Resistance>
<Hack op="add">5</Hack> <Entity>
<Pierce op="add">5</Pierce> <Damage>
<Crush op="add">3</Crush> <Hack op="add">5</Hack>
</Armour> <Pierce op="add">5</Pierce>
<Crush op="add">3</Crush>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Ranged> <Ranged>
<Damage> <Damage>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>20</Pierce> <Damage>
<Crush>1</Crush> <Hack>1</Hack>
<Pierce>20</Pierce>
<Crush>1</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>1</Hack> <Damage>
<Pierce>10</Pierce> <Hack>1</Hack>
<Crush>1</Crush> <Pierce>10</Pierce>
<Crush>1</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Resource</Category> <Category>Resource</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure_resource"> <Entity parent="template_structure_resource">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>40</Pierce> <Damage>
<Crush>5</Crush> <Hack>15</Hack>
</Armour> <Pierce>40</Pierce>
<Crush>5</Crush>
</Damage>
</Entity>
</Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Field</Category> <Category>Field</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>20</Hack> <Entity>
<Pierce>30</Pierce> <Damage>
<Crush>3</Crush> <Hack>20</Hack>
<Pierce>30</Pierce>
<Crush>3</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>3</Hack> <Damage>
<Pierce>10</Pierce> <Hack>3</Hack>
<Crush>3</Crush> <Pierce>10</Pierce>
<Crush>3</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<BuildRestrictions> <BuildRestrictions>
<Category>Special</Category> <Category>Special</Category>
</BuildRestrictions> </BuildRestrictions>

View File

@ -1,15 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_structure"> <Entity parent="template_structure">
<Armour> <Resistance>
<Hack>15</Hack> <Entity>
<Pierce>25</Pierce> <Damage>
<Crush>3</Crush> <Hack>15</Hack>
<Pierce>25</Pierce>
<Crush>3</Crush>
</Damage>
</Entity>
<Foundation> <Foundation>
<Hack>2</Hack> <Damage>
<Pierce>10</Pierce> <Hack>2</Hack>
<Crush>2</Crush> <Pierce>10</Pierce>
<Crush>2</Crush>
</Damage>
</Foundation> </Foundation>
</Armour> </Resistance>
<Auras datatype="tokens"> <Auras datatype="tokens">
structures/wonder_pop_1 structures/wonder_pop_1
structures/wonder_pop_2 structures/wonder_pop_2

View File

@ -1,11 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity> <Entity>
<AIProxy/> <AIProxy/>
<Armour> <Resistance>
<Hack>1</Hack> <Entity>
<Pierce>1</Pierce> <Damage>
<Crush>15</Crush> <Hack>1</Hack>
</Armour> <Pierce>1</Pierce>
<Crush>15</Crush>
</Damage>
</Entity>
</Resistance>
<Cost> <Cost>
<Population>1</Population> <Population>1</Population>
<PopulationBonus>0</PopulationBonus> <PopulationBonus>0</PopulationBonus>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit"> <Entity parent="template_unit">
<Armour> <Resistance>
<Hack>3</Hack> <Entity>
<Pierce>1</Pierce> <Damage>
<Crush>15</Crush> <Hack>3</Hack>
</Armour> <Pierce>1</Pierce>
<Crush>15</Crush>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Capture> <Capture>
<Capture>2.5</Capture> <Capture>2.5</Capture>

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_cavalry_melee"> <Entity parent="template_unit_cavalry_melee">
<Armour> <Resistance>
<Hack>3</Hack> <Entity>
<Pierce>2</Pierce> <Damage>
</Armour> <Hack>3</Hack>
<Pierce>2</Pierce>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_cavalry_melee"> <Entity parent="template_unit_cavalry_melee">
<Armour> <Resistance>
<Hack>4</Hack> <Entity>
<Pierce>3</Pierce> <Damage>
</Armour> <Hack>4</Hack>
<Pierce>3</Pierce>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_cavalry_melee"> <Entity parent="template_unit_cavalry_melee">
<Armour> <Resistance>
<Hack>4</Hack> <Entity>
<Pierce>2</Pierce> <Damage>
</Armour> <Hack>4</Hack>
<Pierce>2</Pierce>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion"> <Entity parent="template_unit_champion">
<Armour> <Resistance>
<Hack>7</Hack> <Entity>
<Pierce>5</Pierce> <Damage>
<Crush>20</Crush> <Hack>7</Hack>
</Armour> <Pierce>5</Pierce>
<Crush>20</Crush>
</Damage>
</Entity>
</Resistance>
<Cost> <Cost>
<Population>1</Population> <Population>1</Population>
<BuildTime>30</BuildTime> <BuildTime>30</BuildTime>

View File

@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_cavalry"> <Entity parent="template_unit_champion_cavalry">
<Armour> <Resistance>
<Pierce op="add">2</Pierce> <Entity>
</Armour> <Damage>
<Pierce op="add">2</Pierce>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_cavalry"> <Entity parent="template_unit_champion_cavalry">
<Armour> <Resistance>
<Hack op="add">1</Hack> <Entity>
<Pierce op="add">2</Pierce> <Damage>
</Armour> <Hack op="add">1</Hack>
<Pierce op="add">2</Pierce>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion_cavalry"> <Entity parent="template_unit_champion_cavalry">
<Armour> <Resistance>
<Hack op="add">1</Hack> <Entity>
<Pierce op="add">2</Pierce> <Damage>
</Armour> <Hack op="add">1</Hack>
<Pierce op="add">2</Pierce>
</Damage>
</Entity>
</Resistance>
<Attack> <Attack>
<Melee> <Melee>
<Damage> <Damage>

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Entity parent="template_unit_champion"> <Entity parent="template_unit_champion">
<Armour> <Resistance>
<Hack>10</Hack> <Entity>
<Pierce>10</Pierce> <Damage>
<Crush>25</Crush> <Hack>10</Hack>
</Armour> <Pierce>10</Pierce>
<Crush>25</Crush>
</Damage>
</Entity>
</Resistance>
<Cost> <Cost>
<Population>3</Population> <Population>3</Population>
<BuildTime>30</BuildTime> <BuildTime>30</BuildTime>

Some files were not shown because too many files have changed in this diff Show More