Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
225
OpenRA.Mods.Common/Terrain/DefaultTerrain.cs
Normal file
225
OpenRA.Mods.Common/Terrain/DefaultTerrain.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileSystem;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Mods.Common.MapGenerator;
|
||||
using OpenRA.Mods.Common.UtilityCommands;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Mods.Common.Terrain
|
||||
{
|
||||
public class DefaultTerrainLoader : ITerrainLoader
|
||||
{
|
||||
public DefaultTerrainLoader(ModData modData) { }
|
||||
|
||||
public ITerrainInfo ParseTerrain(IReadOnlyFileSystem fileSystem, string path)
|
||||
{
|
||||
return new DefaultTerrain(fileSystem, path);
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultTerrainTileInfo : TerrainTileInfo
|
||||
{
|
||||
public readonly float ZOffset = 0.0f;
|
||||
public readonly float ZRamp = 1.0f;
|
||||
}
|
||||
|
||||
public class DefaultTerrainTemplateInfo : TerrainTemplateInfo
|
||||
{
|
||||
public readonly ImmutableArray<string> Images;
|
||||
public readonly ImmutableArray<string> DepthImages;
|
||||
public readonly ImmutableArray<int> Frames;
|
||||
public readonly string Palette;
|
||||
|
||||
public DefaultTerrainTemplateInfo(ITerrainInfo terrainInfo, MiniYaml my)
|
||||
: base(terrainInfo, my) { }
|
||||
|
||||
protected override DefaultTerrainTileInfo LoadTileInfo(ITerrainInfo terrainInfo, MiniYaml my)
|
||||
{
|
||||
var tile = new DefaultTerrainTileInfo();
|
||||
FieldLoader.Load(tile, my);
|
||||
|
||||
// Terrain type must be converted from a string to an index
|
||||
tile.GetType().GetField(nameof(tile.TerrainType)).SetValue(tile, terrainInfo.GetTerrainIndex(my.Value));
|
||||
|
||||
// Fall back to the terrain-type color if necessary
|
||||
var overrideColor = terrainInfo.TerrainTypes[tile.TerrainType].Color;
|
||||
if (tile.MinColor == default)
|
||||
tile.GetType().GetField(nameof(tile.MinColor)).SetValue(tile, overrideColor);
|
||||
|
||||
if (tile.MaxColor == default)
|
||||
tile.GetType().GetField(nameof(tile.MaxColor)).SetValue(tile, overrideColor);
|
||||
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultTerrain : ITemplatedTerrainInfo, IDumpSheetsTerrainInfo, ITerrainInfoNotifyMapCreated
|
||||
{
|
||||
[FluentReference]
|
||||
public readonly string Name;
|
||||
public readonly string Id;
|
||||
public readonly Size TileSize = new(24, 24);
|
||||
public readonly int SheetSize = 512;
|
||||
public readonly ImmutableArray<Color> HeightDebugColors = [Color.Red];
|
||||
public readonly ImmutableArray<string> EditorTemplateOrder;
|
||||
public readonly bool IgnoreTileSpriteOffsets;
|
||||
public readonly bool EnableDepth = false;
|
||||
public readonly float MinHeightColorBrightness = 1.0f;
|
||||
public readonly float MaxHeightColorBrightness = 1.0f;
|
||||
public readonly string Palette = TileSet.TerrainPaletteInternalName;
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
public readonly FrozenDictionary<ushort, TerrainTemplateInfo> Templates;
|
||||
[FieldLoader.Ignore]
|
||||
public readonly ImmutableArray<TerrainTemplateInfo> TemplatesInDefinitionOrder;
|
||||
[FieldLoader.Ignore]
|
||||
public readonly FrozenDictionary<string, ImmutableArray<MultiBrushInfo>> MultiBrushCollections;
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
public readonly ImmutableArray<TerrainTypeInfo> TerrainInfo;
|
||||
readonly FrozenDictionary<string, byte> terrainIndexByType;
|
||||
readonly byte defaultWalkableTerrainIndex;
|
||||
|
||||
public DefaultTerrain(IReadOnlyFileSystem fileSystem, string filepath)
|
||||
{
|
||||
var yaml = MiniYaml.FromStream(fileSystem.Open(filepath), filepath)
|
||||
.ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
// General info
|
||||
FieldLoader.Load(this, yaml["General"]);
|
||||
|
||||
// TerrainTypes
|
||||
TerrainInfo = yaml["Terrain"].ToDictionary().Values
|
||||
.Select(y => new TerrainTypeInfo(y))
|
||||
.OrderBy(tt => tt.Type)
|
||||
.ToImmutableArray();
|
||||
|
||||
if (TerrainInfo.Length >= byte.MaxValue)
|
||||
throw new YamlException("Too many terrain types.");
|
||||
|
||||
var tiby = new Dictionary<string, byte>(TerrainInfo.Length);
|
||||
for (byte i = 0; i < TerrainInfo.Length; i++)
|
||||
{
|
||||
var tt = TerrainInfo[i].Type;
|
||||
|
||||
if (!tiby.TryAdd(tt, i))
|
||||
throw new YamlException($"Duplicate terrain type '{tt}' in '{filepath}'.");
|
||||
}
|
||||
|
||||
terrainIndexByType = tiby.ToFrozenDictionary();
|
||||
|
||||
defaultWalkableTerrainIndex = GetTerrainIndex("Clear");
|
||||
|
||||
// Templates
|
||||
TemplatesInDefinitionOrder = yaml["Templates"].Nodes
|
||||
.Select(n => (TerrainTemplateInfo)new DefaultTerrainTemplateInfo(this, n.Value))
|
||||
.ToImmutableArray();
|
||||
Templates = TemplatesInDefinitionOrder
|
||||
.ToFrozenDictionary(t => t.Id);
|
||||
|
||||
MultiBrushCollections =
|
||||
yaml.TryGetValue("MultiBrushCollections", out var collectionDefinitions)
|
||||
? collectionDefinitions.ToDictionary()
|
||||
.Select(kv => new KeyValuePair<string, ImmutableArray<MultiBrushInfo>>(
|
||||
kv.Key,
|
||||
MultiBrushInfo.ParseCollection(kv.Value)))
|
||||
.ToFrozenDictionary()
|
||||
: FrozenDictionary<string, ImmutableArray<MultiBrushInfo>>.Empty;
|
||||
}
|
||||
|
||||
public TerrainTypeInfo this[byte index] => TerrainInfo[index];
|
||||
|
||||
public byte GetTerrainIndex(string type)
|
||||
{
|
||||
if (terrainIndexByType.TryGetValue(type, out var index))
|
||||
return index;
|
||||
|
||||
throw new InvalidDataException($"Tileset '{Id}' lacks terrain type '{type}'");
|
||||
}
|
||||
|
||||
public byte GetTerrainIndex(TerrainTile r)
|
||||
{
|
||||
var tile = Templates[r.Type][r.Index];
|
||||
if (tile.TerrainType != byte.MaxValue)
|
||||
return tile.TerrainType;
|
||||
|
||||
return defaultWalkableTerrainIndex;
|
||||
}
|
||||
|
||||
public TerrainTileInfo GetTileInfo(TerrainTile r)
|
||||
{
|
||||
return Templates[r.Type][r.Index];
|
||||
}
|
||||
|
||||
public bool TryGetTileInfo(TerrainTile r, out TerrainTileInfo info)
|
||||
{
|
||||
if (!Templates.TryGetValue(r.Type, out var tpl) || !tpl.Contains(r.Index))
|
||||
{
|
||||
info = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
info = tpl[r.Index];
|
||||
return info != null;
|
||||
}
|
||||
|
||||
string ITerrainInfo.Id => Id;
|
||||
string ITerrainInfo.Name => Name;
|
||||
Size ITerrainInfo.TileSize => TileSize;
|
||||
ImmutableArray<TerrainTypeInfo> ITerrainInfo.TerrainTypes => TerrainInfo;
|
||||
TerrainTileInfo ITerrainInfo.GetTerrainInfo(TerrainTile r) { return GetTileInfo(r); }
|
||||
bool ITerrainInfo.TryGetTerrainInfo(TerrainTile r, out TerrainTileInfo info) { return TryGetTileInfo(r, out info); }
|
||||
ImmutableArray<Color> ITerrainInfo.HeightDebugColors => HeightDebugColors;
|
||||
IEnumerable<Color> ITerrainInfo.RestrictedPlayerColors { get { return TerrainInfo.Where(ti => ti.RestrictPlayerColor).Select(ti => ti.Color); } }
|
||||
float ITerrainInfo.MinHeightColorBrightness => MinHeightColorBrightness;
|
||||
float ITerrainInfo.MaxHeightColorBrightness => MaxHeightColorBrightness;
|
||||
|
||||
TerrainTile ITerrainInfo.DefaultTerrainTile => new(TemplatesInDefinitionOrder[0].Id, 0);
|
||||
|
||||
ImmutableArray<string> ITemplatedTerrainInfo.EditorTemplateOrder => EditorTemplateOrder;
|
||||
FrozenDictionary<ushort, TerrainTemplateInfo> ITemplatedTerrainInfo.Templates => Templates;
|
||||
ImmutableArray<TerrainTemplateInfo> ITemplatedTerrainInfo.TemplatesInDefinitionOrder => TemplatesInDefinitionOrder;
|
||||
FrozenDictionary<string, ImmutableArray<MultiBrushInfo>> ITemplatedTerrainInfo.MultiBrushCollections => MultiBrushCollections;
|
||||
|
||||
void IDumpSheetsTerrainInfo.DumpSheets(string terrainName, ImmutablePalette palette, ref int sheetCount)
|
||||
{
|
||||
var tileCache = new DefaultTileCache(this);
|
||||
var sb = tileCache.GetSheetBuilder(SheetType.Indexed);
|
||||
foreach (var s in sb.AllSheets)
|
||||
DumpSequenceSheetsCommand.CommitSheet(sb, s, terrainName, palette, ref sheetCount);
|
||||
|
||||
foreach (var s in tileCache.GetSheetBuilder(SheetType.BGRA).AllSheets)
|
||||
DumpSequenceSheetsCommand.CommitSheet(null, s, terrainName, palette, ref sheetCount);
|
||||
}
|
||||
|
||||
void ITerrainInfoNotifyMapCreated.MapCreated(Map map)
|
||||
{
|
||||
// Randomize PickAny tile variants.
|
||||
var r = new MersenneTwister();
|
||||
foreach (var uv in map.AllCells.MapCoords)
|
||||
{
|
||||
var type = map.Tiles[uv].Type;
|
||||
if (!Templates.TryGetValue(type, out var template) || !template.PickAny)
|
||||
continue;
|
||||
|
||||
map.Tiles[uv] = new TerrainTile(type, (byte)r.Next(0, template.TilesCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
180
OpenRA.Mods.Common/Terrain/DefaultTileCache.cs
Normal file
180
OpenRA.Mods.Common/Terrain/DefaultTileCache.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Collections.Immutable;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Mods.Common.Terrain
|
||||
{
|
||||
public record TheaterTemplate(Sprite[] Sprites, int Stride, int Variants);
|
||||
|
||||
public sealed class DefaultTileCache : IDisposable
|
||||
{
|
||||
readonly Cache<SheetType, SheetBuilder> sheetBuilders;
|
||||
readonly Dictionary<ushort, TheaterTemplate> templates = [];
|
||||
readonly MersenneTwister random;
|
||||
|
||||
public Sprite MissingTile { get; }
|
||||
|
||||
public DefaultTileCache(DefaultTerrain terrainInfo, Action<uint, string> onMissingImage = null)
|
||||
{
|
||||
sheetBuilders = new Cache<SheetType, SheetBuilder>(t => new SheetBuilder(t, terrainInfo.SheetSize));
|
||||
|
||||
random = new MersenneTwister();
|
||||
|
||||
var frameCache = new FrameCache(Game.ModData.DefaultFileSystem, Game.ModData.SpriteLoaders);
|
||||
foreach (var t in terrainInfo.Templates)
|
||||
{
|
||||
var variants = new List<Sprite[]>();
|
||||
var templateInfo = (DefaultTerrainTemplateInfo)t.Value;
|
||||
|
||||
for (var ii = 0; ii < templateInfo.Images.Length; ii++)
|
||||
{
|
||||
var i = templateInfo.Images[ii];
|
||||
|
||||
ISpriteFrame[] allFrames;
|
||||
ISpriteFrame[] depthFrames = null;
|
||||
|
||||
if (onMissingImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
allFrames = frameCache[i];
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
onMissingImage(t.Key, i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
allFrames = frameCache[i];
|
||||
|
||||
if (terrainInfo.EnableDepth && templateInfo.DepthImages != null && templateInfo.DepthImages.Length == templateInfo.Images.Length)
|
||||
{
|
||||
var di = templateInfo.DepthImages[ii];
|
||||
if (onMissingImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
depthFrames = frameCache[di];
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
onMissingImage(t.Key, di);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
depthFrames = frameCache[di];
|
||||
}
|
||||
|
||||
var frameCount = terrainInfo.EnableDepth && depthFrames == null ? allFrames.Length / 2 : allFrames.Length;
|
||||
var indices = templateInfo.Frames != null ? templateInfo.Frames : Exts.MakeArray(t.Value.TilesCount, j => j).ToImmutableArray();
|
||||
|
||||
var start = indices.Min();
|
||||
var end = indices.Max();
|
||||
if (start < 0 || end >= frameCount)
|
||||
throw new YamlException($"Template `{t.Key}` uses frames [{start}..{end}] of {i}, but only [0..{frameCount - 1}] actually exist");
|
||||
|
||||
variants.Add(indices.Select(j =>
|
||||
{
|
||||
var f = allFrames[j];
|
||||
var tile = t.Value.Contains(j) ? (DefaultTerrainTileInfo)t.Value[j] : null;
|
||||
|
||||
// The internal z axis is inverted from expectation (negative is closer)
|
||||
var zOffset = tile != null ? -tile.ZOffset : 0;
|
||||
var zRamp = tile != null ? tile.ZRamp : 1f;
|
||||
var offset = new float3(f.Offset, zOffset);
|
||||
var type = SheetBuilder.FrameTypeToSheetType(f.Type);
|
||||
|
||||
var s = sheetBuilders[type].Allocate(f.Size, zRamp, offset);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(s, f.Data, f.Type);
|
||||
|
||||
if (terrainInfo.EnableDepth)
|
||||
{
|
||||
var depthFrame = depthFrames != null ? depthFrames[j] : allFrames[j + frameCount];
|
||||
var depthType = SheetBuilder.FrameTypeToSheetType(depthFrame.Type);
|
||||
var ss = sheetBuilders[depthType].Allocate(depthFrame.Size, zRamp, offset);
|
||||
OpenRA.Graphics.Util.FastCopyIntoChannel(ss, depthFrame.Data, depthFrame.Type);
|
||||
s = new SpriteWithSecondaryData(s, ss.Sheet, ss.Bounds, ss.Channel);
|
||||
}
|
||||
|
||||
return s;
|
||||
}).ToArray());
|
||||
}
|
||||
|
||||
var allSprites = variants.SelectMany(s => s);
|
||||
|
||||
// Ignore the offsets baked into R8 sprites
|
||||
if (terrainInfo.IgnoreTileSpriteOffsets)
|
||||
allSprites = allSprites.Select(s => new Sprite(s.Sheet, s.Bounds, s.ZRamp, new float3(float2.Zero, s.Offset.Z), s.Channel, s.BlendMode));
|
||||
|
||||
if (onMissingImage != null && variants.Count == 0)
|
||||
continue;
|
||||
|
||||
templates.Add(t.Value.Id, new TheaterTemplate(allSprites.ToArray(), variants[0].Length, templateInfo.Images.Length));
|
||||
}
|
||||
|
||||
// 1x1px transparent tile
|
||||
var missingDataLength = 1;
|
||||
var missingFrameType = SpriteFrameType.Indexed8;
|
||||
var missingSheetType = SheetType.Indexed;
|
||||
|
||||
// Avoid creating an indexed sheet if all tiles are BGRA
|
||||
var missing = sheetBuilders.FirstOrDefault();
|
||||
if (missing.Value != null && missing.Key == SheetType.BGRA)
|
||||
{
|
||||
missingDataLength = 4;
|
||||
missingFrameType = SpriteFrameType.Bgra32;
|
||||
missingSheetType = SheetType.BGRA;
|
||||
}
|
||||
|
||||
MissingTile = sheetBuilders[missingSheetType].Add(new byte[missingDataLength], missingFrameType, new Size(1, 1));
|
||||
foreach (var sb in sheetBuilders.Values)
|
||||
sb.Current?.ReleaseBuffer();
|
||||
}
|
||||
|
||||
public bool HasTileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
return TileSprite(r, variant) != MissingTile;
|
||||
}
|
||||
|
||||
public Sprite TileSprite(TerrainTile r, int? variant = null)
|
||||
{
|
||||
if (!templates.TryGetValue(r.Type, out var template))
|
||||
return MissingTile;
|
||||
|
||||
if (r.Index >= template.Stride)
|
||||
return MissingTile;
|
||||
|
||||
var start = template.Variants > 1 ? variant ?? random.Next(template.Variants) : 0;
|
||||
return template.Sprites[start * template.Stride + r.Index];
|
||||
}
|
||||
|
||||
public SheetBuilder GetSheetBuilder(SheetType sheetType)
|
||||
{
|
||||
return sheetBuilders[sheetType];
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var sb in sheetBuilders.Values)
|
||||
sb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
109
OpenRA.Mods.Common/Terrain/TerrainInfo.cs
Normal file
109
OpenRA.Mods.Common/Terrain/TerrainInfo.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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.Frozen;
|
||||
using System.Collections.Immutable;
|
||||
using OpenRA.Mods.Common.MapGenerator;
|
||||
|
||||
namespace OpenRA.Mods.Common.Terrain
|
||||
{
|
||||
public interface ITemplatedTerrainInfo : ITerrainInfo
|
||||
{
|
||||
ImmutableArray<string> EditorTemplateOrder { get; }
|
||||
FrozenDictionary<ushort, TerrainTemplateInfo> Templates { get; }
|
||||
ImmutableArray<TerrainTemplateInfo> TemplatesInDefinitionOrder { get; }
|
||||
FrozenDictionary<string, ImmutableArray<MultiBrushInfo>> MultiBrushCollections { get; }
|
||||
}
|
||||
|
||||
public interface ITerrainInfoNotifyMapCreated : ITerrainInfo
|
||||
{
|
||||
void MapCreated(Map map);
|
||||
}
|
||||
|
||||
public class TerrainTemplateInfo
|
||||
{
|
||||
public readonly ushort Id;
|
||||
public readonly int2 Size;
|
||||
public readonly bool PickAny;
|
||||
public readonly ImmutableArray<string> Categories;
|
||||
|
||||
readonly TerrainTileInfo[] tileInfo;
|
||||
|
||||
public TerrainTemplateInfo(ITerrainInfo terrainInfo, MiniYaml my)
|
||||
{
|
||||
FieldLoader.Load(this, my);
|
||||
|
||||
var nodes = my.NodeWithKey("Tiles").Value.Nodes;
|
||||
|
||||
if (!PickAny)
|
||||
{
|
||||
tileInfo = new TerrainTileInfo[Size.X * Size.Y];
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (!int.TryParse(node.Key, out var key))
|
||||
throw new YamlException(
|
||||
$"Tileset `{terrainInfo.Id}` template `{Id}` defines a frame `{node.Key}` that is not a valid integer.");
|
||||
|
||||
if (key < 0 || key >= tileInfo.Length)
|
||||
throw new YamlException(
|
||||
$"Tileset `{terrainInfo.Id}` template `{Id}` references frame {key}, " +
|
||||
$"but only [0..{tileInfo.Length - 1}] are valid for a {Size.X}x{Size.Y} Size template.");
|
||||
|
||||
tileInfo[key] = LoadTileInfo(terrainInfo, node.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tileInfo = new TerrainTileInfo[nodes.Length];
|
||||
|
||||
var i = 0;
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (!int.TryParse(node.Key, out var key))
|
||||
throw new YamlException($"Tileset `{terrainInfo.Id}` template `{Id}` defines a frame `{node.Key}` that is not a valid integer.");
|
||||
|
||||
if (key != i++)
|
||||
throw new YamlException($"Tileset `{terrainInfo.Id}` template `{Id}` is missing a definition for frame {i - 1}.");
|
||||
|
||||
tileInfo[key] = LoadTileInfo(terrainInfo, node.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual TerrainTileInfo LoadTileInfo(ITerrainInfo terrainInfo, MiniYaml my)
|
||||
{
|
||||
var tile = new TerrainTileInfo();
|
||||
FieldLoader.Load(tile, my);
|
||||
|
||||
// Terrain type must be converted from a string to an index
|
||||
tile.GetType().GetField(nameof(tile.TerrainType)).SetValue(tile, terrainInfo.GetTerrainIndex(my.Value));
|
||||
|
||||
// Fall back to the terrain-type color if necessary
|
||||
var overrideColor = terrainInfo.TerrainTypes[tile.TerrainType].Color;
|
||||
if (tile.MinColor == default)
|
||||
tile.GetType().GetField(nameof(tile.MinColor)).SetValue(tile, overrideColor);
|
||||
|
||||
if (tile.MaxColor == default)
|
||||
tile.GetType().GetField(nameof(tile.MaxColor)).SetValue(tile, overrideColor);
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
public TerrainTileInfo this[int index] => tileInfo[index];
|
||||
|
||||
public bool Contains(int index)
|
||||
{
|
||||
return index >= 0 && index < tileInfo.Length;
|
||||
}
|
||||
|
||||
public int TilesCount => tileInfo.Length;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user