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.IO; 14 using System.Linq; 15 using OpenRA.Graphics; 16 using OpenRA.Traits; 17 18 namespace OpenRA.Mods.Common.Traits 19 { 20 [Desc("Visualizes the state of the `ResourceLayer`.", " Attach this to the world actor.")] 21 public class ResourceRendererInfo : ITraitInfo, Requires<ResourceLayerInfo> 22 { 23 [FieldLoader.Require] 24 [Desc("Only render these ResourceType names.")] 25 public readonly string[] RenderTypes = null; 26 Create(ActorInitializer init)27 public virtual object Create(ActorInitializer init) { return new ResourceRenderer(init.Self, this); } 28 } 29 30 public class ResourceRenderer : IWorldLoaded, IRenderOverlay, ITickRender, INotifyActorDisposing 31 { 32 protected readonly ResourceLayer ResourceLayer; 33 protected readonly CellLayer<RendererCellContents> RenderContent; 34 protected readonly ResourceRendererInfo Info; 35 36 readonly HashSet<CPos> dirty = new HashSet<CPos>(); 37 readonly Queue<CPos> cleanDirty = new Queue<CPos>(); 38 readonly Dictionary<PaletteReference, TerrainSpriteLayer> spriteLayers = new Dictionary<PaletteReference, TerrainSpriteLayer>(); 39 ResourceRenderer(Actor self, ResourceRendererInfo info)40 public ResourceRenderer(Actor self, ResourceRendererInfo info) 41 { 42 Info = info; 43 44 ResourceLayer = self.Trait<ResourceLayer>(); 45 ResourceLayer.CellChanged += AddDirtyCell; 46 47 RenderContent = new CellLayer<RendererCellContents>(self.World.Map); 48 } 49 AddDirtyCell(CPos cell, ResourceType resType)50 void AddDirtyCell(CPos cell, ResourceType resType) 51 { 52 if (resType == null || Info.RenderTypes.Contains(resType.Info.Type)) 53 dirty.Add(cell); 54 } 55 IWorldLoaded.WorldLoaded(World w, WorldRenderer wr)56 void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) 57 { 58 var resources = w.WorldActor.TraitsImplementing<ResourceType>() 59 .ToDictionary(r => r.Info.ResourceType, r => r); 60 61 // Build the sprite layer dictionary for rendering resources 62 // All resources that have the same palette must also share a sheet and blend mode 63 foreach (var r in resources) 64 { 65 var layer = spriteLayers.GetOrAdd(r.Value.Palette, pal => 66 { 67 var first = r.Value.Variants.First().Value.First(); 68 return new TerrainSpriteLayer(w, wr, first.Sheet, first.BlendMode, pal, wr.World.Type != WorldType.Editor); 69 }); 70 71 // Validate that sprites are compatible with this layer 72 var sheet = layer.Sheet; 73 if (r.Value.Variants.Any(kv => kv.Value.Any(s => s.Sheet != sheet))) 74 throw new InvalidDataException("Resource sprites span multiple sheets. Try loading their sequences earlier."); 75 76 var blendMode = layer.BlendMode; 77 if (r.Value.Variants.Any(kv => kv.Value.Any(s => s.BlendMode != blendMode))) 78 throw new InvalidDataException("Resource sprites specify different blend modes. " 79 + "Try using different palettes for resource types that use different blend modes."); 80 } 81 82 // Initialize the RenderContent with the initial map state 83 // because the shroud may not be enabled. 84 foreach (var cell in w.Map.AllCells) 85 { 86 var type = ResourceLayer.GetResourceType(cell); 87 if (type != null && Info.RenderTypes.Contains(type.Info.Type)) 88 { 89 var resourceContent = ResourceLayer.GetResource(cell); 90 var rendererCellContents = new RendererCellContents(ChooseRandomVariant(resourceContent.Type), resourceContent.Type, resourceContent.Density); 91 RenderContent[cell] = rendererCellContents; 92 UpdateRenderedSprite(cell, rendererCellContents); 93 } 94 } 95 } 96 UpdateSpriteLayers(CPos cell, Sprite sprite, PaletteReference palette)97 protected void UpdateSpriteLayers(CPos cell, Sprite sprite, PaletteReference palette) 98 { 99 foreach (var kv in spriteLayers) 100 { 101 // resource.Type is meaningless (and may be null) if resource.Sprite is null 102 if (sprite != null && palette == kv.Key) 103 kv.Value.Update(cell, sprite); 104 else 105 kv.Value.Update(cell, null); 106 } 107 } 108 IRenderOverlay.Render(WorldRenderer wr)109 void IRenderOverlay.Render(WorldRenderer wr) 110 { 111 foreach (var kv in spriteLayers.Values) 112 kv.Draw(wr.Viewport); 113 } 114 ITickRender.TickRender(WorldRenderer wr, Actor self)115 void ITickRender.TickRender(WorldRenderer wr, Actor self) 116 { 117 foreach (var cell in dirty) 118 { 119 if (self.World.FogObscures(cell)) 120 continue; 121 122 var resourceContent = ResourceLayer.GetResource(cell); 123 if (resourceContent.Density > 0) 124 { 125 var cellContents = RenderContent[cell]; 126 var variant = cellContents.Variant; 127 if (cellContents.Variant == null || cellContents.Type != resourceContent.Type) 128 variant = ChooseRandomVariant(resourceContent.Type); 129 130 var rendererCellContents = new RendererCellContents(variant, resourceContent.Type, resourceContent.Density); 131 RenderContent[cell] = rendererCellContents; 132 133 UpdateRenderedSprite(cell, rendererCellContents); 134 } 135 else 136 { 137 var rendererCellContents = RendererCellContents.Empty; 138 RenderContent[cell] = rendererCellContents; 139 UpdateRenderedSprite(cell, rendererCellContents); 140 } 141 142 cleanDirty.Enqueue(cell); 143 } 144 145 while (cleanDirty.Count > 0) 146 dirty.Remove(cleanDirty.Dequeue()); 147 } 148 UpdateRenderedSprite(CPos cell, RendererCellContents content)149 protected virtual void UpdateRenderedSprite(CPos cell, RendererCellContents content) 150 { 151 var density = content.Density; 152 var type = content.Type; 153 if (content.Density > 0) 154 { 155 // The call chain for this method (that starts with AddDirtyCell()) guarantees 156 // that the new content type would still be suitable for this renderer, 157 // but that is a bit too fragile to rely on in case the code starts changing. 158 if (!Info.RenderTypes.Contains(type.Info.Type)) 159 return; 160 161 var sprites = type.Variants[content.Variant]; 162 var maxDensity = type.Info.MaxDensity; 163 var frame = int2.Lerp(0, sprites.Length - 1, density, maxDensity); 164 165 UpdateSpriteLayers(cell, sprites[frame], type.Palette); 166 } 167 else 168 UpdateSpriteLayers(cell, null, null); 169 } 170 171 bool disposed; INotifyActorDisposing.Disposing(Actor self)172 void INotifyActorDisposing.Disposing(Actor self) 173 { 174 if (disposed) 175 return; 176 177 foreach (var kv in spriteLayers.Values) 178 kv.Dispose(); 179 180 ResourceLayer.CellChanged -= AddDirtyCell; 181 182 disposed = true; 183 } 184 ChooseRandomVariant(ResourceType t)185 protected virtual string ChooseRandomVariant(ResourceType t) 186 { 187 return t.Variants.Keys.Random(Game.CosmeticRandom); 188 } 189 GetRenderedResourceType(CPos cell)190 public ResourceType GetRenderedResourceType(CPos cell) { return RenderContent[cell].Type; } 191 192 public struct RendererCellContents 193 { 194 public readonly string Variant; 195 public readonly ResourceType Type; 196 public readonly int Density; 197 198 public static readonly RendererCellContents Empty = default(RendererCellContents); 199 RendererCellContentsOpenRA.Mods.Common.Traits.ResourceRenderer.RendererCellContents200 public RendererCellContents(string variant, ResourceType type, int density) 201 { 202 Variant = variant; 203 Type = type; 204 Density = density; 205 } 206 } 207 } 208 } 209