From ae0d9c0e7d239b9f6b0eff0652a652871784f6ce Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 5 Jul 2015 13:41:46 +0100 Subject: [PATCH 1/5] Move ResourceLayer init into the constructor. Stylecop rules force renaming content and render. --- .../Traits/World/ResourceLayer.cs | 75 ++++++++++--------- .../Traits/World/D2kResourceLayer.cs | 16 ++-- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index a6d2c9e329..636a76b079 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -17,19 +17,30 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Attach this to the world actor.", "Order of the layers defines the Z sorting.")] - public class ResourceLayerInfo : TraitInfo, Requires { } + public class ResourceLayerInfo : ITraitInfo, Requires, Requires + { + public virtual object Create(ActorInitializer init) { return new ResourceLayer(init.Self); } + } public class ResourceLayer : IRenderOverlay, IWorldLoaded, ITickRender { static readonly CellContents EmptyCell = new CellContents(); - World world; + readonly World world; + readonly BuildingInfluence buildingInfluence; + readonly List dirty = new List(); - BuildingInfluence buildingInfluence; + protected readonly CellLayer Content; + protected readonly CellLayer RenderContent; - protected CellLayer content; - protected CellLayer render; - List dirty; + public ResourceLayer(Actor self) + { + world = self.World; + buildingInfluence = world.WorldActor.Trait(); + + Content = new CellLayer(world.Map); + RenderContent = new CellLayer(world.Map); + } public void Render(WorldRenderer wr) { @@ -39,7 +50,7 @@ namespace OpenRA.Mods.Common.Traits if (shroudObscured(uv)) continue; - var c = render[uv]; + var c = RenderContent[uv]; if (c.Sprite != null) new SpriteRenderable(c.Sprite, wr.World.Map.CenterOfCell(uv.ToCPos(world.Map)), WVec.Zero, -511, c.Type.Palette, 1f, true).Render(wr); // TODO ZOffset is ignored @@ -54,7 +65,7 @@ namespace OpenRA.Mods.Common.Traits for (var v = -1; v < 2; v++) { var c = cell + new CVec(u, v); - if (content.Contains(c) && content[c].Type == t) + if (Content.Contains(c) && Content[c].Type == t) ++sum; } } @@ -64,14 +75,6 @@ namespace OpenRA.Mods.Common.Traits public void WorldLoaded(World w, WorldRenderer wr) { - this.world = w; - - buildingInfluence = world.WorldActor.Trait(); - - content = new CellLayer(w.Map); - render = new CellLayer(w.Map); - dirty = new List(); - var resources = w.WorldActor.TraitsImplementing() .ToDictionary(r => r.Info.ResourceType, r => r); @@ -84,22 +87,22 @@ namespace OpenRA.Mods.Common.Traits if (!AllowResourceAt(t, cell)) continue; - content[cell] = CreateResourceCell(t, cell); + Content[cell] = CreateResourceCell(t, cell); } // Set initial density based on the number of neighboring resources foreach (var cell in w.Map.AllCells) { - var type = content[cell].Type; + var type = Content[cell].Type; if (type != null) { // Adjacent includes the current cell, so is always >= 1 var adjacent = GetAdjacentCellsWith(type, cell); var density = int2.Lerp(0, type.Info.MaxDensity, adjacent, 9); - var temp = content[cell]; + var temp = Content[cell]; temp.Density = Math.Max(density, 1); - render[cell] = content[cell] = temp; + RenderContent[cell] = Content[cell] = temp; UpdateRenderedSprite(cell); } } @@ -107,7 +110,7 @@ namespace OpenRA.Mods.Common.Traits protected virtual void UpdateRenderedSprite(CPos cell) { - var t = render[cell]; + var t = RenderContent[cell]; if (t.Density > 0) { var sprites = t.Type.Variants[t.Variant]; @@ -117,7 +120,7 @@ namespace OpenRA.Mods.Common.Traits else t.Sprite = null; - render[cell] = t; + RenderContent[cell] = t; } protected virtual string ChooseRandomVariant(ResourceType t) @@ -132,7 +135,7 @@ namespace OpenRA.Mods.Common.Traits { if (!self.World.FogObscures(c)) { - render[c] = content[c]; + RenderContent[c] = Content[c]; UpdateRenderedSprite(c); remove.Add(c); } @@ -187,7 +190,7 @@ namespace OpenRA.Mods.Common.Traits public void AddResource(ResourceType t, CPos p, int n) { - var cell = content[p]; + var cell = Content[p]; if (cell.Type == null) cell = CreateResourceCell(t, p); @@ -195,7 +198,7 @@ namespace OpenRA.Mods.Common.Traits return; cell.Density = Math.Min(cell.Type.Info.MaxDensity, cell.Density + n); - content[p] = cell; + Content[p] = cell; if (!dirty.Contains(p)) dirty.Add(p); @@ -203,22 +206,22 @@ namespace OpenRA.Mods.Common.Traits public bool IsFull(CPos cell) { - return content[cell].Density == content[cell].Type.Info.MaxDensity; + return Content[cell].Density == Content[cell].Type.Info.MaxDensity; } public ResourceType Harvest(CPos cell) { - var c = content[cell]; + var c = Content[cell]; if (c.Type == null) return null; if (--c.Density < 0) { - content[cell] = EmptyCell; + Content[cell] = EmptyCell; world.Map.CustomTerrain[cell] = byte.MaxValue; } else - content[cell] = c; + Content[cell] = c; if (!dirty.Contains(cell)) dirty.Add(cell); @@ -229,26 +232,26 @@ namespace OpenRA.Mods.Common.Traits public void Destroy(CPos cell) { // Don't break other users of CustomTerrain if there are no resources - if (content[cell].Type == null) + if (Content[cell].Type == null) return; // Clear cell - content[cell] = EmptyCell; + Content[cell] = EmptyCell; world.Map.CustomTerrain[cell] = byte.MaxValue; if (!dirty.Contains(cell)) dirty.Add(cell); } - public ResourceType GetResource(CPos cell) { return content[cell].Type; } - public ResourceType GetRenderedResource(CPos cell) { return render[cell].Type; } - public int GetResourceDensity(CPos cell) { return content[cell].Density; } + public ResourceType GetResource(CPos cell) { return Content[cell].Type; } + public ResourceType GetRenderedResource(CPos cell) { return RenderContent[cell].Type; } + public int GetResourceDensity(CPos cell) { return Content[cell].Density; } public int GetMaxResourceDensity(CPos cell) { - if (content[cell].Type == null) + if (Content[cell].Type == null) return 0; - return content[cell].Type.Info.MaxDensity; + return Content[cell].Type.Info.MaxDensity; } public struct CellContents diff --git a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs index e63d1ea45d..7ed10b01b1 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs @@ -17,7 +17,10 @@ using OpenRA.Traits; namespace OpenRA.Mods.D2k.Traits { [Desc("Used to render spice with round borders.")] - public class D2kResourceLayerInfo : TraitInfo { } + public class D2kResourceLayerInfo : ResourceLayerInfo + { + public override object Create(ActorInitializer init) { return new D2kResourceLayer(init.Self); } + } public class D2kResourceLayer : ResourceLayer { @@ -96,9 +99,12 @@ namespace OpenRA.Mods.D2k.Traits { ClearSides.Bottom | ClearSides.TopLeft | ClearSides.BottomLeft | ClearSides.BottomRight, 49 }, }; + public D2kResourceLayer(Actor self) + : base(self) { } + bool CellContains(CPos c, ResourceType t) { - return render.Contains(c) && render[c].Type == t; + return RenderContent.Contains(c) && RenderContent[c].Type == t; } ClearSides FindClearSides(ResourceType t, CPos p) @@ -133,10 +139,10 @@ namespace OpenRA.Mods.D2k.Traits void UpdateRenderedTileInner(CPos p) { - if (!render.Contains(p)) + if (!RenderContent.Contains(p)) return; - var t = render[p]; + var t = RenderContent[p]; if (t.Density > 0) { var clear = FindClearSides(t.Type, p); @@ -156,7 +162,7 @@ namespace OpenRA.Mods.D2k.Traits else t.Sprite = null; - render[p] = t; + RenderContent[p] = t; } protected override void UpdateRenderedSprite(CPos p) From f93ad2a9c19a689cd0d3fc38c6f53c23e54f53a9 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 5 Jul 2015 13:51:38 +0100 Subject: [PATCH 2/5] Expose TerrainSpriteLayer.Sheet and BlendMode. --- OpenRA.Game/Graphics/TerrainSpriteLayer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs index bf828bbc36..19b4b28664 100644 --- a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs +++ b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs @@ -19,6 +19,9 @@ namespace OpenRA.Graphics { public sealed class TerrainSpriteLayer : IDisposable { + public readonly Sheet Sheet; + public readonly BlendMode BlendMode; + readonly Sprite emptySprite; readonly IVertexBuffer vertexBuffer; @@ -30,17 +33,14 @@ namespace OpenRA.Graphics readonly WorldRenderer worldRenderer; readonly Map map; - readonly Sheet sheet; - readonly BlendMode blendMode; - float paletteIndex; public TerrainSpriteLayer(World world, WorldRenderer wr, Sheet sheet, BlendMode blendMode, PaletteReference palette, bool restrictToBounds) { worldRenderer = wr; this.restrictToBounds = restrictToBounds; - this.sheet = sheet; - this.blendMode = blendMode; + Sheet = sheet; + BlendMode = blendMode; paletteIndex = palette.TextureIndex; map = world.Map; @@ -77,10 +77,10 @@ namespace OpenRA.Graphics { if (sprite != null) { - if (sprite.Sheet != sheet) + if (sprite.Sheet != Sheet) throw new InvalidDataException("Attempted to add sprite from a different sheet"); - if (sprite.BlendMode != blendMode) + if (sprite.BlendMode != BlendMode) throw new InvalidDataException("Attempted to add sprite with a different blend mode"); } else @@ -122,7 +122,7 @@ namespace OpenRA.Graphics Game.Renderer.WorldSpriteRenderer.DrawVertexBuffer( vertexBuffer, rowStride * firstRow, rowStride * (lastRow - firstRow), - PrimitiveType.QuadList, sheet, blendMode); + PrimitiveType.QuadList, Sheet, BlendMode); Game.Renderer.Flush(); } From 7c0d3f4e4007226e23d2c9b39a594c6aedf341f3 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 5 Jul 2015 14:07:14 +0100 Subject: [PATCH 3/5] Use a HashSet for ResourceLayer dirty cells. --- .../Traits/World/ResourceLayer.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index 636a76b079..ca19cc7dd6 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -28,7 +28,7 @@ namespace OpenRA.Mods.Common.Traits readonly World world; readonly BuildingInfluence buildingInfluence; - readonly List dirty = new List(); + readonly HashSet dirty = new HashSet(); protected readonly CellLayer Content; protected readonly CellLayer RenderContent; @@ -36,7 +36,7 @@ namespace OpenRA.Mods.Common.Traits public ResourceLayer(Actor self) { world = self.World; - buildingInfluence = world.WorldActor.Trait(); + buildingInfluence = self.Trait(); Content = new CellLayer(world.Map); RenderContent = new CellLayer(world.Map); @@ -102,8 +102,8 @@ namespace OpenRA.Mods.Common.Traits var temp = Content[cell]; temp.Density = Math.Max(density, 1); - RenderContent[cell] = Content[cell] = temp; - UpdateRenderedSprite(cell); + Content[cell] = temp; + dirty.Add(cell); } } } @@ -200,8 +200,7 @@ namespace OpenRA.Mods.Common.Traits cell.Density = Math.Min(cell.Type.Info.MaxDensity, cell.Density + n); Content[p] = cell; - if (!dirty.Contains(p)) - dirty.Add(p); + dirty.Add(p); } public bool IsFull(CPos cell) @@ -223,8 +222,7 @@ namespace OpenRA.Mods.Common.Traits else Content[cell] = c; - if (!dirty.Contains(cell)) - dirty.Add(cell); + dirty.Add(cell); return c.Type; } @@ -239,8 +237,7 @@ namespace OpenRA.Mods.Common.Traits Content[cell] = EmptyCell; world.Map.CustomTerrain[cell] = byte.MaxValue; - if (!dirty.Contains(cell)) - dirty.Add(cell); + dirty.Add(cell); } public ResourceType GetResource(CPos cell) { return Content[cell].Type; } From 971d1c13889b7af51cb5a5c137ef13dcceec926f Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 5 Jul 2015 14:30:23 +0100 Subject: [PATCH 4/5] Fix a NRE in TerrainSpriteLayer. --- OpenRA.Game/Graphics/TerrainSpriteLayer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs index 19b4b28664..7471b5d04f 100644 --- a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs +++ b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs @@ -69,7 +69,8 @@ namespace OpenRA.Graphics public void Update(CPos cell, Sprite sprite) { - var pos = worldRenderer.ScreenPosition(map.CenterOfCell(cell)) + sprite.Offset - 0.5f * sprite.Size; + var pos = sprite == null ? float2.Zero : + worldRenderer.ScreenPosition(map.CenterOfCell(cell)) + sprite.Offset - 0.5f * sprite.Size; Update(cell.ToMPos(map.TileShape), sprite, pos); } From d2b7d42cca0cce0d72b28470bc5dab29e0611c96 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Sun, 5 Jul 2015 14:14:37 +0100 Subject: [PATCH 5/5] Use TerrainSpriteLayer for rendering resources. --- .../Traits/World/ResourceLayer.cs | 65 +++++++++++++++---- 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index ca19cc7dd6..042bb2ca65 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using OpenRA.Graphics; using OpenRA.Traits; @@ -22,13 +23,14 @@ namespace OpenRA.Mods.Common.Traits public virtual object Create(ActorInitializer init) { return new ResourceLayer(init.Self); } } - public class ResourceLayer : IRenderOverlay, IWorldLoaded, ITickRender + public class ResourceLayer : IRenderOverlay, IWorldLoaded, ITickRender, INotifyActorDisposing { static readonly CellContents EmptyCell = new CellContents(); readonly World world; readonly BuildingInfluence buildingInfluence; readonly HashSet dirty = new HashSet(); + readonly Dictionary spriteLayers = new Dictionary(); protected readonly CellLayer Content; protected readonly CellLayer RenderContent; @@ -40,21 +42,27 @@ namespace OpenRA.Mods.Common.Traits Content = new CellLayer(world.Map); RenderContent = new CellLayer(world.Map); + + RenderContent.CellEntryChanged += UpdateSpriteLayers; + } + + void UpdateSpriteLayers(CPos cell) + { + var resource = RenderContent[cell]; + foreach (var kv in spriteLayers) + { + // resource.Type is meaningless (and may be null) if resource.Sprite is null + if (resource.Sprite != null && resource.Type.Palette == kv.Key) + kv.Value.Update(cell, resource.Sprite); + else + kv.Value.Update(cell, null); + } } public void Render(WorldRenderer wr) { - var shroudObscured = world.ShroudObscuresTest; - foreach (var uv in wr.Viewport.VisibleCellsInsideBounds.MapCoords) - { - if (shroudObscured(uv)) - continue; - - var c = RenderContent[uv]; - if (c.Sprite != null) - new SpriteRenderable(c.Sprite, wr.World.Map.CenterOfCell(uv.ToCPos(world.Map)), - WVec.Zero, -511, c.Type.Palette, 1f, true).Render(wr); // TODO ZOffset is ignored - } + foreach (var kv in spriteLayers.Values) + kv.Draw(wr.Viewport); } int GetAdjacentCellsWith(ResourceType t, CPos cell) @@ -106,6 +114,27 @@ namespace OpenRA.Mods.Common.Traits dirty.Add(cell); } } + + // Build the sprite layer dictionary for rendering resources + // All resources that have the same palette must also share a sheet and blend mode + foreach (var r in resources) + { + var layer = spriteLayers.GetOrAdd(r.Value.Palette, pal => + { + var first = r.Value.Variants.First().Value.First(); + return new TerrainSpriteLayer(w, wr, first.Sheet, first.BlendMode, pal, wr.World.Type != WorldType.Editor); + }); + + // Validate that sprites are compatible with this layer + var sheet = layer.Sheet; + if (r.Value.Variants.Any(kv => kv.Value.Any(s => s.Sheet != sheet))) + throw new InvalidDataException("Resource sprites span multiple sheets. Try loading their sequences earlier."); + + var blendMode = layer.BlendMode; + if (r.Value.Variants.Any(kv => kv.Value.Any(s => s.BlendMode != blendMode))) + throw new InvalidDataException("Resource sprites specify different blend modes. " + + "Try using different palettes for resource types that use different blend modes."); + } } protected virtual void UpdateRenderedSprite(CPos cell) @@ -251,6 +280,18 @@ namespace OpenRA.Mods.Common.Traits return Content[cell].Type.Info.MaxDensity; } + bool disposed; + public void Disposing(Actor self) + { + if (disposed) + return; + + foreach (var kv in spriteLayers.Values) + kv.Dispose(); + + disposed = true; + } + public struct CellContents { public static readonly CellContents Empty = new CellContents();