#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * 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, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; namespace OpenRA.Traits { public enum TargetType : byte { Invalid, Actor, Terrain, FrozenActor } public readonly struct Target : IEquatable { public static readonly Target[] None = Array.Empty(); public static readonly Target Invalid = default; public Actor Actor { get; } public FrozenActor FrozenActor { get; } readonly TargetType type; readonly WPos terrainCenterPosition; readonly WPos[] terrainPositions; readonly CPos? cell; readonly SubCell? subCell; readonly int generation; Target(WPos terrainCenterPosition, WPos[] terrainPositions = null) { type = TargetType.Terrain; this.terrainCenterPosition = terrainCenterPosition; this.terrainPositions = terrainPositions ?? new[] { terrainCenterPosition }; Actor = null; FrozenActor = null; cell = null; subCell = null; generation = 0; } Target(World w, CPos c, SubCell subCell) { type = TargetType.Terrain; terrainCenterPosition = w.Map.CenterOfSubCell(c, subCell); terrainPositions = new[] { terrainCenterPosition }; cell = c; this.subCell = subCell; Actor = null; FrozenActor = null; generation = 0; } Target(Actor a, int generation) { type = TargetType.Actor; Actor = a; this.generation = generation; terrainCenterPosition = WPos.Zero; terrainPositions = null; FrozenActor = null; cell = null; subCell = null; } Target(FrozenActor fa) { type = TargetType.FrozenActor; FrozenActor = fa; terrainCenterPosition = WPos.Zero; terrainPositions = null; Actor = null; cell = null; subCell = null; generation = 0; } public static Target FromPos(WPos p) { return new Target(p); } public static Target FromTargetPositions(in Target t) { return new Target(t.CenterPosition, t.Positions.ToArray()); } public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell) { return new Target(w, c, subCell); } public static Target FromActor(Actor a) { return a != null ? new Target(a, a.Generation) : Invalid; } public static Target FromFrozenActor(FrozenActor fa) { return new Target(fa); } 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) return false; switch (Type) { case TargetType.Actor: return Actor.IsTargetableBy(targeter); case TargetType.FrozenActor: return FrozenActor.IsValid && FrozenActor.Visible && !FrozenActor.Hidden; case TargetType.Invalid: return false; default: case TargetType.Terrain: return true; } } // Currently all or nothing. // TODO: either replace based on target type or put in singleton trait public bool RequiresForceFire { get { if (Actor == null) return false; // PERF: Avoid LINQ. var isTargetable = false; foreach (var targetable in Actor.Targetables) { if (!targetable.IsTraitEnabled()) continue; isTargetable = true; if (!targetable.RequiresForceFire) return false; } return isTargetable; } } // 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 FrozenActor.CenterPosition; case TargetType.Terrain: return terrainCenterPosition; 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 = Array.Empty(); public IEnumerable Positions { get { switch (Type) { case TargetType.Actor: return Actor.GetTargetablePositions(); case TargetType.FrozenActor: // TargetablePositions may be null if it is Invalid return FrozenActor.TargetablePositions ?? NoPositions; case TargetType.Terrain: return terrainPositions; 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 FrozenActor.ToString(); case TargetType.Terrain: return terrainCenterPosition.ToString(); default: case TargetType.Invalid: return "Invalid"; } } public static bool operator ==(in Target me, in Target other) { if (me.type != other.type) return false; switch (me.type) { case TargetType.Terrain: return me.terrainCenterPosition == other.terrainCenterPosition && me.terrainPositions == other.terrainPositions && me.cell == other.cell && me.subCell == other.subCell; case TargetType.Actor: return me.Actor == other.Actor && me.generation == other.generation; case TargetType.FrozenActor: return me.FrozenActor == other.FrozenActor; default: case TargetType.Invalid: return false; } } public static bool operator !=(in Target me, in Target other) { return !(me == other); } public override int GetHashCode() { switch (type) { case TargetType.Terrain: var hash = terrainCenterPosition.GetHashCode() ^ terrainPositions.GetHashCode(); if (cell != null) hash ^= cell.GetHashCode(); if (subCell != null) hash ^= subCell.GetHashCode(); return hash; case TargetType.Actor: return Actor.GetHashCode() ^ generation.GetHashCode(); case TargetType.FrozenActor: return FrozenActor.GetHashCode(); default: case TargetType.Invalid: return 0; } } public bool Equals(Target other) { return other == this; } public override bool Equals(object other) { return other is Target t && t == this; } // Expose internal state for serialization by the orders code *only* internal static Target FromSerializedActor(Actor a, int generation) { return a != null ? new Target(a, generation) : Invalid; } internal (TargetType Type, Actor Actor, int Generation, CPos? Cell, SubCell? SubCell, WPos Pos) SerializableState => (type, Actor, generation, cell, subCell, terrainCenterPosition); } }