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