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.Collections.Generic;
13 using OpenRA.Graphics;
14 using OpenRA.Mods.Cnc.Activities;
15 using OpenRA.Mods.Common.Graphics;
16 using OpenRA.Mods.Common.Orders;
17 using OpenRA.Mods.Common.Traits;
18 using OpenRA.Primitives;
19 using OpenRA.Traits;
20 
21 namespace OpenRA.Mods.Cnc.Traits
22 {
23 	class PortableChronoInfo : ITraitInfo, Requires<IMoveInfo>
24 	{
25 		[Desc("Cooldown in ticks until the unit can teleport.")]
26 		public readonly int ChargeDelay = 500;
27 
28 		[Desc("Can the unit teleport only a certain distance?")]
29 		public readonly bool HasDistanceLimit = true;
30 
31 		[Desc("The maximum distance in cells this unit can teleport (only used if HasDistanceLimit = true).")]
32 		public readonly int MaxDistance = 12;
33 
34 		[Desc("Sound to play when teleporting.")]
35 		public readonly string ChronoshiftSound = "chrotnk1.aud";
36 
37 		[Desc("Cursor to display when able to deploy the actor.")]
38 		public readonly string DeployCursor = "deploy";
39 
40 		[Desc("Cursor to display when unable to deploy the actor.")]
41 		public readonly string DeployBlockedCursor = "deploy-blocked";
42 
43 		[Desc("Cursor to display when targeting a teleport location.")]
44 		public readonly string TargetCursor = "chrono-target";
45 
46 		[Desc("Cursor to display when the targeted location is blocked.")]
47 		public readonly string TargetBlockedCursor = "move-blocked";
48 
49 		[Desc("Kill cargo on teleporting.")]
50 		public readonly bool KillCargo = true;
51 
52 		[Desc("Flash the screen on teleporting.")]
53 		public readonly bool FlashScreen = false;
54 
55 		[VoiceReference]
56 		public readonly string Voice = "Action";
57 
Create(ActorInitializer init)58 		public object Create(ActorInitializer init) { return new PortableChrono(init.Self, this); }
59 	}
60 
61 	class PortableChrono : IIssueOrder, IResolveOrder, ITick, ISelectionBar, IOrderVoice, ISync
62 	{
63 		public readonly PortableChronoInfo Info;
64 		readonly IMove move;
65 		[Sync]
66 		int chargeTick = 0;
67 
PortableChrono(Actor self, PortableChronoInfo info)68 		public PortableChrono(Actor self, PortableChronoInfo info)
69 		{
70 			Info = info;
71 			move = self.Trait<IMove>();
72 		}
73 
ITick.Tick(Actor self)74 		void ITick.Tick(Actor self)
75 		{
76 			if (chargeTick > 0)
77 				chargeTick--;
78 		}
79 
80 		public IEnumerable<IOrderTargeter> Orders
81 		{
82 			get
83 			{
84 				yield return new PortableChronoOrderTargeter(Info.TargetCursor);
85 				yield return new DeployOrderTargeter("PortableChronoDeploy", 5,
86 					() => CanTeleport ? Info.DeployCursor : Info.DeployBlockedCursor);
87 			}
88 		}
89 
IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)90 		public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
91 		{
92 			if (order.OrderID == "PortableChronoDeploy")
93 			{
94 				// HACK: Switch the global order generator instead of actually issuing an order
95 				if (CanTeleport)
96 					self.World.OrderGenerator = new PortableChronoOrderGenerator(self, Info);
97 
98 				// HACK: We need to issue a fake order to stop the game complaining about the bodge above
99 				return new Order(order.OrderID, self, Target.Invalid, queued);
100 			}
101 
102 			if (order.OrderID == "PortableChronoTeleport")
103 				return new Order(order.OrderID, self, target, queued);
104 
105 			return null;
106 		}
107 
ResolveOrder(Actor self, Order order)108 		public void ResolveOrder(Actor self, Order order)
109 		{
110 			if (order.OrderString == "PortableChronoTeleport" && order.Target.Type != TargetType.Invalid)
111 			{
112 				var maxDistance = Info.HasDistanceLimit ? Info.MaxDistance : (int?)null;
113 				if (!order.Queued)
114 					self.CancelActivity();
115 
116 				var cell = self.World.Map.CellContaining(order.Target.CenterPosition);
117 				if (maxDistance != null)
118 					self.QueueActivity(move.MoveWithinRange(order.Target, WDist.FromCells(maxDistance.Value), targetLineColor: Color.LawnGreen));
119 
120 				self.QueueActivity(new Teleport(self, cell, maxDistance, Info.KillCargo, Info.FlashScreen, Info.ChronoshiftSound));
121 				self.QueueActivity(move.MoveTo(cell, 5, targetLineColor: Color.LawnGreen));
122 				self.ShowTargetLines();
123 			}
124 		}
125 
IOrderVoice.VoicePhraseForOrder(Actor self, Order order)126 		string IOrderVoice.VoicePhraseForOrder(Actor self, Order order)
127 		{
128 			return order.OrderString == "PortableChronoTeleport" ? Info.Voice : null;
129 		}
130 
ResetChargeTime()131 		public void ResetChargeTime()
132 		{
133 			chargeTick = Info.ChargeDelay;
134 		}
135 
136 		public bool CanTeleport
137 		{
138 			get { return chargeTick <= 0; }
139 		}
140 
ISelectionBar.GetValue()141 		float ISelectionBar.GetValue()
142 		{
143 			return (float)(Info.ChargeDelay - chargeTick) / Info.ChargeDelay;
144 		}
145 
ISelectionBar.GetColor()146 		Color ISelectionBar.GetColor() { return Color.Magenta; }
147 		bool ISelectionBar.DisplayWhenEmpty { get { return false; } }
148 	}
149 
150 	class PortableChronoOrderTargeter : IOrderTargeter
151 	{
152 		readonly string targetCursor;
153 
PortableChronoOrderTargeter(string targetCursor)154 		public PortableChronoOrderTargeter(string targetCursor)
155 		{
156 			this.targetCursor = targetCursor;
157 		}
158 
159 		public string OrderID { get { return "PortableChronoTeleport"; } }
160 		public int OrderPriority { get { return 5; } }
161 		public bool IsQueued { get; protected set; }
TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers)162 		public bool TargetOverridesSelection(Actor self, Target target, List<Actor> actorsAt, CPos xy, TargetModifiers modifiers) { return true; }
163 
CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)164 		public bool CanTarget(Actor self, Target target, List<Actor> othersAtTarget, ref TargetModifiers modifiers, ref string cursor)
165 		{
166 			if (modifiers.HasModifier(TargetModifiers.ForceMove))
167 			{
168 				var xy = self.World.Map.CellContaining(target.CenterPosition);
169 
170 				IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
171 
172 				if (self.IsInWorld && self.Owner.Shroud.IsExplored(xy))
173 				{
174 					cursor = targetCursor;
175 					return true;
176 				}
177 
178 				return false;
179 			}
180 
181 			return false;
182 		}
183 	}
184 
185 	class PortableChronoOrderGenerator : OrderGenerator
186 	{
187 		readonly Actor self;
188 		readonly PortableChronoInfo info;
189 
PortableChronoOrderGenerator(Actor self, PortableChronoInfo info)190 		public PortableChronoOrderGenerator(Actor self, PortableChronoInfo info)
191 		{
192 			this.self = self;
193 			this.info = info;
194 		}
195 
OrderInner(World world, CPos cell, int2 worldPixel, MouseInput mi)196 		protected override IEnumerable<Order> OrderInner(World world, CPos cell, int2 worldPixel, MouseInput mi)
197 		{
198 			if (mi.Button == Game.Settings.Game.MouseButtonPreference.Cancel)
199 			{
200 				world.CancelInputMode();
201 				yield break;
202 			}
203 
204 			if (self.IsInWorld && self.Location != cell
205 				&& self.Trait<PortableChrono>().CanTeleport && self.Owner.Shroud.IsExplored(cell))
206 			{
207 				world.CancelInputMode();
208 				yield return new Order("PortableChronoTeleport", self, Target.FromCell(world, cell), mi.Modifiers.HasModifier(Modifiers.Shift));
209 			}
210 		}
211 
Tick(World world)212 		protected override void Tick(World world)
213 		{
214 			if (!self.IsInWorld || self.IsDead)
215 				world.CancelInputMode();
216 		}
217 
Render(WorldRenderer wr, World world)218 		protected override IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
219 
RenderAboveShroud(WorldRenderer wr, World world)220 		protected override IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr, World world) { yield break; }
221 
RenderAnnotations(WorldRenderer wr, World world)222 		protected override IEnumerable<IRenderable> RenderAnnotations(WorldRenderer wr, World world)
223 		{
224 			if (!self.IsInWorld || self.Owner != self.World.LocalPlayer)
225 				yield break;
226 
227 			if (!self.Trait<PortableChrono>().Info.HasDistanceLimit)
228 				yield break;
229 
230 			yield return new RangeCircleAnnotationRenderable(
231 				self.CenterPosition,
232 				WDist.FromCells(self.Trait<PortableChrono>().Info.MaxDistance),
233 				0,
234 				Color.FromArgb(128, Color.LawnGreen),
235 				Color.FromArgb(96, Color.Black));
236 		}
237 
GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)238 		protected override string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
239 		{
240 			if (self.IsInWorld && self.Location != cell
241 				&& self.Trait<PortableChrono>().CanTeleport && self.Owner.Shroud.IsExplored(cell))
242 				return info.TargetCursor;
243 			else
244 				return info.TargetBlockedCursor;
245 		}
246 	}
247 }
248