using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Traits; namespace OpenRA.Mods.RA { public class UnitStanceInfo : ITraitInfo, ITraitPrerequisite { public readonly bool Default = false; public readonly int ScanDelayMin = 12; public readonly int ScanDelayMax = 24; #region ITraitInfo Members public virtual object Create(ActorInitializer init) { throw new Exception("UnitStanceInfo: Override me!"); } #endregion } public abstract class UnitStance : ITick, IResolveOrder, ISelectionColorModifier, IPostRenderSelection { [Sync] public int NextScanTime; public UnitStanceInfo Info { get; protected set; } public abstract Color SelectionColor { get; } [Sync] public bool AllowMultiTrigger { get; protected set; } #region ITick Members protected UnitStance(Actor self, UnitStanceInfo info) { Info = info; Active = Info.Default; } public virtual void Tick(Actor self) { if (!Active) return; TickScan(self); OnTick(self); } protected virtual void OnTick(Actor self) { } private void TickScan(Actor self) { NextScanTime--; if (NextScanTime <= 0) { NextScanTime = GetNextScanTime(self); OnScan(self); } } private int GetNextScanTime(Actor self) { return self.World.SharedRandom.Next(Info.ScanDelayMin, Info.ScanDelayMax+1); } #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 && !AllowMultiTrigger) return; Active = true; NextScanTime = 0; DeactivateOthers(self); OnActivate(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 DeactivateOthers(Actor self, UnitStance stance) { self.TraitsImplementing().Where(t => t != stance).Do(t => t.Deactivate(self)); } public abstract string OrderString { get; } 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) { if (target != null) self.Trait().AttackTarget(Target.FromActor(target), false, !holdStill); } public static void StopAttack(Actor self) { if (self.GetCurrentActivity() is Activities.Attack) self.GetCurrentActivity().Cancel(self); } /// /// 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 OnActivate(Actor self) { } public static Actor ScanForTarget(Actor self) { return self.Trait().ScanForTarget(self, null); } public void ResolveOrder(Actor self, Order order) { if (order.OrderString == OrderString) { // Its our order, activate the stance Activate(self); return; // Do not call OnOrder on our own stance order } if (!Active) return; OnOrder(self, order); } protected virtual void OnOrder(Actor self, Order order) { } public static void OrderStance(Actor self, UnitStance stance) { self.World.IssueOrder(new Order(stance.OrderString, self, false)); } public Color GetSelectionColorModifier(Actor self, Color defaultColor) { if (self.World.LocalPlayer != null && self.Owner.Stances[self.World.LocalPlayer] != Stance.Ally) return defaultColor; return Active ? SelectionColor : defaultColor; } public void RenderAfterWorld(WorldRenderer wr, Actor self) { if (!Active) return; if (!self.IsInWorld) return; if (self.World.LocalPlayer != null && self.Owner.Stances[self.World.LocalPlayer] != Stance.Ally) return; RenderStance(self); } protected virtual string Shape { get { return "xxxx\nx x\nx x\nxxxx"; } } private void RenderStance(Actor self) { var bounds = self.GetBounds(true); var loc = new float2(bounds.Left, bounds.Top) + new float2(0, 1); var max = Math.Max(bounds.Height, bounds.Width); var shape = Shape; // 'Resize' for large actors if (max >= Game.CellSize) { shape = shape.Replace(" ", " "); shape = shape.Replace("x", "xx"); } var color = Color.FromArgb(125, Color.Black); int y = 0; var shapeLines = shape.Split('\n'); foreach (var shapeLine in shapeLines) { for (int yt = 0; yt < ((max >= Game.CellSize) ? 2 : 1); yt++) { int x = 0; foreach (var shapeKey in shapeLine) { if (shapeKey == 'x') { Game.Renderer.LineRenderer.DrawLine(loc + new float2(x, y), loc + new float2(x + 1f, y), color, color); } x++; } y++; } } y = 0; shapeLines = shape.Split('\n'); color = SelectionColor; foreach (var shapeLine in shapeLines) { for (int yt = 0; yt < ((max >= Game.CellSize) ? 2 : 1); yt++) { int x = 0; foreach (var shapeKey in shapeLine) { if (shapeKey == 'x') { Game.Renderer.LineRenderer.DrawLine(loc + new float2(x + 1, y + 1), loc + new float2(x + 1 + 1f, y + 1), color, color); } x++; } y++; } } } } }