diff --git a/OpenRA.Mods.Common/Traits/Buildings/Refinery.cs b/OpenRA.Mods.Common/Traits/Buildings/Refinery.cs index 39026a9a60..b926f6c12e 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Refinery.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Refinery.cs @@ -57,7 +57,6 @@ namespace OpenRA.Mods.Common.Traits { readonly Actor self; readonly RefineryInfo info; - readonly Dictionary resourceValues; PlayerResources playerResources; IEnumerable resourceValueModifiers; @@ -83,8 +82,6 @@ namespace OpenRA.Mods.Common.Traits this.info = info; playerResources = self.Owner.PlayerActor.Trait(); currentDisplayTick = info.TickRate; - resourceValues = self.World.WorldActor.TraitsImplementing() - .ToDictionary(r => r.Info.Type, r => r.Info.ValuePerUnit); } void INotifyCreated.Created(Actor self) @@ -105,7 +102,7 @@ namespace OpenRA.Mods.Common.Traits int IAcceptResources.AcceptResources(string resourceType, int count) { - if (!resourceValues.TryGetValue(resourceType, out var resourceValue)) + if (!playerResources.Info.ResourceValues.TryGetValue(resourceType, out var resourceValue)) return 0; var value = Util.ApplyPercentageModifiers(count * resourceValue, resourceValueModifiers); diff --git a/OpenRA.Mods.Common/Traits/Player/PlayerResources.cs b/OpenRA.Mods.Common/Traits/Player/PlayerResources.cs index a0cbd349c0..aa15b8b894 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlayerResources.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlayerResources.cs @@ -52,6 +52,9 @@ namespace OpenRA.Mods.Common.Traits [NotificationReference("Sounds")] public readonly string CashTickDownNotification = null; + [Desc("Monetery value of each resource type.", "Dictionary of [resource type]: [value per unit].")] + public readonly Dictionary ResourceValues = new Dictionary(); + IEnumerable ILobbyOptions.LobbyOptions(Ruleset rules) { var startingCash = SelectableCash.ToDictionary(c => c.ToString(), c => "$" + c.ToString()); diff --git a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs index 9887a4fcaf..b804fdd1cd 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs @@ -13,22 +13,47 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Required for the map editor to work. Attach this to the world actor.")] - public class EditorResourceLayerInfo : TraitInfo, IResourceLayerInfo, Requires + public class EditorResourceLayerInfo : TraitInfo, IResourceLayerInfo, IMapPreviewSignatureInfo { - public override object Create(ActorInitializer init) { return new EditorResourceLayer(init.Self); } + [FieldLoader.LoadUsing(nameof(LoadResourceTypes))] + public readonly Dictionary ResourceTypes = null; + + // Copied from ResourceLayerInfo + protected static object LoadResourceTypes(MiniYaml yaml) + { + var ret = new Dictionary(); + var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes"); + if (resources != null) + foreach (var r in resources.Value.Nodes) + ret[r.Key] = new ResourceLayerInfo.ResourceTypeInfo(r.Value); + + return ret; + } + + [Desc("Override the density saved in maps with values calculated based on the number of neighbouring resource cells.")] + public readonly bool RecalculateResourceDensity = false; + + void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) + { + ResourceLayerInfo.PopulateMapPreviewSignatureCells(map, ResourceTypes, destinationBuffer); + } + + public override object Create(ActorInitializer init) { return new EditorResourceLayer(init.Self, this); } } public class EditorResourceLayer : IResourceLayer, IWorldLoaded, INotifyActorDisposing { + readonly EditorResourceLayerInfo info; protected readonly Map Map; - protected readonly Dictionary ResourceInfo; - protected readonly Dictionary Resources; + protected readonly Dictionary ResourceTypesByIndex; protected readonly CellLayer Tiles; + protected Dictionary resourceValues; public int NetWorth { get; protected set; } @@ -39,10 +64,7 @@ namespace OpenRA.Mods.Common.Traits ResourceLayerContents IResourceLayer.GetResource(CPos cell) { return Tiles.Contains(cell) ? Tiles[cell] : default; } int IResourceLayer.GetMaxDensity(string resourceType) { - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) - return 0; - - return resourceInfo.MaxDensity; + return info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo) ? resourceInfo.MaxDensity : 0; } bool IResourceLayer.CanAddResource(string resourceType, CPos cell, int amount) { return CanAddResource(resourceType, cell, amount); } @@ -52,17 +74,17 @@ namespace OpenRA.Mods.Common.Traits bool IResourceLayer.IsVisible(CPos cell) { return Map.Contains(cell); } bool IResourceLayer.IsEmpty => false; - public EditorResourceLayer(Actor self) + public EditorResourceLayer(Actor self, EditorResourceLayerInfo info) { if (self.World.Type != WorldType.Editor) return; + this.info = info; Map = self.World.Map; Tiles = new CellLayer(Map); - ResourceInfo = self.TraitsImplementing() - .ToDictionary(r => r.Info.Type, r => r.Info); - Resources = ResourceInfo.Values - .ToDictionary(r => r.ResourceType, r => r.Type); + ResourceTypesByIndex = info.ResourceTypes.ToDictionary( + kv => kv.Value.ResourceIndex, + kv => kv.Key); Map.Resources.CellEntryChanged += UpdateCell; } @@ -72,6 +94,9 @@ namespace OpenRA.Mods.Common.Traits if (w.Type != WorldType.Editor) return; + var playerResourcesInfo = w.Map.Rules.Actors["player"].TraitInfoOrDefault(); + resourceValues = playerResourcesInfo?.ResourceValues ?? new Dictionary(); + foreach (var cell in Map.AllCells) UpdateCell(cell); } @@ -87,9 +112,9 @@ namespace OpenRA.Mods.Common.Traits var newTile = ResourceLayerContents.Empty; var newTerrain = byte.MaxValue; - if (Resources.TryGetValue(tile.Type, out var resourceType) && ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (ResourceTypesByIndex.TryGetValue(tile.Type, out var resourceType) && info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) { - newTile = new ResourceLayerContents(resourceType, CalculateCellDensity(resourceType, cell)); + newTile = new ResourceLayerContents(resourceType, CalculateCellDensity(new ResourceLayerContents(resourceType, tile.Index), cell)); newTerrain = Map.Rules.TerrainInfo.GetTerrainIndex(resourceInfo.TerrainType); } @@ -102,7 +127,10 @@ namespace OpenRA.Mods.Common.Traits Map.CustomTerrain[uv] = newTerrain; CellChanged?.Invoke(cell, newTile.Type); - // Neighbouring cell density depends on this cell + if (!info.RecalculateResourceDensity) + return; + + // Update neighbour density to account for this cell foreach (var d in CVec.Directions) { var neighbouringCell = cell + d; @@ -110,7 +138,10 @@ namespace OpenRA.Mods.Common.Traits continue; var neighbouringTile = Tiles[neighbouringCell]; - var density = CalculateCellDensity(neighbouringTile.Type, neighbouringCell); + if (neighbouringTile.Type == null) + continue; + + var density = CalculateCellDensity(neighbouringTile, neighbouringCell); if (neighbouringTile.Density == density) continue; @@ -124,19 +155,22 @@ namespace OpenRA.Mods.Common.Traits void UpdateNetWorth(string oldResourceType, int oldDensity, string newResourceType, int newDensity) { // Density + 1 as workaround for fixing ResourceLayer.Harvest as it would be very disruptive to balancing - if (oldResourceType != null && oldDensity > 0 && ResourceInfo.TryGetValue(oldResourceType, out var oldResourceInfo)) - NetWorth -= (oldDensity + 1) * oldResourceInfo.ValuePerUnit; + if (oldResourceType != null && oldDensity > 0 && resourceValues.TryGetValue(oldResourceType, out var oldResourceValue)) + NetWorth -= (oldDensity + 1) * oldResourceValue; - if (newResourceType != null && newDensity > 0 && ResourceInfo.TryGetValue(newResourceType, out var newResourceInfo)) - NetWorth += (newDensity + 1) * newResourceInfo.ValuePerUnit; + if (newResourceType != null && newDensity > 0 && resourceValues.TryGetValue(newResourceType, out var newResourceValue)) + NetWorth += (newDensity + 1) * newResourceValue; } - public int CalculateCellDensity(string resourceType, CPos c) + protected virtual int CalculateCellDensity(ResourceLayerContents contents, CPos c) { var resources = Map.Resources; - if (resourceType == null || !ResourceInfo.TryGetValue(resourceType, out var resourceInfo) || resources[c].Type != resourceInfo.ResourceType) + if (contents.Type == null || !info.ResourceTypes.TryGetValue(contents.Type, out var resourceInfo) || resources[c].Type != resourceInfo.ResourceIndex) return 0; + if (!info.RecalculateResourceDensity) + return contents.Density.Clamp(1, resourceInfo.MaxDensity); + // Set density based on the number of neighboring resources var adjacent = 0; for (var u = -1; u < 2; u++) @@ -144,7 +178,7 @@ namespace OpenRA.Mods.Common.Traits for (var v = -1; v < 2; v++) { var cell = c + new CVec(u, v); - if (resources.Contains(cell) && resources[cell].Type == resourceInfo.ResourceType) + if (resources.Contains(cell) && resources[cell].Type == resourceInfo.ResourceIndex) adjacent++; } } @@ -152,23 +186,20 @@ namespace OpenRA.Mods.Common.Traits return Math.Max(int2.Lerp(0, resourceInfo.MaxDensity, adjacent, 9), 1); } - bool AllowResourceAt(string resourceType, CPos cell) + protected virtual bool AllowResourceAt(string resourceType, CPos cell) { - var mapResources = Map.Resources; - if (!mapResources.Contains(cell)) + if (!Map.Ramp.Contains(cell) || Map.Ramp[cell] != 0) return false; - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return false; // Ignore custom terrain types when spawning resources in the editor var terrainInfo = Map.Rules.TerrainInfo; var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(Map.Tiles[cell]).TerrainType].Type; - if (!resourceInfo.AllowedTerrainTypes.Contains(terrainType)) - return false; // TODO: Check against actors in the EditorActorLayer - return resourceInfo.AllowOnRamps || Map.Ramp[cell] == 0; + return resourceInfo.AllowedTerrainTypes.Contains(terrainType); } bool CanAddResource(string resourceType, CPos cell, int amount = 1) @@ -177,57 +208,57 @@ namespace OpenRA.Mods.Common.Traits if (!resources.Contains(cell)) return false; - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return false; // The editor allows the user to replace one resource type with another, so treat mismatching resource type as an empty cell var content = resources[cell]; - if (content.Type != resourceInfo.ResourceType) + if (content.Type != resourceInfo.ResourceIndex) return amount <= resourceInfo.MaxDensity && AllowResourceAt(resourceType, cell); - var oldDensity = content.Type == resourceInfo.ResourceType ? content.Index : 0; + var oldDensity = content.Type == resourceInfo.ResourceIndex ? content.Index : 0; return oldDensity + amount <= resourceInfo.MaxDensity; } - int AddResource(string resourceType, CPos cell, int amount = 1) + protected virtual int AddResource(string resourceType, CPos cell, int amount = 1) { var resources = Map.Resources; if (!resources.Contains(cell)) return 0; - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return 0; // The editor allows the user to replace one resource type with another, so treat mismatching resource type as an empty cell var content = resources[cell]; - var oldDensity = content.Type == resourceInfo.ResourceType ? content.Index : 0; + var oldDensity = content.Type == resourceInfo.ResourceIndex ? content.Index : 0; var density = (byte)Math.Min(resourceInfo.MaxDensity, oldDensity + amount); - Map.Resources[cell] = new ResourceTile((byte)resourceInfo.ResourceType, density); + Map.Resources[cell] = new ResourceTile((byte)resourceInfo.ResourceIndex, density); return density - oldDensity; } - int RemoveResource(string resourceType, CPos cell, int amount = 1) + protected virtual int RemoveResource(string resourceType, CPos cell, int amount = 1) { var resources = Map.Resources; if (!resources.Contains(cell)) return 0; - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return 0; var content = resources[cell]; - if (content.Type == 0 || content.Type != resourceInfo.ResourceType) + if (content.Type == 0 || content.Type != resourceInfo.ResourceIndex) return 0; var oldDensity = content.Index; var density = (byte)Math.Max(0, oldDensity - amount); - resources[cell] = density > 0 ? new ResourceTile((byte)resourceInfo.ResourceType, density) : default; + resources[cell] = density > 0 ? new ResourceTile(resourceInfo.ResourceIndex, density) : default; return oldDensity - density; } - void ClearResources(CPos cell) + protected virtual void ClearResources(CPos cell) { Map.Resources[cell] = default; } diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index 961e60ffac..07d4a81122 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Graphics; +using OpenRA.Primitives; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -31,99 +32,158 @@ namespace OpenRA.Mods.Common.Traits } [Desc("Attach this to the world actor.")] - public class ResourceLayerInfo : TraitInfo, IResourceLayerInfo, Requires, Requires + public class ResourceLayerInfo : TraitInfo, IResourceLayerInfo, Requires, IMapPreviewSignatureInfo { - public override object Create(ActorInitializer init) { return new ResourceLayer(init.Self); } + public class ResourceTypeInfo + { + [FieldLoader.Require] + [Desc("Resource index in the binary map data.")] + public readonly byte ResourceIndex = 0; + + [FieldLoader.Require] + [Desc("Terrain type used to determine unit movement and minimap colors.")] + public readonly string TerrainType = null; + + [FieldLoader.Require] + [Desc("Terrain types that this resource can spawn on.")] + public readonly HashSet AllowedTerrainTypes = null; + + [Desc("Maximum number of resource units allowed in a single cell.")] + public readonly int MaxDensity = 10; + + public ResourceTypeInfo(MiniYaml yaml) + { + FieldLoader.Load(this, yaml); + } + } + + [FieldLoader.LoadUsing(nameof(LoadResourceTypes))] + public readonly Dictionary ResourceTypes = null; + + [Desc("Override the density saved in maps with values calculated based on the number of neighbouring resource cells.")] + public readonly bool RecalculateResourceDensity = false; + + // Copied to EditorResourceLayerInfo, ResourceRendererInfo + protected static object LoadResourceTypes(MiniYaml yaml) + { + var ret = new Dictionary(); + var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes"); + if (resources != null) + foreach (var r in resources.Value.Nodes) + ret[r.Key] = new ResourceTypeInfo(r.Value); + + return ret; + } + + public static void PopulateMapPreviewSignatureCells(Map map, Dictionary resources, List<(MPos, Color)> destinationBuffer) + { + var terrainInfo = map.Rules.TerrainInfo; + var colors = resources.Values.ToDictionary( + r => r.ResourceIndex, + r => terrainInfo.TerrainTypes[terrainInfo.GetTerrainIndex(r.TerrainType)].Color); + + for (var i = 0; i < map.MapSize.X; i++) + { + for (var j = 0; j < map.MapSize.Y; j++) + { + var cell = new MPos(i, j); + if (colors.TryGetValue(map.Resources[cell].Type, out var color)) + destinationBuffer.Add((cell, color)); + } + } + } + + void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) + { + PopulateMapPreviewSignatureCells(map, ResourceTypes, destinationBuffer); + } + + public override object Create(ActorInitializer init) { return new ResourceLayer(init.Self, this); } } public class ResourceLayer : IResourceLayer, IWorldLoaded { + readonly ResourceLayerInfo info; readonly World world; - readonly BuildingInfluence buildingInfluence; - protected readonly Dictionary ResourceInfo; + protected readonly Map Map; + protected readonly BuildingInfluence BuildingInfluence; protected readonly CellLayer Content; + protected readonly Dictionary ResourceTypesByIndex; int resCells; public event Action CellChanged; - public ResourceLayer(Actor self) + public ResourceLayer(Actor self, ResourceLayerInfo info) { + this.info = info; world = self.World; - buildingInfluence = self.Trait(); - ResourceInfo = self.TraitsImplementing() - .ToDictionary(r => r.Info.Type, r => r.Info); - - Content = new CellLayer(world.Map); + Map = world.Map; + BuildingInfluence = self.Trait(); + Content = new CellLayer(Map); + ResourceTypesByIndex = info.ResourceTypes.ToDictionary( + kv => kv.Value.ResourceIndex, + kv => kv.Key); } - int GetAdjacentCellsWith(string resourceType, CPos cell) + protected virtual void WorldLoaded(World w, WorldRenderer wr) { - var sum = 0; - var directions = CVec.Directions; - for (var i = 0; i < directions.Length; i++) - { - var c = cell + directions[i]; - if (Content.Contains(c) && Content[c].Type == resourceType) - ++sum; - } - - return sum; - } - - public void WorldLoaded(World w, WorldRenderer wr) - { - var resources = w.WorldActor.TraitsImplementing() - .ToDictionary(r => r.Info.ResourceType, r => r.Info.Type); - foreach (var cell in w.Map.AllCells) { - if (!resources.TryGetValue(w.Map.Resources[cell].Type, out var resourceType)) + var resource = world.Map.Resources[cell]; + if (!ResourceTypesByIndex.TryGetValue(resource.Type, out var resourceType)) continue; if (!AllowResourceAt(resourceType, cell)) continue; - Content[cell] = CreateResourceCell(resourceType, cell); + Content[cell] = CreateResourceCell(resourceType, cell, resource.Index); } + if (!info.RecalculateResourceDensity) + return; + + // Set initial density based on the number of neighboring resources foreach (var cell in w.Map.AllCells) { - var resourceType = Content[cell].Type; - if (resourceType != null && ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + var resource = Content[cell]; + if (resource.Type == null || !info.ResourceTypes.TryGetValue(resource.Type, out var resourceInfo)) + continue; + + var adjacent = 0; + var directions = CVec.Directions; + for (var i = 0; i < directions.Length; i++) { - // Set initial density based on the number of neighboring resources - // Adjacent includes the current cell, so is always >= 1 - var adjacent = GetAdjacentCellsWith(resourceType, cell); - var density = int2.Lerp(0, resourceInfo.MaxDensity, adjacent, 9); - Content[cell] = new ResourceLayerContents(Content[cell].Type, Math.Max(density, 1)); + var c = cell + directions[i]; + if (Content.Contains(c) && Content[c].Type == resource.Type) + ++adjacent; } + + // Adjacent includes the current cell, so is always >= 1 + var density = Math.Max(int2.Lerp(0, resourceInfo.MaxDensity, adjacent, 9), 1); + Content[cell] = new ResourceLayerContents(resource.Type, density); } } - public bool AllowResourceAt(string resourceType, CPos cell) + void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) { WorldLoaded(w, wr); } + + protected virtual bool AllowResourceAt(string resourceType, CPos cell) { - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!Map.Contains(cell) || Map.Ramp[cell] != 0) return false; - if (!world.Map.Contains(cell)) + if (resourceType == null || !info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return false; - if (!resourceInfo.AllowedTerrainTypes.Contains(world.Map.GetTerrainInfo(cell).Type)) + if (!resourceInfo.AllowedTerrainTypes.Contains(Map.GetTerrainInfo(cell).Type)) return false; - if (!resourceInfo.AllowUnderActors && world.ActorMap.AnyActorsAt(cell)) - return false; - - if (!resourceInfo.AllowUnderBuildings && buildingInfluence.GetBuildingAt(cell) != null) - return false; - - return resourceInfo.AllowOnRamps || world.Map.Ramp[cell] == 0; + return BuildingInfluence.GetBuildingAt(cell) == null; } - ResourceLayerContents CreateResourceCell(string resourceType, CPos cell) + ResourceLayerContents CreateResourceCell(string resourceType, CPos cell, int density) { - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) { world.Map.CustomTerrain[cell] = byte.MaxValue; return ResourceLayerContents.Empty; @@ -132,7 +192,7 @@ namespace OpenRA.Mods.Common.Traits world.Map.CustomTerrain[cell] = world.Map.Rules.TerrainInfo.GetTerrainIndex(resourceInfo.TerrainType); ++resCells; - return new ResourceLayerContents(resourceType, 0); + return new ResourceLayerContents(resourceType, density.Clamp(1, resourceInfo.MaxDensity)); } bool CanAddResource(string resourceType, CPos cell, int amount = 1) @@ -140,7 +200,7 @@ namespace OpenRA.Mods.Common.Traits if (!world.Map.Contains(cell)) return false; - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (resourceType == null || !info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return false; var content = Content[cell]; @@ -158,12 +218,12 @@ namespace OpenRA.Mods.Common.Traits if (!Content.Contains(cell)) return 0; - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (resourceType == null || !info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return 0; var content = Content[cell]; if (content.Type == null) - content = CreateResourceCell(resourceType, cell); + content = CreateResourceCell(resourceType, cell, 0); if (content.Type != resourceType) return 0; @@ -192,7 +252,7 @@ namespace OpenRA.Mods.Common.Traits if (density == 0) { Content[cell] = ResourceLayerContents.Empty; - world.Map.CustomTerrain[cell] = byte.MaxValue; + Map.CustomTerrain[cell] = byte.MaxValue; --resCells; CellChanged?.Invoke(cell, null); @@ -217,7 +277,7 @@ namespace OpenRA.Mods.Common.Traits return; Content[cell] = ResourceLayerContents.Empty; - world.Map.CustomTerrain[cell] = byte.MaxValue; + Map.CustomTerrain[cell] = byte.MaxValue; --resCells; CellChanged?.Invoke(cell, null); @@ -226,7 +286,7 @@ namespace OpenRA.Mods.Common.Traits ResourceLayerContents IResourceLayer.GetResource(CPos cell) { return Content.Contains(cell) ? Content[cell] : default; } int IResourceLayer.GetMaxDensity(string resourceType) { - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) return 0; return resourceInfo.MaxDensity; diff --git a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs index 8183d6e647..ed61c038dd 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs @@ -21,56 +21,97 @@ namespace OpenRA.Mods.Common.Traits [Desc("Visualizes the state of the `ResourceLayer`.", " Attach this to the world actor.")] public class ResourceRendererInfo : TraitInfo, Requires { - [FieldLoader.Require] - [Desc("Only render these ResourceType names.")] - public readonly string[] RenderTypes = null; + public class ResourceTypeInfo + { + [Desc("Sequence image that holds the different variants.")] + public readonly string Image = "resources"; + + [FieldLoader.Require] + [SequenceReference(nameof(Image))] + [Desc("Randomly chosen image sequences.")] + public readonly string[] Sequences = { }; + + [PaletteReference] + [Desc("Palette used for rendering the resource sprites.")] + public readonly string Palette = TileSet.TerrainPaletteInternalName; + + [FieldLoader.Require] + [Desc("Resource name used by tooltips.")] + public readonly string Name = null; + + public ResourceTypeInfo(MiniYaml yaml) + { + FieldLoader.Load(this, yaml); + } + } + + [FieldLoader.LoadUsing(nameof(LoadResourceTypes))] + public readonly Dictionary ResourceTypes = null; + + // Copied from ResourceLayerInfo + protected static object LoadResourceTypes(MiniYaml yaml) + { + var ret = new Dictionary(); + var resources = yaml.Nodes.FirstOrDefault(n => n.Key == "ResourceTypes"); + if (resources != null) + foreach (var r in resources.Value.Nodes) + ret[r.Key] = new ResourceTypeInfo(r.Value); + + return ret; + } public override object Create(ActorInitializer init) { return new ResourceRenderer(init.Self, this); } } public class ResourceRenderer : IResourceRenderer, IWorldLoaded, IRenderOverlay, ITickRender, INotifyActorDisposing { - protected readonly IResourceLayer ResourceLayer; - protected readonly CellLayer RenderContent; protected readonly ResourceRendererInfo Info; - protected readonly Dictionary ResourceInfo; + protected readonly IResourceLayer ResourceLayer; + protected readonly CellLayer RenderContents; + protected readonly Dictionary> Variants = new Dictionary>(); + protected readonly World World; readonly HashSet dirty = new HashSet(); readonly Queue cleanDirty = new Queue(); TerrainSpriteLayer shadowLayer; TerrainSpriteLayer spriteLayer; + bool disposed; public ResourceRenderer(Actor self, ResourceRendererInfo info) { Info = info; + World = self.World; ResourceLayer = self.Trait(); ResourceLayer.CellChanged += AddDirtyCell; - ResourceInfo = self.TraitsImplementing() - .ToDictionary(r => r.Info.Type, r => r); - - RenderContent = new CellLayer(self.World.Map); + RenderContents = new CellLayer(self.World.Map); } void AddDirtyCell(CPos cell, string resourceType) { - if (resourceType == null || Info.RenderTypes.Contains(resourceType)) + if (resourceType == null || Info.ResourceTypes.ContainsKey(resourceType)) dirty.Add(cell); } - void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) + protected virtual void WorldLoaded(World w, WorldRenderer wr) { - foreach (var resourceType in ResourceInfo.Values) + var sequences = w.Map.Rules.Sequences; + foreach (var kv in Info.ResourceTypes) { + var resourceInfo = kv.Value; + var resourceVariants = resourceInfo.Sequences + .ToDictionary(v => v, v => sequences.GetSequence(resourceInfo.Image, v)); + Variants.Add(kv.Key, resourceVariants); + if (spriteLayer == null) { - var first = resourceType.Variants.First().Value.GetSprite(0); + var first = resourceVariants.First().Value.GetSprite(0); var emptySprite = new Sprite(first.Sheet, Rectangle.Empty, TextureChannel.Alpha); spriteLayer = new TerrainSpriteLayer(w, wr, emptySprite, first.BlendMode, wr.World.Type != WorldType.Editor); } if (shadowLayer == null) { - var firstWithShadow = resourceType.Variants.Values.FirstOrDefault(v => v.ShadowStart > 0); + var firstWithShadow = resourceVariants.Values.FirstOrDefault(v => v.ShadowStart > 0); if (firstWithShadow != null) { var first = firstWithShadow.GetShadow(0, WAngle.Zero); @@ -80,30 +121,36 @@ namespace OpenRA.Mods.Common.Traits } // All resources must share a blend mode - var sprites = resourceType.Variants.Values.SelectMany(v => Exts.MakeArray(v.Length, x => v.GetSprite(x))); + var sprites = resourceVariants.Values.SelectMany(v => Exts.MakeArray(v.Length, x => v.GetSprite(x))); if (sprites.Any(s => s.BlendMode != spriteLayer.BlendMode)) throw new InvalidDataException("Resource sprites specify different blend modes. " + "Try using different ResourceRenderer traits for resource types that use different blend modes."); } - // Initialize the RenderContent with the initial map state - // because the shroud may not be enabled. + // Initialize the RenderContent with the initial map state so it is visible + // through the fog with the Explored Map option enabled foreach (var cell in w.Map.AllCells) { - var type = ResourceLayer.GetResource(cell).Type; - if (type != null && Info.RenderTypes.Contains(type)) + var resource = ResourceLayer.GetResource(cell); + var rendererCellContents = CreateRenderCellContents(wr, resource, cell); + if (rendererCellContents.Type != null) { - var resourceContent = ResourceLayer.GetResource(cell); - if (!ResourceInfo.TryGetValue(resourceContent.Type, out var resourceType)) - continue; - - var rendererCellContents = new RendererCellContents(ChooseRandomVariant(resourceType), resourceType, resourceContent.Density); - RenderContent[cell] = rendererCellContents; + RenderContents[cell] = rendererCellContents; UpdateRenderedSprite(cell, rendererCellContents); } } } + void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) { WorldLoaded(w, wr); } + + protected RendererCellContents CreateRenderCellContents(WorldRenderer wr, ResourceLayerContents contents, CPos cell) + { + if (contents.Type != null && contents.Density > 0 && Info.ResourceTypes.TryGetValue(contents.Type, out var resourceInfo)) + return new RendererCellContents(contents.Type, contents.Density, resourceInfo, ChooseVariant(contents.Type, cell), wr.Palette(resourceInfo.Palette)); + + return RendererCellContents.Empty; + } + protected void UpdateSpriteLayers(CPos cell, ISpriteSequence sequence, int frame, PaletteReference palette) { // resource.Type is meaningless (and may be null) if resource.Sequence is null @@ -132,27 +179,21 @@ namespace OpenRA.Mods.Common.Traits if (!ResourceLayer.IsVisible(cell)) continue; - var resourceContent = ResourceLayer.GetResource(cell); - if (resourceContent.Density > 0) + var rendererCellContents = RendererCellContents.Empty; + var contents = ResourceLayer.GetResource(cell); + if (contents.Density > 0) { - var cellContents = RenderContent[cell]; - var resourceData = ResourceInfo[resourceContent.Type]; - var variant = cellContents.Variant; - if (cellContents.Variant == null || cellContents.Type.Info.Type != resourceContent.Type) - variant = ChooseRandomVariant(resourceData); + rendererCellContents = RenderContents[cell]; - var rendererCellContents = new RendererCellContents(variant, resourceData, resourceContent.Density); - RenderContent[cell] = rendererCellContents; - - UpdateRenderedSprite(cell, rendererCellContents); - } - else - { - var rendererCellContents = RendererCellContents.Empty; - RenderContent[cell] = rendererCellContents; - UpdateRenderedSprite(cell, rendererCellContents); + // Contents are the same, so just update the density + if (rendererCellContents.Type == contents.Type) + rendererCellContents = new RendererCellContents(rendererCellContents, contents.Density); + else + rendererCellContents = CreateRenderCellContents(wr, contents, cell); } + RenderContents[cell] = rendererCellContents; + UpdateRenderedSprite(cell, rendererCellContents); cleanDirty.Enqueue(cell); } @@ -162,28 +203,17 @@ namespace OpenRA.Mods.Common.Traits 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); + var maxDensity = ResourceLayer.GetMaxDensity(content.Type); + var frame = int2.Lerp(0, content.Sequence.Length - 1, content.Density, maxDensity); + UpdateSpriteLayers(cell, content.Sequence, frame, content.Palette); } else UpdateSpriteLayers(cell, null, 0, null); } - bool disposed; - void INotifyActorDisposing.Disposing(Actor self) + protected virtual void Disposing(Actor self) { if (disposed) return; @@ -196,16 +226,18 @@ namespace OpenRA.Mods.Common.Traits disposed = true; } - protected virtual string ChooseRandomVariant(ResourceType t) + void INotifyActorDisposing.Disposing(Actor self) { Disposing(self); } + + protected virtual ISpriteSequence ChooseVariant(string resourceType, CPos cell) { - return t.Variants.Keys.Random(Game.CosmeticRandom); + return Variants[resourceType].Values.Random(World.LocalRandom); } - protected virtual string GetRenderedResourceType(CPos cell) { return RenderContent[cell].Type.Info.Type; } + protected virtual string GetRenderedResourceType(CPos cell) { return RenderContents[cell].Type; } - protected virtual string GetRenderedResourceTooltip(CPos cell) { return RenderContent[cell].Type?.Info.Name; } + protected virtual string GetRenderedResourceTooltip(CPos cell) { return RenderContents[cell].Info?.Name; } - IEnumerable IResourceRenderer.ResourceTypes => ResourceInfo.Keys; + IEnumerable IResourceRenderer.ResourceTypes => Info.ResourceTypes.Keys; string IResourceRenderer.GetRenderedResourceType(CPos cell) { return GetRenderedResourceType(cell); } @@ -213,13 +245,16 @@ namespace OpenRA.Mods.Common.Traits IEnumerable IResourceRenderer.RenderUIPreview(WorldRenderer wr, string resourceType, int2 origin, float scale) { - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!Variants.TryGetValue(resourceType, out var variant)) yield break; - var sequence = resourceInfo.Variants.First().Value; + if (!Info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) + yield break; + + var sequence = variant.First().Value; var sprite = sequence.GetSprite(sequence.Length - 1); var shadow = sequence.GetShadow(sequence.Length - 1, WAngle.Zero); - var palette = resourceInfo.Palette; + var palette = wr.Palette(resourceInfo.Palette); if (shadow != null) yield return new UISpriteRenderable(shadow, WPos.Zero, origin, 0, palette, scale); @@ -229,14 +264,17 @@ namespace OpenRA.Mods.Common.Traits IEnumerable IResourceRenderer.RenderPreview(WorldRenderer wr, string resourceType, WPos origin) { - if (!ResourceInfo.TryGetValue(resourceType, out var resourceInfo)) + if (!Variants.TryGetValue(resourceType, out var variant)) yield break; - var sequence = resourceInfo.Variants.First().Value; + if (!Info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) + yield break; + + var sequence = variant.First().Value; var sprite = sequence.GetSprite(sequence.Length - 1); var shadow = sequence.GetShadow(sequence.Length - 1, WAngle.Zero); var alpha = sequence.GetAlpha(sequence.Length - 1); - var palette = resourceInfo.Palette; + var palette = wr.Palette(resourceInfo.Palette); var tintModifiers = sequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None; if (shadow != null) @@ -247,17 +285,30 @@ namespace OpenRA.Mods.Common.Traits public readonly struct RendererCellContents { - public readonly string Variant; - public readonly ResourceType Type; + public readonly string Type; + public readonly ResourceRendererInfo.ResourceTypeInfo Info; + public readonly ISpriteSequence Sequence; + public readonly PaletteReference Palette; public readonly int Density; public static readonly RendererCellContents Empty = default; - public RendererCellContents(string variant, ResourceType type, int density) + public RendererCellContents(string resourceType, int density, ResourceRendererInfo.ResourceTypeInfo info, ISpriteSequence sequence, PaletteReference palette) { - Variant = variant; - Type = type; + Type = resourceType; Density = density; + Info = info; + Sequence = sequence; + Palette = palette; + } + + public RendererCellContents(RendererCellContents contents, int density) + { + Type = contents.Type; + Density = density; + Info = contents.Info; + Sequence = contents.Sequence; + Palette = contents.Palette; } } } diff --git a/OpenRA.Mods.Common/Traits/World/ResourceType.cs b/OpenRA.Mods.Common/Traits/World/ResourceType.cs deleted file mode 100644 index 67402dc6af..0000000000 --- a/OpenRA.Mods.Common/Traits/World/ResourceType.cs +++ /dev/null @@ -1,107 +0,0 @@ -#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 OpenRA.Graphics; -using OpenRA.Primitives; -using OpenRA.Traits; - -namespace OpenRA.Mods.Common.Traits -{ - public class ResourceTypeInfo : TraitInfo, IMapPreviewSignatureInfo - { - [Desc("Sequence image that holds the different variants.")] - public readonly string Image = "resources"; - - [FieldLoader.Require] - [SequenceReference(nameof(Image))] - [Desc("Randomly chosen image sequences.")] - public readonly string[] Sequences = { }; - - [PaletteReference] - [Desc("Palette used for rendering the resource sprites.")] - public readonly string Palette = TileSet.TerrainPaletteInternalName; - - [Desc("Resource index used in the binary map data.")] - public readonly int ResourceType = 1; - - [Desc("Credit value of a single resource unit.")] - public readonly int ValuePerUnit = 0; - - [Desc("Maximum number of resource units allowed in a single cell.")] - public readonly int MaxDensity = 10; - - [FieldLoader.Require] - [Desc("Resource identifier used by other traits.")] - public readonly string Type = null; - - [FieldLoader.Require] - [Desc("Resource name used by tooltips.")] - public readonly string Name = null; - - [FieldLoader.Require] - [Desc("Terrain type used to determine unit movement and minimap colors.")] - public readonly string TerrainType = null; - - [Desc("Terrain types that this resource can spawn on.")] - public readonly HashSet AllowedTerrainTypes = new HashSet(); - - [Desc("Allow resource to spawn under Mobile actors.")] - public readonly bool AllowUnderActors = false; - - [Desc("Allow resource to spawn under Buildings.")] - public readonly bool AllowUnderBuildings = false; - - [Desc("Allow resource to spawn on ramp tiles.")] - public readonly bool AllowOnRamps = false; - - void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) - { - var terrainInfo = map.Rules.TerrainInfo; - var color = terrainInfo.TerrainTypes[terrainInfo.GetTerrainIndex(TerrainType)].Color; - - for (var i = 0; i < map.MapSize.X; i++) - { - for (var j = 0; j < map.MapSize.Y; j++) - { - var cell = new MPos(i, j); - if (map.Resources[cell].Type == ResourceType) - destinationBuffer.Add((cell, color)); - } - } - } - - public override object Create(ActorInitializer init) { return new ResourceType(this, init.World); } - } - - public class ResourceType : IWorldLoaded - { - public readonly ResourceTypeInfo Info; - public PaletteReference Palette { get; private set; } - public readonly Dictionary Variants; - - public ResourceType(ResourceTypeInfo info, World world) - { - Info = info; - Variants = new Dictionary(); - foreach (var v in info.Sequences) - { - var seq = world.Map.Rules.Sequences.GetSequence(Info.Image, v); - Variants.Add(v, seq); - } - } - - public void WorldLoaded(World w, WorldRenderer wr) - { - Palette = wr.Palette(Info.Palette); - } - } -} diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20201213/RemoveResourceType.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20201213/RemoveResourceType.cs new file mode 100644 index 0000000000..86728f9b40 --- /dev/null +++ b/OpenRA.Mods.Common/UpdateRules/Rules/20201213/RemoveResourceType.cs @@ -0,0 +1,126 @@ +#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; +using System.Collections.Generic; +using System.Linq; + +namespace OpenRA.Mods.Common.UpdateRules.Rules +{ + public class RemoveResourceType : UpdateRule + { + public override string Name => "Remove ResourceType definitions."; + + public override string Description => + "The ResourceType trait has been removed, and resource definitions moved to the\n" + + "ResourceLayer, EditorResourceLayer, ResourceRenderer, and PlayerResources traits."; + + MiniYaml resourceLayer; + MiniYaml resourceRenderer; + MiniYaml values; + + public override IEnumerable BeforeUpdate(ModData modData) + { + resourceLayer = new MiniYaml(""); + resourceRenderer = new MiniYaml(""); + values = new MiniYaml(""); + yield break; + } + + public override IEnumerable AfterUpdate(ModData modData) + { + if (resourceLayer.Nodes.Any()) + yield return "Add the following definitions to your ResourceLayer and EditorResourceLayer definitions:\n\t" + + "RecalculateResourceDensity: true\n\t" + + resourceLayer.ToLines("ResourceTypes").JoinWith("\n\t"); + + if (resourceLayer.Nodes.Any()) + yield return "Add the following definitions to your ResourceRenderer definition:\n\t" + + resourceRenderer.ToLines("ResourceTypes").JoinWith("\n\t"); + + if (values.Nodes.Any()) + yield return "Add the following definition to your ^BasePlayer definition:\n\t" + + "PlayerResources:\n\t\t" + + values.ToLines("ResourceValues").JoinWith("\n\t\t"); + + if (resourceLayer.Nodes.Any()) + yield return "Support for AllowUnderActors, AllowUnderBuildings, and AllowOnRamps have been removed.\n" + + "You must define a custom ResourceLayer subclass if you want to customize the default behaviour."; + } + + public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode) + { + foreach (var resourceNode in actorNode.ChildrenMatching("ResourceType")) + { + var typeNode = resourceNode.LastChildMatching("Type"); + if (typeNode != null) + { + var resourceLayerNode = new MiniYamlNode(typeNode.Value.Value, ""); + resourceLayer.Nodes.Add(resourceLayerNode); + + var resourceRendererNode = new MiniYamlNode(typeNode.Value.Value, ""); + resourceRenderer.Nodes.Add(resourceRendererNode); + + var indexNode = resourceNode.LastChildMatching("ResourceType"); + if (indexNode != null) + { + indexNode.RenameKey("ResourceIndex"); + resourceLayerNode.AddNode(indexNode); + } + + var terrainTypeNode = resourceNode.LastChildMatching("TerrainType"); + if (terrainTypeNode != null) + resourceLayerNode.AddNode(terrainTypeNode); + + var allowedTerrainNode = resourceNode.LastChildMatching("AllowedTerrainTypes"); + if (allowedTerrainNode != null) + resourceLayerNode.AddNode(allowedTerrainNode); + + var maxDensityNode = resourceNode.LastChildMatching("MaxDensity"); + if (maxDensityNode != null) + resourceLayerNode.AddNode(maxDensityNode); + + var valueNode = resourceNode.LastChildMatching("ValuePerUnit"); + if (valueNode != null) + values.Nodes.Add(new MiniYamlNode(typeNode.Value.Value, valueNode.Value.Value)); + + var imageNode = resourceNode.LastChildMatching("Image"); + if (imageNode != null) + resourceRendererNode.AddNode(imageNode); + + var sequencesNode = resourceNode.LastChildMatching("Sequences"); + if (sequencesNode != null) + resourceRendererNode.AddNode(sequencesNode); + + var paletteNode = resourceNode.LastChildMatching("Palette"); + if (paletteNode != null) + resourceRendererNode.AddNode(paletteNode); + + var nameNode = resourceNode.LastChildMatching("Name"); + if (nameNode != null) + resourceRendererNode.AddNode(nameNode); + } + else + yield return "Unable to process definition:\n" + + resourceNode.Value.ToLines(resourceNode.Key).JoinWith("\n") + "\n\n" + + "This override has been removed and must be manually reimplemented if still needed."; + } + + actorNode.RemoveNodes("ResourceType"); + + foreach (var resourceRendererNode in actorNode.ChildrenMatching("ResourceRenderer")) + resourceRendererNode.RemoveNodes("RenderTypes"); + + foreach (var resourceRendererNode in actorNode.ChildrenMatching("D2kResourceRenderer")) + resourceRendererNode.RemoveNodes("RenderTypes"); + } + } +} diff --git a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs index 31794d8990..a40b98be56 100644 --- a/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs +++ b/OpenRA.Mods.Common/UpdateRules/UpdatePath.cs @@ -91,6 +91,7 @@ namespace OpenRA.Mods.Common.UpdateRules new RemovePlaceBuildingPalette(), new ReplaceShadowPalette(), new ReplaceResourceValueModifiers(), + new RemoveResourceType(), }) }; diff --git a/OpenRA.Mods.D2k/Traits/World/D2kResourceRenderer.cs b/OpenRA.Mods.D2k/Traits/World/D2kResourceRenderer.cs index bcf092aaea..979e52da95 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kResourceRenderer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kResourceRenderer.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Linq; +using OpenRA.Graphics; using OpenRA.Mods.Common.Traits; namespace OpenRA.Mods.D2k.Traits @@ -94,36 +95,36 @@ namespace OpenRA.Mods.D2k.Traits public D2kResourceRenderer(Actor self, D2kResourceRendererInfo info) : base(self, info) { } - bool CellContains(CPos c, ResourceType t) + bool CellContains(CPos cell, string resourceType) { - return RenderContent.Contains(c) && RenderContent[c].Type == t; + return RenderContents.Contains(cell) && RenderContents[cell].Type == resourceType; } - ClearSides FindClearSides(ResourceType t, CPos p) + ClearSides FindClearSides(CPos cell, string resourceType) { var ret = ClearSides.None; - if (!CellContains(p + new CVec(0, -1), t)) + if (!CellContains(cell + new CVec(0, -1), resourceType)) ret |= ClearSides.Top | ClearSides.TopLeft | ClearSides.TopRight; - if (!CellContains(p + new CVec(-1, 0), t)) + if (!CellContains(cell + new CVec(-1, 0), resourceType)) ret |= ClearSides.Left | ClearSides.TopLeft | ClearSides.BottomLeft; - if (!CellContains(p + new CVec(1, 0), t)) + if (!CellContains(cell + new CVec(1, 0), resourceType)) ret |= ClearSides.Right | ClearSides.TopRight | ClearSides.BottomRight; - if (!CellContains(p + new CVec(0, 1), t)) + if (!CellContains(cell + new CVec(0, 1), resourceType)) ret |= ClearSides.Bottom | ClearSides.BottomLeft | ClearSides.BottomRight; - if (!CellContains(p + new CVec(-1, -1), t)) + if (!CellContains(cell + new CVec(-1, -1), resourceType)) ret |= ClearSides.TopLeft; - if (!CellContains(p + new CVec(1, -1), t)) + if (!CellContains(cell + new CVec(1, -1), resourceType)) ret |= ClearSides.TopRight; - if (!CellContains(p + new CVec(-1, 1), t)) + if (!CellContains(cell + new CVec(-1, 1), resourceType)) ret |= ClearSides.BottomLeft; - if (!CellContains(p + new CVec(1, 1), t)) + if (!CellContains(cell + new CVec(1, 1), resourceType)) ret |= ClearSides.BottomRight; return ret; @@ -135,39 +136,27 @@ namespace OpenRA.Mods.D2k.Traits var directions = CVec.Directions; for (var i = 0; i < directions.Length; i++) - UpdateRenderedSpriteInner(cell + directions[i]); - } - - void UpdateRenderedSpriteInner(CPos cell) - { - UpdateRenderedSpriteInner(cell, RenderContent[cell]); + { + var neighbour = cell + directions[i]; + UpdateRenderedSpriteInner(neighbour, RenderContents[neighbour]); + } } void UpdateRenderedSpriteInner(CPos cell, RendererCellContents content) { - var density = content.Density; - var renderType = content.Type; - - if (density > 0 && renderType != null) + 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(renderType.Info.Type)) - return; - - var clear = FindClearSides(renderType, cell); + var clear = FindClearSides(cell, content.Type); if (clear == ClearSides.None) { - var sprites = renderType.Variants[content.Variant]; - var frame = density > renderType.Info.MaxDensity / 2 ? 1 : 0; - - UpdateSpriteLayers(cell, sprites, frame, renderType.Palette); + var maxDensity = ResourceLayer.GetMaxDensity(content.Type); + var index = content.Density > maxDensity / 2 ? 1 : 0; + UpdateSpriteLayers(cell, content.Sequence, index, content.Palette); } else if (SpriteMap.TryGetValue(clear, out var index)) { - UpdateSpriteLayers(cell, renderType.Variants.First().Value, index, renderType.Palette); + UpdateSpriteLayers(cell, content.Sequence, index, content.Palette); } else throw new InvalidOperationException("SpriteMap does not contain an index for ClearSides type '{0}'".F(clear)); diff --git a/mods/cnc/rules/player.yaml b/mods/cnc/rules/player.yaml index 6922aab62f..0bc4ad536e 100644 --- a/mods/cnc/rules/player.yaml +++ b/mods/cnc/rules/player.yaml @@ -1,6 +1,10 @@ ^BasePlayer: AlwaysVisible: Shroud: + PlayerResources: + ResourceValues: + Tiberium: 35 + BlueTiberium: 60 EditorPlayer: Inherits: ^BasePlayer diff --git a/mods/cnc/rules/world.yaml b/mods/cnc/rules/world.yaml index 49919f5923..db2e1f3f8a 100644 --- a/mods/cnc/rules/world.yaml +++ b/mods/cnc/rules/world.yaml @@ -126,30 +126,16 @@ Name: Nod InternalName: nod Description: Brotherhood of Nod\nThe Brotherhood is a religious cult centered around their leader Kane\nand the alien substance Tiberium. They utilize stealth technology\nand guerilla tactics to defeat those who oppose them. - ResourceType@green-tib: - Type: Tiberium - Name: Tiberium - ResourceType: 1 - Palette: staticterrain - TerrainType: Tiberium - Sequences: ti1,ti2,ti3,ti4,ti5,ti6,ti7,ti8,ti9,ti10,ti11,ti12 - MaxDensity: 12 - ValuePerUnit: 35 - AllowedTerrainTypes: Clear,Road - AllowUnderActors: true - ResourceType@blue-tib: - Type: BlueTiberium - Name: Tiberium - ResourceType: 2 - Palette: bluetiberium - TerrainType: BlueTiberium - Sequences: bti1,bti2,bti3,bti4,bti5,bti6,bti7,bti8,bti9,bti10,bti11,bti12 - MaxDensity: 12 - ValuePerUnit: 60 - AllowedTerrainTypes: Clear,Road - AllowUnderActors: true ResourceRenderer: - RenderTypes: Tiberium, BlueTiberium + ResourceTypes: + Tiberium: + Sequences: ti1, ti2, ti3, ti4, ti5, ti6, ti7, ti8, ti9, ti10, ti11, ti12 + Palette: staticterrain + Name: Tiberium + BlueTiberium: + Sequences: bti1, bti2, bti3, bti4, bti5, bti6, bti7, bti8, bti9, bti10, bti11, bti12 + Palette: bluetiberium + Name: Tiberium World: Inherits: ^BaseWorld @@ -178,6 +164,18 @@ World: SmokeImage: smoke_m SmokeSequences: idle ResourceLayer: + RecalculateResourceDensity: true + ResourceTypes: + Tiberium: + ResourceIndex: 1 + TerrainType: Tiberium + AllowedTerrainTypes: Clear, Road + MaxDensity: 12 + BlueTiberium: + ResourceIndex: 2 + TerrainType: BlueTiberium + AllowedTerrainTypes: Clear, Road + MaxDensity: 12 ResourceClaimLayer: WarheadDebugOverlay: CustomTerrainDebugOverlay: @@ -262,6 +260,18 @@ EditorWorld: EditorActorLayer: EditorCursorLayer: EditorResourceLayer: + RecalculateResourceDensity: true + ResourceTypes: + Tiberium: + ResourceIndex: 1 + TerrainType: Tiberium + AllowedTerrainTypes: Clear, Road + MaxDensity: 12 + BlueTiberium: + ResourceIndex: 2 + TerrainType: BlueTiberium + AllowedTerrainTypes: Clear, Road + MaxDensity: 12 EditorSelectionLayer: LoadWidgetAtGameStart: EditorActionManager: diff --git a/mods/d2k/maps/shellmap/rules.yaml b/mods/d2k/maps/shellmap/rules.yaml index 67df6fe581..8839acab6b 100644 --- a/mods/d2k/maps/shellmap/rules.yaml +++ b/mods/d2k/maps/shellmap/rules.yaml @@ -1,13 +1,14 @@ Player: -ConquestVictoryConditions: -HarvesterInsurance: + PlayerResources: + ResourceValues: + Spice: 0 World: -CrateSpawner: -SpawnStartingUnits: -MapStartingLocations: - ResourceType@Spice: - ValuePerUnit: 0 ActorSpawnManager: Minimum: 1 Maximum: 3 diff --git a/mods/d2k/rules/player.yaml b/mods/d2k/rules/player.yaml index abeb85deb9..77a0dc9a45 100644 --- a/mods/d2k/rules/player.yaml +++ b/mods/d2k/rules/player.yaml @@ -1,6 +1,9 @@ ^BasePlayer: AlwaysVisible: Shroud: + PlayerResources: + ResourceValues: + Spice: 25 EditorPlayer: Inherits: ^BasePlayer diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml index 23ed0bf743..ca464e7852 100644 --- a/mods/d2k/rules/world.yaml +++ b/mods/d2k/rules/world.yaml @@ -107,19 +107,12 @@ Name: Fremen InternalName: fremen Selectable: false - ResourceType@Spice: - Type: Spice - Name: Spice - ResourceType: 1 - Palette: d2k - TerrainType: Spice - Sequences: spicea, spiceb, spicec, spiced - MaxDensity: 20 - ValuePerUnit: 25 - AllowedTerrainTypes: SpiceSand - AllowUnderActors: true D2kResourceRenderer: - RenderTypes: Spice + ResourceTypes: + Spice: + Sequences: spicea, spiceb, spicec, spiced + Palette: d2k + Name: Spice World: Inherits: ^BaseWorld @@ -146,6 +139,13 @@ World: WarheadDebugOverlay: BuildableTerrainLayer: ResourceLayer: + RecalculateResourceDensity: true + ResourceTypes: + Spice: + ResourceIndex: 1 + TerrainType: Spice + AllowedTerrainTypes: SpiceSand + MaxDensity: 20 ResourceClaimLayer: CustomTerrainDebugOverlay: SmudgeLayer@Rock: @@ -241,6 +241,13 @@ EditorWorld: EditorActorLayer: EditorCursorLayer: EditorResourceLayer: + RecalculateResourceDensity: true + ResourceTypes: + Spice: + ResourceIndex: 1 + TerrainType: Spice + AllowedTerrainTypes: SpiceSand + MaxDensity: 20 EditorSelectionLayer: LoadWidgetAtGameStart: EditorActionManager: diff --git a/mods/ra/maps/desert-shellmap/rules.yaml b/mods/ra/maps/desert-shellmap/rules.yaml index cf01387c16..d2b0ebc956 100644 --- a/mods/ra/maps/desert-shellmap/rules.yaml +++ b/mods/ra/maps/desert-shellmap/rules.yaml @@ -3,6 +3,10 @@ Player: LobbyPrerequisiteCheckbox@GLOBALBOUNTY: Enabled: False Locked: True + PlayerResources: + ResourceValues: + Ore: 0 + Gems: 0 World: -CrateSpawner: @@ -12,8 +16,6 @@ World: BackgroundMusic: intro AllowMuteBackgroundMusic: true DisableWorldSounds: true - ResourceType@ore: - ValuePerUnit: 0 LuaScript: Scripts: desert-shellmap.lua -StartGameNotification: diff --git a/mods/ra/rules/player.yaml b/mods/ra/rules/player.yaml index 0da15bbe06..0bd8a2d554 100644 --- a/mods/ra/rules/player.yaml +++ b/mods/ra/rules/player.yaml @@ -1,6 +1,10 @@ ^BasePlayer: AlwaysVisible: Shroud: + PlayerResources: + ResourceValues: + Ore: 25 + Gems: 50 EditorPlayer: Inherits: ^BasePlayer diff --git a/mods/ra/rules/world.yaml b/mods/ra/rules/world.yaml index d0f475a995..04560ff909 100644 --- a/mods/ra/rules/world.yaml +++ b/mods/ra/rules/world.yaml @@ -148,30 +148,16 @@ RandomFactionMembers: russia, ukraine Side: Random Description: Random Soviet Country\nA random Soviet country will be chosen when the game starts. - ResourceType@ore: - Type: Ore - Name: Valuable Minerals - ResourceType: 1 - TerrainType: Ore - Palette: player - Sequences: gold01,gold02,gold03,gold04 - MaxDensity: 12 - ValuePerUnit: 25 - AllowedTerrainTypes: Clear,Road - AllowUnderActors: true - ResourceType@gem: - Type: Gems - Name: Valuable Minerals - ResourceType: 2 - TerrainType: Gems - Palette: player - Sequences: gem01,gem02,gem03,gem04 - MaxDensity: 3 - ValuePerUnit: 50 - AllowedTerrainTypes: Clear,Road - AllowUnderActors: true ResourceRenderer: - RenderTypes: Ore, Gems + ResourceTypes: + Ore: + Sequences: gold01, gold02, gold03, gold04 + Palette: player + Name: Valuable Minerals + Gems: + Sequences: gem01, gem02, gem03, gem04 + Palette: player + Name: Valuable Minerals World: Inherits: ^BaseWorld @@ -210,6 +196,18 @@ World: SmokeImage: smoke_m SmokeSequences: idle ResourceLayer: + RecalculateResourceDensity: true + ResourceTypes: + Ore: + ResourceIndex: 1 + TerrainType: Ore + AllowedTerrainTypes: Clear, Road + MaxDensity: 12 + Gems: + ResourceIndex: 2 + TerrainType: Gems + AllowedTerrainTypes: Clear, Road + MaxDensity: 3 ResourceClaimLayer: WarheadDebugOverlay: SpawnMapActors: @@ -288,6 +286,18 @@ EditorWorld: EditorActorLayer: EditorCursorLayer: EditorResourceLayer: + RecalculateResourceDensity: true + ResourceTypes: + Ore: + ResourceIndex: 1 + TerrainType: Ore + AllowedTerrainTypes: Clear, Road + MaxDensity: 12 + Gems: + ResourceIndex: 2 + TerrainType: Gems + AllowedTerrainTypes: Clear, Road + MaxDensity: 3 EditorSelectionLayer: LoadWidgetAtGameStart: EditorActionManager: diff --git a/mods/ts/maps/fields-of-green/rules.yaml b/mods/ts/maps/fields-of-green/rules.yaml index ae276a3503..b2ea4c84da 100644 --- a/mods/ts/maps/fields-of-green/rules.yaml +++ b/mods/ts/maps/fields-of-green/rules.yaml @@ -1,5 +1,9 @@ Player: -ConquestVictoryConditions: + PlayerResources: + ResourceValues: + Tiberium: 0 + BlueTiberium: 0 World: -CrateSpawner: @@ -8,8 +12,6 @@ World: -MapStartingLocations: LuaScript: Scripts: fields-of-green.lua - ResourceType@Tiberium: - ValuePerUnit: 0 MusicPlaylist: BackgroundMusic: intro DisableWorldSounds: true diff --git a/mods/ts/rules/player.yaml b/mods/ts/rules/player.yaml index d8174e871d..92d9cd3d02 100644 --- a/mods/ts/rules/player.yaml +++ b/mods/ts/rules/player.yaml @@ -1,6 +1,10 @@ ^BasePlayer: AlwaysVisible: Shroud: + PlayerResources: + ResourceValues: + Tiberium: 25 + BlueTiberium: 40 EditorPlayer: Inherits: ^BasePlayer diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index 5beb7e664f..f9c3af88ca 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -198,39 +198,6 @@ Name: Nod InternalName: nod Description: Brotherhood of Nod\nThe Brotherhood is a religious cult centered around their leader Kane\nand the alien substance Tiberium. They utilize stealth technology\nand guerilla tactics to defeat those who oppose them. - ResourceType@Tiberium: - Type: Tiberium - Name: Tiberium - ResourceType: 1 - Palette: greentiberium - Sequences: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12 - MaxDensity: 12 - ValuePerUnit: 25 - AllowedTerrainTypes: Clear, Rough, DirtRoad - AllowUnderActors: true - TerrainType: Tiberium - ResourceType@BlueTiberium: - Type: BlueTiberium - Name: Tiberium - ResourceType: 2 - Palette: bluetiberium - Sequences: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12 - MaxDensity: 12 - ValuePerUnit: 40 - AllowedTerrainTypes: Clear, Rough, DirtRoad - AllowUnderActors: true - TerrainType: BlueTiberium - ResourceType@Veins: - Type: Veins - Name: Veins - ResourceType: 3 - Palette: player - Sequences: veins - MaxDensity: 1 - ValuePerUnit: 0 - AllowedTerrainTypes: Clear, Rough, DirtRoad - AllowUnderActors: true - TerrainType: Veins TerrainGeometryOverlay: DebugVisualizations: ExitsDebugOverlayManager: @@ -238,7 +205,19 @@ SubterraneanActorLayer: JumpjetActorLayer: ResourceRenderer: - RenderTypes: Tiberium, BlueTiberium, Veins + ResourceTypes: + Tiberium: + Sequences: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12 + Palette: greentiberium + Name: Tiberium + BlueTiberium: + Sequences: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12 + Palette: bluetiberium + Name: Tiberium + Veins: + Sequences: veins + Palette: player + Name: Veins World: Inherits: ^BaseWorld @@ -279,6 +258,22 @@ World: Type: LargeCrater Sequence: largecraters ResourceLayer: + ResourceTypes: + Tiberium: + ResourceIndex: 1 + TerrainType: Tiberium + AllowedTerrainTypes: Clear, Rough, DirtRoad + MaxDensity: 12 + BlueTiberium: + ResourceIndex: 2 + TerrainType: BlueTiberium + AllowedTerrainTypes: Clear, Rough, DirtRoad + MaxDensity: 12 + Veins: + ResourceIndex: 3 + TerrainType: Veins + AllowedTerrainTypes: Clear, Rough, DirtRoad + MaxDensity: 1 BridgeLayer: CustomTerrainDebugOverlay: ResourceClaimLayer: @@ -377,6 +372,22 @@ EditorWorld: EditorActorLayer: EditorCursorLayer: EditorResourceLayer: + ResourceTypes: + Tiberium: + ResourceIndex: 1 + TerrainType: Tiberium + AllowedTerrainTypes: Clear, Rough, DirtRoad + MaxDensity: 12 + BlueTiberium: + ResourceIndex: 2 + TerrainType: BlueTiberium + AllowedTerrainTypes: Clear, Rough, DirtRoad + MaxDensity: 12 + Veins: + ResourceIndex: 3 + TerrainType: Veins + AllowedTerrainTypes: Clear, Rough, DirtRoad + MaxDensity: 1 EditorSelectionLayer: Palette: ra FootprintAlpha: 0.7