Index: binaries/data/mods/public/globalscripts/AttackEffects.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/globalscripts/AttackEffects.js
@@ -0,0 +1,16 @@
+// TODO: could be worth putting this in json files someday
+const g_EffectTypes = ["Damage", "Capture", "GiveStatus"];
+const g_EffectReceiver = {
+ "Damage": {
+ "IID": "IID_Health",
+ "method": "TakeDamage"
+ },
+ "Capture": {
+ "IID": "IID_Capturable",
+ "method": "Capture"
+ },
+ "GiveStatus": {
+ "IID": "IID_StatusEffectsReceiver",
+ "method": "GiveStatus"
+ }
+};
Index: binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- binaries/data/mods/public/globalscripts/Templates.js
+++ binaries/data/mods/public/globalscripts/Templates.js
@@ -170,6 +170,22 @@
ret.armour[damageType] = getEntityValue("Armour/" + damageType);
}
+ let getAttackEffects = (temp, path) => {
+ let effects = {};
+ if (temp.Capture)
+ effects.Capture = getEntityValue(path + "/Capture");
+
+ if (temp.Damage)
+ {
+ effects.Damage = {};
+ for (let damageType in temp.Damage)
+ effects.Damage[damageType] = getEntityValue(path + "/Damage/" + damageType);
+ }
+
+ // TODO: status effects
+ return effects;
+ };
+
if (template.Attack)
{
ret.attack = {};
@@ -179,36 +195,27 @@
return getEntityValue("Attack/" + type + "/" + stat);
};
- if (type == "Capture")
- ret.attack.Capture = {
- "value": getAttackStat("Value")
- };
- else
- {
- ret.attack[type] = {
- "minRange": getAttackStat("MinRange"),
- "maxRange": getAttackStat("MaxRange"),
- "elevationBonus": getAttackStat("ElevationBonus"),
- "damage": {}
- };
- for (let damageType in template.Attack[type].Damage)
- ret.attack[type].damage[damageType] = getAttackStat("Damage/" + damageType);
+ ret.attack[type] = {
+ "minRange": getAttackStat("MinRange"),
+ "maxRange": getAttackStat("MaxRange"),
+ "elevationBonus": getAttackStat("ElevationBonus"),
+ };
+
+ ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
+ (2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange));
- ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
- (2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange));
- }
ret.attack[type].repeatTime = getAttackStat("RepeatTime");
+ Object.assign(ret.attack[type], getAttackEffects(template.Attack[type], "Attack/" + type));
+
if (template.Attack[type].Splash)
{
ret.attack[type].splash = {
// true if undefined
"friendlyFire": template.Attack[type].Splash.FriendlyFire != "false",
"shape": template.Attack[type].Splash.Shape,
- "damage": {}
};
- for (let damageType in template.Attack[type].Splash.Damage)
- ret.attack[type].splash.damage[damageType] = getAttackStat("Splash/Damage/" + damageType);
+ Object.assign(ret.attack[type].splash, getAttackEffects(template.Attack[type].Splash, "Attack/" + type + "/Splash"));
}
}
}
@@ -217,10 +224,9 @@
{
ret.deathDamage = {
"friendlyFire": template.DeathDamage.FriendlyFire != "false",
- "damage": {}
};
- for (let damageType in template.DeathDamage.Damage)
- ret.deathDamage.damage[damageType] = getEntityValue("DeathDamage/Damage/" + damageType);
+
+ Object.assign(ret.deathDamage, getAttackEffects(template.DeathDamage, "DeathDamage"));
}
if (template.Auras && auraTemplates)
Index: binaries/data/mods/public/gui/common/tooltips.js
===================================================================
--- binaries/data/mods/public/gui/common/tooltips.js
+++ binaries/data/mods/public/gui/common/tooltips.js
@@ -270,8 +270,8 @@
"attackLabel": attackLabel,
"details":
type == "Capture" ?
- template.attack.Capture.value :
- damageTypesToText(template.attack[type].damage),
+ template.attack.Capture.Capture :
+ damageTypesToText(template.attack[type].Damage),
"rate": rate
}));
continue;
@@ -283,7 +283,7 @@
let relativeRange = realRange ? Math.round(realRange - maxRange) : 0;
tooltips.push(sprintf(g_RangeTooltipString[relativeRange ? "relative" : "non-relative"][minRange ? "minRange" : "no-minRange"], {
"attackLabel": attackLabel,
- "damageTypes": damageTypesToText(template.attack[type].damage),
+ "damageTypes": damageTypesToText(template.attack[type].Damage),
"rangeLabel": headerFont(translate("Range:")),
"minRange": minRange,
"maxRange": maxRange,
@@ -313,7 +313,7 @@
let splashDamageTooltip = sprintf(translate("%(label)s: %(value)s"), {
"label": headerFont(g_SplashDamageTypes[splash.shape]),
- "value": damageTypesToText(splash.damage)
+ "value": damageTypesToText(splash.Damage)
});
if (g_AlwaysDisplayFriendlyFire || splash.friendlyFire)
Index: binaries/data/mods/public/maps/random/polar_sea_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/polar_sea_triggers.js
+++ binaries/data/mods/public/maps/random/polar_sea_triggers.js
@@ -37,7 +37,6 @@
let allTargets;
- let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
let players = new Array(TriggerHelper.GetNumberOfPlayers()).fill(0).map((v, i) => i + 1);
for (let spawnPoint in attackers)
@@ -52,7 +51,7 @@
continue;
// The returned entities are sorted by RangeManager already
- let targets = cmpDamage.EntitiesNearPoint(attackerPos, 200, players).filter(ent => {
+ let targets = Attack.EntitiesNearPoint(attackerPos, 200, players).filter(ent => {
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), targetClasses);
});
Index: binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
===================================================================
--- binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
+++ binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
@@ -152,7 +152,7 @@
{
this.playerCivicCenter[playerID] = TriggerHelper.GetPlayerEntitiesByClass(playerID, "CivilCentre")[0];
this.treasureFemale[playerID] = TriggerHelper.GetPlayerEntitiesByClass(playerID, "FemaleCitizen")[0];
- Engine.QueryInterface(this.treasureFemale[playerID], IID_DamageReceiver).SetInvulnerability(true);
+ Engine.QueryInterface(this.treasureFemale[playerID], IID_Resistance).SetInvulnerability(true);
}
};
Index: binaries/data/mods/public/maps/scripts/CaptureTheRelic.js
===================================================================
--- binaries/data/mods/public/maps/scripts/CaptureTheRelic.js
+++ binaries/data/mods/public/maps/scripts/CaptureTheRelic.js
@@ -20,8 +20,8 @@
{
this.relics[i] = TriggerHelper.SpawnUnits(pickRandom(potentialSpawnPoints), catafalqueTemplates[i], 1, 0)[0];
- let cmpDamageReceiver = Engine.QueryInterface(this.relics[i], IID_DamageReceiver);
- cmpDamageReceiver.SetInvulnerability(true);
+ let cmpResistance = Engine.QueryInterface(this.relics[i], IID_Resistance);
+ cmpResistance.SetInvulnerability(true);
let cmpPositionRelic = Engine.QueryInterface(this.relics[i], IID_Position);
cmpPositionRelic.SetYRotation(randomAngle());
Index: binaries/data/mods/public/simulation/ai/common-api/entity.js
===================================================================
--- binaries/data/mods/public/simulation/ai/common-api/entity.js
+++ binaries/data/mods/public/simulation/ai/common-api/entity.js
@@ -246,7 +246,7 @@
if (!this.get("Attack/Capture"))
return undefined;
- return +this.get("Attack/Capture/Value") || 0;
+ return +this.get("Attack/Capture/Capture") || 0;
},
"attackTimes": function(type) {
Index: binaries/data/mods/public/simulation/components/AIProxy.js
===================================================================
--- binaries/data/mods/public/simulation/components/AIProxy.js
+++ binaries/data/mods/public/simulation/components/AIProxy.js
@@ -260,9 +260,9 @@
ret.hitpoints = cmpHealth.GetHitpoints();
}
- let cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver);
- if (cmpDamageReceiver)
- ret.invulnerability = cmpDamageReceiver.IsInvulnerable();
+ let cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance);
+ if (cmpResistance)
+ ret.invulnerability = cmpResistance.IsInvulnerable();
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (cmpOwnership)
Index: binaries/data/mods/public/simulation/components/Armour.js
===================================================================
--- binaries/data/mods/public/simulation/components/Armour.js
+++ binaries/data/mods/public/simulation/components/Armour.js
@@ -1,5 +1,15 @@
function Armour() {}
+Armour.prototype.DamageResistanceSchema = "" +
+ "" +
+ "" +
+ "" +
+ "Foundation" +
+ "" +
+ "" +
+ "" +
+ "";
+
Armour.prototype.Schema =
"Controls the damage resistance of the unit." +
"" +
@@ -7,12 +17,10 @@
"0.0" +
"5.0" +
"" +
- BuildDamageTypesSchema("damage protection") +
+ Armour.prototype.DamageResistanceSchema +
"" +
"" +
- "" +
- BuildDamageTypesSchema("damage protection") +
- "" +
+ Armour.prototype.DamageResistanceSchema +
"" +
"";
@@ -32,32 +40,7 @@
Engine.PostMessage(this.entity, MT_InvulnerabilityChanged, { "entity": this.entity, "invulnerability": invulnerability });
};
-/**
- * Take damage according to the entity's armor.
- * @param {Object} strengths - { "hack": number, "pierce": number, "crush": number } or something like that.
- * @param {number} multiplier - the damage multiplier.
- * Returns object of the form { "killed": false, "change": -12 }.
- */
-Armour.prototype.TakeDamage = function(strengths, multiplier = 1)
-{
- if (this.invulnerable)
- return { "killed": false, "change": 0 };
-
- // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour
- var armourStrengths = this.GetArmourStrengths();
-
- // Total is sum of individual damages
- // Don't bother rounding, since HP is no longer integral.
- var total = 0;
- for (let type in strengths)
- total += strengths[type] * multiplier * Math.pow(0.9, armourStrengths[type] || 0);
-
- // Reduce health
- var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
- return cmpHealth.Reduce(total);
-};
-
-Armour.prototype.GetArmourStrengths = function()
+Armour.prototype.GetArmourStrengths = function(effectType)
{
// Work out the armour values with technology effects.
let applyMods = (type, foundation) => {
@@ -70,12 +53,16 @@
else
strength = +this.template[type];
- return ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity);
+ return ApplyValueModificationsToEntity("Armour/" + effectType + "/" + 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);
@@ -83,4 +70,4 @@
return ret;
};
-Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour);
+Engine.RegisterComponentType(IID_Resistance, "Armour", Armour);
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js
+++ binaries/data/mods/public/simulation/components/Attack.js
@@ -2,40 +2,6 @@
var g_AttackTypes = ["Melee", "Ranged", "Capture"];
-Attack.prototype.statusEffectsSchema =
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "";
-
-Attack.prototype.bonusesSchema =
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "";
-
Attack.prototype.preferredClassesSchema =
"" +
"" +
@@ -130,9 +96,7 @@
"" +
"" +
"" +
- "" +
- BuildDamageTypesSchema("damage strength") +
- "" +
+ Attacking.BuildAttackEffectsSchema() +
"" +
"" +
"" +
@@ -140,7 +104,6 @@
"" + // TODO: it shouldn't be stretched
"" +
"" +
- Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
@@ -149,9 +112,7 @@
"" +
"" +
"" +
- "" +
- BuildDamageTypesSchema("damage strength") +
- "" +
+ Attacking.BuildAttackEffectsSchema() +
"" +
"" +
""+
@@ -179,10 +140,7 @@
"" +
"" +
"" +
- "" +
- BuildDamageTypesSchema("damage strength") +
- "" +
- Attack.prototype.bonusesSchema +
+ Attacking.BuildAttackEffectsSchema() +
"" +
"" +
"" +
@@ -217,8 +175,6 @@
"" +
"" +
"" +
- Attack.prototype.statusEffectsSchema +
- Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
@@ -227,12 +183,11 @@
"" +
"" +
"" +
- "" +
+ Attacking.BuildAttackEffectsSchema() +
"" +
"" + // TODO: it shouldn't be stretched
"" +
"" +
- Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
@@ -241,11 +196,8 @@
"" +
"" +
"" +
- "" +
- BuildDamageTypesSchema("damage strength") +
- "" +
+ Attacking.BuildAttackEffectsSchema() +
"" + // TODO: how do these work?
- Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
@@ -387,6 +339,14 @@
return ret;
};
+Attack.prototype.GetAttackEffectsData = function(type, splash)
+{
+ let tp = this.template[type];
+ if (splash)
+ tp = tp.Splash;
+ return Attacking.GetAttackEffectsData("Attack/" + type + splash ? "/Splash" : "", tp, this.entity);
+};
+
Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
@@ -448,40 +408,16 @@
return { "prepare": prepare, "repeat": repeat };
};
-Attack.prototype.GetAttackStrengths = function(type)
-{
- // Work out the attack values with technology effects
- let template = this.template[type];
- let splash = "";
- if (!template)
- {
- template = this.template[type.split(".")[0]].Splash;
- splash = "/Splash";
- }
-
- let applyMods = damageType =>
- ApplyValueModificationsToEntity("Attack/" + type + splash + "/Damage/" + damageType, +(template.Damage[damageType] || 0), this.entity);
-
- if (type == "Capture")
- return { "value": ApplyValueModificationsToEntity("Attack/Capture/Value", +(template.Value || 0), this.entity) };
-
- let ret = {};
- for (let damageType in template.Damage)
- ret[damageType] = applyMods(damageType);
-
- return ret;
-};
-
Attack.prototype.GetSplashDamage = function(type)
{
if (!this.template[type].Splash)
return false;
- let splash = {};
- splash.damage = this.GetAttackStrengths(type + ".Splash");
- splash.friendlyFire = this.template[type].Splash.FriendlyFire != "false";
- splash.shape = this.template[type].Splash.Shape;
- return splash;
+ return {
+ "attackData": this.GetAttackEffectsData(type, true),
+ "friendlyFire": this.template[type].Splash.FriendlyFire != "false",
+ "shape": this.template[type].Splash.Shape,
+ };
};
Attack.prototype.GetRange = function(type)
@@ -498,15 +434,6 @@
return { "max": max, "min": min, "elevationBonus": elevationBonus };
};
-Attack.prototype.GetBonusTemplate = function(type)
-{
- let template = this.template[type];
- if (!template)
- template = this.template[type.split(".")[0]].Splash;
-
- return template.Bonuses || null;
-};
-
/**
* Attack the target entity. This should only be called after a successful range check,
* and should only be called after GetTimers().repeat msec has passed since the last
@@ -515,7 +442,6 @@
Attack.prototype.PerformAttack = function(type, target)
{
let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
- let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
// If this is a ranged attack, then launch a projectile
if (type == "Ranged")
@@ -528,7 +454,7 @@
let horizSpeed = +this.template[type].Projectile.Speed;
let gravity = +this.template[type].Projectile.Gravity;
- //horizSpeed /= 2; gravity /= 2; // slow it down for testing
+ // horizSpeed /= 2; gravity /= 2; // slow it down for testing
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
@@ -575,7 +501,7 @@
// TODO: Use unit rotation to implement x/z offsets.
let deltaLaunchPoint = new Vector3D(0, this.template[type].Projectile.LaunchPoint["@y"], 0.0);
let launchPoint = Vector3D.add(selfPosition, deltaLaunchPoint);
-
+
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
{
@@ -598,66 +524,26 @@
let data = {
"type": type,
- "attacker": this.entity,
+ "attackData": this.GetAttackEffectsData(type),
"target": target,
- "strengths": this.GetAttackStrengths(type),
+ "attacker": this.entity,
+ "attackerOwner": attackerOwner,
"position": realTargetPosition,
"direction": missileDirection,
"projectileId": id,
- "bonus": this.GetBonusTemplate(type),
- "isSplash": false,
- "attackerOwner": attackerOwner,
- "attackImpactSound": attackImpactSound,
- "statusEffects": this.template[type].StatusEffects
+ "attackImpactSound": attackImpactSound
};
if (this.template[type].Splash)
- {
- data.friendlyFire = this.template[type].Splash.FriendlyFire != "false";
- data.radius = +this.template[type].Splash.Range;
- data.shape = this.template[type].Splash.Shape;
- data.isSplash = true;
- data.splashStrengths = this.GetAttackStrengths(type + ".Splash");
- data.splashBonus = this.GetBonusTemplate(type + ".Splash");
- }
- cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + +this.template[type].Delay, data);
- }
- else if (type == "Capture")
- {
- if (attackerOwner == INVALID_PLAYER)
- return;
-
- let multiplier = GetDamageBonus(this.entity, target, type, this.GetBonusTemplate(type));
- let cmpHealth = Engine.QueryInterface(target, IID_Health);
- if (!cmpHealth || cmpHealth.GetHitpoints() == 0)
- return;
- multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
-
- let cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
- if (!cmpCapturable || !cmpCapturable.CanCapture(attackerOwner))
- return;
-
- let strength = this.GetAttackStrengths("Capture").value * multiplier;
- if (cmpCapturable.Reduce(strength, attackerOwner) && IsOwnedByEnemyOfPlayer(attackerOwner, target))
- Engine.PostMessage(target, MT_Attacked, {
- "attacker": this.entity,
- "target": target,
- "type": type,
- "damage": strength,
- "attackerOwner": attackerOwner
- });
+ data.splash = {
+ "friendlyFire": this.template[type].Splash.FriendlyFire != "false",
+ "radius": +this.template[type].Splash.Range,
+ "shape": this.template[type].Splash.Shape,
+ "attackData": this.GetAttackEffectsData(type, true),
+ };
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", +this.template[type].Delay + timeToTarget * 1000, data);
}
else
- {
- // Melee attack - hurt the target immediately
- cmpDamage.CauseDamage({
- "strengths": this.GetAttackStrengths(type),
- "target": target,
- "attacker": this.entity,
- "multiplier": GetDamageBonus(this.entity, target, type, this.GetBonusTemplate(type)),
- "type": type,
- "attackerOwner": attackerOwner
- });
- }
+ Attacking.HandleAttackEffects(type, this.GetAttackEffectsData(type), target, this.entity, attackerOwner);
};
/**
Index: binaries/data/mods/public/simulation/components/BuildingAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/BuildingAI.js
+++ binaries/data/mods/public/simulation/components/BuildingAI.js
@@ -131,7 +131,7 @@
var range = cmpAttack.GetRange(attackType);
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(
this.entity, range.min, range.max, range.elevationBonus,
- enemies, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
+ enemies, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal"));
cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery);
};
Index: binaries/data/mods/public/simulation/components/Capturable.js
===================================================================
--- binaries/data/mods/public/simulation/components/Capturable.js
+++ binaries/data/mods/public/simulation/components/Capturable.js
@@ -48,6 +48,24 @@
this.cp = capturePointsArray;
};
+Capturable.prototype.Capture = function(effectData, attacker, attackerOwner, bonusMultiplier)
+{
+ let cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
+
+ if (attackerOwner == INVALID_PLAYER || !this.CanCapture(attackerOwner) ||
+ !cmpHealth || cmpHealth.GetHitpoints() == 0)
+ return {};
+
+ bonusMultiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
+
+ let total = Attacking.GetTotalAttackEffects({ "Capture": effectData }, "Capture") * bonusMultiplier;
+
+ let change = this.Reduce(total, attackerOwner);
+ // TODO: implement loot
+
+ return { "captureChange": change };
+};
+
/**
* Reduces the amount of capture points of an entity,
* in favour of the player of the source
Index: binaries/data/mods/public/simulation/components/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/Damage.js
+++ /dev/null
@@ -1,312 +0,0 @@
-function Damage() {}
-
-Damage.prototype.Schema =
- "";
-
-Damage.prototype.Init = function()
-{
-};
-
-/**
- * Gives the position of the given entity, taking the lateness into account.
- * @param {number} ent - Entity id of the entity we are finding the location for.
- * @param {number} lateness - The time passed since the expected time to fire the function.
- * @return {Vector3D} The location of the entity.
- */
-Damage.prototype.InterpolatedLocation = function(ent, lateness)
-{
- let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
- if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
- return undefined;
- let curPos = cmpTargetPosition.GetPosition();
- let prevPos = cmpTargetPosition.GetPreviousPosition();
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- let turnLength = cmpTimer.GetLatestTurnLength();
- return new Vector3D(
- (curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength,
- 0,
- (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength);
-};
-
-/**
- * Test if a point is inside of an entity's footprint.
- * @param {number} ent - Id of the entity we are checking with.
- * @param {Vector3D} point - The point we are checking with.
- * @param {number} lateness - The time passed since the expected time to fire the function.
- * @return {boolean} True if the point is inside of the entity's footprint.
- */
-Damage.prototype.TestCollision = function(ent, point, lateness)
-{
- let targetPosition = this.InterpolatedLocation(ent, lateness);
- if (!targetPosition)
- return false;
-
- let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint);
- if (!cmpFootprint)
- return false;
-
- let targetShape = cmpFootprint.GetShape();
-
- if (!targetShape)
- return false;
-
- if (targetShape.type == "circle")
- return targetPosition.horizDistanceToSquared(point) < targetShape.radius * targetShape.radius;
-
- if (targetShape.type == "square")
- {
- let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
- let distance = Vector2D.from3D(Vector3D.sub(point, targetPosition)).rotate(-angle);
- return Math.abs(distance.x) < targetShape.width / 2 && Math.abs(distance.y) < targetShape.depth / 2;
- }
-
- warn("TestCollision called with an invalid footprint shape");
- return false;
-};
-
-/**
- * Get the list of players affected by the damage.
- * @param {number} attackerOwner - The player id of the attacker.
- * @param {boolean} friendlyFire - A flag indicating if allied entities are also damaged.
- * @return {number[]} The ids of players need to be damaged.
- */
-Damage.prototype.GetPlayersToDamage = function(attackerOwner, friendlyFire)
-{
- if (!friendlyFire)
- return QueryPlayerIDInterface(attackerOwner).GetEnemies();
-
- return Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayers();
-};
-
-/**
- * Handles hit logic after the projectile travel time has passed.
- * @param {Object} data - The data sent by the caller.
- * @param {number} data.attacker - The entity id of the attacker.
- * @param {number} data.target - The entity id of the target.
- * @param {Vector2D} data.origin - The origin of the projectile hit.
- * @param {Object} data.strengths - Data of the form { 'hack': number, 'pierce': number, 'crush': number }.
- * @param {string} data.type - The type of damage.
- * @param {number} data.attackerOwner - The player id of the owner of the attacker.
- * @param {boolean} data.isSplash - A flag indicating if it's splash damage.
- * @param {Vector3D} data.position - The expected position of the target.
- * @param {number} data.projectileId - The id of the projectile.
- * @param {Vector3D} data.direction - The unit vector defining the direction.
- * @param {Object} data.bonus - The attack bonus template from the attacker.
- * @param {string} data.attackImpactSound - The name of the sound emited on impact.
- * @param {Object} data.statusEffects - Status effects eg. poisoning, burning etc.
- * ***When splash damage***
- * @param {boolean} data.friendlyFire - A flag indicating if allied entities are also damaged.
- * @param {number} data.radius - The radius of the splash damage.
- * @param {string} data.shape - The shape of the splash range.
- * @param {Object} data.splashBonus - The attack bonus template from the attacker.
- * @param {Object} data.splashStrengths - Data of the form { 'hack': number, 'pierce': number, 'crush': number }.
- */
-Damage.prototype.MissileHit = function(data, lateness)
-{
- if (!data.position)
- return;
-
- let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager);
- if (cmpSoundManager && data.attackImpactSound)
- cmpSoundManager.PlaySoundGroupAtPosition(data.attackImpactSound, data.position);
-
- // Do this first in case the direct hit kills the target
- if (data.isSplash)
- {
- this.CauseDamageOverArea({
- "attacker": data.attacker,
- "origin": Vector2D.from3D(data.position),
- "radius": data.radius,
- "shape": data.shape,
- "strengths": data.splashStrengths,
- "splashBonus": data.splashBonus,
- "direction": data.direction,
- "playersToDamage": this.GetPlayersToDamage(data.attackerOwner, data.friendlyFire),
- "type": data.type,
- "attackerOwner": data.attackerOwner
- });
- }
-
- let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
-
- // Deal direct damage if we hit the main target
- // and if the target has DamageReceiver (not the case for a mirage for example)
- let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
- if (cmpDamageReceiver && this.TestCollision(data.target, data.position, lateness))
- {
- data.multiplier = GetDamageBonus(data.attacker, data.target, data.type, data.bonus);
- this.CauseDamage(data);
- cmpProjectileManager.RemoveProjectile(data.projectileId);
-
- let cmpStatusReceiver = Engine.QueryInterface(data.target, IID_StatusEffectsReceiver);
- if (cmpStatusReceiver && data.statusEffects)
- cmpStatusReceiver.InflictEffects(data.statusEffects);
-
- return;
- }
-
- let targetPosition = this.InterpolatedLocation(data.target, lateness);
- if (!targetPosition)
- return;
-
- // If we didn't hit the main target look for nearby units
- let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner);
- let ents = this.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
- for (let ent of ents)
- {
- if (!this.TestCollision(ent, data.position, lateness))
- continue;
-
- this.CauseDamage({
- "strengths": data.strengths,
- "target": ent,
- "attacker": data.attacker,
- "multiplier": GetDamageBonus(data.attacker, ent, data.type, data.bonus),
- "type": data.type,
- "attackerOwner": data.attackerOwner
- });
- cmpProjectileManager.RemoveProjectile(data.projectileId);
- break;
- }
-};
-
-/**
- * Damages units around a given origin.
- * @param {Object} data - The data sent by the caller.
- * @param {number} data.attacker - The entity id of the attacker.
- * @param {Vector2D} data.origin - The origin of the projectile hit.
- * @param {number} data.radius - The radius of the splash damage.
- * @param {string} data.shape - The shape of the radius.
- * @param {Object} data.strengths - Data of the form { 'hack': number, 'pierce': number, 'crush': number }.
- * @param {string} data.type - The type of damage.
- * @param {number} data.attackerOwner - The player id of the attacker.
- * @param {Vector3D} [data.direction] - The unit vector defining the direction. Needed for linear splash damage.
- * @param {Object} data.splashBonus - The attack bonus template from the attacker.
- * @param {number[]} data.playersToDamage - The array of player id's to damage.
- */
-Damage.prototype.CauseDamageOverArea = function(data)
-{
- // Get nearby entities and define variables
- let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
- let damageMultiplier = 1;
-
- // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
- for (let ent of nearEnts)
- {
- let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D();
- if (data.shape == 'Circular') // circular effect with quadratic falloff in every direction
- damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius);
- else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
- {
- // Get position of entity relative to splash origin.
- let relativePos = entityPosition.sub(data.origin);
-
- // Get the position relative to the missile direction.
- let direction = Vector2D.from3D(data.direction);
- let parallelPos = relativePos.dot(direction);
- let perpPos = relativePos.cross(direction);
-
- // The width of linear splash is one fifth of the normal splash radius.
- let width = data.radius / 5;
-
- // Check that the unit is within the distance splash width of the line starting at the missile's
- // landing point which extends in the direction of the missile for length splash radius.
- if (parallelPos >= 0 && Math.abs(perpPos) < width) // If in radius, quadratic falloff in both directions
- damageMultiplier = (1 - parallelPos * parallelPos / (data.radius * data.radius)) *
- (1 - perpPos * perpPos / (width * width));
- else
- damageMultiplier = 0;
- }
- else // In case someone calls this function with an invalid shape.
- {
- warn("The " + data.shape + " splash damage shape is not implemented!");
- }
-
- if (data.splashBonus)
- damageMultiplier *= GetDamageBonus(data.attacker, ent, data.type, data.splashBonus);
-
- // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
- this.CauseDamage({
- "strengths": data.strengths,
- "target": ent,
- "attacker": data.attacker,
- "multiplier": damageMultiplier,
- "type": data.type + ".Splash",
- "attackerOwner": data.attackerOwner
- });
- }
-};
-
-/**
- * Causes damage on a given unit.
- * @param {Object} data - The data passed by the caller.
- * @param {Object} data.strengths - Data in the form of { 'hack': number, 'pierce': number, 'crush': number }.
- * @param {number} data.target - The entity id of the target.
- * @param {number} data.attacker - The entity id of the attacker.
- * @param {number} data.multiplier - The damage multiplier.
- * @param {string} data.type - The type of damage.
- * @param {number} data.attackerOwner - The player id of the attacker.
- */
-Damage.prototype.CauseDamage = function(data)
-{
- let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
- if (!cmpDamageReceiver)
- return;
-
- let targetState = cmpDamageReceiver.TakeDamage(data.strengths, data.multiplier);
-
- let cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion);
- let cmpLoot = Engine.QueryInterface(data.target, IID_Loot);
- let cmpHealth = Engine.QueryInterface(data.target, IID_Health);
- if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0)
- cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints());
-
- if (targetState.killed)
- this.TargetKilled(data.attacker, data.target, data.attackerOwner);
-
- Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change, "attackerOwner": data.attackerOwner });
-};
-
-/**
- * Gets entities near a give point for given players.
- * @param {Vector2D} origin - The point to check around.
- * @param {number} radius - The radius around the point to check.
- * @param {number[]} players - The players of which we need to check entities.
- * @return {number[]} The id's of the entities in range of the given point.
- */
-Damage.prototype.EntitiesNearPoint = function(origin, radius, players)
-{
- // If there is insufficient data return an empty array.
- if (!origin || !radius || !players)
- return [];
-
- return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver);
-};
-
-/**
- * Called when a unit kills something (another unit, building, animal etc).
- * @param {number} attacker - The entity id of the killer.
- * @param {number} target - The entity id of the target.
- * @param {number} attackerOwner - The player id of the attacker.
- */
-Damage.prototype.TargetKilled = function(attacker, target, attackerOwner)
-{
- let cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership);
- let atkOwner = cmpAttackerOwnership && cmpAttackerOwnership.GetOwner() != INVALID_PLAYER ? cmpAttackerOwnership.GetOwner() : attackerOwner;
-
- // Add to killer statistics.
- let cmpKillerPlayerStatisticsTracker = QueryPlayerIDInterface(atkOwner, IID_StatisticsTracker);
- if (cmpKillerPlayerStatisticsTracker)
- cmpKillerPlayerStatisticsTracker.KilledEntity(target);
- // Add to loser statistics.
- let cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(target, IID_StatisticsTracker);
- if (cmpTargetPlayerStatisticsTracker)
- cmpTargetPlayerStatisticsTracker.LostEntity(target);
-
- // If killer can collect loot, let's try to collect it.
- let cmpLooter = Engine.QueryInterface(attacker, IID_Looter);
- if (cmpLooter)
- cmpLooter.Collect(target);
-};
-
-Engine.RegisterSystemComponentType(IID_Damage, "Damage", Damage);
Index: binaries/data/mods/public/simulation/components/DeathDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/DeathDamage.js
+++ binaries/data/mods/public/simulation/components/DeathDamage.js
@@ -33,10 +33,7 @@
"" +
"" +
"" +
- "" +
- BuildDamageTypesSchema("damage strength") +
- "" +
- DeathDamage.prototype.bonusesSchema;
+ Attacking.BuildAttackEffectsSchema();
DeathDamage.prototype.Init = function()
{
@@ -44,22 +41,9 @@
DeathDamage.prototype.Serialize = null; // we have no dynamic state to save
-DeathDamage.prototype.GetDeathDamageStrengths = function()
+DeathDamage.prototype.GetDeathDamageEffects = function()
{
- // Work out the damage values with technology effects
- let applyMods = damageType =>
- ApplyValueModificationsToEntity("DeathDamage/Damage/" + damageType, +(this.template.Damage[damageType] || 0), this.entity);
-
- let ret = {};
- for (let damageType in this.template.Damage)
- ret[damageType] = applyMods(damageType);
-
- return ret;
-};
-
-DeathDamage.prototype.GetBonusTemplate = function()
-{
- return this.template.Bonuses || null;
+ return Attacking.GetAttackEffectsData("DeathDamage", this.template, this.entity);
};
DeathDamage.prototype.CauseDeathDamage = function()
@@ -74,21 +58,19 @@
if (owner == INVALID_PLAYER)
warn("Unit causing death damage does not have any owner.");
- let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
- let playersToDamage = cmpDamage.GetPlayersToDamage(owner, this.template.FriendlyFire);
+ let playersToDamage = Attacking.GetPlayersToDamage(owner, this.template.FriendlyFire);
let radius = ApplyValueModificationsToEntity("DeathDamage/Range", +this.template.Range, this.entity);
- cmpDamage.CauseDamageOverArea({
+ Attacking.CauseDamageOverArea({
+ "type": "Death",
+ "attackData": this.GetDeathDamageEffects(),
"attacker": this.entity,
+ "attackerOwner": owner,
"origin": pos,
"radius": radius,
"shape": this.template.Shape,
- "strengths": this.GetDeathDamageStrengths(),
- "splashBonus": this.GetBonusTemplate(),
"playersToDamage": playersToDamage,
- "type": "Death",
- "attackerOwner": owner
});
};
Index: binaries/data/mods/public/simulation/components/DelayedDamage.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/DelayedDamage.js
@@ -0,0 +1,85 @@
+function DelayedDamage() {}
+
+DelayedDamage.prototype.Schema =
+ "";
+
+DelayedDamage.prototype.Init = function()
+{
+};
+
+/**
+ * Handles hit logic after the projectile travel time has passed.
+ * @param {Object} data - The data sent by the caller.
+ * @param {string} data.type - The type of damage.
+ * @param {Object} data.attackData - Data of the form { 'effectType': { ...opaque effect data... }, 'Bonuses': {...} }.
+ * @param {number} data.target - The entity id of the target.
+ * @param {number} data.attacker - The entity id of the attacker.
+ * @param {number} data.attackerOwner - The player id of the owner of the attacker.
+ * @param {Vector2D} data.origin - The origin of the projectile hit.
+ * @param {Vector3D} data.position - The expected position of the target.
+ * @param {number} data.projectileId - The id of the projectile.
+ * @param {Vector3D} data.direction - The unit vector defining the direction.
+ * @param {string} data.attackImpactSound - The name of the sound emited on impact.
+ * ***When splash damage***
+ * @param {boolean} data.splash.friendlyFire - A flag indicating if allied entities are also damaged.
+ * @param {number} data.splash.radius - The radius of the splash damage.
+ * @param {string} data.splash.shape - The shape of the splash range.
+ * @param {Object} data.splash.attackData - same as attackData, for splash.
+ */
+DelayedDamage.prototype.MissileHit = function(data, lateness)
+{
+ if (!data.position)
+ return;
+
+ let cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager);
+ if (cmpSoundManager && data.attackImpactSound)
+ cmpSoundManager.PlaySoundGroupAtPosition(data.attackImpactSound, data.position);
+
+ // Do this first in case the direct hit kills the target
+ if (data.splash)
+ {
+ Attacking.CauseDamageOverArea({
+ "type": data.type,
+ "attackData": data.splash.attackData,
+ "attacker": data.attacker,
+ "attackerOwner": data.attackerOwner,
+ "origin": Vector2D.from3D(data.position),
+ "radius": data.splash.radius,
+ "shape": data.splash.shape,
+ "direction": data.direction,
+ "playersToDamage": Attacking.GetPlayersToDamage(data.attackerOwner, 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))
+ {
+ cmpProjectileManager.RemoveProjectile(data.projectileId);
+
+ Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner);
+ return;
+ }
+
+ let targetPosition = Attacking.InterpolatedLocation(data.target, lateness);
+ if (!targetPosition)
+ return;
+
+ // If we didn't hit the main target look for nearby units
+ let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner);
+ let ents = Attacking.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
+ for (let ent of ents)
+ {
+ if (!Attacking.TestCollision(ent, data.position, lateness))
+ continue;
+
+ Attacking.HandleAttackEffects(data.type, data.attackData, ent, data.attacker, data.attackerOwner);
+
+ cmpProjectileManager.RemoveProjectile(data.projectileId);
+ break;
+ }
+};
+
+Engine.RegisterSystemComponentType(IID_DelayedDamage, "DelayedDamage", DelayedDamage);
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -389,12 +389,12 @@
for (let type of types)
{
ret.attack[type] = {};
- if (type == "Capture")
- ret.attack[type] = cmpAttack.GetAttackStrengths(type);
- else
- ret.attack[type].damage = cmpAttack.GetAttackStrengths(type);
+
+ Object.assign(ret.attack[type], cmpAttack.GetAttackEffectsData(type));
ret.attack[type].splash = cmpAttack.GetSplashDamage(type);
+ if (ret.attack[type].splash)
+ Object.assign(ret.attack[type].splash, cmpAttack.GetAttackEffectsData(type, true));
let range = cmpAttack.GetRange(type);
ret.attack[type].minRange = range.min;
@@ -432,9 +432,9 @@
}
}
- let cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
+ let cmpArmour = Engine.QueryInterface(ent, IID_Resistance);
if (cmpArmour)
- ret.armour = cmpArmour.GetArmourStrengths();
+ ret.armour = cmpArmour.GetArmourStrengths("Damage");
let cmpBuildingAI = Engine.QueryInterface(ent, IID_BuildingAI);
if (cmpBuildingAI)
Index: binaries/data/mods/public/simulation/components/Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/Health.js
+++ binaries/data/mods/public/simulation/components/Health.js
@@ -177,9 +177,34 @@
this.Reduce(this.hitpoints);
};
+/**
+ * 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 { "killed": false, "change": -12 }.
+ */
+Health.prototype.TakeDamage = function(effectData, attacker, attackerOwner, bonusMultiplier)
+{
+ let cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance);
+
+ if (cmpResistance && cmpResistance.IsInvulnerable())
+ return { "killed": false };
+
+ let total = Attacking.GetTotalAttackEffects(effectData, "Damage", cmpResistance) * bonusMultiplier;
+
+ // Reduce health
+ let change = this.Reduce(total);
+
+ let cmpLoot = Engine.QueryInterface(this.entity, IID_Loot);
+ if (cmpLoot && cmpLoot.GetXp() > 0 && change.HPchange < 0)
+ change.xp = cmpLoot.GetXp() * -change.HPchange / this.GetMaxHitpoints();
+
+ return change;
+};
+
/**
* @param {number} amount - The amount of hitpoints to substract. Kills the entity if required.
- * @return {{killed:boolean, change:number}} - Number of health points lost and whether the entity was killed.
+ * @return {{killed:boolean, HPchange:number}} - Number of health points lost and whether the entity was killed.
*/
Health.prototype.Reduce = function(amount)
{
@@ -188,7 +213,7 @@
// might get called multiple times)
// Likewise if the amount is 0.
if (!amount || !this.hitpoints)
- return { "killed": false, "change": 0 };
+ return { "killed": false, "HPchange": 0 };
// Before changing the value, activate Fogging if necessary to hide changes
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
@@ -202,7 +227,7 @@
this.hitpoints = 0;
this.RegisterHealthChanged(oldHitpoints);
this.HandleDeath();
- return { "killed": true, "change": -oldHitpoints };
+ return { "killed": true, "HPchange": -oldHitpoints };
}
// If we are not marked as injured, do it now
@@ -215,7 +240,7 @@
this.hitpoints -= amount;
this.RegisterHealthChanged(oldHitpoints);
- return { "killed": false, "change": this.hitpoints - oldHitpoints };
+ return { "killed": false, "HPchange": this.hitpoints - oldHitpoints };
};
/**
Index: binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js
===================================================================
--- binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js
+++ binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js
@@ -5,13 +5,18 @@
this.activeStatusEffects = {};
};
-StatusEffectsReceiver.prototype.InflictEffects = function(statusEffects)
+// Called by attacking effects.
+StatusEffectsReceiver.prototype.GiveStatus = function(effectData, attacker, attackerOwner, bonusMultiplier)
{
- for (let effect in statusEffects)
- this.InflictEffect(effect, statusEffects[effect]);
+ for (let effect in effectData)
+ this.AddStatus(effect, effectData[effect]);
+
+ // TODO: implement loot / resistance.
+
+ return { "inflictedStatuses": Object.keys(effectData) };
};
-StatusEffectsReceiver.prototype.InflictEffect = function(statusName, data)
+StatusEffectsReceiver.prototype.AddStatus = function(statusName, data)
{
if (this.activeStatusEffects[statusName])
return;
@@ -28,7 +33,7 @@
status.timer = cmpTimer.SetInterval(this.entity, IID_StatusEffectsReceiver, "ExecuteEffect", 0, +status.interval, statusName);
};
-StatusEffectsReceiver.prototype.RemoveEffect = function(statusName) {
+StatusEffectsReceiver.prototype.RemoveStatus = function(statusName) {
if (!this.activeStatusEffects[statusName])
return;
@@ -51,19 +56,10 @@
else
status.timeElapsed += status.interval + lateness;
- let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
-
- cmpDamage.CauseDamage({
- "strengths": { [statusName]: status.damage },
- "target": this.entity,
- "attacker": -1,
- "multiplier": 1,
- "type": statusName,
- "attackerOwner": -1
- });
+ Attacking.HandleAttackEffects(statusName, { "Damage": { [statusName]: status.damage } }, this.entity, -1, -1);
if (status.timeElapsed >= status.duration)
- this.RemoveEffect(statusName);
+ this.RemoveStatus(statusName);
};
Engine.RegisterComponentType(IID_StatusEffectsReceiver, "StatusEffectsReceiver", StatusEffectsReceiver);
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js
+++ binaries/data/mods/public/simulation/components/UnitAI.js
@@ -2958,8 +2958,8 @@
"CHEERING": {
"enter": function() {
// Unit is invulnerable while cheering
- var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver);
- cmpDamageReceiver.SetInvulnerability(true);
+ var cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance);
+ cmpResistance.SetInvulnerability(true);
this.SelectAnimation("promotion");
this.StartTimer(2800, 2800);
return false;
@@ -2968,8 +2968,8 @@
"leave": function() {
this.StopTimer();
this.ResetAnimation();
- var cmpDamageReceiver = Engine.QueryInterface(this.entity, IID_DamageReceiver);
- cmpDamageReceiver.SetInvulnerability(false);
+ var cmpResistance = Engine.QueryInterface(this.entity, IID_Resistance);
+ cmpResistance.SetInvulnerability(false);
},
"Timer": function(msg) {
@@ -3466,7 +3466,7 @@
var players = cmpPlayer.GetEnemies();
var range = this.GetQueryRange(IID_Attack);
- this.losRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
+ this.losRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity, range.min, range.max, players, IID_Resistance, cmpRangeManager.GetEntityFlagMask("normal"));
if (enable)
cmpRangeManager.EnableActiveQuery(this.losRangeQuery);
Index: binaries/data/mods/public/simulation/components/interfaces/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Damage.js
+++ /dev/null
@@ -1 +0,0 @@
-Engine.RegisterInterface("Damage");
Index: binaries/data/mods/public/simulation/components/interfaces/DelayedDamage.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/components/interfaces/DelayedDamage.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("DelayedDamage");
Index: binaries/data/mods/public/simulation/components/interfaces/Resistance.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Resistance.js
+++ binaries/data/mods/public/simulation/components/interfaces/Resistance.js
@@ -1,4 +1,4 @@
-Engine.RegisterInterface("DamageReceiver");
+Engine.RegisterInterface("Resistance");
/**
* Message of the form { "entity": entity, "invulnerability": true/false }
Index: binaries/data/mods/public/simulation/components/tests/test_Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Attack.js
+++ binaries/data/mods/public/simulation/components/tests/test_Attack.js
@@ -1,5 +1,5 @@
Engine.LoadHelperScript("DamageBonus.js");
-Engine.LoadHelperScript("DamageTypes.js");
+Engine.LoadHelperScript("Attacking.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
@@ -104,7 +104,7 @@
}
},
"Capture": {
- "Value": 8,
+ "Capture": 8,
"MaxRange": 10,
},
"Slaughter": {}
@@ -150,18 +150,28 @@
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Melee"), ["FemaleCitizen"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 });
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Capture"), { "value": 8 });
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Capture"), { "Capture": 8 });
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged"), {
- "Hack": 0,
- "Pierce": 10,
- "Crush": 0
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged"), {
+ "Damage": {
+ "Hack": 0,
+ "Pierce": 10,
+ "Crush": 0
+ }
});
- TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged.Splash"), {
- "Hack": 0.0,
- "Pierce": 15.0,
- "Crush": 35.0
+ TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackEffectsData("Ranged", true), {
+ "Damage": {
+ "Hack": 0.0,
+ "Pierce": 15.0,
+ "Crush": 35.0
+ },
+ "Bonuses": {
+ "BonusCav": {
+ "Classes": "Cavalry",
+ "Multiplier": 3
+ }
+ }
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Ranged"), {
@@ -175,10 +185,18 @@
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashDamage("Ranged"), {
- "damage": {
- "Hack": 0,
- "Pierce": 15,
- "Crush": 35,
+ "attackData": {
+ "Damage": {
+ "Hack": 0,
+ "Pierce": 15,
+ "Crush": 35,
+ },
+ "Bonuses": {
+ "BonusCav": {
+ "Classes": "Cavalry",
+ "Multiplier": 3
+ }
+ }
},
"friendlyFire": false,
"shape": "Circular"
@@ -188,14 +206,14 @@
for (let className of ["Infantry", "Cavalry"])
attackComponentTest(className, true, (attacker, cmpAttack, defender) => {
- TS_ASSERT_EQUALS(cmpAttack.GetBonusTemplate("Melee").BonusCav.Multiplier, 2);
+ TS_ASSERT_EQUALS(cmpAttack.GetAttackEffectsData("Melee").Bonuses.BonusCav.Multiplier, 2);
- TS_ASSERT(cmpAttack.GetBonusTemplate("Capture") === null);
+ TS_ASSERT_EQUALS(cmpAttack.GetAttackEffectsData("Capture").Bonuses || null, null);
- let getAttackBonus = (s, t, e) => GetDamageBonus(s, e, t, cmpAttack.GetBonusTemplate(t));
+ let getAttackBonus = (s, t, e, splash) => GetAttackBonus(s, e, t, cmpAttack.GetAttackEffectsData(t, splash).Bonuses || null);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Melee", defender), className == "Cavalry" ? 2 : 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Ranged", defender), 1);
- TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Ranged.Splash", defender), className == "Cavalry" ? 3 : 1);
+ TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Ranged", defender, true), className == "Cavalry" ? 3 : 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Capture", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus(attacker, "Slaughter", defender), 1);
});
Index: binaries/data/mods/public/simulation/components/tests/test_Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Damage.js
+++ binaries/data/mods/public/simulation/components/tests/test_Damage.js
@@ -1,13 +1,13 @@
Engine.LoadHelperScript("DamageBonus.js");
-Engine.LoadHelperScript("DamageTypes.js");
+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/AuraManager.js");
-Engine.LoadComponentScript("interfaces/Damage.js");
-Engine.LoadComponentScript("interfaces/DamageReceiver.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");
@@ -16,22 +16,24 @@
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Attack.js");
-Engine.LoadComponentScript("Damage.js");
+Engine.LoadComponentScript("DelayedDamage.js");
Engine.LoadComponentScript("Timer.js");
function Test_Generic()
{
ResetState();
- let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
- cmpTimer.OnUpdate({ turnLength: 1 });
+ cmpTimer.OnUpdate({ "turnLength": 1 });
let attacker = 11;
let atkPlayerEntity = 1;
let attackerOwner = 6;
let cmpAttack = ConstructComponent(attacker, "Attack",
{
"Ranged": {
+ "Damage": {
+ "Crush": 5,
+ },
"MaxRange": 50,
"MinRange": 0,
"Delay": 0,
@@ -51,19 +53,19 @@
let type = "Melee";
let damageTaken = false;
- cmpAttack.GetAttackStrengths = attackType => ({ "hack": 0, "pierce": 0, "crush": damage });
+ cmpAttack.GetAttackStrengths = attackType => ({ "Hack": 0, "Pierce": 0, "Crush": damage });
let data = {
- "attacker": attacker,
- "target": target,
"type": "Melee",
- "strengths": { "hack": 0, "pierce": 0, "crush": damage },
- "multiplier": 1.0,
+ "attackData": {
+ "Damage": { "Hack": 0, "Pierce": 0, "Crush": damage },
+ },
+ "target": target,
+ "attacker": attacker,
"attackerOwner": attackerOwner,
"position": targetPos,
- "isSplash": false,
"projectileId": 9,
- "direction": new Vector3D(1,0,0)
+ "direction": new Vector3D(1, 0, 0)
};
AddMock(atkPlayerEntity, IID_Player, {
@@ -87,15 +89,22 @@
"IsInWorld": () => true,
});
- AddMock(target, IID_Health, {});
+ AddMock(target, IID_Health, {
+ "TakeDamage": (effectData, __, ___, bonusMultiplier) => {
+ damageTaken = true;
+ return { "killed": false, "HPchange": -bonusMultiplier * effectData.Crush };
+ },
+ });
- AddMock(target, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => { damageTaken = true; return { "killed": false, "change": -multiplier * strengths.crush }; },
+ AddMock(SYSTEM_ENTITY, IID_DelayedDamage, {
+ "MissileHit": () => {
+ damageTaken = true;
+ },
});
Engine.PostMessage = function(ent, iid, message)
{
- TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": type, "damage": damage, "attackerOwner": attackerOwner }, message);
+ TS_ASSERT_UNEVAL_EQUALS({ "type": type, "target": target, "attacker": attacker, "attackerOwner": attackerOwner, "damage": damage, "capture": 0, "statusEffects": [] }, message);
};
AddMock(target, IID_Footprint, {
@@ -114,16 +123,17 @@
function TestDamage()
{
- cmpTimer.OnUpdate({ turnLength: 1 });
+ cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT(damageTaken);
damageTaken = false;
}
- cmpDamage.CauseDamage(data);
+ Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner);
TestDamage();
- type = data.type = "Ranged";
- cmpDamage.CauseDamage(data);
+ data.type = "Ranged";
+ type = data.type;
+ Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner);
TestDamage();
// Check for damage still being dealt if the attacker dies
@@ -135,8 +145,8 @@
AddMock(atkPlayerEntity, IID_Player, {
"GetEnemies": () => [2, 3]
});
- TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(atkPlayerEntity, true), [0, 1, 2, 3, 4]);
- TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(atkPlayerEntity, false), [2, 3]);
+ TS_ASSERT_UNEVAL_EQUALS(Attacking.GetPlayersToDamage(atkPlayerEntity, true), [0, 1, 2, 3, 4]);
+ TS_ASSERT_UNEVAL_EQUALS(Attacking.GetPlayersToDamage(atkPlayerEntity, false), [2, 3]);
}
Test_Generic();
@@ -152,26 +162,24 @@
const origin = new Vector2D(0, 0);
let data = {
+ "type": "Ranged",
+ "attackData": { "Damage": { "Hack": 100, "Pierce": 0, "Crush": 0 } },
"attacker": attacker,
+ "attackerOwner": attackerOwner,
"origin": origin,
"radius": 10,
"shape": "Linear",
- "strengths": { "hack" : 100, "pierce" : 0, "crush": 0 },
"direction": new Vector3D(1, 747, 0),
"playersToDamage": [2],
- "type": "Ranged",
- "attackerOwner": attackerOwner
};
- let fallOff = function(x,y)
+ let fallOff = function(x, y)
{
return (1 - x * x / (data.radius * data.radius)) * (1 - 25 * y * y / (data.radius * data.radius));
};
let hitEnts = new Set();
- let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
-
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [60, 61, 62],
});
@@ -188,31 +196,31 @@
"GetPosition2D": () => new Vector2D(5, 2),
});
- AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ AddMock(60, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(60);
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(2.2, -0.4));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(2.2, -0.4));
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ AddMock(61, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(61);
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(0, 0));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(0, 0));
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- AddMock(62, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ AddMock(62, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(62);
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 0);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 0);
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- cmpDamage.CauseDamageOverArea(data);
+ Attacking.CauseDamageOverArea(data);
TS_ASSERT(hitEnts.has(60));
TS_ASSERT(hitEnts.has(61));
TS_ASSERT(hitEnts.has(62));
@@ -220,15 +228,15 @@
data.direction = new Vector3D(0.6, 747, 0.8);
- AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ AddMock(60, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(60);
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(1, 2));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(1, 2));
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- cmpDamage.CauseDamageOverArea(data);
+ Attacking.CauseDamageOverArea(data);
TS_ASSERT(hitEnts.has(60));
TS_ASSERT(hitEnts.has(61));
TS_ASSERT(hitEnts.has(62));
@@ -249,8 +257,6 @@
return 1 - r * r / (radius * radius);
};
- let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
-
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [60, 61, 62, 64],
});
@@ -276,49 +282,49 @@
"GetPosition2D": () => new Vector2D(9, -4),
});
- AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(0));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ AddMock(60, IID_Resistance, {
+ "TakeDamage": (effectData, __, ___, mult) => {
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(0));
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(5));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ AddMock(61, IID_Resistance, {
+ "TakeDamage": (effectData, __, ___, mult) => {
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(5));
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- AddMock(62, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(1));
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ AddMock(62, IID_Resistance, {
+ "TakeDamage": (effectData, __, ___, mult) => {
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100 * fallOff(1));
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- AddMock(63, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ AddMock(63, IID_Resistance, {
+ "TakeDamage": (effectData, __, ___, mult) => {
TS_ASSERT(false);
}
});
- AddMock(64, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 0);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ AddMock(64, IID_Resistance, {
+ "TakeDamage": (effectData, __, ___, mult) => {
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 0);
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
- cmpDamage.CauseDamageOverArea({
+ Attacking.CauseDamageOverArea({
+ "type": "Ranged",
+ "attackData": { "Damage": { "Hack": 100, "Pierce": 0, "Crush": 0 } },
"attacker": 50,
+ "attackerOwner": 1,
"origin": new Vector2D(3, 4),
"radius": radius,
"shape": "Circular",
- "strengths": { "hack" : 100, "pierce" : 0, "crush": 0 },
"playersToDamage": [2],
- "type": "Ranged",
- "attackerOwner": 1
});
}
@@ -329,7 +335,7 @@
ResetState();
Engine.PostMessage = (ent, iid, message) => {};
- let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
+ let cmpDelayedDamage = ConstructComponent(SYSTEM_ENTITY, "DelayedDamage");
let target = 60;
let targetOwner = 1;
@@ -344,15 +350,13 @@
let data = {
"type": "Ranged",
- "attacker": 70,
+ "attackData": { "Damage": { "Hack": 0, "Pierce": 100, "Crush": 0 } },
"target": 60,
- "strengths": { "hack": 0, "pierce": 100, "crush": 0 },
+ "attacker": 70,
+ "attackerOwner": 1,
"position": targetPos,
"direction": new Vector3D(1, 0, 0),
"projectileId": 9,
- "bonus": undefined,
- "isSplash": false,
- "attackerOwner": 1
};
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
@@ -372,13 +376,11 @@
"IsInWorld": () => true,
});
- AddMock(60, IID_Health, {});
-
- AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ AddMock(60, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(60);
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100);
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
@@ -400,7 +402,7 @@
"GetEnemies": () => [2]
});
- cmpDamage.MissileHit(data, 0);
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(60));
hitEnts.clear();
@@ -413,10 +415,10 @@
"IsInWorld": () => true,
});
- AddMock(60, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
+ AddMock(60, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
TS_ASSERT_EQUALS(false);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
@@ -431,13 +433,11 @@
"IsInWorld": () => true,
});
- AddMock(61, IID_Health, {});
-
- AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100);
+ AddMock(61, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(61);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 100);
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
@@ -445,28 +445,27 @@
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
- cmpDamage.MissileHit(data, 0);
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
hitEnts.clear();
// Add a splash damage.
-
- data.friendlyFire = false;
- data.radius = 10;
- data.shape = "Circular";
- data.isSplash = true;
- data.splashStrengths = { "hack": 0, "pierce": 0, "crush": 200 };
+ data.splash = {};
+ data.splash.friendlyFire = false;
+ data.splash.radius = 10;
+ data.splash.shape = "Circular";
+ data.splash.attackData = { "Damage": { "Hack": 0, "Pierce": 0, "Crush": 200 } };
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [61, 62]
});
let dealtDamage = 0;
- AddMock(61, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
- dealtDamage += multiplier * (strengths.hack + strengths.pierce + strengths.crush);
+ AddMock(61, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(61);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ dealtDamage += mult * (effectData.Hack + effectData.Pierce + effectData.Crush);
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
@@ -477,13 +476,11 @@
"IsInWorld": () => true,
});
- AddMock(62, IID_Health, {});
-
- AddMock(62, IID_DamageReceiver, {
- "TakeDamage": (strengths, multiplier) => {
- TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 200 * 0.75);
+ AddMock(62, IID_Health, {
+ "TakeDamage": (effectData, __, ___, mult) => {
hitEnts.add(62);
- return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
+ TS_ASSERT_EQUALS(mult * (effectData.Hack + effectData.Pierce + effectData.Crush), 200 * 0.75);
+ return { "killed": false, "change": -mult * (effectData.Hack + effectData.Pierce + effectData.Crush) };
}
});
@@ -491,7 +488,7 @@
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
- cmpDamage.MissileHit(data, 0);
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 200);
dealtDamage = 0;
@@ -512,36 +509,36 @@
"HasClass": cl => cl == "Cavalry"
});
- data.bonus = bonus;
- cmpDamage.MissileHit(data, 0);
+ data.attackData.Bonuses = bonus;
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 200);
dealtDamage = 0;
hitEnts.clear();
- data.splashBonus = splashBonus;
- cmpDamage.MissileHit(data, 0);
+ data.splash.attackData.Bonuses = splashBonus;
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
- data.bonus = undefined;
- cmpDamage.MissileHit(data, 0);
+ data.attackData.Bonuses = undefined;
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
- data.bonus = null;
- cmpDamage.MissileHit(data, 0);
+ data.attackData.Bonuses = null;
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
- data.bonus = {};
- cmpDamage.MissileHit(data, 0);
+ data.attackData.Bonuses = {};
+ cmpDelayedDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
Index: binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
+++ binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
@@ -1,8 +1,7 @@
Engine.LoadHelperScript("DamageBonus.js");
-Engine.LoadHelperScript("DamageTypes.js");
+Engine.LoadHelperScript("Attacking.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
-Engine.LoadComponentScript("interfaces/Damage.js");
Engine.LoadComponentScript("interfaces/DeathDamage.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("DeathDamage.js");
@@ -28,10 +27,12 @@
}
};
-let modifiedDamage = {
- "Hack": 0.0,
- "Pierce": 215.0,
- "Crush": 35.0
+let effects = {
+ "Damage": {
+ "Hack": 0.0,
+ "Pierce": 215.0,
+ "Crush": 35.0
+ }
};
let cmpDeathDamage = ConstructComponent(deadEnt, "DeathDamage", template);
@@ -40,21 +41,18 @@
let pos = new Vector2D(3, 4.2);
let result = {
+ "type": "Death",
+ "attackData": effects,
"attacker": deadEnt,
+ "attackerOwner": player,
"origin": pos,
"radius": template.Range,
"shape": template.Shape,
- "strengths": modifiedDamage,
- "splashBonus": null,
- "playersToDamage": playersToDamage,
- "type": "Death",
- "attackerOwner": player
+ "playersToDamage": playersToDamage
};
-AddMock(SYSTEM_ENTITY, IID_Damage, {
- "CauseDamageOverArea": data => TS_ASSERT_UNEVAL_EQUALS(data, result),
- "GetPlayersToDamage": (owner, friendlyFire) => playersToDamage
-});
+Attacking.CauseDamageOverArea = data => TS_ASSERT_UNEVAL_EQUALS(data, result);
+Attacking.GetPlayersToDamage = () => playersToDamage;
AddMock(deadEnt, IID_Position, {
"GetPosition2D": () => pos,
@@ -65,13 +63,13 @@
"GetOwner": () => player
});
-TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageStrengths(), modifiedDamage);
+TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageEffects(), effects);
cmpDeathDamage.CauseDeathDamage();
// Test splash damage bonus
-let splashBonus = { "BonusCav": { "Classes": "Cavalry", "Multiplier": 3 } };
-template.Bonuses = splashBonus;
+effects.Bonuses = { "BonusCav": { "Classes": "Cavalry", "Multiplier": 3 } };
+template.Bonuses = effects.Bonuses;
cmpDeathDamage = ConstructComponent(deadEnt, "DeathDamage", template);
-result.splashBonus = splashBonus;
-TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageStrengths(), modifiedDamage);
+result.attackData.Bonuses = effects.Bonuses;
+TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageEffects(), effects);
cmpDeathDamage.CauseDeathDamage();
Index: binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
+++ binaries/data/mods/public/simulation/components/tests/test_GuiInterface.js
@@ -6,7 +6,7 @@
Engine.LoadComponentScript("interfaces/Builder.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/CeasefireManager.js");
-Engine.LoadComponentScript("interfaces/DamageReceiver.js");
+Engine.LoadComponentScript("interfaces/Resistance.js");
Engine.LoadComponentScript("interfaces/DeathDamage.js");
Engine.LoadComponentScript("interfaces/EndGameManager.js");
Engine.LoadComponentScript("interfaces/EntityLimits.js");
Index: binaries/data/mods/public/simulation/components/tests/test_Health.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Health.js
+++ binaries/data/mods/public/simulation/components/tests/test_Health.js
@@ -64,7 +64,7 @@
TS_ASSERT_EQUALS(injured_flag, true);
TS_ASSERT_EQUALS(change.killed, false);
-TS_ASSERT_EQUALS(change.change, -25);
+TS_ASSERT_EQUALS(change.HPchange, -25);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 25);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), true);
@@ -107,7 +107,7 @@
TS_ASSERT_EQUALS(injured_flag, false);
TS_ASSERT_EQUALS(change.killed, true);
-TS_ASSERT_EQUALS(change.change, -50);
+TS_ASSERT_EQUALS(change.HPchange, -50);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false);
@@ -122,7 +122,7 @@
// Check that we can't die twice.
change = cmpHealth.Reduce(50);
TS_ASSERT_EQUALS(change.killed, false);
-TS_ASSERT_EQUALS(change.change, 0);
+TS_ASSERT_EQUALS(change.HPchange, 0);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false);
@@ -132,7 +132,7 @@
// Check that we still die with > Max HP of damage.
change = cmpHealth.Reduce(60);
TS_ASSERT_EQUALS(change.killed, true);
-TS_ASSERT_EQUALS(change.change, -50);
+TS_ASSERT_EQUALS(change.HPchange, -50);
TS_ASSERT_EQUALS(cmpHealth.GetHitpoints(), 0);
TS_ASSERT_EQUALS(cmpHealth.GetMaxHitpoints(), 50);
TS_ASSERT_EQUALS(cmpHealth.IsInjured(), false);
Index: binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js
+++ binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js
@@ -1,4 +1,3 @@
-Engine.LoadComponentScript("interfaces/Damage.js");
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("StatusEffectsReceiver.js");
@@ -20,17 +19,16 @@
{
setup();
let statusName = "Burn";
- AddMock(SYSTEM_ENTITY, IID_Damage, {
- "CauseDamage": (data) => { dealtDamage += data.strengths[statusName]; }
- });
+ let Attacking = {
+ "HandleAttackEffects": (_, attackData) => { dealtDamage += attackData.Damage[statusName]; }
+ };
+ Engine.RegisterGlobal("Attacking", Attacking);
// damage scheduled: 0, 10, 20 sec
- cmpStatusReceiver.InflictEffects({
- [statusName]: {
- "Duration": 20000,
- "Interval": 10000,
- "Damage": 1
- }
+ cmpStatusReceiver.AddStatus(statusName, {
+ "Duration": 20000,
+ "Interval": 10000,
+ "Damage": 1
});
cmpTimer.OnUpdate({ "turnLength": 1 });
@@ -54,15 +52,16 @@
function testMultipleEffects()
{
setup();
- AddMock(SYSTEM_ENTITY, IID_Damage, {
- "CauseDamage": (data) => {
- if (data.strengths.Burn) dealtDamage += data.strengths.Burn;
- if (data.strengths.Poison) dealtDamage += data.strengths.Poison;
- }
- });
+ let Attacking = {
+ "HandleAttackEffects": (_, attackData) => {
+ if (attackData.Damage.Burn) dealtDamage += attackData.Damage.Burn;
+ if (attackData.Damage.Poison) dealtDamage += attackData.Damage.Poison;
+ },
+ };
+ Engine.RegisterGlobal("Attacking", Attacking);
// damage scheduled: 0, 1, 2, 10 sec
- cmpStatusReceiver.InflictEffects({
+ cmpStatusReceiver.GiveStatus({
"Burn": {
"Duration": 20000,
"Interval": 10000,
@@ -90,30 +89,29 @@
testMultipleEffects();
-function testRemoveEffect()
+function testRemoveStatus()
{
setup();
let statusName = "Poison";
- AddMock(SYSTEM_ENTITY, IID_Damage, {
- "CauseDamage": (data) => { dealtDamage += data.strengths[statusName]; }
- });
+ let Attacking = {
+ "HandleAttackEffects": (_, attackData) => { dealtDamage += attackData.Damage[statusName]; }
+ };
+ Engine.RegisterGlobal("Attacking", Attacking);
// damage scheduled: 0, 10, 20 sec
- cmpStatusReceiver.InflictEffects({
- [statusName]: {
- "Duration": 20000,
- "Interval": 10000,
- "Damage": 1
- }
+ cmpStatusReceiver.AddStatus(statusName, {
+ "Duration": 20000,
+ "Interval": 10000,
+ "Damage": 1
});
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec
- cmpStatusReceiver.RemoveEffect(statusName);
+ cmpStatusReceiver.RemoveStatus(statusName);
cmpTimer.OnUpdate({ "turnLength": 10 });
TS_ASSERT_EQUALS(dealtDamage, 1); // 11 sec
}
-testRemoveEffect();
+testRemoveStatus();
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
@@ -5,7 +5,7 @@
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/BuildingAI.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
-Engine.LoadComponentScript("interfaces/DamageReceiver.js");
+Engine.LoadComponentScript("interfaces/Resistance.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Heal.js");
Engine.LoadComponentScript("interfaces/Health.js");
Index: binaries/data/mods/public/simulation/helpers/Attacking.js
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/helpers/Attacking.js
@@ -0,0 +1,308 @@
+/**
+ * Provides attack and damage-related helpers under the Attacking umbrella (to avoid name ambiguity with the component).
+ */
+function Attacking() {}
+
+/**
+ * Builds a RelaxRNG schema of possible attack effects.
+ * Currently harcoded to "Damage", "Capture" and "StatusEffects".
+ * Attacks may also have a "Bonuses" element.
+ *
+ * @return {string} - RelaxNG schema string
+ */
+Attacking.prototype.BuildAttackEffectsSchema = function()
+{
+ return "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ // Armour requires Foundation to not be a damage type.
+ "Foundation" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
+};
+
+/**
+ * Returns a template-like object of attack effects.
+ */
+Attacking.prototype.GetAttackEffectsData = function(valueModifRoot, template, entity)
+{
+ let ret = {};
+
+ if (template.Damage)
+ {
+ ret.Damage = {};
+ let applyMods = damageType =>
+ ApplyValueModificationsToEntity(valueModifRoot + "/Damage/" + damageType, +(template.Damage[damageType] || 0), entity);
+ for (let damageType in template.Damage)
+ ret.Damage[damageType] = applyMods(damageType);
+ }
+ if (template.Capture)
+ ret.Capture = ApplyValueModificationsToEntity(valueModifRoot + "/Capture", +(template.Capture || 0), entity);
+
+ if (template.StatusEffects)
+ ret.StatusEffects = template.StatusEffects;
+
+ if (template.Bonuses)
+ ret.Bonuses = template.Bonuses;
+
+ return ret;
+};
+
+Attacking.prototype.GetTotalAttackEffects = function(effectData, effectType, cmpResistance)
+{
+ let total = 0;
+ let armourStrengths = cmpResistance ? cmpResistance.GetArmourStrengths(effectType) : {};
+
+ for (let type in effectData)
+ total += effectData[type] * Math.pow(0.9, armourStrengths[type] || 0);
+
+ return total;
+};
+
+/**
+ * Gives the position of the given entity, taking the lateness into account.
+ * @param {number} ent - Entity id of the entity we are finding the location for.
+ * @param {number} lateness - The time passed since the expected time to fire the function.
+ * @return {Vector3D} The location of the entity.
+ */
+Attacking.prototype.InterpolatedLocation = function(ent, lateness)
+{
+ let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
+ if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
+ return undefined;
+ let curPos = cmpTargetPosition.GetPosition();
+ let prevPos = cmpTargetPosition.GetPreviousPosition();
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ let turnLength = cmpTimer.GetLatestTurnLength();
+ return new Vector3D(
+ (curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength,
+ 0,
+ (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength
+ );
+};
+
+/**
+ * Test if a point is inside of an entity's footprint.
+ * @param {number} ent - Id of the entity we are checking with.
+ * @param {Vector3D} point - The point we are checking with.
+ * @param {number} lateness - The time passed since the expected time to fire the function.
+ * @return {boolean} True if the point is inside of the entity's footprint.
+ */
+Attacking.prototype.TestCollision = function(ent, point, lateness)
+{
+ let targetPosition = this.InterpolatedLocation(ent, lateness);
+ if (!targetPosition)
+ return false;
+
+ let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint);
+ if (!cmpFootprint)
+ return false;
+
+ let targetShape = cmpFootprint.GetShape();
+
+ if (!targetShape)
+ return false;
+
+ if (targetShape.type == "circle")
+ return targetPosition.horizDistanceToSquared(point) < targetShape.radius * targetShape.radius;
+
+ if (targetShape.type == "square")
+ {
+ let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
+ let distance = Vector2D.from3D(Vector3D.sub(point, targetPosition)).rotate(-angle);
+ return Math.abs(distance.x) < targetShape.width / 2 && Math.abs(distance.y) < targetShape.depth / 2;
+ }
+
+ warn("TestCollision called with an invalid footprint shape");
+ return false;
+};
+
+/**
+ * Get the list of players affected by the damage.
+ * @param {number} attackerOwner - The player id of the attacker.
+ * @param {boolean} friendlyFire - A flag indicating if allied entities are also damaged.
+ * @return {number[]} The ids of players need to be damaged.
+ */
+Attacking.prototype.GetPlayersToDamage = function(attackerOwner, friendlyFire)
+{
+ if (!friendlyFire)
+ return QueryPlayerIDInterface(attackerOwner).GetEnemies();
+
+ return Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayers();
+};
+
+/**
+ * Damages units around a given origin.
+ * @param {Object} data - The data sent by the caller.
+ * @param {string} data.type - The type of damage.
+ * @param {Object} data.attackData - The attack data.
+ * @param {number} data.attacker - The entity id of the attacker.
+ * @param {number} data.attackerOwner - The player id of the attacker.
+ * @param {Vector2D} data.origin - The origin of the projectile hit.
+ * @param {number} data.radius - The radius of the splash damage.
+ * @param {string} data.shape - The shape of the radius.
+ * @param {Vector3D} [data.direction] - The unit vector defining the direction. Needed for linear splash damage.
+ * @param {number[]} data.playersToDamage - The array of player id's to damage.
+ */
+Attacking.prototype.CauseDamageOverArea = function(data)
+{
+ // Get nearby entities and define variables
+ let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
+ let damageMultiplier = 1;
+
+ // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
+ for (let ent of nearEnts)
+ {
+ let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D();
+ if (data.shape == 'Circular') // circular effect with quadratic falloff in every direction
+ damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius);
+ else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
+ {
+ // Get position of entity relative to splash origin.
+ let relativePos = entityPosition.sub(data.origin);
+
+ // Get the position relative to the missile direction.
+ let direction = Vector2D.from3D(data.direction);
+ let parallelPos = relativePos.dot(direction);
+ let perpPos = relativePos.cross(direction);
+
+ // The width of linear splash is one fifth of the normal splash radius.
+ let width = data.radius / 5;
+
+ // Check that the unit is within the distance splash width of the line starting at the missile's
+ // landing point which extends in the direction of the missile for length splash radius.
+ if (parallelPos >= 0 && Math.abs(perpPos) < width) // If in radius, quadratic falloff in both directions
+ damageMultiplier = (1 - parallelPos * parallelPos / (data.radius * data.radius)) *
+ (1 - perpPos * perpPos / (width * width));
+ else
+ damageMultiplier = 0;
+ }
+ else // In case someone calls this function with an invalid shape.
+ {
+ warn("The " + data.shape + " splash damage shape is not implemented!");
+ }
+
+ this.HandleAttackEffects(data.type + ".Splash", data.attackData, ent, data.attacker, data.attackerOwner, damageMultiplier);
+ }
+};
+
+Attacking.prototype.HandleAttackEffects = function(attackType, attackData, target, attacker, attackerOwner, bonusMultiplier = 1)
+{
+ let targetState = {};
+ for (let effectType of g_EffectTypes)
+ {
+ if (!attackData[effectType])
+ continue;
+
+ bonusMultiplier *= !attackData.Bonuses ? 1 : GetAttackBonus(attacker, target, attackType, attackData.Bonuses);
+
+ let receiver = g_EffectReceiver[effectType];
+ let cmpReceiver = Engine.QueryInterface(target, global[receiver.IID]);
+ if (!cmpReceiver)
+ return;
+
+ Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attacker, attackerOwner, bonusMultiplier));
+ }
+
+ let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion);
+ if (cmpPromotion && targetState.xp)
+ cmpPromotion.IncreaseXp(targetState.xp);
+
+ if (targetState.killed)
+ this.TargetKilled(attacker, target, attackerOwner);
+
+ Engine.PostMessage(target, MT_Attacked, {
+ "type": attackType,
+ "target": target,
+ "attacker": attacker,
+ "attackerOwner": attackerOwner,
+ "damage": -(targetState.HPchange || 0),
+ "capture": targetState.captureChange || 0,
+ "statusEffects": targetState.inflictedStatuses || [],
+ });
+};
+
+/**
+ * Gets entities near a give point for given players.
+ * @param {Vector2D} origin - The point to check around.
+ * @param {number} radius - The radius around the point to check.
+ * @param {number[]} players - The players of which we need to check entities.
+ * @return {number[]} The id's of the entities in range of the given point.
+ */
+Attacking.prototype.EntitiesNearPoint = function(origin, radius, players)
+{
+ // If there is insufficient data return an empty array.
+ if (!origin || !radius || !players)
+ return [];
+
+ return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).ExecuteQueryAroundPos(origin, 0, radius, players, IID_Resistance);
+};
+
+/**
+ * Called when a unit kills something (another unit, building, animal etc).
+ * @param {number} attacker - The entity id of the killer.
+ * @param {number} target - The entity id of the target.
+ * @param {number} attackerOwner - The player id of the attacker.
+ */
+Attacking.prototype.TargetKilled = function(attacker, target, attackerOwner)
+{
+ let cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership);
+ let atkOwner = cmpAttackerOwnership && cmpAttackerOwnership.GetOwner() != INVALID_PLAYER ? cmpAttackerOwnership.GetOwner() : attackerOwner;
+
+ // Add to killer statistics.
+ let cmpKillerPlayerStatisticsTracker = QueryPlayerIDInterface(atkOwner, IID_StatisticsTracker);
+ if (cmpKillerPlayerStatisticsTracker)
+ cmpKillerPlayerStatisticsTracker.KilledEntity(target);
+ // Add to loser statistics.
+ let cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(target, IID_StatisticsTracker);
+ if (cmpTargetPlayerStatisticsTracker)
+ cmpTargetPlayerStatisticsTracker.LostEntity(target);
+
+ // If killer can collect loot, let's try to collect it.
+ let cmpLooter = Engine.QueryInterface(attacker, IID_Looter);
+ if (cmpLooter)
+ cmpLooter.Collect(target);
+};
+
+var AttackingInstance = new Attacking();
+Engine.RegisterGlobal("Attacking", AttackingInstance);
Index: binaries/data/mods/public/simulation/helpers/DamageBonus.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/DamageBonus.js
+++ binaries/data/mods/public/simulation/helpers/DamageBonus.js
@@ -6,7 +6,7 @@
* @param {Object} template - The bonus' template.
* @return {number} - The source entity's attack bonus against the specified target.
*/
-function GetDamageBonus(source, target, type, template)
+function GetAttackBonus(source, target, type, template)
{
let attackBonus = 1;
@@ -28,4 +28,4 @@
return attackBonus;
}
-Engine.RegisterGlobal("GetDamageBonus", GetDamageBonus);
+Engine.RegisterGlobal("GetAttackBonus", GetAttackBonus);
Index: binaries/data/mods/public/simulation/helpers/DamageTypes.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/DamageTypes.js
+++ /dev/null
@@ -1,22 +0,0 @@
-/**
- * Builds a RelaxRNG schema based on currently valid elements.
- *
- * To prevent validation errors, disabled damage types are included in the schema.
- *
- * @param {string} helptext - Text displayed as help
- * @return {string} - RelaxNG schema string
- */
-function BuildDamageTypesSchema(helptext = "")
-{
- return "" +
- "" +
- "" +
- // Armour requires Foundation to not be a damage type.
- "Foundation" +
- "" +
- "" +
- "" +
- "";
-}
-
-Engine.RegisterGlobal("BuildDamageTypesSchema", BuildDamageTypesSchema);
Index: binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
@@ -7,7 +7,7 @@
- 2
+ 2
4
1000
Field Palisade SiegeWall StoneWall
Index: binaries/data/mods/public/simulation/templates/template_unit_champion.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_champion.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_champion.xml
@@ -2,7 +2,7 @@
- 5
+ 5
4
1000
Field Palisade SiegeWall StoneWall
Index: binaries/data/mods/public/simulation/templates/template_unit_hero.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_hero.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_hero.xml
@@ -7,7 +7,7 @@
- 15
+ 15
4
1000
Field Palisade SiegeWall StoneWall
Index: binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
@@ -7,7 +7,7 @@
- 2
+ 2
4
1000
Field Palisade SiegeWall StoneWall