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 @@ -273,7 +273,7 @@ "attackLabel": attackLabel, "details": type == "Capture" ? - template.attack.Capture.value : + template.attack.Capture.Capture : damageTypesToText(template.attack[type].damage), "rate": rate })); 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 @@ -7,6 +7,7 @@ "0.0" + "5.0" + "" + + DamageTypes.BuildSchema("damage protection") + "" + "" + @@ -35,33 +36,76 @@ /** * 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. + * @param {number} bonusMultiplier - the damage multiplier. * Returns object of the form { "killed": false, "change": -12 }. */ -Armour.prototype.TakeDamage = function(strengths, multiplier = 1) +Armour.prototype.TakeDamage = function(effectData, attacker, attackerOwner, bonusMultiplier) { if (this.invulnerable) - return { "killed": false, "change": 0 }; + return { "killed": false }; + let total = this.GetTotalEffect(effectData, "Damage", bonusMultiplier); + + // Reduce health + let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); + let change = cmpHealth.Reduce(total); + + let cmpLoot = Engine.QueryInterface(this.entity, IID_Loot); + if (cmpLoot && cmpLoot.GetXp() > 0 && change.HPchange < 0) + change.xp = cmpLoot.GetXp() * -change.HPchange / cmpHealth.GetMaxHitpoints(); + return change; +}; + +Armour.prototype.TakeCapture = function(effectData, attacker, attackerOwner, bonusMultiplier) +{ + let cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable); + let cmpHealth = Engine.QueryInterface(this.entity, IID_Health); + + if (this.invulnerable || attackerOwner == INVALID_PLAYER || + !cmpCapturable || !cmpCapturable.CanCapture(attackerOwner) || + !cmpHealth || cmpHealth.GetHitpoints() == 0) + return { "killed": false }; + + bonusMultiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints()); + + let total = this.GetTotalEffect({ "Capture" : effectData }, "Capture", bonusMultiplier); + + let change = cmpCapturable.Reduce(total, attackerOwner); + + // TODO: implement loot + return { "killed": false, "captureChange": change }; +}; + +Armour.prototype.TakeStatusEffects = function(effectData, attacker, attackerOwner, bonusMultiplier) +{ + let cmpStatusReceiver = Engine.QueryInterface(this.entity, IID_StatusEffectsReceiver); + + if (this.invulnerable || !cmpStatusReceiver) + return { "killed": false }; + + cmpStatusReceiver.InflictEffects(data.statusEffects); + + // TODO: implement loot / resistance. + return { "killed": false, "inflictedEffects": Object.keys(data.statusEffects) }; +}; + +Armour.prototype.GetTotalEffect = function(effectData, effectType, bonusMultiplier) +{ // Adjust damage values based on armour; exponential armour: damage = attack * 0.9^armour - var armourStrengths = this.GetArmourStrengths(); + let armourStrengths = this.GetArmourStrengths(effectType); - // 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); + let total = 0; + for (let type in effectData) + total += effectData[type] * bonusMultiplier * Math.pow(0.9, armourStrengths[type] || 0); + return total; }; -Armour.prototype.GetArmourStrengths = function() +Armour.prototype.GetArmourStrengths = function(effectType) { // Work out the armour values with technology effects - var applyMods = (type, foundation) => { - var strength; + let applyMods = (type, foundation) => { + let strength; if (foundation) { strength = +this.template.Foundation[type]; @@ -70,14 +114,17 @@ else strength = +this.template[type]; - return ApplyValueModificationsToEntity("Armour/" + type, strength, this.entity); + return ApplyValueModificationsToEntity("Armour/" + effectType + "/" + type, strength, this.entity); }; - var foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation; + let foundation = Engine.QueryInterface(this.entity, IID_Foundation) && this.template.Foundation; let ret = {}; - for (let damageType of DamageTypes.GetTypes()) - ret[damageType] = applyMods(damageType, foundation); + if (effectType != "Damage") + return ret; + for (let damageType in this.template) + if (damageType != "Foundation") + ret[damageType] = applyMods(damageType, foundation); return ret; }; 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 @@ -56,6 +56,16 @@ "" + ""; +Attack.prototype.EffectsSchema = + "" + + "" + + "" + DamageTypes.BuildSchema("damage strength") + "" + + "" + + Attack.prototype.statusEffectsSchema + + "" + + "" + + Attack.prototype.bonusesSchema; + Attack.prototype.Schema = "Controls the attack abilities and strengths of the unit." + "" + @@ -130,9 +140,7 @@ "" + "" + "" + - "" + - DamageTypes.BuildSchema("damage strength") + - "" + + Attack.prototype.EffectsSchema + "" + "" + "" + @@ -140,7 +148,6 @@ "" + // TODO: it shouldn't be stretched "" + "" + - Attack.prototype.bonusesSchema + Attack.prototype.preferredClassesSchema + Attack.prototype.restrictedClassesSchema + "" + @@ -149,9 +156,7 @@ "" + "" + "" + - "" + - DamageTypes.BuildSchema("damage strength") + - "" + + Attack.prototype.EffectsSchema + "" + "" + ""+ @@ -179,10 +184,7 @@ "" + "" + "" + - "" + - DamageTypes.BuildSchema("damage strength") + - "" + - Attack.prototype.bonusesSchema + + Attack.prototype.EffectsSchema + "" + "" + "" + @@ -217,8 +219,6 @@ "" + "" + "" + - Attack.prototype.statusEffectsSchema + - Attack.prototype.bonusesSchema + Attack.prototype.preferredClassesSchema + Attack.prototype.restrictedClassesSchema + "" + @@ -227,12 +227,11 @@ "" + "" + "" + - "" + + Attack.prototype.EffectsSchema + "" + "" + // TODO: it shouldn't be stretched "" + "" + - Attack.prototype.bonusesSchema + Attack.prototype.preferredClassesSchema + Attack.prototype.restrictedClassesSchema + "" + @@ -241,11 +240,8 @@ "" + "" + "" + - "" + - DamageTypes.BuildSchema("damage strength") + - "" + + Attack.prototype.EffectsSchema + "" + // TODO: how do these work? - Attack.prototype.bonusesSchema + Attack.prototype.preferredClassesSchema + Attack.prototype.restrictedClassesSchema + "" + @@ -452,26 +448,35 @@ return { "prepare": prepare, "repeat": repeat }; }; -Attack.prototype.GetAttackStrengths = function(type) +/** + * Returns a template-like object of attack effects. + */ +Attack.prototype.GetAttackData = function(type, splash) { // 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) }; + if (splash) + template = template.Splash; let ret = {}; - for (let damageType of DamageTypes.GetTypes()) - ret[damageType] = applyMods(damageType); + + if (template.Damage) + { + ret.Damage = {}; + let applyMods = damageType => + ApplyValueModificationsToEntity("Attack/" + type + splash ? "/Splash" : "" + "/Damage/" + damageType, +(template.Damage[damageType] || 0), this.entity); + for (let damageType of DamageTypes.GetTypes()) + ret.Damage[damageType] = applyMods(damageType); + } + if (template.Capture) + ret.Capture = ApplyValueModificationsToEntity("Attack/Capture/Capture/Value", +(template.Capture || 0), this.entity); + + if (template.StatusEffects) + ret.StatusEffects = template.StatusEffects; + + let bonuses = this.GetBonusTemplate(type, splash); + if (bonuses) + ret.Bonuses = bonuses; return ret; }; @@ -482,7 +487,7 @@ return false; let splash = {}; - splash.damage = this.GetAttackStrengths(type + ".Splash"); + splash.attackData = this.GetAttackData(type, true); splash.friendlyFire = this.template[type].Splash.FriendlyFire != "false"; splash.shape = this.template[type].Splash.Shape; return splash; @@ -502,11 +507,11 @@ return { "max": max, "min": min, "elevationBonus": elevationBonus }; }; -Attack.prototype.GetBonusTemplate = function(type) +Attack.prototype.GetBonusTemplate = function(type, splash) { let template = this.template[type]; - if (!template) - template = this.template[type.split(".")[0]].Splash; + if (splash) + template = this.template[type].Splash; return template.Bonuses || null; }; @@ -519,7 +524,7 @@ Attack.prototype.PerformAttack = function(type, target) { let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner(); - let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage); + let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage); // If this is a ranged attack, then launch a projectile if (type == "Ranged") @@ -532,7 +537,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()) @@ -579,7 +584,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) { @@ -602,66 +607,27 @@ let data = { "type": type, - "attacker": this.entity, + "attackData": this.GetAttackData(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"); + data.splash = {}; + data.splash.friendlyFire = this.template[type].Splash.FriendlyFire != "false"; + data.splash.radius = +this.template[type].Splash.Range; + data.splash.shape = this.template[type].Splash.Shape; + data.splash.attackData = this.GetAttackData(type, true); } - 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 - }); + cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + (+this.template[type].Delay), 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 - }); - } + cmpDamage.HandleAttackEffects(type, this.GetAttackData(type), target, this.entity, 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 @@ -5,6 +5,13 @@ Damage.prototype.Init = function() { + // TODO: would be nice to template this or put it in json + this.effectTypes = ["Damage", "Capture", "StatusEffects"]; +}; + +Damage.prototype.GeteffectTypes = function() +{ + return this.effectTypes; }; /** @@ -81,25 +88,21 @@ /** * 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 {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 {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.attacker - The entity id of the attacker. * @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 {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 {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 }. + * @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. */ Damage.prototype.MissileHit = function(data, lateness) { @@ -111,19 +114,18 @@ cmpSoundManager.PlaySoundGroupAtPosition(data.attackImpactSound, data.position); // Do this first in case the direct hit kills the target - if (data.isSplash) + if (data.splash) { this.CauseDamageOverArea({ + "type": data.type, + "attackData": data.splash.attackData, "attacker": data.attacker, + "attackerOwner": data.attackerOwner, "origin": Vector2D.from3D(data.position), - "radius": data.radius, - "shape": data.shape, - "strengths": data.splashStrengths, - "splashBonus": data.splashBonus, + "radius": data.splash.radius, + "shape": data.splash.shape, "direction": data.direction, - "playersToDamage": this.GetPlayersToDamage(data.attackerOwner, data.friendlyFire), - "type": data.type, - "attackerOwner": data.attackerOwner + "playersToDamage": this.GetPlayersToDamage(data.attackerOwner, data.splash.friendlyFire) }); } @@ -131,17 +133,11 @@ // 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)) + if (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); - + this.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner); return; } @@ -157,14 +153,8 @@ 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 - }); + this.HandleAttackEffects(data.type, data.attackData, ent, data.attacker, data.attackerOwner); + cmpProjectileManager.RemoveProjectile(data.projectileId); break; } @@ -173,15 +163,14 @@ /** * 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 {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 {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) @@ -222,49 +211,42 @@ 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 - }); + this.HandleAttackEffects(data.type + ".Splash", data.attackData, ent, data.attacker, data.attackerOwner, damageMultiplier); } }; -/** - * 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) +Damage.prototype.HandleAttackEffects = function(attackType, attackData, target, attacker, attackerOwner, bonusMultiplier = 1) { - let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); + let cmpDamageReceiver = Engine.QueryInterface(target, IID_DamageReceiver); if (!cmpDamageReceiver) return; - let targetState = cmpDamageReceiver.TakeDamage(data.strengths, data.multiplier); + for (let effectType of this.effectTypes) + { + if (!attackData[effectType]) + continue; - 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()); + bonusMultiplier *= !attackData.Bonuses ? 1 : GetAttackBonus(attacker, target, attackType, attackData.Bonuses); - if (targetState.killed) - this.TargetKilled(data.attacker, data.target, data.attackerOwner); + let targetState = cmpDamageReceiver["Take" + effectType](attackData[effectType], attacker, attackerOwner, bonusMultiplier); - Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change, "attackerOwner": data.attackerOwner }); + 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.inflictedEffects || [], + }); + } }; /** 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 @@ -390,11 +390,13 @@ { ret.attack[type] = {}; if (type == "Capture") - ret.attack[type] = cmpAttack.GetAttackStrengths(type); + ret.attack[type] = cmpAttack.GetAttackData(type); else - ret.attack[type].damage = cmpAttack.GetAttackStrengths(type); + ret.attack[type].damage = cmpAttack.GetAttackData(type).Damage; ret.attack[type].splash = cmpAttack.GetSplashDamage(type); + if (ret.attack[type].splash) + ret.attack[type].splash.damage = ret.attack[type].splash.attackData.Damage; let range = cmpAttack.GetRange(type); ret.attack[type].minRange = range.min; @@ -434,7 +436,7 @@ let cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver); 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 @@ -179,7 +179,7 @@ /** * @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 +188,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 +202,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 +215,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 @@ -52,15 +52,7 @@ 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 - }); + cmpDamage.HandleAttackEffects(statusName, { "Damage": { [statusName]: status.damage } }, this.entity, -1, -1); if (status.timeElapsed >= status.duration) this.RemoveEffect(statusName); 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 @@ -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.GetAttackData("Capture"), { "Capture": 8 }); - TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged"), { - "Hack": 0, - "Pierce": 10, - "Crush": 0 + TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackData("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.GetAttackData("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" @@ -192,10 +210,10 @@ TS_ASSERT(cmpAttack.GetBonusTemplate("Capture") === null); - let getAttackBonus = (s, t, e) => GetDamageBonus(s, e, t, cmpAttack.GetBonusTemplate(t)); + let getAttackBonus = (s, t, e, splash) => GetAttackBonus(s, e, t, cmpAttack.GetBonusTemplate(t, splash)); 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 @@ -25,13 +25,16 @@ 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 +54,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, { @@ -90,12 +93,15 @@ AddMock(target, IID_Health, {}); AddMock(target, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { damageTaken = true; return { "killed": false, "change": -multiplier * strengths.crush }; }, + "TakeDamage": (_, effectData, __, ___, bonusMultiplier) => { + damageTaken = true; + return { "killed": false, "HPchange": -bonusMultiplier * effectData.Crush }; + }, }); 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 +120,17 @@ function TestDamage() { - cmpTimer.OnUpdate({ turnLength: 1 }); + cmpTimer.OnUpdate({ "turnLength": 1 }); TS_ASSERT(damageTaken); damageTaken = false; } - cmpDamage.CauseDamage(data); + cmpDamage.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; + cmpDamage.HandleAttackEffects(data.type, data.attackData, data.target, data.attacker, data.attackerOwner); TestDamage(); // Check for damage still being dealt if the attacker dies @@ -152,18 +159,18 @@ 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)); }; @@ -189,26 +196,26 @@ }); AddMock(60, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { + "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) => { + "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) => { + "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) }; } }); @@ -221,10 +228,10 @@ data.direction = new Vector3D(0.6, 747, 0.8); AddMock(60, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { + "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) }; } }); @@ -277,48 +284,48 @@ }); 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) }; + "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) }; + "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) }; + "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) => { + "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) }; + "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({ + "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 }); } @@ -344,15 +351,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, { @@ -375,10 +380,10 @@ AddMock(60, IID_Health, {}); AddMock(60, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { + "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) }; } }); @@ -414,9 +419,9 @@ }); AddMock(60, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { + "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) }; } }); @@ -434,10 +439,10 @@ AddMock(61, IID_Health, {}); AddMock(61, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { - TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100); + "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) }; } }); @@ -450,12 +455,11 @@ 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] @@ -463,10 +467,10 @@ let dealtDamage = 0; AddMock(61, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { - dealtDamage += multiplier * (strengths.hack + strengths.pierce + strengths.crush); + "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) }; } }); @@ -480,10 +484,10 @@ AddMock(62, IID_Health, {}); AddMock(62, IID_DamageReceiver, { - "TakeDamage": (strengths, multiplier) => { - TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 200 * 0.75); + "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) }; } }); @@ -512,35 +516,35 @@ "HasClass": cl => cl == "Cavalry" }); - data.bonus = bonus; + data.attackData.Bonuses = bonus; cmpDamage.MissileHit(data, 0); TS_ASSERT(hitEnts.has(61)); TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 200); dealtDamage = 0; hitEnts.clear(); - data.splashBonus = splashBonus; + data.splash.attackData.Bonuses = splashBonus; cmpDamage.MissileHit(data, 0); TS_ASSERT(hitEnts.has(61)); TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 10000 * 200); dealtDamage = 0; hitEnts.clear(); - data.bonus = undefined; + data.attackData.Bonuses = undefined; cmpDamage.MissileHit(data, 0); TS_ASSERT(hitEnts.has(61)); TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200); dealtDamage = 0; hitEnts.clear(); - data.bonus = null; + data.attackData.Bonuses = null; cmpDamage.MissileHit(data, 0); TS_ASSERT(hitEnts.has(61)); TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200); dealtDamage = 0; hitEnts.clear(); - data.bonus = {}; + data.attackData.Bonuses = {}; cmpDamage.MissileHit(data, 0); TS_ASSERT(hitEnts.has(61)); TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200); 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/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/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