mirror of
https://gitea.wildfiregames.com/0ad/0ad.git
synced 2026-07-21 06:05:51 +00:00
Allow entities to be affected by Status Effects from ranged attacks.
This implements a status effects receiver component (in a similar fashion to the damage receiver component). The plan is to further extend this component notably to handle graphical indication of status effects, and a variety of other effects. Currently implemented: ranged attacks can inflict status effects that reduce HP over time. This can be resisted by armour. No units currently utilise this in-game but with proper graphics support that could be changed. Patch By: Mate-86 Reviewed By: wraitii References #1912 Differential Revision: https://code.wildfiregames.com/D1252 This was SVN commit r22304.
This commit is contained in:
@@ -2,6 +2,22 @@ function Attack() {}
|
||||
|
||||
var g_AttackTypes = ["Melee", "Ranged", "Capture"];
|
||||
|
||||
Attack.prototype.statusEffectsSchema =
|
||||
"<optional>" +
|
||||
"<element name='StatusEffects' a:help='Effects like poisioning or burning a unit.'>" +
|
||||
"<oneOrMore>" +
|
||||
"<element>" +
|
||||
"<anyName/>" +
|
||||
"<interleave>" +
|
||||
"<element name='Duration' a:help='The duration of the status while the effect occurs.'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='Interval' a:help='Interval between the occurances of the effect.'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='Damage' a:help='Damage caused by the effect.'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
"</oneOrMore>" +
|
||||
"</element>" +
|
||||
"</optional>";
|
||||
|
||||
Attack.prototype.bonusesSchema =
|
||||
"<optional>" +
|
||||
"<element name='Bonuses'>" +
|
||||
@@ -187,6 +203,7 @@ Attack.prototype.Schema =
|
||||
"</optional>" +
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
Attack.prototype.statusEffectsSchema +
|
||||
Attack.prototype.bonusesSchema +
|
||||
Attack.prototype.preferredClassesSchema +
|
||||
Attack.prototype.restrictedClassesSchema +
|
||||
@@ -577,7 +594,8 @@ Attack.prototype.PerformAttack = function(type, target)
|
||||
"bonus": this.GetBonusTemplate(type),
|
||||
"isSplash": false,
|
||||
"attackerOwner": attackerOwner,
|
||||
"attackImpactSound": attackImpactSound
|
||||
"attackImpactSound": attackImpactSound,
|
||||
"statusEffects": this.template[type].StatusEffects
|
||||
};
|
||||
if (this.template[type].Splash)
|
||||
{
|
||||
|
||||
@@ -93,6 +93,7 @@ Damage.prototype.GetPlayersToDamage = function(attackerOwner, friendlyFire)
|
||||
* @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 @@ Damage.prototype.MissileHit = function(data, lateness)
|
||||
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 @@ Damage.prototype.CauseSplashDamage = function(data)
|
||||
* @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.
|
||||
|
||||
@@ -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);
|
||||
+1
@@ -0,0 +1 @@
|
||||
Engine.RegisterInterface("StatusEffectsReceiver");
|
||||
@@ -12,6 +12,7 @@ Engine.LoadComponentScript("interfaces/Health.js");
|
||||
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");
|
||||
|
||||
+119
@@ -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();
|
||||
@@ -141,6 +141,7 @@
|
||||
<BarHeight>0.6</BarHeight>
|
||||
<HeightOffset>12.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<StatusEffectsReceiver/>
|
||||
<TerritoryDecay>
|
||||
<DecayRate>20</DecayRate>
|
||||
</TerritoryDecay>
|
||||
|
||||
@@ -111,6 +111,7 @@
|
||||
<BarHeight>0.333</BarHeight>
|
||||
<HeightOffset>5.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<StatusEffectsReceiver/>
|
||||
<UnitAI>
|
||||
<DefaultStance>aggressive</DefaultStance>
|
||||
<FleeDistance>12.0</FleeDistance>
|
||||
|
||||
Reference in New Issue
Block a user