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.GetAttackTemplate = 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.template = this.GetAttackTemplate(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; }; @@ -604,11 +609,10 @@ "type": type, "attacker": this.entity, "target": target, - "strengths": this.GetAttackStrengths(type), + "template": this.GetAttackTemplate(type), "position": realTargetPosition, "direction": missileDirection, "projectileId": id, - "bonus": this.GetBonusTemplate(type), "isSplash": false, "attackerOwner": attackerOwner, "attackImpactSound": attackImpactSound, @@ -620,48 +624,12 @@ 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.splashTemplate = this.GetAttackTemplate(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 - }); - } 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.GetAttackTemplate(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 @@ -111,15 +111,14 @@ cmpSoundManager.PlaySoundGroupAtPosition(data.attackImpactSound, data.position); // Do this first in case the direct hit kills the target - if (data.isSplash) + if (data.splashTemplate) { this.CauseSplashDamage({ "attacker": data.attacker, "origin": Vector2D.from3D(data.position), "radius": data.radius, "shape": data.shape, - "strengths": data.splashStrengths, - "splashBonus": data.splashBonus, + "splashTemplate": data.splashTemplate, "direction": data.direction, "playersToDamage": this.GetPlayersToDamage(data.attackerOwner, data.friendlyFire), "type": data.type, @@ -131,17 +130,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.template, data.target, data.attacker, data.attackerOwner); return; } @@ -157,14 +150,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, ent, data.target, data.attacker, data.attackerOwner); + cmpProjectileManager.RemoveProjectile(data.projectileId); break; } @@ -222,19 +209,43 @@ warn("The " + data.shape + " splash damage shape is not implemented!"); } - if (data.splashBonus) - damageMultiplier *= GetDamageBonus(data.attacker, ent, data.type, data.splashBonus); + cmpDamage.HandleAttackEffects(data.type + ".Splash", data.splashTemplate, ent, data.attacker, data.attackerOwner); + } +}; + +Damage.prototype.HandleAttackEffects = function(name, attackTemplate, target, attacker, attackerOwner) +{ + if (attackTemplate.Damage) + { + let multiplier = 1; + if (attackTemplate.Bonuses) + multiplier = GetDamageBonus(attacker, target, name, attackTemplate.Bonuses); - // 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 + "type": name, + "target": target, + "attacker": attacker, + "attackerOwner": attackerOwner, + "strengths": attackTemplate.Damage, + "multiplier": multiplier }); } + + if (attackTemplate.Capture) + this.CauseCapture({ + "type": name, + "target": target, + "attacker": attacker, + "attackerOwner": attackerOwner, + "template": attackTemplate + }); + + if (attackTemplate.StatusEffects) + { + let cmpStatusReceiver = Engine.QueryInterface(data.target, IID_StatusEffectsReceiver); + if (cmpStatusReceiver) + cmpStatusReceiver.InflictEffects(attackTemplate.StatusEffects); + } }; /** @@ -267,6 +278,32 @@ Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change, "attackerOwner": data.attackerOwner }); }; +Damage.prototype.CauseCapture = function(data) +{ + if (data.attackerOwner == INVALID_PLAYER) + return; + + let multiplier = GetDamageBonus(data.attacker, data.target, data.type, data.template.Bonuses); + let cmpHealth = Engine.QueryInterface(data.target, IID_Health); + if (!cmpHealth || cmpHealth.GetHitpoints() == 0) + return; + multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints()); + + let cmpCapturable = Engine.QueryInterface(data.target, IID_Capturable); + if (!cmpCapturable || !cmpCapturable.CanCapture(data.attackerOwner)) + return; + + let strength = data.template.Capture * multiplier; + if (cmpCapturable.Reduce(strength, data.attackerOwner) && IsOwnedByEnemyOfPlayer(data.attackerOwner, data.target)) + Engine.PostMessage(data.target, MT_Attacked, { + "attacker": data.attacker, + "target": data.target, + "type": data.type, + "damage": data.strength, + "attackerOwner": data.attackerOwner + }); +}; + /** * Gets entities near a give point for given players. * @param {Vector2D} origin - The point to check around. 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,9 +390,9 @@ { ret.attack[type] = {}; if (type == "Capture") - ret.attack[type] = cmpAttack.GetAttackStrengths(type); + ret.attack[type] = cmpAttack.GetAttackTemplate(type); else - ret.attack[type].damage = cmpAttack.GetAttackStrengths(type); + ret.attack[type].damage = cmpAttack.GetAttackTemplate(type).Damage; ret.attack[type].splash = cmpAttack.GetSplashDamage(type); 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