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 System.Linq;
14 using OpenRA.Graphics;
15 using OpenRA.Mods.Common.Orders;
16 using OpenRA.Traits;
17 
18 namespace OpenRA.Mods.Common.Traits
19 {
20 	[Desc("Renders target lines between order waypoints.")]
21 	public class DrawLineToTargetInfo : ITraitInfo
22 	{
23 		[Desc("Delay (in ticks) before the target lines disappear.")]
24 		public readonly int Delay = 60;
25 
26 		[Desc("Width (in pixels) of the target lines.")]
27 		public readonly int LineWidth = 1;
28 
29 		[Desc("Width (in pixels) of the queued target lines.")]
30 		public readonly int QueuedLineWidth = 1;
31 
32 		[Desc("Width (in pixels) of the end node markers.")]
33 		public readonly int MarkerWidth = 2;
34 
35 		[Desc("Width (in pixels) of the queued end node markers.")]
36 		public readonly int QueuedMarkerWidth = 2;
37 
Create(ActorInitializer init)38 		public virtual object Create(ActorInitializer init) { return new DrawLineToTarget(init.Self, this); }
39 	}
40 
41 	public class DrawLineToTarget : IRenderAboveShroud, IRenderAnnotationsWhenSelected, INotifySelected
42 	{
43 		readonly DrawLineToTargetInfo info;
44 		readonly List<IRenderable> renderableCache = new List<IRenderable>();
45 		int lifetime;
46 
DrawLineToTarget(Actor self, DrawLineToTargetInfo info)47 		public DrawLineToTarget(Actor self, DrawLineToTargetInfo info)
48 		{
49 			this.info = info;
50 		}
51 
ShowTargetLines(Actor self)52 		public void ShowTargetLines(Actor self)
53 		{
54 			if (Game.Settings.Game.TargetLines < TargetLinesType.Automatic || self.IsIdle)
55 				return;
56 
57 			// Reset the order line timeout.
58 			lifetime = info.Delay;
59 		}
60 
INotifySelected.Selected(Actor self)61 		void INotifySelected.Selected(Actor self)
62 		{
63 			ShowTargetLines(self);
64 		}
65 
IRenderAboveShroud.RenderAboveShroud(Actor self, WorldRenderer wr)66 		IEnumerable<IRenderable> IRenderAboveShroud.RenderAboveShroud(Actor self, WorldRenderer wr)
67 		{
68 			if (!self.Owner.IsAlliedWith(self.World.LocalPlayer) || Game.Settings.Game.TargetLines == TargetLinesType.Disabled)
69 				yield break;
70 
71 			// Players want to see the lines when in waypoint mode.
72 			var force = Game.GetModifierKeys().HasModifier(Modifiers.Shift) || self.World.OrderGenerator is ForceModifiersOrderGenerator;
73 
74 			if (--lifetime <= 0 && !force)
75 				yield break;
76 
77 			var pal = wr.Palette(TileSet.TerrainPaletteInternalName);
78 			var a = self.CurrentActivity;
79 			for (; a != null; a = a.NextActivity)
80 				if (!a.IsCanceling)
81 					foreach (var n in a.TargetLineNodes(self))
82 						if (n.Tile != null && n.Target.Type != TargetType.Invalid)
83 							yield return new SpriteRenderable(n.Tile, n.Target.CenterPosition, WVec.Zero, -511, pal, 1f, true);
84 		}
85 
86 		bool IRenderAboveShroud.SpatiallyPartitionable { get { return false; } }
87 
IRenderAnnotationsWhenSelected.RenderAnnotations(Actor self, WorldRenderer wr)88 		IEnumerable<IRenderable> IRenderAnnotationsWhenSelected.RenderAnnotations(Actor self, WorldRenderer wr)
89 		{
90 			if (!self.Owner.IsAlliedWith(self.World.LocalPlayer) || Game.Settings.Game.TargetLines == TargetLinesType.Disabled)
91 				return Enumerable.Empty<IRenderable>();
92 
93 			// Players want to see the lines when in waypoint mode.
94 			var force = Game.GetModifierKeys().HasModifier(Modifiers.Shift) || self.World.OrderGenerator is ForceModifiersOrderGenerator;
95 
96 			if (--lifetime <= 0 && !force)
97 				return Enumerable.Empty<IRenderable>();
98 
99 			renderableCache.Clear();
100 			var prev = self.CenterPosition;
101 			var a = self.CurrentActivity;
102 			for (; a != null; a = a.NextActivity)
103 			{
104 				if (a.IsCanceling)
105 					continue;
106 
107 				foreach (var n in a.TargetLineNodes(self))
108 				{
109 					if (n.Target.Type != TargetType.Invalid && n.Tile == null)
110 					{
111 						var lineWidth = renderableCache.Any() ? info.QueuedLineWidth : info.LineWidth;
112 						var markerWidth = renderableCache.Any() ? info.QueuedMarkerWidth : info.MarkerWidth;
113 
114 						var pos = n.Target.CenterPosition;
115 						renderableCache.Add(new TargetLineRenderable(new[] { prev, pos }, n.Color, lineWidth, markerWidth));
116 						prev = pos;
117 					}
118 				}
119 			}
120 
121 			// Reverse draw order so target markers are drawn on top of the next line
122 			renderableCache.Reverse();
123 			return renderableCache;
124 		}
125 
126 		bool IRenderAnnotationsWhenSelected.SpatiallyPartitionable { get { return false; } }
127 	}
128 
129 	public static class LineTargetExts
130 	{
ShowTargetLines(this Actor self)131 		public static void ShowTargetLines(this Actor self)
132 		{
133 			// Target lines are only automatically shown for the owning player
134 			// Spectators and allies must use the force-display modifier
135 			if (self.Owner != self.World.LocalPlayer)
136 				return;
137 
138 			// Draw after frame end so that all the queueing of activities are done before drawing.
139 			var line = self.TraitOrDefault<DrawLineToTarget>();
140 			if (line != null)
141 				self.World.AddFrameEndTask(w => line.ShowTargetLines(self));
142 		}
143 	}
144 }
145