Remove floating points from map generation
Removes all use of floating point from the RaMapGenerator map generator and its dependencies. Floating point behavior is potentially non-portable across client hardware. Removing them should make the map generation logic consistent even across clients with different floating point hardware or compiler behavior. This may be useful for sync-safe multiplayer map generation where clients independently generate the map from settings. Most previously fractional public-facing settings are now represented as numbers out of 1000, with some exceptions using 1000000. Most internal logic which relies on fixed-point concepts now uses 1024ths, though some floating point mechanisms have been replaced with alternative discrete approximations (e.g. gaussian to binomial).
This commit is contained in:
committed by
Gustas Kažukauskas
parent
04e9cef38e
commit
037326024b
@@ -268,14 +268,14 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uniformally add to or subtract from all matrix cells such that the given quantile,
|
||||
/// fraction, has the given target value.
|
||||
/// Uniformally add to or subtract from all cells such that count out of every outOf cells,
|
||||
/// are no greater than the given target value.
|
||||
/// </summary>
|
||||
public static void CalibrateQuantileInPlace(CellLayer<float> cellLayer, float target, float fraction)
|
||||
public static void CalibrateQuantileInPlace(CellLayer<int> cellLayer, int target, int count, int outOf)
|
||||
{
|
||||
var sorted = Entries(cellLayer);
|
||||
Array.Sort(sorted);
|
||||
var adjustment = target - MatrixUtils.ArrayQuantile(sorted, fraction);
|
||||
var adjustment = target - sorted[(sorted.Length - 1) * count / outOf];
|
||||
foreach (var mpos in cellLayer.CellRegion.MapCoords)
|
||||
cellLayer[mpos] += adjustment;
|
||||
}
|
||||
@@ -394,12 +394,15 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
FromMatrix(output, roominess);
|
||||
}
|
||||
|
||||
/// <summary>Wrapper around MatrixUtils.WalkingDistance in CPos space.</summary>
|
||||
/// <summary>
|
||||
/// Wrapper around MatrixUtils.WalkingDistance in CPos space.
|
||||
/// Returns world distances (1024ths).
|
||||
/// </summary>
|
||||
public static void WalkingDistances(
|
||||
CellLayer<float> distances,
|
||||
CellLayer<int> distances,
|
||||
CellLayer<bool> passable,
|
||||
IEnumerable<CPos> seeds,
|
||||
float maxDistance)
|
||||
int maxDistance)
|
||||
{
|
||||
var passableMatrix = ToMatrix(passable, false);
|
||||
var cellBounds = CellBounds(passable);
|
||||
@@ -441,7 +444,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// Pick a random MPos position in a CellLayer where each cell is a
|
||||
/// selection weight.
|
||||
/// </summary>
|
||||
public static MPos PickWeighted(CellLayer<float> weights, MersenneTwister random)
|
||||
public static MPos PickWeighted(CellLayer<int> weights, MersenneTwister random)
|
||||
{
|
||||
var entries = Entries(weights);
|
||||
var choice = random.PickWeighted(entries);
|
||||
|
||||
@@ -20,6 +20,8 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
{
|
||||
public static class MatrixUtils
|
||||
{
|
||||
public const int MaxBinomialKernelRadius = 10;
|
||||
|
||||
/// <summary>
|
||||
/// Debugging method that prints a matrix to stderr.
|
||||
/// </summary>
|
||||
@@ -156,22 +158,20 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Compute the in-game walking distances from a set of seeds.
|
||||
/// Compute the in-game walking distances (in 1024ths) from a set of seeds.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The output matrix cells will contain either the distance (if reachable) or
|
||||
/// PositiveInfinity.
|
||||
/// int.MaxValue.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static Matrix<float> WalkingDistances(Matrix<bool> passable, IEnumerable<int2> seeds, float maxDistance)
|
||||
public static Matrix<int> WalkingDistances(Matrix<bool> passable, IEnumerable<int2> seeds, int maxDistance)
|
||||
{
|
||||
const float SQRT2 = 1.4142135623730951f;
|
||||
const int Diagonal = 1448;
|
||||
const int Straight = 1024;
|
||||
|
||||
if (maxDistance == float.PositiveInfinity)
|
||||
maxDistance = float.MaxValue;
|
||||
|
||||
var output = new Matrix<float>(passable.Size).Fill(float.PositiveInfinity);
|
||||
var unprocessed = new PriorityArray<float>(passable.Size.X * passable.Size.Y, float.PositiveInfinity);
|
||||
var output = new Matrix<int>(passable.Size).Fill(int.MaxValue);
|
||||
var unprocessed = new PriorityArray<int>(passable.Size.X * passable.Size.Y, int.MaxValue);
|
||||
foreach (var seed in seeds)
|
||||
unprocessed[passable.Index(seed)] = 0;
|
||||
|
||||
@@ -186,7 +186,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
if (distance <= maxDistance && output.ContainsXY(xy))
|
||||
output[xy] = distance;
|
||||
unprocessed[i] = float.PositiveInfinity;
|
||||
unprocessed[i] = int.MaxValue;
|
||||
|
||||
foreach (var (offset, direction) in Direction.Spread8D)
|
||||
{
|
||||
@@ -195,13 +195,13 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
continue;
|
||||
if (!passable[nextXY])
|
||||
continue;
|
||||
if (output[nextXY] != float.PositiveInfinity)
|
||||
if (output[nextXY] != int.MaxValue)
|
||||
continue;
|
||||
float nextDistance;
|
||||
int nextDistance;
|
||||
if (Direction.IsDiagonal(direction))
|
||||
nextDistance = distance + SQRT2;
|
||||
nextDistance = distance + Diagonal;
|
||||
else
|
||||
nextDistance = distance + 1;
|
||||
nextDistance = distance + Straight;
|
||||
|
||||
var nextI = passable.Index(nextXY);
|
||||
if (nextDistance < unprocessed[nextI])
|
||||
@@ -390,83 +390,89 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Create a one-dimensional gaussian kernel.
|
||||
/// Create a one-dimensional binomial kernel of size (2 * radius + 1, 1).
|
||||
/// The total of all kernel cells is 1 << (radius * 2).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This can be applied once, transposed, then applied again to perform a full gaussian blur.
|
||||
/// See <see cref="GaussianBlur"/>.
|
||||
/// This can be applied once, transposed, then applied again to perform a full binomial blur.
|
||||
/// See <see cref="BinomialBlur"/>. Maximum supported radius is MaxBinomialKernelRadius.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static Matrix<float> GaussianKernel1D(int radius, float standardDeviation)
|
||||
static Matrix<long> BinomialKernel1D(int radius)
|
||||
{
|
||||
var span = radius * 2 + 1;
|
||||
var kernel = new Matrix<float>(new int2(span, 1));
|
||||
var dsd2 = 2 * standardDeviation * standardDeviation;
|
||||
var total = 0.0f;
|
||||
for (var x = -radius; x <= radius; x++)
|
||||
{
|
||||
var value = MathF.Exp(-x * x / dsd2);
|
||||
kernel[x + radius] = value;
|
||||
total += value;
|
||||
}
|
||||
if (radius < 0 || radius > 10)
|
||||
throw new ArgumentException($"Binomial kernel radius was not in supported range (0 to {MaxBinomialKernelRadius} inclusive).");
|
||||
|
||||
// Instead of dividing by sqrt(PI * dsd2), divide by the total.
|
||||
for (var i = 0; i < span; i++)
|
||||
kernel[i] /= total;
|
||||
var span = radius * 2 + 1;
|
||||
var kernel = new Matrix<long>(new int2(span, 1));
|
||||
var factorials = new long[span];
|
||||
factorials[0] = 1;
|
||||
for (var i = 1; i < span; i++)
|
||||
factorials[i] = factorials[i - 1] * i;
|
||||
|
||||
var n = span - 1;
|
||||
for (var k = 0; k < span; k++)
|
||||
kernel[k] = factorials[n] / (factorials[k] * factorials[n - k]);
|
||||
|
||||
return kernel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply an arithmetic convolution of a kernel over an input matrix.
|
||||
/// Cells outside the input matrix take the value of the nearest edge/corner cell.
|
||||
/// </summary>
|
||||
public static Matrix<float> KernelBlur(Matrix<float> input, Matrix<float> kernel, int2 kernelCenter)
|
||||
public static Matrix<long> KernelFilter(Matrix<long> input, Matrix<long> kernel, int2 kernelCenter)
|
||||
{
|
||||
var output = new Matrix<float>(input.Size);
|
||||
var output = new Matrix<long>(input.Size);
|
||||
for (var cy = 0; cy < input.Size.Y; cy++)
|
||||
for (var cx = 0; cx < input.Size.X; cx++)
|
||||
{
|
||||
var total = 0.0f;
|
||||
long total = 0;
|
||||
var samples = 0;
|
||||
for (var ky = 0; ky < kernel.Size.Y; ky++)
|
||||
for (var kx = 0; kx < kernel.Size.X; kx++)
|
||||
{
|
||||
var x = cx + kx - kernelCenter.X;
|
||||
var y = cy + ky - kernelCenter.Y;
|
||||
if (!input.ContainsXY(x, y))
|
||||
continue;
|
||||
total += input[x, y] * kernel[kx, ky];
|
||||
total += input[input.ClampXY(new int2(x, y))] * kernel[kx, ky];
|
||||
samples++;
|
||||
}
|
||||
|
||||
output[cx, cy] = total / samples;
|
||||
output[cx, cy] = total;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply a square gaussian blur to a matrix, returning a new matrix.
|
||||
/// Apply a binomial filter-based blur to a matrix, returning a new matrix. The result is
|
||||
/// somewhat similar to a gaussian blur. Maximum supported radius is MaxBinomialKernelRadius.
|
||||
/// </summary>
|
||||
public static Matrix<float> GaussianBlur(Matrix<float> input, int radius, float standardDeviation)
|
||||
public static Matrix<int> BinomialBlur(Matrix<int> input, int radius)
|
||||
{
|
||||
var kernel = GaussianKernel1D(radius, standardDeviation);
|
||||
var stage1 = KernelBlur(input, kernel, new int2(radius, 0));
|
||||
var stage2 = KernelBlur(stage1, kernel.Transpose(), new int2(0, radius));
|
||||
return stage2;
|
||||
var kernel = BinomialKernel1D(radius);
|
||||
var downscale = 2 * radius;
|
||||
var stage1 = KernelFilter(input.Map(v => (long)v), kernel, new int2(radius, 0));
|
||||
for (var i = 0; i < stage1.Data.Length; i++)
|
||||
stage1[i] >>= downscale;
|
||||
var stage2 = KernelFilter(stage1, kernel.Transpose(), new int2(0, radius));
|
||||
for (var i = 0; i < stage2.Data.Length; i++)
|
||||
stage2[i] >>= downscale;
|
||||
|
||||
return stage2.Map(v => (int)v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the local variance of points in a grid (using a square sample area).
|
||||
/// Sample areas are centered on data point corners, so output is (size + 1) * (size + 1).
|
||||
/// </summary>
|
||||
public static Matrix<float> GridVariance(Matrix<float> input, int radius)
|
||||
public static Matrix<int> GridVariance(Matrix<int> input, int radius)
|
||||
{
|
||||
var output = new Matrix<float>(input.Size + new int2(1, 1));
|
||||
var output = new Matrix<int>(input.Size + new int2(1, 1));
|
||||
for (var cy = 0; cy < output.Size.Y; cy++)
|
||||
for (var cx = 0; cx < output.Size.X; cx++)
|
||||
{
|
||||
var total = 0.0f;
|
||||
var total = 0;
|
||||
var samples = 0;
|
||||
for (var ry = -radius; ry < radius; ry++)
|
||||
for (var rx = -radius; rx < radius; rx++)
|
||||
@@ -480,7 +486,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
}
|
||||
|
||||
var mean = total / samples;
|
||||
var sumOfSquares = 0.0f;
|
||||
long sumOfSquares = 0;
|
||||
for (var ry = -radius; ry < radius; ry++)
|
||||
for (var rx = -radius; rx < radius; rx++)
|
||||
{
|
||||
@@ -488,10 +494,11 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
var x = cx + rx;
|
||||
if (!input.ContainsXY(x, y))
|
||||
continue;
|
||||
sumOfSquares += MathF.Pow(mean - input[x, y], 2);
|
||||
long difference = mean - input[x, y];
|
||||
sumOfSquares += difference * difference;
|
||||
}
|
||||
|
||||
output[cx, cy] = sumOfSquares / samples;
|
||||
output[cx, cy] = (int)(sumOfSquares / samples);
|
||||
}
|
||||
|
||||
return output;
|
||||
@@ -503,8 +510,9 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// if the neighborhood is significantly different based on a threshold.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For example, a threshold of 0.75 means any change requires a 75%
|
||||
/// majority within the kernel.
|
||||
/// The threshold / thresholdOutOf is the size of a majority needed to
|
||||
/// change a value. For example, a threshold of 20 / 25 means, 80% of
|
||||
/// cells must agree to change a cell's value.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The space outside of the matrix is treated as if the border was
|
||||
@@ -522,21 +530,22 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static (Matrix<bool> Output, int Changes) BooleanBlur(
|
||||
Matrix<bool> input, int radius, float threshold)
|
||||
Matrix<bool> input, int radius, int threshold, int thresholdOutOf)
|
||||
{
|
||||
if (threshold < 0.5f || threshold > 1.0f)
|
||||
throw new ArgumentException("threshold must between 0.5 and 1.0 inclusive");
|
||||
|
||||
var output = new Matrix<bool>(input.Size);
|
||||
var changes = 0;
|
||||
|
||||
// Sum radius-by-1 kernels first in O((size.X + radius) * size.Y) time using a diffing sliding
|
||||
// window, then sum 1-by-radius kernels in O(size.X * (size.Y + radius)) time.
|
||||
var hTrueCounts = new Matrix<int>(input.Size);
|
||||
var kernelArea = (2 * radius + 1) * (2 * radius + 1);
|
||||
var trueThreshold = (int)MathF.Ceiling(kernelArea * threshold);
|
||||
|
||||
if (threshold < 1 || thresholdOutOf < 1 || threshold * 2 < thresholdOutOf)
|
||||
throw new ArgumentException("invalid threshold");
|
||||
|
||||
var trueThreshold = (kernelArea * threshold + thresholdOutOf - 1) / thresholdOutOf;
|
||||
var falseThreshold = kernelArea - trueThreshold;
|
||||
|
||||
var output = new Matrix<bool>(input.Size);
|
||||
var changes = 0;
|
||||
|
||||
for (var cy = 0; cy < input.Size.Y; cy++)
|
||||
{
|
||||
var trueCount = 0;
|
||||
@@ -642,19 +651,35 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
return (output, changes);
|
||||
}
|
||||
|
||||
/// <summary>Read a linearly interpolated value between the cells of a matrix.</summary>
|
||||
public static float Interpolate(Matrix<float> matrix, float x, float y)
|
||||
/// <summary>
|
||||
/// Read a linearly interpolated value between the cells of a matrix. xWeight and yWeight
|
||||
/// must be between 0 and scale inclusive and define the interpolation position
|
||||
/// between x and x+1, and y and y+1.
|
||||
/// </summary>
|
||||
public static int IntegerInterpolate(
|
||||
Matrix<int> matrix,
|
||||
int x,
|
||||
int y,
|
||||
int xWeight,
|
||||
int yWeight,
|
||||
int scale)
|
||||
{
|
||||
var xa = (int)MathF.Floor(x);
|
||||
var xb = (int)MathF.Ceiling(x);
|
||||
var ya = (int)MathF.Floor(y);
|
||||
var yb = (int)MathF.Ceiling(y);
|
||||
var xa = x;
|
||||
var xb = x + 1;
|
||||
var ya = y;
|
||||
var yb = y + 1;
|
||||
|
||||
if (scale <= 0)
|
||||
throw new ArgumentException("Interpolation scale was not > 0");
|
||||
|
||||
if (xWeight < 0 || yWeight < 0 || xWeight > scale || yWeight > scale)
|
||||
throw new ArgumentException("Interpolation weights were not between 0 and scale inclusive.");
|
||||
|
||||
// "w" for "weight"
|
||||
var xbw = x - xa;
|
||||
var ybw = y - ya;
|
||||
var xaw = 1.0f - xbw;
|
||||
var yaw = 1.0f - ybw;
|
||||
var xbw = xWeight;
|
||||
var ybw = yWeight;
|
||||
var xaw = scale - xWeight;
|
||||
var yaw = scale - yWeight;
|
||||
|
||||
if (xa < 0)
|
||||
{
|
||||
@@ -678,46 +703,22 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
yb = matrix.Size.Y - 1;
|
||||
}
|
||||
|
||||
var naa = matrix[xa, ya];
|
||||
var nba = matrix[xb, ya];
|
||||
var nab = matrix[xa, yb];
|
||||
var nbb = matrix[xb, yb];
|
||||
return (naa * xaw + nba * xbw) * yaw + (nab * xaw + nbb * xbw) * ybw;
|
||||
long naa = matrix[xa, ya];
|
||||
long nba = matrix[xb, ya];
|
||||
long nab = matrix[xa, yb];
|
||||
long nbb = matrix[xb, yb];
|
||||
return (int)(((naa * xaw + nba * xbw) * yaw + (nab * xaw + nbb * xbw) * ybw) / scale / scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the (linearly interpolated) value a given fraction through a sorted array.
|
||||
/// Uniformally add to or subtract from all cells such that count out of every outOf cells,
|
||||
/// are no greater than the given target value.
|
||||
/// </summary>
|
||||
public static float ArrayQuantile(float[] array, float quantile)
|
||||
public static void CalibrateQuantileInPlace(Matrix<int> matrix, int target, int count, int outOf)
|
||||
{
|
||||
if (array.Length == 0)
|
||||
throw new ArgumentException("Cannot get quantile of empty array");
|
||||
|
||||
var iFloat = quantile * (array.Length - 1);
|
||||
if (iFloat < 0)
|
||||
iFloat = 0;
|
||||
|
||||
if (iFloat > array.Length - 1)
|
||||
iFloat = array.Length - 1;
|
||||
|
||||
var iLow = (int)iFloat;
|
||||
if (iLow == iFloat)
|
||||
return array[iLow];
|
||||
|
||||
var iHigh = iLow + 1;
|
||||
var weight = iFloat - iLow;
|
||||
return array[iLow] * (1 - weight) + array[iHigh] * weight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uniformally add to or subtract from all matrix cells such that the given quantile,
|
||||
/// fraction, has the given target value.
|
||||
/// </summary>
|
||||
public static void CalibrateQuantileInPlace(Matrix<float> matrix, float target, float fraction)
|
||||
{
|
||||
var sorted = (float[])matrix.Data.Clone();
|
||||
var sorted = (int[])matrix.Data.Clone();
|
||||
Array.Sort(sorted);
|
||||
var adjustment = target - ArrayQuantile(sorted, fraction);
|
||||
var adjustment = target - sorted[(sorted.Length - 1) * count / outOf];
|
||||
for (var i = 0; i < matrix.Data.Length; i++)
|
||||
matrix[i] += adjustment;
|
||||
}
|
||||
@@ -1017,14 +1018,15 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
public static Matrix<bool> BooleanBlotch(
|
||||
Matrix<bool> input,
|
||||
int terrainSmoothing,
|
||||
float smoothingThreshold,
|
||||
int smoothingThreshold,
|
||||
int smoothingThresholdOutOf,
|
||||
int minimumThickness,
|
||||
bool bias)
|
||||
{
|
||||
var maxSpan = Math.Max(input.Size.X, input.Size.Y);
|
||||
var matrix = input;
|
||||
|
||||
(matrix, _) = BooleanBlur(matrix, terrainSmoothing, 0.5f);
|
||||
(matrix, _) = BooleanBlur(matrix, terrainSmoothing, 1, 2);
|
||||
for (var i1 = 0; i1 < /*max passes*/16; i1++)
|
||||
{
|
||||
for (var i2 = 0; i2 < maxSpan; i2++)
|
||||
@@ -1033,7 +1035,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
var changesAcc = 0;
|
||||
for (var r = 1; r <= terrainSmoothing; r++)
|
||||
{
|
||||
(matrix, changes) = BooleanBlur(matrix, r, smoothingThreshold);
|
||||
(matrix, changes) = BooleanBlur(matrix, r, smoothingThreshold, smoothingThresholdOutOf);
|
||||
changesAcc += changes;
|
||||
}
|
||||
|
||||
@@ -1067,8 +1069,8 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
if (diff[x, y])
|
||||
OverCircle(
|
||||
matrix: matrix,
|
||||
center: new float2(x, y),
|
||||
radius: minimumThickness * 2,
|
||||
centerIn1024ths: new int2(x * 1024 + 512, y * 1024 + 512),
|
||||
radiusIn1024ths: minimumThickness * 2048,
|
||||
outside: false,
|
||||
action: (xy, _) => matrix[xy] = bias);
|
||||
}
|
||||
@@ -1425,22 +1427,25 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Run an action over the inside or outside of a circle of given center and radius. The
|
||||
/// action is called with the int2 position and the squared distance to the circle's
|
||||
/// center. If outside is true, the action is run for cells outside of the circle instead
|
||||
/// Run an action over the inside or outside of a circle of given center and radius,
|
||||
/// measured in 1024ths of a cell. The action is called with the int2 cell position (NOT in
|
||||
/// 1024ths), and the square of the distance-in-1024ths from the cell's center to the
|
||||
/// circle's center. (Square root and divide by 1024 to get the distance in whole cells.)
|
||||
/// (0, 0) is a corner of the matrix, and (512, 512) is the center of the first cell.
|
||||
/// If outside is true, the action is run for cells outside of the circle instead
|
||||
/// of the inside.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A matrix cell is inside the circle if its position is <= radius from center.
|
||||
/// A matrix cell is inside the circle if its center is <= radius from center.
|
||||
/// Coordinates outside of the Matrix are ignored.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static void OverCircle<T>(
|
||||
Matrix<T> matrix,
|
||||
float2 center,
|
||||
float radius,
|
||||
int2 centerIn1024ths,
|
||||
int radiusIn1024ths,
|
||||
bool outside,
|
||||
Action<int2, float> action)
|
||||
Action<int2, long> action)
|
||||
{
|
||||
var size = matrix.Size;
|
||||
int minX;
|
||||
@@ -1456,10 +1461,10 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
}
|
||||
else
|
||||
{
|
||||
minX = (int)MathF.Floor(center.X - radius);
|
||||
minY = (int)MathF.Floor(center.Y - radius);
|
||||
maxX = (int)MathF.Ceiling(center.X + radius);
|
||||
maxY = (int)MathF.Ceiling(center.Y + radius);
|
||||
minX = (centerIn1024ths.X - radiusIn1024ths) / 1024;
|
||||
minY = (centerIn1024ths.Y - radiusIn1024ths) / 1024;
|
||||
maxX = (centerIn1024ths.X + radiusIn1024ths + 1023) / 1024;
|
||||
maxY = (centerIn1024ths.Y + radiusIn1024ths + 1023) / 1024;
|
||||
if (minX < 0)
|
||||
minX = 0;
|
||||
if (minY < 0)
|
||||
@@ -1470,16 +1475,32 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
maxY = size.Y - 1;
|
||||
}
|
||||
|
||||
var radiusSquared = radius * radius;
|
||||
var radiusSquared = (long)radiusIn1024ths * radiusIn1024ths;
|
||||
for (var y = minY; y <= maxY; y++)
|
||||
for (var x = minX; x <= maxX; x++)
|
||||
{
|
||||
var rx = x - center.X;
|
||||
var ry = y - center.Y;
|
||||
var thisRadiusSquared = rx * rx + ry * ry;
|
||||
var rx = x * 1024 + 512 - centerIn1024ths.X;
|
||||
var ry = y * 1024 + 512 - centerIn1024ths.Y;
|
||||
var thisRadiusSquared = (long)rx * rx + (long)ry * ry;
|
||||
if (thisRadiusSquared <= radiusSquared != outside)
|
||||
action(new int2(x, y), thisRadiusSquared);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Linearly scales the range of values in a matrix to the given target amplitude.
|
||||
/// Returns the modified input. If the input matrix is all zeros, it is left unmodified.
|
||||
/// </summary>
|
||||
public static Matrix<int> NormalizeRangeInPlace(Matrix<int> matrix, int targetAmplitude)
|
||||
{
|
||||
long inputAmplitude = matrix.Data.Max(Math.Abs);
|
||||
if (inputAmplitude == 0)
|
||||
return matrix;
|
||||
|
||||
for (var i = 0; i < matrix.Data.Length; i++)
|
||||
matrix[i] = (int)((long)matrix[i] * targetAmplitude / inputAmplitude);
|
||||
|
||||
return matrix;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// </summary>
|
||||
public sealed class MultiBrushInfo
|
||||
{
|
||||
public readonly float Weight;
|
||||
public readonly int Weight;
|
||||
public readonly ImmutableArray<string> Actors;
|
||||
public readonly TerrainTile? BackingTile;
|
||||
public readonly ImmutableArray<ushort> Templates;
|
||||
@@ -33,7 +33,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
// Currently doesn't support specifying offsets. Add this capability if/when needed.
|
||||
public MultiBrushInfo(MiniYaml my)
|
||||
{
|
||||
Weight = 1.0f;
|
||||
Weight = MultiBrush.DefaultWeight;
|
||||
var actors = new List<string>();
|
||||
var templates = new List<ushort>();
|
||||
var tiles = new List<TerrainTile>();
|
||||
@@ -41,7 +41,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
switch (node.Key.Split('@')[0])
|
||||
{
|
||||
case "Weight":
|
||||
if (!Exts.TryParseFloatOrPercentInvariant(node.Value.Value, out Weight))
|
||||
if (!Exts.TryParseInt32Invariant(node.Value.Value, out Weight))
|
||||
throw new YamlException($"Invalid MultiBrush Weight `${node.Value.Value}`");
|
||||
break;
|
||||
case "Actor":
|
||||
@@ -89,6 +89,8 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// <summary>A super template that can be used to paint both tiles and actors.</summary>
|
||||
sealed class MultiBrush
|
||||
{
|
||||
public const int DefaultWeight = 1000;
|
||||
|
||||
public enum Replaceability
|
||||
{
|
||||
/// <summary>Area cannot be replaced by a tile or obstructing actor.</summary>
|
||||
@@ -104,7 +106,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
Any = 3,
|
||||
}
|
||||
|
||||
public float Weight;
|
||||
public int Weight;
|
||||
readonly List<(CVec, TerrainTile)> tiles;
|
||||
readonly List<ActorPlan> actorPlans;
|
||||
CVec[] shape;
|
||||
@@ -134,7 +136,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// </summary>
|
||||
public MultiBrush()
|
||||
{
|
||||
Weight = 1.0f;
|
||||
Weight = DefaultWeight;
|
||||
tiles = new List<(CVec, TerrainTile)>();
|
||||
actorPlans = new List<ActorPlan>();
|
||||
shape = Array.Empty<CVec>();
|
||||
@@ -264,10 +266,10 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
}
|
||||
|
||||
/// <summary>Update the weight.</summary>
|
||||
public MultiBrush WithWeight(float weight)
|
||||
public MultiBrush WithWeight(int weight)
|
||||
{
|
||||
if (!(weight > 0.0f))
|
||||
throw new ArgumentException("Weight was not > 0.0");
|
||||
if (weight <= 0)
|
||||
throw new ArgumentException("Weight was not > 0");
|
||||
Weight = weight;
|
||||
return this;
|
||||
}
|
||||
@@ -438,7 +440,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
var remainingQuota =
|
||||
brushArea == 1
|
||||
? int.MaxValue
|
||||
: (int)Math.Ceiling(replaceMposes.Count * brushWeightForArea / brushTotalWeight);
|
||||
: (int)(((long)replaceMposes.Count * brushWeightForArea + brushTotalWeight - 1) / brushTotalWeight);
|
||||
RefreshIndices();
|
||||
foreach (var mpos in mposes)
|
||||
{
|
||||
|
||||
@@ -10,21 +10,25 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenRA.Support;
|
||||
|
||||
namespace OpenRA.Mods.Common.MapGenerator
|
||||
{
|
||||
public static class NoiseUtils
|
||||
{
|
||||
const int Scale = 1024;
|
||||
const int ScaledSqrt2 = 1448;
|
||||
|
||||
/// <summary>Amplitude proportional to wavelength.</summary>
|
||||
public static float PinkAmplitude(float wavelength) => wavelength;
|
||||
public static int PinkAmplitude(int wavelength) => wavelength;
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Create noise by combining multiple layers of Perlin noise of halving wavelengths.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// wavelengthScale defines the largest wavelength as a fraction of the largest dimension of
|
||||
/// featureSize defines the largest wavelength in 1024ths of a matrix cell.
|
||||
/// the output.
|
||||
/// </para>
|
||||
/// <para>
|
||||
@@ -32,38 +36,45 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// choice.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static Matrix<float> FractalNoise(
|
||||
public static Matrix<int> FractalNoise(
|
||||
MersenneTwister random,
|
||||
int2 size,
|
||||
float featureSize,
|
||||
Func<float, float> ampFunc)
|
||||
int featureSize,
|
||||
Func<int, int> ampFunc)
|
||||
{
|
||||
var span = Math.Max(size.X, size.Y);
|
||||
var wavelengths = new float[(int)Math.Log2(span)];
|
||||
var wavelengths = new int[BitOperations.Log2((uint)span)];
|
||||
for (var i = 0; i < wavelengths.Length; i++)
|
||||
wavelengths[i] = featureSize / (1 << i);
|
||||
wavelengths[i] = featureSize >> i;
|
||||
|
||||
var noise = new Matrix<float>(size);
|
||||
var noise = new Matrix<int>(size);
|
||||
foreach (var wavelength in wavelengths)
|
||||
{
|
||||
if (wavelength <= 0.5)
|
||||
if (wavelength <= Scale / 2)
|
||||
break;
|
||||
|
||||
var amps = ampFunc(wavelength);
|
||||
var subSpan = (int)(span / wavelength) + 2;
|
||||
var subSpan = span * Scale / wavelength + 2;
|
||||
var subNoise = PerlinNoise(random, subSpan);
|
||||
|
||||
// Offsets should align to grid.
|
||||
// (The wavelength is divided back out later.)
|
||||
var offsetX = (int)(random.NextFloat() * wavelength);
|
||||
var offsetY = (int)(random.NextFloat() * wavelength);
|
||||
var scaledOffsetX = (int)(random.NextUint() % (wavelength + 1));
|
||||
var scaledOffsetY = (int)(random.NextUint() % (wavelength + 1));
|
||||
for (var y = 0; y < size.Y; y++)
|
||||
for (var x = 0; x < size.X; x++)
|
||||
{
|
||||
var scaledMappedX = x * Scale + scaledOffsetX;
|
||||
var scaledMappedY = y * Scale + scaledOffsetY;
|
||||
noise[y * size.X + x] +=
|
||||
amps * MatrixUtils.Interpolate(
|
||||
amps * MatrixUtils.IntegerInterpolate(
|
||||
subNoise,
|
||||
(offsetX + x) / wavelength,
|
||||
(offsetY + y) / wavelength);
|
||||
scaledMappedX / wavelength,
|
||||
scaledMappedY / wavelength,
|
||||
scaledMappedX % wavelength,
|
||||
scaledMappedY % wavelength,
|
||||
wavelength);
|
||||
}
|
||||
}
|
||||
|
||||
return noise;
|
||||
@@ -71,25 +82,25 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
/// <summary>
|
||||
/// 2D Perlin Noise generator without interpolation, producing a span-by-span sized matrix.
|
||||
/// Output values range from -5792 to +5792.
|
||||
/// </summary>
|
||||
public static Matrix<float> PerlinNoise(MersenneTwister random, int span)
|
||||
public static Matrix<int> PerlinNoise(MersenneTwister random, int span)
|
||||
{
|
||||
var noise = new Matrix<float>(span, span);
|
||||
const float D = 0.25f;
|
||||
var noise = new Matrix<int>(span, span);
|
||||
for (var y = 0; y <= span; y++)
|
||||
for (var x = 0; x <= span; x++)
|
||||
{
|
||||
var phase = MathF.Tau * random.NextFloatExclusive();
|
||||
var vx = MathF.Cos(phase);
|
||||
var vy = MathF.Sin(phase);
|
||||
var phase = new WAngle((int)random.NextUint() % 1024);
|
||||
var vx = phase.Cos();
|
||||
var vy = phase.Sin();
|
||||
if (x > 0 && y > 0)
|
||||
noise[x - 1, y - 1] += vx * -D + vy * -D;
|
||||
noise[x - 1, y - 1] += -vx + -vy;
|
||||
if (x < span && y > 0)
|
||||
noise[x, y - 1] += vx * D + vy * -D;
|
||||
noise[x, y - 1] += vx + -vy;
|
||||
if (x > 0 && y < span)
|
||||
noise[x - 1, y] += vx * -D + vy * D;
|
||||
noise[x - 1, y] += -vx + vy;
|
||||
if (x < span && y < span)
|
||||
noise[x, y] += vx * D + vy * D;
|
||||
noise[x, y] += vx + vy;
|
||||
}
|
||||
|
||||
return noise;
|
||||
@@ -105,13 +116,13 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// noise with different properties to simple Perlin noise.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static Matrix<float> SymmetricFractalNoise(
|
||||
public static Matrix<int> SymmetricFractalNoise(
|
||||
MersenneTwister random,
|
||||
int2 size,
|
||||
int rotations,
|
||||
Symmetry.Mirror mirror,
|
||||
float featureSize,
|
||||
Func<float, float> ampFunc)
|
||||
int featureSize,
|
||||
Func<int, int> ampFunc)
|
||||
{
|
||||
if (rotations < 1)
|
||||
throw new ArgumentException("rotations must be >= 1");
|
||||
@@ -119,32 +130,37 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
// Need higher resolution due to cropping and rotation artifacts
|
||||
var templateSpan = Math.Max(size.X, size.Y) * 2 + 2;
|
||||
var templateSize = new int2(templateSpan, templateSpan);
|
||||
var templateCenter = new float2(templateSpan - 1, templateSpan - 1) / 2.0f;
|
||||
var scaledTemplateCenter = new int2(templateSpan - 1, templateSpan - 1) * Scale / 2;
|
||||
var template = FractalNoise(random, templateSize, featureSize, ampFunc);
|
||||
|
||||
var output = new Matrix<float>(size);
|
||||
var output = new Matrix<int>(size);
|
||||
|
||||
var inclusiveOutputSize = size - new int2(1, 1);
|
||||
var outputMid = new float2(inclusiveOutputSize) / 2.0f;
|
||||
var scaledOutputMid = inclusiveOutputSize * Scale / 2;
|
||||
|
||||
for (var y = 0; y < size.Y; y++)
|
||||
for (var x = 0; x < size.X; x++)
|
||||
{
|
||||
const float Sqrt2 = 1.4142135623730951f;
|
||||
var outputXy = new float2(x, y);
|
||||
var outputXyFromCenter = outputXy - outputMid;
|
||||
var templateXyFromCenter = outputXyFromCenter * Sqrt2;
|
||||
var templateXy = templateXyFromCenter + templateCenter;
|
||||
var outputXy = new int2(x, y);
|
||||
var scaledOutputXy = outputXy * Scale;
|
||||
var scaledOutputXyFromCenter = scaledOutputXy - scaledOutputMid;
|
||||
|
||||
// Apply sqrt2 scaling so that diagonal samples don't alias.
|
||||
var scaledTemplateXyFromCenter = scaledOutputXyFromCenter * ScaledSqrt2 / Scale;
|
||||
var scaledTemplateXy = scaledTemplateXyFromCenter + scaledTemplateCenter;
|
||||
|
||||
var projections = Symmetry.RotateAndMirrorPointAround(
|
||||
templateXy, templateCenter, rotations, mirror);
|
||||
scaledTemplateXy, scaledTemplateCenter, rotations, mirror);
|
||||
|
||||
foreach (var projection in projections)
|
||||
output[x, y] +=
|
||||
MatrixUtils.Interpolate(
|
||||
MatrixUtils.IntegerInterpolate(
|
||||
template,
|
||||
projection.X,
|
||||
projection.Y);
|
||||
projection.X / Scale,
|
||||
projection.Y / Scale,
|
||||
projection.X % Scale,
|
||||
projection.Y % Scale,
|
||||
Scale);
|
||||
}
|
||||
|
||||
return output;
|
||||
@@ -156,11 +172,11 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// </summary>
|
||||
public static void SymmetricFractalNoiseIntoCellLayer(
|
||||
MersenneTwister random,
|
||||
CellLayer<float> cellLayer,
|
||||
CellLayer<int> cellLayer,
|
||||
int rotations,
|
||||
Symmetry.Mirror mirror,
|
||||
float featureSize,
|
||||
Func<float, float> ampFunc)
|
||||
int featureSize,
|
||||
Func<int, int> ampFunc)
|
||||
{
|
||||
var cellBounds = CellLayerUtils.CellBounds(cellLayer);
|
||||
var size = new int2(cellBounds.Size.Width, cellBounds.Size.Height);
|
||||
|
||||
@@ -44,26 +44,26 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// Mirrors a (zero-area) point around a given center.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For example, if using a center of (4.0, 4.0) a point at (0.1, 0.1) could be projected
|
||||
/// to (0.1, 0.1), (0.1, 7.9), (7.9, 0.1), or (7.9, 7.9).
|
||||
/// For example, if using a center of (40, 40) a point at (1, 1) could be projected
|
||||
/// to (1, 1), (1, 79), (79, 1), or (79, 79).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static float2 MirrorPointAround(Mirror mirror, float2 original, float2 center)
|
||||
public static int2 MirrorPointAround(Mirror mirror, int2 original, int2 center)
|
||||
{
|
||||
switch (mirror)
|
||||
{
|
||||
case Mirror.None:
|
||||
throw new ArgumentException("Mirror.None has no transformed point");
|
||||
case Mirror.LeftMatchesRight:
|
||||
return new float2(2.0f * center.X - original.X, original.Y);
|
||||
return new int2(2 * center.X - original.X, original.Y);
|
||||
case Mirror.TopLeftMatchesBottomRight:
|
||||
return new float2(
|
||||
return new int2(
|
||||
center.Y - original.Y + center.X,
|
||||
center.X - original.X + center.Y);
|
||||
case Mirror.TopMatchesBottom:
|
||||
return new float2(original.X, 2.0f * center.Y - original.Y);
|
||||
return new int2(original.X, 2 * center.Y - original.Y);
|
||||
case Mirror.TopRightMatchesBottomLeft:
|
||||
return new float2(
|
||||
return new int2(
|
||||
center.X + original.Y - center.Y,
|
||||
center.Y + original.X - center.X);
|
||||
default:
|
||||
@@ -73,27 +73,11 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
|
||||
public static WPos MirrorWPosAround(Mirror mirror, WPos original, WPos center)
|
||||
{
|
||||
switch (mirror)
|
||||
{
|
||||
case Mirror.None:
|
||||
throw new ArgumentException("Mirror.None has no transformed point");
|
||||
case Mirror.LeftMatchesRight:
|
||||
return new WPos(2 * center.X - original.X, original.Y, original.Z);
|
||||
case Mirror.TopLeftMatchesBottomRight:
|
||||
return new WPos(
|
||||
center.Y - original.Y + center.X,
|
||||
center.X - original.X + center.Y,
|
||||
original.Z);
|
||||
case Mirror.TopMatchesBottom:
|
||||
return new WPos(original.X, 2 * center.Y - original.Y, original.Z);
|
||||
case Mirror.TopRightMatchesBottomLeft:
|
||||
return new WPos(
|
||||
center.X + original.Y - center.Y,
|
||||
center.Y + original.X - center.X,
|
||||
original.Z);
|
||||
default:
|
||||
throw new ArgumentException("Bad mirror");
|
||||
}
|
||||
var result = MirrorPointAround(
|
||||
mirror,
|
||||
new int2(original.X, original.Y),
|
||||
new int2(center.X, center.Y));
|
||||
return new WPos(result.X, result.Y, original.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -199,17 +183,17 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
/// functions. This may be slightly imprecise for non-trivial rotations.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For example, if using a center of (4.0, 4.0) a point at (0.1, 0.1) could be projected
|
||||
/// to (0.1, 0.1), (0.1, 7.9), (7.9, 0.1), and (7.9, 7.9).
|
||||
/// For example, if using a center of (40, 40) a point at (1, 1) could be projected
|
||||
/// to (1, 1), (1, 79), (79, 1), and (79, 79).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static float2[] RotateAndMirrorPointAround(
|
||||
float2 original,
|
||||
float2 center,
|
||||
public static int2[] RotateAndMirrorPointAround(
|
||||
int2 original,
|
||||
int2 center,
|
||||
int rotations,
|
||||
Mirror mirror)
|
||||
{
|
||||
var projections = new float2[RotateAndMirrorProjectionCount(rotations, mirror)];
|
||||
var projections = new int2[RotateAndMirrorProjectionCount(rotations, mirror)];
|
||||
var projectionIndex = 0;
|
||||
|
||||
for (var rotation = 0; rotation < rotations; rotation++)
|
||||
@@ -217,12 +201,12 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
// This could be made more accurate using dedicated, higher precision
|
||||
// rotation count to cos and sin lookup tables.
|
||||
var wangle = new WAngle(rotation * 1024 / rotations);
|
||||
var cos = wangle.Cos() / 1024.0f;
|
||||
var sin = wangle.Sin() / 1024.0f;
|
||||
long cos = wangle.Cos();
|
||||
long sin = wangle.Sin();
|
||||
var relOrig = original - center;
|
||||
var projX = relOrig.X * cos - relOrig.Y * sin + center.X;
|
||||
var projY = relOrig.X * sin + relOrig.Y * cos + center.Y;
|
||||
var projection = new float2(projX, projY);
|
||||
var projX = (relOrig.X * cos - relOrig.Y * sin) / 1024 + center.X;
|
||||
var projY = (relOrig.X * sin + relOrig.Y * cos) / 1024 + center.Y;
|
||||
var projection = new int2((int)projX, (int)projY);
|
||||
projections[projectionIndex++] = projection;
|
||||
|
||||
if (mirror != Mirror.None)
|
||||
@@ -303,7 +287,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
public static bool IsCPosNearCenter<T>(
|
||||
CPos cpos,
|
||||
CellLayer<T> cellLayer,
|
||||
float reservationRadius,
|
||||
int reservationRadius,
|
||||
Mirror mirror)
|
||||
{
|
||||
CPos[] testPoints;
|
||||
@@ -313,7 +297,7 @@ namespace OpenRA.Mods.Common.MapGenerator
|
||||
testPoints = RotateAndMirrorCPos(cpos, cellLayer, 1, mirror);
|
||||
|
||||
var separation = (testPoints[1] - testPoints[0]).LengthSquared;
|
||||
return separation <= reservationRadius * reservationRadius * 4.0f;
|
||||
return separation <= reservationRadius * reservationRadius * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,9 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public sealed class RaMapGenerator : IMapGenerator
|
||||
{
|
||||
const int FractionMax = 1000;
|
||||
const int EntityBonusMax = 1000000;
|
||||
|
||||
sealed class Parameters
|
||||
{
|
||||
[FieldLoader.Ignore]
|
||||
@@ -74,17 +77,17 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly int Players = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float TerrainFeatureSize = default;
|
||||
public readonly int TerrainFeatureSize = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float ForestFeatureSize = default;
|
||||
public readonly int ForestFeatureSize = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float ResourceFeatureSize = default;
|
||||
public readonly int ResourceFeatureSize = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float Water = default;
|
||||
public readonly int Water = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float Mountains = default;
|
||||
public readonly int Mountains = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float Forests = default;
|
||||
public readonly int Forests = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly int ForestCutout = default;
|
||||
[FieldLoader.Require]
|
||||
@@ -94,7 +97,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly int TerrainSmoothing = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float SmoothingThreshold = default;
|
||||
public readonly int SmoothingThreshold = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly int MinimumLandSeaThickness = default;
|
||||
[FieldLoader.Require]
|
||||
@@ -104,13 +107,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly int RoughnessRadius = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float Roughness = default;
|
||||
public readonly int Roughness = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly int MinimumTerrainContourSpacing = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly int MinimumCliffLength = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float ForestClumpiness = default;
|
||||
public readonly int ForestClumpiness = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly bool DenyWalledAreas = default;
|
||||
[FieldLoader.Require]
|
||||
@@ -124,11 +127,11 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly bool CreateEntities = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float AreaEntityBonus = default;
|
||||
public readonly int AreaEntityBonus = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float PlayerCountEntityBonus = default;
|
||||
public readonly int PlayerCountEntityBonus = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float CentralSpawnReservationFraction = default;
|
||||
public readonly int CentralSpawnReservationFraction = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly int ResourceSpawnReservation = default;
|
||||
[FieldLoader.Require]
|
||||
@@ -140,13 +143,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly int SpawnReservation = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float SpawnResourceBias = default;
|
||||
public readonly int SpawnResourceBias = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly int ResourcesPerPlayer = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float OreUniformity = default;
|
||||
public readonly int OreUniformity = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly float OreClumpiness = default;
|
||||
public readonly int OreClumpiness = default;
|
||||
[FieldLoader.Require]
|
||||
public readonly int MaximumExpansionResourceSpawns = default;
|
||||
[FieldLoader.Require]
|
||||
@@ -164,7 +167,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Require]
|
||||
public readonly int MaximumBuildings = default;
|
||||
[FieldLoader.LoadUsing(nameof(BuildingWeightsLoader))]
|
||||
public readonly IReadOnlyDictionary<string, float> BuildingWeights = default;
|
||||
public readonly IReadOnlyDictionary<string, int> BuildingWeights = default;
|
||||
|
||||
[FieldLoader.Require]
|
||||
public readonly ushort LandTile = default;
|
||||
@@ -188,7 +191,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlyDictionary<string, ResourceTypeInfo> ResourceSpawnSeeds;
|
||||
[FieldLoader.LoadUsing(nameof(ResourceSpawnWeightsLoader))]
|
||||
public readonly IReadOnlyDictionary<string, float> ResourceSpawnWeights = default;
|
||||
public readonly IReadOnlyDictionary<string, int> ResourceSpawnWeights = default;
|
||||
|
||||
[FieldLoader.Ignore]
|
||||
public readonly IReadOnlySet<byte> ClearTerrain;
|
||||
@@ -311,22 +314,22 @@ namespace OpenRA.Mods.Common.Traits
|
||||
throw new YamlException($"Invalid Mirror value `{my.NodeWithKey("Mirror").Value.Value}`");
|
||||
}
|
||||
|
||||
static IReadOnlyDictionary<string, float> BuildingWeightsLoader(MiniYaml my)
|
||||
static IReadOnlyDictionary<string, int> BuildingWeightsLoader(MiniYaml my)
|
||||
{
|
||||
return my.NodeWithKey("BuildingWeights").Value.ToDictionary(subMy =>
|
||||
{
|
||||
if (Exts.TryParseFloatOrPercentInvariant(subMy.Value, out var f))
|
||||
if (Exts.TryParseInt32Invariant(subMy.Value, out var f))
|
||||
return f;
|
||||
else
|
||||
throw new YamlException($"Invalid building weight `{subMy.Value}`");
|
||||
});
|
||||
}
|
||||
|
||||
static IReadOnlyDictionary<string, float> ResourceSpawnWeightsLoader(MiniYaml my)
|
||||
static IReadOnlyDictionary<string, int> ResourceSpawnWeightsLoader(MiniYaml my)
|
||||
{
|
||||
return my.NodeWithKey("ResourceSpawnWeights").Value.ToDictionary(subMy =>
|
||||
{
|
||||
if (Exts.TryParseFloatOrPercentInvariant(subMy.Value, out var f))
|
||||
if (Exts.TryParseInt32Invariant(subMy.Value, out var f))
|
||||
return f;
|
||||
else
|
||||
throw new YamlException($"Invalid resource spawn weight `{subMy.Value}`");
|
||||
@@ -337,34 +340,34 @@ namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
if (Rotations < 1)
|
||||
throw new MapGenerationException("Rotations must be >= 1");
|
||||
if (TerrainFeatureSize < 1.0f)
|
||||
throw new MapGenerationException("TerrainFeatureSize must be >= 1.0");
|
||||
if (ForestFeatureSize < 1.0f)
|
||||
throw new MapGenerationException("ForestFeatureSize must be >= 1.0");
|
||||
if (ResourceFeatureSize < 1.0f)
|
||||
throw new MapGenerationException("ResourceFeatureSize must be >= 1.0");
|
||||
if (TerrainSmoothing < 1)
|
||||
throw new MapGenerationException("TerrainSmoothing must be < 1");
|
||||
if (SmoothingThreshold < 0.5f || SmoothingThreshold > 1.0f)
|
||||
throw new MapGenerationException("SmoothingThreshold must be between 0.5 and 1.0 inclusive");
|
||||
if (TerrainFeatureSize < 1)
|
||||
throw new MapGenerationException("TerrainFeatureSize must be >= 1");
|
||||
if (ForestFeatureSize < 1)
|
||||
throw new MapGenerationException("ForestFeatureSize must be >= 1");
|
||||
if (ResourceFeatureSize < 1)
|
||||
throw new MapGenerationException("ResourceFeatureSize must be >= 1");
|
||||
if (TerrainSmoothing < 0 || TerrainSmoothing > MatrixUtils.MaxBinomialKernelRadius)
|
||||
throw new MapGenerationException($"TerrainSmoothing must be between 0 and {MatrixUtils.MaxBinomialKernelRadius} inclusive");
|
||||
if (SmoothingThreshold < (FractionMax + 1) / 2 || SmoothingThreshold > FractionMax)
|
||||
throw new MapGenerationException($"SmoothingThreshold must be between {(FractionMax + 1) / 2} and {FractionMax} inclusive");
|
||||
if (MinimumLandSeaThickness < 1)
|
||||
throw new MapGenerationException("MinimumLandSeaThickness must be >= 1");
|
||||
if (MinimumMountainThickness < 1)
|
||||
throw new MapGenerationException("MinimumMountainThickness must be >= 1");
|
||||
if (Water < 0.0f || Water > 1.0f)
|
||||
throw new MapGenerationException("Water must be between 0.0 and 1.0 inclusive");
|
||||
if (Forests < 0.0f || Forests > 1.0f)
|
||||
throw new MapGenerationException("Forest must be between 0.0 and 1.0 inclusive");
|
||||
if (Water < 0 || Water > FractionMax)
|
||||
throw new MapGenerationException($"Water must be between 0 and {FractionMax} inclusive");
|
||||
if (Forests < 0 || Forests > FractionMax)
|
||||
throw new MapGenerationException($"Forest must be between 0 and {FractionMax} inclusive");
|
||||
if (ForestCutout < 0)
|
||||
throw new MapGenerationException("ForestCutout must be >= 0");
|
||||
if (MaximumCutoutSpacing < 0)
|
||||
throw new MapGenerationException("TopologyAugmentationThreshold must be >= 0");
|
||||
if (ForestClumpiness < 0.0f)
|
||||
throw new MapGenerationException("ForestClumpiness must be >= 0.0");
|
||||
if (Mountains < 0.0f || Mountains > 1.0f)
|
||||
throw new MapGenerationException("Mountains must be between 0.0 and 1.0 inclusive");
|
||||
if (Roughness < 0.0f || Roughness > 1.0f)
|
||||
throw new MapGenerationException("Roughness must be between 0.0 and 1.0");
|
||||
if (ForestClumpiness < 0)
|
||||
throw new MapGenerationException("ForestClumpiness must be >= 0");
|
||||
if (Mountains < 0 || Mountains > FractionMax)
|
||||
throw new MapGenerationException($"Mountains must be between 0 and {FractionMax} inclusive");
|
||||
if (Roughness < 0 || Roughness > FractionMax)
|
||||
throw new MapGenerationException("Roughness must be between 0 and {FractionMax}");
|
||||
if (RoughnessRadius < 1)
|
||||
throw new MapGenerationException("RoughnessRadius must be >= 1");
|
||||
if (MaximumAltitude < 0)
|
||||
@@ -379,12 +382,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
throw new MapGenerationException("RoadShrink must be >= 0");
|
||||
if (Players < 0)
|
||||
throw new MapGenerationException("Players must be >= 0");
|
||||
if (CentralSpawnReservationFraction < 0.0f)
|
||||
throw new MapGenerationException("CentralSpawnReservationFraction must be >= 0.0");
|
||||
if (AreaEntityBonus < 0.0f)
|
||||
throw new MapGenerationException("PlayableAreaDensityBonus must be >= 0.0");
|
||||
if (PlayerCountEntityBonus < 0.0f)
|
||||
throw new MapGenerationException("PlayerCountDensityBonus must be >= 0.0");
|
||||
if (CentralSpawnReservationFraction < 0)
|
||||
throw new MapGenerationException("CentralSpawnReservationFraction must be >= 0");
|
||||
if (AreaEntityBonus < 0)
|
||||
throw new MapGenerationException("PlayableAreaDensityBonus must be >= 0");
|
||||
if (PlayerCountEntityBonus < 0)
|
||||
throw new MapGenerationException("PlayerCountDensityBonus must be >= 0");
|
||||
if (SpawnRegionSize < 1)
|
||||
throw new MapGenerationException("SpawnRegionSize must be >= 1");
|
||||
if (SpawnReservation < 1)
|
||||
@@ -417,16 +420,16 @@ namespace OpenRA.Mods.Common.Traits
|
||||
throw new MapGenerationException("MinimumBuildings must be <= maximumBuildings");
|
||||
if (ResourcesPerPlayer < 0)
|
||||
throw new MapGenerationException("ResourcesPerPlayer must be >= 0");
|
||||
if (OreUniformity < 0.0f)
|
||||
throw new MapGenerationException("OreUniformity must be >= 0.0");
|
||||
if (OreClumpiness < 0.0f)
|
||||
throw new MapGenerationException("OreClumpiness must be >= 0.0");
|
||||
if (OreUniformity < 0)
|
||||
throw new MapGenerationException("OreUniformity must be >= 0");
|
||||
if (OreClumpiness < 0)
|
||||
throw new MapGenerationException("OreClumpiness must be >= 0");
|
||||
foreach (var kv in BuildingWeights)
|
||||
if (kv.Value < 0.0f)
|
||||
throw new MapGenerationException("BuildingWeights.* must be >= 0.0");
|
||||
if (kv.Value < 0)
|
||||
throw new MapGenerationException("BuildingWeights.* must be >= 0");
|
||||
foreach (var kv in ResourceSpawnWeights)
|
||||
if (kv.Value < 0.0f)
|
||||
throw new MapGenerationException("ResourceSpawnWeights.* must be >= 0.0");
|
||||
if (kv.Value < 0)
|
||||
throw new MapGenerationException("ResourceSpawnWeights.* must be >= 0");
|
||||
foreach (var kv in ResourceSpawnWeights)
|
||||
if (!ResourceSpawnSeeds.ContainsKey(kv.Key))
|
||||
throw new MapGenerationException($"ResourceSpawnSeeds does not contain possible resource spawn `{kv.Key}`");
|
||||
@@ -440,7 +443,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
throw new MapGenerationException("Total number of players must not exceed 32");
|
||||
}
|
||||
|
||||
public static (T[] Types, float[] Weights) SplitWeights<T>(IReadOnlyDictionary<T, float> typeWeights)
|
||||
public static (T[] Types, int[] Weights) SplitWeights<T>(IReadOnlyDictionary<T, int> typeWeights)
|
||||
{
|
||||
var types = typeWeights
|
||||
.Select(kv => kv.Key)
|
||||
@@ -469,14 +472,14 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
public void Generate(Map map, MiniYaml settings)
|
||||
{
|
||||
const float ExternalBias = 1000000.0f;
|
||||
const int ExternalBias = 4096;
|
||||
|
||||
var size = map.MapSize;
|
||||
var minSpan = Math.Min(size.X, size.Y);
|
||||
var maxSpan = Math.Max(size.X, size.Y);
|
||||
var mapCenter = (size.ToFloat2() - new float2(1.0f, 1.0f)) / 2.0f;
|
||||
var mapCenter1024ths = size * 512;
|
||||
var wMapCenter = CellLayerUtils.Center(map.Tiles);
|
||||
var matrixMapCenter = (CellLayerUtils.CellBounds(map).Size.ToInt2().ToFloat2() - new float2(1.0f, 1.0f)) / 2.0f;
|
||||
var matrixMapCenter1024ths = CellLayerUtils.CellBounds(map).Size.ToInt2() * 512;
|
||||
var cellBounds = CellLayerUtils.CellBounds(map);
|
||||
var minCSpan = Math.Min(cellBounds.Size.Width, cellBounds.Size.Height);
|
||||
var gridType = map.Grid.Type;
|
||||
@@ -596,32 +599,33 @@ namespace OpenRA.Mods.Common.Traits
|
||||
param.Mirror,
|
||||
param.TerrainFeatureSize,
|
||||
NoiseUtils.PinkAmplitude);
|
||||
MatrixUtils.NormalizeRangeInPlace(elevation, 1024);
|
||||
|
||||
if (param.TerrainSmoothing > 0)
|
||||
{
|
||||
var radius = param.TerrainSmoothing;
|
||||
elevation = MatrixUtils.GaussianBlur(elevation, radius, radius);
|
||||
elevation = MatrixUtils.BinomialBlur(elevation, radius);
|
||||
}
|
||||
|
||||
MatrixUtils.CalibrateQuantileInPlace(
|
||||
elevation,
|
||||
0.0f,
|
||||
param.Water);
|
||||
0,
|
||||
param.Water, FractionMax);
|
||||
|
||||
if (param.ExternalCircularBias != 0)
|
||||
MatrixUtils.OverCircle(
|
||||
matrix: elevation,
|
||||
center: mapCenter,
|
||||
radius: externalCircleRadius,
|
||||
centerIn1024ths: mapCenter1024ths,
|
||||
radiusIn1024ths: externalCircleRadius * 1024,
|
||||
outside: true,
|
||||
action: (xy, _) => elevation[xy] = param.ExternalCircularBias * ExternalBias);
|
||||
|
||||
var landPlan = MatrixUtils.BooleanBlotch(
|
||||
elevation.Map(v => v >= 0),
|
||||
param.TerrainSmoothing,
|
||||
param.SmoothingThreshold,
|
||||
param.SmoothingThreshold, FractionMax,
|
||||
param.MinimumLandSeaThickness,
|
||||
/*bias=*/param.Water < 0.5);
|
||||
/*bias=*/param.Water < FractionMax / 2);
|
||||
|
||||
var beaches = CellLayerUtils.FromMatrixPoints(
|
||||
MatrixUtils.BordersToPoints(landPlan),
|
||||
@@ -712,24 +716,23 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
if (param.Mountains > 0.0f || param.ExternalCircularBias == 1)
|
||||
if (param.Mountains > 0 || param.ExternalCircularBias == 1)
|
||||
{
|
||||
var roughnessMatrix = MatrixUtils.GridVariance(
|
||||
elevation,
|
||||
param.RoughnessRadius)
|
||||
.Map(v => MathF.Sqrt(v));
|
||||
param.RoughnessRadius);
|
||||
MatrixUtils.CalibrateQuantileInPlace(
|
||||
roughnessMatrix,
|
||||
0.0f,
|
||||
1.0f - param.Roughness);
|
||||
var cliffMask = roughnessMatrix.Map(v => v >= 0.0f);
|
||||
0,
|
||||
FractionMax - param.Roughness, FractionMax);
|
||||
var cliffMask = roughnessMatrix.Map(v => v >= 0);
|
||||
var mountainElevation = elevation.Clone();
|
||||
var cliffPlan = landPlan;
|
||||
if (param.ExternalCircularBias > 0)
|
||||
MatrixUtils.OverCircle(
|
||||
matrix: cliffPlan,
|
||||
center: matrixMapCenter,
|
||||
radius: externalCircleRadius,
|
||||
centerIn1024ths: matrixMapCenter1024ths,
|
||||
radiusIn1024ths: externalCircleRadius * 1024,
|
||||
outside: true,
|
||||
action: (xy, _) => cliffPlan[xy] = false);
|
||||
|
||||
@@ -742,22 +745,21 @@ namespace OpenRA.Mods.Common.Traits
|
||||
for (var n = 0; n < mountainElevation.Data.Length; n++)
|
||||
{
|
||||
if (roominess.Data[n] < param.MinimumTerrainContourSpacing)
|
||||
mountainElevation.Data[n] = -1.0f;
|
||||
mountainElevation.Data[n] = -1;
|
||||
else
|
||||
available++;
|
||||
|
||||
total++;
|
||||
}
|
||||
|
||||
var availableFraction = (float)available / total;
|
||||
MatrixUtils.CalibrateQuantileInPlace(
|
||||
mountainElevation,
|
||||
0.0f,
|
||||
1.0f - availableFraction * param.Mountains);
|
||||
0,
|
||||
total - available * param.Mountains / FractionMax, total);
|
||||
cliffPlan = MatrixUtils.BooleanBlotch(
|
||||
mountainElevation.Map(v => v >= 0),
|
||||
param.TerrainSmoothing,
|
||||
param.SmoothingThreshold,
|
||||
param.SmoothingThreshold, FractionMax,
|
||||
param.MinimumMountainThickness,
|
||||
/*bias=*/false);
|
||||
var unmaskedCliffs = MatrixUtils.BordersToPoints(cliffPlan);
|
||||
@@ -795,24 +797,24 @@ namespace OpenRA.Mods.Common.Traits
|
||||
}
|
||||
}
|
||||
|
||||
if (param.Forests > 0.0f)
|
||||
if (param.Forests > 0)
|
||||
{
|
||||
var forestNoise = new CellLayer<float>(map);
|
||||
var forestNoise = new CellLayer<int>(map);
|
||||
NoiseUtils.SymmetricFractalNoiseIntoCellLayer(
|
||||
forestRandom,
|
||||
forestNoise,
|
||||
param.Rotations,
|
||||
param.Mirror,
|
||||
param.ForestFeatureSize,
|
||||
wavelength => MathF.Pow(wavelength, param.ForestClumpiness));
|
||||
wavelength => ClumpinessAmplitude(wavelength, param.ForestClumpiness));
|
||||
CellLayerUtils.CalibrateQuantileInPlace(
|
||||
forestNoise,
|
||||
0.0f,
|
||||
1.0f - param.Forests);
|
||||
0,
|
||||
FractionMax - param.Forests, FractionMax);
|
||||
|
||||
var forestPlan = new CellLayer<bool>(map);
|
||||
foreach (var mpos in map.AllCells.MapCoords)
|
||||
if (param.ClearTerrain.Contains(map.GetTerrainIndex(mpos)) && forestNoise[mpos] >= 0.0f)
|
||||
if (param.ClearTerrain.Contains(map.GetTerrainIndex(mpos)) && forestNoise[mpos] >= 0)
|
||||
forestPlan[mpos] = true;
|
||||
|
||||
if (param.ForestCutout > 0)
|
||||
@@ -1081,8 +1083,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var kernel = new Matrix<bool>(param.RoadSpacing * 2 + 1, param.RoadSpacing * 2 + 1);
|
||||
MatrixUtils.OverCircle(
|
||||
matrix: kernel,
|
||||
center: new float2(param.RoadSpacing, param.RoadSpacing),
|
||||
radius: param.RoadSpacing,
|
||||
centerIn1024ths: kernel.Size * 512,
|
||||
radiusIn1024ths: param.RoadSpacing * 1024,
|
||||
outside: false,
|
||||
action: (xy, _) => kernel[xy] = true);
|
||||
var dilated = MatrixUtils.KernelDilateOrErode(
|
||||
@@ -1165,7 +1167,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
(projections, cpos) =>
|
||||
projectionSpacing[cpos] = Symmetry.ProjectionProximity(projections) / 2);
|
||||
|
||||
var spawnReservationRadius = minSpan * param.CentralSpawnReservationFraction;
|
||||
var spawnReservationRadius = minSpan * param.CentralSpawnReservationFraction / FractionMax;
|
||||
var spawnReservation = new CellLayer<bool>(map);
|
||||
foreach (var mpos in map.AllCells.MapCoords)
|
||||
spawnReservation[mpos] = Symmetry.IsCPosNearCenter(
|
||||
@@ -1209,8 +1211,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
var zoneableArea = zoneable.Count(v => v);
|
||||
var entityMultiplier =
|
||||
zoneableArea * param.AreaEntityBonus +
|
||||
param.TotalPlayers * param.PlayerCountEntityBonus;
|
||||
(long)zoneableArea * param.AreaEntityBonus +
|
||||
(long)param.TotalPlayers * param.PlayerCountEntityBonus;
|
||||
var perSymmetryEntityMultiplier = entityMultiplier / param.SymmetryCount;
|
||||
|
||||
// Spawn generation
|
||||
@@ -1242,18 +1244,18 @@ namespace OpenRA.Mods.Common.Traits
|
||||
Location = chosenMPos.ToCPos(gridType),
|
||||
};
|
||||
|
||||
var preferedRange = (param.SpawnBuildSize + param.SpawnRegionSize * 2) / 2;
|
||||
var resourceSpawnPreferences = new CellLayer<float>(map);
|
||||
var preferedRange1024ths = (param.SpawnBuildSize + param.SpawnRegionSize * 2) * 512;
|
||||
var resourceSpawnPreferences = new CellLayer<int>(map);
|
||||
CellLayerUtils.WalkingDistances(
|
||||
resourceSpawnPreferences,
|
||||
zoneable,
|
||||
new[] { chosenMPos.ToCPos(gridType) },
|
||||
param.SpawnRegionSize);
|
||||
param.SpawnRegionSize * 1024);
|
||||
foreach (var mpos in map.AllCells.MapCoords)
|
||||
{
|
||||
var v = resourceSpawnPreferences[mpos];
|
||||
resourceSpawnPreferences[mpos] = MathF.Ceiling(
|
||||
v > preferedRange ? 2 * preferedRange - v : v);
|
||||
resourceSpawnPreferences[mpos] =
|
||||
((v > preferedRange1024ths ? 2 * preferedRange1024ths - v : v) + 1023) / 1024;
|
||||
}
|
||||
|
||||
var resourceSpawns = new List<ActorPlan>();
|
||||
@@ -1263,7 +1265,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
resourceSpawnPreferences,
|
||||
playerRandom,
|
||||
(a, b) => a.CompareTo(b));
|
||||
if (value <= 1.0f)
|
||||
if (value <= 1)
|
||||
break;
|
||||
|
||||
var resourceSpawnType = resourceSpawnTypes[playerRandom.PickWeighted(resourceSpawnWeights)];
|
||||
@@ -1278,7 +1280,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
wCenter: resourceSpawnPlan.WPosLocation,
|
||||
wRadius: 1024,
|
||||
outside: false,
|
||||
action: (mpos, _, _, _) => resourceSpawnPreferences[mpos] = 0.0f);
|
||||
action: (mpos, _, _, _) => resourceSpawnPreferences[mpos] = 0);
|
||||
}
|
||||
|
||||
var projectedSpawns = Symmetry.RotateAndMirrorActorPlan(spawn, param.Rotations, param.Mirror);
|
||||
@@ -1304,7 +1306,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
// Expansions
|
||||
{
|
||||
var resourceSpawnsRemaining = (int)(param.MaximumExpansionResourceSpawns * perSymmetryEntityMultiplier);
|
||||
var resourceSpawnsRemaining = (int)(param.MaximumExpansionResourceSpawns * perSymmetryEntityMultiplier / EntityBonusMax);
|
||||
while (resourceSpawnsRemaining > 0)
|
||||
{
|
||||
var roominess = new CellLayer<int>(map);
|
||||
@@ -1327,20 +1329,23 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var resourceSpawnCount = Math.Min(resourceSpawnsRemaining, expansionRandom.Next(param.MaximumResourceSpawnsPerExpansion) + 1);
|
||||
resourceSpawnsRemaining -= resourceSpawnCount;
|
||||
|
||||
if (radius1 < 1.0f)
|
||||
if (radius1 < 1)
|
||||
break;
|
||||
|
||||
var resourceSpawns = new List<ActorPlan>();
|
||||
var resourceSpawnPreferences = new CellLayer<float>(map);
|
||||
var wRadius1Sq = (long)radius1 * radius1 * 1024 * 1024;
|
||||
var resourceSpawnPreferences = new CellLayer<int>(map);
|
||||
var radius1Sq = radius1 * radius1;
|
||||
CellLayerUtils.OverCircle(
|
||||
cellLayer: resourceSpawnPreferences,
|
||||
wCenter: CellLayerUtils.MPosToWPos(chosenMPos, gridType),
|
||||
wRadius: radius2 * 1024,
|
||||
outside: false,
|
||||
action: (mpos, _, _, rSq) =>
|
||||
action: (mpos, _, _, wrSq) =>
|
||||
{
|
||||
var rSq = (int)(wrSq / (1024 * 1024));
|
||||
resourceSpawnPreferences[mpos] =
|
||||
rSq >= wRadius1Sq ? rSq : 0.0f);
|
||||
rSq >= radius1Sq ? rSq : 0;
|
||||
});
|
||||
for (var resourceSpawn = 0; resourceSpawn < resourceSpawnCount; resourceSpawn++)
|
||||
{
|
||||
var mpos = CellLayerUtils.PickWeighted(resourceSpawnPreferences, expansionRandom);
|
||||
@@ -1356,7 +1361,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
wCenter: resourceSpawnPlan.WPosLocation,
|
||||
wRadius: 1024,
|
||||
outside: false,
|
||||
action: (mpos, _, _, _) => resourceSpawnPreferences[mpos] = 0.0f);
|
||||
action: (mpos, _, _, _) => resourceSpawnPreferences[mpos] = 0);
|
||||
}
|
||||
|
||||
var projectedResourceSpawns = Symmetry.RotateAndMirrorActorPlans(resourceSpawns, param.Rotations, param.Mirror);
|
||||
@@ -1376,8 +1381,8 @@ namespace OpenRA.Mods.Common.Traits
|
||||
var targetBuildingCount =
|
||||
(param.MaximumBuildings != 0)
|
||||
? expansionRandom.Next(
|
||||
(int)(param.MinimumBuildings * perSymmetryEntityMultiplier),
|
||||
(int)(param.MaximumBuildings * perSymmetryEntityMultiplier) + 1)
|
||||
(int)(param.MinimumBuildings * perSymmetryEntityMultiplier / EntityBonusMax),
|
||||
(int)(param.MaximumBuildings * perSymmetryEntityMultiplier / EntityBonusMax) + 1)
|
||||
: 0;
|
||||
for (var i = 0; i < targetBuildingCount; i++)
|
||||
{
|
||||
@@ -1415,128 +1420,126 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
// Grow resources
|
||||
{
|
||||
var pattern = new CellLayer<float>(map);
|
||||
var pattern1024ths = new CellLayer<int>(map);
|
||||
NoiseUtils.SymmetricFractalNoiseIntoCellLayer(
|
||||
resourceRandom,
|
||||
pattern,
|
||||
pattern1024ths,
|
||||
param.Rotations,
|
||||
param.Mirror,
|
||||
param.ResourceFeatureSize,
|
||||
wavelength => MathF.Pow(wavelength, param.OreClumpiness));
|
||||
wavelength => ClumpinessAmplitude(wavelength, param.OreClumpiness));
|
||||
{
|
||||
CellLayerUtils.CalibrateQuantileInPlace(
|
||||
pattern,
|
||||
0.0f,
|
||||
0.0f);
|
||||
var max = pattern.Max();
|
||||
pattern1024ths,
|
||||
0,
|
||||
0, 1);
|
||||
var max1024ths = pattern1024ths.Max();
|
||||
var uniformity1024ths = param.OreUniformity * 1024 / FractionMax;
|
||||
foreach (var mpos in map.AllCells.MapCoords)
|
||||
{
|
||||
pattern[mpos] /= max;
|
||||
pattern[mpos] += param.OreUniformity;
|
||||
}
|
||||
pattern1024ths[mpos] = uniformity1024ths + 1024 * pattern1024ths[mpos] / max1024ths;
|
||||
}
|
||||
|
||||
var strengths = new Dictionary<ResourceTypeInfo, CellLayer<float>>();
|
||||
var strengths1024ths = new Dictionary<ResourceTypeInfo, CellLayer<int>>();
|
||||
foreach (var actorPlan in actorPlans)
|
||||
{
|
||||
var type = actorPlan.Reference.Type;
|
||||
if (param.ResourceSpawnWeights.ContainsKey(type))
|
||||
{
|
||||
var resource = param.ResourceSpawnSeeds[type];
|
||||
if (!strengths.TryGetValue(resource, out var strength))
|
||||
if (!strengths1024ths.TryGetValue(resource, out var strength1024ths))
|
||||
{
|
||||
strength = new CellLayer<float>(map);
|
||||
strengths.Add(resource, strength);
|
||||
strength1024ths = new CellLayer<int>(map);
|
||||
strengths1024ths.Add(resource, strength1024ths);
|
||||
}
|
||||
|
||||
CellLayerUtils.OverCircle(
|
||||
cellLayer: strength,
|
||||
cellLayer: strength1024ths,
|
||||
wCenter: actorPlan.WPosLocation,
|
||||
wRadius: 16 * 1024,
|
||||
outside: false,
|
||||
action: (mpos, _, _, rSq) =>
|
||||
strength[mpos] +=
|
||||
1024.0f / (1024.0f + MathF.Sqrt(rSq)));
|
||||
action: (mpos, _, _, wrSq) =>
|
||||
strength1024ths[mpos] +=
|
||||
(int)(1024 * 1024 / (1024 + Exts.ISqrt(wrSq))));
|
||||
}
|
||||
}
|
||||
|
||||
var maxStrength = new CellLayer<float>(map);
|
||||
var maxStrength1024ths = new CellLayer<int>(map);
|
||||
var bestResource = new CellLayer<ResourceTypeInfo>(map);
|
||||
bestResource.Clear(param.DefaultResource);
|
||||
foreach (var resourceStrength in strengths)
|
||||
foreach (var resourceStrength in strengths1024ths)
|
||||
{
|
||||
var resource = resourceStrength.Key;
|
||||
var strength = resourceStrength.Value;
|
||||
var strength1024ths = resourceStrength.Value;
|
||||
foreach (var mpos in map.AllCells.MapCoords)
|
||||
if (strength[mpos] > maxStrength[mpos])
|
||||
if (strength1024ths[mpos] > maxStrength1024ths[mpos])
|
||||
{
|
||||
maxStrength[mpos] = strength[mpos];
|
||||
maxStrength1024ths[mpos] = strength1024ths[mpos];
|
||||
bestResource[mpos] = resource;
|
||||
}
|
||||
}
|
||||
|
||||
// Closer to +inf means "more preferable" for plan.
|
||||
var plan = new CellLayer<float>(map);
|
||||
var plan1024ths = new CellLayer<int>(map);
|
||||
foreach (var mpos in map.AllCells.MapCoords)
|
||||
if (playableArea[mpos] && param.AllowedTerrainResourceCombos.Contains((bestResource[mpos], map.GetTerrainIndex(mpos))))
|
||||
plan[mpos] = pattern[mpos] * maxStrength[mpos];
|
||||
plan1024ths[mpos] = pattern1024ths[mpos] * maxStrength1024ths[mpos] / 1024;
|
||||
else
|
||||
plan[mpos] = float.NegativeInfinity;
|
||||
plan1024ths[mpos] = -int.MaxValue;
|
||||
|
||||
var wSpawnBuildSizeSq = (long)param.SpawnBuildSize * param.SpawnBuildSize * 1024 * 1024;
|
||||
foreach (var actorPlan in actorPlans)
|
||||
if (actorPlan.Reference.Type == "mpspawn")
|
||||
CellLayerUtils.OverCircle(
|
||||
cellLayer: plan,
|
||||
cellLayer: plan1024ths,
|
||||
wCenter: actorPlan.WPosLocation,
|
||||
wRadius: param.SpawnRegionSize * 2 * 1024,
|
||||
outside: false,
|
||||
action: (mpos, _, _, rSq) =>
|
||||
plan[mpos] *= 1.0f + param.SpawnResourceBias * wSpawnBuildSizeSq / rSq);
|
||||
plan1024ths[mpos] += (int)(plan1024ths[mpos] * param.SpawnResourceBias * wSpawnBuildSizeSq / Math.Max(rSq, 1024 * 1024) / FractionMax));
|
||||
|
||||
foreach (var actorPlan in actorPlans)
|
||||
if (actorPlan.Reference.Type == "mpspawn")
|
||||
CellLayerUtils.OverCircle(
|
||||
cellLayer: plan,
|
||||
cellLayer: plan1024ths,
|
||||
wCenter: actorPlan.WPosLocation,
|
||||
wRadius: param.SpawnBuildSize * 1024,
|
||||
outside: false,
|
||||
action: (mpos, _, _, _) => plan[mpos] = float.NegativeInfinity);
|
||||
action: (mpos, _, _, _) => plan1024ths[mpos] = -int.MaxValue);
|
||||
|
||||
foreach (var actorPlan in actorPlans)
|
||||
foreach (var (cpos, _) in actorPlan.Footprint())
|
||||
if (plan.Contains(cpos))
|
||||
plan[cpos] = float.NegativeInfinity;
|
||||
if (plan1024ths.Contains(cpos))
|
||||
plan1024ths[cpos] = -int.MaxValue;
|
||||
|
||||
// Improve symmetry.
|
||||
{
|
||||
var newPlan = new CellLayer<float>(map);
|
||||
var newPlan = new CellLayer<int>(map);
|
||||
Symmetry.RotateAndMirrorOverCPos(
|
||||
plan,
|
||||
plan1024ths,
|
||||
param.Rotations,
|
||||
param.Mirror,
|
||||
(sources, destination)
|
||||
=> newPlan[destination] =
|
||||
sources.Min(source => plan.TryGetValue(source, out var value) ? value : float.NegativeInfinity));
|
||||
plan = newPlan;
|
||||
sources.Min(source => plan1024ths.TryGetValue(source, out var value) ? value : -int.MaxValue));
|
||||
plan1024ths = newPlan;
|
||||
}
|
||||
|
||||
var remaining = param.ResourcesPerPlayer * entityMultiplier;
|
||||
var remaining = param.ResourcesPerPlayer * entityMultiplier / EntityBonusMax;
|
||||
|
||||
// Closer to -inf means "more preferable" for priorities.
|
||||
var priorities = new PriorityArray<float>(
|
||||
plan.Size.Width * plan.Size.Height,
|
||||
float.PositiveInfinity);
|
||||
var priorities = new PriorityArray<int>(
|
||||
plan1024ths.Size.Width * plan1024ths.Size.Height,
|
||||
int.MaxValue);
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var v in plan)
|
||||
foreach (var v in plan1024ths)
|
||||
priorities[i++] = -v;
|
||||
}
|
||||
|
||||
int PriorityIndex(MPos mpos) => mpos.V * plan.Size.Width + mpos.U;
|
||||
int PriorityIndex(MPos mpos) => mpos.V * plan1024ths.Size.Width + mpos.U;
|
||||
MPos PriorityMPos(int index)
|
||||
{
|
||||
var v = Math.DivRem(index, plan.Size.Width, out var u);
|
||||
var v = Math.DivRem(index, plan1024ths.Size.Width, out var u);
|
||||
return new MPos(u, v);
|
||||
}
|
||||
|
||||
@@ -1584,7 +1587,7 @@ namespace OpenRA.Mods.Common.Traits
|
||||
int AddResource(CPos cpos)
|
||||
{
|
||||
var mpos = cpos.ToMPos(gridType);
|
||||
priorities[PriorityIndex(mpos)] = float.PositiveInfinity;
|
||||
priorities[PriorityIndex(mpos)] = int.MaxValue;
|
||||
|
||||
// Generally shouldn't happen, but perhaps a rotation/mirror related inaccuracy.
|
||||
if (map.Resources[mpos].Type != 0)
|
||||
@@ -1602,12 +1605,12 @@ namespace OpenRA.Mods.Common.Traits
|
||||
while (remaining > 0)
|
||||
{
|
||||
var n = priorities.GetMinIndex();
|
||||
if (priorities[n] == float.PositiveInfinity)
|
||||
if (priorities[n] == int.MaxValue)
|
||||
break;
|
||||
|
||||
var chosenMPos = PriorityMPos(n);
|
||||
var chosenCPos = chosenMPos.ToCPos(gridType);
|
||||
foreach (var cpos in Symmetry.RotateAndMirrorCPos(chosenCPos, plan, param.Rotations, param.Mirror))
|
||||
foreach (var cpos in Symmetry.RotateAndMirrorCPos(chosenCPos, plan1024ths, param.Rotations, param.Mirror))
|
||||
if (map.Resources.Contains(cpos))
|
||||
remaining -= AddResource(cpos);
|
||||
}
|
||||
@@ -1657,5 +1660,13 @@ namespace OpenRA.Mods.Common.Traits
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static int ClumpinessAmplitude(int wavelength, int clumpiness)
|
||||
{
|
||||
var amplitude = wavelength;
|
||||
for (var i = 0; i < clumpiness; i++)
|
||||
amplitude = Exts.ISqrt(amplitude);
|
||||
return amplitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user