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,6 +2,22 @@ var g_AttackTypes = ["Melee", "Ranged", "Capture"]; +Attack.prototype.statusEffectsSchema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + Attack.prototype.bonusesSchema = "" + "" + @@ -187,6 +203,7 @@ "" + "" + "" + + Attack.prototype.statusEffectsSchema + Attack.prototype.bonusesSchema + Attack.prototype.preferredClassesSchema + Attack.prototype.restrictedClassesSchema + @@ -577,7 +594,8 @@ "bonus": this.GetBonusTemplate(type), "isSplash": false, "attackerOwner": attackerOwner, - "attackImpactSound": attackImpactSound + "attackImpactSound": attackImpactSound, + "statusEffects": this.template[type].StatusEffects }; if (this.template[type].Splash) { 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 @@ -93,6 +93,7 @@ * @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. @@ -136,6 +137,12 @@ data.multiplier = GetDamageBonus(data.target, data.bonus); this.CauseDamage(data); cmpProjectileManager.RemoveProjectile(data.projectileId); + + // Do the status change, eg. poisoning, burning etc. + let cmpStatus = Engine.QueryInterface(data.target, IID_Status); + if (cmpStatus && data.statusEffects) + cmpStatus.StartEffects(data.statusEffects); + return; } @@ -236,7 +243,7 @@ * @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 og the attacker. + * @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. Index: binaries/data/mods/public/simulation/components/Status.js =================================================================== --- binaries/data/mods/public/simulation/components/Status.js +++ binaries/data/mods/public/simulation/components/Status.js @@ -0,0 +1,69 @@ +function Status() {} + +Status.prototype.Init = function() +{ + this.currentStatuses = {}; +}; + +Status.prototype.StartEffects = function(statusEffects) +{ + for (let effect in statusEffects) + this.StartEffect(effect, statusEffects[effect]); +}; + +Status.prototype.StartEffect = function(statusName, data) +{ + if (this.currentStatuses[statusName]) + return; + + this.currentStatuses[statusName] = {}; + let status = this.currentStatuses[statusName]; + status.duration = +data.Duration; + status.interval = +data.Interval; + status.damage = +data.Damage; + status.timeElapsed = 0; + status.firstTime = true; + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + status.timer = cmpTimer.SetInterval(this.entity, IID_Status, "ExecuteEffect", 0, +status.interval, statusName); +}; + +Status.prototype.StopEffect = function(statusName) { + if (!this.currentStatuses[statusName]) + return; + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.currentStatuses[statusName].timer); + this.currentStatuses[statusName] = undefined; +}; + +Status.prototype.ExecuteEffect = function(statusName, lateness) +{ + let status = this.currentStatuses[statusName]; + if (!status) + return; + + if (status.firstTime) + { + status.firstTime = false; + status.timeElapsed += lateness; + } + 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 + }); + + if (status.timeElapsed >= status.duration) + this.StopEffect(statusName); +}; + +Engine.RegisterComponentType(IID_Status, "Status", Status); Index: binaries/data/mods/public/simulation/components/interfaces/Status.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/Status.js +++ binaries/data/mods/public/simulation/components/interfaces/Status.js @@ -0,0 +1 @@ +Engine.RegisterInterface("Status"); 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 @@ -12,6 +12,7 @@ Engine.LoadComponentScript("interfaces/Loot.js"); Engine.LoadComponentScript("interfaces/Player.js"); Engine.LoadComponentScript("interfaces/Promotion.js"); +Engine.LoadComponentScript("interfaces/Status.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("Attack.js"); Index: binaries/data/mods/public/simulation/components/tests/test_Status.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_Status.js +++ binaries/data/mods/public/simulation/components/tests/test_Status.js @@ -0,0 +1,116 @@ +Engine.LoadComponentScript("interfaces/Damage.js"); +Engine.LoadComponentScript("interfaces/Status.js"); +Engine.LoadComponentScript("interfaces/Timer.js"); + +Engine.LoadComponentScript("Status.js"); +Engine.LoadComponentScript("Timer.js"); + +let target = 42; +let cmpStatus; +let cmpTimer; +let dealtDamage; + +function setup() +{ + cmpStatus = ConstructComponent(target, "Status"); + cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer"); + dealtDamage = 0; +}; + +function testStartEffects() +{ + setup(); + let statusName = "Burn"; + AddMock(SYSTEM_ENTITY, IID_Damage, { + "CauseDamage": (data) => { dealtDamage += data.strengths[statusName]; } + }); + + // damage scheduled: 0, 10, 20 sec + cmpStatus.StartEffects({[statusName]: { + "Duration": 20000, + "Interval": 10000, + "Damage": 1 + }}); + + cmpTimer.OnUpdate({ turnLength: 1 }); + TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec + + cmpTimer.OnUpdate({ turnLength: 8 }); + TS_ASSERT_EQUALS(dealtDamage, 1); // 9 sec + + cmpTimer.OnUpdate({ turnLength: 1 }); + TS_ASSERT_EQUALS(dealtDamage, 2); // 10 sec + + cmpTimer.OnUpdate({ turnLength: 10 }); + TS_ASSERT_EQUALS(dealtDamage, 3); // 20 sec + + cmpTimer.OnUpdate({ turnLength: 10 }); + TS_ASSERT_EQUALS(dealtDamage, 3); // 30 sec +}; + +testStartEffects(); + +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"]; + } + }); + + // damage scheduled: 0, 1, 2, 10 sec + cmpStatus.StartEffects({ + "Burn": { + "Duration": 20000, + "Interval": 10000, + "Damage": 10 + }, + "Poison": { + "Duration": 3000, + "Interval": 1000, + "Damage": 1 + } + }); + + cmpTimer.OnUpdate({ turnLength: 1 }); + TS_ASSERT_EQUALS(dealtDamage, 12); // 1 sec + + cmpTimer.OnUpdate({ turnLength: 1 }); + TS_ASSERT_EQUALS(dealtDamage, 13); // 2 sec + + cmpTimer.OnUpdate({ turnLength: 1 }); + TS_ASSERT_EQUALS(dealtDamage, 13); // 3 sec + + cmpTimer.OnUpdate({ turnLength: 7 }); + TS_ASSERT_EQUALS(dealtDamage, 23); // 10 sec +}; + +testMultipleEffects(); + +function testStopEffect() +{ + setup(); + let statusName = "Poison"; + AddMock(SYSTEM_ENTITY, IID_Damage, { + "CauseDamage": (data) => { dealtDamage += data.strengths[statusName]; } + }); + + // damage scheduled: 0, 10, 20 sec + cmpStatus.StartEffects({[statusName]: { + "Duration": 20000, + "Interval": 10000, + "Damage": 1 + }}); + + cmpTimer.OnUpdate({ turnLength: 1 }); + TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec + + cmpStatus.StopEffect(statusName); + + cmpTimer.OnUpdate({ turnLength: 10 }); + TS_ASSERT_EQUALS(dealtDamage, 1); // 11 sec +}; + +testStopEffect(); Index: binaries/data/mods/public/simulation/templates/template_structure.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_structure.xml +++ binaries/data/mods/public/simulation/templates/template_structure.xml @@ -1,5 +1,6 @@ + 1 Index: binaries/data/mods/public/simulation/templates/template_unit.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_unit.xml +++ binaries/data/mods/public/simulation/templates/template_unit.xml @@ -1,5 +1,6 @@ + 1 Index: binaries/data/mods/public/simulation/templates/units/iber_champion_cavalry.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/iber_champion_cavalry.xml +++ binaries/data/mods/public/simulation/templates/units/iber_champion_cavalry.xml @@ -4,6 +4,13 @@ 5 15 + + + 50000 + 1000 + 1 + +