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

View File

@ -160,12 +160,23 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
let ret = {};
if (template.Armour)
if (template.Resistance)
{
ret.armour = {};
for (let damageType in template.Armour)
if (damageType != "Foundation")
ret.armour[damageType] = getEntityValue("Armour/" + damageType);
// Don't show Foundation resistance.
ret.resistance = {};
if (template.Resistance.Entity)
{
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) => {

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%%"), {
"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 sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Armor:")),
"label": headerFont(translate("Damage Resistance:")),
"details":
g_DamageTypesMetadata.sort(Object.keys(template.armour)).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s %(armorPercentage)s"), {
"damage": template.armour[dmgType].toFixed(1),
g_DamageTypesMetadata.sort(Object.keys(resistanceTypeTemplate)).map(
dmgType => sprintf(translate("%(damage)s %(damageType)s %(resistancePercentage)s"), {
"damage": resistanceTypeTemplate[dmgType].toFixed(1),
"damageType": unitFont(translateWithContext("damage type", g_DamageTypesMetadata.getName(dmgType))),
"armorPercentage":
"resistancePercentage":
'[font="sans-10"]' +
sprintf(translate("(%(armorPercentage)s)"), {
"armorPercentage": armorLevelToPercentageString(template.armour[dmgType])
sprintf(translate("(%(resistancePercentage)s)"), {
"resistancePercentage": resistanceLevelToPercentageString(resistanceTypeTemplate[dmgType])
}) + '[/font]'
})
).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)
{
if (!interval)

View File

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

View File

@ -76,9 +76,9 @@ class TemplateParser
if (!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);
parsed.armour = struct.armour;
parsed.resistance = struct.resistance;
parsed.auras = struct.auras;
// For technology cost multiplier, we need to use the tower

View File

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

View File

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

View File

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

View File

@ -55,9 +55,9 @@
</object>
<object size="0 63 100% 99" type="image" sprite="edgedPanelShader">
<!-- Attack and Armor -->
<object size="90 -2 126 34" name="attackAndArmorStats" type="image" sprite="stretched:session/icons/stances/defensive.png" tooltip_style="sessionToolTipInstantly">
<translatableAttribute id="tooltip">Attack and Armor</translatableAttribute>
<!-- Attack and Resistance -->
<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 Resistance</translatableAttribute>
</object>
<!-- Resource carrying icon/counter -->
@ -94,7 +94,7 @@
</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">
<!-- 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"); },
"armourStrengths": function() {
let armourDamageTypes = this.get("Armour");
if (!armourDamageTypes)
"resistanceStrengths": function() {
let resistanceTypes = this.get("Resistance");
if (!resistanceTypes || !resistanceTypes.Entity)
return undefined;
let armour = {};
for (let damageType in armourDamageTypes)
if (damageType != "Foundation")
armour[damageType] = +armourDamageTypes[damageType];
let resistance = {};
if (resistanceTypes.Entity.Capture)
resistance.Capture = +this.get("Resistance/Entity/Capture");
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() {
@ -754,8 +762,8 @@ m.Entity = m.Class({
return false;
let canCapture = allowCapture && this.canCapture(target);
let armourStrengths = target.get("Armour");
if (!armourStrengths)
let health = target.get("Health");
if (!health)
return canCapture;
for (let type in attackTypes)

View File

@ -59,15 +59,19 @@ PETRA.getMaxStrength = function(ent, debugLevel, DamageTypeImportance, againstCl
}
}
let armourStrength = ent.armourStrengths();
for (let str in armourStrength)
{
let val = parseFloat(armourStrength[str]);
if (DamageTypeImportance[str])
strength += DamageTypeImportance[str] * val / damageTypes.length;
else if (debugLevel > 0)
API3.warn("Petra: " + str + " unknown armourStrength in getMaxStrength (please add " + str + " to config.js).");
}
let resistanceStrength = ent.resistanceStrengths();
if (resistanceStrength.Damage)
for (let str in resistanceStrength.Damage)
{
let val = +resistanceStrength.Damage[str];
if (DamageTypeImportance[str])
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;
};

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);
}
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.
* @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} 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.
*/
Capturable.prototype.Capture = function(effectData, captor, captorOwner, bonusMultiplier)
Capturable.prototype.Capture = function(amount, captor, captorOwner)
{
let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
let hitpoints = cmpHealth && cmpHealth.GetHitpoints();
if (captorOwner == INVALID_PLAYER || !this.CanCapture(captorOwner) || !hitpoints)
if (captorOwner == INVALID_PLAYER || !this.CanCapture(captorOwner))
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
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)
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)
{
Attacking.CauseDamageOverArea({
"type": data.type,
"attackData": data.splash.attackData,
@ -57,17 +56,15 @@ DelayedDamage.prototype.MissileHit = function(data, lateness)
"direction": data.direction,
"friendlyFire": data.splash.friendlyFire
});
}
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
// Deal direct damage if we hit the main target
// and if the target has Resistance (not the case for a mirage for example)
if (Attacking.TestCollision(data.target, data.position, lateness))
// and we could handle the attack.
if (Attacking.TestCollision(data.target, data.position, lateness) &&
Attacking.HandleAttackEffects(data.target, data.type, data.attackData, data.attacker, data.attackerOwner))
{
cmpProjectileManager.RemoveProjectile(data.projectileId);
Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner);
return;
}
@ -81,11 +78,10 @@ DelayedDamage.prototype.MissileHit = function(data, lateness)
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;
Attacking.HandleAttackEffects(data.type, data.attackData, ent, data.attacker, data.attackerOwner);
cmpProjectileManager.RemoveProjectile(data.projectileId);
break;
}

View File

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

View File

@ -178,22 +178,15 @@ Health.prototype.Kill = function()
};
/**
* Take damage according to the entity's resistance.
* @param {Object} strengths - { "hack": number, "pierce": number, "crush": number } or something like that.
* @param {number} bonusMultiplier - the damage multiplier.
* Returns object of the form { "healthChange": -12 }.
* @param {number} amount - The amount of damage to be taken.
* @param {number} attacker - The entityID of the attacker.
* @param {number} attackerOwner - The playerID of the owner of the attacker.
*
* @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);
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 change = this.Reduce(amount);
let cmpLoot = Engine.QueryInterface(this.entity, IID_Loot);
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.
*/
StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, attackerOwner, bonusMultiplier)
StatusEffectsReceiver.prototype.ApplyStatus = function(effectData, attacker, attackerOwner)
{
let attackerData = { "entity": attacker, "owner": attackerOwner };
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 }
*/
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("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");

View File

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

View File

@ -1,6 +1,5 @@
Engine.LoadHelperScript("ObstructionSnap.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AlertRaiser.js");
Engine.LoadComponentScript("interfaces/Auras.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("Player.js");
Engine.LoadHelperScript("Sound.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Builder.js");
Engine.LoadComponentScript("interfaces/BuildingAI.js");

View File

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

View File

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

View File

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

View File

@ -2,9 +2,9 @@
"type": "global",
"affects": ["Human", "Siege"],
"modifications": [
{ "value": "Armour/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 }
{ "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Resistance/Entity/Damage/Crush", "add": 1 }
],
"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."

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@
"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.",
"modifications": [
{ "value": "Armour/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 },
{ "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Attack/Capture/Capture", "add": 0.7 },
{ "value": "Attack/Melee/Damage/Hack", "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",
"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": [
{ "value": "Armour/Hack", "add": 1 },
{ "value": "Armour/Pierce", "add": 1 },
{ "value": "Armour/Crush", "add": 1 },
{ "value": "Resistance/Entity/Damage/Hack", "add": 1 },
{ "value": "Resistance/Entity/Damage/Pierce", "add": 1 },
{ "value": "Resistance/Entity/Damage/Crush", "add": 1 },
{ "value": "Attack/Capture/Capture", "add": 0.8 },
{ "value": "Attack/Melee/Damage/Hack", "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'>" +
"<oneOrMore>" +
"<element a:help='One or more elements describing damage types'>" +
"<anyName>" +
// Armour requires Foundation to not be a damage type.
"<except><name>Foundation</name></except>" +
"</anyName>" +
"<anyName/>" +
"<ref name='nonNegativeDecimal' />" +
"</element>" +
"</oneOrMore>" +
@ -168,15 +165,41 @@ Attacking.prototype.GetStatusEffectsModifications = function(valueModifRoot, tem
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 armourStrengths = cmpResistance ? cmpResistance.GetArmourStrengths(effectType) : {};
if (!cmpResistance)
cmpResistance = Engine.QueryInterface(target, IID_Resistance);
for (let type in effectData)
total += effectData[type] * Math.pow(0.9, armourStrengths[type] || 0);
let resistanceStrengths = cmpResistance ? cmpResistance.GetEffectiveResistanceAgainst(effectType) : {};
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!");
}
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);
let targetState = {};
@ -321,10 +359,11 @@ Attacking.prototype.HandleAttackEffects = function(attackType, attackData, targe
if (!cmpReceiver)
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)
return;
return false;
Engine.PostMessage(target, MT_Attacked, {
"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.
if (!!attackData.StatusEffect)
return;
return true;
let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion);
if (cmpPromotion && targetState.xp)
cmpPromotion.IncreaseXp(targetState.xp);
return true;
};
/**

View File

@ -1,8 +1,8 @@
Engine.LoadHelperScript("Attacking.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/Resistance.js");
// Unit tests for the Attacking helper.
// 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.attackData = {
"Damage": "Uniquely Hashed Value",
"Capture": "Something Else Entirely",
"Damage": "1",
"Capture": "2"
};
}
@ -24,13 +24,15 @@ class testHandleAttackEffects {
testMultipleEffects() {
AddMock(this.TESTED_ENTITY_ID, IID_Health, {
"TakeDamage": x => { this.resultString += x; },
"GetHitpoints": () => 1,
"GetMaxHitpoints": () => 1,
});
AddMock(this.TESTED_ENTITY_ID, IID_Capturable, {
"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.Capture) !== -1);
@ -44,7 +46,7 @@ class testHandleAttackEffects {
"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.Capture) !== -1);
@ -52,9 +54,11 @@ class testHandleAttackEffects {
DeleteMock(this.TESTED_ENTITY_ID, IID_Capturable);
AddMock(this.TESTED_ENTITY_ID, IID_Health, {
"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.Capture) === -1);
}
@ -64,22 +68,24 @@ class testHandleAttackEffects {
*/
testAttackedMessage() {
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, {
"Capture": () => ({ "captureChange": 0 }),
});
let count = 0;
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);
AddMock(this.TESTED_ENTITY_ID, IID_Health, {
"TakeDamage": () => ({ "healthChange": 0 }),
"GetHitpoints": () => 1,
"GetMaxHitpoints": () => 1,
});
count = 0;
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);
}
@ -88,13 +94,19 @@ class testHandleAttackEffects {
*/
testBonusMultiplier() {
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, {
"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"?>
<Entity parent="template_unit_hero_infantry_swordsman">
<Armour>
<Crush>20</Crush>
</Armour>
<Resistance>
<Entity>
<Damage>
<Crush>20</Crush>
</Damage>
</Entity>
</Resistance>
<Attack>
<Melee>
<MaxRange>7.0</MaxRange>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity filtered="">
<AIProxy merge=""/>
<Armour merge=""/>
<Resistance merge=""/>
<AutoBuildable merge=""/>
<BuildRestrictions merge=""/>
<!-- 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"?>
<Entity parent="template_structure_special">
<Armour>
<Pierce>35</Pierce>
</Armour>
<Resistance>
<Entity>
<Damage>
<Pierce>35</Pierce>
</Damage>
</Entity>
</Resistance>
<Auras datatype="tokens">
structures/cart_super_dock_repair
</Auras>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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