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.Activities;
16 using OpenRA.Mods.Common.Activities;
17 using OpenRA.Primitives;
18 using OpenRA.Traits;
19 
20 namespace OpenRA.Mods.Common.Traits
21 {
22 	[Desc("Add to a building to expose a move cursor that triggers Transforms and issues a move order to the transformed actor.")]
23 	public class TransformsIntoMobileInfo : ConditionalTraitInfo, Requires<TransformsInfo>
24 	{
25 		[LocomotorReference]
26 		[FieldLoader.Require]
27 		[Desc("Locomotor used by the transformed actor. Must be defined on the World actor.")]
28 		public readonly string Locomotor = null;
29 
30 		public readonly string Cursor = "move";
31 		public readonly string BlockedCursor = "move-blocked";
32 
33 		[VoiceReference]
34 		public readonly string Voice = "Action";
35 
36 		[Desc("Require the force-move modifier to display the move cursor.")]
37 		public readonly bool RequiresForceMove = false;
38 
Create(ActorInitializer init)39 		public override object Create(ActorInitializer init) { return new TransformsIntoMobile(init, this); }
40 
41 		public LocomotorInfo LocomotorInfo { get; private set; }
42 
RulesetLoaded(Ruleset rules, ActorInfo ai)43 		public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
44 		{
45 			var locomotorInfos = rules.Actors["world"].TraitInfos<LocomotorInfo>();
46 			LocomotorInfo = locomotorInfos.FirstOrDefault(li => li.Name == Locomotor);
47 			if (LocomotorInfo == null)
48 				throw new YamlException("A locomotor named '{0}' doesn't exist.".F(Locomotor));
49 			else if (locomotorInfos.Count(li => li.Name == Locomotor) > 1)
50 				throw new YamlException("There is more than one locomotor named '{0}'.".F(Locomotor));
51 
52 			base.RulesetLoaded(rules, ai);
53 		}
54 	}
55 
56 	public class TransformsIntoMobile : ConditionalTrait<TransformsIntoMobileInfo>, IIssueOrder, IResolveOrder, IOrderVoice
57 	{
58 		readonly Actor self;
59 		Transforms[] transforms;
60 		Locomotor locomotor;
61 
TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info)62 		public TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info)
63 			: base(info)
64 		{
65 			self = init.Self;
66 		}
67 
Created(Actor self)68 		protected override void Created(Actor self)
69 		{
70 			transforms = self.TraitsImplementing<Transforms>().ToArray();
71 			locomotor = self.World.WorldActor.TraitsImplementing<Locomotor>()
72 				.Single(l => l.Info.Name == Info.Locomotor);
73 			base.Created(self);
74 		}
75 
76 		IEnumerable<IOrderTargeter> IIssueOrder.Orders
77 		{
78 			get
79 			{
80 				if (!IsTraitDisabled)
81 					yield return new MoveOrderTargeter(self, this);
82 			}
83 		}
84 
85 		// Note: Returns a valid order even if the unit can't move to the target
IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)86 		Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
87 		{
88 			if (order is MoveOrderTargeter)
89 				return new Order("Move", self, target, queued);
90 
91 			return null;
92 		}
93 
IResolveOrder.ResolveOrder(Actor self, Order order)94 		void IResolveOrder.ResolveOrder(Actor self, Order order)
95 		{
96 			if (IsTraitDisabled)
97 				return;
98 
99 			if (order.OrderString == "Move")
100 			{
101 				var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(order.Target.CenterPosition));
102 				if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell))
103 					return;
104 
105 				var currentTransform = self.CurrentActivity as Transform;
106 				var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused);
107 				if (transform == null && currentTransform == null)
108 					return;
109 
110 				// Manually manage the inner activity queue
111 				var activity = currentTransform ?? transform.GetTransformActivity(self);
112 				if (!order.Queued && activity.NextActivity != null)
113 					activity.NextActivity.Cancel(self);
114 
115 				activity.Queue(new IssueOrderAfterTransform("Move", order.Target, Color.Green));
116 
117 				if (currentTransform == null)
118 					self.QueueActivity(order.Queued, activity);
119 
120 				self.ShowTargetLines();
121 			}
122 			else if (order.OrderString == "Stop")
123 			{
124 				// We don't want Stop orders from traits other than Mobile or Aircraft to cancel Resupply activity.
125 				// Resupply is always either the main activity or a child of ReturnToBase.
126 				// TODO: This should generally only cancel activities queued by this trait.
127 				if (self.CurrentActivity == null || self.CurrentActivity is Resupply || self.CurrentActivity is ReturnToBase)
128 					return;
129 
130 				self.CancelActivity();
131 			}
132 		}
133 
IOrderVoice.VoicePhraseForOrder(Actor self, Order order)134 		string IOrderVoice.VoicePhraseForOrder(Actor self, Order order)
135 		{
136 			if (IsTraitDisabled)
137 				return null;
138 
139 			switch (order.OrderString)
140 			{
141 				case "Move":
142 					if (!Info.LocomotorInfo.MoveIntoShroud && order.Target.Type != TargetType.Invalid)
143 					{
144 						var cell = self.World.Map.CellContaining(order.Target.CenterPosition);
145 						if (!self.Owner.Shroud.IsExplored(cell))
146 							return null;
147 					}
148 
149 					return Info.Voice;
150 				case "Stop":
151 					return Info.Voice;
152 				default:
153 					return null;
154 			}
155 		}
156 
157 		class MoveOrderTargeter : IOrderTargeter
158 		{
159 			readonly TransformsIntoMobile mobile;
160 			readonly bool rejectMove;
TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)161 			public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)
162 			{
163 				// Always prioritise orders over selecting other peoples actors or own actors that are already selected
164 				if (target.Type == TargetType.Actor && (target.Actor.Owner != self.Owner || self.World.Selection.Contains(target.Actor)))
165 					return true;
166 
167 				return modifiers.HasModifier(TargetModifiers.ForceMove);
168 			}
169 
MoveOrderTargeter(Actor self, TransformsIntoMobile mobile)170 			public MoveOrderTargeter(Actor self, TransformsIntoMobile mobile)
171 			{
172 				this.mobile = mobile;
173 				rejectMove = !self.AcceptsOrder("Move");
174 			}
175 
176 			public string OrderID { get { return "Move"; } }
177 			public int OrderPriority { get { return 4; } }
178 			public bool IsQueued { get; protected set; }
179 
CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)180 			public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
181 			{
182 				if (rejectMove || target.Type != TargetType.Terrain || (mobile.Info.RequiresForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove)))
183 					return false;
184 
185 				var location = self.World.Map.CellContaining(target.CenterPosition);
186 				IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
187 
188 				var explored = self.Owner.Shroud.IsExplored(location);
189 				cursor = self.World.Map.Contains(location) ?
190 					(self.World.Map.GetTerrainInfo(location).CustomCursor ?? mobile.Info.Cursor) : mobile.Info.BlockedCursor;
191 
192 				if (!(self.CurrentActivity is Transform || mobile.transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused))
193 					|| (!explored && !mobile.locomotor.Info.MoveIntoShroud)
194 					|| (explored && !CanEnterCell(self, location)))
195 					cursor = mobile.Info.BlockedCursor;
196 
197 				return true;
198 			}
199 
CanEnterCell(Actor self, CPos cell)200 			bool CanEnterCell(Actor self, CPos cell)
201 			{
202 				if (mobile.locomotor.MovementCostForCell(cell) == short.MaxValue)
203 					return false;
204 
205 				return mobile.locomotor.CanMoveFreelyInto(self, cell, BlockedByActor.All, null);
206 			}
207 		}
208 	}
209 }
210