diff --git a/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs b/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs new file mode 100644 index 0000000000..7bb81ccf14 --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs @@ -0,0 +1,110 @@ +#region Copyright & License Information +/* + * Copyright 2007-2021 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; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + class TSEditorResourceLayerInfo : EditorResourceLayerInfo, Requires + { + public readonly string VeinType = "Veins"; + + [ActorReference] + [Desc("Actor types that should be treated as veins for adjacency.")] + public readonly HashSet VeinholeActors = new HashSet { }; + + public override object Create(ActorInitializer init) { return new TSEditorResourceLayer(init.Self, this); } + } + + class TSEditorResourceLayer : EditorResourceLayer + { + readonly TSEditorResourceLayerInfo info; + readonly EditorActorLayer actorLayer; + + public TSEditorResourceLayer(Actor self, TSEditorResourceLayerInfo info) + : base(self, info) + { + this.info = info; + actorLayer = self.Trait(); + } + + bool IsValidVeinNeighbour(CPos cell, CPos neighbour) + { + // Cell is automatically valid if it contains a veinhole actor + if (actorLayer.PreviewsAt(neighbour).Any(a => info.VeinholeActors.Contains(a.Info.Name))) + return true; + + // Neighbour must be flat or a cardinal slope, unless the resource cell itself is a slope + if (Map.Ramp[cell] == 0 && Map.Ramp[neighbour] > 4) + return false; + + var terrainInfo = Map.Rules.TerrainInfo; + var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(Map.Tiles[neighbour]).TerrainType].Type; + return info.ResourceTypes[info.VeinType].AllowedTerrainTypes.Contains(terrainType); + } + + protected override bool AllowResourceAt(string resourceType, CPos cell) + { + var mapResources = Map.Resources; + if (!mapResources.Contains(cell)) + return false; + + // Resources are allowed on flat terrain and cardinal slopes + if (Map.Ramp[cell] > 4) + return false; + + 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; + + // Veins must be placed next to a compatible border cell + if (resourceType == info.VeinType) + { + var neighboursValid = Common.Util.ExpandFootprint(cell, false) + .All(c => IsValidVeinNeighbour(cell, c)); + + if (!neighboursValid) + return false; + } + + // TODO: Check against actors in the EditorActorLayer + return true; + } + + protected override int AddResource(string resourceType, CPos cell, int amount = 1) + { + var added = base.AddResource(resourceType, cell, amount); + + // Update neighbouring cells if needed to provide space for vein borders + var resourceIsVeins = resourceType == info.VeinType; + foreach (var c in Common.Util.ExpandFootprint(cell, false)) + { + var resourceIndex = Map.Resources[c].Type; + if (resourceIndex == 0 || !ResourceTypesByIndex.TryGetValue(resourceIndex, out var neighourResourceType)) + neighourResourceType = null; + + var neighbourIsVeins = neighourResourceType == info.VeinType; + if (resourceIsVeins ^ neighbourIsVeins) + ClearResources(c); + } + + return added; + } + } +} diff --git a/OpenRA.Mods.Cnc/Traits/World/TSResourceLayer.cs b/OpenRA.Mods.Cnc/Traits/World/TSResourceLayer.cs new file mode 100644 index 0000000000..9d9cbbd687 --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/World/TSResourceLayer.cs @@ -0,0 +1,120 @@ +#region Copyright & License Information +/* + * Copyright 2007-2021 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; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + class TSResourceLayerInfo : ResourceLayerInfo + { + public readonly string VeinType = "Veins"; + + [ActorReference] + [Desc("Actor types that should be treated as veins for adjacency.")] + public readonly HashSet VeinholeActors = new HashSet { }; + + public override object Create(ActorInitializer init) { return new TSResourceLayer(init.Self, this); } + } + + class TSResourceLayer : ResourceLayer, INotifyActorDisposing + { + readonly TSResourceLayerInfo info; + readonly HashSet veinholeCells = new HashSet(); + + public TSResourceLayer(Actor self, TSResourceLayerInfo info) + : base(self, info) + { + this.info = info; + } + + protected override void WorldLoaded(World w, WorldRenderer wr) + { + // Cache locations of veinhole actors + w.ActorAdded += ActorAddedToWorld; + w.ActorRemoved += ActorRemovedFromWorld; + foreach (var a in w.Actors) + ActorAddedToWorld(a); + + base.WorldLoaded(w, wr); + } + + void ActorAddedToWorld(Actor a) + { + if (info.VeinholeActors.Contains(a.Info.Name)) + foreach (var cell in a.OccupiesSpace.OccupiedCells()) + veinholeCells.Add(cell.Cell); + } + + void ActorRemovedFromWorld(Actor a) + { + if (info.VeinholeActors.Contains(a.Info.Name)) + foreach (var cell in a.OccupiesSpace.OccupiedCells()) + veinholeCells.Remove(cell.Cell); + } + + void INotifyActorDisposing.Disposing(Actor self) + { + self.World.ActorAdded -= ActorAddedToWorld; + self.World.ActorRemoved -= ActorRemovedFromWorld; + } + + bool IsValidResourceNeighbour(CPos cell, CPos neighbour) + { + // Non-vein resources are not allowed in the cardinal neighbours to + // an already existing vein cell + return Content[neighbour].Type != info.VeinType; + } + + bool IsValidVeinNeighbour(CPos cell, CPos neighbour) + { + // Cell is automatically valid if it contains a veinhole actor + if (veinholeCells.Contains(neighbour)) + return true; + + // Neighbour must be flat or a cardinal slope, unless the resource cell itself is a slope + if (Map.Ramp[cell] == 0 && Map.Ramp[neighbour] > 4) + return false; + + // Neighbour must be have a compatible terrain type (which also implies no other resources) + var neighbourTerrain = Map.GetTerrainInfo(neighbour).Type; + var veinInfo = info.ResourceTypes[info.VeinType]; + return neighbourTerrain == veinInfo.TerrainType || veinInfo.AllowedTerrainTypes.Contains(neighbourTerrain); + } + + protected override bool AllowResourceAt(string resourceType, CPos cell) + { + if (!Map.Contains(cell)) + return false; + + // Resources are allowed on flat terrain and cardinal slopes + if (Map.Ramp[cell] > 4) + return false; + + if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) + return false; + + if (!resourceInfo.AllowedTerrainTypes.Contains(Map.GetTerrainInfo(cell).Type)) + return false; + + // Ensure there is space for the vein border tiles (not needed on ramps) + var check = resourceType == info.VeinType ? (Func)IsValidVeinNeighbour : IsValidResourceNeighbour; + var blockedByNeighbours = Map.Ramp[cell] == 0 && !Common.Util.ExpandFootprint(cell, false) + .All(c => check(cell, c)); + + return !blockedByNeighbours && (resourceType == info.VeinType || BuildingInfluence.GetBuildingAt(cell) == null); + } + } +} diff --git a/OpenRA.Mods.Cnc/Traits/World/TSTiberiumRenderer.cs b/OpenRA.Mods.Cnc/Traits/World/TSTiberiumRenderer.cs new file mode 100644 index 0000000000..cace0c9fe1 --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/World/TSTiberiumRenderer.cs @@ -0,0 +1,92 @@ +#region Copyright & License Information +/* + * Copyright 2007-2021 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; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + [Desc("Renders the Tiberian Sun Tiberium resources.", "Attach this to the world actor")] + public class TSTiberiumRendererInfo : ResourceRendererInfo + { + [Desc("Sequences to use for ramp type 1.", "Dictionary of [resource type]: [list of sequences].")] + public readonly Dictionary Ramp1Sequences = new Dictionary(); + + [Desc("Sequences to use for ramp type 2.", "Dictionary of [resource type]: [list of sequences].")] + public readonly Dictionary Ramp2Sequences = new Dictionary(); + + [Desc("Sequences to use for ramp type 3.", "Dictionary of [resource type]: [list of sequences].")] + public readonly Dictionary Ramp3Sequences = new Dictionary(); + + [Desc("Sequences to use for ramp type 4.", "Dictionary of [resource type]: [list of sequences].")] + public readonly Dictionary Ramp4Sequences = new Dictionary(); + + public override object Create(ActorInitializer init) { return new TSTiberiumRenderer(init.Self, this); } + } + + public class TSTiberiumRenderer : ResourceRenderer + { + readonly TSTiberiumRendererInfo info; + readonly World world; + readonly Dictionary> ramp1Variants = new Dictionary>(); + readonly Dictionary> ramp2Variants = new Dictionary>(); + readonly Dictionary> ramp3Variants = new Dictionary>(); + readonly Dictionary> ramp4Variants = new Dictionary>(); + + public TSTiberiumRenderer(Actor self, TSTiberiumRendererInfo info) + : base(self, info) + { + this.info = info; + world = self.World; + } + + void LoadVariants(Dictionary rampSequences, Dictionary> rampVariants) + { + var sequences = world.Map.Rules.Sequences; + foreach (var kv in rampSequences) + { + if (!Info.ResourceTypes.TryGetValue(kv.Key, out var resourceInfo)) + continue; + + var resourceVariants = kv.Value + .ToDictionary(v => v, v => sequences.GetSequence(resourceInfo.Image, v)); + rampVariants.Add(kv.Key, resourceVariants); + } + } + + protected override void WorldLoaded(World w, WorldRenderer wr) + { + LoadVariants(info.Ramp1Sequences, ramp1Variants); + LoadVariants(info.Ramp2Sequences, ramp2Variants); + LoadVariants(info.Ramp3Sequences, ramp3Variants); + LoadVariants(info.Ramp4Sequences, ramp4Variants); + + base.WorldLoaded(w, wr); + } + + protected override ISpriteSequence ChooseVariant(string resourceType, CPos cell) + { + Dictionary> variants; + switch (world.Map.Ramp[cell]) + { + case 1: variants = ramp1Variants; break; + case 2: variants = ramp2Variants; break; + case 3: variants = ramp3Variants; break; + case 4: variants = ramp4Variants; break; + default: variants = Variants; break; + } + + return variants[resourceType].Values.Random(world.LocalRandom); + } + } +} diff --git a/OpenRA.Mods.Cnc/Traits/World/TSVeinsRenderer.cs b/OpenRA.Mods.Cnc/Traits/World/TSVeinsRenderer.cs new file mode 100644 index 0000000000..ff2227a9fb --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/World/TSVeinsRenderer.cs @@ -0,0 +1,338 @@ +#region Copyright & License Information +/* + * Copyright 2007-2021 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; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + [Desc("Renders the Tiberian Sun Vein resources.", "Attach this to the world actor")] + public class TSVeinsRendererInfo : TraitInfo, Requires + { + [FieldLoader.Require] + [Desc("Resource type used for veins.")] + public readonly string ResourceType = null; + + [Desc("Sequence image that holds the different variants.")] + public readonly string Image = "resources"; + + [SequenceReference(nameof(Image))] + [Desc("Vein sequence name.")] + public readonly string Sequence = "veins"; + + [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; + + [ActorReference] + [Desc("Actor types that should be treated as veins for adjacency.")] + public readonly HashSet VeinholeActors = new HashSet { }; + + public override object Create(ActorInitializer init) { return new TSVeinsRenderer(init.Self, this); } + } + + public class TSVeinsRenderer : IResourceRenderer, IWorldLoaded, IRenderOverlay, ITickRender, INotifyActorDisposing + { + [Flags] + enum Adjacency : byte + { + None = 0x0, + MinusX = 0x1, + PlusX = 0x2, + MinusY = 0x4, + PlusY = 0x8, + } + + static readonly Dictionary BorderIndices = new Dictionary() + { + { Adjacency.MinusY, new[] { 3, 4, 5 } }, + { Adjacency.PlusX, new[] { 6, 7, 8 } }, + { Adjacency.MinusY | Adjacency.PlusX, new[] { 9, 10, 11 } }, + { Adjacency.PlusY, new[] { 12, 13, 14 } }, + { Adjacency.MinusY | Adjacency.PlusY, new[] { 15, 16, 17 } }, + { Adjacency.PlusY | Adjacency.PlusX, new[] { 18, 19, 20 } }, + { Adjacency.MinusY | Adjacency.PlusY | Adjacency.PlusX, new[] { 21, 22, 23 } }, + { Adjacency.MinusX, new[] { 24, 25, 26 } }, + { Adjacency.MinusX | Adjacency.MinusY, new[] { 27, 28, 29 } }, + { Adjacency.MinusX | Adjacency.PlusX, new[] { 30, 31, 32 } }, + { Adjacency.MinusX | Adjacency.PlusX | Adjacency.MinusY, new[] { 33, 34, 35 } }, + { Adjacency.MinusX | Adjacency.PlusY, new[] { 36, 37, 38 } }, + { Adjacency.MinusX | Adjacency.MinusY | Adjacency.PlusY, new[] { 39, 40, 41 } }, + { Adjacency.MinusX | Adjacency.PlusX | Adjacency.PlusY, new[] { 42, 43, 44 } }, + { Adjacency.MinusX | Adjacency.PlusX | Adjacency.MinusY | Adjacency.PlusY, new[] { 45, 46, 47 } }, + }; + + static readonly int[] HeavyIndices = { 48, 49, 50, 51 }; + static readonly int[] LightIndices = { 52 }; + static readonly int[] Ramp1Indices = { 53, 54 }; + static readonly int[] Ramp2Indices = { 55, 56 }; + static readonly int[] Ramp3Indices = { 57, 58 }; + static readonly int[] Ramp4Indices = { 59, 60 }; + + readonly TSVeinsRendererInfo info; + readonly World world; + readonly IResourceLayer resourceLayer; + readonly CellLayer renderIndices; + readonly CellLayer borders; + readonly HashSet dirty = new HashSet(); + readonly Queue cleanDirty = new Queue(); + readonly HashSet veinholeCells = new HashSet(); + readonly int maxDensity; + + ISpriteSequence veinSequence; + PaletteReference veinPalette; + TerrainSpriteLayer spriteLayer; + + public TSVeinsRenderer(Actor self, TSVeinsRendererInfo info) + { + this.info = info; + world = self.World; + + resourceLayer = self.Trait(); + resourceLayer.CellChanged += AddDirtyCell; + maxDensity = resourceLayer.GetMaxDensity(info.ResourceType); + + renderIndices = new CellLayer(world.Map); + borders = new CellLayer(world.Map); + } + + void AddDirtyCell(CPos cell, string resourceType) + { + if (resourceType == null || resourceType == info.ResourceType) + dirty.Add(cell); + } + + void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) + { + // Cache locations of veinhole actors + // TODO: Add support for monitoring actors placed in the map editor! + w.ActorAdded += ActorAddedToWorld; + w.ActorRemoved += ActorRemovedFromWorld; + foreach (var a in w.Actors) + ActorAddedToWorld(a); + + veinSequence = w.Map.Rules.Sequences.GetSequence(info.Image, info.Sequence); + veinPalette = wr.Palette(info.Palette); + + var first = veinSequence.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); + + // Initialize the renderIndices 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 resource = resourceLayer.GetResource(cell); + var cellIndices = CalculateCellIndices(resource, cell); + if (cellIndices != null) + { + renderIndices[cell] = cellIndices; + UpdateRenderedSprite(cell, cellIndices); + } + } + } + + int[] CalculateCellIndices(ResourceLayerContents contents, CPos cell) + { + if (contents.Type != info.ResourceType || contents.Density == 0) + return null; + + var ramp = world.Map.Ramp[cell]; + switch (ramp) + { + case 1: return Ramp1Indices; + case 2: return Ramp2Indices; + case 3: return Ramp3Indices; + case 4: return Ramp4Indices; + default: return contents.Density == maxDensity ? HeavyIndices : LightIndices; + } + } + + void IRenderOverlay.Render(WorldRenderer wr) + { + spriteLayer.Draw(wr.Viewport); + } + + void ITickRender.TickRender(WorldRenderer wr, Actor self) + { + foreach (var cell in dirty) + { + if (!resourceLayer.IsVisible(cell)) + continue; + + var contents = resourceLayer.GetResource(cell); + var cellIndices = CalculateCellIndices(contents, cell); + if (cellIndices != renderIndices[cell]) + { + renderIndices[cell] = cellIndices; + UpdateRenderedSprite(cell, cellIndices); + } + + cleanDirty.Enqueue(cell); + } + + while (cleanDirty.Count > 0) + dirty.Remove(cleanDirty.Dequeue()); + } + + bool HasBorder(CPos cell) + { + if (!renderIndices.Contains(cell)) + return false; + + // Draw the vein border if this is a flat cell with veins, or a veinhole + return (world.Map.Ramp[cell] == 0 && renderIndices[cell] != null) || veinholeCells.Contains(cell); + } + + Adjacency CalculateBorders(CPos cell) + { + // Borders are only valid on flat cells + if (world.Map.Ramp[cell] != 0) + return Adjacency.None; + + var ret = Adjacency.None; + if (HasBorder(cell + new CVec(0, -1))) + ret |= Adjacency.MinusY; + + if (HasBorder(cell + new CVec(-1, 0))) + ret |= Adjacency.MinusX; + + if (HasBorder(cell + new CVec(1, 0))) + ret |= Adjacency.PlusX; + + if (HasBorder(cell + new CVec(0, 1))) + ret |= Adjacency.PlusY; + + return ret; + } + + void UpdateRenderedSprite(CPos cell, int[] indices) + { + borders[cell] = Adjacency.None; + UpdateSpriteLayers(cell, indices); + + foreach (var c in Common.Util.ExpandFootprint(cell, false)) + UpdateBorderSprite(c); + } + + void UpdateBorderSprite(CPos cell) + { + // Borders are never drawn on ramps or in cells that contain resources + if (HasBorder(cell) || world.Map.Ramp[cell] != 0) + return; + + var adjacency = CalculateBorders(cell); + if (borders[cell] == adjacency) + return; + + borders[cell] = adjacency; + + if (adjacency == Adjacency.None) + UpdateSpriteLayers(cell, null); + else if (BorderIndices.TryGetValue(adjacency, out var indices)) + UpdateSpriteLayers(cell, indices); + else + throw new InvalidOperationException("SpriteMap does not contain an index for Adjacency type '{0}'".F(adjacency)); + } + + void UpdateSpriteLayers(CPos cell, int[] indices) + { + if (indices != null) + spriteLayer.Update(cell, veinSequence, veinPalette, indices.Random(world.LocalRandom)); + else + spriteLayer.Clear(cell); + } + + void ActorAddedToWorld(Actor a) + { + if (info.VeinholeActors.Contains(a.Info.Name)) + { + foreach (var cell in a.OccupiesSpace.OccupiedCells()) + { + veinholeCells.Add(cell.Cell); + AddDirtyCell(cell.Cell, info.ResourceType); + } + } + } + + void ActorRemovedFromWorld(Actor a) + { + if (info.VeinholeActors.Contains(a.Info.Name)) + { + foreach (var cell in a.OccupiesSpace.OccupiedCells()) + { + veinholeCells.Remove(cell.Cell); + AddDirtyCell(cell.Cell, null); + } + } + } + + void INotifyActorDisposing.Disposing(Actor self) + { + resourceLayer.CellChanged -= AddDirtyCell; + world.ActorAdded -= ActorAddedToWorld; + world.ActorRemoved -= ActorRemovedFromWorld; + } + + IEnumerable IResourceRenderer.ResourceTypes { get { yield return info.ResourceType; } } + + string IResourceRenderer.GetRenderedResourceType(CPos cell) + { + if (renderIndices[cell] != null) + return info.ResourceType; + + // This makes sure harvesters will get a harvest cursor but then move to the next actual resource cell to start harvesting + return borders[cell] != Adjacency.None ? info.ResourceType : null; + } + + string IResourceRenderer.GetRenderedResourceTooltip(CPos cell) + { + if (renderIndices[cell] != null) + return info.Name; + + return borders[cell] != Adjacency.None ? info.Name : null; + } + + IEnumerable IResourceRenderer.RenderUIPreview(WorldRenderer wr, string resourceType, int2 origin, float scale) + { + if (resourceType != info.ResourceType) + yield break; + + var sprite = veinSequence.GetSprite(HeavyIndices.First()); + var palette = wr.Palette(info.Palette); + + yield return new UISpriteRenderable(sprite, WPos.Zero, origin, 0, palette, scale); + } + + IEnumerable IResourceRenderer.RenderPreview(WorldRenderer wr, string resourceType, WPos origin) + { + if (resourceType != info.ResourceType) + yield break; + + var frame = HeavyIndices.First(); + var sprite = veinSequence.GetSprite(frame); + var alpha = veinSequence.GetAlpha(frame); + var palette = wr.Palette(info.Palette); + + var tintModifiers = veinSequence.IgnoreWorldTint ? TintModifiers.IgnoreWorldTint : TintModifiers.None; + yield return new SpriteRenderable(sprite, origin, WVec.Zero, 0, palette, veinSequence.Scale, alpha, float3.Ones, tintModifiers, false); + } + } +} diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs index 3990044d2a..9ce3f232eb 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs @@ -237,6 +237,9 @@ namespace OpenRA.Mods.Cnc.UtilityCommands { 0x03, new byte[] { 0x7E } } }; + // Veins and vein holes + static readonly int[] ValidVeinNeighbours = { 0x7E, 0xA7, 0xB2 }; + static readonly Dictionary DeployableActors = new Dictionary() { { "gadpsa", "lpst" }, @@ -428,9 +431,14 @@ namespace OpenRA.Mods.Cnc.UtilityCommands continue; } + // Fix position of vein hole actors + var location = cell; + if (actorType == "veinhole") + location -= new CVec(1, 1); + var ar = new ActorReference(actorType) { - new LocationInit(cell), + new LocationInit(location), new OwnerInit("Neutral") }; @@ -453,6 +461,19 @@ namespace OpenRA.Mods.Cnc.UtilityCommands continue; } + // TS maps encode the non-harvestable border tiles as overlay + // Only convert to vein resources if the overlay data specifies non-border frames + if (overlayType == 0x7E) + { + var frame = overlayDataPack[overlayIndex[cell]]; + if (frame < 48 || frame > 60) + continue; + + // Pick half or full density based on the frame + map.Resources[cell] = new ResourceTile(3, (byte)(frame == 52 ? 1 : 2)); + continue; + } + var resourceType = ResourceFromOverlay .Where(kv => kv.Value.Contains(overlayType)) .Select(kv => kv.Key) diff --git a/OpenRA.Mods.Common/Util.cs b/OpenRA.Mods.Common/Util.cs index a70109c57a..5728993932 100644 --- a/OpenRA.Mods.Common/Util.cs +++ b/OpenRA.Mods.Common/Util.cs @@ -157,6 +157,11 @@ namespace OpenRA.Mods.Common return Math.Abs(offset.X) < 2 && Math.Abs(offset.Y) < 2; } + public static IEnumerable ExpandFootprint(CPos cell, bool allowDiagonal) + { + return Neighbours(cell, allowDiagonal); + } + public static IEnumerable ExpandFootprint(IEnumerable cells, bool allowDiagonal) { return cells.SelectMany(c => Neighbours(c, allowDiagonal)).Distinct(); diff --git a/mods/ts/maps/1ice6/map.bin b/mods/ts/maps/1ice6/map.bin index bc72d7f3e3..080a3634ad 100644 Binary files a/mods/ts/maps/1ice6/map.bin and b/mods/ts/maps/1ice6/map.bin differ diff --git a/mods/ts/maps/1ice6/map.png b/mods/ts/maps/1ice6/map.png index 8801e0419a..5c2ca45616 100644 Binary files a/mods/ts/maps/1ice6/map.png and b/mods/ts/maps/1ice6/map.png differ diff --git a/mods/ts/maps/2temp7/map.bin b/mods/ts/maps/2temp7/map.bin index 21c3cd8f97..f87d270c49 100644 Binary files a/mods/ts/maps/2temp7/map.bin and b/mods/ts/maps/2temp7/map.bin differ diff --git a/mods/ts/maps/2temp7/map.png b/mods/ts/maps/2temp7/map.png index cad67ba5cd..b1eb994098 100644 Binary files a/mods/ts/maps/2temp7/map.png and b/mods/ts/maps/2temp7/map.png differ diff --git a/mods/ts/maps/2temp7/map.yaml b/mods/ts/maps/2temp7/map.yaml index 15c7b8381d..eb9e0eaafd 100644 --- a/mods/ts/maps/2temp7/map.yaml +++ b/mods/ts/maps/2temp7/map.yaml @@ -465,7 +465,7 @@ Actors: Location: 68,-48 Owner: Neutral Actor136: veinhole - Location: 63,-35 + Location: 62,-36 Owner: Neutral Actor137: srock01 Location: 60,-19 @@ -567,7 +567,7 @@ Actors: Location: 80,25 Owner: Neutral Actor170: veinhole - Location: 71,41 + Location: 70,40 Owner: Neutral Actor171: srock02 Location: 133,-14 diff --git a/mods/ts/maps/arivruns/map.bin b/mods/ts/maps/arivruns/map.bin index bd0097b1f8..749a055c74 100644 Binary files a/mods/ts/maps/arivruns/map.bin and b/mods/ts/maps/arivruns/map.bin differ diff --git a/mods/ts/maps/arivruns/map.png b/mods/ts/maps/arivruns/map.png index d9c34165ad..c72cfea947 100644 Binary files a/mods/ts/maps/arivruns/map.png and b/mods/ts/maps/arivruns/map.png differ diff --git a/mods/ts/maps/arivruns/map.yaml b/mods/ts/maps/arivruns/map.yaml index 32a4515aa3..52d82403b7 100644 --- a/mods/ts/maps/arivruns/map.yaml +++ b/mods/ts/maps/arivruns/map.yaml @@ -1264,7 +1264,7 @@ Actors: Location: 75,48 Owner: Neutral Actor384: veinhole - Location: 138,-13 + Location: 137,-14 Owner: Neutral Actor385: trock01 Location: 144,-16 diff --git a/mods/ts/maps/cliffsin/map.png b/mods/ts/maps/cliffsin/map.png index 6c514d4da6..586f2be632 100644 Binary files a/mods/ts/maps/cliffsin/map.png and b/mods/ts/maps/cliffsin/map.png differ diff --git a/mods/ts/maps/drawbrid/map.png b/mods/ts/maps/drawbrid/map.png index 1d728028bd..d662082fed 100644 Binary files a/mods/ts/maps/drawbrid/map.png and b/mods/ts/maps/drawbrid/map.png differ diff --git a/mods/ts/maps/float/map.bin b/mods/ts/maps/float/map.bin index 9f31287f0b..209d4469d8 100644 Binary files a/mods/ts/maps/float/map.bin and b/mods/ts/maps/float/map.bin differ diff --git a/mods/ts/maps/float/map.png b/mods/ts/maps/float/map.png index 7f5e548329..dd74547eef 100644 Binary files a/mods/ts/maps/float/map.png and b/mods/ts/maps/float/map.png differ diff --git a/mods/ts/maps/float/map.yaml b/mods/ts/maps/float/map.yaml index dfc67cdd1d..2c9a86915c 100644 --- a/mods/ts/maps/float/map.yaml +++ b/mods/ts/maps/float/map.yaml @@ -651,7 +651,7 @@ Actors: Location: 70,-20 Owner: Neutral Actor195: veinhole - Location: 31,11 + Location: 30,10 Owner: Neutral Actor196: srock05 Location: 34,14 @@ -666,13 +666,13 @@ Actors: Location: 98,-44 Owner: Neutral Actor200: veinhole - Location: 97,-39 + Location: 96,-40 Owner: Neutral Actor201: trock03 Location: 102,-44 Owner: Neutral Actor202: veinhole - Location: 71,-8 + Location: 70,-9 Owner: Neutral Actor203: trock01 Location: 96,-33 diff --git a/mods/ts/maps/forestfr/map.png b/mods/ts/maps/forestfr/map.png index 20a0b53368..045f27d5f7 100644 Binary files a/mods/ts/maps/forestfr/map.png and b/mods/ts/maps/forestfr/map.png differ diff --git a/mods/ts/maps/rivrrad4/map.png b/mods/ts/maps/rivrrad4/map.png index eb22c1d6da..8afedac3e6 100644 Binary files a/mods/ts/maps/rivrrad4/map.png and b/mods/ts/maps/rivrrad4/map.png differ diff --git a/mods/ts/maps/springs/map.bin b/mods/ts/maps/springs/map.bin index b874327a19..031933ee84 100644 Binary files a/mods/ts/maps/springs/map.bin and b/mods/ts/maps/springs/map.bin differ diff --git a/mods/ts/maps/springs/map.png b/mods/ts/maps/springs/map.png index da0252c339..63125f5621 100644 Binary files a/mods/ts/maps/springs/map.png and b/mods/ts/maps/springs/map.png differ diff --git a/mods/ts/maps/t_garden/map.bin b/mods/ts/maps/t_garden/map.bin index 3a3c3bd342..e6c0440f23 100644 Binary files a/mods/ts/maps/t_garden/map.bin and b/mods/ts/maps/t_garden/map.bin differ diff --git a/mods/ts/maps/t_garden/map.png b/mods/ts/maps/t_garden/map.png index 3ab1c210df..c2e38d3315 100644 Binary files a/mods/ts/maps/t_garden/map.png and b/mods/ts/maps/t_garden/map.png differ diff --git a/mods/ts/maps/t_garden/map.yaml b/mods/ts/maps/t_garden/map.yaml index d1e4dadddb..7a106ff8ef 100644 --- a/mods/ts/maps/t_garden/map.yaml +++ b/mods/ts/maps/t_garden/map.yaml @@ -872,10 +872,10 @@ Actors: Location: 129,-36 Owner: Neutral Actor253: veinhole - Location: 101,-6 + Location: 100,-7 Owner: Neutral Actor254: veinhole - Location: 86,18 + Location: 85,17 Owner: Neutral Actor255: trock03 Location: 80,60 diff --git a/mods/ts/maps/tactical/map.png b/mods/ts/maps/tactical/map.png index 810e4729da..bd1cc15502 100644 Binary files a/mods/ts/maps/tactical/map.png and b/mods/ts/maps/tactical/map.png differ diff --git a/mods/ts/maps/terrace/map.png b/mods/ts/maps/terrace/map.png index b3c67b5a0a..c4842dbecd 100644 Binary files a/mods/ts/maps/terrace/map.png and b/mods/ts/maps/terrace/map.png differ diff --git a/mods/ts/maps/tiers/map.bin b/mods/ts/maps/tiers/map.bin index 1d0dd77362..d42987a8b8 100644 Binary files a/mods/ts/maps/tiers/map.bin and b/mods/ts/maps/tiers/map.bin differ diff --git a/mods/ts/maps/tiers/map.png b/mods/ts/maps/tiers/map.png index 0c0cc1a6aa..259cc8d112 100644 Binary files a/mods/ts/maps/tiers/map.png and b/mods/ts/maps/tiers/map.png differ diff --git a/mods/ts/maps/tiers/map.yaml b/mods/ts/maps/tiers/map.yaml index 445f06a3dd..a90711a971 100644 --- a/mods/ts/maps/tiers/map.yaml +++ b/mods/ts/maps/tiers/map.yaml @@ -437,13 +437,13 @@ Actors: Location: 55,-5 Owner: Neutral Actor129: veinhole - Location: 61,-18 + Location: 60,-19 Owner: Neutral Actor130: veinhole - Location: 72,-11 + Location: 71,-12 Owner: Neutral Actor131: veinhole - Location: 62,1 + Location: 61,0 Owner: Neutral Rules: diff --git a/mods/ts/maps/tread_l/map.bin b/mods/ts/maps/tread_l/map.bin index 20d4180e23..636a4d039f 100644 Binary files a/mods/ts/maps/tread_l/map.bin and b/mods/ts/maps/tread_l/map.bin differ diff --git a/mods/ts/maps/tread_l/map.png b/mods/ts/maps/tread_l/map.png index 6ba27bbad8..2f65b3008e 100644 Binary files a/mods/ts/maps/tread_l/map.png and b/mods/ts/maps/tread_l/map.png differ diff --git a/mods/ts/rules/nod-structures.yaml b/mods/ts/rules/nod-structures.yaml index f77a9bb23c..b5df72a30f 100644 --- a/mods/ts/rules/nod-structures.yaml +++ b/mods/ts/rules/nod-structures.yaml @@ -508,7 +508,7 @@ NAWAST: Tooltip: Name: Waste Refinery Buildable: - Queue: Buildig + Queue: Building BuildPaletteOrder: 130 Prerequisites: namisl, ~structures.nod, ~techlevel.superweapons BuildLimit: 1 diff --git a/mods/ts/rules/player.yaml b/mods/ts/rules/player.yaml index 92d9cd3d02..87f9eac6bc 100644 --- a/mods/ts/rules/player.yaml +++ b/mods/ts/rules/player.yaml @@ -5,6 +5,7 @@ ResourceValues: Tiberium: 25 BlueTiberium: 40 + Veins: 0 EditorPlayer: Inherits: ^BasePlayer diff --git a/mods/ts/rules/trees.yaml b/mods/ts/rules/trees.yaml index 02bf31a0c6..233b77911a 100644 --- a/mods/ts/rules/trees.yaml +++ b/mods/ts/rules/trees.yaml @@ -99,20 +99,17 @@ TREE24: TREE25: Inherits: ^Tree -VEINHOLEDUMMY: +VEINHOLE: HiddenUnderShroud: RadarColorFromTerrain: Terrain: Veins AppearsOnMapPreview: Terrain: Veins Building: - Footprint: xx xx - Dimensions: 2, 2 + Footprint: xxx xxx xxx + Dimensions: 3, 3 BodyOrientation: QuantizedFacings: 1 - -VEINHOLE: - Inherits: VEINHOLEDUMMY AppearsOnRadar: Tooltip: Name: Veinhole diff --git a/mods/ts/rules/world.yaml b/mods/ts/rules/world.yaml index f9c3af88ca..660cb96389 100644 --- a/mods/ts/rules/world.yaml +++ b/mods/ts/rules/world.yaml @@ -204,7 +204,7 @@ CliffBackImpassabilityLayer: SubterraneanActorLayer: JumpjetActorLayer: - ResourceRenderer: + TSTiberiumRenderer: ResourceTypes: Tiberium: Sequences: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12 @@ -214,10 +214,23 @@ Sequences: tib01, tib02, tib03, tib04, tib05, tib06, tib07, tib08, tib09, tib10, tib11, tib12 Palette: bluetiberium Name: Tiberium - Veins: - Sequences: veins - Palette: player - Name: Veins + Ramp1Sequences: + Tiberium: tib13, tib14 + BlueTiberium: tib13, tib14 + Ramp2Sequences: + Tiberium: tib15, tib16 + BlueTiberium: tib15, tib16 + Ramp3Sequences: + Tiberium: tib17, tib18 + BlueTiberium: tib17, tib18 + Ramp4Sequences: + Tiberium: tib19, tib20 + BlueTiberium: tib19, tib20 + TSVeinsRenderer: + ResourceType: Veins + Name: Veins + Palette: player + VeinholeActors: veinhole World: Inherits: ^BaseWorld @@ -257,7 +270,8 @@ World: SmudgeLayer@LARGECRATER: Type: LargeCrater Sequence: largecraters - ResourceLayer: + TSResourceLayer: + VeinholeActors: veinhole ResourceTypes: Tiberium: ResourceIndex: 1 @@ -273,7 +287,7 @@ World: ResourceIndex: 3 TerrainType: Veins AllowedTerrainTypes: Clear, Rough, DirtRoad - MaxDensity: 1 + MaxDensity: 2 BridgeLayer: CustomTerrainDebugOverlay: ResourceClaimLayer: @@ -371,7 +385,7 @@ EditorWorld: Inherits: ^BaseWorld EditorActorLayer: EditorCursorLayer: - EditorResourceLayer: + TSEditorResourceLayer: ResourceTypes: Tiberium: ResourceIndex: 1 @@ -387,7 +401,8 @@ EditorWorld: ResourceIndex: 3 TerrainType: Veins AllowedTerrainTypes: Clear, Rough, DirtRoad - MaxDensity: 1 + MaxDensity: 2 + VeinholeActors: veinhole EditorSelectionLayer: Palette: ra FootprintAlpha: 0.7 diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml index f3d022f692..7fa15e745e 100644 --- a/mods/ts/sequences/misc.yaml +++ b/mods/ts/sequences/misc.yaml @@ -400,20 +400,19 @@ resources: tib10: tib10 tib11: tib11 tib12: tib12 - tib13: tib13 # TODO: NW ramp variant, currently unused - tib14: tib14 # TODO: NW ramp variant, currently unused - tib15: tib15 # TODO: NE ramp variant, currently unused - tib16: tib16 # TODO: NE ramp variant, currently unused - tib17: tib17 # TODO: SE ramp variant, currently unused - tib18: tib18 # TODO: SE ramp variant, currently unused - tib19: tib19 # TODO: SW ramp variant, currently unused - tib20: tib20 # TODO: SW ramp variant, currently unused + tib13: tib13 + tib14: tib14 + tib15: tib15 + tib16: tib16 + tib17: tib17 + tib18: tib18 + tib19: tib19 + tib20: tib20 veins: veins - Length: 1 - Start: 52 + Length: * ShadowStart: -1 - Tick: 180 - Offset: 0, -12, 2.5 + # TODO: Reduce z offset again after fixing #12229 + Offset: 0, -12, 5.5 IgnoreWorldTint: false veins: diff --git a/mods/ts/sequences/trees.yaml b/mods/ts/sequences/trees.yaml index 3f65b4e28d..229e9ae826 100644 --- a/mods/ts/sequences/trees.yaml +++ b/mods/ts/sequences/trees.yaml @@ -192,5 +192,5 @@ veinhole: idle: ShadowStart: 1 UseTilesetExtension: true - Offset: 0,-60 + Offset: 0, -36 ZRamp: 1