From 6b40abb58cfee789371b5e7ae6a734de5e312a15 Mon Sep 17 00:00:00 2001 From: geckosoft Date: Thu, 11 Nov 2010 04:28:11 +0100 Subject: [PATCH] Implemented: Stances Added: Basic stances (Aggressive, Guard (Hold Ground), Hold Fire, None (dummy), Return Fire) Added: WorldCommandWidget (to be able to set said stances) Added: WorldCommandWidget to ra (cnc will follow, later on) Changed: Added support to AttackBase for firing with movement disabled + utility method ScanForTarget (used by stances) Added: AssignUnitStance (attach this to unit-producing actors, together with what stances can be picked as 'default') --- OpenRA.Mods.RA/Activities/Attack.cs | 14 +- OpenRA.Mods.RA/Air/AttackHeli.cs | 2 +- OpenRA.Mods.RA/Air/AttackPlane.cs | 2 +- OpenRA.Mods.RA/AttackBase.cs | 62 +++-- OpenRA.Mods.RA/AttackFrontal.cs | 4 +- OpenRA.Mods.RA/AttackLeap.cs | 4 +- OpenRA.Mods.RA/AttackOmni.cs | 2 +- OpenRA.Mods.RA/AttackTurreted.cs | 2 +- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 8 + .../UnitStances/AssignUnitStance.cs | 29 +++ OpenRA.Mods.RA/UnitStances/UnitStance.cs | 218 ++++++++++++++++++ .../UnitStances/UnitStanceAggressive.cs | 56 +++++ OpenRA.Mods.RA/UnitStances/UnitStanceGuard.cs | 54 +++++ .../UnitStances/UnitStanceHoldFire.cs | 38 +++ OpenRA.Mods.RA/UnitStances/UnitStanceNone.cs | 21 ++ .../UnitStances/UnitStanceReturnFire.cs | 38 +++ OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs | 110 +++++++++ mods/ra/chrome/ingame.yaml | 5 + 18 files changed, 640 insertions(+), 29 deletions(-) create mode 100644 OpenRA.Mods.RA/UnitStances/AssignUnitStance.cs create mode 100644 OpenRA.Mods.RA/UnitStances/UnitStance.cs create mode 100644 OpenRA.Mods.RA/UnitStances/UnitStanceAggressive.cs create mode 100644 OpenRA.Mods.RA/UnitStances/UnitStanceGuard.cs create mode 100644 OpenRA.Mods.RA/UnitStances/UnitStanceHoldFire.cs create mode 100644 OpenRA.Mods.RA/UnitStances/UnitStanceNone.cs create mode 100644 OpenRA.Mods.RA/UnitStances/UnitStanceReturnFire.cs create mode 100644 OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs diff --git a/OpenRA.Mods.RA/Activities/Attack.cs b/OpenRA.Mods.RA/Activities/Attack.cs index 9462432ba7..d538d4a7d2 100755 --- a/OpenRA.Mods.RA/Activities/Attack.cs +++ b/OpenRA.Mods.RA/Activities/Attack.cs @@ -20,16 +20,24 @@ namespace OpenRA.Mods.RA.Activities { Target Target; int Range; + bool AllowMovement; - public Attack(Target target, int range) + public Attack(Target target, int range, bool allowMovement) { Target = target; Range = range; + AllowMovement = allowMovement; + } + + public Attack(Target target, int range) : this(target, range, true) + { + } public override IActivity Tick( Actor self ) { var attack = self.Trait(); + var ret = InnerTick( self, attack ); attack.IsAttacking = ( ret == this ); return ret; @@ -42,8 +50,8 @@ namespace OpenRA.Mods.RA.Activities if (!Target.IsValid) return NextActivity; - if (!Combat.IsInRange( self.CenterLocation, Range, Target)) - return Util.SequenceActivities( self.Trait().MoveWithinRange( Target, Range ), this ); + if (!Combat.IsInRange(self.CenterLocation, Range, Target)) + return (AllowMovement) ? Util.SequenceActivities(self.Trait().MoveWithinRange(Target, Range), this) : NextActivity; var desiredFacing = Util.GetFacing(Target.CenterLocation - self.CenterLocation, 0); var renderUnit = self.TraitOrDefault(); diff --git a/OpenRA.Mods.RA/Air/AttackHeli.cs b/OpenRA.Mods.RA/Air/AttackHeli.cs index 8e134c24f3..d95665fc9b 100755 --- a/OpenRA.Mods.RA/Air/AttackHeli.cs +++ b/OpenRA.Mods.RA/Air/AttackHeli.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.RA.Air { public AttackHeli(Actor self, AttackHeliInfo info) : base(self, info) { } - protected override IActivity GetAttackActivity(Actor self, Target newTarget) + protected override IActivity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { return new HeliAttack( newTarget ); } diff --git a/OpenRA.Mods.RA/Air/AttackPlane.cs b/OpenRA.Mods.RA/Air/AttackPlane.cs index 4293b137f6..620c06b3eb 100755 --- a/OpenRA.Mods.RA/Air/AttackPlane.cs +++ b/OpenRA.Mods.RA/Air/AttackPlane.cs @@ -22,7 +22,7 @@ namespace OpenRA.Mods.RA.Air { public AttackPlane(Actor self, AttackPlaneInfo info) : base(self, info) { } - protected override IActivity GetAttackActivity(Actor self, Target newTarget) + protected override IActivity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { return new FlyAttack( newTarget ); } diff --git a/OpenRA.Mods.RA/AttackBase.cs b/OpenRA.Mods.RA/AttackBase.cs index d99f256987..d9eb4e42a6 100644 --- a/OpenRA.Mods.RA/AttackBase.cs +++ b/OpenRA.Mods.RA/AttackBase.cs @@ -147,9 +147,10 @@ namespace OpenRA.Mods.RA public void ResolveOrder(Actor self, Order order) { - if (order.OrderString == "Attack") + if (order.OrderString == "Attack" || order.OrderString == "AttackHold") { - self.QueueActivity(order.Queued, GetAttackActivity(self, Target.FromOrder(order))); + bool allowMove = order.OrderString == "Attack"; + self.QueueActivity(order.Queued, GetAttackActivity(self, Target.FromOrder(order), allowMove)); if (self.Owner == self.World.LocalPlayer) self.World.AddFrameEndTask(w => @@ -163,24 +164,29 @@ namespace OpenRA.Mods.RA if (order.TargetActor != null) line.SetTarget(self, Target.FromOrder(order), Color.Red); else line.SetTarget(self, Target.FromOrder(order), Color.Red); }); - } - else + return; + } // else not an attack order + + // StopAttack order cancels the current activity IF it is an attack one + if (order.OrderString == "StopAttack") { - target = Target.None; - - /* hack */ - if (self.HasTrait() && self.Info.Traits.Get().AlignIdleTurrets) - self.Trait().desiredFacing = null; + if (self.GetCurrentActivity() is Activities.Attack) + self.GetCurrentActivity().Cancel(self); } + target = Target.None; + + /* hack */ + if (self.HasTrait() && self.Info.Traits.Get().AlignIdleTurrets) + self.Trait().desiredFacing = null; } public string VoicePhraseForOrder(Actor self, Order order) { - return (order.OrderString == "Attack") ? "Attack" : null; + return (order.OrderString == "Attack" || order.OrderString == "AttackHold") ? "Attack" : null; } - - protected abstract IActivity GetAttackActivity(Actor self, Target newTarget); + + protected abstract IActivity GetAttackActivity(Actor self, Target newTarget, bool allowMove); public bool HasAnyValidWeapons(Target t) { return Weapons.Any(w => w.IsValidAgainst(self.World, t)); } public float GetMaximumRange() { return Weapons.Max(w => w.Info.Range); } @@ -188,26 +194,30 @@ namespace OpenRA.Mods.RA public Weapon ChooseWeaponForTarget(Target t) { return Weapons.FirstOrDefault(w => w.IsValidAgainst(self.World, t)); } public void AttackTarget(Actor self, Actor target, bool allowMovement) + { + AttackTarget(self, target, allowMovement, false); + } + + public void AttackTarget(Actor self, Actor target, bool allowMovement, bool holdStill) { var attack = self.Trait(); if (target != null) { if (allowMovement) - attack.ResolveOrder(self, new Order("Attack", self, target, false)); + attack.ResolveOrder(self, new Order((holdStill) ? "AttackHold" : "Attack", self, target, false)); else attack.target = Target.FromActor(target); // for turreted things on rails. } } - public void ScanAndAttack(Actor self, bool allowMovement) + public void ScanAndAttack(Actor self, bool allowMovement, bool holdStill) { if (--nextScanTime <= 0) { - var attack = self.Trait(); - var range = attack.GetMaximumRange(); + var targetActor = ScanForTarget(self); - if (!attack.target.IsValid || !Combat.IsInRange( self.CenterLocation, range, attack.target )) - AttackTarget(self, ChooseTarget(self, range), allowMovement); + if (targetActor != null) + AttackTarget(self, targetActor, allowMovement, holdStill); var info = self.Info.Traits.Get(); nextScanTime = (int)(25 * (info.ScanTimeAverage + @@ -215,6 +225,22 @@ namespace OpenRA.Mods.RA } } + public Actor ScanForTarget(Actor self) + { + var attack = self.Trait(); + var range = attack.GetMaximumRange(); + + if ((!attack.target.IsValid || self.IsIdle) || !Combat.IsInRange(self.CenterLocation, range, attack.target)) + return ChooseTarget(self, range); + + return null; + } + + public void ScanAndAttack(Actor self, bool allowMovement) + { + ScanAndAttack(self, allowMovement, false); + } + Actor ChooseTarget(Actor self, float range) { var inRange = self.World.FindUnitsInCircle(self.CenterLocation, Game.CellSize * range); diff --git a/OpenRA.Mods.RA/AttackFrontal.cs b/OpenRA.Mods.RA/AttackFrontal.cs index 3dcdf736a4..77ca57c99e 100644 --- a/OpenRA.Mods.RA/AttackFrontal.cs +++ b/OpenRA.Mods.RA/AttackFrontal.cs @@ -40,12 +40,12 @@ namespace OpenRA.Mods.RA return true; } - protected override IActivity GetAttackActivity(Actor self, Target newTarget) + protected override IActivity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { var weapon = ChooseWeaponForTarget(newTarget); if( weapon == null ) return null; - return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Info.Range)); + return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Info.Range), allowMove); } } } diff --git a/OpenRA.Mods.RA/AttackLeap.cs b/OpenRA.Mods.RA/AttackLeap.cs index ce5640e1f0..47eff0edb9 100644 --- a/OpenRA.Mods.RA/AttackLeap.cs +++ b/OpenRA.Mods.RA/AttackLeap.cs @@ -40,12 +40,12 @@ namespace OpenRA.Mods.RA self.QueueActivity(new Leap(self, target)); } - protected override IActivity GetAttackActivity(Actor self, Target newTarget) + protected override IActivity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { var weapon = ChooseWeaponForTarget(newTarget); if( weapon == null ) return null; - return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Info.Range)); + return new Activities.Attack(newTarget, Math.Max(0, (int)weapon.Info.Range), allowMove); } } } diff --git a/OpenRA.Mods.RA/AttackOmni.cs b/OpenRA.Mods.RA/AttackOmni.cs index 039ca79a0e..224d7b2464 100644 --- a/OpenRA.Mods.RA/AttackOmni.cs +++ b/OpenRA.Mods.RA/AttackOmni.cs @@ -38,7 +38,7 @@ namespace OpenRA.Mods.RA DoAttack(self, target); } - protected override IActivity GetAttackActivity(Actor self, Target newTarget) + protected override IActivity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { return new SetTarget( newTarget ); } diff --git a/OpenRA.Mods.RA/AttackTurreted.cs b/OpenRA.Mods.RA/AttackTurreted.cs index 20b0e9b449..610411f575 100644 --- a/OpenRA.Mods.RA/AttackTurreted.cs +++ b/OpenRA.Mods.RA/AttackTurreted.cs @@ -46,7 +46,7 @@ namespace OpenRA.Mods.RA DoAttack( self, target ); } - protected override IActivity GetAttackActivity(Actor self, Target newTarget) + protected override IActivity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { return new AttackActivity( newTarget ); } diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index eae4fa3fbd..8de12b9924 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -59,6 +59,7 @@ + @@ -80,6 +81,7 @@ + @@ -94,6 +96,11 @@ + + + + + @@ -252,6 +259,7 @@ + diff --git a/OpenRA.Mods.RA/UnitStances/AssignUnitStance.cs b/OpenRA.Mods.RA/UnitStances/AssignUnitStance.cs new file mode 100644 index 0000000000..29e03ebb1d --- /dev/null +++ b/OpenRA.Mods.RA/UnitStances/AssignUnitStance.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.UnitStances +{ + public class AssignUnitStanceInfo : TraitInfo + { + + } + + public class AssignUnitStance : INotifyProduction + { + public void UnitProduced(Actor self, Actor other, int2 exit) + { + var stance = UnitStance.GetActive(self); + if (stance == null) + return; + + var target = other.TraitsImplementing().Where(t => t.GetType() == stance.GetType()).FirstOrDefault(); + if (target == null) + return; + + target.Activate(other); + } + } +} diff --git a/OpenRA.Mods.RA/UnitStances/UnitStance.cs b/OpenRA.Mods.RA/UnitStances/UnitStance.cs new file mode 100644 index 0000000000..ab539a21d5 --- /dev/null +++ b/OpenRA.Mods.RA/UnitStances/UnitStance.cs @@ -0,0 +1,218 @@ +using System; +using System.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public interface IUnitStance + { + bool Active { get; set; } + bool IsDefault { get; } + void Activate(Actor self); + void Deactivate(Actor self); + } + + public class UnitStanceInfo : ITraitInfo + { + public readonly bool Default; + + #region ITraitInfo Members + + public virtual object Create(ActorInitializer init) + { + throw new Exception("Do not use UnitStance at the rules!"); + } + + #endregion + } + + public abstract class UnitStance : IUnitStance, ITick + { + public int NextScantime; + public int ScanDelay = 12; // 2x - second + private bool _unsetFirstTick; + + public UnitStanceInfo Info { get; protected set; } + + public bool IsFirstTick { get; private set; } + + public bool IsScanAvailable + { + get + { + NextScantime--; + if (NextScantime <= 0) + { + NextScantime = ScanDelay; + return true; + } + + return false; + } + } + + #region ITick Members + + public virtual void Tick(Actor self) + { + if (!Active) return; + + if (IsFirstTick && _unsetFirstTick) + { + IsFirstTick = false; + _unsetFirstTick = false; + } + else if (IsFirstTick) + { + _unsetFirstTick = true; + OnFirstTick(self); + } + + if (IsScanAvailable) + { + OnScan(self); + } + } + + #endregion + + #region IUnitStance Members + + public bool Active { get; set; } + + public virtual bool IsDefault + { + get { return Info.Default; } + } + + public virtual void Activate(Actor self) + { + if (Active) return; + + Active = true; + IsFirstTick = true; + NextScantime = 0; + _unsetFirstTick = false; + + DeactivateOthers(self); + } + + public virtual void Deactivate(Actor self) + { + if (Active) + { + Active = false; + } + } + + #endregion + + public virtual void DeactivateOthers(Actor self) + { + DeactivateOthers(self, this); + } + + public static bool IsActive(Actor self) where T : UnitStance + { + var stance = self.TraitOrDefault(); + + return stance != null && stance.Active; + } + + public static void ActivateDefault(Actor self) + { + if (!self.TraitsImplementing().Where(t => t.IsDefault).Any()) + { + // deactive all of them as a default if nobody has a default + DeactivateOthers(self, null); + return; + } + + self.TraitsImplementing().Where(t => t.IsDefault).First().Activate(self); + } + + public static void DeactivateOthers(Actor self, IUnitStance stance) + { + self.TraitsImplementing().Where(t => t != stance).Do(t => t.Deactivate(self)); + } + + public static bool ReturnFire(Actor self, AttackInfo e, bool allowActivity, bool allowTargetSwitch, bool holdStill) + { + if (!self.IsIdle && !allowActivity) return false; + if (e.Attacker.Destroyed) return false; + + var attack = self.TraitOrDefault(); + + // this unit cannot fight back at all (no guns) + if (attack == null) return false; + + // if attacking already and force was used, return (ie to respond to attacks while moving around) + if (attack.IsAttacking && (!allowTargetSwitch)) return false; + + // don't fight back if we dont have the guns to do so + if (!attack.HasAnyValidWeapons(Target.FromActor(e.Attacker))) return false; + + // don't retaliate against allies + if (self.Owner.Stances[e.Attacker.Owner] == Stance.Ally) return false; + + // don't retaliate against healers + if (e.Damage < 0) return false; + + // perform the attack + AttackTarget(self, e.Attacker, holdStill); + + return true; + } + + public static bool ReturnFire(Actor self, AttackInfo e, bool allowActivity, bool allowTargetSwitch) + { + return ReturnFire(self, e, allowActivity, allowTargetSwitch, false); + } + + public static bool ReturnFire(Actor self, AttackInfo e, bool allowActivity) + { + return ReturnFire(self, e, allowActivity, false); + } + + public static UnitStance GetActive(Actor self) + { + return self.TraitsImplementing().Where(t => t.Active).FirstOrDefault(); + } + + public static void AttackTarget(Actor self, Actor target, bool holdStill) + { + var attack = self.Trait(); + + if (attack != null && target != null) + { + self.World.IssueOrder(new Order((holdStill) ? "AttackHold" : "Attack", self, target, false)); + } + } + + public static void StopAttack(Actor self) + { + self.World.IssueOrder(new Order("StopAttack", self, self, false)); + } + + /// + /// Called when on the first tick after the stance has been activated + /// + /// + protected virtual void OnScan(Actor self) + { + } + + /// + /// Called when on the first tick after the stance has been activated + /// + /// + protected virtual void OnFirstTick(Actor self) + { + } + + public static Actor ScanForTarget(Actor self) + { + return self.Trait().ScanForTarget(self); + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/UnitStances/UnitStanceAggressive.cs b/OpenRA.Mods.RA/UnitStances/UnitStanceAggressive.cs new file mode 100644 index 0000000000..e35ee05fd9 --- /dev/null +++ b/OpenRA.Mods.RA/UnitStances/UnitStanceAggressive.cs @@ -0,0 +1,56 @@ +using System; +using System.Drawing; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class UnitStanceAggressiveInfo : UnitStanceInfo + { + public override object Create(ActorInitializer init) { return new UnitStanceAggressive(init.self, this); } + } + + /// + /// Inherits the Return Fire damage handler! + /// + public class UnitStanceAggressive : UnitStance, INotifyDamage, ISelectionColorModifier + { + public UnitStanceAggressive(Actor self, UnitStanceAggressiveInfo info) + { + Info = info; + Active = (self.World.LocalPlayer == self.Owner || (self.Owner.IsBot && Game.IsHost)) ? Info.Default : false; + } + + protected override void OnScan(Actor self) + { + if (!self.IsIdle) return; + if (!self.HasTrait()) return; + + var target = ScanForTarget(self); + if (target == null) + return; + + AttackTarget(self, target, false); + } + + protected override void OnFirstTick(Actor self) + { + if (!self.HasTrait()) return; + + if (self.Trait().IsAttacking) + StopAttack(self); + } + + public virtual void Damaged(Actor self, AttackInfo e) + { + if (!Active) return; + if (!self.HasTrait()) return; + + ReturnFire(self, e, false); // only triggers when standing still + } + + public Color GetSelectionColorModifier(Actor self, Color defaultColor) + { + return Active ? Color.Red : defaultColor; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/UnitStances/UnitStanceGuard.cs b/OpenRA.Mods.RA/UnitStances/UnitStanceGuard.cs new file mode 100644 index 0000000000..82ade0777a --- /dev/null +++ b/OpenRA.Mods.RA/UnitStances/UnitStanceGuard.cs @@ -0,0 +1,54 @@ +using System; +using System.Drawing; +using OpenRA.Mods.RA.Move; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class UnitStanceGuardInfo : UnitStanceInfo + { + public override object Create(ActorInitializer init) { return new UnitStanceGuard(init.self, this); } + } + + public class UnitStanceGuard : UnitStance, INotifyDamage, ISelectionColorModifier + { + public UnitStanceGuard(Actor self, UnitStanceGuardInfo info) + { + Info = info; + Active = (self.World.LocalPlayer == self.Owner || (self.Owner.IsBot && Game.IsHost)) ? Info.Default : false; + } + + protected override void OnScan(Actor self) + { + if (!self.IsIdle) return; + if (!self.HasTrait()) return; + + var target = ScanForTarget(self); + if (target == null) + return; + + AttackTarget(self, target, true); + } + + protected override void OnFirstTick(Actor self) + { + if (!self.HasTrait()) return; + + if (self.Trait().IsAttacking) + StopAttack(self); + } + + public void Damaged(Actor self, AttackInfo e) + { + if (!Active) return; + if (!self.HasTrait()) return; + + ReturnFire(self, e, false, false, true); // only triggers when standing still + } + + public virtual Color GetSelectionColorModifier(Actor self, Color defaultColor) + { + return Active ? Color.Yellow : defaultColor; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/UnitStances/UnitStanceHoldFire.cs b/OpenRA.Mods.RA/UnitStances/UnitStanceHoldFire.cs new file mode 100644 index 0000000000..029f97fb47 --- /dev/null +++ b/OpenRA.Mods.RA/UnitStances/UnitStanceHoldFire.cs @@ -0,0 +1,38 @@ +using System; +using System.Drawing; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class UnitStanceHoldFireInfo : UnitStanceInfo + { + public override object Create(ActorInitializer init) { return new UnitStanceHoldFire(init.self, this); } + } + + /// + /// Hold Fire + /// + /// Will not perform any attacks automaticly + /// + public class UnitStanceHoldFire : UnitStance, ISelectionColorModifier + { + public UnitStanceHoldFire(Actor self, UnitStanceHoldFireInfo info) + { + Info = info; + Active = (self.World.LocalPlayer == self.Owner || (self.Owner.IsBot && Game.IsHost)) ? Info.Default : false; + } + + protected override void OnFirstTick(Actor self) + { + if (!self.HasTrait()) return; + + if (self.Trait().IsAttacking) + StopAttack(self); + } + + public Color GetSelectionColorModifier(Actor self, Color defaultColor) + { + return Active ? Color.SpringGreen : defaultColor; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/UnitStances/UnitStanceNone.cs b/OpenRA.Mods.RA/UnitStances/UnitStanceNone.cs new file mode 100644 index 0000000000..826d4880a8 --- /dev/null +++ b/OpenRA.Mods.RA/UnitStances/UnitStanceNone.cs @@ -0,0 +1,21 @@ +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class UnitStanceNoneInfo : ITraitInfo + { + public readonly bool Default = false; + + public object Create(ActorInitializer init) { return new UnitStanceNone(init.self, this); } + } + public class UnitStanceNone : UnitStance + { + public readonly UnitStanceNoneInfo Info; + + public UnitStanceNone(Actor self, UnitStanceNoneInfo info) + { + Info = info; + Active = (self.World.LocalPlayer == self.Owner || (self.Owner.IsBot && Game.IsHost)) ? Info.Default : false; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/UnitStances/UnitStanceReturnFire.cs b/OpenRA.Mods.RA/UnitStances/UnitStanceReturnFire.cs new file mode 100644 index 0000000000..5e48eeaac1 --- /dev/null +++ b/OpenRA.Mods.RA/UnitStances/UnitStanceReturnFire.cs @@ -0,0 +1,38 @@ +using System; +using System.Drawing; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class UnitStanceReturnFireInfo : UnitStanceInfo + { + public override object Create(ActorInitializer init) { return new UnitStanceReturnFire(init.self, this); } + } + + /// + /// Return Fire + /// + /// Will fire only when fired upon + /// + public class UnitStanceReturnFire : UnitStance, INotifyDamage, ISelectionColorModifier + { + public UnitStanceReturnFire(Actor self, UnitStanceReturnFireInfo info) + { + Info = info; + Active = (self.World.LocalPlayer == self.Owner || (self.Owner.IsBot && Game.IsHost)) ? Info.Default : false; + } + + public void Damaged(Actor self, AttackInfo e) + { + if (!Active) return; + if (!self.HasTrait()) return; + + ReturnFire(self, e, false); // only triggers when standing still + } + + public Color GetSelectionColorModifier(Actor self, Color defaultColor) + { + return Active ? Color.Orange : defaultColor; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs new file mode 100644 index 0000000000..194dc5de9a --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs @@ -0,0 +1,110 @@ +using System; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Network; +using OpenRA.Orders; +using OpenRA.Widgets; + +namespace OpenRA.Mods.RA.Widgets +{ + public class WorldCommandWidget : Widget + { + public World World { get { return OrderManager.world; } } + + public char AttackMoveKey = 'a'; + public char GuardKey = 'g'; // (G)uard + // public char DefensiveKey = 'd'; // (D)efensive + public char AggressiveKey = 'a'; // (A)ggressive + public char ReturnFireKey = 'r'; // (R)eturn Fire + public char HoldFire = 'h'; // (h)old fire + public readonly OrderManager OrderManager; + + [ObjectCreator.UseCtor] + public WorldCommandWidget([ObjectCreator.Param] OrderManager orderManager ) + { + OrderManager = orderManager; + } + + public override void DrawInner(WorldRenderer wr) + { + + } + + public override string GetCursor(int2 pos) + { + return null; + } + + public override bool HandleKeyPressInner(KeyInput e) + { + if (World == null) return false; + if (World.LocalPlayer == null) return false; + + return ProcessInput(e); + } + + private bool ProcessInput(KeyInput e) + { + // command: AttackMove + if (e.KeyChar == AttackMoveKey && e.Modifiers == Modifiers.None) + { + return PerformAttackMove(); + } + + // command: GuardStance + if (e.KeyChar == GuardKey && (e.Modifiers.HasModifier(Modifiers.Alt))) + { + return EnableStance(); + } + + // command: AggressiveStance + if (e.KeyChar == AggressiveKey && (e.Modifiers.HasModifier(Modifiers.Alt))) + { + return EnableStance(); + } + + // stance: Return Fire + // description: Fires only when fired upon, stops firing if no longer under attack + if (e.KeyChar == ReturnFireKey && (e.Modifiers.HasModifier(Modifiers.Alt))) + { + return EnableStance(); + } + + // stance: Hold Fire + // description: Prevents attacking (ie no autotarget is being done) + if (e.KeyChar == HoldFire && (e.Modifiers.HasModifier(Modifiers.Alt))) + { + return EnableStance(); + } + + return false; + } + + private bool EnableStance() where T : UnitStance + { + if (World.Selection.Actors.Count() == 0) + return false; + + var traits = + World.Selection.Actors.Where(a => !a.Destroyed && a.Owner == World.LocalPlayer && a.TraitOrDefault() != null && !UnitStance.IsActive(a)). + Select(a => new Pair(a, a.TraitOrDefault()) ); + + World.AddFrameEndTask(w => traits.Do(p => p.Second.Activate(p.First))); + + return traits.Any(); + } + + private bool PerformAttackMove() + { + if (World.Selection.Actors.Count() > 0) + { + World.OrderGenerator = new GenericSelectTarget(World.Selection.Actors, "AttackMove", "attackmove", MouseButton.Right); + + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/mods/ra/chrome/ingame.yaml b/mods/ra/chrome/ingame.yaml index cca7f18b34..cbec32b27b 100644 --- a/mods/ra/chrome/ingame.yaml +++ b/mods/ra/chrome/ingame.yaml @@ -13,6 +13,11 @@ Container@INGAME_ROOT: Y:0 Width:WINDOW_RIGHT Height:WINDOW_BOTTOM + WorldCommand: + X:0 + Y:0 + Width:WINDOW_RIGHT + Height:WINDOW_BOTTOM Timer@GAME_TIMER: Id:GAME_TIMER X: WINDOW_RIGHT/2