diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index ef56cb64a3..7399465b2f 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -111,10 +111,13 @@ namespace OpenRa.Game public void InflictDamage(Actor attacker, int damage, WarheadInfo warhead) { /* todo: auto-retaliate, etc */ - /* todo: death sequence for infantry based on inflictor */ if (IsDead) return; /* overkill! don't count extra hits as more kills! */ + /* apply the damage modifiers, if we have any. */ + damage = (int)traits.WithInterface().Aggregate( + (float)damage, (a, t) => t.GetDamageModifier() * a); + Health -= damage; if (Health <= 0) { diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index bd1e25d090..ba20ebf291 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -183,6 +183,7 @@ + diff --git a/OpenRa.Game/Traits/Activities/Move.cs b/OpenRa.Game/Traits/Activities/Move.cs index 7d07b4d550..4392571a68 100755 --- a/OpenRa.Game/Traits/Activities/Move.cs +++ b/OpenRa.Game/Traits/Activities/Move.cs @@ -168,7 +168,11 @@ namespace OpenRa.Game.Traits.Activities var oldFraction = moveFraction; var oldTotal = moveFractionTotal; - moveFraction += ( self.Info as MobileInfo ).Speed; + var actualSpeed = (int)self.traits.WithInterface().Aggregate( + (float)(self.Info as MobileInfo).Speed, + (a, t) => t.GetSpeedModifier() * a); + + moveFraction += actualSpeed; UpdateCenterLocation( self, mobile ); if( moveFraction >= moveFractionTotal ) { diff --git a/OpenRa.Game/Traits/Helicopter.cs b/OpenRa.Game/Traits/Helicopter.cs index 67354418c0..5a958f16ee 100644 --- a/OpenRa.Game/Traits/Helicopter.cs +++ b/OpenRa.Game/Traits/Helicopter.cs @@ -48,8 +48,12 @@ namespace OpenRa.Game.Traits Util.TickFacing(ref unit.Facing, desiredFacing, self.Info.ROT); + var actualSpeed = self.traits.WithInterface().Aggregate( + (float)(self.Info as MobileInfo).Speed, + (a, t) => t.GetSpeedModifier() * a); + // .6f going the wrong way; .8f going sideways, 1f going forward. - var rawSpeed = .2f * (self.Info as VehicleInfo).Speed; + var rawSpeed = .2f * actualSpeed; var angle = (unit.Facing - desiredFacing) / 128f * Math.PI; var scale = .4f + .6f * (float)Math.Cos(angle); diff --git a/OpenRa.Game/Traits/RenderInfantry.cs b/OpenRa.Game/Traits/RenderInfantry.cs index e24f166ab3..6e7da392d2 100644 --- a/OpenRa.Game/Traits/RenderInfantry.cs +++ b/OpenRa.Game/Traits/RenderInfantry.cs @@ -26,10 +26,13 @@ namespace OpenRa.Game.Traits if (float2.WithinEpsilon(self.CenterLocation, Util.CenterOfCell(mobile.toCell), 2)) return false; var dir = Util.QuantizeFacing(self.traits.Get().Facing, 8); - if (anim.CurrentSequence.Name.StartsWith("run-")) - anim.ReplaceAnim("run-" + dir); + var takeCover = self.traits.GetOrDefault(); + var prefix = (takeCover != null && takeCover.IsProne) ? "crawl-" : "run-"; + + if (anim.CurrentSequence.Name.StartsWith(prefix)) + anim.ReplaceAnim(prefix + dir); else - anim.PlayRepeating("run-" + dir); + anim.PlayRepeating(prefix + dir); return true; } @@ -40,7 +43,11 @@ namespace OpenRa.Game.Traits { var dir = Util.QuantizeFacing(self.traits.Get().Facing, 8); inAttack = true; - anim.PlayThen("shoot-" + dir, () => inAttack = false); + + var takeCover = self.traits.GetOrDefault(); + var prefix = (takeCover != null && takeCover.IsProne) ? "prone-shoot-" : "shoot-"; + + anim.PlayThen(prefix + dir, () => inAttack = false); } public override void Tick(Actor self) diff --git a/OpenRa.Game/Traits/TakeCover.cs b/OpenRa.Game/Traits/TakeCover.cs new file mode 100644 index 0000000000..e0f70665f3 --- /dev/null +++ b/OpenRa.Game/Traits/TakeCover.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.GameRules; + +namespace OpenRa.Game.Traits +{ + // infantry prone behavior + class TakeCover : ITick, INotifyDamageEx, IDamageModifier, ISpeedModifier + { + const int defaultProneTime = 100; /* ticks, =4s */ + const float proneDamage = .5f; + const float proneSpeed = .5f; + + int remainingProneTime = 0; + + public bool IsProne { get { return remainingProneTime > 0; } } + + public TakeCover(Actor self) {} + + public void Damaged(Actor self, int damage, WarheadInfo warhead) + { + remainingProneTime = defaultProneTime; + } + + public void Tick(Actor self) + { + if (IsProne) + --remainingProneTime; + } + + public float GetDamageModifier() + { + return IsProne ? proneDamage : 1f; + } + + public float GetSpeedModifier() + { + return IsProne ? proneSpeed : 1f; + } + } +} diff --git a/OpenRa.Game/Traits/TraitsInterfaces.cs b/OpenRa.Game/Traits/TraitsInterfaces.cs index 33fe7c7016..32f4781c73 100644 --- a/OpenRa.Game/Traits/TraitsInterfaces.cs +++ b/OpenRa.Game/Traits/TraitsInterfaces.cs @@ -21,4 +21,6 @@ namespace OpenRa.Game.Traits interface INotifyAttack { void Attacking(Actor self); } interface IRenderModifier { IEnumerable> ModifyRender( Actor self, IEnumerable> r ); } + interface IDamageModifier { float GetDamageModifier(); } + interface ISpeedModifier { float GetSpeedModifier(); } } diff --git a/sequences.xml b/sequences.xml index a78a04b9ec..f32518a523 100644 --- a/sequences.xml +++ b/sequences.xml @@ -422,6 +422,22 @@ + + + + + + + + + + + + + + + + @@ -440,6 +456,14 @@ + + + + + + + + @@ -476,6 +500,14 @@ + + + + + + + + @@ -651,6 +683,22 @@ + + + + + + + + + + + + + + + + @@ -700,6 +748,22 @@ + + + + + + + + + + + + + + + + @@ -729,6 +793,22 @@ + + + + + + + + + + + + + + + + @@ -755,6 +835,22 @@ + + + + + + + + + + + + + + + + @@ -775,5 +871,13 @@ + + + + + + + + \ No newline at end of file diff --git a/units.ini b/units.ini index 9428ff18dd..2ef769949d 100755 --- a/units.ini +++ b/units.ini @@ -524,47 +524,47 @@ Traits=Unit, Mobile, RenderInfantry LongDesc=Anti-infantry unit. Not fooled by the \nSpy's disguise.\n Strong vs Infantry\n Weak vs Vehicles [E1] Description=Rifle Infantry -Traits=Unit, Mobile, RenderInfantry, AttackBase +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover LongDesc=General-purpose infantry. Strong vs Infantry\n Weak vs Vehicles [E2] Description=Grenadier -Traits=Unit, Mobile, RenderInfantry, AttackBase +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover FireDelay=15 LongDesc=Infantry armed with grenades. \n Strong vs Buildings, Infantry\n Weak vs Vehicles [E3] Description=Rocket Soldier -Traits=Unit, Mobile, RenderInfantry, AttackBase +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover PrimaryOffset=0,0,0,-13 LongDesc=Anti-tank/Anti-aircraft infantry.\n Strong vs Tanks, Aircraft\n Weak vs Infantry [E4] Description=Flamethrower -Traits=Unit, Mobile, RenderInfantry, AttackBase +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover FireDelay=8 LongDesc=Advanced Anti-infantry unit.\n Strong vs Infantry, Buildings\n Weak vs Vehicles [E6] Description=Engineer -Traits=Unit, Mobile, RenderInfantry +Traits=Unit, Mobile, RenderInfantry, TakeCover Voice=EngineerVoice LongDesc=Infiltrates and captures enemy structures.\n Strong vs Nothing\n Weak vs Everything [SPY] Description=Spy Voice=SpyVoice -Traits=Unit, Mobile, RenderInfantry +Traits=Unit, Mobile, RenderInfantry, TakeCover LongDesc=Infiltrates enemy structures to gather \nintelligence. Exact effect depends on the \nbuilding infiltrated.\n Strong vs Nothing\n Weak vs Everything\n Special Ability: Disguised [THF] Description=Thief Voice=ThiefVoice -Traits=Unit, Mobile, RenderInfantry +Traits=Unit, Mobile, RenderInfantry, TakeCover LongDesc=Infiltrates enemy refineries & \nsilos, and steals money stored there.\n Unarmed [E7] Description=Tanya Voice=TanyaVoice -Traits=Unit, Mobile, RenderInfantry, AttackBase +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover LongDesc=Elite commando infantry, armed with \ndual pistols and C4.\n Strong vs Infantry, Buildings\n Weak vs Vehicles\n Special Ability: Destroy Building with C4 [MEDI] Description=Medic Voice=MedicVoice -Traits=Unit, Mobile, RenderInfantry, AttackBase +Traits=Unit, Mobile, RenderInfantry, AttackBase, TakeCover LongDesc=Heals nearby infantry.\n Strong vs Nothing\n Weak vs Everything