Index: ps/trunk/binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Attack.js +++ ps/trunk/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: ps/trunk/binaries/data/mods/public/simulation/components/Damage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Damage.js +++ ps/trunk/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,11 @@ data.multiplier = GetDamageBonus(data.target, 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); + return; } @@ -236,7 +242,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: ps/trunk/binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js +++ ps/trunk/binaries/data/mods/public/simulation/components/StatusEffectsReceiver.js @@ -0,0 +1,69 @@ +function StatusEffectsReceiver() {} + +StatusEffectsReceiver.prototype.Init = function() +{ + this.activeStatusEffects = {}; +}; + +StatusEffectsReceiver.prototype.InflictEffects = function(statusEffects) +{ + for (let effect in statusEffects) + this.InflictEffect(effect, statusEffects[effect]); +}; + +StatusEffectsReceiver.prototype.InflictEffect = function(statusName, data) +{ + if (this.activeStatusEffects[statusName]) + return; + + this.activeStatusEffects[statusName] = {}; + let status = this.activeStatusEffects[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_StatusEffectsReceiver, "ExecuteEffect", 0, +status.interval, statusName); +}; + +StatusEffectsReceiver.prototype.RemoveEffect = function(statusName) { + if (!this.activeStatusEffects[statusName]) + return; + + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.activeStatusEffects[statusName].timer); + this.activeStatusEffects[statusName] = undefined; +}; + +StatusEffectsReceiver.prototype.ExecuteEffect = function(statusName, lateness) +{ + let status = this.activeStatusEffects[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.RemoveEffect(statusName); +}; + +Engine.RegisterComponentType(IID_StatusEffectsReceiver, "StatusEffectsReceiver", StatusEffectsReceiver); Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/StatusEffectsReceiver.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/StatusEffectsReceiver.js +++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/StatusEffectsReceiver.js @@ -0,0 +1 @@ +Engine.RegisterInterface("StatusEffectsReceiver"); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js +++ ps/trunk/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/StatusEffectsReceiver.js"); Engine.LoadComponentScript("interfaces/TechnologyManager.js"); Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("Attack.js"); Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_StatusEffectsReceiver.js @@ -0,0 +1,119 @@ +Engine.LoadComponentScript("interfaces/Damage.js"); +Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js"); +Engine.LoadComponentScript("interfaces/Timer.js"); +Engine.LoadComponentScript("StatusEffectsReceiver.js"); +Engine.LoadComponentScript("Timer.js"); + +var target = 42; +var cmpStatusReceiver; +var cmpTimer; +var dealtDamage; + +function setup() +{ + cmpStatusReceiver = ConstructComponent(target, "StatusEffectsReceiver"); + cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer"); + dealtDamage = 0; +} + +function testInflictEffects() +{ + setup(); + let statusName = "Burn"; + AddMock(SYSTEM_ENTITY, IID_Damage, { + "CauseDamage": (data) => { dealtDamage += data.strengths[statusName]; } + }); + + // damage scheduled: 0, 10, 20 sec + cmpStatusReceiver.InflictEffects({ + [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 +} + +testInflictEffects(); + +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 + cmpStatusReceiver.InflictEffects({ + "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 testRemoveEffect() +{ + setup(); + let statusName = "Poison"; + AddMock(SYSTEM_ENTITY, IID_Damage, { + "CauseDamage": (data) => { dealtDamage += data.strengths[statusName]; } + }); + + // damage scheduled: 0, 10, 20 sec + cmpStatusReceiver.InflictEffects({ + [statusName]: { + "Duration": 20000, + "Interval": 10000, + "Damage": 1 + } + }); + + cmpTimer.OnUpdate({ "turnLength": 1 }); + TS_ASSERT_EQUALS(dealtDamage, 1); // 1 sec + + cmpStatusReceiver.RemoveEffect(statusName); + + cmpTimer.OnUpdate({ "turnLength": 10 }); + TS_ASSERT_EQUALS(dealtDamage, 1); // 11 sec +} + +testRemoveEffect(); Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml @@ -141,6 +141,7 @@ 0.6 12.0 + 20 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml @@ -111,6 +111,7 @@ 0.333 5.0 + aggressive 12.0