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 @@ -445,7 +445,17 @@ */ Attack.prototype.PerformAttack = function(type, target) { - let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner(); + let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + if (!cmpPosition || !cmpPosition.IsInWorld()) + return; + let selfPosition = cmpPosition.GetPosition(); + + let attackerData = { + "owner": Engine.QueryInterface(this.entity, IID_Ownership).GetOwner(), + "entity": this.entity, + "position": selfPosition, + "elevationBonus": this.GetRange(type).elevationBonus + }; // If this is a ranged attack, then launch a projectile if (type == "Ranged") @@ -460,10 +470,6 @@ let gravity = +this.template[type].Projectile.Gravity; // horizSpeed /= 2; gravity /= 2; // slow it down for testing - let cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - if (!cmpPosition || !cmpPosition.IsInWorld()) - return; - let selfPosition = cmpPosition.GetPosition(); let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) return; @@ -530,8 +536,7 @@ "type": type, "attackData": this.GetAttackEffectsData(type), "target": target, - "attacker": this.entity, - "attackerOwner": attackerOwner, + "attackerData": attackerData, "position": realTargetPosition, "direction": missileDirection, "projectileId": id, @@ -547,7 +552,7 @@ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_DelayedDamage, "MissileHit", +this.template[type].Delay + timeToTarget * 1000, data); } else - Attacking.HandleAttackEffects(type, this.GetAttackEffectsData(type), target, this.entity, attackerOwner); + Attacking.HandleAttackEffects(type, this.GetAttackEffectsData(type), target, attackerData); }; /** Index: binaries/data/mods/public/simulation/components/DelayedDamage.js =================================================================== --- binaries/data/mods/public/simulation/components/DelayedDamage.js +++ binaries/data/mods/public/simulation/components/DelayedDamage.js @@ -41,8 +41,7 @@ Attacking.CauseDamageOverArea({ "type": data.type, "attackData": data.splash.attackData, - "attacker": data.attacker, - "attackerOwner": data.attackerOwner, + "attackerData": data.attackerData, "origin": Vector2D.from3D(data.position), "radius": data.splash.radius, "shape": data.splash.shape, @@ -59,7 +58,7 @@ { cmpProjectileManager.RemoveProjectile(data.projectileId); - Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner); + Attacking.HandleAttackEffects(data.type, data.attackData, data.target, data.attackerData); return; } @@ -68,14 +67,14 @@ return; // If we didn't hit the main target look for nearby units - let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner); + let cmpPlayer = QueryPlayerIDInterface(data.attackerData.owner); 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); + Attacking.HandleAttackEffects(data.type, data.attackData, ent, data.attackerData); cmpProjectileManager.RemoveProjectile(data.projectileId); break; Index: binaries/data/mods/public/simulation/helpers/Attacking.js =================================================================== --- binaries/data/mods/public/simulation/helpers/Attacking.js +++ binaries/data/mods/public/simulation/helpers/Attacking.js @@ -226,7 +226,7 @@ } }; -Attacking.prototype.HandleAttackEffects = function(attackType, attackData, target, attacker, attackerOwner, bonusMultiplier = 1) +Attacking.prototype.HandleAttackEffects = function(attackType, attackData, target, attackerData, bonusMultiplier = 1) { let targetState = {}; for (let effectType of g_EffectTypes) @@ -234,28 +234,31 @@ if (!attackData[effectType]) continue; - bonusMultiplier *= !attackData.Bonuses ? 1 : GetAttackBonus(attacker, target, attackType, attackData.Bonuses); + if (effectType == "Damage") + bonusMultiplier *= this.GetElevationDamageBonus(target, attackerData); + + bonusMultiplier *= !attackData.Bonuses ? 1 : GetAttackBonus(attackerData.entity, target, attackType, attackData.Bonuses); let receiver = g_EffectReceiver[effectType]; let cmpReceiver = Engine.QueryInterface(target, global[receiver.IID]); if (!cmpReceiver) continue; - Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attacker, attackerOwner, bonusMultiplier)); + Object.assign(targetState, cmpReceiver[receiver.method](attackData[effectType], attackerData.entity, attackerData.owner, bonusMultiplier)); } - let cmpPromotion = Engine.QueryInterface(attacker, IID_Promotion); + let cmpPromotion = Engine.QueryInterface(attackerData.entity, IID_Promotion); if (cmpPromotion && targetState.xp) cmpPromotion.IncreaseXp(targetState.xp); if (targetState.killed) - this.TargetKilled(attacker, target, attackerOwner); + this.TargetKilled(attackerData.entity, target, attackerData.owner); Engine.PostMessage(target, MT_Attacked, { "type": attackType, "target": target, - "attacker": attacker, - "attackerOwner": attackerOwner, + "attacker": attackerData.entity, + "attackerOwner": attackerData.owner, "damage": -(targetState.HPchange || 0), "capture": targetState.captureChange || 0, "statusEffects": targetState.inflictedStatuses || [], @@ -279,6 +282,24 @@ }; /** + * Calculate the damage bonus for entities with a height difference. + * For an explanation of the numbers, see D781. + * @param {string} attackType - The attack type, needed for the initial height of the attack. + * @param {number} elevationDifference - The height difference between the source of attack and the target. + * @return {number} - The factor that the damage is multiplied with. + */ +Attacking.prototype.GetElevationDamageBonus = function(target, attackerData) +{ + let cmpTargetPosition = Engine.QueryInterface(target, IID_Position); + if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) + return 1; + let targetPosition = cmpTargetPosition.GetPosition(); + let elevationDifference = (attackerData.position.y + attackerData.elevationBonus) - targetPosition.y; + + return Math.max(0.1, 1 + 0.01 * (elevationDifference)); +}; + +/** * 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.