313 lines
7.2 KiB
C#
313 lines
7.2 KiB
C#
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<AttackBaseInfo>
|
|
{
|
|
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<T>(Actor self) where T : UnitStance
|
|
{
|
|
var stance = self.TraitOrDefault<T>();
|
|
|
|
return stance != null && stance.Active;
|
|
}
|
|
|
|
public static void DeactivateOthers(Actor self, UnitStance stance)
|
|
{
|
|
self.TraitsImplementing<UnitStance>().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<AttackBase>();
|
|
|
|
// 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<UnitStance>().Where(t => t.Active).FirstOrDefault();
|
|
}
|
|
|
|
public static void AttackTarget(Actor self, Actor target, bool holdStill)
|
|
{
|
|
if (target != null)
|
|
self.Trait<AttackBase>().AttackTarget(Target.FromActor(target), false, !holdStill);
|
|
}
|
|
|
|
public static void StopAttack(Actor self)
|
|
{
|
|
if (self.GetCurrentActivity() is Activities.Attack)
|
|
self.GetCurrentActivity().Cancel(self);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when on the first tick after the stance has been activated
|
|
/// </summary>
|
|
/// <param name="self"></param>
|
|
protected virtual void OnScan(Actor self)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when on the first tick after the stance has been activated
|
|
/// </summary>
|
|
/// <param name="self"></param>
|
|
protected virtual void OnActivate(Actor self)
|
|
{
|
|
}
|
|
|
|
public static Actor ScanForTarget(Actor self)
|
|
{
|
|
return self.Trait<AttackBase>().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++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |