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