Support relaxed end points in TilingPath
Whilst TilingPath has always allowed deviating from the path points between the start and end point, it didn't allow deviation from the start or end points themselves. This change allows the end point to deviate if the tiling would otherwise fail. The start point remains strictly positioned, as before. Loop end points (which must necessarily match the start) also remain strict. The ExperimentalMapGenerator is modified to benefit from the relaxed tiling constraints. As a result, failed map generation due to tiling failures is much rarer, and will make some otherwise very inflexible template categories viable for tiling. Includes some impure refactoring around the treatment of start, intermediate, and end segments. As such, this changes map generation output, even for maps which already tiled perfectly. A previous workaround used for CnC road templates, whereby additional segments were defined for road ending templates, is now redundant and cleaned up.
This commit is contained in:
committed by
Gustas Kažukauskas
parent
b2acc653a0
commit
f09959ecba
@@ -15,6 +15,7 @@ using System.Collections.Immutable;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
using OpenRA.Support;
|
||||||
|
|
||||||
namespace OpenRA.Mods.Common.MapGenerator
|
namespace OpenRA.Mods.Common.MapGenerator
|
||||||
{
|
{
|
||||||
@@ -1502,5 +1503,34 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
|
|
||||||
return matrix;
|
return matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rank all cell values and select the best (greatest compared) value.
|
||||||
|
/// If there are equally good best candidates, choose one at random.
|
||||||
|
/// </summary>
|
||||||
|
public static (int2 MPos, T Value) FindRandomBest<T>(
|
||||||
|
Matrix<T> matrix,
|
||||||
|
MersenneTwister random,
|
||||||
|
Comparison<T> comparison)
|
||||||
|
{
|
||||||
|
var candidates = new List<int2>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,6 +212,13 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int MinSeparation;
|
public int MinSeparation;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxEndDeviation;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stores start type and direction.
|
/// Stores start type and direction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -242,6 +249,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
MaxDeviation = maxDeviation;
|
MaxDeviation = maxDeviation;
|
||||||
MaxSkip = 0;
|
MaxSkip = 0;
|
||||||
MinSeparation = 0;
|
MinSeparation = 0;
|
||||||
|
MaxEndDeviation = 0;
|
||||||
Start = new Terminal(startType, null);
|
Start = new Terminal(startType, null);
|
||||||
End = new Terminal(endType, null);
|
End = new Terminal(endType, null);
|
||||||
Segments = permittedTemplates;
|
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 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.
|
// 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
|
// 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
|
// 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 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 pathEnd = points[^1];
|
||||||
var orderedPermittedSegments = Segments.All.ToImmutableArray();
|
var orderedPermittedSegments = Segments.All.ToImmutableArray();
|
||||||
var permittedSegments = orderedPermittedSegments.ToImmutableHashSet();
|
var permittedSegments = orderedPermittedSegments.ToImmutableHashSet();
|
||||||
|
var permittedStartSegments = Segments.Start.ToImmutableHashSet();
|
||||||
|
var permittedInnerSegments = Segments.Inner.ToImmutableHashSet();
|
||||||
|
var permittedEndSegments = Segments.End.ToImmutableHashSet();
|
||||||
|
|
||||||
const int MaxCost = int.MaxValue;
|
const int MaxCost = int.MaxValue;
|
||||||
var segmentTypeToId = new Dictionary<string, int>();
|
var segmentTypeToId = new Dictionary<string, int>();
|
||||||
var segmentsByStart = new List<List<TilingSegment>>();
|
var segmentsByStart = new List<List<(TilingSegment Segment, bool CanStart, bool CanInner, bool CanEnd)>>();
|
||||||
var segmentsByEnd = new List<List<TilingSegment>>();
|
var segmentsByEnd = new List<List<(TilingSegment Segment, bool CanStart, bool CanInner, bool CanEnd)>>();
|
||||||
var costs = new List<Matrix<int>>();
|
|
||||||
|
// 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<int>(size).Fill(MaxCost);
|
||||||
|
var innerCosts = new List<Matrix<int>>();
|
||||||
{
|
{
|
||||||
void RegisterSegmentType(string type)
|
void RegisterSegmentType(string type)
|
||||||
{
|
{
|
||||||
@@ -578,7 +602,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
segmentTypeToId.Add(type, newId);
|
segmentTypeToId.Add(type, newId);
|
||||||
segmentsByStart.Add([]);
|
segmentsByStart.Add([]);
|
||||||
segmentsByEnd.Add([]);
|
segmentsByEnd.Add([]);
|
||||||
costs.Add(new Matrix<int>(size).Fill(MaxCost));
|
innerCosts.Add(new Matrix<int>(size).Fill(MaxCost));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var segment in orderedPermittedSegments)
|
foreach (var segment in orderedPermittedSegments)
|
||||||
@@ -589,8 +613,13 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
var startTypeId = segmentTypeToId[segment.Start];
|
var startTypeId = segmentTypeToId[segment.Start];
|
||||||
var endTypeId = segmentTypeToId[segment.End];
|
var endTypeId = segmentTypeToId[segment.End];
|
||||||
var tilePathSegment = new TilingSegment(template, segment, startTypeId, endTypeId);
|
var tilePathSegment = new TilingSegment(template, segment, startTypeId, endTypeId);
|
||||||
segmentsByStart[startTypeId].Add(tilePathSegment);
|
var tuple = (
|
||||||
segmentsByEnd[endTypeId].Add(tilePathSegment);
|
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 pathStartTypeId = segmentTypeToId[start.SegmentType];
|
||||||
var pathEndTypeId = segmentTypeToId[end.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.
|
// Lower (closer to zero) costs are better matches.
|
||||||
// MaxScore means totally unacceptable.
|
// MaxScore means totally unacceptable.
|
||||||
int ScoreSegment(TilingSegment segment, CVec from)
|
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;
|
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)
|
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
|
// We've missed the start/end of the loop and have potentially gone past it
|
||||||
// (as far as low and high progress are concerned).
|
// (as far as low and high progress are concerned).
|
||||||
return MaxCost;
|
return MaxCost;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var deviationAcc = 0;
|
var deviationAcc = 0;
|
||||||
@@ -700,10 +704,23 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
return deviationAcc;
|
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;
|
var to = from + segment.Moves;
|
||||||
if (to.X < 0 || to.X >= size.X || to.Y < 0 || to.Y >= size.Y)
|
if (to.X < 0 || to.X >= size.X || to.Y < 0 || to.Y >= size.Y)
|
||||||
continue;
|
continue;
|
||||||
@@ -721,44 +738,59 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
|
|
||||||
var toCost = fromCost + segmentCost;
|
var toCost = fromCost + segmentCost;
|
||||||
var toTypeId = segment.EndTypeId;
|
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);
|
SetPriorityAt(toTypeId, to, toCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canEnd && toCost < endCosts[to.X, to.Y])
|
||||||
|
endCosts[to.X, to.Y] = toCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetPriorityAt(fromTypeId, from, MaxCost);
|
SetPriorityAt(fromTypeId, from, MaxCost);
|
||||||
}
|
}
|
||||||
|
|
||||||
// costs[pathStartTypeId][pathStart.X, pathStart.Y] is preset to
|
UpdateFrom(pathStart, pathStartTypeId, true);
|
||||||
// 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);
|
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var (fromTypeId, from, priority) = GetNextPriority();
|
var (fromTypeId, from, priority) = GetNextPriority();
|
||||||
|
|
||||||
if (priority == MaxCost || from == pathEnd)
|
if (priority == MaxCost)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
UpdateFrom(from, fromTypeId, costs[fromTypeId][from.X, from.Y]);
|
UpdateFrom(from, fromTypeId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trace back and update tiles
|
// Trace back and update tiles
|
||||||
var resultPoints = new List<CPos>
|
var resultPoints = new List<CPos>();
|
||||||
{
|
|
||||||
new(pathEnd.X + minPoint.X, pathEnd.Y + minPoint.Y)
|
|
||||||
};
|
|
||||||
|
|
||||||
(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<TilingSegment>();
|
var candidates = new List<TilingSegment>();
|
||||||
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 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)
|
if (from.X < 0 || from.X >= size.X || from.Y < 0 || from.Y >= size.Y)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -774,7 +806,9 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var fromCost = toCost - segmentCost;
|
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);
|
candidates.Add(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,24 +828,80 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var to = pathEnd;
|
|
||||||
var toTypeId = pathEndTypeId;
|
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,
|
if (endCosts[pathEnd.X, pathEnd.Y] == MaxCost)
|
||||||
// this was the shared start and end point and got set to
|
{
|
||||||
// bestCost. We set it to 0 for traceback, but perform the
|
// There isn't a tiling solution to the exact target end point. If enabled,
|
||||||
// first iteration using bestCost. (The opposite of how we
|
// search for an alternative, nearby end point.
|
||||||
// traced forward.)
|
var maxEndDeviation = Math.Min(MaxEndDeviation, MaxDeviation);
|
||||||
costs[pathStartTypeId][pathStart.X, pathStart.Y] = 0;
|
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<int>(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<int>(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.
|
// No need to check direction. If that is an issue, I have bigger problems to worry about.
|
||||||
while (to != pathStart)
|
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.
|
// Traced back in reverse, so reverse the reversal.
|
||||||
@@ -1275,5 +1365,19 @@ namespace OpenRA.Mods.Common.MapGenerator
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Set MaxEndDeviation.</summary>
|
||||||
|
public TilingPath SetMaxEndDeviation(int maxEndDeviation)
|
||||||
|
{
|
||||||
|
MaxEndDeviation = maxEndDeviation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Allow end point deviation as far as MaxDeviation will allow.</summary>
|
||||||
|
public TilingPath SetAutoEndDeviation()
|
||||||
|
{
|
||||||
|
MaxEndDeviation = int.MaxValue;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -666,6 +666,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
beachPermittedTemplates);
|
beachPermittedTemplates);
|
||||||
beachPath
|
beachPath
|
||||||
.ExtendEdge(4)
|
.ExtendEdge(4)
|
||||||
|
.SetAutoEndDeviation()
|
||||||
.OptimizeLoop();
|
.OptimizeLoop();
|
||||||
tiledBeaches[i] =
|
tiledBeaches[i] =
|
||||||
beachPath.Tile(beachTilingRandom)
|
beachPath.Tile(beachTilingRandom)
|
||||||
@@ -732,6 +733,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
nonLoopedCliffPermittedTemplates);
|
nonLoopedCliffPermittedTemplates);
|
||||||
cliffPath
|
cliffPath
|
||||||
.ExtendEdge(4)
|
.ExtendEdge(4)
|
||||||
|
.SetAutoEndDeviation()
|
||||||
.OptimizeLoop();
|
.OptimizeLoop();
|
||||||
if (cliffPath.Tile(cliffTilingRandom) == null)
|
if (cliffPath.Tile(cliffTilingRandom) == null)
|
||||||
throw new MapGenerationException("Could not fit tiles for exterior circle cliffs");
|
throw new MapGenerationException("Could not fit tiles for exterior circle cliffs");
|
||||||
@@ -812,6 +814,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
nonLoopedCliffPermittedTemplates);
|
nonLoopedCliffPermittedTemplates);
|
||||||
cliffPath
|
cliffPath
|
||||||
.ExtendEdge(4)
|
.ExtendEdge(4)
|
||||||
|
.SetAutoEndDeviation()
|
||||||
.OptimizeLoop();
|
.OptimizeLoop();
|
||||||
if (cliffPath.Tile(cliffTilingRandom) == null)
|
if (cliffPath.Tile(cliffTilingRandom) == null)
|
||||||
throw new MapGenerationException("Could not fit tiles for cliffs");
|
throw new MapGenerationException("Could not fit tiles for cliffs");
|
||||||
@@ -1176,6 +1179,7 @@ namespace OpenRA.Mods.Common.Traits
|
|||||||
.ExtendEdge(2 * roadTotalShrink + RoadMinimumShrinkLength)
|
.ExtendEdge(2 * roadTotalShrink + RoadMinimumShrinkLength)
|
||||||
.Shrink(roadTotalShrink, RoadMinimumShrinkLength)
|
.Shrink(roadTotalShrink, RoadMinimumShrinkLength)
|
||||||
.InertiallyExtend(RoadStraightenGrow, RoadInertialRange)
|
.InertiallyExtend(RoadStraightenGrow, RoadInertialRange)
|
||||||
|
.SetAutoEndDeviation()
|
||||||
.OptimizeLoop()
|
.OptimizeLoop()
|
||||||
.RetainIfValid();
|
.RetainIfValid();
|
||||||
|
|
||||||
|
|||||||
@@ -1069,16 +1069,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 2,1, 2,0
|
Points: 2,1, 2,0
|
||||||
Start: Clear.U
|
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:
|
Template@94:
|
||||||
Id: 94
|
Id: 94
|
||||||
Images: d02.des
|
Images: d02.des
|
||||||
@@ -1100,16 +1090,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 2,1
|
Points: 1,1, 2,1
|
||||||
Start: Clear.R
|
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:
|
Template@95:
|
||||||
Id: 95
|
Id: 95
|
||||||
Images: d03.des
|
Images: d03.des
|
||||||
@@ -1129,16 +1109,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 0,1, 0,2
|
Points: 0,1, 0,2
|
||||||
Start: Clear.D
|
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:
|
Template@96:
|
||||||
Id: 96
|
Id: 96
|
||||||
Images: d04.des
|
Images: d04.des
|
||||||
@@ -1159,16 +1129,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 0,1
|
Points: 1,1, 0,1
|
||||||
Start: Clear.L
|
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:
|
Template@97:
|
||||||
Id: 97
|
Id: 97
|
||||||
Images: d05.des
|
Images: d05.des
|
||||||
|
|||||||
@@ -1143,16 +1143,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 2,1, 2,0
|
Points: 2,1, 2,0
|
||||||
Start: Clear.U
|
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:
|
Template@94:
|
||||||
Id: 94
|
Id: 94
|
||||||
Images: d02.sno
|
Images: d02.sno
|
||||||
@@ -1174,16 +1164,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 2,1
|
Points: 1,1, 2,1
|
||||||
Start: Clear.R
|
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:
|
Template@95:
|
||||||
Id: 95
|
Id: 95
|
||||||
Images: d03.sno
|
Images: d03.sno
|
||||||
@@ -1203,16 +1183,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 0,1, 0,2
|
Points: 0,1, 0,2
|
||||||
Start: Clear.D
|
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:
|
Template@96:
|
||||||
Id: 96
|
Id: 96
|
||||||
Images: d04.sno
|
Images: d04.sno
|
||||||
@@ -1233,16 +1203,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 0,1
|
Points: 1,1, 0,1
|
||||||
Start: Clear.L
|
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:
|
Template@97:
|
||||||
Id: 97
|
Id: 97
|
||||||
Images: d05.sno
|
Images: d05.sno
|
||||||
|
|||||||
@@ -1131,16 +1131,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 2,1, 2,0
|
Points: 2,1, 2,0
|
||||||
Start: Clear.U
|
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:
|
Template@94:
|
||||||
Id: 94
|
Id: 94
|
||||||
Images: d02.tem
|
Images: d02.tem
|
||||||
@@ -1162,16 +1152,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 2,1
|
Points: 1,1, 2,1
|
||||||
Start: Clear.R
|
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:
|
Template@95:
|
||||||
Id: 95
|
Id: 95
|
||||||
Images: d03.tem
|
Images: d03.tem
|
||||||
@@ -1191,16 +1171,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 0,1, 0,2
|
Points: 0,1, 0,2
|
||||||
Start: Clear.D
|
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:
|
Template@96:
|
||||||
Id: 96
|
Id: 96
|
||||||
Images: d04.tem
|
Images: d04.tem
|
||||||
@@ -1221,16 +1191,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 0,1
|
Points: 1,1, 0,1
|
||||||
Start: Clear.L
|
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:
|
Template@97:
|
||||||
Id: 97
|
Id: 97
|
||||||
Images: d05.tem
|
Images: d05.tem
|
||||||
|
|||||||
@@ -1115,16 +1115,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 2,1, 2,0
|
Points: 2,1, 2,0
|
||||||
Start: Clear.U
|
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:
|
Template@94:
|
||||||
Id: 94
|
Id: 94
|
||||||
Images: d02.win
|
Images: d02.win
|
||||||
@@ -1146,16 +1136,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 2,1
|
Points: 1,1, 2,1
|
||||||
Start: Clear.R
|
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:
|
Template@95:
|
||||||
Id: 95
|
Id: 95
|
||||||
Images: d03.win
|
Images: d03.win
|
||||||
@@ -1175,16 +1155,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 0,1, 0,2
|
Points: 0,1, 0,2
|
||||||
Start: Clear.D
|
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:
|
Template@96:
|
||||||
Id: 96
|
Id: 96
|
||||||
Images: d04.win
|
Images: d04.win
|
||||||
@@ -1205,16 +1175,6 @@ Templates:
|
|||||||
Inner: Road
|
Inner: Road
|
||||||
Points: 1,1, 0,1
|
Points: 1,1, 0,1
|
||||||
Start: Clear.L
|
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:
|
Template@97:
|
||||||
Id: 97
|
Id: 97
|
||||||
Images: d05.win
|
Images: d05.win
|
||||||
|
|||||||
Reference in New Issue
Block a user