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 using OpenRA.Primitives;
16 using OpenRA.Traits;
17 
18 namespace OpenRA.Mods.Common.Traits
19 {
20 	public class TurretedInfo : PausableConditionalTraitInfo, Requires<BodyOrientationInfo>, IActorPreviewInitInfo, IEditorActorOptions
21 	{
22 		public readonly string Turret = "primary";
23 		[Desc("Speed at which the turret turns.")]
24 		public readonly int TurnSpeed = 255;
25 		public readonly int InitialFacing = 0;
26 
27 		[Desc("Number of ticks before turret is realigned. (-1 turns off realignment)")]
28 		public readonly int RealignDelay = 40;
29 
30 		[Desc("Muzzle position relative to turret or body. (forward, right, up) triples")]
31 		public readonly WVec Offset = WVec.Zero;
32 
33 		[Desc("Facing to use for actor previews (map editor, color picker, etc)")]
34 		public readonly int PreviewFacing = 96;
35 
36 		[Desc("Display order for the turret facing slider in the map editor")]
37 		public readonly int EditorTurretFacingDisplayOrder = 4;
38 
IActorPreviewInitInfo.ActorPreviewInits(ActorInfo ai, ActorPreviewType type)39 		IEnumerable<object> IActorPreviewInitInfo.ActorPreviewInits(ActorInfo ai, ActorPreviewType type)
40 		{
41 			// HACK: The ActorInit system does not support multiple instances of the same type
42 			// Make sure that we only return one TurretFacingInit, even for actors with multiple turrets
43 			if (ai.TraitInfos<TurretedInfo>().FirstOrDefault() == this)
44 				yield return new TurretFacingInit(PreviewFacing);
45 		}
46 
IEditorActorOptions.ActorOptions(ActorInfo ai, World world)47 		IEnumerable<EditorActorOption> IEditorActorOptions.ActorOptions(ActorInfo ai, World world)
48 		{
49 			// TODO: Handle multiple turrets properly (will probably require a rewrite of the Init system)
50 			if (ai.TraitInfos<TurretedInfo>().FirstOrDefault() != this)
51 				yield break;
52 
53 			yield return new EditorActorSlider("Turret", EditorTurretFacingDisplayOrder, 0, 255, 8,
54 				actor =>
55 				{
56 					var init = actor.Init<TurretFacingInit>();
57 					if (init != null)
58 						return init.Value(world);
59 
60 					var facingInit = actor.Init<FacingInit>();
61 					if (facingInit != null)
62 						return facingInit.Value(world);
63 
64 					return InitialFacing;
65 				},
66 				(actor, value) =>
67 				{
68 					actor.RemoveInit<TurretFacingsInit>();
69 					actor.ReplaceInit(new TurretFacingInit((int)value));
70 				});
71 		}
72 
Create(ActorInitializer init)73 		public override object Create(ActorInitializer init) { return new Turreted(init, this); }
74 	}
75 
76 	public class Turreted : PausableConditionalTrait<TurretedInfo>, ITick, IDeathActorInitModifier, IActorPreviewInitModifier
77 	{
78 		AttackTurreted attack;
79 		IFacing facing;
80 		BodyOrientation body;
81 
82 		[Sync]
83 		public int QuantizedFacings = 0;
84 
85 		[Sync]
86 		public int TurretFacing = 0;
87 
88 		public int? DesiredFacing;
89 		int realignTick = 0;
90 
91 		// For subclasses that want to move the turret relative to the body
92 		protected WVec localOffset = WVec.Zero;
93 
94 		public WVec Offset { get { return Info.Offset + localOffset; } }
95 		public string Name { get { return Info.Turret; } }
96 
TurretFacingFromInit(IActorInitializer init, int def, string turret = null)97 		public static Func<int> TurretFacingFromInit(IActorInitializer init, int def, string turret = null)
98 		{
99 			if (turret != null && init.Contains<DynamicTurretFacingsInit>())
100 			{
101 				Func<int> facing;
102 				if (init.Get<DynamicTurretFacingsInit, Dictionary<string, Func<int>>>().TryGetValue(turret, out facing))
103 					return facing;
104 			}
105 
106 			if (turret != null && init.Contains<TurretFacingsInit>())
107 			{
108 				int facing;
109 				if (init.Get<TurretFacingsInit, Dictionary<string, int>>().TryGetValue(turret, out facing))
110 					return () => facing;
111 			}
112 
113 			if (init.Contains<TurretFacingInit>())
114 			{
115 				var facing = init.Get<TurretFacingInit, int>();
116 				return () => facing;
117 			}
118 
119 			if (init.Contains<DynamicFacingInit>())
120 				return init.Get<DynamicFacingInit, Func<int>>();
121 
122 			if (init.Contains<FacingInit>())
123 			{
124 				var facing = init.Get<FacingInit, int>();
125 				return () => facing;
126 			}
127 
128 			return () => def;
129 		}
130 
Turreted(ActorInitializer init, TurretedInfo info)131 		public Turreted(ActorInitializer init, TurretedInfo info)
132 			: base(info)
133 		{
134 			TurretFacing = TurretFacingFromInit(init, Info.InitialFacing, Info.Turret)();
135 		}
136 
Created(Actor self)137 		protected override void Created(Actor self)
138 		{
139 			base.Created(self);
140 			attack = self.TraitsImplementing<AttackTurreted>().SingleOrDefault(at => ((AttackTurretedInfo)at.Info).Turrets.Contains(Info.Turret));
141 			facing = self.TraitOrDefault<IFacing>();
142 			body = self.Trait<BodyOrientation>();
143 		}
144 
ITick.Tick(Actor self)145 		void ITick.Tick(Actor self)
146 		{
147 			Tick(self);
148 		}
149 
Tick(Actor self)150 		protected virtual void Tick(Actor self)
151 		{
152 			if (IsTraitDisabled)
153 				return;
154 
155 			// NOTE: FaceTarget is called in AttackTurreted.CanAttack if the turret has a target.
156 			if (attack != null)
157 			{
158 				// Only realign while not attacking anything
159 				if (attack.IsAiming)
160 					return;
161 
162 				if (realignTick < Info.RealignDelay)
163 					realignTick++;
164 				else if (Info.RealignDelay > -1)
165 					DesiredFacing = null;
166 
167 				MoveTurret();
168 			}
169 			else
170 			{
171 				realignTick = 0;
172 				MoveTurret();
173 			}
174 		}
175 
MoveTurret()176 		void MoveTurret()
177 		{
178 			var df = DesiredFacing ?? (facing != null ? facing.Facing : TurretFacing);
179 			TurretFacing = Util.TickFacing(TurretFacing, df, Info.TurnSpeed);
180 		}
181 
FaceTarget(Actor self, Target target)182 		public bool FaceTarget(Actor self, Target target)
183 		{
184 			if (IsTraitDisabled || IsTraitPaused || attack == null || attack.IsTraitDisabled || attack.IsTraitPaused)
185 				return false;
186 
187 			var pos = self.CenterPosition;
188 			var targetPos = attack.GetTargetPosition(pos, target);
189 			var delta = targetPos - pos;
190 			DesiredFacing = delta.HorizontalLengthSquared != 0 ? delta.Yaw.Facing : TurretFacing;
191 			MoveTurret();
192 			return HasAchievedDesiredFacing;
193 		}
194 
195 		public virtual bool HasAchievedDesiredFacing
196 		{
197 			get { return DesiredFacing == null || TurretFacing == DesiredFacing.Value; }
198 		}
199 
200 		// Turret offset in world-space
Position(Actor self)201 		public WVec Position(Actor self)
202 		{
203 			var bodyOrientation = body.QuantizeOrientation(self, self.Orientation);
204 			return body.LocalToWorld(Offset.Rotate(bodyOrientation));
205 		}
206 
207 		// Orientation in world-space
WorldOrientation(Actor self)208 		public WRot WorldOrientation(Actor self)
209 		{
210 			// Hack: turretFacing is relative to the world, so subtract the body yaw
211 			var world = WRot.FromYaw(WAngle.FromFacing(TurretFacing));
212 
213 			if (QuantizedFacings == 0)
214 				return world;
215 
216 			// Quantize orientation to match a rendered sprite
217 			// Implies no pitch or yaw
218 			var facing = body.QuantizeFacing(world.Yaw.Angle / 4, QuantizedFacings);
219 			return new WRot(WAngle.Zero, WAngle.Zero, WAngle.FromFacing(facing));
220 		}
221 
ModifyDeathActorInit(Actor self, TypeDictionary init)222 		public void ModifyDeathActorInit(Actor self, TypeDictionary init)
223 		{
224 			var facings = init.GetOrDefault<TurretFacingsInit>();
225 			if (facings == null)
226 			{
227 				facings = new TurretFacingsInit();
228 				init.Add(facings);
229 			}
230 
231 			if (!facings.Value(self.World).ContainsKey(Name))
232 				facings.Value(self.World).Add(Name, TurretFacing);
233 		}
234 
IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits)235 		void IActorPreviewInitModifier.ModifyActorPreviewInit(Actor self, TypeDictionary inits)
236 		{
237 			var facings = inits.GetOrDefault<DynamicTurretFacingsInit>();
238 			if (facings == null)
239 			{
240 				facings = new DynamicTurretFacingsInit();
241 				inits.Add(facings);
242 			}
243 
244 			Func<int> bodyFacing = () => facing.Facing;
245 			var dynamicFacing = inits.GetOrDefault<DynamicFacingInit>();
246 			var staticFacing = inits.GetOrDefault<FacingInit>();
247 			if (dynamicFacing != null)
248 				bodyFacing = dynamicFacing.Value(self.World);
249 			else if (staticFacing != null)
250 				bodyFacing = () => staticFacing.Value(self.World);
251 
252 			// Freeze the relative turret facing to its current value
253 			var facingOffset = TurretFacing - bodyFacing();
254 			facings.Value(self.World).Add(Name, () => bodyFacing() + facingOffset);
255 		}
256 
TraitDisabled(Actor self)257 		protected override void TraitDisabled(Actor self)
258 		{
259 			if (attack != null && attack.IsAiming)
260 				attack.OnStopOrder(self);
261 		}
262 	}
263 
264 	public class TurretFacingInit : IActorInit<int>
265 	{
266 		[FieldFromYamlKey]
267 		readonly int value = 128;
268 
TurretFacingInit()269 		public TurretFacingInit() { }
TurretFacingInit(int init)270 		public TurretFacingInit(int init) { value = init; }
Value(World world)271 		public int Value(World world) { return value; }
272 	}
273 
274 	public class TurretFacingsInit : IActorInit<Dictionary<string, int>>
275 	{
276 		[DictionaryFromYamlKey]
277 		readonly Dictionary<string, int> value = new Dictionary<string, int>();
278 
TurretFacingsInit()279 		public TurretFacingsInit() { }
TurretFacingsInit(Dictionary<string, int> init)280 		public TurretFacingsInit(Dictionary<string, int> init) { value = init; }
Value(World world)281 		public Dictionary<string, int> Value(World world) { return value; }
282 	}
283 
284 	public class DynamicTurretFacingsInit : IActorInit<Dictionary<string, Func<int>>>
285 	{
286 		readonly Dictionary<string, Func<int>> value = new Dictionary<string, Func<int>>();
DynamicTurretFacingsInit()287 		public DynamicTurretFacingsInit() { }
DynamicTurretFacingsInit(Dictionary<string, Func<int>> init)288 		public DynamicTurretFacingsInit(Dictionary<string, Func<int>> init) { value = init; }
Value(World world)289 		public Dictionary<string, Func<int>> Value(World world) { return value; }
290 	}
291 }
292