diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 6bbfda6396..b03832ecbe 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -277,7 +277,7 @@ namespace OpenRA.Mods.Common.Orders { var isCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, topLeft); foreach (var t in buildingInfo.Tiles(topLeft)) - footprint.Add(t, MakeCellType(isCloseEnough && world.IsCellBuildable(t, actorInfo, buildingInfo) && (resourceLayer == null || resourceLayer.GetResource(t) == null))); + footprint.Add(t, MakeCellType(isCloseEnough && world.IsCellBuildable(t, actorInfo, buildingInfo) && (resourceLayer == null || resourceLayer.GetResourceType(t) == null))); } return preview != null ? preview.Render(wr, topLeft, footprint) : Enumerable.Empty(); diff --git a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs index dbff340307..e3e24b4be2 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs @@ -56,7 +56,7 @@ namespace OpenRA.Mods.Common.Traits var res = world.WorldActor.TraitOrDefault(); return bi.Tiles(cell).All( - t => world.Map.Contains(t) && (res == null || res.GetResource(t) == null) && + t => world.Map.Contains(t) && (res == null || res.GetResourceType(t) == null) && world.IsCellBuildable(t, ai, bi, toIgnore)); } diff --git a/OpenRA.Mods.Common/Traits/Harvester.cs b/OpenRA.Mods.Common/Traits/Harvester.cs index 5eaa9f7610..890a858551 100644 --- a/OpenRA.Mods.Common/Traits/Harvester.cs +++ b/OpenRA.Mods.Common/Traits/Harvester.cs @@ -269,7 +269,7 @@ namespace OpenRA.Mods.Common.Traits if (cell.Layer != 0) return false; - var resType = resLayer.GetResource(cell); + var resType = resLayer.GetResourceType(cell); if (resType == null) return false; @@ -395,7 +395,7 @@ namespace OpenRA.Mods.Common.Traits if (!self.Owner.Shroud.IsExplored(location)) return false; - var res = self.World.WorldActor.Trait().GetRenderedResource(location); + var res = self.World.WorldActor.Trait().GetRenderedResourceType(location); var info = self.Info.TraitInfo(); if (res == null || !info.Resources.Contains(res.Info.Type)) diff --git a/OpenRA.Mods.Common/Traits/SeedsResource.cs b/OpenRA.Mods.Common/Traits/SeedsResource.cs index db9481def1..ce985bef0d 100644 --- a/OpenRA.Mods.Common/Traits/SeedsResource.cs +++ b/OpenRA.Mods.Common/Traits/SeedsResource.cs @@ -65,7 +65,7 @@ namespace OpenRA.Mods.Common.Traits var cell = Util.RandomWalk(self.Location, self.World.SharedRandom) .Take(info.MaxRange) .SkipWhile(p => !self.World.Map.Contains(p) || - (resLayer.GetResource(p) == resourceType && resLayer.IsFull(p))) + (resLayer.GetResourceType(p) == resourceType && resLayer.IsFull(p))) .Cast().FirstOrDefault(); if (cell != null && resLayer.CanSpawnResourceAt(resourceType, cell.Value)) diff --git a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs index 3405844a87..4637d47c33 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs @@ -31,7 +31,7 @@ namespace OpenRA.Mods.Common.Traits protected readonly Map Map; protected readonly TileSet Tileset; protected readonly Dictionary Resources; - protected readonly CellLayer Tiles; + protected readonly CellLayer Tiles; protected readonly HashSet Dirty = new HashSet(); readonly Dictionary spriteLayers = new Dictionary(); @@ -48,7 +48,7 @@ namespace OpenRA.Mods.Common.Traits Map = self.World.Map; Tileset = self.World.Map.Rules.TileSet; - Tiles = new CellLayer(Map); + Tiles = new CellLayer(Map); Resources = self.TraitsImplementing() .ToDictionary(r => r.Info.ResourceType, r => r); @@ -98,7 +98,7 @@ namespace OpenRA.Mods.Common.Traits ResourceType type; if (Resources.TryGetValue(tile.Type, out type)) { - Tiles[uv] = new CellContents + Tiles[uv] = new EditorCellContents { Type = type, Variant = ChooseRandomVariant(type), @@ -108,7 +108,7 @@ namespace OpenRA.Mods.Common.Traits } else { - Tiles[uv] = CellContents.Empty; + Tiles[uv] = EditorCellContents.Empty; Map.CustomTerrain[uv] = byte.MaxValue; } @@ -143,7 +143,7 @@ namespace OpenRA.Mods.Common.Traits return Math.Max(int2.Lerp(0, type.Info.MaxDensity, adjacent, 9), 1); } - public virtual CellContents UpdateDirtyTile(CPos c) + public virtual EditorCellContents UpdateDirtyTile(CPos c) { var t = Tiles[c]; var type = t.Type; @@ -213,4 +213,13 @@ namespace OpenRA.Mods.Common.Traits disposed = true; } } + + public struct EditorCellContents + { + public static readonly EditorCellContents Empty = default(EditorCellContents); + public ResourceType Type; + public int Density; + public string Variant; + public Sprite Sprite; + } } diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index 13df1ab8d0..c227af65c2 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -24,64 +24,38 @@ namespace OpenRA.Mods.Common.Traits public virtual object Create(ActorInitializer init) { return new ResourceLayer(init.Self); } } - public class ResourceLayer : IRenderOverlay, IWorldLoaded, ITickRender, INotifyActorDisposing + public class ResourceLayer : IWorldLoaded { static readonly CellContents EmptyCell = default(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; public bool IsResourceLayerEmpty { get { return resCells < 1; } } - bool disposed; int resCells; + public event Action CellChanged; + public ResourceLayer(Actor self) { world = self.World; buildingInfluence = self.Trait(); 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); - } - } - - void IRenderOverlay.Render(WorldRenderer wr) - { - foreach (var kv in spriteLayers.Values) - kv.Draw(wr.Viewport); } int GetAdjacentCellsWith(ResourceType t, CPos cell) { var sum = 0; - for (var u = -1; u < 2; u++) + var directions = CVec.Directions; + for (var i = 0; i < directions.Length; i++) { - for (var v = -1; v < 2; v++) - { - var c = cell + new CVec(u, v); - if (Content.Contains(c) && Content[c].Type == t) - ++sum; - } + var c = cell + directions[i]; + if (Content.Contains(c) && Content[c].Type == t) + ++sum; } return sum; @@ -92,27 +66,6 @@ namespace OpenRA.Mods.Common.Traits var resources = w.WorldActor.TraitsImplementing() .ToDictionary(r => r.Info.ResourceType, r => r); - // 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."); - } - foreach (var cell in w.Map.AllCells) { ResourceType t; @@ -127,61 +80,21 @@ namespace OpenRA.Mods.Common.Traits foreach (var cell in w.Map.AllCells) { - var type = Content[cell].Type; + var type = GetResourceType(cell); if (type != null) { // Set initial density based on the number of neighboring resources // 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 = GetResource(cell); temp.Density = Math.Max(density, 1); - // Initialize the RenderContent with the initial map state - // because the shroud may not be enabled. - RenderContent[cell] = Content[cell] = temp; - UpdateRenderedSprite(cell); + Content[cell] = temp; } } } - protected virtual void UpdateRenderedSprite(CPos cell) - { - var t = RenderContent[cell]; - if (t.Density > 0) - { - var sprites = t.Type.Variants[t.Variant]; - var frame = int2.Lerp(0, sprites.Length - 1, t.Density, t.Type.Info.MaxDensity); - t.Sprite = sprites[frame]; - } - else - t.Sprite = null; - - RenderContent[cell] = t; - } - - protected virtual string ChooseRandomVariant(ResourceType t) - { - return t.Variants.Keys.Random(Game.CosmeticRandom); - } - - void ITickRender.TickRender(WorldRenderer wr, Actor self) - { - var remove = new List(); - foreach (var c in dirty) - { - if (!self.World.FogObscures(c)) - { - RenderContent[c] = Content[c]; - UpdateRenderedSprite(c); - remove.Add(c); - } - } - - foreach (var r in remove) - dirty.Remove(r); - } - public bool AllowResourceAt(ResourceType rt, CPos cell) { if (!world.Map.Contains(cell)) @@ -212,7 +125,7 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Contains(cell)) return false; - var currentResourceType = GetResource(cell); + var currentResourceType = GetResourceType(cell); return (currentResourceType == newResourceType && !IsFull(cell)) || (currentResourceType == null && AllowResourceAt(newResourceType, cell)); } @@ -224,8 +137,7 @@ namespace OpenRA.Mods.Common.Traits return new CellContents { - Type = t, - Variant = ChooseRandomVariant(t), + Type = t }; } @@ -241,12 +153,14 @@ namespace OpenRA.Mods.Common.Traits cell.Density = Math.Min(cell.Type.Info.MaxDensity, cell.Density + n); Content[p] = cell; - dirty.Add(p); + if (CellChanged != null) + CellChanged(p, cell.Type); } public bool IsFull(CPos cell) { - return Content[cell].Density == Content[cell].Type.Info.MaxDensity; + var cellContents = Content[cell]; + return cellContents.Density == cellContents.Type.Info.MaxDensity; } public ResourceType Harvest(CPos cell) @@ -264,7 +178,8 @@ namespace OpenRA.Mods.Common.Traits else Content[cell] = c; - dirty.Add(cell); + if (CellChanged != null) + CellChanged(cell, c.Type); return c.Type; } @@ -272,7 +187,8 @@ 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) + var c = Content[cell]; + if (c.Type == null) return; --resCells; @@ -281,11 +197,13 @@ namespace OpenRA.Mods.Common.Traits Content[cell] = EmptyCell; world.Map.CustomTerrain[cell] = byte.MaxValue; - dirty.Add(cell); + if (CellChanged != null) + CellChanged(cell, c.Type); } - public ResourceType GetResource(CPos cell) { return Content[cell].Type; } - public ResourceType GetRenderedResource(CPos cell) { return RenderContent[cell].Type; } + public CellContents GetResource(CPos cell) { return Content[cell]; } + public ResourceType GetResourceType(CPos cell) { return Content[cell].Type; } + public int GetResourceDensity(CPos cell) { return Content[cell].Density; } public int GetMaxResourceDensity(CPos cell) { @@ -295,26 +213,11 @@ namespace OpenRA.Mods.Common.Traits return Content[cell].Type.Info.MaxDensity; } - void INotifyActorDisposing.Disposing(Actor self) - { - if (disposed) - return; - - foreach (var kv in spriteLayers.Values) - kv.Dispose(); - - RenderContent.CellEntryChanged -= UpdateSpriteLayers; - - disposed = true; - } - public struct CellContents { public static readonly CellContents Empty = default(CellContents); public ResourceType Type; public int Density; - public string Variant; - public Sprite Sprite; } } } diff --git a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs new file mode 100644 index 0000000000..3b34bdf7a1 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs @@ -0,0 +1,208 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Visualizes the state of the `ResourceLayer`.", " Attach this to the world actor.")] + public class ResourceRendererInfo : ITraitInfo, Requires + { + [FieldLoader.Require] + [Desc("Only render these ResourceType names.")] + public readonly string[] RenderTypes = null; + + public virtual object Create(ActorInitializer init) { return new ResourceRenderer(init.Self, this); } + } + + public class ResourceRenderer : IWorldLoaded, IRenderOverlay, ITickRender, INotifyActorDisposing + { + protected readonly ResourceLayer ResourceLayer; + protected readonly CellLayer RenderContent; + protected readonly ResourceRendererInfo Info; + + readonly HashSet dirty = new HashSet(); + readonly Queue cleanDirty = new Queue(); + readonly Dictionary spriteLayers = new Dictionary(); + + public ResourceRenderer(Actor self, ResourceRendererInfo info) + { + Info = info; + + ResourceLayer = self.Trait(); + ResourceLayer.CellChanged += AddDirtyCell; + + RenderContent = new CellLayer(self.World.Map); + } + + void AddDirtyCell(CPos cell, ResourceType resType) + { + if (resType == null || Info.RenderTypes.Contains(resType.Info.Type)) + dirty.Add(cell); + } + + void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) + { + var resources = w.WorldActor.TraitsImplementing() + .ToDictionary(r => r.Info.ResourceType, r => r); + + // 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."); + } + + // Initialize the RenderContent with the initial map state + // because the shroud may not be enabled. + foreach (var cell in w.Map.AllCells) + { + var type = ResourceLayer.GetResourceType(cell); + if (type != null && Info.RenderTypes.Contains(type.Info.Type)) + { + var resourceContent = ResourceLayer.GetResource(cell); + var rendererCellContents = new RendererCellContents(ChooseRandomVariant(resourceContent.Type), resourceContent.Type, resourceContent.Density); + RenderContent[cell] = rendererCellContents; + UpdateRenderedSprite(cell, rendererCellContents); + } + } + } + + protected void UpdateSpriteLayers(CPos cell, Sprite sprite, PaletteReference palette) + { + foreach (var kv in spriteLayers) + { + // resource.Type is meaningless (and may be null) if resource.Sprite is null + if (sprite != null && palette == kv.Key) + kv.Value.Update(cell, sprite); + else + kv.Value.Update(cell, null); + } + } + + void IRenderOverlay.Render(WorldRenderer wr) + { + foreach (var kv in spriteLayers.Values) + kv.Draw(wr.Viewport); + } + + void ITickRender.TickRender(WorldRenderer wr, Actor self) + { + foreach (var cell in dirty) + { + if (self.World.FogObscures(cell)) + continue; + + var resourceContent = ResourceLayer.GetResource(cell); + if (resourceContent.Density > 0) + { + var cellContents = RenderContent[cell]; + var variant = cellContents.Variant; + if (cellContents.Variant == null || cellContents.Type != resourceContent.Type) + variant = ChooseRandomVariant(resourceContent.Type); + + var rendererCellContents = new RendererCellContents(variant, resourceContent.Type, resourceContent.Density); + RenderContent[cell] = rendererCellContents; + + UpdateRenderedSprite(cell, rendererCellContents); + } + else + { + var rendererCellContents = RendererCellContents.Empty; + RenderContent[cell] = rendererCellContents; + UpdateRenderedSprite(cell, rendererCellContents); + } + + cleanDirty.Enqueue(cell); + } + + while (cleanDirty.Count > 0) + dirty.Remove(cleanDirty.Dequeue()); + } + + protected virtual void UpdateRenderedSprite(CPos cell, RendererCellContents content) + { + var density = content.Density; + var type = content.Type; + if (content.Density > 0) + { + // The call chain for this method (that starts with AddDirtyCell()) guarantees + // that the new content type would still be suitable for this renderer, + // but that is a bit too fragile to rely on in case the code starts changing. + if (!Info.RenderTypes.Contains(type.Info.Type)) + return; + + var sprites = type.Variants[content.Variant]; + var maxDensity = type.Info.MaxDensity; + var frame = int2.Lerp(0, sprites.Length - 1, density, maxDensity); + + UpdateSpriteLayers(cell, sprites[frame], type.Palette); + } + else + UpdateSpriteLayers(cell, null, null); + } + + bool disposed; + void INotifyActorDisposing.Disposing(Actor self) + { + if (disposed) + return; + + foreach (var kv in spriteLayers.Values) + kv.Dispose(); + + ResourceLayer.CellChanged -= AddDirtyCell; + + disposed = true; + } + + protected virtual string ChooseRandomVariant(ResourceType t) + { + return t.Variants.Keys.Random(Game.CosmeticRandom); + } + + public ResourceType GetRenderedResourceType(CPos cell) { return RenderContent[cell].Type; } + + public struct RendererCellContents + { + public readonly string Variant; + public readonly ResourceType Type; + public readonly int Density; + + public static readonly RendererCellContents Empty = default(RendererCellContents); + + public RendererCellContents(string variant, ResourceType type, int density) + { + Variant = variant; + Type = type; + Density = density; + } + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/AddResourceRenderer.cs b/OpenRA.Mods.Common/UpdateRules/Rules/AddResourceRenderer.cs new file mode 100644 index 0000000000..c0f0a527a5 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/AddResourceRenderer.cs @@ -0,0 +1,65 @@ +#region Copyright & License Information +/* + * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class AddResourceRenderer : UpdateRule + { + public override string Name + { + get { return "Add ResourceRenderer trait"; } + } + + public override string Description + { + get { return "The rendering parts of ResourceLayer have been moved to a new trait"; } + } + + readonly List locations = new List(); + + public override IEnumerable AfterUpdate(ModData modData) + { + if (locations.Any()) + yield return "[D2k]ResourceRenderer has been added.\n" + + "You need to adjust the the field RenderTypes on trait [D2k]ResourceRenderer\n" + + "on the following actors:\n" + + UpdateUtils.FormatMessageList(locations); + + locations.Clear(); + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + if (actorNode.ChildrenMatching("ResourceLayer").Any() && !actorNode.ChildrenMatching("ResourceRenderer").Any()) + { + locations.Add("{0} ({1})".F(actorNode.Key, actorNode.Location.Filename)); + var resourceRenderer = new MiniYamlNode("ResourceRenderer", ""); + resourceRenderer.AddNode("RenderTypes", ""); + actorNode.AddNode(resourceRenderer); + } + + if (actorNode.ChildrenMatching("D2kResourceLayer").Any() && !actorNode.ChildrenMatching("D2kResourceRenderer").Any()) + { + actorNode.RenameChildrenMatching("D2kResourceLayer", "ResourceLayer"); + + locations.Add("{0} ({1})".F(actorNode.Key, actorNode.Location.Filename)); + var resourceRenderer = new MiniYamlNode("D2kResourceRenderer", ""); + resourceRenderer.AddNode("RenderTypes", ""); + actorNode.AddNode(resourceRenderer); + } + + yield break; + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs b/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs index b0e2f38449..a8d422203a 100644 --- a/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs +++ b/OpenRA.Mods.Common/Widgets/ViewportControllerWidget.cs @@ -25,7 +25,7 @@ namespace OpenRA.Mods.Common.Widgets public class ViewportControllerWidget : Widget { readonly ModData modData; - readonly ResourceLayer resourceLayer; + readonly ResourceRenderer resourceRenderer; public readonly HotkeyReference ZoomInKey = new HotkeyReference(); public readonly HotkeyReference ZoomOutKey = new HotkeyReference(); @@ -144,7 +144,7 @@ namespace OpenRA.Mods.Common.Widgets tooltipContainer = Exts.Lazy(() => Ui.Root.Get(TooltipContainer)); - resourceLayer = world.WorldActor.TraitOrDefault(); + resourceRenderer = world.WorldActor.TraitOrDefault(); } public override void Initialize(WidgetArgs args) @@ -266,9 +266,9 @@ namespace OpenRA.Mods.Common.Widgets return; } - if (resourceLayer != null) + if (resourceRenderer != null) { - var resource = resourceLayer.GetRenderedResource(cell); + var resource = resourceRenderer.GetRenderedResourceType(cell); if (resource != null) { TooltipType = WorldTooltipType.Resource; diff --git a/OpenRA.Mods.D2k/Traits/SpiceBloom.cs b/OpenRA.Mods.D2k/Traits/SpiceBloom.cs index 68233d5236..3f13774c4e 100644 --- a/OpenRA.Mods.D2k/Traits/SpiceBloom.cs +++ b/OpenRA.Mods.D2k/Traits/SpiceBloom.cs @@ -126,7 +126,7 @@ namespace OpenRA.Mods.D2k.Traits for (var i = 0; i < pieces; i++) { - var cell = cells.SkipWhile(p => resLayer.GetResource(p) == resType && resLayer.IsFull(p)).Cast().RandomOrDefault(self.World.SharedRandom); + var cell = cells.SkipWhile(p => resLayer.GetResourceType(p) == resType && resLayer.IsFull(p)).Cast().RandomOrDefault(self.World.SharedRandom); if (cell == null) cell = cells.Random(self.World.SharedRandom); diff --git a/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs index d361599253..c94a8d2654 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs @@ -15,8 +15,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.D2k.Traits { - using CellContents = D2kResourceLayer.CellContents; - using ClearSides = D2kResourceLayer.ClearSides; + using ClearSides = D2kResourceRenderer.ClearSides; [Desc("Used to render spice with round borders.")] public class D2kEditorResourceLayerInfo : EditorResourceLayerInfo @@ -29,7 +28,7 @@ namespace OpenRA.Mods.D2k.Traits public D2kEditorResourceLayer(Actor self) : base(self) { } - public override CellContents UpdateDirtyTile(CPos c) + public override EditorCellContents UpdateDirtyTile(CPos c) { var t = Tiles[c]; @@ -50,11 +49,11 @@ namespace OpenRA.Mods.D2k.Traits var clear = FindClearSides(t.Type, c); if (clear == ClearSides.None) { - var sprites = D2kResourceLayer.Variants[t.Variant]; + var sprites = D2kResourceRenderer.Variants[t.Variant]; var frame = t.Density > t.Type.Info.MaxDensity / 2 ? 1 : 0; t.Sprite = t.Type.Variants.First().Value[sprites[frame]]; } - else if (D2kResourceLayer.SpriteMap.TryGetValue(clear, out index)) + else if (D2kResourceRenderer.SpriteMap.TryGetValue(clear, out index)) t.Sprite = t.Type.Variants.First().Value[index]; else t.Sprite = null; @@ -64,7 +63,7 @@ namespace OpenRA.Mods.D2k.Traits protected override string ChooseRandomVariant(ResourceType t) { - return D2kResourceLayer.Variants.Keys.Random(Game.CosmeticRandom); + return D2kResourceRenderer.Variants.Keys.Random(Game.CosmeticRandom); } bool CellContains(CPos c, ResourceType t) diff --git a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kResourceRenderer.cs similarity index 79% rename from OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs rename to OpenRA.Mods.D2k/Traits/World/D2kResourceRenderer.cs index 1261c7ed73..28322deeed 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kResourceRenderer.cs @@ -13,17 +13,16 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Traits; -using OpenRA.Traits; namespace OpenRA.Mods.D2k.Traits { - [Desc("Used to render spice with round borders.")] - public class D2kResourceLayerInfo : ResourceLayerInfo + [Desc("Used to render spice with round borders.", "Attach this to the world actor")] + public class D2kResourceRendererInfo : ResourceRendererInfo { - public override object Create(ActorInitializer init) { return new D2kResourceLayer(init.Self); } + public override object Create(ActorInitializer init) { return new D2kResourceRenderer(init.Self, this); } } - public class D2kResourceLayer : ResourceLayer + public class D2kResourceRenderer : ResourceRenderer { [Flags] public enum ClearSides : byte @@ -101,8 +100,8 @@ namespace OpenRA.Mods.D2k.Traits { ClearSides.Bottom | ClearSides.TopLeft | ClearSides.BottomLeft | ClearSides.BottomRight, 49 }, }; - public D2kResourceLayer(Actor self) - : base(self) { } + public D2kResourceRenderer(Actor self, D2kResourceRendererInfo info) + : base(self, info) { } bool CellContains(CPos c, ResourceType t) { @@ -139,46 +138,52 @@ namespace OpenRA.Mods.D2k.Traits return ret; } - void UpdateRenderedTileInner(CPos p) + protected override void UpdateRenderedSprite(CPos cell, RendererCellContents content) { - if (!RenderContent.Contains(p)) - return; + UpdateRenderedSpriteInner(cell, content); - var t = RenderContent[p]; - if (t.Density > 0) + var directions = CVec.Directions; + for (var i = 0; i < directions.Length; i++) + UpdateRenderedSpriteInner(cell + directions[i]); + } + + void UpdateRenderedSpriteInner(CPos cell) + { + UpdateRenderedSpriteInner(cell, RenderContent[cell]); + } + + void UpdateRenderedSpriteInner(CPos cell, RendererCellContents content) + { + var density = content.Density; + var renderType = content.Type; + + if (density > 0 && renderType != null) { - var clear = FindClearSides(t.Type, p); + // The call chain for this method (that starts with AddDirtyCell()) guarantees + // that the new content type would still be suitable for this renderer, + // but that is a bit too fragile to rely on in case the code starts changing. + if (!Info.RenderTypes.Contains(renderType.Info.Type)) + return; + + var clear = FindClearSides(renderType, cell); int index; if (clear == ClearSides.None) { - var sprites = Variants[t.Variant]; - var frame = t.Density > t.Type.Info.MaxDensity / 2 ? 1 : 0; - t.Sprite = t.Type.Variants.First().Value[sprites[frame]]; + var sprites = Variants[content.Variant]; + var frame = density > ResourceLayer.GetMaxResourceDensity(cell) / 2 ? 1 : 0; + + UpdateSpriteLayers(cell, renderType.Variants.First().Value[sprites[frame]], renderType.Palette); } else if (SpriteMap.TryGetValue(clear, out index)) - t.Sprite = t.Type.Variants.First().Value[index]; + { + UpdateSpriteLayers(cell, renderType.Variants.First().Value[index], renderType.Palette); + } else - t.Sprite = null; + throw new InvalidOperationException("SpriteMap does not contain an index for ClearSides type '{0}'".F(clear)); } else - t.Sprite = null; - - RenderContent[p] = t; - } - - protected override void UpdateRenderedSprite(CPos p) - { - // Need to update neighbouring tiles too - UpdateRenderedTileInner(p); - UpdateRenderedTileInner(p + new CVec(-1, -1)); - UpdateRenderedTileInner(p + new CVec(0, -1)); - UpdateRenderedTileInner(p + new CVec(1, -1)); - UpdateRenderedTileInner(p + new CVec(-1, 0)); - UpdateRenderedTileInner(p + new CVec(1, 0)); - UpdateRenderedTileInner(p + new CVec(-1, 1)); - UpdateRenderedTileInner(p + new CVec(0, 1)); - UpdateRenderedTileInner(p + new CVec(1, 1)); + UpdateSpriteLayers(cell, null, null); } protected override string ChooseRandomVariant(ResourceType t) diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index dafe2b023c..2777d62d1f 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -184,6 +184,8 @@ World: Type: Crater Sequence: craters ResourceLayer: + ResourceRenderer: + RenderTypes: Tiberium, BlueTiberium ResourceClaimLayer: WarheadDebugOverlay: CustomTerrainDebugOverlay: diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index bf07b0d0b9..ae6dec51a3 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -144,7 +144,9 @@ World: DomainIndex: WarheadDebugOverlay: BuildableTerrainLayer: - D2kResourceLayer: + ResourceLayer: + D2kResourceRenderer: + RenderTypes: Spice ResourceClaimLayer: CustomTerrainDebugOverlay: SmudgeLayer@Rock: diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index b3e12dba82..c543b1ed8a 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -205,6 +205,8 @@ World: Type: Crater Sequence: craters ResourceLayer: + ResourceRenderer: + RenderTypes: Ore, Gems ResourceClaimLayer: WarheadDebugOverlay: SpawnMapActors: diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index bee4b0f5d6..84586f5bd1 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -279,6 +279,8 @@ World: SmokeType: largesmoke Sequence: largecraters ResourceLayer: + ResourceRenderer: + RenderTypes: Tiberium, BlueTiberium, Veins BridgeLayer: CustomTerrainDebugOverlay: ResourceClaimLayer: