1 #region Copyright & License Information
2 /*
3  * Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
4  * This file is part of OpenRA, which is free software. It is made
5  * available to you under the terms of the GNU General Public License
6  * as published by the Free Software Foundation, either version 3 of
7  * the License, or (at your option) any later version. For more
8  * information, see COPYING.
9  */
10 #endregion
11 
12 using System;
13 using System.Collections.Generic;
14 using System.Linq;
15 
16 namespace OpenRA.Traits
17 {
18 	public enum TargetType : byte { Invalid, Actor, Terrain, FrozenActor }
19 	public struct Target
20 	{
21 		public static readonly Target[] None = { };
22 		public static readonly Target Invalid = default(Target);
23 
24 		readonly TargetType type;
25 		readonly Actor actor;
26 		readonly FrozenActor frozen;
27 		readonly WPos terrainCenterPosition;
28 		readonly WPos[] terrainPositions;
29 		readonly CPos? cell;
30 		readonly SubCell? subCell;
31 		readonly int generation;
32 
TargetOpenRA.Traits.Target33 		Target(WPos terrainCenterPosition, WPos[] terrainPositions = null)
34 		{
35 			type = TargetType.Terrain;
36 			this.terrainCenterPosition = terrainCenterPosition;
37 			this.terrainPositions = terrainPositions ?? new[] { terrainCenterPosition };
38 
39 			actor = null;
40 			frozen = null;
41 			cell = null;
42 			subCell = null;
43 			generation = 0;
44 		}
45 
TargetOpenRA.Traits.Target46 		Target(World w, CPos c, SubCell subCell)
47 		{
48 			type = TargetType.Terrain;
49 			terrainCenterPosition = w.Map.CenterOfSubCell(c, subCell);
50 			terrainPositions = new[] { terrainCenterPosition };
51 			cell = c;
52 			this.subCell = subCell;
53 
54 			actor = null;
55 			frozen = null;
56 			generation = 0;
57 		}
58 
TargetOpenRA.Traits.Target59 		Target(Actor a)
60 		{
61 			type = TargetType.Actor;
62 			actor = a;
63 			generation = a.Generation;
64 
65 			terrainCenterPosition = WPos.Zero;
66 			terrainPositions = null;
67 			frozen = null;
68 			cell = null;
69 			subCell = null;
70 		}
71 
TargetOpenRA.Traits.Target72 		Target(FrozenActor fa)
73 		{
74 			type = TargetType.FrozenActor;
75 			frozen = fa;
76 
77 			terrainCenterPosition = WPos.Zero;
78 			terrainPositions = null;
79 			actor = null;
80 			cell = null;
81 			subCell = null;
82 			generation = 0;
83 		}
84 
FromPosOpenRA.Traits.Target85 		public static Target FromPos(WPos p) { return new Target(p); }
FromTargetPositionsOpenRA.Traits.Target86 		public static Target FromTargetPositions(Target t) { return new Target(t.CenterPosition, t.Positions.ToArray()); }
FromCellOpenRA.Traits.Target87 		public static Target FromCell(World w, CPos c, SubCell subCell = SubCell.FullCell) { return new Target(w, c, subCell); }
FromActorOpenRA.Traits.Target88 		public static Target FromActor(Actor a) { return a != null ? new Target(a) : Invalid; }
FromFrozenActorOpenRA.Traits.Target89 		public static Target FromFrozenActor(FrozenActor fa) { return new Target(fa); }
90 
91 		public Actor Actor { get { return actor; } }
92 		public FrozenActor FrozenActor { get { return frozen; } }
93 
94 		public TargetType Type
95 		{
96 			get
97 			{
98 				if (type == TargetType.Actor)
99 				{
100 					// Actor is no longer in the world
101 					if (!actor.IsInWorld || actor.IsDead)
102 						return TargetType.Invalid;
103 
104 					// Actor generation has changed (teleported or captured)
105 					if (actor.Generation != generation)
106 						return TargetType.Invalid;
107 				}
108 
109 				return type;
110 			}
111 		}
112 
IsValidForOpenRA.Traits.Target113 		public bool IsValidFor(Actor targeter)
114 		{
115 			if (targeter == null)
116 				return false;
117 
118 			switch (Type)
119 			{
120 				case TargetType.Actor:
121 					return actor.IsTargetableBy(targeter);
122 				case TargetType.FrozenActor:
123 					return frozen.IsValid && frozen.Visible && !frozen.Hidden;
124 				case TargetType.Invalid:
125 					return false;
126 				default:
127 				case TargetType.Terrain:
128 					return true;
129 			}
130 		}
131 
132 		// Currently all or nothing.
133 		// TODO: either replace based on target type or put in singleton trait
134 		public bool RequiresForceFire
135 		{
136 			get
137 			{
138 				if (actor == null)
139 					return false;
140 
141 				// PERF: Avoid LINQ.
142 				var isTargetable = false;
143 				foreach (var targetable in actor.Targetables)
144 				{
145 					if (!targetable.IsTraitEnabled())
146 						continue;
147 
148 					isTargetable = true;
149 					if (!targetable.RequiresForceFire)
150 						return false;
151 				}
152 
153 				return isTargetable;
154 			}
155 		}
156 
157 		// Representative position - see Positions for the full set of targetable positions.
158 		public WPos CenterPosition
159 		{
160 			get
161 			{
162 				switch (Type)
163 				{
164 					case TargetType.Actor:
165 						return actor.CenterPosition;
166 					case TargetType.FrozenActor:
167 						return frozen.CenterPosition;
168 					case TargetType.Terrain:
169 						return terrainCenterPosition;
170 					default:
171 					case TargetType.Invalid:
172 						throw new InvalidOperationException("Attempting to query the position of an invalid Target");
173 				}
174 			}
175 		}
176 
177 		// Positions available to target for range checks
178 		static readonly WPos[] NoPositions = { };
179 		public IEnumerable<WPos> Positions
180 		{
181 			get
182 			{
183 				switch (Type)
184 				{
185 					case TargetType.Actor:
186 						return actor.GetTargetablePositions();
187 					case TargetType.FrozenActor:
188 						// TargetablePositions may be null if it is Invalid
189 						return frozen.TargetablePositions ?? NoPositions;
190 					case TargetType.Terrain:
191 						return terrainPositions;
192 					default:
193 					case TargetType.Invalid:
194 						return NoPositions;
195 				}
196 			}
197 		}
198 
IsInRangeOpenRA.Traits.Target199 		public bool IsInRange(WPos origin, WDist range)
200 		{
201 			if (Type == TargetType.Invalid)
202 				return false;
203 
204 			// Target ranges are calculated in 2D, so ignore height differences
205 			return Positions.Any(t => (t - origin).HorizontalLengthSquared <= range.LengthSquared);
206 		}
207 
ToStringOpenRA.Traits.Target208 		public override string ToString()
209 		{
210 			switch (Type)
211 			{
212 				case TargetType.Actor:
213 					return actor.ToString();
214 
215 				case TargetType.FrozenActor:
216 					return frozen.ToString();
217 
218 				case TargetType.Terrain:
219 					return terrainCenterPosition.ToString();
220 
221 				default:
222 				case TargetType.Invalid:
223 					return "Invalid";
224 			}
225 		}
226 
227 		// Expose internal state for serialization by the orders code *only*
228 		internal TargetType SerializableType { get { return type; } }
229 		internal Actor SerializableActor { get { return actor; } }
230 		internal CPos? SerializableCell { get { return cell; } }
231 		internal SubCell? SerializableSubCell { get { return subCell; } }
232 		internal WPos SerializablePos { get { return terrainCenterPosition; } }
233 	}
234 }
235