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.Graphics;
16 using OpenRA.Primitives;
17 using OpenRA.Traits;
18 
19 namespace OpenRA.Mods.Common.Traits.Render
20 {
21 	public class SelectionDecorationsInfo : ITraitInfo, Requires<IDecorationBoundsInfo>
22 	{
23 		[PaletteReference]
24 		public readonly string Palette = "chrome";
25 
26 		[Desc("Health bar, production progress bar etc.")]
27 		public readonly bool RenderSelectionBars = true;
28 
29 		public readonly bool RenderSelectionBox = true;
30 
31 		public readonly Color SelectionBoxColor = Color.White;
32 
33 		public readonly string Image = "pips";
34 
Create(ActorInitializer init)35 		public object Create(ActorInitializer init) { return new SelectionDecorations(init.Self, this); }
36 	}
37 
38 	public class SelectionDecorations : ISelectionDecorations, IRenderAnnotations, INotifyCreated, ITick
39 	{
40 		// depends on the order of pips in TraitsInterfaces.cs!
41 		static readonly string[] PipStrings = { "pip-empty", "pip-green", "pip-yellow", "pip-red", "pip-gray", "pip-blue", "pip-ammo", "pip-ammoempty" };
42 
43 		public readonly SelectionDecorationsInfo Info;
44 
45 		readonly IDecorationBounds[] decorationBounds;
46 		readonly Animation pipImages;
47 		IPips[] pipSources;
48 
SelectionDecorations(Actor self, SelectionDecorationsInfo info)49 		public SelectionDecorations(Actor self, SelectionDecorationsInfo info)
50 		{
51 			Info = info;
52 
53 			decorationBounds = self.TraitsImplementing<IDecorationBounds>().ToArray();
54 			pipImages = new Animation(self.World, Info.Image);
55 		}
56 
INotifyCreated.Created(Actor self)57 		void INotifyCreated.Created(Actor self)
58 		{
59 			pipSources = self.TraitsImplementing<IPips>().ToArray();
60 		}
61 
ActivityTargetPath(Actor self)62 		IEnumerable<WPos> ActivityTargetPath(Actor self)
63 		{
64 			if (!self.IsInWorld || self.IsDead)
65 				yield break;
66 
67 			var activity = self.CurrentActivity;
68 			if (activity != null)
69 			{
70 				var targets = activity.GetTargets(self);
71 				yield return self.CenterPosition;
72 
73 				foreach (var t in targets.Where(t => t.Type != TargetType.Invalid))
74 					yield return t.CenterPosition;
75 			}
76 		}
77 
IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr)78 		IEnumerable<IRenderable> IRenderAnnotations.RenderAnnotations(Actor self, WorldRenderer wr)
79 		{
80 			if (self.World.FogObscures(self))
81 				return Enumerable.Empty<IRenderable>();
82 
83 			return DrawDecorations(self, wr);
84 		}
85 
86 		bool IRenderAnnotations.SpatiallyPartitionable { get { return true; } }
87 
DrawDecorations(Actor self, WorldRenderer wr)88 		IEnumerable<IRenderable> DrawDecorations(Actor self, WorldRenderer wr)
89 		{
90 			var selected = self.World.Selection.Contains(self);
91 			var regularWorld = self.World.Type == WorldType.Regular;
92 			var statusBars = Game.Settings.Game.StatusBars;
93 			var bounds = decorationBounds.FirstNonEmptyBounds(self, wr);
94 
95 			// Health bars are shown when:
96 			//  * actor is selected
97 			//  * status bar preference is set to "always show"
98 			//  * status bar preference is set to "when damaged" and actor is damaged
99 			var displayHealth = selected || (regularWorld && statusBars == StatusBarsType.AlwaysShow)
100 				|| (regularWorld && statusBars == StatusBarsType.DamageShow && self.GetDamageState() != DamageState.Undamaged);
101 
102 			// Extra bars are shown when:
103 			//  * actor is selected
104 			//  * status bar preference is set to "always show"
105 			//  * status bar preference is set to "when damaged"
106 			var displayExtra = selected || (regularWorld && statusBars != StatusBarsType.Standard);
107 
108 			if (Info.RenderSelectionBox && selected)
109 				yield return new SelectionBoxAnnotationRenderable(self, bounds, Info.SelectionBoxColor);
110 
111 			if (Info.RenderSelectionBars && (displayHealth || displayExtra))
112 				yield return new SelectionBarsAnnotationRenderable(self, bounds, displayHealth, displayExtra);
113 
114 			// Target lines and pips are always only displayed for selected allied actors
115 			if (!selected || !self.Owner.IsAlliedWith(wr.World.RenderPlayer))
116 				yield break;
117 
118 			if (self.World.LocalPlayer != null && self.World.LocalPlayer.PlayerActor.Trait<DeveloperMode>().PathDebug)
119 				yield return new TargetLineRenderable(ActivityTargetPath(self), Color.Green);
120 
121 			foreach (var r in DrawPips(self, bounds, wr))
122 				yield return r;
123 		}
124 
DrawRollover(Actor self, WorldRenderer worldRenderer)125 		public void DrawRollover(Actor self, WorldRenderer worldRenderer)
126 		{
127 			var bounds = decorationBounds.FirstNonEmptyBounds(self, worldRenderer);
128 			new SelectionBarsAnnotationRenderable(self, bounds, true, true).Render(worldRenderer);
129 		}
130 
DrawPips(Actor self, Rectangle bounds, WorldRenderer wr)131 		IEnumerable<IRenderable> DrawPips(Actor self, Rectangle bounds, WorldRenderer wr)
132 		{
133 			if (pipSources.Length == 0)
134 				return Enumerable.Empty<IRenderable>();
135 
136 			return DrawPipsInner(self, bounds, wr);
137 		}
138 
DrawPipsInner(Actor self, Rectangle bounds, WorldRenderer wr)139 		IEnumerable<IRenderable> DrawPipsInner(Actor self, Rectangle bounds, WorldRenderer wr)
140 		{
141 			pipImages.PlayRepeating(PipStrings[0]);
142 
143 			var palette = wr.Palette(Info.Palette);
144 			var basePosition = wr.Viewport.WorldToViewPx(new int2(bounds.Left, bounds.Bottom));
145 			var pipSize = pipImages.Image.Size.XY.ToInt2();
146 			var pipxyBase = basePosition + new int2(1 - pipSize.X / 2, -(3 + pipSize.Y / 2));
147 			var pipxyOffset = new int2(0, 0);
148 			var width = bounds.Width;
149 
150 			foreach (var pips in pipSources)
151 			{
152 				var thisRow = pips.GetPips(self);
153 				if (thisRow == null)
154 					continue;
155 
156 				foreach (var pip in thisRow)
157 				{
158 					if (pipxyOffset.X + pipSize.X >= width)
159 						pipxyOffset = new int2(0, pipxyOffset.Y - pipSize.Y);
160 
161 					pipImages.PlayRepeating(PipStrings[(int)pip]);
162 					pipxyOffset += new int2(pipSize.X, 0);
163 
164 					yield return new UISpriteRenderable(pipImages.Image, self.CenterPosition, pipxyBase + pipxyOffset, 0, palette, 1f);
165 				}
166 
167 				// Increment row
168 				pipxyOffset = new int2(0, pipxyOffset.Y - (pipSize.Y + 1));
169 			}
170 		}
171 
ITick.Tick(Actor self)172 		void ITick.Tick(Actor self)
173 		{
174 			pipImages.Tick();
175 		}
176 	}
177 }
178