diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs
index 9d38df8d5c3..d54293f5683 100644
--- a/Content.Server/Parallax/BiomeSystem.cs
+++ b/Content.Server/Parallax/BiomeSystem.cs
@@ -417,7 +417,7 @@ private void LoadChunks(
var startNodeY = rand.Next(lower, upper + 1);
var startNode = new Vector2i(startNodeX, startNodeY);
frontier.Clear();
- frontier.Add(startNode);
+ frontier.Add(startNode + chunk);
while (groupCount > 0 && frontier.Count > 0)
{
@@ -434,40 +434,39 @@ private void LoadChunks(
continue;
var neighbor = new Vector2i(x + node.X, y + node.Y);
+ var chunkOffset = neighbor - chunk;
// Check if it's inbounds.
- if (neighbor.X < lower ||
- neighbor.Y < lower ||
- neighbor.X > upper ||
- neighbor.Y > upper)
+ if (chunkOffset.X < lower ||
+ chunkOffset.Y < lower ||
+ chunkOffset.X > upper ||
+ chunkOffset.Y > upper)
{
continue;
}
+ if (!spawnSet.Add(neighbor))
+ continue;
+
frontier.Add(neighbor);
}
}
- var actualNode = node + chunk;
-
- if (!spawnSet.Add(actualNode))
- continue;
-
// Check if it's a valid spawn, if so then use it.
- var enumerator = grid.GetAnchoredEntitiesEnumerator(actualNode);
+ var enumerator = grid.GetAnchoredEntitiesEnumerator(node);
if (enumerator.MoveNext(out _))
continue;
// Check if mask matches.
- TryGetEntity(actualNode, component.Layers, noiseCopy, grid, out var proto);
+ TryGetEntity(node, component.Layers, noiseCopy, grid, out var proto);
if (proto != layerProto.EntityMask)
{
continue;
}
- var chunkOrigin = SharedMapSystem.GetChunkIndices(actualNode, ChunkSize) * ChunkSize;
+ var chunkOrigin = SharedMapSystem.GetChunkIndices(node, ChunkSize) * ChunkSize;
if (!pending.TryGetValue(chunkOrigin, out var pendingMarkers))
{
@@ -482,7 +481,7 @@ private void LoadChunks(
}
// Log.Info($"Added node at {actualNode} for chunk {chunkOrigin}");
- layerMarkers.Add(actualNode);
+ layerMarkers.Add(node);
groupCount--;
}
}
diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs
new file mode 100644
index 00000000000..a33d6c522df
--- /dev/null
+++ b/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs
@@ -0,0 +1,27 @@
+using Robust.Shared.Noise;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Parallax.Biomes.Layers;
+
+///
+/// Contains more biome layers recursively via a biome template.
+/// Can be used for sub-biomes.
+///
+[Serializable, NetSerializable]
+public sealed class BiomeMetaLayer : IBiomeLayer
+{
+ [DataField("noise")]
+ public FastNoiseLite Noise { get; } = new(0);
+
+ ///
+ [DataField("threshold")]
+ public float Threshold { get; } = -1f;
+
+ ///
+ [DataField("invert")]
+ public bool Invert { get; }
+
+ [DataField("template", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))]
+ public string Template = string.Empty;
+}
diff --git a/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs b/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs
index f3ec77b3c57..e22f009760b 100644
--- a/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs
+++ b/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs
@@ -109,6 +109,23 @@ public bool TryGetBiomeTile(Vector2i indices, List layers, FastNois
{
var layer = layers[i];
+ // Check if the tile is from meta layer, otherwise fall back to default layers.
+ if (layer is BiomeMetaLayer meta)
+ {
+ SetNoise(noise, oldSeed, layer.Noise);
+ var found = noise.GetNoise(indices.X, indices.Y);
+ found *= layer.Invert ? -1 : 1;
+
+ if (found > layer.Threshold && TryGetBiomeTile(indices, ProtoManager.Index(meta.Template).Layers, noise,
+ grid, out tile))
+ {
+ noise.SetSeed(oldSeed);
+ return true;
+ }
+
+ continue;
+ }
+
if (layer is not BiomeTileLayer tileLayer)
continue;
@@ -187,6 +204,8 @@ protected bool TryGetEntity(Vector2i indices, List layers, FastNois
if (!worldLayer.AllowedTiles.Contains(tileId))
continue;
+ break;
+ case BiomeMetaLayer:
break;
default:
continue;
@@ -198,7 +217,16 @@ protected bool TryGetEntity(Vector2i indices, List layers, FastNois
value = invert ? value * -1 : value;
if (value < layer.Threshold)
+ continue;
+
+ if (layer is BiomeMetaLayer meta)
{
+ if (TryGetEntity(indices, ProtoManager.Index(meta.Template).Layers, noise, grid, out entity))
+ {
+ noise.SetSeed(oldSeed);
+ return true;
+ }
+
continue;
}
@@ -248,6 +276,8 @@ public bool TryGetDecals(Vector2i indices, List layers, FastNoiseLi
if (!worldLayer.AllowedTiles.Contains(tileId))
continue;
+ break;
+ case BiomeMetaLayer:
break;
default:
continue;
@@ -256,6 +286,20 @@ public bool TryGetDecals(Vector2i indices, List layers, FastNoiseLi
SetNoise(noise, oldSeed, layer.Noise);
var invert = layer.Invert;
+ if (layer is BiomeMetaLayer meta)
+ {
+ var found = noise.GetNoise(indices.X, indices.Y);
+ found *= layer.Invert ? -1 : 1;
+
+ if (found > layer.Threshold && TryGetDecals(indices, ProtoManager.Index(meta.Template).Layers, noise, grid, out decals))
+ {
+ noise.SetSeed(oldSeed);
+ return true;
+ }
+
+ continue;
+ }
+
// Check if the other layer should even render, if not then keep going.
if (layer is not BiomeDecalLayer decalLayer)
{
diff --git a/Resources/Prototypes/Procedural/biome_templates.yml b/Resources/Prototypes/Procedural/biome_templates.yml
index 51b3a28a46b..1dd0a0aaa47 100644
--- a/Resources/Prototypes/Procedural/biome_templates.yml
+++ b/Resources/Prototypes/Procedural/biome_templates.yml
@@ -1,3 +1,40 @@
+# Contains several biomes
+- type: biomeTemplate
+ id: Continental
+ layers:
+ - !type:BiomeMetaLayer
+ template: Lava
+ - !type:BiomeMetaLayer
+ template: Caves
+ threshold: -0.5
+ noise:
+ frequency: 0.001
+ noiseType: OpenSimplex2
+ fractalType: FBm
+ octaves: 2
+ lacunarity: 2
+ gain: 0.5
+ - !type:BiomeMetaLayer
+ template: Grasslands
+ threshold: 0
+ noise:
+ frequency: 0.001
+ noiseType: OpenSimplex2
+ fractalType: FBm
+ octaves: 2
+ lacunarity: 2
+ gain: 0.5
+ - !type:BiomeMetaLayer
+ template: Snow
+ threshold: 0.5
+ noise:
+ frequency: 0.001
+ noiseType: OpenSimplex2
+ fractalType: FBm
+ octaves: 2
+ lacunarity: 2
+ gain: 0.5
+
# Desert
# TODO: Water in desert
- type: biomeTemplate