Index: binaries/data/mods/public/simulation/ai/petra/entityExtend.js =================================================================== --- binaries/data/mods/public/simulation/ai/petra/entityExtend.js +++ binaries/data/mods/public/simulation/ai/petra/entityExtend.js @@ -164,7 +164,6 @@ return capture > antiCapture + sumCapturePoints/80; }; -/** copy of GetAttackBonus from Attack.js */ m.getAttackBonus = function(ent, target, type) { let attackBonus = 1; 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 @@ -432,33 +432,14 @@ return { "max": max, "min": min, "elevationBonus": elevationBonus }; }; -// Calculate the attack damage multiplier against a target -Attack.prototype.GetAttackBonus = function(type, target) +Attack.prototype.GetBonusTemplate = function(type) { - let attackBonus = 1; let template = this.template[type]; if (!template) template = this.template[type.split(".")[0]].Splash; - if (template.Bonuses) - { - let cmpIdentity = Engine.QueryInterface(target, IID_Identity); - if (!cmpIdentity) - return 1; - - // Multiply the bonuses for all matching classes - for (let key in template.Bonuses) - { - let bonus = template.Bonuses[key]; - if (bonus.Civ && bonus.Civ !== cmpIdentity.GetCiv()) - continue; - if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !cmpIdentity.HasClass(cls))) - continue; - attackBonus *= bonus.Multiplier; - } - } - - return attackBonus; + return clone(template.Bonuses) + return undefined; }; /** @@ -528,7 +509,7 @@ "position": realTargetPosition, "direction": missileDirection, "projectileId": id, - "multiplier": this.GetAttackBonus(type, target), + "bonus": this.GetBonusTemplate(type), "isSplash": false, "attackerOwner": attackerOwner }; @@ -538,7 +519,8 @@ data.radius = +this.template.Ranged.Splash.Range; data.shape = this.template.Ranged.Splash.Shape; data.isSplash = true; - data.splashStrengths = this.GetAttackStrengths(type+".Splash"); + data.splashStrengths = this.GetAttackStrengths(type + ".Splash"); + data.splashBonus = this.GetBonusTemplate(type + ".Splash"); } cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data); } @@ -547,7 +529,7 @@ if (attackerOwner == -1) return; - let multiplier = this.GetAttackBonus(type, target); + let multiplier = GetDamageBonus(target, this.GetBonusTemplate(type)); let cmpHealth = Engine.QueryInterface(target, IID_Health); if (!cmpHealth || cmpHealth.GetHitpoints() == 0) return; @@ -574,7 +556,7 @@ "strengths": this.GetAttackStrengths(type), "target": target, "attacker": this.entity, - "multiplier": this.GetAttackBonus(type, target), + "multiplier": GetDamageBonus(target, this.GetBonusTemplate(type)), "type": type, "attackerOwner": attackerOwner }); Index: binaries/data/mods/public/simulation/components/Damage.js =================================================================== --- binaries/data/mods/public/simulation/components/Damage.js +++ binaries/data/mods/public/simulation/components/Damage.js @@ -98,10 +98,12 @@ * @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. * ***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. */ Damage.prototype.MissileHit = function(data, lateness) { @@ -117,6 +119,7 @@ "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, @@ -131,6 +134,7 @@ let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); if (cmpDamageReceiver && this.TestCollision(data.target, data.position, lateness)) { + data.multiplier = GetDamageBonus(data.target, data.bonus); this.CauseDamage(data); cmpProjectileManager.RemoveProjectile(data.projectileId); return; @@ -147,11 +151,12 @@ { if (!this.TestCollision(ent, data.position, lateness)) continue; + this.CauseDamage({ "strengths": data.strengths, "target": ent, "attacker": data.attacker, - "multiplier": data.multiplier, + "multiplier": GetDamageBonus(ent, data.bonus), "type": data.type, "attackerOwner": data.attackerOwner }); @@ -171,6 +176,7 @@ * @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. + * @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.CauseSplashDamage = function(data) @@ -210,6 +216,10 @@ { warn("The " + data.shape + " splash damage shape is not implemented!"); } + + if (data.splashBonus) + damageMultiplier *= GetDamageBonus(ent, data.splashBonus) + // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds.... this.CauseDamage({ "strengths": data.strengths, 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 @@ -78,6 +78,7 @@ "radius": radius, "shape": this.template.Shape, "strengths": this.GetDeathDamageStrengths("Death"), + "splashBonus": this.template.Bonuses, "playersToDamage": playersToDamage, "type": "Death", "attackerOwner": owner 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 @@ -4684,14 +4684,6 @@ return cmpAttack.GetBestAttackAgainst(target, allowCapture); }; -UnitAI.prototype.GetAttackBonus = function(type, target) -{ - var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); - if (!cmpAttack) - return 1; - return cmpAttack.GetAttackBonus(type, target); -}; - /** * Try to find one of the given entities which can be attacked, * and start attacking it. 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,3 +1,4 @@ +Engine.LoadHelperScript("DamageBonus.js"); Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/Auras.js"); @@ -86,7 +87,7 @@ "Bonuses": { "BonusCav": { "Classes": "Cavalry", - "Multiplier": 2 + "Multiplier": 3 } } } @@ -163,11 +164,18 @@ for (let className of ["Infantry", "Cavalry"]) attackComponentTest(className, true, (attacker, cmpAttack, defender) => { - TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Melee", defender), className == "Cavalry" ? 2 : 1); - TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Ranged", defender), 1); - TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Ranged.Splash", defender), className == "Cavalry" ? 2 : 1); - TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Capture", defender), 1); - TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackBonus("Slaughter", defender), 1); + TS_ASSERT_EQUALS(cmpAttack.GetBonusTemplate("Melee").BonusCav.Multiplier, 2); + // Check that we don't leak data + let bonus = cmpAttack.GetBonusTemplate("Melee"); + bonus.BonusCav.Multiplier = 2.7; + TS_ASSERT_EQUALS(cmpAttack.GetBonusTemplate("Melee").BonusCav.Multiplier, 2); + + let getAttackBonus = (t, e) => GetDamageBonus(e, cmpAttack.GetBonusTemplate(t)); + TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Melee", defender), className == "Cavalry" ? 2 : 1); + TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Ranged", defender), 1); + TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Ranged.Splash", defender), className == "Cavalry" ? 3 : 1); + TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Capture", defender), 1); + TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Slaughter", defender), 1); }); // CanAttack rejects elephant attack due to RestrictedClasses 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,3 +1,4 @@ +Engine.LoadHelperScript("DamageBonus.js"); Engine.LoadHelperScript("Player.js"); Engine.LoadHelperScript("Sound.js"); Engine.LoadHelperScript("ValueModification.js"); @@ -37,7 +38,6 @@ let damageTaken = false; cmpAttack.GetAttackStrengths = (type) => ({ "hack": 0, "pierce": 0, "crush": damage }); - cmpAttack.GetAttackBonus = (type, target) => 1.0; let data = { "attacker": attacker, @@ -62,13 +62,12 @@ }); AddMock(SYSTEM_ENTITY, IID_RangeManager, { - "ExecuteQueryAroundPos": () => [target], - "GetElevationAdaptedRange": (pos, rot, max, bonus, a) => max, + "ExecuteQueryAroundPos": () => [target] }); AddMock(SYSTEM_ENTITY, IID_ProjectileManager, { - RemoveProjectile: () => {}, - LaunchProjectileAtPoint: (ent, pos, speed, gravity) => {}, + "RemoveProjectile": () => {}, + "LaunchProjectileAtPoint": (ent, pos, speed, gravity) => {}, }); AddMock(target, IID_Position, { @@ -121,6 +120,7 @@ data.range = 10; data.shape = "Circular"; data.isSplash = true; + data.bonus = null; cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data); TestDamage(); 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,3 +1,4 @@ +Engine.LoadHelperScript("DamageBonus.js"); Engine.LoadHelperScript("ValueModification.js"); Engine.LoadComponentScript("interfaces/AuraManager.js"); Engine.LoadComponentScript("interfaces/Damage.js"); @@ -41,6 +42,7 @@ "radius": template.Range, "shape": template.Shape, "strengths": modifiedDamage, + "splashBonus": undefined, "playersToDamage": playersToDamage, "type": "Death", "attackerOwner": player Index: binaries/data/mods/public/simulation/helpers/DamageBonus.js =================================================================== --- /dev/null +++ binaries/data/mods/public/simulation/helpers/DamageBonus.js @@ -0,0 +1,26 @@ +/** + * Calculate the attack damage multiplier against a target. + */ +function GetDamageBonus(target, template) +{ + let attackBonus = 1; + + let cmpIdentity = Engine.QueryInterface(target, IID_Identity); + if (!cmpIdentity) + return 1; + + // Multiply the bonuses for all matching classes + for (let key in template) + { + let bonus = template[key]; + if (bonus.Civ && bonus.Civ !== cmpIdentity.GetCiv()) + continue; + if (bonus.Classes && bonus.Classes.split(/\s+/).some(cls => !cmpIdentity.HasClass(cls))) + continue; + attackBonus *= bonus.Multiplier; + } + + return attackBonus; +}; + +Engine.RegisterGlobal("GetDamageBonus", GetDamageBonus);