diff --git a/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs b/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs
index b95e1a1479..14e87e99f2 100644
--- a/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs
+++ b/OpenRA.Mods.Common/MapGenerator/MatrixUtils.cs
@@ -15,6 +15,7 @@ using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using OpenRA.Primitives;
+using OpenRA.Support;
namespace OpenRA.Mods.Common.MapGenerator
{
@@ -1502,5 +1503,34 @@ namespace OpenRA.Mods.Common.MapGenerator
return matrix;
}
+
+ ///
+ /// Rank all cell values and select the best (greatest compared) value.
+ /// If there are equally good best candidates, choose one at random.
+ ///
+ public static (int2 MPos, T Value) FindRandomBest(
+ Matrix matrix,
+ MersenneTwister random,
+ Comparison comparison)
+ {
+ var candidates = new List();
+ var best = matrix[new int2(0, 0)];
+ for (var y = 0; y < matrix.Size.Y; y++)
+ for (var x = 0; x < matrix.Size.X; x++)
+ {
+ var rank = comparison(matrix[x, y], best);
+ if (rank > 0)
+ {
+ best = matrix[x, y];
+ candidates.Clear();
+ }
+
+ if (rank >= 0)
+ candidates.Add(new int2(x, y));
+ }
+
+ var choice = candidates[random.Next(candidates.Count)];
+ return (choice, best);
+ }
}
}
diff --git a/OpenRA.Mods.Common/MapGenerator/TilingPath.cs b/OpenRA.Mods.Common/MapGenerator/TilingPath.cs
index a1b4059601..671b8b4ccf 100644
--- a/OpenRA.Mods.Common/MapGenerator/TilingPath.cs
+++ b/OpenRA.Mods.Common/MapGenerator/TilingPath.cs
@@ -212,6 +212,13 @@ namespace OpenRA.Mods.Common.MapGenerator
///
public int MinSeparation;
+ ///
+ /// If the path cannot be tiled exactly, the resulting tiling is allowed to deviate from
+ /// target end point by this Chebychev distance. Ignored for loops. This will be capped to
+ /// MaxDeviation at tiling time.
+ ///
+ public int MaxEndDeviation;
+
///
/// Stores start type and direction.
///
@@ -242,6 +249,7 @@ namespace OpenRA.Mods.Common.MapGenerator
MaxDeviation = maxDeviation;
MaxSkip = 0;
MinSeparation = 0;
+ MaxEndDeviation = 0;
Start = new Terminal(startType, null);
End = new Terminal(endType, null);
Segments = permittedTemplates;
@@ -337,6 +345,9 @@ namespace OpenRA.Mods.Common.MapGenerator
// The search is conducted from the path start node until the best possible cost of
// the end node is confirmed. This also populates possible intermediate nodes' costs.
//
+ // 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
// 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
@@ -563,12 +574,25 @@ namespace OpenRA.Mods.Common.MapGenerator
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();
const int MaxCost = int.MaxValue;
var segmentTypeToId = new Dictionary();
- var segmentsByStart = new List>();
- var segmentsByEnd = new List>();
- var costs = new List>();
+ var segmentsByStart = new List>();
+ var segmentsByEnd = new List>();
+
+ // We store the end costs of valid end segments separately to inner costs.
+ //
+ // Note also that:
+ // - The start cost is always zero and only applies to a single node.
+ // - Permitted end and inner segments may be distinct, but the end terminal could exist
+ // in the permitted inner segments and shouldn't be a valid intermediate cost.
+ // - Avoids confusing start, inner, and end costs when processing looped paths.
+ // - We may be interested in multiple end costs if MaxEndDeviation is non-zero.
+ var endCosts = new Matrix(size).Fill(MaxCost);
+ var innerCosts = new List>();
{
void RegisterSegmentType(string type)
{
@@ -578,7 +602,7 @@ namespace OpenRA.Mods.Common.MapGenerator
segmentTypeToId.Add(type, newId);
segmentsByStart.Add([]);
segmentsByEnd.Add([]);
- costs.Add(new Matrix(size).Fill(MaxCost));
+ innerCosts.Add(new Matrix(size).Fill(MaxCost));
}
foreach (var segment in orderedPermittedSegments)
@@ -589,8 +613,13 @@ namespace OpenRA.Mods.Common.MapGenerator
var startTypeId = segmentTypeToId[segment.Start];
var endTypeId = segmentTypeToId[segment.End];
var tilePathSegment = new TilingSegment(template, segment, startTypeId, endTypeId);
- segmentsByStart[startTypeId].Add(tilePathSegment);
- segmentsByEnd[endTypeId].Add(tilePathSegment);
+ var tuple = (
+ tilePathSegment,
+ permittedStartSegments.Contains(segment) && segment.Start == start.SegmentType,
+ permittedInnerSegments.Contains(segment),
+ permittedEndSegments.Contains(segment) && segment.End == end.SegmentType);
+ segmentsByStart[startTypeId].Add(tuple);
+ segmentsByEnd[endTypeId].Add(tuple);
}
}
@@ -610,43 +639,18 @@ namespace OpenRA.Mods.Common.MapGenerator
var pathStartTypeId = segmentTypeToId[start.SegmentType];
var pathEndTypeId = segmentTypeToId[end.SegmentType];
- var innerTypeIds = Segments.Inner
- .SelectMany(segment => new[] { segment.Start, segment.End })
- .Select(segmentType => segmentTypeToId[segmentType])
- .ToImmutableHashSet();
// Lower (closer to zero) costs are better matches.
// MaxScore means totally unacceptable.
int ScoreSegment(TilingSegment segment, CVec from)
{
- if (from == pathStart)
- {
- if (segment.StartTypeId != pathStartTypeId)
- return MaxCost;
- }
- else
- {
- if (!innerTypeIds.Contains(segment.StartTypeId))
- return MaxCost;
- }
-
var to = from + segment.Moves;
- if (to == pathEnd)
- {
- if (segment.EndTypeId != pathEndTypeId)
- return MaxCost;
- }
- else
- {
- if (!innerTypeIds.Contains(segment.EndTypeId))
- return MaxCost;
- if (isLoop && lowProgress[from.X, from.Y] > highProgress[to.X, to.Y] && highProgress[to.X, to.Y] != 0)
- {
- // We've missed the start/end of the loop and have potentially gone past it
- // (as far as low and high progress are concerned).
- return MaxCost;
- }
+ if (isLoop && to != pathEnd && lowProgress[from.X, from.Y] > highProgress[to.X, to.Y] && highProgress[to.X, to.Y] != 0)
+ {
+ // We've missed the start/end of the loop and have potentially gone past it
+ // (as far as low and high progress are concerned).
+ return MaxCost;
}
var deviationAcc = 0;
@@ -700,10 +704,23 @@ namespace OpenRA.Mods.Common.MapGenerator
return deviationAcc;
}
- void UpdateFrom(CVec from, int fromTypeId, int fromCost)
+ void UpdateFrom(CVec from, int fromTypeId, bool isForStart)
{
- foreach (var segment in segmentsByStart[fromTypeId])
+ var fromCost = isForStart ? 0 : innerCosts[fromTypeId][from.X, from.Y];
+
+ foreach (var (segment, canStart, canInner, canEnd) in segmentsByStart[fromTypeId])
{
+ if (isForStart)
+ {
+ if (!canStart)
+ continue;
+ }
+ else
+ {
+ if (!(canEnd || canInner))
+ continue;
+ }
+
var to = from + segment.Moves;
if (to.X < 0 || to.X >= size.X || to.Y < 0 || to.Y >= size.Y)
continue;
@@ -721,44 +738,59 @@ namespace OpenRA.Mods.Common.MapGenerator
var toCost = fromCost + segmentCost;
var toTypeId = segment.EndTypeId;
- if (toCost < costs[toTypeId][to.X, to.Y])
+
+ if ((canStart || canInner) && toCost < innerCosts[toTypeId][to.X, to.Y])
{
- costs[toTypeId][to.X, to.Y] = toCost;
+ innerCosts[toTypeId][to.X, to.Y] = toCost;
SetPriorityAt(toTypeId, to, toCost);
}
+
+ if (canEnd && toCost < endCosts[to.X, to.Y])
+ endCosts[to.X, to.Y] = toCost;
}
SetPriorityAt(fromTypeId, from, MaxCost);
}
- // costs[pathStartTypeId][pathStart.X, pathStart.Y] is preset to
- // MaxCost, but we pass in a cost of 0 for the first iteration. We
- // leave it like this in case this is a looped path with a shared
- // start and end point. We set it to 0 later when tracing back.
- UpdateFrom(pathStart, pathStartTypeId, 0);
+ UpdateFrom(pathStart, pathStartTypeId, true);
while (true)
{
var (fromTypeId, from, priority) = GetNextPriority();
- if (priority == MaxCost || from == pathEnd)
+ if (priority == MaxCost)
break;
- UpdateFrom(from, fromTypeId, costs[fromTypeId][from.X, from.Y]);
+ UpdateFrom(from, fromTypeId, false);
}
// Trace back and update tiles
- var resultPoints = new List
- {
- new(pathEnd.X + minPoint.X, pathEnd.Y + minPoint.Y)
- };
+ var resultPoints = new List();
- (CVec From, int FromTypeId) TraceBackStep(CVec to, int toTypeId, int toCost)
+ (CVec From, int FromTypeId) TraceBackStep(CVec to, int toTypeId, bool isForEnd)
{
+ var toCost = isForEnd ? endCosts[to.X, to.Y] : innerCosts[toTypeId][to.X, to.Y];
var candidates = new List();
- foreach (var segment in segmentsByEnd[toTypeId])
+ foreach (var (segment, canStart, canInner, canEnd) in segmentsByEnd[toTypeId])
{
+ if (isForEnd)
+ {
+ if (!canEnd)
+ continue;
+ }
+ else
+ {
+ if (!(canStart || canInner))
+ continue;
+ }
+
var from = to - segment.Moves;
+ var mustStart =
+ from == pathStart && segment.StartTypeId == pathStartTypeId;
+
+ if (mustStart && !canStart)
+ continue;
+
if (from.X < 0 || from.X >= size.X || from.Y < 0 || from.Y >= size.Y)
continue;
@@ -774,7 +806,9 @@ namespace OpenRA.Mods.Common.MapGenerator
continue;
var fromCost = toCost - segmentCost;
- if (fromCost == costs[segment.StartTypeId][from.X, from.Y])
+ var requiredFromCost =
+ mustStart ? 0 : innerCosts[segment.StartTypeId][from.X, from.Y];
+ if (fromCost == requiredFromCost)
candidates.Add(segment);
}
@@ -794,24 +828,80 @@ namespace OpenRA.Mods.Common.MapGenerator
}
{
- var to = pathEnd;
var toTypeId = pathEndTypeId;
- var bestCost = costs[toTypeId][to.X, to.Y];
- if (bestCost == MaxCost)
- return null;
- // For non-loops, this remained unset at MaxCost. For loops,
- // this was the shared start and end point and got set to
- // bestCost. We set it to 0 for traceback, but perform the
- // first iteration using bestCost. (The opposite of how we
- // traced forward.)
- costs[pathStartTypeId][pathStart.X, pathStart.Y] = 0;
+ if (endCosts[pathEnd.X, pathEnd.Y] == MaxCost)
+ {
+ // There isn't a tiling solution to the exact target end point. If enabled,
+ // search for an alternative, nearby end point.
+ var maxEndDeviation = Math.Min(MaxEndDeviation, MaxDeviation);
+ if (maxEndDeviation == 0 || isLoop)
+ return null;
- (to, toTypeId) = TraceBackStep(to, toTypeId, bestCost);
+ // Find the closest points which are near the original target end point and
+ // have a tiling solution.
+ const int Unreached = int.MaxValue;
+ const int Unsolved = int.MaxValue - 1;
+ var fallbackDistances =
+ new Matrix(maxEndDeviation * 2 + 1, maxEndDeviation * 2 + 1)
+ .Fill(Unreached);
+
+ int? FallbacksFiller(int2 xy, int distance)
+ {
+ if (fallbackDistances[xy] != Unreached)
+ return null;
+
+ var p = new int2(pathEnd.X - maxEndDeviation, pathEnd.Y - maxEndDeviation) + xy;
+
+ if (!deviations.ContainsXY(p.X, p.Y) || deviations[p.X, p.Y] == OverDeviation)
+ {
+ fallbackDistances[xy] = Unsolved;
+ return null;
+ }
+
+ fallbackDistances[xy] =
+ endCosts[p.X, p.Y] != MaxCost ? distance : Unsolved;
+ return distance + 1;
+ }
+
+ MatrixUtils.FloodFill(
+ fallbackDistances.Size,
+ [(new int2(maxEndDeviation, maxEndDeviation), 0)],
+ FallbacksFiller,
+ Direction.Spread4);
+
+ var bestDistance = fallbackDistances.Data.Min();
+ if (bestDistance == Unreached || bestDistance == Unsolved)
+ return null;
+
+ // Find the lowest cost candidate end point.
+ var fallbackCosts = new Matrix(maxEndDeviation * 2 + 1, maxEndDeviation * 2 + 1);
+ for (var y = -maxEndDeviation; y <= maxEndDeviation; y++)
+ for (var x = -maxEndDeviation; x <= maxEndDeviation; x++)
+ {
+ var fallbackXy = new int2(x + maxEndDeviation, y + maxEndDeviation);
+ var p = new int2(x + pathEnd.X, y + pathEnd.Y);
+ fallbackCosts[fallbackXy] =
+ (fallbackDistances[fallbackXy] == bestDistance) ? endCosts[p] : MaxCost;
+ }
+
+ var (chosenXy, _) = MatrixUtils.FindRandomBest(
+ fallbackCosts,
+ random,
+ (a, b) => b.CompareTo(a));
+
+ pathEnd = new CVec(chosenXy.X - maxEndDeviation, chosenXy.Y - maxEndDeviation) + pathEnd;
+ }
+
+ var to = pathEnd;
+
+ resultPoints.Add(new(to.X + minPoint.X, to.Y + minPoint.Y));
+
+ (to, toTypeId) = TraceBackStep(to, toTypeId, true);
// No need to check direction. If that is an issue, I have bigger problems to worry about.
while (to != pathStart)
- (to, toTypeId) = TraceBackStep(to, toTypeId, costs[toTypeId][to.X, to.Y]);
+ (to, toTypeId) = TraceBackStep(to, toTypeId, false);
}
// Traced back in reverse, so reverse the reversal.
@@ -1275,5 +1365,19 @@ namespace OpenRA.Mods.Common.MapGenerator
return true;
}
+
+ /// Set MaxEndDeviation.
+ public TilingPath SetMaxEndDeviation(int maxEndDeviation)
+ {
+ MaxEndDeviation = maxEndDeviation;
+ return this;
+ }
+
+ /// Allow end point deviation as far as MaxDeviation will allow.
+ public TilingPath SetAutoEndDeviation()
+ {
+ MaxEndDeviation = int.MaxValue;
+ return this;
+ }
}
}
diff --git a/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs b/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs
index cc97c8f4fb..9459724455 100644
--- a/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs
+++ b/OpenRA.Mods.Common/Traits/World/ExperimentalMapGenerator.cs
@@ -666,6 +666,7 @@ namespace OpenRA.Mods.Common.Traits
beachPermittedTemplates);
beachPath
.ExtendEdge(4)
+ .SetAutoEndDeviation()
.OptimizeLoop();
tiledBeaches[i] =
beachPath.Tile(beachTilingRandom)
@@ -732,6 +733,7 @@ namespace OpenRA.Mods.Common.Traits
nonLoopedCliffPermittedTemplates);
cliffPath
.ExtendEdge(4)
+ .SetAutoEndDeviation()
.OptimizeLoop();
if (cliffPath.Tile(cliffTilingRandom) == null)
throw new MapGenerationException("Could not fit tiles for exterior circle cliffs");
@@ -812,6 +814,7 @@ namespace OpenRA.Mods.Common.Traits
nonLoopedCliffPermittedTemplates);
cliffPath
.ExtendEdge(4)
+ .SetAutoEndDeviation()
.OptimizeLoop();
if (cliffPath.Tile(cliffTilingRandom) == null)
throw new MapGenerationException("Could not fit tiles for cliffs");
@@ -1176,6 +1179,7 @@ namespace OpenRA.Mods.Common.Traits
.ExtendEdge(2 * roadTotalShrink + RoadMinimumShrinkLength)
.Shrink(roadTotalShrink, RoadMinimumShrinkLength)
.InertiallyExtend(RoadStraightenGrow, RoadInertialRange)
+ .SetAutoEndDeviation()
.OptimizeLoop()
.RetainIfValid();
diff --git a/mods/cnc/tilesets/desert.yaml b/mods/cnc/tilesets/desert.yaml
index 0141818b43..dce8aab34a 100644
--- a/mods/cnc/tilesets/desert.yaml
+++ b/mods/cnc/tilesets/desert.yaml
@@ -1069,16 +1069,6 @@ Templates:
Inner: Road
Points: 2,1, 2,0
Start: Clear.U
- Segment@1extended:
- End: Clear.D
- Inner: Road
- Points: 1,0, 1,1, 1,2
- Start: Road.D
- Segment@2extended:
- End: Road.U
- Inner: Road
- Points: 2,2, 2,1, 2,0
- Start: Clear.U
Template@94:
Id: 94
Images: d02.des
@@ -1100,16 +1090,6 @@ Templates:
Inner: Road
Points: 1,1, 2,1
Start: Clear.R
- Segment@1extended:
- End: Clear.L
- Inner: Road
- Points: 2,0, 1,0, 0,0
- Start: Road.L
- Segment@2extended:
- End: Road.R
- Inner: Road
- Points: 0,1, 1,1, 2,1
- Start: Clear.R
Template@95:
Id: 95
Images: d03.des
@@ -1129,16 +1109,6 @@ Templates:
Inner: Road
Points: 0,1, 0,2
Start: Clear.D
- Segment@1extended:
- End: Clear.U
- Inner: Road
- Points: 1,2, 1,1, 1,0
- Start: Road.U
- Segment@2extended:
- End: Road.D
- Inner: Road
- Points: 0,0, 0,1, 0,2
- Start: Clear.D
Template@96:
Id: 96
Images: d04.des
@@ -1159,16 +1129,6 @@ Templates:
Inner: Road
Points: 1,1, 0,1
Start: Clear.L
- Segment@1extended:
- End: Clear.R
- Inner: Road
- Points: 0,2, 1,2, 2,2
- Start: Road.R
- Segment@2extended:
- End: Road.L
- Inner: Road
- Points: 2,1, 1,1, 0,1
- Start: Clear.L
Template@97:
Id: 97
Images: d05.des
diff --git a/mods/cnc/tilesets/snow.yaml b/mods/cnc/tilesets/snow.yaml
index ac0bd2937a..7d6de69f25 100644
--- a/mods/cnc/tilesets/snow.yaml
+++ b/mods/cnc/tilesets/snow.yaml
@@ -1143,16 +1143,6 @@ Templates:
Inner: Road
Points: 2,1, 2,0
Start: Clear.U
- Segment@1extended:
- End: Clear.D
- Inner: Road
- Points: 1,0, 1,1, 1,2
- Start: Road.D
- Segment@2extended:
- End: Road.U
- Inner: Road
- Points: 2,2, 2,1, 2,0
- Start: Clear.U
Template@94:
Id: 94
Images: d02.sno
@@ -1174,16 +1164,6 @@ Templates:
Inner: Road
Points: 1,1, 2,1
Start: Clear.R
- Segment@1extended:
- End: Clear.L
- Inner: Road
- Points: 2,0, 1,0, 0,0
- Start: Road.L
- Segment@2extended:
- End: Road.R
- Inner: Road
- Points: 0,1, 1,1, 2,1
- Start: Clear.R
Template@95:
Id: 95
Images: d03.sno
@@ -1203,16 +1183,6 @@ Templates:
Inner: Road
Points: 0,1, 0,2
Start: Clear.D
- Segment@1extended:
- End: Clear.U
- Inner: Road
- Points: 1,2, 1,1, 1,0
- Start: Road.U
- Segment@2extended:
- End: Road.D
- Inner: Road
- Points: 0,0, 0,1, 0,2
- Start: Clear.D
Template@96:
Id: 96
Images: d04.sno
@@ -1233,16 +1203,6 @@ Templates:
Inner: Road
Points: 1,1, 0,1
Start: Clear.L
- Segment@1extended:
- End: Clear.R
- Inner: Road
- Points: 0,2, 1,2, 2,2
- Start: Road.R
- Segment@2extended:
- End: Road.L
- Inner: Road
- Points: 2,1, 1,1, 0,1
- Start: Clear.L
Template@97:
Id: 97
Images: d05.sno
diff --git a/mods/cnc/tilesets/temperat.yaml b/mods/cnc/tilesets/temperat.yaml
index 47549b0537..d8d1342a06 100644
--- a/mods/cnc/tilesets/temperat.yaml
+++ b/mods/cnc/tilesets/temperat.yaml
@@ -1131,16 +1131,6 @@ Templates:
Inner: Road
Points: 2,1, 2,0
Start: Clear.U
- Segment@1extended:
- End: Clear.D
- Inner: Road
- Points: 1,0, 1,1, 1,2
- Start: Road.D
- Segment@2extended:
- End: Road.U
- Inner: Road
- Points: 2,2, 2,1, 2,0
- Start: Clear.U
Template@94:
Id: 94
Images: d02.tem
@@ -1162,16 +1152,6 @@ Templates:
Inner: Road
Points: 1,1, 2,1
Start: Clear.R
- Segment@1extended:
- End: Clear.L
- Inner: Road
- Points: 2,0, 1,0, 0,0
- Start: Road.L
- Segment@2extended:
- End: Road.R
- Inner: Road
- Points: 0,1, 1,1, 2,1
- Start: Clear.R
Template@95:
Id: 95
Images: d03.tem
@@ -1191,16 +1171,6 @@ Templates:
Inner: Road
Points: 0,1, 0,2
Start: Clear.D
- Segment@1extended:
- End: Clear.U
- Inner: Road
- Points: 1,2, 1,1, 1,0
- Start: Road.U
- Segment@2extended:
- End: Road.D
- Inner: Road
- Points: 0,0, 0,1, 0,2
- Start: Clear.D
Template@96:
Id: 96
Images: d04.tem
@@ -1221,16 +1191,6 @@ Templates:
Inner: Road
Points: 1,1, 0,1
Start: Clear.L
- Segment@1extended:
- End: Clear.R
- Inner: Road
- Points: 0,2, 1,2, 2,2
- Start: Road.R
- Segment@2extended:
- End: Road.L
- Inner: Road
- Points: 2,1, 1,1, 0,1
- Start: Clear.L
Template@97:
Id: 97
Images: d05.tem
diff --git a/mods/cnc/tilesets/winter.yaml b/mods/cnc/tilesets/winter.yaml
index ec15ffcc54..dd375c65d0 100644
--- a/mods/cnc/tilesets/winter.yaml
+++ b/mods/cnc/tilesets/winter.yaml
@@ -1115,16 +1115,6 @@ Templates:
Inner: Road
Points: 2,1, 2,0
Start: Clear.U
- Segment@1extended:
- End: Clear.D
- Inner: Road
- Points: 1,0, 1,1, 1,2
- Start: Road.D
- Segment@2extended:
- End: Road.U
- Inner: Road
- Points: 2,2, 2,1, 2,0
- Start: Clear.U
Template@94:
Id: 94
Images: d02.win
@@ -1146,16 +1136,6 @@ Templates:
Inner: Road
Points: 1,1, 2,1
Start: Clear.R
- Segment@1extended:
- End: Clear.L
- Inner: Road
- Points: 2,0, 1,0, 0,0
- Start: Road.L
- Segment@2extended:
- End: Road.R
- Inner: Road
- Points: 0,1, 1,1, 2,1
- Start: Clear.R
Template@95:
Id: 95
Images: d03.win
@@ -1175,16 +1155,6 @@ Templates:
Inner: Road
Points: 0,1, 0,2
Start: Clear.D
- Segment@1extended:
- End: Clear.U
- Inner: Road
- Points: 1,2, 1,1, 1,0
- Start: Road.U
- Segment@2extended:
- End: Road.D
- Inner: Road
- Points: 0,0, 0,1, 0,2
- Start: Clear.D
Template@96:
Id: 96
Images: d04.win
@@ -1205,16 +1175,6 @@ Templates:
Inner: Road
Points: 1,1, 0,1
Start: Clear.L
- Segment@1extended:
- End: Clear.R
- Inner: Road
- Points: 0,2, 1,2, 2,2
- Start: Road.R
- Segment@2extended:
- End: Road.L
- Inner: Road
- Points: 2,1, 1,1, 0,1
- Start: Clear.L
Template@97:
Id: 97
Images: d05.win