Use MultiBrush as the input and output of TilingPath
This generalizes TilingPath to accept MultiBrushes instead of templates and makes the output also a MultiBrush. As a result: - Composite or faked templates can be used in tiling. This will (in a later change) be used to support CnC beaches outside of the desert tileset. - The tiling result can be previewed without committing it onto the map, making it more viable for adoption as an editor tool. - The full shape of the tiling result can be inspected without messily reading it back off of the map, simplifying some map generator logic. - Separates map generation-specific segment definitions from the core template definitions. - As MultiBrushes can be many-to-one with templates, there is no need to have a one-to-many template-segment relationship. Multi-segment templates, such as roads, now use multiple brushes instead. This simplifies the logic of segment consumers. Previously, MultiBrushes which contained no offset information in their definitions were automatically aligned such that the first tile was under 0,0. This is no longer the case. This was previously done for use in MultiBrush.PaintArea to improve tile packing success, but the alignment is now handled automatically by PaintArea instead. Due to some impure refactoring which changes the randomization of tiling choices, map generation results may change.
This commit is contained in:
committed by
Gustas Kažukauskas
parent
cf11c6633e
commit
a2096b6768
@@ -13,6 +13,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using OpenRA.Mods.Common.Terrain;
|
||||
using OpenRA.Support;
|
||||
|
||||
@@ -24,19 +25,72 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// </summary>
|
||||
public sealed class MultiBrushInfo
|
||||
{
|
||||
public readonly int Weight;
|
||||
public readonly ImmutableArray<string> Actors;
|
||||
public readonly TerrainTile? BackingTile;
|
||||
public readonly ImmutableArray<ushort> Templates;
|
||||
public readonly ImmutableArray<TerrainTile> Tiles;
|
||||
public sealed class ActorInfo
|
||||
{
|
||||
[FieldLoader.Ignore]
|
||||
public readonly string Type;
|
||||
public readonly WVec Offset = new(0, 0, 0);
|
||||
|
||||
public ActorInfo(MiniYaml my)
|
||||
{
|
||||
if (string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException("Missing actor type");
|
||||
|
||||
Type = my.Value;
|
||||
FieldLoader.Load(this, my);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TemplateInfo
|
||||
{
|
||||
[FieldLoader.Ignore]
|
||||
public readonly ushort Type;
|
||||
public readonly CVec Offset = new(0, 0);
|
||||
|
||||
public TemplateInfo(MiniYaml my)
|
||||
{
|
||||
if (string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException("Missing template type");
|
||||
|
||||
if (!Exts.TryParseUshortInvariant(my.Value, out Type))
|
||||
throw new YamlException($"Invalid MultiBrush Template `${my.Value}`");
|
||||
|
||||
FieldLoader.Load(this, my);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TileInfo
|
||||
{
|
||||
[FieldLoader.Ignore]
|
||||
public readonly TerrainTile Type;
|
||||
public readonly CVec Offset = new(0, 0);
|
||||
|
||||
public TileInfo(MiniYaml my)
|
||||
{
|
||||
if (string.IsNullOrEmpty(my.Value))
|
||||
throw new YamlException("Missing tile type");
|
||||
|
||||
if (!TerrainTile.TryParse(my.Value, out Type))
|
||||
throw new YamlException($"Invalid MultiBrush Tile `${my.Value}`");
|
||||
|
||||
FieldLoader.Load(this, my);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly int Weight;
|
||||
|
||||
public readonly ImmutableArray<ActorInfo> Actors;
|
||||
public readonly TerrainTile? BackingTile;
|
||||
public readonly ImmutableArray<TemplateInfo> Templates;
|
||||
public readonly ImmutableArray<TileInfo> Tiles;
|
||||
public readonly MultiBrushSegment Segment;
|
||||
|
||||
// Currently doesn't support specifying offsets. Add this capability if/when needed.
|
||||
public MultiBrushInfo(MiniYaml my)
|
||||
{
|
||||
Weight = MultiBrush.DefaultWeight;
|
||||
var actors = new List<string>();
|
||||
var templates = new List<ushort>();
|
||||
var tiles = new List<TerrainTile>();
|
||||
var actors = new List<ActorInfo>();
|
||||
var templates = new List<TemplateInfo>();
|
||||
var tiles = new List<TileInfo>();
|
||||
foreach (var node in my.Nodes)
|
||||
switch (node.Key.Split('@')[0])
|
||||
{
|
||||
@@ -45,7 +99,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
throw new YamlException($"Invalid MultiBrush Weight `${node.Value.Value}`");
|
||||
break;
|
||||
case "Actor":
|
||||
actors.Add(node.Value.Value);
|
||||
actors.Add(new ActorInfo(node.Value));
|
||||
break;
|
||||
case "BackingTile":
|
||||
if (TerrainTile.TryParse(node.Value.Value, out var backingTile))
|
||||
@@ -54,24 +108,23 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
throw new YamlException($"Invalid MultiBrush BackingTile `${node.Value.Value}`");
|
||||
break;
|
||||
case "Template":
|
||||
if (Exts.TryParseUshortInvariant(node.Value.Value, out var template))
|
||||
templates.Add(template);
|
||||
else
|
||||
throw new YamlException($"Invalid MultiBrush Template `${node.Value.Value}`");
|
||||
templates.Add(new TemplateInfo(node.Value));
|
||||
break;
|
||||
case "Tile":
|
||||
if (TerrainTile.TryParse(node.Value.Value, out var tile))
|
||||
Tiles.Add(tile);
|
||||
else
|
||||
throw new YamlException($"Invalid MultiBrush Tile `${node.Value.Value}`");
|
||||
tiles.Add(new TileInfo(node.Value));
|
||||
break;
|
||||
case "Segment":
|
||||
if (Segment != null)
|
||||
throw new YamlException("Multiple MultiBrush Segment definitions");
|
||||
Segment = new MultiBrushSegment(node.Value);
|
||||
break;
|
||||
default:
|
||||
throw new YamlException($"Unrecognized MultiBrush key {node.Key.Split('@')[0]}");
|
||||
}
|
||||
|
||||
Actors = actors.ToImmutableArray();
|
||||
Templates = templates.ToImmutableArray();
|
||||
Tiles = tiles.ToImmutableArray();
|
||||
Actors = [.. actors];
|
||||
Templates = [.. templates];
|
||||
Tiles = [.. tiles];
|
||||
}
|
||||
|
||||
public static ImmutableArray<MultiBrushInfo> ParseCollection(MiniYaml my)
|
||||
@@ -86,8 +139,67 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about how certain MultiBrushes (like cliffs, beaches, roads) link together.
|
||||
/// </summary>
|
||||
public sealed class MultiBrushSegment
|
||||
{
|
||||
public readonly string Start;
|
||||
public readonly string Inner;
|
||||
public readonly string End;
|
||||
|
||||
/// <summary>
|
||||
/// Point sequence, where points are -X-Y corners of template tiles.
|
||||
/// </summary>
|
||||
[FieldLoader.Ignore]
|
||||
public readonly ImmutableArray<CVec> Points;
|
||||
|
||||
/// <summary>
|
||||
/// Create a Segment from a point sequence and given start, inner, and end types.
|
||||
/// </summary>
|
||||
public MultiBrushSegment(string start, string inner, string end, ImmutableArray<CVec> points)
|
||||
{
|
||||
Start = start;
|
||||
Inner = inner;
|
||||
End = end;
|
||||
Points = points;
|
||||
}
|
||||
|
||||
public MultiBrushSegment(MiniYaml my)
|
||||
{
|
||||
FieldLoader.Load(this, my);
|
||||
{
|
||||
// Unlike FieldLoader.ParseInt2Array, whitespace is ignored.
|
||||
var value = my.NodeWithKey("Points").Value.Value;
|
||||
var parts = Regex.Replace(value, @"\s+", string.Empty)
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length % 2 != 0)
|
||||
FieldLoader.InvalidValueAction(value, typeof(int2[]), "Points");
|
||||
var points = new CVec[parts.Length / 2];
|
||||
for (var i = 0; i < points.Length; i++)
|
||||
points[i] = new CVec(Exts.ParseInt32Invariant(parts[2 * i]), Exts.ParseInt32Invariant(parts[2 * i + 1]));
|
||||
Points = [.. points];
|
||||
}
|
||||
}
|
||||
|
||||
public static bool MatchesType(string type, string matcher)
|
||||
{
|
||||
if (type == matcher)
|
||||
return true;
|
||||
|
||||
return type.StartsWith($"{matcher}.", StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
public bool HasStartType(string matcher)
|
||||
=> MatchesType(Start, matcher);
|
||||
public bool HasInnerType(string matcher)
|
||||
=> MatchesType(Inner, matcher);
|
||||
public bool HasEndType(string matcher)
|
||||
=> MatchesType(End, matcher);
|
||||
}
|
||||
|
||||
/// <summary>A super template that can be used to paint both tiles and actors.</summary>
|
||||
sealed class MultiBrush
|
||||
public sealed class MultiBrush
|
||||
{
|
||||
public const int DefaultWeight = 1000;
|
||||
|
||||
@@ -109,14 +221,24 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
public int Weight;
|
||||
readonly List<(CVec, TerrainTile)> tiles;
|
||||
readonly List<ActorPlan> actorPlans;
|
||||
public MultiBrushSegment Segment { get; private set; }
|
||||
|
||||
// A cache for the shape/footprint of the brush.
|
||||
// Null means the shape is dirty and must be recomputed.
|
||||
CVec[] shape;
|
||||
|
||||
public IEnumerable<(CVec XY, TerrainTile Tile)> Tiles => tiles;
|
||||
public IEnumerable<ActorPlan> ActorPlans => actorPlans;
|
||||
public bool HasTiles => tiles.Count != 0;
|
||||
public bool HasActors => actorPlans.Count != 0;
|
||||
public IEnumerable<CVec> Shape => shape;
|
||||
public int Area => shape.Length;
|
||||
public IEnumerable<CVec> Shape => GetShape();
|
||||
|
||||
/// <summary>Total area covered by the MultiBrush.</summary>
|
||||
public int Area => GetShape().Length;
|
||||
|
||||
/// <summary>The CVec of the top-left most cell covered by the MultiBrush.</summary>
|
||||
public CVec TopLeft => GetShape()[0];
|
||||
|
||||
public Replaceability Contract()
|
||||
{
|
||||
var hasTiles = tiles.Count != 0;
|
||||
@@ -139,29 +261,43 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
Weight = DefaultWeight;
|
||||
tiles = [];
|
||||
actorPlans = [];
|
||||
shape = [];
|
||||
Segment = null;
|
||||
shape = null;
|
||||
}
|
||||
|
||||
MultiBrush(MultiBrush other)
|
||||
{
|
||||
Weight = other.Weight;
|
||||
tiles = other.tiles.ToList();
|
||||
actorPlans = other.actorPlans.ToList();
|
||||
shape = other.shape.ToArray();
|
||||
tiles = [.. other.tiles];
|
||||
actorPlans = [.. other.actorPlans];
|
||||
Segment = null;
|
||||
shape = [.. other.shape];
|
||||
}
|
||||
|
||||
public MultiBrush(Map map, MultiBrushInfo info)
|
||||
: this()
|
||||
{
|
||||
WithWeight(info.Weight);
|
||||
foreach (var actor in info.Actors)
|
||||
WithActor(new ActorPlan(map, actor).AlignFootprint());
|
||||
foreach (var actorInfo in info.Actors)
|
||||
{
|
||||
var actor = new ActorPlan(map, actorInfo.Type)
|
||||
{
|
||||
WPosLocation = WPos.Zero + actorInfo.Offset
|
||||
};
|
||||
|
||||
WithActor(actor);
|
||||
}
|
||||
|
||||
if (info.BackingTile != null)
|
||||
WithBackingTile((TerrainTile)info.BackingTile);
|
||||
foreach (var template in info.Templates)
|
||||
WithTemplate(map, template);
|
||||
foreach (var tile in info.Tiles)
|
||||
WithTile(tile);
|
||||
|
||||
foreach (var templateInfo in info.Templates)
|
||||
WithTemplate(map, templateInfo.Type, templateInfo.Offset);
|
||||
|
||||
foreach (var tileInfo in info.Tiles)
|
||||
WithTile(tileInfo.Type, tileInfo.Offset);
|
||||
|
||||
ReplaceSegment(info.Segment);
|
||||
}
|
||||
|
||||
/// <summary>Load a named MultiBrush collection from a map's tileset.</summary>
|
||||
@@ -198,12 +334,20 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
shape = [new CVec(0, 0)];
|
||||
}
|
||||
|
||||
CVec[] GetShape()
|
||||
{
|
||||
if (shape == null)
|
||||
UpdateShape();
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add tiles from a template, optionally with a given offset. By
|
||||
/// default, it will be auto-offset such that the first tile is
|
||||
/// under (0, 0).
|
||||
/// </summary>
|
||||
public MultiBrush WithTemplate(Map map, ushort templateId, CVec? offset = null)
|
||||
public MultiBrush WithTemplate(Map map, ushort templateId, CVec offset)
|
||||
{
|
||||
var tileset = map.Rules.TerrainInfo as ITemplatedTerrainInfo;
|
||||
if (!tileset.Templates.TryGetValue(templateId, out var templateInfo))
|
||||
@@ -211,7 +355,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
return WithTemplate(templateInfo, offset);
|
||||
}
|
||||
|
||||
public MultiBrush WithTemplate(TerrainTemplateInfo templateInfo, CVec? offset = null)
|
||||
public MultiBrush WithTemplate(TerrainTemplateInfo templateInfo, CVec offset)
|
||||
{
|
||||
if (templateInfo.PickAny)
|
||||
throw new ArgumentException("PickAny not supported - create separate MultiBrushes using WithTile instead.");
|
||||
@@ -221,14 +365,12 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
var i = y * templateInfo.Size.X + x;
|
||||
if (templateInfo[i] != null)
|
||||
{
|
||||
if (offset == null)
|
||||
offset = new CVec(-x, -y);
|
||||
var tile = new TerrainTile(templateInfo.Id, (byte)i);
|
||||
tiles.Add((new CVec(x, y) + (CVec)offset, tile));
|
||||
tiles.Add((new CVec(x, y) + offset, tile));
|
||||
}
|
||||
}
|
||||
|
||||
UpdateShape();
|
||||
shape = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -236,10 +378,10 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// Add a single tile, optionally with a given offset. By default, it
|
||||
/// will be positioned under (0, 0).
|
||||
/// </summary>
|
||||
public MultiBrush WithTile(TerrainTile tile, CVec? offset = null)
|
||||
public MultiBrush WithTile(TerrainTile tile, CVec offset)
|
||||
{
|
||||
tiles.Add((offset ?? new CVec(0, 0), tile));
|
||||
UpdateShape();
|
||||
tiles.Add((offset, tile));
|
||||
shape = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -247,7 +389,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
public MultiBrush WithActor(ActorPlan actor)
|
||||
{
|
||||
actorPlans.Add(actor);
|
||||
UpdateShape();
|
||||
shape = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -265,6 +407,15 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Segment to this MultiBrush for later use with TilingPath.
|
||||
/// </summary>
|
||||
public MultiBrush ReplaceSegment(MultiBrushSegment segment)
|
||||
{
|
||||
Segment = segment;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Update the weight.</summary>
|
||||
public MultiBrush WithWeight(int weight)
|
||||
{
|
||||
@@ -274,6 +425,25 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add the tiles and actors from another MultiBrush into this one at a given offset.
|
||||
/// (Does not copy segments.)
|
||||
/// </summary>
|
||||
public void MergeFrom(MultiBrush other, CVec at, MapGridType mapGridType)
|
||||
{
|
||||
foreach (var original in other.actorPlans)
|
||||
{
|
||||
var actorPlan = original.Clone();
|
||||
actorPlan.WPosLocation += CellLayerUtils.CVecToWVec(at, mapGridType);
|
||||
actorPlans.Add(actorPlan);
|
||||
}
|
||||
|
||||
foreach (var (xy, tile) in other.tiles)
|
||||
tiles.Add((xy + at, tile));
|
||||
|
||||
shape = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>Paint tiles onto the map and/or add actors to actorPlans at the given location.</para>
|
||||
/// <para>contract specifies whether tiles or actors are allowed to be painted.</para>
|
||||
@@ -446,7 +616,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
foreach (var mpos in mposes)
|
||||
{
|
||||
var brush = brushes[random.PickWeighted(brushWeights)];
|
||||
var paintAt = mpos.ToCPos(map);
|
||||
var paintAt = mpos.ToCPos(map) - brush.TopLeft;
|
||||
var contract = ReserveShape(paintAt, brush.Shape, brush.Contract());
|
||||
if (contract != Replaceability.None)
|
||||
brush.Paint(map, actorPlans, paintAt, contract);
|
||||
|
||||
@@ -14,13 +14,12 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OpenRA.Mods.Common.Terrain;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Mods.Common.MapGenerator
|
||||
{
|
||||
/// <summary>Path to be tiled onto a map using TemplateSegments.</summary>
|
||||
/// <summary>Path to be tiled onto a map using MultiBrushSegments.</summary>
|
||||
public sealed class TilingPath
|
||||
{
|
||||
/// <summary>Describes the type and direction of the start or end of a TilingPath.</summary>
|
||||
@@ -35,8 +34,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
public int? Direction;
|
||||
|
||||
/// <summary>
|
||||
/// A string which can match the format used by
|
||||
/// OpenRA.Mods.Common.Terrain.TemplateSegment's Start or End.
|
||||
/// A string which can match the format used by MultiBrushSegment's Start or End.
|
||||
/// </summary>
|
||||
public readonly string SegmentType
|
||||
{
|
||||
@@ -56,34 +54,30 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the permitted start, middle, and end segments/templates that can be used to
|
||||
/// tile the path.
|
||||
/// Describes the permitted start, middle, and end segments/MultiBrushes that can be used
|
||||
/// to tile a path.
|
||||
/// </summary>
|
||||
public sealed class PermittedSegments
|
||||
{
|
||||
public readonly ITemplatedTerrainInfo TemplatedTerrainInfo;
|
||||
public readonly ImmutableArray<TemplateSegment> Start;
|
||||
public readonly ImmutableArray<TemplateSegment> Inner;
|
||||
public readonly ImmutableArray<TemplateSegment> End;
|
||||
public IEnumerable<TemplateSegment> All => Start.Union(Inner).Union(End);
|
||||
public readonly ImmutableArray<MultiBrush> Start;
|
||||
public readonly ImmutableArray<MultiBrush> Inner;
|
||||
public readonly ImmutableArray<MultiBrush> End;
|
||||
public IEnumerable<MultiBrush> All => Start.Union(Inner).Union(End);
|
||||
|
||||
public PermittedSegments(
|
||||
ITemplatedTerrainInfo templatedTerrainInfo,
|
||||
IEnumerable<TemplateSegment> start,
|
||||
IEnumerable<TemplateSegment> inner,
|
||||
IEnumerable<TemplateSegment> end)
|
||||
IEnumerable<MultiBrush> start,
|
||||
IEnumerable<MultiBrush> inner,
|
||||
IEnumerable<MultiBrush> end)
|
||||
{
|
||||
TemplatedTerrainInfo = templatedTerrainInfo;
|
||||
Start = start.ToImmutableArray();
|
||||
Inner = inner.ToImmutableArray();
|
||||
End = end.ToImmutableArray();
|
||||
}
|
||||
|
||||
public PermittedSegments(
|
||||
ITemplatedTerrainInfo templatedTerrainInfo,
|
||||
IEnumerable<TemplateSegment> all)
|
||||
IReadOnlyList<MultiBrush> multiBrushes,
|
||||
IEnumerable<MultiBrush> all)
|
||||
{
|
||||
TemplatedTerrainInfo = templatedTerrainInfo;
|
||||
var array = all.ToImmutableArray();
|
||||
Start = array;
|
||||
Inner = array;
|
||||
@@ -94,87 +88,60 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// Creates a PermittedSegments using only the given types.
|
||||
/// </summary>
|
||||
public static PermittedSegments FromType(
|
||||
ITemplatedTerrainInfo templatedTerrainInfo,
|
||||
IReadOnlyList<MultiBrush> multiBrushes,
|
||||
IEnumerable<string> types)
|
||||
=> new(templatedTerrainInfo, FindSegments(templatedTerrainInfo, types));
|
||||
=> new(multiBrushes, FindSegments(multiBrushes, types));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a PermittedSegments suitable for a path with given inner and terminal types
|
||||
/// at the start and end.
|
||||
/// </summary>
|
||||
public static PermittedSegments FromInnerAndTerminalTypes(
|
||||
ITemplatedTerrainInfo templatedTerrainInfo,
|
||||
IReadOnlyList<MultiBrush> multiBrushes,
|
||||
IEnumerable<string> innerTypes,
|
||||
IEnumerable<string> terminalTypes)
|
||||
{
|
||||
var innerTypesArray = innerTypes.ToImmutableArray();
|
||||
var terminalTypesArray = terminalTypes.ToImmutableArray();
|
||||
return new(
|
||||
templatedTerrainInfo,
|
||||
FindSegments(templatedTerrainInfo, terminalTypesArray, innerTypesArray, innerTypesArray),
|
||||
FindSegments(templatedTerrainInfo, innerTypesArray),
|
||||
FindSegments(templatedTerrainInfo, innerTypesArray, innerTypesArray, terminalTypesArray));
|
||||
FindSegments(multiBrushes, terminalTypesArray, innerTypesArray, innerTypesArray),
|
||||
FindSegments(multiBrushes, innerTypesArray),
|
||||
FindSegments(multiBrushes, innerTypesArray, innerTypesArray, terminalTypesArray));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Equivalent to FindSegments(templatedTerrainInfo, types, types, types).
|
||||
/// Equivalent to FindSegments(multiBrushes, types, types, types).
|
||||
/// </summary>
|
||||
public static IEnumerable<TemplateSegment> FindSegments(
|
||||
ITemplatedTerrainInfo templatedTerrainInfo,
|
||||
public static IEnumerable<MultiBrush> FindSegments(
|
||||
IReadOnlyList<MultiBrush> multiBrushes,
|
||||
IEnumerable<string> types)
|
||||
{
|
||||
var array = types.ToImmutableArray();
|
||||
return FindSegments(templatedTerrainInfo, array, array, array);
|
||||
return FindSegments(multiBrushes, array, array, array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find templates that use some combination of the given start, inner, and end types.
|
||||
/// Filter MultiBrushes to segments that use the given start, inner, and end types.
|
||||
/// </summary>
|
||||
public static IEnumerable<TemplateSegment> FindSegments(
|
||||
ITemplatedTerrainInfo templatedTerrainInfo,
|
||||
public static IEnumerable<MultiBrush> FindSegments(
|
||||
IReadOnlyList<MultiBrush> multiBrushes,
|
||||
IEnumerable<string> startTypes,
|
||||
IEnumerable<string> innerTypes,
|
||||
IEnumerable<string> endTypes)
|
||||
{
|
||||
var templateSegments = new List<TemplateSegment>();
|
||||
foreach (var templateInfo in templatedTerrainInfo.Templates.Values.OrderBy(tti => tti.Id))
|
||||
foreach (var segment in templateInfo.Segments)
|
||||
var filtered = new List<MultiBrush>();
|
||||
var startTypesArray = startTypes.ToImmutableArray();
|
||||
var innerTypesArray = innerTypes.ToImmutableArray();
|
||||
var endTypesArray = endTypes.ToImmutableArray();
|
||||
foreach (var multiBrush in multiBrushes)
|
||||
if (startTypesArray.Any(multiBrush.Segment.HasStartType) &&
|
||||
innerTypesArray.Any(multiBrush.Segment.HasInnerType) &&
|
||||
endTypesArray.Any(multiBrush.Segment.HasEndType))
|
||||
{
|
||||
if (startTypes.Any(segment.HasStartType) &&
|
||||
innerTypes.Any(segment.HasInnerType) &&
|
||||
endTypes.Any(segment.HasEndType))
|
||||
{
|
||||
templateSegments.Add(segment);
|
||||
}
|
||||
filtered.Add(multiBrush);
|
||||
}
|
||||
|
||||
return templateSegments.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all possible templates that could be layed, ordered by template id.
|
||||
/// </summary>
|
||||
public IEnumerable<TerrainTemplateInfo> PossibleTemplates()
|
||||
{
|
||||
var templates = new List<TerrainTemplateInfo>();
|
||||
var segments = Start.Union(Inner).Union(End).ToHashSet();
|
||||
foreach (var template in TemplatedTerrainInfo.Templates.Values.OrderBy(tti => tti.Id))
|
||||
if (template.Segments.Any(segments.Contains))
|
||||
templates.Add(template);
|
||||
return templates;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all possible tiles that could be layed, ordered by template id, tile index.
|
||||
/// </summary>
|
||||
public IEnumerable<TerrainTile> PossibleTiles()
|
||||
{
|
||||
var tiles = new List<TerrainTile>();
|
||||
foreach (var template in PossibleTemplates())
|
||||
for (var index = 0; index < template.TilesCount; index++)
|
||||
if (template[index] != null)
|
||||
tiles.Add(new TerrainTile(template.Id, (byte)index));
|
||||
return tiles;
|
||||
return [.. filtered];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +149,9 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Target point sequence to fit TemplateSegments to. Whether these CPos positions
|
||||
/// Target point sequence to fit MultiBrushSegments to. Whether these CPos positions
|
||||
/// represent cell corners or cell centers is dependent on the system used by the path's
|
||||
/// PermittedSegments' TemplateSegments.
|
||||
/// PermittedSegments' MultiBrushSegments.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If null, Tiling will be a no-op. If non-null, must have at least two points.
|
||||
@@ -196,7 +163,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
public CPos[] Points;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum permitted Chebyshev distance that layed TemplateSegments may be from the
|
||||
/// Maximum permitted Chebyshev distance that layed MultiBrushSegments may be from the
|
||||
/// specified points.
|
||||
/// </summary>
|
||||
public int MaxDeviation;
|
||||
@@ -228,7 +195,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// Stores end type and direction.
|
||||
/// </summary>
|
||||
public Terminal End;
|
||||
public PermittedSegments Segments;
|
||||
public PermittedSegments Brushes;
|
||||
|
||||
/// <summary>Whether the start and end points are the same.</summary>
|
||||
public bool IsLoop
|
||||
@@ -252,13 +219,12 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
MaxEndDeviation = 0;
|
||||
Start = new Terminal(startType, null);
|
||||
End = new Terminal(endType, null);
|
||||
Segments = permittedTemplates;
|
||||
Brushes = permittedTemplates;
|
||||
}
|
||||
|
||||
sealed class TilingSegment
|
||||
{
|
||||
public readonly TerrainTemplateInfo TemplateInfo;
|
||||
public readonly TemplateSegment TemplateSegment;
|
||||
public readonly MultiBrush MultiBrush;
|
||||
public readonly int StartTypeId;
|
||||
public readonly int EndTypeId;
|
||||
public readonly CVec Offset;
|
||||
@@ -268,16 +234,15 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
public readonly int[] DirectionMasks;
|
||||
public readonly int[] ReverseDirectionMasks;
|
||||
|
||||
public TilingSegment(TerrainTemplateInfo templateInfo, TemplateSegment templateSegment, int startId, int endId)
|
||||
public TilingSegment(MultiBrush multiBrush, int startId, int endId)
|
||||
{
|
||||
TemplateInfo = templateInfo;
|
||||
TemplateSegment = templateSegment;
|
||||
MultiBrush = multiBrush;
|
||||
StartTypeId = startId;
|
||||
EndTypeId = endId;
|
||||
Offset = templateSegment.Points[0];
|
||||
Moves = templateSegment.Points[^1] - Offset;
|
||||
RelativePoints = templateSegment.Points
|
||||
.Select(p => p - templateSegment.Points[0])
|
||||
Offset = multiBrush.Segment.Points[0];
|
||||
Moves = multiBrush.Segment.Points[^1] - Offset;
|
||||
RelativePoints = multiBrush.Segment.Points
|
||||
.Select(p => p - multiBrush.Segment.Points[0])
|
||||
.ToArray();
|
||||
|
||||
Directions = new int[RelativePoints.Length];
|
||||
@@ -292,7 +257,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
{
|
||||
var direction = Direction.FromCVec(RelativePoints[i + 1] - RelativePoints[i]);
|
||||
if (direction == Direction.None)
|
||||
throw new ArgumentException("TemplateSegment has duplicate points in sequence");
|
||||
throw new ArgumentException("MultiBrushSegment has duplicate points in sequence");
|
||||
Directions[i] = direction;
|
||||
DirectionMasks[i] = 1 << direction;
|
||||
ReverseDirectionMasks[i] = 1 << Direction.Reverse(direction);
|
||||
@@ -302,26 +267,28 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Attempt to tile the given path onto a map.
|
||||
/// Attempt to tile the given path, producing a new MultiBrush if the path could be tiled,
|
||||
/// or null if the path could not be tiled within constraints.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the path could be tiled, returns the sequence of points actually traversed by the
|
||||
/// chosen TemplateSegments. Returns null if the path could not be tiled within constraints.
|
||||
/// The resulting MultiBrush is created from stitching MultiBrushes from the
|
||||
/// PermittedSegments together, and will contain a segment that represents the stitched
|
||||
/// segments of the constituent MultiBrushes.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public CPos[] Tile(MersenneTwister random)
|
||||
public MultiBrush Tile(MersenneTwister random)
|
||||
{
|
||||
// This is essentially a Dijkstra's algorithm best-first search.
|
||||
//
|
||||
// The search is performed over a 3-dimensional space: (x, y, connection type).
|
||||
// Connection types correspond to the .Start or .End values of TemplateSegments.
|
||||
// Connection types correspond to the .Start or .End values of MultiBrushSegments.
|
||||
//
|
||||
// The best found costs of the nodes in this space are stored as an array of matrices.
|
||||
// There is a matrix for each possible connection type, and each matrix stores the
|
||||
// (current) best costs at the (x, y) locations for that given connection type.
|
||||
//
|
||||
// The directed edges between the nodes of this 3-dimensional space are defined by the
|
||||
// TemplateSegments within the permitted set of templates. For example, a segment
|
||||
// MultiBrushSegments within the permitted set of segments. For example, a segment
|
||||
// defined as
|
||||
//
|
||||
// Segment:
|
||||
@@ -333,7 +300,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
// "Beach.D" matrix. (The overall point displacement is (2,3) - (3,1) = (-1, +2))
|
||||
//
|
||||
// The cost of a transition/link/edge between nodes is defined by how well the
|
||||
// template segment fits the path (how little "deviation" is accumulates). However, in
|
||||
// MultiBrushSegment fits the path (how little "deviation" is accumulates). However, in
|
||||
// order for a transition to be allowed at all, it must satisfy some constraints:
|
||||
//
|
||||
// - It must not regress backward along the path (but no immediate progress is OK).
|
||||
@@ -348,16 +315,16 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
// If the original target end node is unreachable (at MaxCost), a nearby node may be
|
||||
// selected as a fallback end point, provided the target path isn't a loop.
|
||||
//
|
||||
// Then, from the end node, it works backwards. It finds any (random) suitable template
|
||||
// Then, from the end node, it works backwards. It finds any (random) suitable
|
||||
// segment which connects back to a previous node where the difference in cost is
|
||||
// that of the template segment's cost, implying that the previous node is on an
|
||||
// that of the segment's cost, implying that the previous node is on an
|
||||
// optimal path towards the end node. This process repeats until the start node is
|
||||
// reached, painting templates along the way.
|
||||
// reached, merging MultiBrushes into a result along the way.
|
||||
//
|
||||
// Note that this algorithm makes a few (reasonable) assumptions about the shapes of
|
||||
// templates, such as that they don't individually snake around too much. The actual
|
||||
// tiles of a template are ignored during the search, with only the segment being used
|
||||
// to calculate transition cost and validity.
|
||||
// MultiBrushes, such as that they don't individually snake around too much. The actual
|
||||
// tiles of a MultiBrush are ignored during the search, with only the segment being
|
||||
// used to calculate transition cost and validity.
|
||||
if (Points == null)
|
||||
return null;
|
||||
|
||||
@@ -572,11 +539,11 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
var pathStart = points[0];
|
||||
var pathEnd = points[^1];
|
||||
var orderedPermittedSegments = Segments.All.ToImmutableArray();
|
||||
var permittedSegments = orderedPermittedSegments.ToImmutableHashSet();
|
||||
var permittedStartSegments = Segments.Start.ToImmutableHashSet();
|
||||
var permittedInnerSegments = Segments.Inner.ToImmutableHashSet();
|
||||
var permittedEndSegments = Segments.End.ToImmutableHashSet();
|
||||
var orderedPermittedBrushes = Brushes.All.ToImmutableArray();
|
||||
var permittedBrushes = orderedPermittedBrushes.ToImmutableHashSet();
|
||||
var permittedStartBrushes = Brushes.Start.ToImmutableHashSet();
|
||||
var permittedInnerBrushes = Brushes.Inner.ToImmutableHashSet();
|
||||
var permittedEndBrushes = Brushes.End.ToImmutableHashSet();
|
||||
|
||||
const int MaxCost = int.MaxValue;
|
||||
var segmentTypeToId = new Dictionary<string, int>();
|
||||
@@ -605,19 +572,19 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
innerCosts.Add(new Matrix<int>(size).Fill(MaxCost));
|
||||
}
|
||||
|
||||
foreach (var segment in orderedPermittedSegments)
|
||||
foreach (var multiBrush in orderedPermittedBrushes)
|
||||
{
|
||||
var template = Segments.TemplatedTerrainInfo.SegmentsToTemplates[segment];
|
||||
var segment = multiBrush.Segment;
|
||||
RegisterSegmentType(segment.Start);
|
||||
RegisterSegmentType(segment.End);
|
||||
var startTypeId = segmentTypeToId[segment.Start];
|
||||
var endTypeId = segmentTypeToId[segment.End];
|
||||
var tilePathSegment = new TilingSegment(template, segment, startTypeId, endTypeId);
|
||||
var tilePathSegment = new TilingSegment(multiBrush, startTypeId, endTypeId);
|
||||
var tuple = (
|
||||
tilePathSegment,
|
||||
permittedStartSegments.Contains(segment) && segment.Start == start.SegmentType,
|
||||
permittedInnerSegments.Contains(segment),
|
||||
permittedEndSegments.Contains(segment) && segment.End == end.SegmentType);
|
||||
permittedStartBrushes.Contains(multiBrush) && segment.Start == start.SegmentType,
|
||||
permittedInnerBrushes.Contains(multiBrush),
|
||||
permittedEndBrushes.Contains(multiBrush) && segment.End == end.SegmentType);
|
||||
segmentsByStart[startTypeId].Add(tuple);
|
||||
segmentsByEnd[endTypeId].Add(tuple);
|
||||
}
|
||||
@@ -765,7 +732,9 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
}
|
||||
|
||||
// Trace back and update tiles
|
||||
var resultPoints = new List<CPos>();
|
||||
var resultPoints = new List<CVec>();
|
||||
|
||||
var compositeBrush = new MultiBrush();
|
||||
|
||||
(CVec From, int FromTypeId) TraceBackStep(CVec to, int toTypeId, bool isForEnd)
|
||||
{
|
||||
@@ -815,12 +784,15 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
Debug.Assert(candidates.Count >= 1, "TraceBack didn't find an original route");
|
||||
var chosenSegment = candidates[random.Next(candidates.Count)];
|
||||
var chosenFrom = to - chosenSegment.Moves;
|
||||
PaintTemplate(Map, chosenFrom - chosenSegment.Offset + minPoint, chosenSegment.TemplateInfo);
|
||||
compositeBrush.MergeFrom(
|
||||
chosenSegment.MultiBrush,
|
||||
chosenFrom - chosenSegment.Offset + minPoint - CPos.Zero,
|
||||
Map.Grid.Type);
|
||||
|
||||
// Skip end point as it is recorded in the previous template.
|
||||
// Skip end point as it is recorded in the previous segment.
|
||||
for (var i = chosenSegment.RelativePoints.Length - 2; i >= 0; i--)
|
||||
{
|
||||
var point = chosenFrom + chosenSegment.RelativePoints[i] + minPoint;
|
||||
var point = chosenFrom + chosenSegment.RelativePoints[i] + minPoint - CPos.Zero;
|
||||
resultPoints.Add(point);
|
||||
}
|
||||
|
||||
@@ -906,24 +878,15 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
// Traced back in reverse, so reverse the reversal.
|
||||
resultPoints.Reverse();
|
||||
return resultPoints.ToArray();
|
||||
}
|
||||
var compositeSegment = new MultiBrushSegment(
|
||||
start.SegmentType,
|
||||
"(Tiled Path)",
|
||||
end.SegmentType,
|
||||
[.. resultPoints]);
|
||||
|
||||
static void PaintTemplate(Map map, CPos at, TerrainTemplateInfo template)
|
||||
{
|
||||
if (template.PickAny)
|
||||
throw new ArgumentException("PaintTemplate does not expect PickAny");
|
||||
for (var y = 0; y < template.Size.Y; y++)
|
||||
for (var x = 0; x < template.Size.X; x++)
|
||||
{
|
||||
var i = (byte)(y * template.Size.X + x);
|
||||
if (template[i] == null)
|
||||
continue;
|
||||
var tile = new TerrainTile(template.Id, i);
|
||||
var mpos = new CPos(at.X + x, at.Y + y).ToMPos(map);
|
||||
if (map.Tiles.Contains(mpos))
|
||||
map.Tiles[mpos] = tile;
|
||||
}
|
||||
compositeBrush.ReplaceSegment(compositeSegment);
|
||||
|
||||
return compositeBrush;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -83,8 +83,6 @@ namespace OpenRA.Mods.Common.Terrain
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyDictionary<ushort, TerrainTemplateInfo> Templates;
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyDictionary<TemplateSegment, TerrainTemplateInfo> SegmentsToTemplates;
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyDictionary<string, IEnumerable<MultiBrushInfo>> MultiBrushCollections;
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
@@ -125,11 +123,6 @@ namespace OpenRA.Mods.Common.Terrain
|
||||
Templates = yaml["Templates"].ToDictionary().Values
|
||||
.Select(y => (TerrainTemplateInfo)new DefaultTerrainTemplateInfo(this, y)).ToDictionary(t => t.Id);
|
||||
|
||||
SegmentsToTemplates = ImmutableDictionary.CreateRange(
|
||||
Templates.Values.SelectMany(
|
||||
template => template.Segments.Select(
|
||||
segment => new KeyValuePair<TemplateSegment, TerrainTemplateInfo>(segment, template))));
|
||||
|
||||
MultiBrushCollections =
|
||||
yaml.TryGetValue("MultiBrushCollections", out var collectionDefinitions)
|
||||
? collectionDefinitions.ToDictionary()
|
||||
@@ -189,7 +182,6 @@ namespace OpenRA.Mods.Common.Terrain
|
||||
|
||||
string[] ITemplatedTerrainInfo.EditorTemplateOrder => EditorTemplateOrder;
|
||||
IReadOnlyDictionary<ushort, TerrainTemplateInfo> ITemplatedTerrainInfo.Templates => Templates;
|
||||
IReadOnlyDictionary<TemplateSegment, TerrainTemplateInfo> ITemplatedTerrainInfo.SegmentsToTemplates => SegmentsToTemplates;
|
||||
IReadOnlyDictionary<string, IEnumerable<MultiBrushInfo>> ITemplatedTerrainInfo.MultiBrushCollections => MultiBrushCollections;
|
||||
|
||||
void ITerrainInfoNotifyMapCreated.MapCreated(Map map)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#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.Text.RegularExpressions;
|
||||
|
||||
namespace OpenRA.Mods.Common.Terrain
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about how certain templates (like cliffs, beaches, roads) link together.
|
||||
/// </summary>
|
||||
public class TemplateSegment
|
||||
{
|
||||
public readonly string Start;
|
||||
public readonly string Inner;
|
||||
public readonly string End;
|
||||
|
||||
/// <summary>
|
||||
/// Point sequence, where points are -X-Y corners of template tiles.
|
||||
/// </summary>
|
||||
[FieldLoader.Ignore]
|
||||
public readonly CVec[] Points;
|
||||
|
||||
public TemplateSegment(MiniYaml my)
|
||||
{
|
||||
FieldLoader.Load(this, my);
|
||||
{
|
||||
// Unlike FieldLoader.ParseInt2Array, whitespace is ignored.
|
||||
var value = my.NodeWithKey("Points").Value.Value;
|
||||
var parts = Regex.Replace(value, @"\s+", string.Empty)
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length % 2 != 0)
|
||||
FieldLoader.InvalidValueAction(value, typeof(int2[]), "Points");
|
||||
Points = new CVec[parts.Length / 2];
|
||||
for (var i = 0; i < Points.Length; i++)
|
||||
Points[i] = new CVec(Exts.ParseInt32Invariant(parts[2 * i]), Exts.ParseInt32Invariant(parts[2 * i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool MatchesType(string type, string matcher)
|
||||
{
|
||||
if (type == matcher)
|
||||
return true;
|
||||
|
||||
return type.StartsWith($"{matcher}.", StringComparison.InvariantCulture);
|
||||
}
|
||||
|
||||
public bool HasStartType(string matcher)
|
||||
=> MatchesType(Start, matcher);
|
||||
public bool HasInnerType(string matcher)
|
||||
=> MatchesType(Inner, matcher);
|
||||
public bool HasEndType(string matcher)
|
||||
=> MatchesType(End, matcher);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ namespace OpenRA.Mods.Common.Terrain
|
||||
{
|
||||
string[] EditorTemplateOrder { get; }
|
||||
IReadOnlyDictionary<ushort, TerrainTemplateInfo> Templates { get; }
|
||||
IReadOnlyDictionary<TemplateSegment, TerrainTemplateInfo> SegmentsToTemplates { get; }
|
||||
IReadOnlyDictionary<string, IEnumerable<MultiBrushInfo>> MultiBrushCollections { get; }
|
||||
}
|
||||
|
||||
@@ -36,8 +35,6 @@ namespace OpenRA.Mods.Common.Terrain
|
||||
|
||||
readonly TerrainTileInfo[] tileInfo;
|
||||
|
||||
public readonly TemplateSegment[] Segments;
|
||||
|
||||
public TerrainTemplateInfo(ITerrainInfo terrainInfo, MiniYaml my)
|
||||
{
|
||||
FieldLoader.Load(this, my);
|
||||
@@ -77,19 +74,6 @@ namespace OpenRA.Mods.Common.Terrain
|
||||
tileInfo[key] = LoadTileInfo(terrainInfo, node.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var segmentsNode = my.NodeWithKeyOrDefault("Segments");
|
||||
if (segmentsNode != null)
|
||||
{
|
||||
Segments = new TemplateSegment[segmentsNode.Value.Nodes.Length];
|
||||
var i = 0;
|
||||
foreach (var segmentNode in segmentsNode.Value.Nodes)
|
||||
Segments[i++] = new TemplateSegment(segmentNode.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Segments = [];
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual TerrainTileInfo LoadTileInfo(ITerrainInfo terrainInfo, MiniYaml my)
|
||||
|
||||
@@ -180,6 +180,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly ushort WaterTile = default;
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyList<MultiBrush> SegmentedBrushes;
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyList<MultiBrush> ForestObstacles;
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyList<MultiBrush> UnplayableObstacles;
|
||||
@@ -229,6 +231,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
FieldLoader.Load(this, my);
|
||||
|
||||
var terrainInfo = (ITemplatedTerrainInfo)map.Rules.TerrainInfo;
|
||||
SegmentedBrushes = MultiBrush.LoadCollection(map, "Segmented");
|
||||
ForestObstacles = MultiBrush.LoadCollection(map, my.NodeWithKey("ForestObstacles").Value.Value);
|
||||
UnplayableObstacles = MultiBrush.LoadCollection(map, my.NodeWithKey("UnplayableObstacles").Value.Value);
|
||||
CivilianBuildingsObstacles = MultiBrush.LoadCollection(map, my.NodeWithKey("CivilianBuildingsObstacles").Value.Value);
|
||||
@@ -505,8 +508,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
var tileset = (ITemplatedTerrainInfo)map.Rules.TerrainInfo;
|
||||
var beachPermittedTemplates =
|
||||
TilingPath.PermittedSegments.FromType(tileset, param.BeachSegmentTypes);
|
||||
var beachTiles = beachPermittedTemplates.PossibleTiles().ToImmutableHashSet();
|
||||
TilingPath.PermittedSegments.FromType(param.SegmentedBrushes, param.BeachSegmentTypes);
|
||||
|
||||
var replaceabilityMap = new Dictionary<TerrainTile, MultiBrush.Replaceability>();
|
||||
var playabilityMap = new Dictionary<TerrainTile, PlayableSpace.Playability>();
|
||||
@@ -636,6 +638,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var beaches = CellLayerUtils.FromMatrixPoints(
|
||||
MatrixUtils.BordersToPoints(landPlan),
|
||||
map.Tiles);
|
||||
var beachesShape = new HashSet<CPos>();
|
||||
if (beaches.Length > 0)
|
||||
{
|
||||
var tiledBeaches = new CPos[beaches.Length][];
|
||||
@@ -652,9 +655,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
.ExtendEdge(4)
|
||||
.SetAutoEndDeviation()
|
||||
.OptimizeLoop();
|
||||
tiledBeaches[i] =
|
||||
beachPath.Tile(beachTilingRandom)
|
||||
?? throw new MapGenerationException("Could not fit tiles for beach");
|
||||
var brush = beachPath.Tile(beachTilingRandom)
|
||||
?? throw new MapGenerationException("Could not fit tiles for beach");
|
||||
brush.Paint(map, actorPlans, CPos.Zero, MultiBrush.Replaceability.Tile);
|
||||
tiledBeaches[i] = brush.Segment.Points.Select(vec => CPos.Zero + vec).ToArray();
|
||||
foreach (var cvec in brush.Shape)
|
||||
beachesShape.Add(CPos.Zero + cvec);
|
||||
}
|
||||
|
||||
var beachChiralityMatrix = MatrixUtils.PointsChirality(
|
||||
@@ -665,7 +671,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
foreach (var mpos in map.AllCells.MapCoords)
|
||||
{
|
||||
// `map.Tiles[mpos].Type == param.LandTile` avoids overwriting beach tiles.
|
||||
if (beachChirality[mpos] < 0 && map.Tiles[mpos].Type == param.LandTile)
|
||||
if (beachChirality[mpos] < 0 && !beachesShape.Contains(mpos.ToCPos(map)))
|
||||
map.Tiles[mpos] = PickTile(param.WaterTile);
|
||||
}
|
||||
}
|
||||
@@ -682,10 +688,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
var nonLoopedCliffPermittedTemplates =
|
||||
TilingPath.PermittedSegments.FromInnerAndTerminalTypes(
|
||||
tileset, param.CliffSegmentTypes, param.ClearSegmentTypes);
|
||||
param.SegmentedBrushes, param.CliffSegmentTypes, param.ClearSegmentTypes);
|
||||
var loopedCliffPermittedTemplates =
|
||||
TilingPath.PermittedSegments.FromType(
|
||||
tileset, param.CliffSegmentTypes);
|
||||
param.SegmentedBrushes, param.CliffSegmentTypes);
|
||||
if (param.ExternalCircularBias > 0)
|
||||
{
|
||||
var cliffRing = new CellLayer<bool>(map);
|
||||
@@ -719,8 +725,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
.ExtendEdge(4)
|
||||
.SetAutoEndDeviation()
|
||||
.OptimizeLoop();
|
||||
if (cliffPath.Tile(cliffTilingRandom) == null)
|
||||
throw new MapGenerationException("Could not fit tiles for exterior circle cliffs");
|
||||
var brush = cliffPath.Tile(cliffTilingRandom)
|
||||
?? throw new MapGenerationException("Could not fit tiles for exterior circle cliffs");
|
||||
brush.Paint(map, actorPlans, CPos.Zero, MultiBrush.Replaceability.Tile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -800,8 +807,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
.ExtendEdge(4)
|
||||
.SetAutoEndDeviation()
|
||||
.OptimizeLoop();
|
||||
if (cliffPath.Tile(cliffTilingRandom) == null)
|
||||
throw new MapGenerationException("Could not fit tiles for cliffs");
|
||||
var brush = cliffPath.Tile(cliffTilingRandom)
|
||||
?? throw new MapGenerationException("Could not fit tiles for cliffs");
|
||||
brush.Paint(map, actorPlans, CPos.Zero, MultiBrush.Replaceability.Tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1048,8 +1056,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
var mpos = cpos.ToMPos(gridType);
|
||||
var propagate =
|
||||
map.Tiles[mpos].Type == param.WaterTile ||
|
||||
beachTiles.Contains(map.Tiles[mpos]);
|
||||
beachesShape.Remove(cpos) ||
|
||||
map.Tiles[mpos].Type == param.WaterTile;
|
||||
map.Tiles[mpos] = PickTile(param.LandTile);
|
||||
regionMask[mpos] = PlayableSpace.NullRegion;
|
||||
return propagate ? false : null;
|
||||
@@ -1132,10 +1140,10 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
var nonLoopedRoadPermittedTemplates =
|
||||
TilingPath.PermittedSegments.FromInnerAndTerminalTypes(
|
||||
tileset, param.RoadSegmentTypes, param.ClearSegmentTypes);
|
||||
param.SegmentedBrushes, param.RoadSegmentTypes, param.ClearSegmentTypes);
|
||||
var loopedRoadPermittedTemplates =
|
||||
TilingPath.PermittedSegments.FromType(
|
||||
tileset, param.RoadSegmentTypes);
|
||||
param.SegmentedBrushes, param.RoadSegmentTypes);
|
||||
|
||||
foreach (var pointArray in pointArrays)
|
||||
{
|
||||
@@ -1179,8 +1187,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
if (maxX - minX < 6 || maxY - minY < 6)
|
||||
continue;
|
||||
|
||||
if (path.Tile(roadTilingRandom) == null)
|
||||
throw new MapGenerationException("Could not fit tiles for roads");
|
||||
var brush = path.Tile(roadTilingRandom)
|
||||
?? throw new MapGenerationException("Could not fit tiles for roads");
|
||||
brush.Paint(map, actorPlans, CPos.Zero, MultiBrush.Replaceability.Tile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user