#region Copyright & License Information /* * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, * see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; namespace OpenRA.Traits { public enum TargetType { Invalid, Actor, Terrain, FrozenActor } public struct Target { public static readonly Target[] None = { }; public static readonly Target Invalid = new Target { type = TargetType.Invalid }; TargetType type; Actor actor; IEnumerable targetable; FrozenActor frozen; WPos pos; int generation; public static Target FromPos(WPos p) { return new Target { pos = p, type = TargetType.Terrain }; } public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell) { return new Target { pos = w.Map.CenterOfSubCell(c, subCell), type = TargetType.Terrain }; } public static Target FromOrder(World w, Order o) { return o.TargetActor != null ? FromActor(o.TargetActor) : FromCell(w, o.TargetLocation); } public static Target FromActor(Actor a) { if (a == null) return Invalid; return new Target { actor = a, targetable = a.TraitsImplementing(), type = TargetType.Actor, generation = a.Generation, }; } public static Target FromFrozenActor(FrozenActor a) { return new Target { frozen = a, type = TargetType.FrozenActor }; } public Actor Actor { get { return actor; } } public FrozenActor FrozenActor { get { return frozen; } } public TargetType Type { get { if (type == TargetType.Actor) { // Actor is no longer in the world if (!actor.IsInWorld || actor.IsDead) return TargetType.Invalid; // Actor generation has changed (teleported or captured) if (actor.Generation != generation) return TargetType.Invalid; } return type; } } public bool IsValidFor(Actor targeter) { if (targeter == null || Type == TargetType.Invalid) return false; var targeted = this.actor; if (targeted != null && !targetable.Any(t => t.IsTraitEnabled() && t.TargetableBy(targeted, targeter))) return false; return true; } // Currently all or nothing. // TODO: either replace based on target type or put in singleton trait public bool RequiresForceFire { get { return targetable != null && targetable.Any(Exts.IsTraitEnabled) && targetable.Where(Exts.IsTraitEnabled).All(t => t.RequiresForceFire); } } // Representative position - see Positions for the full set of targetable positions. public WPos CenterPosition { get { switch (Type) { case TargetType.Actor: return actor.CenterPosition; case TargetType.FrozenActor: return frozen.CenterPosition; case TargetType.Terrain: return pos; default: case TargetType.Invalid: throw new InvalidOperationException("Attempting to query the position of an invalid Target"); } } } // Positions available to target for range checks static readonly WPos[] NoPositions = { }; public IEnumerable Positions { get { switch (Type) { case TargetType.Actor: var targetable = actor.TraitsImplementing().Where(Exts.IsTraitEnabled); if (!targetable.Any()) return new[] { actor.CenterPosition }; var targeted = this.actor; var positions = targetable.SelectMany(t => t.TargetablePositions(targeted)).Distinct(); return positions.Any() ? positions : new[] { targeted.CenterPosition }; case TargetType.FrozenActor: return new[] { frozen.CenterPosition }; case TargetType.Terrain: return new[] { pos }; default: case TargetType.Invalid: return NoPositions; } } } public bool IsInRange(WPos origin, WDist range) { if (Type == TargetType.Invalid) return false; // Target ranges are calculated in 2D, so ignore height differences return Positions.Any(t => (t - origin).HorizontalLengthSquared <= range.LengthSquared); } public override string ToString() { switch (Type) { case TargetType.Actor: return actor.ToString(); case TargetType.FrozenActor: return frozen.ToString(); case TargetType.Terrain: return pos.ToString(); default: case TargetType.Invalid: return "Invalid"; } } } }