Add experimental RA procedural map generator
Add an experimental procedural map generator for the Red Alert mod, along with supporting code that may assist in the development of map generators for other mods. Map generation may be accessed as a tool in the Map Editor. This change does not presently introduce direct lobby options for generated maps. Features: - Terrain with land, water, beaches, cliffs, roads, debris, and trees. - Placement of mpspawns, neutral buildings, and resources. - Rotational and mirror symmetry options. - Various configurable parameters with presets. - Deterministic with configurable seed. - Performant.
This commit is contained in:
@@ -510,6 +510,11 @@ namespace OpenRA
|
||||
return byte.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static ushort ParseUshortInvariant(string s)
|
||||
{
|
||||
return ushort.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static short ParseInt16Invariant(string s)
|
||||
{
|
||||
return short.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
@@ -520,6 +525,22 @@ namespace OpenRA
|
||||
return int.Parse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static float ParseFloatOrPercentInvariant(string s)
|
||||
{
|
||||
var f = float.Parse(s.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo);
|
||||
return f * (s.Contains('%') ? 0.01f : 1f);
|
||||
}
|
||||
|
||||
public static bool TryParseByteInvariant(string s, out byte i)
|
||||
{
|
||||
return byte.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
}
|
||||
|
||||
public static bool TryParseUshortInvariant(string s, out ushort i)
|
||||
{
|
||||
return ushort.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
}
|
||||
|
||||
public static bool TryParseInt32Invariant(string s, out int i)
|
||||
{
|
||||
return int.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
@@ -530,6 +551,17 @@ namespace OpenRA
|
||||
return long.TryParse(s, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out i);
|
||||
}
|
||||
|
||||
public static bool TryParseFloatOrPercentInvariant(string s, out float f)
|
||||
{
|
||||
if (float.TryParse(s.Replace("%", ""), NumberStyles.Float, NumberFormatInfo.InvariantInfo, out f))
|
||||
{
|
||||
f *= s.Contains('%') ? 0.01f : 1f;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this byte i)
|
||||
{
|
||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||
@@ -545,6 +577,11 @@ namespace OpenRA
|
||||
return i.ToString(NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this float f)
|
||||
{
|
||||
return f.ToString(NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
public static string ToStringInvariant(this int i, string format)
|
||||
{
|
||||
return i.ToString(format, NumberFormatInfo.InvariantInfo);
|
||||
|
||||
@@ -63,12 +63,16 @@ namespace OpenRA
|
||||
|
||||
var u = (x - y) / 2;
|
||||
var v = x + y;
|
||||
if (!Bounds.Contains(u, v))
|
||||
throw new IndexOutOfRangeException();
|
||||
return v * Size.Width + u;
|
||||
}
|
||||
|
||||
// Resolve an array index from map coordinates
|
||||
int Index(MPos uv)
|
||||
{
|
||||
if (!Bounds.Contains(uv.U, uv.V))
|
||||
throw new IndexOutOfRangeException();
|
||||
return uv.V * Size.Width + uv.U;
|
||||
}
|
||||
|
||||
@@ -145,6 +149,9 @@ namespace OpenRA
|
||||
{
|
||||
return uv.Clamp(new Rectangle(0, 0, Size.Width - 1, Size.Height - 1));
|
||||
}
|
||||
|
||||
public CellRegion CellRegion =>
|
||||
new(GridType, new MPos(0, 0), new MPos(Size.Width - 1, Size.Height - 1));
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
@@ -1145,6 +1145,11 @@ namespace OpenRA
|
||||
}
|
||||
|
||||
public byte GetTerrainIndex(CPos cell)
|
||||
{
|
||||
return GetTerrainIndex(cell.ToMPos(this));
|
||||
}
|
||||
|
||||
public byte GetTerrainIndex(MPos uv)
|
||||
{
|
||||
// Lazily initialize a cache for terrain indexes.
|
||||
if (cachedTerrainIndexes == null)
|
||||
@@ -1153,7 +1158,6 @@ namespace OpenRA
|
||||
cachedTerrainIndexes.Clear(InvalidCachedTerrainIndex);
|
||||
}
|
||||
|
||||
var uv = cell.ToMPos(this);
|
||||
var terrainIndex = cachedTerrainIndexes[uv];
|
||||
|
||||
// PERF: Cache terrain indexes per cell on demand.
|
||||
@@ -1168,7 +1172,12 @@ namespace OpenRA
|
||||
|
||||
public TerrainTypeInfo GetTerrainInfo(CPos cell)
|
||||
{
|
||||
return Rules.TerrainInfo.TerrainTypes[GetTerrainIndex(cell)];
|
||||
return GetTerrainInfo(cell.ToMPos(this));
|
||||
}
|
||||
|
||||
public TerrainTypeInfo GetTerrainInfo(MPos uv)
|
||||
{
|
||||
return Rules.TerrainInfo.TerrainTypes[GetTerrainIndex(uv)];
|
||||
}
|
||||
|
||||
public CPos Clamp(CPos cell)
|
||||
|
||||
@@ -25,6 +25,21 @@ namespace OpenRA
|
||||
public override int GetHashCode() { return Type.GetHashCode() ^ Index.GetHashCode(); }
|
||||
|
||||
public override string ToString() { return Type + "," + Index; }
|
||||
|
||||
public static bool TryParse(string s, out TerrainTile tt)
|
||||
{
|
||||
var split = s.Split(',');
|
||||
if (split.Length == 2 &&
|
||||
Exts.TryParseUshortInvariant(split[0], out var type) &&
|
||||
Exts.TryParseByteInvariant(split[1], out var index))
|
||||
{
|
||||
tt = new TerrainTile(type, index);
|
||||
return true;
|
||||
}
|
||||
|
||||
tt = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResourceTile
|
||||
|
||||
130
OpenRA.Game/Primitives/PriorityArray.cs
Normal file
130
OpenRA.Game/Primitives/PriorityArray.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
|
||||
namespace OpenRA.Primitives
|
||||
{
|
||||
/// <summary>
|
||||
/// A random-access array which keeps track of the minimum item.
|
||||
/// </summary>
|
||||
public sealed class PriorityArray<T> where T : IComparable<T>
|
||||
{
|
||||
readonly T[] items;
|
||||
|
||||
readonly int[] itemIndexToHeapIndex;
|
||||
readonly int[] heapOfItemIndices;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new PriorityArray of given size with all values preset to the given init value.
|
||||
/// </summary>
|
||||
public PriorityArray(int size, T init)
|
||||
{
|
||||
items = new T[size];
|
||||
itemIndexToHeapIndex = new int[size];
|
||||
heapOfItemIndices = new int[size];
|
||||
Array.Fill(items, init);
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
items[i] = init;
|
||||
itemIndexToHeapIndex[i] = i;
|
||||
heapOfItemIndices[i] = i;
|
||||
}
|
||||
}
|
||||
|
||||
public int Length => items.Length;
|
||||
|
||||
/// <summary>Get the index of the minimum element.</summary>
|
||||
public int GetMinIndex() => heapOfItemIndices[0];
|
||||
|
||||
public T this[int itemIndex]
|
||||
{
|
||||
get => items[itemIndex];
|
||||
set
|
||||
{
|
||||
items[itemIndex] = value;
|
||||
Bubble(itemIndexToHeapIndex[itemIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
void Bubble(int heapIndex)
|
||||
{
|
||||
if (!BubbleUp(heapIndex))
|
||||
{
|
||||
BubbleDown(heapIndex);
|
||||
}
|
||||
}
|
||||
|
||||
bool BubbleUp(int heapIndex)
|
||||
{
|
||||
if (heapIndex == 0)
|
||||
return false;
|
||||
var itemIndex = heapOfItemIndices[heapIndex];
|
||||
var upHeapIndex = ((heapIndex + 1) >> 1) - 1;
|
||||
var upItemIndex = heapOfItemIndices[upHeapIndex];
|
||||
if (items[itemIndex].CompareTo(items[upItemIndex]) >= 0)
|
||||
return false;
|
||||
Swap(heapIndex, upHeapIndex, itemIndex, upItemIndex);
|
||||
BubbleUp(upHeapIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BubbleDown(int heapIndex)
|
||||
{
|
||||
var itemIndex = heapOfItemIndices[heapIndex];
|
||||
var leftDownHeapIndex = ((heapIndex + 1) << 1) - 1;
|
||||
var rightDownHeapIndex = leftDownHeapIndex + 1;
|
||||
if (leftDownHeapIndex >= Length)
|
||||
return false;
|
||||
var item = items[itemIndex];
|
||||
if (rightDownHeapIndex < Length)
|
||||
{
|
||||
var leftDownItemIndex = heapOfItemIndices[leftDownHeapIndex];
|
||||
var rightDownItemIndex = heapOfItemIndices[rightDownHeapIndex];
|
||||
var leftDownItem = items[leftDownItemIndex];
|
||||
var rightDownItem = items[rightDownItemIndex];
|
||||
if (item.CompareTo(leftDownItem) <= 0 && item.CompareTo(rightDownItem) <= 0)
|
||||
return false;
|
||||
if (leftDownItem.CompareTo(rightDownItem) <= 0)
|
||||
{
|
||||
Swap(heapIndex, leftDownHeapIndex, itemIndex, leftDownItemIndex);
|
||||
BubbleDown(leftDownHeapIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
Swap(heapIndex, rightDownHeapIndex, itemIndex, rightDownItemIndex);
|
||||
BubbleDown(rightDownHeapIndex);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Just the left down one exists.
|
||||
var leftDownItemIndex = heapOfItemIndices[leftDownHeapIndex];
|
||||
var leftDownItem = items[leftDownItemIndex];
|
||||
if (item.CompareTo(leftDownItem) <= 0)
|
||||
return false;
|
||||
Swap(heapIndex, leftDownHeapIndex, itemIndex, leftDownItemIndex);
|
||||
BubbleDown(leftDownHeapIndex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Swap(int heapIndex1, int heapIndex2, int itemIndex1, int itemIndex2)
|
||||
{
|
||||
(itemIndexToHeapIndex[itemIndex1], itemIndexToHeapIndex[itemIndex2]) =
|
||||
(itemIndexToHeapIndex[itemIndex2], itemIndexToHeapIndex[itemIndex1]);
|
||||
(heapOfItemIndices[heapIndex1], heapOfItemIndices[heapIndex2]) =
|
||||
(heapOfItemIndices[heapIndex2], heapOfItemIndices[heapIndex1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,16 @@ namespace OpenRA.Primitives
|
||||
|
||||
public bool IsEmpty => Width == 0 && Height == 0;
|
||||
|
||||
public int2 ToInt2()
|
||||
{
|
||||
return new(Width, Height);
|
||||
}
|
||||
|
||||
public static Size FromInt2(int2 xy)
|
||||
{
|
||||
return new(xy.X, xy.Y);
|
||||
}
|
||||
|
||||
public bool Equals(Size other)
|
||||
{
|
||||
return this == other;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Support
|
||||
{
|
||||
@@ -32,7 +34,10 @@ namespace OpenRA.Support
|
||||
mt[i] = 1812433253u * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i;
|
||||
}
|
||||
|
||||
public int Next()
|
||||
/// <summary>
|
||||
/// Produces an unsigned integer between -0x80000000 and 0x7fffffff inclusive.
|
||||
/// </summary>
|
||||
public uint NextUint()
|
||||
{
|
||||
if (index == 0) Generate();
|
||||
|
||||
@@ -45,6 +50,24 @@ namespace OpenRA.Support
|
||||
index = (index + 1) % 624;
|
||||
TotalCount++;
|
||||
Last = (int)(y % int.MaxValue);
|
||||
return y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces an unsigned integer between -0x80000000 and 0x7fffffff inclusive.
|
||||
/// </summary>
|
||||
public ulong NextUlong()
|
||||
{
|
||||
return (ulong)NextUint() << 32 | NextUint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces signed integers between -0x7fffffff and 0x7fffffff inclusive.
|
||||
/// 0 is twice as likely as any other number.
|
||||
/// </summary>
|
||||
public int Next()
|
||||
{
|
||||
NextUint();
|
||||
return Last;
|
||||
}
|
||||
|
||||
@@ -65,11 +88,73 @@ namespace OpenRA.Support
|
||||
return Next(0, high);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces random 32-bit floats between 0 inclusive and 1 inclusive.
|
||||
/// Note that whilst floats are 32-bit (23-bit mantissa), the entropy is not. Lower numbers preserve more entropy.
|
||||
/// </summary>
|
||||
public float NextFloat()
|
||||
{
|
||||
return Math.Abs(Next() / (float)0x7fffffff);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces uniformally distributed random floats between 0 inclusive and 1 exclusive.
|
||||
/// Note that whilst floats are 32-bit (23-bit mantissa), each output contains exactly 23 bits of entropy.
|
||||
/// </summary>
|
||||
public float NextFloatExclusive()
|
||||
{
|
||||
return (NextUint() & 0x7fffff) / (float)0x800000;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Produces uniformally distributed random doubles between 0 inclusive and 1 exclusive.
|
||||
/// Note that whilst doubles are 64-bit (52-bit mantissa), each output contains exactly 52 bits of entropy.
|
||||
/// </summary>
|
||||
public double NextDoubleExclusive()
|
||||
{
|
||||
return (NextUlong() & 0xfffffffffffffL) / (double)0x10000000000000L;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick a random an index from a list of weights.
|
||||
/// </summary>
|
||||
public int PickWeighted(IReadOnlyList<float> weights)
|
||||
{
|
||||
var total = weights.Sum();
|
||||
var spin = NextFloatExclusive() * total;
|
||||
int i;
|
||||
float acc = 0;
|
||||
for (i = 0; i < weights.Count; i++)
|
||||
{
|
||||
acc += weights[i];
|
||||
if (spin < acc)
|
||||
return i;
|
||||
}
|
||||
|
||||
// This might be possible due to floating point precision loss
|
||||
// (in rare cases). Or we might have been given rubbish
|
||||
// weights. Return anything > 0.
|
||||
for (i = 0; i < weights.Count; i++)
|
||||
if (weights[i] > 0)
|
||||
return i;
|
||||
|
||||
// All <= 0!
|
||||
return Next(0, weights.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuffle a portion of a list list in place. Has minor biases.
|
||||
/// </summary>
|
||||
public void ShuffleInPlace<T>(IList<T> list, int start, int len)
|
||||
{
|
||||
for (var i = len; i > 1; i--)
|
||||
{
|
||||
var swap = Next(i);
|
||||
(list[start + i - 1], list[start + swap]) =
|
||||
(list[start + swap], list[start + i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
void Generate()
|
||||
{
|
||||
unchecked
|
||||
|
||||
Reference in New Issue
Block a user