diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index 7399465b2f..85f15b5c24 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -108,12 +108,19 @@ namespace OpenRa.Game public bool IsDead { get { return Health <= 0; } } + DamageState GetDamageState() + { + if (Health <= 0) return DamageState.Dead; + var halfStrength = Info.Strength * Rules.General.ConditionYellow; + return Health < halfStrength ? DamageState.Half : DamageState.Normal; + } + public void InflictDamage(Actor attacker, int damage, WarheadInfo warhead) { - /* todo: auto-retaliate, etc */ - if (IsDead) return; /* overkill! don't count extra hits as more kills! */ + var oldState = GetDamageState(); + /* apply the damage modifiers, if we have any. */ damage = (int)traits.WithInterface().Aggregate( (float)damage, (a, t) => t.GetDamageModifier() * a); @@ -126,24 +133,19 @@ namespace OpenRa.Game attacker.Owner.Kills++; Game.world.AddFrameEndTask(w => w.Remove(this)); - - if (Owner == Game.LocalPlayer && !traits.Contains()) - Sound.Play("unitlst1.aud"); - - if (traits.Contains()) - Sound.Play("kaboom22.aud"); } - var halfStrength = Info.Strength * Rules.General.ConditionYellow; - if (Health < halfStrength && (Health + damage) >= halfStrength) - { - /* we just went below half health! */ - foreach (var nd in traits.WithInterface()) - nd.Damaged(this, DamageState.Half); - } + var newState = GetDamageState(); - foreach (var ndx in traits.WithInterface()) - ndx.Damaged(this, damage, warhead); + foreach (var nd in traits.WithInterface()) + nd.Damaged(this, new AttackInfo + { + Attacker = attacker, + Damage = damage, + DamageState = newState, + DamageStateChanged = newState != oldState, + Warhead = warhead + }); } public void QueueActivity( IActivity nextActivity ) diff --git a/OpenRa.Game/Exts.cs b/OpenRa.Game/Exts.cs index cc04e81a91..3de1b767a5 100644 --- a/OpenRa.Game/Exts.cs +++ b/OpenRa.Game/Exts.cs @@ -25,7 +25,7 @@ namespace OpenRa.Game public static float Product(this IEnumerable xs) { - return xs.Aggregate((a, x) => a * x); + return xs.Aggregate(1f, (a, x) => a * x); } } } diff --git a/OpenRa.Game/OpenRa.Game.csproj b/OpenRa.Game/OpenRa.Game.csproj index 40e700ea59..292381043d 100644 --- a/OpenRa.Game/OpenRa.Game.csproj +++ b/OpenRa.Game/OpenRa.Game.csproj @@ -2,7 +2,7 @@ Debug AnyCPU - 9.0.21022 + 9.0.30729 2.0 {0DFB103F-2962-400F-8C6D-E2C28CCBA633} WinExe @@ -159,11 +159,11 @@ + - - + diff --git a/OpenRa.Game/Traits/AttackInfo.cs b/OpenRa.Game/Traits/AttackInfo.cs new file mode 100644 index 0000000000..52ad8177d5 --- /dev/null +++ b/OpenRa.Game/Traits/AttackInfo.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.GameRules; + +namespace OpenRa.Game.Traits +{ + class AttackInfo + { + public Actor Attacker; + public WarheadInfo Warhead; + public int Damage; + public DamageState DamageState; + public bool DamageStateChanged; + } +} diff --git a/OpenRa.Game/Traits/AttackTurreted.cs b/OpenRa.Game/Traits/AttackTurreted.cs index 286d97bb2c..67e9f022a1 100755 --- a/OpenRa.Game/Traits/AttackTurreted.cs +++ b/OpenRa.Game/Traits/AttackTurreted.cs @@ -3,7 +3,7 @@ using OpenRa.Game.GameRules; namespace OpenRa.Game.Traits { - class AttackTurreted : AttackBase + class AttackTurreted : AttackBase, INotifyBuildComplete { public AttackTurreted( Actor self ) : base(self) { self.traits.Get(); } @@ -13,6 +13,9 @@ namespace OpenRa.Game.Traits if( !CanAttack( self ) ) return; + if (self.traits.Contains() && !buildComplete) + return; /* base defenses can't do anything until they finish building !*/ + var turreted = self.traits.Get(); turreted.desiredFacing = Util.GetFacing( target.CenterLocation - self.CenterLocation, turreted.turretFacing ); if( turreted.desiredFacing != turreted.turretFacing ) @@ -38,5 +41,8 @@ namespace OpenRa.Game.Traits Math.Max( 0, (int)Rules.WeaponInfo[ weapon ].Range - RangeTolerance ) ) ); self.traits.Get().target = order.TargetActor; } + + bool buildComplete = false; + public void BuildingComplete(Actor self) { buildComplete = true; } } } diff --git a/OpenRa.Game/Traits/Building.cs b/OpenRa.Game/Traits/Building.cs index a0f9b8b786..df080e2188 100644 --- a/OpenRa.Game/Traits/Building.cs +++ b/OpenRa.Game/Traits/Building.cs @@ -2,7 +2,7 @@ namespace OpenRa.Game.Traits { - class Building : ITick + class Building : ITick, INotifyDamage { public readonly BuildingInfo unitInfo; @@ -19,5 +19,11 @@ namespace OpenRa.Game.Traits first = false; } + + public void Damaged(Actor self, AttackInfo e) + { + if (e.DamageState == DamageState.Dead) + Sound.Play("kaboom22.aud"); + } } } diff --git a/OpenRa.Game/Traits/Explodes.cs b/OpenRa.Game/Traits/Explodes.cs new file mode 100644 index 0000000000..7392d08712 --- /dev/null +++ b/OpenRa.Game/Traits/Explodes.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRa.Game.Effects; + +namespace OpenRa.Game.Traits +{ + class Explodes : INotifyDamage + { + public Explodes(Actor self) {} + + public void Damaged(Actor self, AttackInfo e) + { + if (self.IsDead) + { + Game.world.AddFrameEndTask( + w => w.Add(new Bullet("UnitExplode", e.Attacker.Owner, e.Attacker, + self.CenterLocation.ToInt2(), self.CenterLocation.ToInt2()))); + } + } + } +} diff --git a/OpenRa.Game/Traits/Harvester.cs b/OpenRa.Game/Traits/Harvester.cs index 9426921f66..1b2f15422b 100644 --- a/OpenRa.Game/Traits/Harvester.cs +++ b/OpenRa.Game/Traits/Harvester.cs @@ -1,6 +1,7 @@ -namespace OpenRa.Game.Traits +using System.Drawing; +namespace OpenRa.Game.Traits { - class Harvester : IOrder + class Harvester : IOrder, IPips { public int oreCarried = 0; /* sum of these must not exceed capacity */ public int gemsCarried = 0; @@ -53,5 +54,14 @@ self.QueueActivity( new Traits.Activities.DeliverOre( order.TargetActor ) ); } } + + public Color GetBorderColor() { return Color.Black; } + public int GetPipCount() { return 7; } + public Color GetColorForPip(int index) + { + if ((oreCarried + gemsCarried)*1.0f/Rules.General.BailCount* GetPipCount() < index + 1) + return Color.Transparent; + return Color.LimeGreen; + } } } diff --git a/OpenRa.Game/Traits/RenderBuilding.cs b/OpenRa.Game/Traits/RenderBuilding.cs index 0975f20921..f580d9fcce 100644 --- a/OpenRa.Game/Traits/RenderBuilding.cs +++ b/OpenRa.Game/Traits/RenderBuilding.cs @@ -65,9 +65,12 @@ namespace OpenRa.Game.Traits yield return Tuple.New(anim.Image, 24f * (float2)self.Location, pal); } - public virtual void Damaged(Actor self, DamageState state) + public virtual void Damaged(Actor self, AttackInfo e) { - switch( state ) + if (!e.DamageStateChanged) + return; + + switch( e.DamageState ) { case DamageState.Normal: anim.ReplaceAnim("idle"); diff --git a/OpenRa.Game/Traits/RenderBuildingTurreted.cs b/OpenRa.Game/Traits/RenderBuildingTurreted.cs index aa759a16db..61819c3dec 100644 --- a/OpenRa.Game/Traits/RenderBuildingTurreted.cs +++ b/OpenRa.Game/Traits/RenderBuildingTurreted.cs @@ -14,9 +14,11 @@ namespace OpenRa.Game.Traits anim.PlayFacing(a, () => self.traits.Get().turretFacing); } - public override void Damaged(Actor self, DamageState ds) + public override void Damaged(Actor self, AttackInfo e) { - switch (ds) + if (!e.DamageStateChanged) return; + + switch (e.DamageState) { case DamageState.Normal: PlayTurretAnim(self, "idle"); diff --git a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs index 10dd2e334c..03b88929c2 100644 --- a/OpenRa.Game/Traits/RenderBuildingWarFactory.cs +++ b/OpenRa.Game/Traits/RenderBuildingWarFactory.cs @@ -51,11 +51,12 @@ namespace OpenRa.Game.Traits roof.PlayThen(prefix + "build-top", () => isOpen = true); } - public override void Damaged(Actor self, DamageState ds) + public override void Damaged(Actor self, AttackInfo e) { - base.Damaged(self, ds); + base.Damaged(self, e); - switch (ds) + if (!e.DamageStateChanged) return; + switch (e.DamageState) { case DamageState.Normal: prefix = ""; diff --git a/OpenRa.Game/Traits/RenderInfantry.cs b/OpenRa.Game/Traits/RenderInfantry.cs index 7a643f3cff..735577a635 100644 --- a/OpenRa.Game/Traits/RenderInfantry.cs +++ b/OpenRa.Game/Traits/RenderInfantry.cs @@ -8,7 +8,7 @@ using OpenRa.Game.Effects; namespace OpenRa.Game.Traits { - class RenderInfantry : RenderSimple, INotifyAttack, INotifyDamageEx + class RenderInfantry : RenderSimple, INotifyAttack, INotifyDamage { public RenderInfantry(Actor self) : base(self) @@ -75,12 +75,10 @@ namespace OpenRa.Game.Traits yield return Util.Centered(self, anim.Image, self.CenterLocation); } - public void Damaged(Actor self, int damage, WarheadInfo warhead) + public void Damaged(Actor self, AttackInfo e) { - if (self.Health <= 0) - Game.world.AddFrameEndTask(w => w.Add(new Corpse(self, warhead.InfDeath))); + if (e.DamageState == DamageState.Dead) + Game.world.AddFrameEndTask(w => w.Add(new Corpse(self, e.Warhead.InfDeath))); } - - public void Damaged(Actor self, DamageState ds) {} } } diff --git a/OpenRa.Game/Traits/RenderUnit.cs b/OpenRa.Game/Traits/RenderUnit.cs index d4bd96505c..5d6522a651 100644 --- a/OpenRa.Game/Traits/RenderUnit.cs +++ b/OpenRa.Game/Traits/RenderUnit.cs @@ -5,7 +5,7 @@ using OpenRa.Game.GameRules; namespace OpenRa.Game.Traits { - class RenderUnit : RenderSimple, INotifyDamageEx + class RenderUnit : RenderSimple, INotifyDamage { public RenderUnit(Actor self) : base(self) @@ -33,24 +33,20 @@ namespace OpenRa.Game.Traits } bool isSmoking; - DamageState currentDs; Animation smoke; - public void Damaged(Actor self, DamageState ds) { currentDs = ds; } - - public void Damaged(Actor self, int damage, WarheadInfo warhead) + public void Damaged(Actor self, AttackInfo e) { - if (currentDs != DamageState.Half) return; - if (!isSmoking) - { - isSmoking = true; - smoke.PlayThen("idle", - () => smoke.PlayThen("loop", - () => smoke.PlayBackwardsThen("end", - () => isSmoking = false))); - } - } + if (e.DamageState != DamageState.Half) return; + if (isSmoking) return; + isSmoking = true; + smoke.PlayThen("idle", + () => smoke.PlayThen("loop", + () => smoke.PlayBackwardsThen("end", + () => isSmoking = false))); + } + public override void Tick(Actor self) { base.Tick(self); diff --git a/OpenRa.Game/Traits/TakeCover.cs b/OpenRa.Game/Traits/TakeCover.cs index e0f70665f3..a8aafe9ca8 100644 --- a/OpenRa.Game/Traits/TakeCover.cs +++ b/OpenRa.Game/Traits/TakeCover.cs @@ -7,7 +7,7 @@ using OpenRa.Game.GameRules; namespace OpenRa.Game.Traits { // infantry prone behavior - class TakeCover : ITick, INotifyDamageEx, IDamageModifier, ISpeedModifier + class TakeCover : ITick, INotifyDamage, IDamageModifier, ISpeedModifier { const int defaultProneTime = 100; /* ticks, =4s */ const float proneDamage = .5f; @@ -19,9 +19,10 @@ namespace OpenRa.Game.Traits public TakeCover(Actor self) {} - public void Damaged(Actor self, int damage, WarheadInfo warhead) + public void Damaged(Actor self, AttackInfo e) { - remainingProneTime = defaultProneTime; + if (e.Damage > 0) /* fix to allow healing via `damage` */ + remainingProneTime = defaultProneTime; } public void Tick(Actor self) diff --git a/OpenRa.Game/Traits/TraitsInterfaces.cs b/OpenRa.Game/Traits/TraitsInterfaces.cs index ee23a2cf82..d5998373e0 100644 --- a/OpenRa.Game/Traits/TraitsInterfaces.cs +++ b/OpenRa.Game/Traits/TraitsInterfaces.cs @@ -9,8 +9,7 @@ namespace OpenRa.Game.Traits interface ITick { void Tick(Actor self); } interface IRender { IEnumerable> Render(Actor self); } - interface INotifyDamage { void Damaged(Actor self, DamageState ds); } - interface INotifyDamageEx { void Damaged(Actor self, int damage, WarheadInfo warhead); } + interface INotifyDamage { void Damaged(Actor self, AttackInfo e); } interface INotifyBuildComplete { void BuildingComplete (Actor self); } interface IOrder { diff --git a/OpenRa.Game/Traits/Unit.cs b/OpenRa.Game/Traits/Unit.cs index 53d1cff06b..98f005c8d3 100755 --- a/OpenRa.Game/Traits/Unit.cs +++ b/OpenRa.Game/Traits/Unit.cs @@ -1,11 +1,18 @@  namespace OpenRa.Game.Traits { - class Unit + class Unit : INotifyDamage { public int Facing; public int Altitude; public Unit( Actor self ) { } + + public void Damaged(Actor self, AttackInfo e) + { + if (e.DamageState == DamageState.Dead) + if (self.Owner == Game.LocalPlayer) + Sound.Play("unitlst1.aud"); + } } } diff --git a/OpenRa.Game/World.cs b/OpenRa.Game/World.cs index fff8797889..da2905de3e 100644 --- a/OpenRa.Game/World.cs +++ b/OpenRa.Game/World.cs @@ -21,13 +21,8 @@ namespace OpenRa.Game public void AddFrameEndTask( Action a ) { frameEndActions.Add( a ); } public event Action ActorAdded = _ => { }; - public event Action ActorRemoved = a => - { - a.Health = 0; /* make sure everyone sees it as dead */ - foreach (var nr in a.traits.WithInterface()) - nr.Damaged(a, DamageState.Dead); - }; - + public event Action ActorRemoved = _ => { }; + public void Tick() { foreach (var a in actors) a.Tick(); diff --git a/sequences.xml b/sequences.xml index f32518a523..c07e576fb2 100644 --- a/sequences.xml +++ b/sequences.xml @@ -523,6 +523,7 @@ + diff --git a/units.ini b/units.ini index 5c7999f85a..0c03238ad0 100755 --- a/units.ini +++ b/units.ini @@ -57,7 +57,7 @@ Voice=VehicleVoice LongDesc=Regenerates Fog of War in a small area \naround the unit.\n Unarmed [ARTY] Description=Artillery -Traits=Unit, Mobile, AttackBase, RenderUnit +Traits=Unit, Mobile, AttackBase, RenderUnit, Explodes Voice=VehicleVoice LongDesc=Long-range artillery.\n Strong vs Infantry, Buildings\n Weak vs Tanks, Aircraft [HARV] @@ -607,10 +607,17 @@ ParaBomb DogJaw Heal SCUD +UnitExplode [TeslaZap] RenderAsTesla=true +[UnitExplode] +Damage=500 +Speed=100 +Projectile=Invisible +Warhead=UnitExplodeWarhead + @@ -646,11 +653,19 @@ HollowPoint Super Organic Nuke +UnitExplodeWarhead [HE] ImpactSound=kaboom25 WaterImpactSound=splash9 +[UnitExplodeWarhead] +Spread=10 +Verses=90%,75%,60%,25%,100% +Explosion=8 +InfDeath=3 +ImpactSound=kaboom15 + [General] OreChance=.02 LowPowerSlowdown=3