]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Revert biome rework (#38724)
authorPieter-Jan Briers <pieterjan.briers+git@gmail.com>
Thu, 3 Jul 2025 18:48:04 +0000 (20:48 +0200)
committerGitHub <noreply@github.com>
Thu, 3 Jul 2025 18:48:04 +0000 (20:48 +0200)
* Revert "Fix world generation (#38713)"

This reverts commit 10fa6ff4af32f0ae59ed01f243164fcf6b31f965.

* Revert "Biome rework (#37735)"

This reverts commit fe7b96147c05afc160c33f033deca384e2886755.

116 files changed:
Content.Client/Parallax/BiomeDebugOverlay.cs [new file with mode: 0644]
Content.Client/Parallax/BiomeSystem.cs [new file with mode: 0644]
Content.Client/Parallax/Commands/ShowBiomeCommand.cs [new file with mode: 0644]
Content.Client/Parallax/ParallaxOverlay.cs
Content.Client/Salvage/UI/OfferingWindowOption.xaml.cs
Content.Server/Gateway/Components/GatewayGeneratorComponent.cs
Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs
Content.Server/Maps/PlanetCommand.cs
Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs
Content.Server/Parallax/BiomeSystem.Commands.cs [new file with mode: 0644]
Content.Server/Parallax/BiomeSystem.cs [new file with mode: 0644]
Content.Server/Procedural/BiomeSystem.Commands.cs [deleted file]
Content.Server/Procedural/BiomeSystem.Planet.cs [deleted file]
Content.Server/Procedural/BiomeSystem.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.AutoCabling.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs [new file with mode: 0644]
Content.Server/Procedural/DungeonJob/DungeonJob.BiomeMarkerLayer.cs [new file with mode: 0644]
Content.Server/Procedural/DungeonJob/DungeonJob.BoundaryWall.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.Corridor.cs
Content.Server/Procedural/DungeonJob/DungeonJob.CorridorClutter.cs
Content.Server/Procedural/DungeonJob/DungeonJob.CorridorDecalSkirting.cs
Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs
Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs
Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs
Content.Server/Procedural/DungeonJob/DungeonJob.DungeonEntrance.cs
Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs
Content.Server/Procedural/DungeonJob/DungeonJob.EntranceFlank.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Exterior.cs
Content.Server/Procedural/DungeonJob/DungeonJob.ExternalWindow.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Fill.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Helpers.cs
Content.Server/Procedural/DungeonJob/DungeonJob.InternalWindow.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Junction.cs
Content.Server/Procedural/DungeonJob/DungeonJob.MiddleConnection.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Mobs.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Noise.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.RoomEntrance.cs
Content.Server/Procedural/DungeonJob/DungeonJob.SampleDecal.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.SampleEntity.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.SampleTile.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.SplineDungeonConnector.cs
Content.Server/Procedural/DungeonJob/DungeonJob.WallMount.cs
Content.Server/Procedural/DungeonJob/DungeonJob.cs
Content.Server/Procedural/DungeonSystem.Rooms.cs
Content.Server/Procedural/DungeonSystem.cs
Content.Server/Salvage/SpawnSalvageMissionJob.cs
Content.Server/Shuttles/Systems/ArrivalsSystem.cs
Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
Content.Server/Shuttles/Systems/ShuttleSystem.cs
Content.Server/Station/Components/StationBiomeComponent.cs
Content.Server/Station/Systems/StationBiomeSystem.cs
Content.Server/Tabletop/TabletopSystem.cs
Content.Shared/CCVar/CCVars.Biome.cs [deleted file]
Content.Shared/Parallax/Biomes/BiomeComponent.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs [moved from Content.Shared/Procedural/DungeonLayers/SampleTileDunGen.cs with 66% similarity]
Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs [new file with mode: 0644]
Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs [new file with mode: 0644]
Content.Shared/Procedural/Components/BiomeComponent.cs [deleted file]
Content.Shared/Procedural/Components/BiomeForceUnloadComponent.cs [deleted file]
Content.Shared/Procedural/Distance/DunGenDistanceSquared.cs [deleted file]
Content.Shared/Procedural/DungeonConfig.cs
Content.Shared/Procedural/DungeonData.cs [deleted file]
Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs [deleted file]
Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs
Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs
Content.Shared/Procedural/DungeonLayers/FillGridDunGen.cs
Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs
Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs [deleted file]
Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs [deleted file]
Content.Shared/Procedural/DungeonLayers/SampleEntityDunGen.cs [deleted file]
Content.Shared/Procedural/Loot/BiomeLoot.cs [deleted file]
Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs [new file with mode: 0644]
Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs [new file with mode: 0644]
Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/AutoCablingDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs [new file with mode: 0644]
Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs [new file with mode: 0644]
Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/BoundaryWallDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/CornerClutterDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/CorridorClutterDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/CorridorDecalSkirtingDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/CorridorDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/DungeonEntranceDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/EntranceFlankDunGen.cs with 93% similarity]
Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/ExternalWindowDunGen.cs with 94% similarity]
Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/InternalWindowDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/JunctionDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/MiddleConnectionDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/RoomEntranceDunGen.cs with 93% similarity]
Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/SplineDungeonConnectorDunGen.cs with 83% similarity]
Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/WallMountDunGen.cs with 100% similarity]
Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs [moved from Content.Shared/Procedural/DungeonLayers/WormCorridorDunGen.cs with 100% similarity]
Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs
Resources/Locale/en-US/procedural/biome.ftl
Resources/Prototypes/Entities/Tiles/water.yml
Resources/Prototypes/Procedural/Magnet/asteroid.yml
Resources/Prototypes/Procedural/Magnet/space_debris.yml
Resources/Prototypes/Procedural/Magnet/space_debris_templates.yml
Resources/Prototypes/Procedural/biome_markers.yml
Resources/Prototypes/Procedural/biome_ore_templates.yml
Resources/Prototypes/Procedural/biome_ore_templates_low.yml
Resources/Prototypes/Procedural/biome_templates.yml
Resources/Prototypes/Procedural/dungeon_configs.yml
Resources/Prototypes/Procedural/salvage_loot.yml
Resources/Prototypes/Procedural/salvage_mods.yml
Resources/Prototypes/Procedural/vgroid.yml

diff --git a/Content.Client/Parallax/BiomeDebugOverlay.cs b/Content.Client/Parallax/BiomeDebugOverlay.cs
new file mode 100644 (file)
index 0000000..c914cb5
--- /dev/null
@@ -0,0 +1,87 @@
+using System.Numerics;
+using System.Text;
+using Content.Shared.Parallax.Biomes;
+using Robust.Client.Graphics;
+using Robust.Client.Input;
+using Robust.Client.ResourceManagement;
+using Robust.Shared.Enums;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+
+namespace Content.Client.Parallax;
+
+public sealed class BiomeDebugOverlay : Overlay
+{
+    public override OverlaySpace Space => OverlaySpace.ScreenSpace;
+
+    [Dependency] private readonly IEntityManager _entManager = default!;
+    [Dependency] private readonly IEyeManager _eyeManager = default!;
+    [Dependency] private readonly IInputManager _inputManager = default!;
+    [Dependency] private readonly IResourceCache _cache = default!;
+    [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
+
+    private BiomeSystem _biomes;
+    private SharedMapSystem _maps;
+
+    private Font _font;
+
+    public BiomeDebugOverlay()
+    {
+        IoCManager.InjectDependencies(this);
+
+        _biomes = _entManager.System<BiomeSystem>();
+        _maps = _entManager.System<SharedMapSystem>();
+
+        _font = new VectorFont(_cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 12);
+    }
+
+    protected override bool BeforeDraw(in OverlayDrawArgs args)
+    {
+        var mapUid = _maps.GetMapOrInvalid(args.MapId);
+
+        return _entManager.HasComponent<BiomeComponent>(mapUid);
+    }
+
+    protected override void Draw(in OverlayDrawArgs args)
+    {
+        var mouseScreenPos = _inputManager.MouseScreenPosition;
+        var mousePos = _eyeManager.ScreenToMap(mouseScreenPos);
+
+        if (mousePos.MapId == MapId.Nullspace || mousePos.MapId != args.MapId)
+            return;
+
+        var mapUid = _maps.GetMapOrInvalid(args.MapId);
+
+        if (!_entManager.TryGetComponent(mapUid, out BiomeComponent? biomeComp) || !_entManager.TryGetComponent(mapUid, out MapGridComponent? grid))
+            return;
+
+        var sb = new StringBuilder();
+        var nodePos = _maps.WorldToTile(mapUid, grid, mousePos.Position);
+
+        if (_biomes.TryGetEntity(nodePos, biomeComp, (mapUid, grid), out var ent))
+        {
+            var text = $"Entity: {ent}";
+            sb.AppendLine(text);
+        }
+
+        if (_biomes.TryGetDecals(nodePos, biomeComp.Layers, biomeComp.Seed, (mapUid, grid), out var decals))
+        {
+            var text = $"Decals: {decals.Count}";
+            sb.AppendLine(text);
+
+            foreach (var decal in decals)
+            {
+                var decalText = $"- {decal.ID}";
+                sb.AppendLine(decalText);
+            }
+        }
+
+        if (_biomes.TryGetBiomeTile(nodePos, biomeComp.Layers, biomeComp.Seed, (mapUid, grid), out var tile))
+        {
+            var tileText = $"Tile: {_tileDefManager[tile.Value.TypeId].ID}";
+            sb.AppendLine(tileText);
+        }
+
+        args.ScreenHandle.DrawString(_font, mouseScreenPos.Position + new Vector2(0f, 32f), sb.ToString());
+    }
+}
diff --git a/Content.Client/Parallax/BiomeSystem.cs b/Content.Client/Parallax/BiomeSystem.cs
new file mode 100644 (file)
index 0000000..dc326e1
--- /dev/null
@@ -0,0 +1,8 @@
+using Content.Shared.Parallax.Biomes;
+
+namespace Content.Client.Parallax;
+
+public sealed class BiomeSystem : SharedBiomeSystem
+{
+
+}
diff --git a/Content.Client/Parallax/Commands/ShowBiomeCommand.cs b/Content.Client/Parallax/Commands/ShowBiomeCommand.cs
new file mode 100644 (file)
index 0000000..2a628dd
--- /dev/null
@@ -0,0 +1,22 @@
+using Robust.Client.Graphics;
+using Robust.Shared.Console;
+
+namespace Content.Client.Parallax.Commands;
+
+public sealed class ShowBiomeCommand : LocalizedCommands
+{
+    [Dependency] private readonly IOverlayManager _overlayMgr = default!;
+
+    public override string Command => "showbiome";
+    public override void Execute(IConsoleShell shell, string argStr, string[] args)
+    {
+        if (_overlayMgr.HasOverlay<BiomeDebugOverlay>())
+        {
+            _overlayMgr.RemoveOverlay<BiomeDebugOverlay>();
+        }
+        else
+        {
+            _overlayMgr.AddOverlay(new BiomeDebugOverlay());
+        }
+    }
+}
index 8e3a1dddfe130597464fbe268bf75573949c8b81..06f830675dadd05cf68124adb9ac2d97c995cdb9 100644 (file)
@@ -1,6 +1,8 @@
 using System.Numerics;
 using Content.Client.Parallax.Managers;
 using Content.Shared.CCVar;
+using Content.Shared.Parallax.Biomes;
+using Robust.Client.GameObjects;
 using Robust.Client.Graphics;
 using Robust.Shared.Configuration;
 using Robust.Shared.Enums;
@@ -17,6 +19,7 @@ public sealed class ParallaxOverlay : Overlay
     [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
     [Dependency] private readonly IConfigurationManager _configurationManager = default!;
     [Dependency] private readonly IParallaxManager _manager = default!;
+    private readonly SharedMapSystem _mapSystem;
     private readonly ParallaxSystem _parallax;
 
     public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
@@ -25,12 +28,13 @@ public sealed class ParallaxOverlay : Overlay
     {
         ZIndex = ParallaxSystem.ParallaxZIndex;
         IoCManager.InjectDependencies(this);
+        _mapSystem = _entManager.System<SharedMapSystem>();
         _parallax = _entManager.System<ParallaxSystem>();
     }
 
     protected override bool BeforeDraw(in OverlayDrawArgs args)
     {
-        if (args.MapId == MapId.Nullspace)
+        if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapSystem.GetMapOrInvalid(args.MapId)))
             return false;
 
         return true;
index 8d718f1be5bfcfb6340d02997dd1c555c5f50982..7855577e69462e658549fe7fc0b88ab8e79f30aa 100644 (file)
@@ -1,9 +1,23 @@
+using System.Linq;
+using Content.Client.Computer;
 using Content.Client.Stylesheets;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.CCVar;
+using Content.Shared.Parallax.Biomes;
+using Content.Shared.Procedural;
+using Content.Shared.Salvage;
+using Content.Shared.Salvage.Expeditions;
+using Content.Shared.Salvage.Expeditions.Modifiers;
+using Content.Shared.Shuttles.BUIStates;
 using Robust.Client.AutoGenerated;
 using Robust.Client.Graphics;
 using Robust.Client.UserInterface;
 using Robust.Client.UserInterface.Controls;
 using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
 
 namespace Content.Client.Salvage.UI;
 
index 03a7ec5c4d1dacfbd2224bda978fae3afcde7720..d65760e270fdc4f81fc6868d9d741838a420a472 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Procedural;
+using Content.Shared.Parallax.Biomes.Markers;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
@@ -42,7 +42,7 @@ public sealed partial class GatewayGeneratorComponent : Component
     /// Mob layers to pick from.
     /// </summary>
     [DataField]
-    public List<ProtoId<DungeonConfigPrototype>> MobLayers = new()
+    public List<ProtoId<BiomeMarkerLayerPrototype>> MobLayers = new()
     {
         "Carps",
         "Xenos",
@@ -54,7 +54,7 @@ public sealed partial class GatewayGeneratorComponent : Component
     /// <summary>
     /// Loot layers to pick from.
     /// </summary>
-    public List<ProtoId<DungeonConfigPrototype>> LootLayers = new()
+    public List<ProtoId<BiomeMarkerLayerPrototype>> LootLayers = new()
     {
         "OreIron",
         "OreQuartz",
index 5d08247b560d0d8668e377a8bd3428cc945b7202..83471cdbc166190629dba0c42bf823fd44f4d873 100644 (file)
@@ -1,11 +1,12 @@
 using System.Linq;
 using Content.Server.Gateway.Components;
+using Content.Server.Parallax;
 using Content.Server.Procedural;
 using Content.Shared.CCVar;
 using Content.Shared.Dataset;
 using Content.Shared.Maps;
+using Content.Shared.Parallax.Biomes;
 using Content.Shared.Procedural;
-using Content.Shared.Procedural.Components;
 using Content.Shared.Salvage;
 using Robust.Shared.Configuration;
 using Robust.Shared.Map;
@@ -110,7 +111,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
         };
         AddComp(mapUid, restricted);
 
-        _biome.EnsurePlanet(mapUid, _protoManager.Index("BiomeGrasslands"), seed);
+        _biome.EnsurePlanet(mapUid, _protoManager.Index<BiomeTemplatePrototype>("Continental"), seed);
 
         var grid = Comp<MapGridComponent>(mapUid);
 
@@ -198,7 +199,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
                 var layer = lootLayers[layerIdx];
                 lootLayers.RemoveSwap(layerIdx);
 
-                _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
+                _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
             }
 
             // - Mobs
@@ -210,7 +211,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
                 var layer = mobLayers[layerIdx];
                 mobLayers.RemoveSwap(layerIdx);
 
-                _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
+                _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
             }
         }
     }
index d3b7abc1715a35e7c38dc00d55f47d3f4926b991..8e8b5b10edb96782b6d76c366ea80af835c7129f 100644 (file)
@@ -1,11 +1,20 @@
 using System.Linq;
 using Content.Server.Administration;
-using Content.Server.Procedural;
+using Content.Server.Atmos;
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Parallax;
 using Content.Shared.Administration;
-using Content.Shared.Procedural.Components;
+using Content.Shared.Atmos;
+using Content.Shared.Gravity;
+using Content.Shared.Movement.Components;
+using Content.Shared.Parallax.Biomes;
+using Robust.Shared.Audio;
 using Robust.Shared.Console;
 using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
 
 namespace Content.Server.Maps;
 
@@ -43,7 +52,7 @@ public sealed class PlanetCommand : LocalizedEntityCommands
             return;
         }
 
-        if (!_protoManager.TryIndex<EntityPrototype>(args[1], out var biomeTemplate))
+        if (!_protoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var biomeTemplate))
         {
             shell.WriteError(Loc.GetString("cmd-planet-map-prototype", ("prototype", args[1])));
             return;
@@ -61,12 +70,9 @@ public sealed class PlanetCommand : LocalizedEntityCommands
         if (args.Length == 1)
             return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entManager), "Map Id");
 
-        var biomeName = _entManager.ComponentFactory.GetComponentName<BiomeComponent>();
-
         if (args.Length == 2)
         {
-            var options = _protoManager.EnumeratePrototypes<EntityPrototype>()
-                .Where(o => o.Components.ContainsKey(biomeName))
+            var options = _protoManager.EnumeratePrototypes<BiomeTemplatePrototype>()
                 .Select(o => new CompletionOption(o.ID, "Biome"));
             return CompletionResult.FromOptions(options);
         }
index 07d6a624c84997e66c8ee403de7cf6d48c0f2f93..7afd3d78df1ff510b43e06338afdfcc8b75cae8c 100644 (file)
@@ -65,9 +65,6 @@ public sealed partial class PathfindingSystem
                 {
                     for (var y = -1; y <= 1; y++)
                     {
-                        if (x == 0 && y == 0)
-                            continue;
-
                         var neighbor = node + new Vector2i(x, y);
                         var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f;
 
@@ -124,7 +121,8 @@ public sealed partial class PathfindingSystem
                         cameFrom[neighbor] = node;
                         costSoFar[neighbor] = gScore;
 
-                        var hScore = ManhattanDistance(args.End, neighbor);
+                        // Still use octile even for manhattan distance.
+                        var hScore = OctileDistance(args.End, neighbor) * 1.001f;
                         var fScore = gScore + hScore;
                         frontier.Enqueue(neighbor, fScore);
                     }
diff --git a/Content.Server/Parallax/BiomeSystem.Commands.cs b/Content.Server/Parallax/BiomeSystem.Commands.cs
new file mode 100644 (file)
index 0000000..a915e17
--- /dev/null
@@ -0,0 +1,188 @@
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Parallax.Biomes;
+using Content.Shared.Parallax.Biomes.Layers;
+using Content.Shared.Parallax.Biomes.Markers;
+using Robust.Shared.Console;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+
+namespace Content.Server.Parallax;
+
+public sealed partial class BiomeSystem
+{
+    private void InitializeCommands()
+    {
+        _console.RegisterCommand("biome_clear", Loc.GetString("cmd-biome_clear-desc"), Loc.GetString("cmd-biome_clear-help"), BiomeClearCallback, BiomeClearCallbackHelper);
+        _console.RegisterCommand("biome_addlayer", Loc.GetString("cmd-biome_addlayer-desc"), Loc.GetString("cmd-biome_addlayer-help"), AddLayerCallback, AddLayerCallbackHelp);
+        _console.RegisterCommand("biome_addmarkerlayer", Loc.GetString("cmd-biome_addmarkerlayer-desc"), Loc.GetString("cmd-biome_addmarkerlayer-desc"), AddMarkerLayerCallback, AddMarkerLayerCallbackHelper);
+    }
+
+    [AdminCommand(AdminFlags.Fun)]
+    private void BiomeClearCallback(IConsoleShell shell, string argstr, string[] args)
+    {
+        if (args.Length != 1)
+        {
+            return;
+        }
+
+        int.TryParse(args[0], out var mapInt);
+        var mapId = new MapId(mapInt);
+        var mapUid = _mapSystem.GetMapOrInvalid(mapId);
+
+        if (_mapSystem.MapExists(mapId) ||
+            !TryComp<BiomeComponent>(mapUid, out var biome))
+        {
+            return;
+        }
+
+        ClearTemplate(mapUid, biome);
+    }
+
+    private CompletionResult BiomeClearCallbackHelper(IConsoleShell shell, string[] args)
+    {
+        if (args.Length == 1)
+        {
+            return CompletionResult.FromHintOptions(CompletionHelper.Components<BiomeComponent>(args[0], EntityManager), "Biome");
+        }
+
+        return CompletionResult.Empty;
+    }
+
+    [AdminCommand(AdminFlags.Fun)]
+    private void AddLayerCallback(IConsoleShell shell, string argstr, string[] args)
+    {
+        if (args.Length < 3 || args.Length > 4)
+        {
+            return;
+        }
+
+        if (!int.TryParse(args[0], out var mapInt))
+        {
+            return;
+        }
+
+        var mapId = new MapId(mapInt);
+        var mapUid = _mapSystem.GetMapOrInvalid(mapId);
+
+        if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(mapUid, out var biome))
+        {
+            return;
+        }
+
+        if (!ProtoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var template))
+        {
+            return;
+        }
+
+        var offset = 0;
+
+        if (args.Length == 4)
+        {
+            int.TryParse(args[3], out offset);
+        }
+
+        AddTemplate(mapUid, biome, args[2], template, offset);
+    }
+
+    private CompletionResult AddLayerCallbackHelp(IConsoleShell shell, string[] args)
+    {
+        if (args.Length == 1)
+        {
+            return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), "Map ID");
+        }
+
+        if (args.Length == 2)
+        {
+            return CompletionResult.FromHintOptions(
+                CompletionHelper.PrototypeIDs<BiomeTemplatePrototype>(proto: ProtoManager), "Biome template");
+        }
+
+        if (args.Length == 3)
+        {
+            if (int.TryParse(args[0], out var mapInt))
+            {
+                var mapId = new MapId(mapInt);
+
+                if (TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
+                {
+                    var results = new List<string>();
+
+                    foreach (var layer in biome.Layers)
+                    {
+                        if (layer is not BiomeDummyLayer dummy)
+                            continue;
+
+                        results.Add(dummy.ID);
+                    }
+
+                    return CompletionResult.FromHintOptions(results, "Dummy layer ID");
+                }
+            }
+        }
+
+        if (args.Length == 4)
+        {
+            return CompletionResult.FromHint("Seed offset");
+        }
+
+        return CompletionResult.Empty;
+    }
+
+    [AdminCommand(AdminFlags.Fun)]
+    private void AddMarkerLayerCallback(IConsoleShell shell, string argstr, string[] args)
+    {
+        if (args.Length != 2)
+        {
+            return;
+        }
+
+        if (!int.TryParse(args[0], out var mapInt))
+        {
+            return;
+        }
+
+        var mapId = new MapId(mapInt);
+
+        if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
+        {
+            return;
+        }
+
+        if (!ProtoManager.HasIndex<BiomeMarkerLayerPrototype>(args[1]))
+        {
+            return;
+        }
+
+        if (!biome.MarkerLayers.Add(args[1]))
+        {
+            return;
+        }
+
+        biome.ForcedMarkerLayers.Add(args[1]);
+    }
+
+    private CompletionResult AddMarkerLayerCallbackHelper(IConsoleShell shell, string[] args)
+    {
+        if (args.Length == 1)
+        {
+            var allQuery = AllEntityQuery<MapComponent, BiomeComponent>();
+            var options = new List<CompletionOption>();
+
+            while (allQuery.MoveNext(out var mapComp, out _))
+            {
+                options.Add(new CompletionOption(mapComp.MapId.ToString()));
+            }
+
+            return CompletionResult.FromHintOptions(options, "Biome");
+        }
+
+        if (args.Length == 2)
+        {
+            return CompletionResult.FromHintOptions(
+                CompletionHelper.PrototypeIDs<BiomeMarkerLayerPrototype>(proto: ProtoManager), "Marker");
+        }
+
+        return CompletionResult.Empty;
+    }
+}
diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs
new file mode 100644 (file)
index 0000000..496cb38
--- /dev/null
@@ -0,0 +1,1086 @@
+using System.Linq;
+using System.Numerics;
+using System.Threading.Tasks;
+using Content.Server.Atmos;
+using Content.Server.Atmos.Components;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Decals;
+using Content.Server.Ghost.Roles.Components;
+using Content.Server.Shuttles.Events;
+using Content.Server.Shuttles.Systems;
+using Content.Shared.Atmos;
+using Content.Shared.Decals;
+using Content.Shared.Ghost;
+using Content.Shared.Gravity;
+using Content.Shared.Light.Components;
+using Content.Shared.Parallax.Biomes;
+using Content.Shared.Parallax.Biomes.Layers;
+using Content.Shared.Parallax.Biomes.Markers;
+using Content.Shared.Tag;
+using Microsoft.Extensions.ObjectPool;
+using Robust.Server.Player;
+using Robust.Shared;
+using Robust.Shared.Collections;
+using Robust.Shared.Configuration;
+using Robust.Shared.Console;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Threading;
+using Robust.Shared.Utility;
+using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
+
+namespace Content.Server.Parallax;
+
+public sealed partial class BiomeSystem : SharedBiomeSystem
+{
+    [Dependency] private readonly IConfigurationManager _configManager = default!;
+    [Dependency] private readonly IConsoleHost _console = default!;
+    [Dependency] private readonly IMapManager _mapManager = default!;
+    [Dependency] private readonly IParallelManager _parallel = default!;
+    [Dependency] private readonly IPrototypeManager _proto = default!;
+    [Dependency] private readonly IPlayerManager _playerManager = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly AtmosphereSystem _atmos = default!;
+    [Dependency] private readonly DecalSystem _decals = default!;
+    [Dependency] private readonly SharedMapSystem _mapSystem = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly ShuttleSystem _shuttles = default!;
+    [Dependency] private readonly TagSystem _tags = default!;
+
+    private EntityQuery<BiomeComponent> _biomeQuery;
+    private EntityQuery<FixturesComponent> _fixturesQuery;
+    private EntityQuery<GhostComponent> _ghostQuery;
+    private EntityQuery<TransformComponent> _xformQuery;
+
+    private readonly HashSet<EntityUid> _handledEntities = new();
+    private const float DefaultLoadRange = 16f;
+    private float _loadRange = DefaultLoadRange;
+    private static readonly ProtoId<TagPrototype> AllowBiomeLoadingTag = "AllowBiomeLoading";
+
+    private List<(Vector2i, Tile)> _tiles = new();
+
+    private ObjectPool<HashSet<Vector2i>> _tilePool =
+        new DefaultObjectPool<HashSet<Vector2i>>(new SetPolicy<Vector2i>(), 256);
+
+    /// <summary>
+    /// Load area for chunks containing tiles, decals etc.
+    /// </summary>
+    private Box2 _loadArea = new(-DefaultLoadRange, -DefaultLoadRange, DefaultLoadRange, DefaultLoadRange);
+
+    /// <summary>
+    /// Stores the chunks active for this tick temporarily.
+    /// </summary>
+    private readonly Dictionary<BiomeComponent, HashSet<Vector2i>> _activeChunks = new();
+
+    private readonly Dictionary<BiomeComponent,
+        Dictionary<string, HashSet<Vector2i>>> _markerChunks = new();
+
+    public override void Initialize()
+    {
+        base.Initialize();
+        Log.Level = LogLevel.Debug;
+        _biomeQuery = GetEntityQuery<BiomeComponent>();
+        _fixturesQuery = GetEntityQuery<FixturesComponent>();
+        _ghostQuery = GetEntityQuery<GhostComponent>();
+        _xformQuery = GetEntityQuery<TransformComponent>();
+        SubscribeLocalEvent<BiomeComponent, MapInitEvent>(OnBiomeMapInit);
+        SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
+        SubscribeLocalEvent<ShuttleFlattenEvent>(OnShuttleFlatten);
+        Subs.CVar(_configManager, CVars.NetMaxUpdateRange, SetLoadRange, true);
+        InitializeCommands();
+        SubscribeLocalEvent<PrototypesReloadedEventArgs>(ProtoReload);
+    }
+
+    private void ProtoReload(PrototypesReloadedEventArgs obj)
+    {
+        if (!obj.ByType.TryGetValue(typeof(BiomeTemplatePrototype), out var reloads))
+            return;
+
+        var query = AllEntityQuery<BiomeComponent>();
+
+        while (query.MoveNext(out var uid, out var biome))
+        {
+            if (biome.Template == null || !reloads.Modified.TryGetValue(biome.Template, out var proto))
+                continue;
+
+            SetTemplate(uid, biome, (BiomeTemplatePrototype)proto);
+        }
+    }
+
+    private void SetLoadRange(float obj)
+    {
+        // Round it up
+        _loadRange = MathF.Ceiling(obj / ChunkSize) * ChunkSize;
+        _loadArea = new Box2(-_loadRange, -_loadRange, _loadRange, _loadRange);
+    }
+
+    private void OnBiomeMapInit(EntityUid uid, BiomeComponent component, MapInitEvent args)
+    {
+        if (component.Seed == -1)
+        {
+            SetSeed(uid, component, _random.Next());
+        }
+
+        if (_proto.TryIndex(component.Template, out var biome))
+            SetTemplate(uid, component, biome);
+
+        var xform = Transform(uid);
+        var mapId = xform.MapID;
+
+        if (mapId != MapId.Nullspace && HasComp<MapGridComponent>(uid))
+        {
+            var setTiles = new List<(Vector2i Index, Tile tile)>();
+
+            foreach (var grid in _mapManager.GetAllGrids(mapId))
+            {
+                if (!_fixturesQuery.TryGetComponent(grid.Owner, out var fixtures))
+                    continue;
+
+                // Don't want shuttles flying around now do we.
+                _shuttles.Disable(grid.Owner);
+                var pTransform = _physics.GetPhysicsTransform(grid.Owner);
+
+                foreach (var fixture in fixtures.Fixtures.Values)
+                {
+                    for (var i = 0; i < fixture.Shape.ChildCount; i++)
+                    {
+                        var aabb = fixture.Shape.ComputeAABB(pTransform, i);
+
+                        setTiles.Clear();
+                        ReserveTiles(uid, aabb, setTiles);
+                    }
+                }
+            }
+        }
+    }
+
+    public void SetEnabled(Entity<BiomeComponent?> ent, bool enabled = true)
+    {
+        if (!Resolve(ent, ref ent.Comp) || ent.Comp.Enabled == enabled)
+            return;
+
+        ent.Comp.Enabled = enabled;
+        Dirty(ent, ent.Comp);
+    }
+
+    public void SetSeed(EntityUid uid, BiomeComponent component, int seed, bool dirty = true)
+    {
+        component.Seed = seed;
+
+        if (dirty)
+            Dirty(uid, component);
+    }
+
+    public void ClearTemplate(EntityUid uid, BiomeComponent component, bool dirty = true)
+    {
+        component.Layers.Clear();
+        component.Template = null;
+
+        if (dirty)
+            Dirty(uid, component);
+    }
+
+    /// <summary>
+    /// Sets the <see cref="BiomeComponent.Template"/> and refreshes layers.
+    /// </summary>
+    public void SetTemplate(EntityUid uid, BiomeComponent component, BiomeTemplatePrototype template, bool dirty = true)
+    {
+        component.Layers.Clear();
+        component.Template = template.ID;
+
+        foreach (var layer in template.Layers)
+        {
+            component.Layers.Add(layer);
+        }
+
+        if (dirty)
+            Dirty(uid, component);
+    }
+
+    /// <summary>
+    /// Adds the specified layer at the specified marker if it exists.
+    /// </summary>
+    public void AddLayer(EntityUid uid, BiomeComponent component, string id, IBiomeLayer addedLayer, int seedOffset = 0)
+    {
+        for (var i = 0; i < component.Layers.Count; i++)
+        {
+            var layer = component.Layers[i];
+
+            if (layer is not BiomeDummyLayer dummy || dummy.ID != id)
+                continue;
+
+            addedLayer.Noise.SetSeed(addedLayer.Noise.GetSeed() + seedOffset);
+            component.Layers.Insert(i, addedLayer);
+            break;
+        }
+
+        Dirty(uid, component);
+    }
+
+    public void AddMarkerLayer(EntityUid uid, BiomeComponent component, string marker)
+    {
+        component.MarkerLayers.Add(marker);
+        Dirty(uid, component);
+    }
+
+    /// <summary>
+    /// Adds the specified template at the specified marker if it exists, withour overriding every layer.
+    /// </summary>
+    public void AddTemplate(EntityUid uid, BiomeComponent component, string id, BiomeTemplatePrototype template, int seedOffset = 0)
+    {
+        for (var i = 0; i < component.Layers.Count; i++)
+        {
+            var layer = component.Layers[i];
+
+            if (layer is not BiomeDummyLayer dummy || dummy.ID != id)
+                continue;
+
+            for (var j = template.Layers.Count - 1; j >= 0; j--)
+            {
+                var addedLayer = template.Layers[j];
+                addedLayer.Noise.SetSeed(addedLayer.Noise.GetSeed() + seedOffset);
+                component.Layers.Insert(i, addedLayer);
+            }
+
+            break;
+        }
+
+        Dirty(uid, component);
+    }
+
+    private void OnFTLStarted(ref FTLStartedEvent ev)
+    {
+        var targetMap = _transform.ToMapCoordinates(ev.TargetCoordinates);
+        var targetMapUid = _mapSystem.GetMapOrInvalid(targetMap.MapId);
+
+        if (!TryComp<BiomeComponent>(targetMapUid, out var biome))
+            return;
+
+        var preloadArea = new Vector2(32f, 32f);
+        var targetArea = new Box2(targetMap.Position - preloadArea, targetMap.Position + preloadArea);
+        Preload(targetMapUid, biome, targetArea);
+    }
+
+    private void OnShuttleFlatten(ref ShuttleFlattenEvent ev)
+    {
+        if (!TryComp<BiomeComponent>(ev.MapUid, out var biome) ||
+            !TryComp<MapGridComponent>(ev.MapUid, out var grid))
+        {
+            return;
+        }
+
+        var tiles = new List<(Vector2i Index, Tile Tile)>();
+
+        foreach (var aabb in ev.AABBs)
+        {
+            for (var x = Math.Floor(aabb.Left); x <= Math.Ceiling(aabb.Right); x++)
+            {
+                for (var y = Math.Floor(aabb.Bottom); y <= Math.Ceiling(aabb.Top); y++)
+                {
+                    var index = new Vector2i((int)x, (int)y);
+                    var chunk = SharedMapSystem.GetChunkIndices(index, ChunkSize);
+
+                    var mod = biome.ModifiedTiles.GetOrNew(chunk * ChunkSize);
+
+                    if (!mod.Add(index) || !TryGetBiomeTile(index, biome.Layers, biome.Seed, (ev.MapUid, grid), out var tile))
+                        continue;
+
+                    // If we flag it as modified then the tile is never set so need to do it ourselves.
+                    tiles.Add((index, tile.Value));
+                }
+            }
+        }
+
+        _mapSystem.SetTiles(ev.MapUid, grid, tiles);
+    }
+
+    /// <summary>
+    /// Preloads biome for the specified area.
+    /// </summary>
+    public void Preload(EntityUid uid, BiomeComponent component, Box2 area)
+    {
+        var markers = component.MarkerLayers;
+        var goobers = _markerChunks.GetOrNew(component);
+
+        foreach (var layer in markers)
+        {
+            var proto = ProtoManager.Index(layer);
+            var enumerator = new ChunkIndicesEnumerator(area, proto.Size);
+
+            while (enumerator.MoveNext(out var chunk))
+            {
+                var chunkOrigin = chunk * proto.Size;
+                var layerChunks = goobers.GetOrNew(proto.ID);
+                layerChunks.Add(chunkOrigin.Value);
+            }
+        }
+    }
+
+    private bool CanLoad(EntityUid uid)
+    {
+        return !_ghostQuery.HasComp(uid) || _tags.HasTag(uid, AllowBiomeLoadingTag);
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+        var biomes = AllEntityQuery<BiomeComponent>();
+
+        while (biomes.MoveNext(out var biome))
+        {
+            if (biome.LifeStage < ComponentLifeStage.Running)
+                continue;
+
+            _activeChunks.Add(biome, _tilePool.Get());
+            _markerChunks.GetOrNew(biome);
+        }
+
+        // Get chunks in range
+        foreach (var pSession in Filter.GetAllPlayers(_playerManager))
+        {
+            if (_xformQuery.TryGetComponent(pSession.AttachedEntity, out var xform) &&
+                _handledEntities.Add(pSession.AttachedEntity.Value) &&
+                 _biomeQuery.TryGetComponent(xform.MapUid, out var biome) &&
+                biome.Enabled &&
+                CanLoad(pSession.AttachedEntity.Value))
+            {
+                var worldPos = _transform.GetWorldPosition(xform);
+                AddChunksInRange(biome, worldPos);
+
+                foreach (var layer in biome.MarkerLayers)
+                {
+                    var layerProto = ProtoManager.Index(layer);
+                    AddMarkerChunksInRange(biome, worldPos, layerProto);
+                }
+            }
+
+            foreach (var viewer in pSession.ViewSubscriptions)
+            {
+                if (!_handledEntities.Add(viewer) ||
+                    !_xformQuery.TryGetComponent(viewer, out xform) ||
+                    !_biomeQuery.TryGetComponent(xform.MapUid, out biome) ||
+                    !biome.Enabled ||
+                    !CanLoad(viewer))
+                {
+                    continue;
+                }
+
+                var worldPos = _transform.GetWorldPosition(xform);
+                AddChunksInRange(biome, worldPos);
+
+                foreach (var layer in biome.MarkerLayers)
+                {
+                    var layerProto = ProtoManager.Index(layer);
+                    AddMarkerChunksInRange(biome, worldPos, layerProto);
+                }
+            }
+        }
+
+        var loadBiomes = AllEntityQuery<BiomeComponent, MapGridComponent>();
+
+        while (loadBiomes.MoveNext(out var gridUid, out var biome, out var grid))
+        {
+            // If not MapInit don't run it.
+            if (biome.LifeStage < ComponentLifeStage.Running)
+                continue;
+
+            if (!biome.Enabled)
+                continue;
+
+            // Load new chunks
+            LoadChunks(biome, gridUid, grid, biome.Seed);
+            // Unload old chunks
+            UnloadChunks(biome, gridUid, grid, biome.Seed);
+        }
+
+        _handledEntities.Clear();
+
+        foreach (var tiles in _activeChunks.Values)
+        {
+            _tilePool.Return(tiles);
+        }
+
+        _activeChunks.Clear();
+        _markerChunks.Clear();
+    }
+
+    private void AddChunksInRange(BiomeComponent biome, Vector2 worldPos)
+    {
+        var enumerator = new ChunkIndicesEnumerator(_loadArea.Translated(worldPos), ChunkSize);
+
+        while (enumerator.MoveNext(out var chunkOrigin))
+        {
+            _activeChunks[biome].Add(chunkOrigin.Value * ChunkSize);
+        }
+    }
+
+    private void AddMarkerChunksInRange(BiomeComponent biome, Vector2 worldPos, IBiomeMarkerLayer layer)
+    {
+        // Offset the load area so it's centralised.
+        var loadArea = new Box2(0, 0, layer.Size, layer.Size);
+        var halfLayer = new Vector2(layer.Size / 2f);
+
+        var enumerator = new ChunkIndicesEnumerator(loadArea.Translated(worldPos - halfLayer), layer.Size);
+
+        while (enumerator.MoveNext(out var chunkOrigin))
+        {
+            var lay = _markerChunks[biome].GetOrNew(layer.ID);
+            lay.Add(chunkOrigin.Value * layer.Size);
+        }
+    }
+
+    #region Load
+
+    /// <summary>
+    /// Loads all of the chunks for a particular biome, as well as handle any marker chunks.
+    /// </summary>
+    private void LoadChunks(
+        BiomeComponent component,
+        EntityUid gridUid,
+        MapGridComponent grid,
+        int seed)
+    {
+        BuildMarkerChunks(component, gridUid, grid, seed);
+
+        var active = _activeChunks[component];
+
+        foreach (var chunk in active)
+        {
+            LoadChunkMarkers(component, gridUid, grid, chunk, seed);
+
+            if (!component.LoadedChunks.Add(chunk))
+                continue;
+
+            // Load NOW!
+            LoadChunk(component, gridUid, grid, chunk, seed);
+        }
+    }
+
+    /// <summary>
+    /// Goes through all marker chunks that haven't been calculated, then calculates what spawns there are and
+    /// allocates them to the relevant actual chunks in the biome (marker chunks may be many times larger than biome chunks).
+    /// </summary>
+    private void BuildMarkerChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, int seed)
+    {
+        var markers = _markerChunks[component];
+        var loadedMarkers = component.LoadedMarkers;
+        var idx = 0;
+
+        foreach (var (layer, chunks) in markers)
+        {
+            // I know dictionary ordering isn't guaranteed but I just need something to differentiate seeds.
+            idx++;
+            var localIdx = idx;
+
+            Parallel.ForEach(chunks, new ParallelOptions() { MaxDegreeOfParallelism = _parallel.ParallelProcessCount }, chunk =>
+            {
+                if (loadedMarkers.TryGetValue(layer, out var mobChunks) && mobChunks.Contains(chunk))
+                    return;
+
+                var forced = component.ForcedMarkerLayers.Contains(layer);
+
+                // Make a temporary version and copy back in later.
+                var pending = new Dictionary<Vector2i, Dictionary<string, List<Vector2i>>>();
+
+                // Essentially get the seed + work out a buffer to adjacent chunks so we don't
+                // inadvertantly spawn too many near the edges.
+                var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
+                var markerSeed = seed + chunk.X * ChunkSize + chunk.Y + localIdx;
+                var rand = new Random(markerSeed);
+                var buffer = (int)(layerProto.Radius / 2f);
+                var bounds = new Box2i(chunk + buffer, chunk + layerProto.Size - buffer);
+                var count = (int)(bounds.Area / (layerProto.Radius * layerProto.Radius));
+                count = Math.Min(count, layerProto.MaxCount);
+
+                GetMarkerNodes(gridUid, component, grid, layerProto, forced, bounds, count, rand,
+                    out var spawnSet, out var existing);
+
+                // Forcing markers to spawn so delete any that were found to be in the way.
+                if (forced && existing.Count > 0)
+                {
+                    // Lock something so we can delete these safely.
+                    lock (component.PendingMarkers)
+                    {
+                        foreach (var ent in existing)
+                        {
+                            Del(ent);
+                        }
+                    }
+                }
+
+                foreach (var node in spawnSet.Keys)
+                {
+                    var chunkOrigin = SharedMapSystem.GetChunkIndices(node, ChunkSize) * ChunkSize;
+
+                    if (!pending.TryGetValue(chunkOrigin, out var pendingMarkers))
+                    {
+                        pendingMarkers = new Dictionary<string, List<Vector2i>>();
+                        pending[chunkOrigin] = pendingMarkers;
+                    }
+
+                    if (!pendingMarkers.TryGetValue(layer, out var layerMarkers))
+                    {
+                        layerMarkers = new List<Vector2i>();
+                        pendingMarkers[layer] = layerMarkers;
+                    }
+
+                    layerMarkers.Add(node);
+                }
+
+                lock (loadedMarkers)
+                {
+                    if (!loadedMarkers.TryGetValue(layer, out var lockMobChunks))
+                    {
+                        lockMobChunks = new HashSet<Vector2i>();
+                        loadedMarkers[layer] = lockMobChunks;
+                    }
+
+                    lockMobChunks.Add(chunk);
+
+                    foreach (var (chunkOrigin, layers) in pending)
+                    {
+                        if (!component.PendingMarkers.TryGetValue(chunkOrigin, out var lockMarkers))
+                        {
+                            lockMarkers = new Dictionary<string, List<Vector2i>>();
+                            component.PendingMarkers[chunkOrigin] = lockMarkers;
+                        }
+
+                        foreach (var (lockLayer, nodes) in layers)
+                        {
+                            lockMarkers[lockLayer] = nodes;
+                        }
+                    }
+                }
+            });
+        }
+
+        component.ForcedMarkerLayers.Clear();
+    }
+
+    /// <summary>
+    /// Gets the marker nodes for the specified area.
+    /// </summary>
+    /// <param name="emptyTiles">Should we include empty tiles when determine markers (e.g. if they are yet to be loaded)</param>
+    public void GetMarkerNodes(
+        EntityUid gridUid,
+        BiomeComponent biome,
+        MapGridComponent grid,
+        BiomeMarkerLayerPrototype layerProto,
+        bool forced,
+        Box2i bounds,
+        int count,
+        Random rand,
+        out Dictionary<Vector2i, string?> spawnSet,
+        out HashSet<EntityUid> existingEnts,
+        bool emptyTiles = true)
+    {
+        DebugTools.Assert(count > 0);
+        var remainingTiles = _tilePool.Get();
+        var nodeEntities = new Dictionary<Vector2i, EntityUid?>();
+        var nodeMask = new Dictionary<Vector2i, string?>();
+
+        // Okay so originally we picked a random tile and BFS outwards
+        // the problem is if you somehow get a cooked frontier then it might drop entire veins
+        // hence we'll grab all valid tiles up front and use that as possible seeds.
+        // It's hella more expensive but stops issues.
+        for (var x = bounds.Left; x < bounds.Right; x++)
+        {
+            for (var y = bounds.Bottom; y < bounds.Top; y++)
+            {
+                var node = new Vector2i(x, y);
+
+                // Empty tile, skip if relevant.
+                if (!emptyTiles && (!_mapSystem.TryGetTile(grid, node, out var tile) || tile.IsEmpty))
+                    continue;
+
+                // Check if it's a valid spawn, if so then use it.
+                var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, node);
+                enumerator.MoveNext(out var existing);
+
+                if (!forced && existing != null)
+                    continue;
+
+                // Check if mask matches // anything blocking.
+                TryGetEntity(node, biome, (gridUid, grid), out var proto);
+
+                // If there's an existing entity and it doesn't match the mask then skip.
+                if (layerProto.EntityMask.Count > 0 &&
+                    (proto == null ||
+                     !layerProto.EntityMask.ContainsKey(proto)))
+                {
+                    continue;
+                }
+
+                // If it's just a flat spawn then just check for anything blocking.
+                if (proto != null && layerProto.Prototype != null)
+                {
+                    continue;
+                }
+
+                DebugTools.Assert(layerProto.EntityMask.Count == 0 || !string.IsNullOrEmpty(proto));
+                remainingTiles.Add(node);
+                nodeEntities.Add(node, existing);
+                nodeMask.Add(node, proto);
+            }
+        }
+
+        var frontier = new ValueList<Vector2i>(32);
+        // TODO: Need poisson but crashes whenever I use moony's due to inputs or smth idk
+        // Get the total amount of groups to spawn across the entire chunk.
+        // We treat a null entity mask as requiring nothing else on the tile
+
+        spawnSet = new Dictionary<Vector2i, string?>();
+        existingEnts = new HashSet<EntityUid>();
+
+        // Iterate the group counts and pathfind out each group.
+        for (var i = 0; i < count; i++)
+        {
+            var groupSize = rand.Next(layerProto.MinGroupSize, layerProto.MaxGroupSize + 1);
+
+            // While we have remaining tiles keep iterating
+            while (groupSize > 0 && remainingTiles.Count > 0)
+            {
+                var startNode = rand.PickAndTake(remainingTiles);
+                frontier.Clear();
+                frontier.Add(startNode);
+
+                // This essentially may lead to a vein being split in multiple areas but the count matters more than position.
+                while (frontier.Count > 0 && groupSize > 0)
+                {
+                    // Need to pick a random index so we don't just get straight lines of ores.
+                    var frontierIndex = rand.Next(frontier.Count);
+                    var node = frontier[frontierIndex];
+                    frontier.RemoveSwap(frontierIndex);
+                    remainingTiles.Remove(node);
+
+                    // Add neighbors if they're valid, worst case we add no more and pick another random seed tile.
+                    for (var x = -1; x <= 1; x++)
+                    {
+                        for (var y = -1; y <= 1; y++)
+                        {
+                            var neighbor = new Vector2i(node.X + x, node.Y + y);
+
+                            if (frontier.Contains(neighbor) || !remainingTiles.Contains(neighbor))
+                                continue;
+
+                            frontier.Add(neighbor);
+                        }
+                    }
+
+                    // Tile valid salad so add it.
+                    var mask = nodeMask[node];
+                    spawnSet.Add(node, mask);
+                    groupSize--;
+
+                    if (nodeEntities.TryGetValue(node, out var existing))
+                    {
+                        Del(existing);
+                    }
+                }
+            }
+
+            if (groupSize > 0)
+            {
+                Log.Warning($"Found remaining group size for ore veins!");
+            }
+        }
+
+        _tilePool.Return(remainingTiles);
+    }
+
+    /// <summary>
+    /// Loads the pre-deteremined marker nodes for a particular chunk.
+    /// This is calculated in <see cref="BuildMarkerChunks"/>
+    /// </summary>
+    /// <remarks>
+    /// Note that the marker chunks do not correspond to this chunk.
+    /// </remarks>
+    private void LoadChunkMarkers(
+        BiomeComponent component,
+        EntityUid gridUid,
+        MapGridComponent grid,
+        Vector2i chunk,
+        int seed)
+    {
+        // Load any pending marker tiles first.
+        if (!component.PendingMarkers.TryGetValue(chunk, out var layers))
+            return;
+
+        // This needs to be done separately in case we try to add a marker layer and want to force it on existing
+        // loaded chunks.
+        component.ModifiedTiles.TryGetValue(chunk, out var modified);
+        modified ??= _tilePool.Get();
+
+        foreach (var (layer, nodes) in layers)
+        {
+            var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
+
+            foreach (var node in nodes)
+            {
+                if (modified.Contains(node))
+                    continue;
+
+                // Need to ensure the tile under it has loaded for anchoring.
+                if (TryGetBiomeTile(node, component.Layers, seed, (gridUid, grid), out var tile))
+                {
+                    _mapSystem.SetTile(gridUid, grid, node, tile.Value);
+                }
+
+                string? prototype;
+
+                if (TryGetEntity(node, component, (gridUid, grid), out var proto) &&
+                    layerProto.EntityMask.TryGetValue(proto, out var maskedProto))
+                {
+                    prototype = maskedProto;
+                }
+                else
+                {
+                    prototype = layerProto.Prototype;
+                }
+
+                // If it is a ghost role then purge it
+                // TODO: This is *kind* of a bandaid but natural mobs spawns needs a lot more work.
+                // Ideally we'd just have ghost role and non-ghost role variants for some stuff.
+                var uid = EntityManager.CreateEntityUninitialized(prototype, _mapSystem.GridTileToLocal(gridUid, grid, node));
+                RemComp<GhostTakeoverAvailableComponent>(uid);
+                RemComp<GhostRoleComponent>(uid);
+                EntityManager.InitializeAndStartEntity(uid);
+                modified.Add(node);
+            }
+        }
+
+        if (modified.Count == 0)
+        {
+            component.ModifiedTiles.Remove(chunk);
+            _tilePool.Return(modified);
+        }
+
+        component.PendingMarkers.Remove(chunk);
+    }
+
+    /// <summary>
+    /// Loads a particular queued chunk for a biome.
+    /// </summary>
+    private void LoadChunk(
+        BiomeComponent component,
+        EntityUid gridUid,
+        MapGridComponent grid,
+        Vector2i chunk,
+        int seed)
+    {
+        component.ModifiedTiles.TryGetValue(chunk, out var modified);
+        modified ??= _tilePool.Get();
+        _tiles.Clear();
+
+        // Set tiles first
+        for (var x = 0; x < ChunkSize; x++)
+        {
+            for (var y = 0; y < ChunkSize; y++)
+            {
+                var indices = new Vector2i(x + chunk.X, y + chunk.Y);
+
+                // Pass in null so we don't try to get the tileref.
+                if (modified.Contains(indices))
+                    continue;
+
+                // If there's existing data then don't overwrite it.
+                if (_mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
+                    continue;
+
+                if (!TryGetBiomeTile(indices, component.Layers, seed, (gridUid, grid), out var biomeTile))
+                    continue;
+
+                _tiles.Add((indices, biomeTile.Value));
+            }
+        }
+
+        _mapSystem.SetTiles(gridUid, grid, _tiles);
+        _tiles.Clear();
+
+        // Now do entities
+        var loadedEntities = new Dictionary<EntityUid, Vector2i>();
+        component.LoadedEntities.Add(chunk, loadedEntities);
+
+        for (var x = 0; x < ChunkSize; x++)
+        {
+            for (var y = 0; y < ChunkSize; y++)
+            {
+                var indices = new Vector2i(x + chunk.X, y + chunk.Y);
+
+                if (modified.Contains(indices))
+                    continue;
+
+                // Don't mess with anything that's potentially anchored.
+                var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
+
+                if (anchored.MoveNext(out _) || !TryGetEntity(indices, component, (gridUid, grid), out var entPrototype))
+                    continue;
+
+                // TODO: Fix non-anchored ents spawning.
+                // Just track loaded chunks for now.
+                var ent = Spawn(entPrototype, _mapSystem.GridTileToLocal(gridUid, grid, indices));
+
+                // At least for now unless we do lookups or smth, only work with anchoring.
+                if (_xformQuery.TryGetComponent(ent, out var xform) && !xform.Anchored)
+                {
+                    _transform.AnchorEntity((ent, xform), (gridUid, grid), indices);
+                }
+
+                loadedEntities.Add(ent, indices);
+            }
+        }
+
+        // Decals
+        var loadedDecals = new Dictionary<uint, Vector2i>();
+        component.LoadedDecals.Add(chunk, loadedDecals);
+
+        for (var x = 0; x < ChunkSize; x++)
+        {
+            for (var y = 0; y < ChunkSize; y++)
+            {
+                var indices = new Vector2i(x + chunk.X, y + chunk.Y);
+
+                if (modified.Contains(indices))
+                    continue;
+
+                // Don't mess with anything that's potentially anchored.
+                var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
+
+                if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, seed, (gridUid, grid), out var decals))
+                    continue;
+
+                foreach (var decal in decals)
+                {
+                    if (!_decals.TryAddDecal(decal.ID, new EntityCoordinates(gridUid, decal.Position), out var dec))
+                        continue;
+
+                    loadedDecals.Add(dec, indices);
+                }
+            }
+        }
+
+        if (modified.Count == 0)
+        {
+            _tilePool.Return(modified);
+            component.ModifiedTiles.Remove(chunk);
+        }
+        else
+        {
+            component.ModifiedTiles[chunk] = modified;
+        }
+    }
+
+    #endregion
+
+    #region Unload
+
+    /// <summary>
+    /// Handles all of the queued chunk unloads for a particular biome.
+    /// </summary>
+    private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, int seed)
+    {
+        var active = _activeChunks[component];
+        List<(Vector2i, Tile)>? tiles = null;
+
+        foreach (var chunk in component.LoadedChunks)
+        {
+            if (active.Contains(chunk) || !component.LoadedChunks.Remove(chunk))
+                continue;
+
+            // Unload NOW!
+            tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
+            UnloadChunk(component, gridUid, grid, chunk, seed, tiles);
+        }
+    }
+
+    /// <summary>
+    /// Unloads a specific biome chunk.
+    /// </summary>
+    private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, int seed, List<(Vector2i, Tile)> tiles)
+    {
+        // Reverse order to loading
+        component.ModifiedTiles.TryGetValue(chunk, out var modified);
+        modified ??= new HashSet<Vector2i>();
+
+        // Delete decals
+        foreach (var (dec, indices) in component.LoadedDecals[chunk])
+        {
+            // If we couldn't remove it then flag the tile to never be touched.
+            if (!_decals.RemoveDecal(gridUid, dec))
+            {
+                modified.Add(indices);
+            }
+        }
+
+        component.LoadedDecals.Remove(chunk);
+
+        // Delete entities
+        // Ideally any entities that aren't modified just get deleted and re-generated later
+        // This is because if we want to save the map (e.g. persistent server) it makes the file much smaller
+        // and also if the map is enormous will make stuff like physics broadphase much faster
+        var xformQuery = GetEntityQuery<TransformComponent>();
+
+        foreach (var (ent, tile) in component.LoadedEntities[chunk])
+        {
+            if (Deleted(ent) || !xformQuery.TryGetComponent(ent, out var xform))
+            {
+                modified.Add(tile);
+                continue;
+            }
+
+            // It's moved
+            var entTile = _mapSystem.LocalToTile(gridUid, grid, xform.Coordinates);
+
+            if (!xform.Anchored || entTile != tile)
+            {
+                modified.Add(tile);
+                continue;
+            }
+
+            if (!EntityManager.IsDefault(ent))
+            {
+                modified.Add(tile);
+                continue;
+            }
+
+            Del(ent);
+        }
+
+        component.LoadedEntities.Remove(chunk);
+
+        // Unset tiles (if the data is custom)
+
+        for (var x = 0; x < ChunkSize; x++)
+        {
+            for (var y = 0; y < ChunkSize; y++)
+            {
+                var indices = new Vector2i(x + chunk.X, y + chunk.Y);
+
+                if (modified.Contains(indices))
+                    continue;
+
+                // Don't mess with anything that's potentially anchored.
+                var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
+
+                if (anchored.MoveNext(out _))
+                {
+                    modified.Add(indices);
+                    continue;
+                }
+
+                // If it's default data unload the tile.
+                if (!TryGetBiomeTile(indices, component.Layers, seed, null, out var biomeTile) ||
+                    _mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
+                {
+                    modified.Add(indices);
+                    continue;
+                }
+
+                tiles.Add((indices, Tile.Empty));
+            }
+        }
+
+        _mapSystem.SetTiles(gridUid, grid, tiles);
+        tiles.Clear();
+        component.LoadedChunks.Remove(chunk);
+
+        if (modified.Count == 0)
+        {
+            component.ModifiedTiles.Remove(chunk);
+        }
+        else
+        {
+            component.ModifiedTiles[chunk] = modified;
+        }
+    }
+
+    #endregion
+
+    /// <summary>
+    /// Creates a simple planet setup for a map.
+    /// </summary>
+    public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null)
+    {
+        if (!Resolve(mapUid, ref metadata))
+            return;
+
+        EnsureComp<MapGridComponent>(mapUid);
+        var biome = EntityManager.ComponentFactory.GetComponent<BiomeComponent>();
+        seed ??= _random.Next();
+        SetSeed(mapUid, biome, seed.Value, false);
+        SetTemplate(mapUid, biome, biomeTemplate, false);
+        AddComp(mapUid, biome, true);
+        Dirty(mapUid, biome, metadata);
+
+        var gravity = EnsureComp<GravityComponent>(mapUid);
+        gravity.Enabled = true;
+        gravity.Inherent = true;
+        Dirty(mapUid, gravity, metadata);
+
+        // Day lighting
+        // Daylight: #D8B059
+        // Midday: #E6CB8B
+        // Moonlight: #2b3143
+        // Lava: #A34931
+        var light = EnsureComp<MapLightComponent>(mapUid);
+        light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059");
+        Dirty(mapUid, light, metadata);
+
+        EnsureComp<RoofComponent>(mapUid);
+
+        EnsureComp<LightCycleComponent>(mapUid);
+
+        EnsureComp<SunShadowComponent>(mapUid);
+        EnsureComp<SunShadowCycleComponent>(mapUid);
+
+        var moles = new float[Atmospherics.AdjustedNumberOfGases];
+        moles[(int)Gas.Oxygen] = 21.824779f;
+        moles[(int)Gas.Nitrogen] = 82.10312f;
+
+        var mixture = new GasMixture(moles, Atmospherics.T20C);
+
+        _atmos.SetMapAtmosphere(mapUid, false, mixture);
+    }
+
+    /// <summary>
+    /// Sets the specified tiles as relevant and marks them as modified.
+    /// </summary>
+    public void ReserveTiles(EntityUid mapUid, Box2 bounds, List<(Vector2i Index, Tile Tile)> tiles, BiomeComponent? biome = null, MapGridComponent? mapGrid = null)
+    {
+        if (!Resolve(mapUid, ref biome, ref mapGrid, false))
+            return;
+
+        foreach (var tileSet in _mapSystem.GetLocalTilesIntersecting(mapUid, mapGrid, bounds, false))
+        {
+            Vector2i chunkOrigin;
+            HashSet<Vector2i> modified;
+
+            // Existing, ignore
+            if (_mapSystem.TryGetTileRef(mapUid, mapGrid, tileSet.GridIndices, out var existingRef) && !existingRef.Tile.IsEmpty)
+            {
+                chunkOrigin = SharedMapSystem.GetChunkIndices(tileSet.GridIndices, ChunkSize) * ChunkSize;
+                modified = biome.ModifiedTiles.GetOrNew(chunkOrigin);
+                modified.Add(tileSet.GridIndices);
+                continue;
+            }
+
+            if (!TryGetBiomeTile(tileSet.GridIndices, biome.Layers, biome.Seed, (mapUid, mapGrid), out var tile))
+            {
+                continue;
+            }
+
+            chunkOrigin = SharedMapSystem.GetChunkIndices(tileSet.GridIndices, ChunkSize) * ChunkSize;
+            modified = biome.ModifiedTiles.GetOrNew(chunkOrigin);
+            modified.Add(tileSet.GridIndices);
+            tiles.Add((tileSet.GridIndices, tile.Value));
+        }
+
+        _mapSystem.SetTiles(mapUid, mapGrid, tiles);
+    }
+}
diff --git a/Content.Server/Procedural/BiomeSystem.Commands.cs b/Content.Server/Procedural/BiomeSystem.Commands.cs
deleted file mode 100644 (file)
index ef90310..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-using Content.Server.Administration;
-using Content.Shared.Administration;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.Components;
-using Robust.Shared.Console;
-using Robust.Shared.Map;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Procedural;
-
-public sealed partial class BiomeSystem
-{
-    [AdminCommand(AdminFlags.Mapping)]
-    public sealed class BiomeAddLayerCommand : LocalizedEntityCommands
-    {
-        [Dependency] private readonly IPrototypeManager _protoManager = default!;
-
-        public override string Command => "biome_addlayer";
-        public override void Execute(IConsoleShell shell, string argStr, string[] args)
-        {
-            if (args.Length != 3)
-            {
-                shell.WriteError(Help);
-                return;
-            }
-
-            if (!int.TryParse(args[0], out var entInt))
-            {
-                return;
-            }
-
-            var entId = new EntityUid(entInt);
-
-            if (!EntityManager.TryGetComponent(entId, out BiomeComponent? biome))
-            {
-                return;
-            }
-
-            if (!_protoManager.TryIndex(args[2], out DungeonConfigPrototype? config))
-            {
-                return;
-            }
-
-            var system = EntityManager.System<BiomeSystem>();
-            system.AddLayer((entId, biome), args[1], config);
-        }
-
-        public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
-        {
-            switch (args.Length)
-            {
-                case 1:
-                    return CompletionResult.FromOptions(CompletionHelper.Components<BiomeComponent>(args[0]));
-                case 2:
-                    return CompletionResult.FromHint("layerId");
-                case 3:
-                    return CompletionResult.FromOptions(CompletionHelper.PrototypeIDs<DungeonConfigPrototype>());
-            }
-
-            return CompletionResult.Empty;
-        }
-    }
-
-    [AdminCommand(AdminFlags.Mapping)]
-    public sealed class BiomeRemoveLayerCommand : LocalizedEntityCommands
-    {
-        public override string Command => "biome_removelayer";
-        public override void Execute(IConsoleShell shell, string argStr, string[] args)
-        {
-            if (args.Length != 2)
-            {
-                shell.WriteError(Help);
-                return;
-            }
-
-            if (!int.TryParse(args[0], out var entInt))
-            {
-                return;
-            }
-
-            var entId = new EntityUid(entInt);
-
-            if (!EntityManager.TryGetComponent(entId, out BiomeComponent? biome))
-            {
-                return;
-            }
-
-            var system = EntityManager.System<BiomeSystem>();
-            system.RemoveLayer((entId, biome), args[1]);
-        }
-
-        public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
-        {
-            switch (args.Length)
-            {
-                case 0:
-                    return CompletionResult.FromOptions(CompletionHelper.Components<BiomeComponent>(args[0]));
-                case 1:
-                    return CompletionResult.FromHint("layerId");
-            }
-
-            return CompletionResult.Empty;
-        }
-    }
-}
diff --git a/Content.Server/Procedural/BiomeSystem.Planet.cs b/Content.Server/Procedural/BiomeSystem.Planet.cs
deleted file mode 100644 (file)
index c5138ca..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-using Content.Shared.Atmos;
-using Content.Shared.Gravity;
-using Content.Shared.Light.Components;
-using Content.Shared.Procedural.Components;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Prototypes;
-
-namespace Content.Server.Procedural;
-
-public sealed partial class BiomeSystem
-{
-    /// <summary>
-    /// Copies the biomecomponent to the specified map.
-    /// </summary>
-    public BiomeComponent? AddBiome(Entity<BiomeComponent?> mapUid, EntProtoId biomeTemplate, int? seed = null)
-    {
-        if (!_protomanager.Index(biomeTemplate).Components.TryGetComponent(Factory.GetComponentName<BiomeComponent>(), out var template))
-        {
-            return null;
-        }
-
-        var biome = Factory.GetComponent<BiomeComponent>();
-        var biomeObj = (object)biome;
-        _serManager.CopyTo(template, ref biomeObj, notNullableOverride: true);
-        seed ??= _random.Next();
-        biome.Seed = seed.Value;
-        AddComp(mapUid, biome, true);
-        return biome;
-    }
-
-    /// <summary>
-    /// Creates a simple planet setup for a map.
-    /// </summary>
-    public void EnsurePlanet(EntityUid mapUid, EntProtoId biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null)
-    {
-        if (!Resolve(mapUid, ref metadata))
-            return;
-
-        EnsureComp<MapGridComponent>(mapUid);
-        AddBiome(mapUid, biomeTemplate, seed);
-        var gravity = EnsureComp<GravityComponent>(mapUid);
-        gravity.Enabled = true;
-        gravity.Inherent = true;
-        Dirty(mapUid, gravity, metadata);
-
-        var light = EnsureComp<MapLightComponent>(mapUid);
-        light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059");
-        Dirty(mapUid, light, metadata);
-
-        EnsureComp<RoofComponent>(mapUid);
-
-        EnsureComp<LightCycleComponent>(mapUid);
-
-        EnsureComp<SunShadowComponent>(mapUid);
-        EnsureComp<SunShadowCycleComponent>(mapUid);
-
-        var moles = new float[Atmospherics.AdjustedNumberOfGases];
-        moles[(int)Gas.Oxygen] = 21.824779f;
-        moles[(int)Gas.Nitrogen] = 82.10312f;
-
-        var mixture = new GasMixture(moles, Atmospherics.T20C);
-
-        _atmos.SetMapAtmosphere(mapUid, false, mixture);
-    }
-}
diff --git a/Content.Server/Procedural/BiomeSystem.cs b/Content.Server/Procedural/BiomeSystem.cs
deleted file mode 100644 (file)
index 6d584ad..0000000
+++ /dev/null
@@ -1,661 +0,0 @@
-using System.Numerics;
-using System.Threading;
-using System.Threading.Tasks;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Decals;
-using Content.Server.Shuttles.Events;
-using Content.Shared.CCVar;
-using Content.Shared.Decals;
-using Content.Shared.Ghost;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.Components;
-using Content.Shared.Procedural.DungeonGenerators;
-using Content.Shared.Sprite;
-using Content.Shared.Tag;
-using Robust.Server.Player;
-using Robust.Shared.Configuration;
-using Robust.Shared.CPUJob.JobQueues;
-using Robust.Shared.CPUJob.JobQueues.Queues;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Map.Enumerators;
-using Robust.Shared.Physics.Components;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Serialization.Manager;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Procedural;
-
-public sealed partial class BiomeSystem : EntitySystem
-{
-    /*
-     * Handles loading in biomes around players.
-     * These are essentially chunked-areas that load in dungeons and can also be unloaded.
-     */
-
-    [Dependency] private readonly IConfigurationManager _cfgManager = default!;
-    [Dependency] private readonly IPlayerManager _player = default!;
-    [Dependency] private readonly IPrototypeManager _protomanager = default!;
-    [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly ISerializationManager _serManager = default!;
-    [Dependency] private readonly AtmosphereSystem _atmos = default!;
-    [Dependency] private readonly SharedMapSystem _maps = default!;
-    [Dependency] private readonly TagSystem _tags = default!;
-    [Dependency] private readonly SharedTransformSystem _xforms = default!;
-
-    /// <summary>
-    /// Ignored components for default checks
-    /// </summary>
-    public static readonly List<string> IgnoredComponents = new();
-
-    /// <summary>
-    /// Jobs for biomes to load.
-    /// </summary>
-    private JobQueue _biomeQueue = default!;
-
-    private float _loadRange = 1f;
-    private float _loadTime;
-
-    private EntityQuery<GhostComponent> _ghostQuery;
-    private EntityQuery<BiomeComponent> _biomeQuery;
-
-    private static readonly ProtoId<TagPrototype> AllowBiomeLoadingTag = "AllowBiomeLoading";
-
-    public override void Initialize()
-    {
-        base.Initialize();
-        _ghostQuery = GetEntityQuery<GhostComponent>();
-        _biomeQuery = GetEntityQuery<BiomeComponent>();
-
-        IgnoredComponents.Add(Factory.GetComponentName<RandomSpriteComponent>());
-
-        Subs.CVar(_cfgManager, CCVars.BiomeLoadRange, OnLoadRange, true);
-        Subs.CVar(_cfgManager, CCVars.BiomeLoadTime, OnLoadTime, true);
-
-        SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
-    }
-
-    private void OnLoadTime(float obj)
-    {
-        _biomeQueue = new JobQueue(obj);
-        _loadTime = obj;
-    }
-
-    private void OnLoadRange(float obj)
-    {
-        _loadRange = obj;
-    }
-
-    private void OnFTLStarted(ref FTLStartedEvent ev)
-    {
-        var targetMap = _xforms.ToMapCoordinates(ev.TargetCoordinates);
-        var targetMapUid = _maps.GetMapOrInvalid(targetMap.MapId);
-
-        if (!TryComp<BiomeComponent>(targetMapUid, out var biome))
-            return;
-
-        var preloadArea = new Vector2(32f, 32f);
-        var targetArea = new Box2(targetMap.Position - preloadArea, targetMap.Position + preloadArea);
-        Preload(targetMapUid, biome, (Box2i) targetArea);
-    }
-
-    /// <summary>
-    /// Preloads biome for the specified area.
-    /// </summary>
-    public void Preload(EntityUid uid, BiomeComponent component, Box2i area)
-    {
-        component.PreloadAreas.Add(area);
-    }
-
-    private bool CanLoad(EntityUid uid)
-    {
-        return !_ghostQuery.HasComp(uid) || _tags.HasTag(uid, AllowBiomeLoadingTag);
-    }
-
-    public bool RemoveLayer(Entity<BiomeComponent?> biome, string label)
-    {
-        if (!Resolve(biome.Owner, ref biome.Comp))
-            return false;
-
-        if (!biome.Comp.Layers.ContainsKey(label))
-        {
-            return false;
-        }
-
-        // Technically this can race-condition with adds but uhh tell people to not do that.
-        biome.Comp.PendingRemovals.Add(label);
-        return true;
-    }
-
-    public void AddLayer(Entity<BiomeComponent?> biome, string label, BiomeMetaLayer layer)
-    {
-        if (!Resolve(biome.Owner, ref biome.Comp))
-            return;
-
-        if (!biome.Comp.Layers.TryAdd(label, layer))
-        {
-            Log.Warning($"Tried to add layer {label} to biome {ToPrettyString(biome)} that already has it?");
-            return;
-        }
-    }
-
-    public void AddLayer(Entity<BiomeComponent?> biome, string label, ProtoId<DungeonConfigPrototype> layer)
-    {
-        if (!Resolve(biome.Owner, ref biome.Comp))
-            return;
-
-        var metaLayer = new BiomeMetaLayer()
-        {
-            Dungeon = layer,
-        };
-
-        AddLayer(biome, label, metaLayer);
-    }
-
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        var query = AllEntityQuery<BiomeComponent>();
-
-        while (query.MoveNext(out var biome))
-        {
-            // If it's still loading then don't touch the observer bounds.
-            if (biome.Loading)
-                continue;
-
-            biome.LoadedBounds.Clear();
-
-            // Make sure preloads go in.
-            foreach (var preload in biome.PreloadAreas)
-            {
-                biome.LoadedBounds.Add(preload);
-            }
-        }
-
-        // Get all relevant players.
-        foreach (var player in _player.Sessions)
-        {
-            if (player.AttachedEntity != null)
-            {
-                TryAddBiomeBounds(player.AttachedEntity.Value);
-            }
-
-            foreach (var viewer in player.ViewSubscriptions)
-            {
-                TryAddBiomeBounds(viewer);
-            }
-        }
-
-        // Unload first in case we can't catch up.
-        UnloadChunks();
-
-        var loadQuery = AllEntityQuery<BiomeComponent, MapGridComponent>();
-
-        // Check if any biomes are intersected and queue up loads.
-        while (loadQuery.MoveNext(out var uid, out var biome, out var grid))
-        {
-            if (biome.Loading || biome.LoadedBounds.Count == 0)
-                continue;
-
-            biome.Loading = true;
-            var job = new BiomeLoadJob(_loadTime)
-            {
-                Grid = (uid, biome, grid),
-            };
-            _biomeQueue.EnqueueJob(job);
-        }
-
-        // Process jobs.
-        _biomeQueue.Process();
-    }
-
-    private void UnloadChunks()
-    {
-        var query = AllEntityQuery<BiomeComponent, MapGridComponent>();
-
-        while (query.MoveNext(out var uid, out var biome, out var grid))
-        {
-            // Only start unloading if it's currently not loading anything.
-            if (biome.Loading)
-                continue;
-
-            var toUnload = new Dictionary<string, List<Vector2i>>();
-
-            foreach (var (layerId, loadedLayer) in biome.LoadedData)
-            {
-                var layer = biome.Layers[layerId];
-
-                // If it can't unload then ignore it.
-                if (!layer.CanUnload)
-                    continue;
-
-                var size = GetSize(_protomanager.Index(layer.Dungeon), layer);
-
-                if (size == null)
-                    continue;
-
-                // Go through each loaded chunk and check if they can be unloaded by checking if any players are in range.
-                foreach (var chunk in loadedLayer.Keys)
-                {
-                    var chunkBounds = new Box2i(chunk, chunk + size.Value);
-                    var canUnload = true;
-
-                    foreach (var playerView in biome.LoadedBounds)
-                    {
-                        // Give a buffer range so we don't immediately unload if we wiggle, we'll just double the load area.
-                        var enlarged = playerView.Enlarged((int) _loadRange);
-
-                        // Still relevant
-                        if (chunkBounds.Intersects(enlarged))
-                        {
-                            canUnload = false;
-                            break;
-                        }
-                    }
-
-                    if (!canUnload)
-                        continue;
-
-                    toUnload.GetOrNew(layerId).Add(chunk);
-                }
-            }
-
-            if (biome.PendingRemovals.Count > 0)
-            {
-                foreach (var label in biome.PendingRemovals)
-                {
-                    var bounds = toUnload.GetOrNew(label);
-
-                    foreach (var chunkOrigin in biome.LoadedData[label].Keys)
-                    {
-                        bounds.Add(chunkOrigin);
-                    }
-                }
-            }
-
-            if (toUnload.Count == 0)
-                continue;
-
-            // Queue up unloads.
-            biome.Loading = true;
-            var job = new BiomeUnloadJob(_loadTime)
-            {
-                Biome = (uid, grid, biome),
-                ToUnload = toUnload,
-            };
-            _biomeQueue.EnqueueJob(job);
-        }
-    }
-
-    /// <summary>
-    /// Gets the full bounds to be loaded. Considers layer dependencies where they may have different chunk sizes.
-    /// </summary>
-    private Box2i GetFullBounds(BiomeComponent component, Box2i bounds)
-    {
-        var result = bounds;
-
-        foreach (var layer in component.Layers.Values)
-        {
-            var layerBounds = GetLayerBounds(layer, result);
-
-            if (layer.DependsOn != null)
-            {
-                foreach (var sub in layer.DependsOn)
-                {
-                    var depLayer = component.Layers[sub];
-
-                    layerBounds = layerBounds.Union(GetLayerBounds(depLayer, layerBounds));
-                }
-            }
-
-            result = result.Union(layerBounds);
-        }
-
-        return result;
-    }
-
-    /// <summary>
-    /// Tries to add the viewer bounds of this entity for loading.
-    /// </summary>
-    private void TryAddBiomeBounds(EntityUid uid)
-    {
-        if (!CanLoad(uid))
-            return;
-
-        var xform = Transform(uid);
-
-        // No biome to load
-        if (!_biomeQuery.TryComp(xform.MapUid, out var biome))
-            return;
-
-        // Currently already loading.
-        if (biome.Loading)
-            return;
-
-        var center = _xforms.GetWorldPosition(uid);
-
-        var bounds = new Box2i((center - new Vector2(_loadRange, _loadRange)).Floored(), (center + new Vector2(_loadRange, _loadRange)).Floored());
-
-        // If it's moving then preload in that direction
-        if (TryComp(uid, out PhysicsComponent? physics))
-        {
-            bounds = bounds.Union(bounds.Translated((physics.LinearVelocity * 2f).Floored()));
-        }
-
-        var adjustedBounds = GetFullBounds(biome, bounds);
-        biome.LoadedBounds.Add(adjustedBounds);
-    }
-
-    public int? GetSize(DungeonConfigPrototype config, BiomeMetaLayer layer)
-    {
-        var size = layer.Size;
-
-        if (size == null && config.Layers[0] is ChunkDunGen chunkGen)
-        {
-            size = chunkGen.Size;
-        }
-        // No size
-        else
-        {
-            Log.Warning($"Unable to infer chunk size for biome {layer} / config {config.ID}");
-            return null;
-        }
-
-        return size.Value;
-    }
-
-    public Box2i GetLayerBounds(BiomeMetaLayer layer, Box2i layerBounds)
-    {
-        var size = GetSize(_protomanager.Index(layer.Dungeon), layer);
-
-        if (size == null)
-            return Box2i.Empty;
-
-        var chunkSize = new Vector2(size.Value, size.Value);
-
-        // Need to round the bounds to our chunk size to ensure we load whole chunks.
-        // We also need to know the minimum bounds for our dependencies to load.
-        var layerBL = (layerBounds.BottomLeft / chunkSize).Floored() * chunkSize;
-        var layerTR = (layerBounds.TopRight / chunkSize).Ceiled() * chunkSize;
-
-        var loadBounds = new Box2i(layerBL.Floored(), layerTR.Ceiled());
-        return loadBounds;
-    }
-}
-
- public sealed class BiomeLoadJob : Job<bool>
- {
-     [Dependency] private IEntityManager _entManager = default!;
-     [Dependency] private IPrototypeManager _protoManager = default!;
-
-     private BiomeSystem System = default!;
-     private DungeonSystem DungeonSystem = default!;
-
-     public Entity<BiomeComponent, MapGridComponent> Grid;
-
-     internal ISawmill _sawmill = default!;
-
-     public BiomeLoadJob(double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation)
-     {
-        IoCManager.InjectDependencies(this);
-        System = _entManager.System<BiomeSystem>();
-        DungeonSystem = _entManager.System<DungeonSystem>();
-     }
-
-    protected override async Task<bool> Process()
-    {
-        try
-        {
-            foreach (var bound in Grid.Comp1.LoadedBounds)
-            {
-                foreach (var (layerId, layer) in Grid.Comp1.Layers)
-                {
-                    await LoadLayer(layerId, layer, bound);
-                }
-            }
-        }
-        finally
-        {
-            // Finished
-            DebugTools.Assert(Grid.Comp1.Loading);
-            Grid.Comp1.Loading = false;
-        }
-
-        // If we have any preloads then mark those as modified so they persist.
-        foreach (var preload in Grid.Comp1.PreloadAreas)
-        {
-            for (var x = preload.Left; x <= preload.Right; x++)
-            {
-                for (var y = preload.Bottom; y <= preload.Top; y++)
-                {
-                    var index = new Vector2i(x, y);
-                    Grid.Comp1.ModifiedTiles.Add(index);
-                }
-            }
-
-            await SuspendIfOutOfTime();
-        }
-
-        Grid.Comp1.PreloadAreas.Clear();
-
-        return true;
-    }
-
-    private async Task LoadLayer(string layerId, BiomeMetaLayer layer, Box2i parentBounds)
-    {
-        // Nothing to do
-        var dungeon = _protoManager.Index(layer.Dungeon);
-
-        if (dungeon.Layers.Count == 0)
-            return;
-
-        var loadBounds = System.GetLayerBounds(layer, parentBounds);
-
-        // Make sure our dependencies are loaded first.
-        if (layer.DependsOn != null)
-        {
-            foreach (var sub in layer.DependsOn)
-            {
-                var actualLayer = Grid.Comp1.Layers[sub];
-
-                await LoadLayer(sub, actualLayer, loadBounds);
-            }
-        }
-
-        var size = System.GetSize(dungeon, layer);
-
-        if (size == null)
-            return;
-
-        // The reason we do this is so if we dynamically add similar layers (e.g. we add 3 mob layers at runtime)
-        // they don't all have the same seeds.
-        var layerSeed = Grid.Comp1.Seed + layerId.GetHashCode();
-
-        // Okay all of our dependencies loaded so we can send it.
-        var chunkEnumerator = new NearestChunkEnumerator(loadBounds, size.Value);
-
-        while (chunkEnumerator.MoveNext(out var chunk))
-        {
-            var chunkOrigin = chunk.Value;
-            var layerLoaded = Grid.Comp1.LoadedData.GetOrNew(layerId);
-
-            // Layer already loaded for this chunk.
-            // This can potentially happen if we're moving and the player's bounds changed but some existing chunks remain.
-            if (layerLoaded.ContainsKey(chunkOrigin))
-            {
-                continue;
-            }
-
-            // Load dungeon here async await and all that jaz.
-            var (_, data) = await WaitAsyncTask(DungeonSystem
-                .GenerateDungeonAsync(dungeon, Grid.Owner, Grid.Comp2, chunkOrigin, layerSeed, reservedTiles: Grid.Comp1.ModifiedTiles));
-
-            // If we can unload it then store the data to check for later.
-            if (layer.CanUnload)
-            {
-                layerLoaded.Add(chunkOrigin, data);
-            }
-        }
-    }
-}
-
-public sealed class BiomeUnloadJob : Job<bool>
-{
-    [Dependency] private EntityManager _entManager = default!;
-
-    public Entity<MapGridComponent, BiomeComponent> Biome;
-    public Dictionary<string, List<Vector2i>> ToUnload = default!;
-
-    public BiomeUnloadJob(double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation)
-    {
-        IoCManager.InjectDependencies(this);
-    }
-
-    public BiomeUnloadJob(double maxTime, IStopwatch stopwatch, CancellationToken cancellation = default) : base(maxTime, stopwatch, cancellation)
-    {
-    }
-
-    protected override async Task<bool> Process()
-    {
-        try
-        {
-            var grid = Biome.Comp1;
-            var biome = Biome.Comp2;
-            DebugTools.Assert(biome.Loading);
-            var maps = _entManager.System<SharedMapSystem>();
-            var decals = _entManager.System<DecalSystem>();
-            var lookup = _entManager.System<EntityLookupSystem>();
-            _entManager.TryGetComponent(Biome.Owner, out DecalGridComponent? decalGrid);
-            var forceUnload = _entManager.GetEntityQuery<BiomeForceUnloadComponent>();
-            var entities = new HashSet<EntityUid>();
-            var tiles = new List<(Vector2i, Tile)>();
-
-            foreach (var (layer, chunkOrigins) in ToUnload)
-            {
-                if (!biome.Layers.TryGetValue(layer, out var meta))
-                    continue;
-
-                if (!biome.LoadedData.TryGetValue(layer, out var data))
-                    continue;
-
-                DebugTools.Assert(meta.CanUnload);
-
-                foreach (var chunk in chunkOrigins)
-                {
-                    // Not loaded anymore?
-                    if (!data.Remove(chunk, out var loaded))
-                        continue;
-
-                    tiles.Clear();
-
-                    foreach (var (ent, pos) in loaded.Entities)
-                    {
-                        // Already flagged as modified so go next.
-                        if (biome.ModifiedTiles.Contains(pos))
-                        {
-                            continue;
-                        }
-
-                        // IsDefault is actually super expensive so really need to run this check in the loop.
-                        await SuspendIfOutOfTime();
-
-                        if (forceUnload.HasComp(ent))
-                        {
-                            _entManager.DeleteEntity(ent);
-                            continue;
-                        }
-
-                        // Deleted so counts as modified.
-                        if (!_entManager.TransformQuery.TryComp(ent, out var xform))
-                        {
-                            biome.ModifiedTiles.Add(pos);
-                            continue;
-                        }
-
-                        // If it stayed still and had no data change then keep it.
-                        if (pos == xform.LocalPosition.Floored() && xform.GridUid == Biome.Owner && _entManager.IsDefault(ent, BiomeSystem.IgnoredComponents))
-                        {
-                            _entManager.DeleteEntity(ent);
-                            continue;
-                        }
-
-                        // Need the entity's current tile to be flagged for unloading.
-                        if (Biome.Owner == xform.GridUid)
-                        {
-                            var entTile = maps.LocalToTile(Biome.Owner, grid, xform.Coordinates);
-                            biome.ModifiedTiles.Add(entTile);
-                        }
-                    }
-
-                    foreach (var (decal, pos) in loaded.Decals)
-                    {
-                        // Modified go NEXT
-                        if (biome.ModifiedTiles.Contains(pos.Floored()))
-                            continue;
-
-                        // Should just be able to remove them as you can't actually edit a decal.
-                        if (!decals.RemoveDecal(Biome.Owner, decal, decalGrid))
-                        {
-                            biome.ModifiedTiles.Add(pos.Floored());
-                        }
-                    }
-
-                    await SuspendIfOutOfTime();
-
-                    foreach (var (index, tile) in loaded.Tiles)
-                    {
-                        await SuspendIfOutOfTime();
-
-                        if (Biome.Comp2.ModifiedTiles.Contains(index))
-                        {
-                            continue;
-                        }
-
-                        if (!maps.TryGetTileRef(Biome.Owner, Biome.Comp1, index, out var tileRef) ||
-                            tileRef.Tile != tile)
-                        {
-                            Biome.Comp2.ModifiedTiles.Add(index);
-                            continue;
-                        }
-
-                        entities.Clear();
-                        var tileBounds = lookup.GetLocalBounds(index, Biome.Comp1.TileSize).Enlarged(-0.05f);
-
-                        lookup.GetEntitiesIntersecting(Biome.Owner,
-                            tileBounds,
-                            entities);
-
-                        // Still entities remaining so just leave the tile.
-                        if (entities.Count > 0)
-                        {
-                            Biome.Comp2.ModifiedTiles.Add(index);
-                            continue;
-                        }
-
-                        if (decals.GetDecalsIntersecting(Biome.Owner, tileBounds, component: decalGrid).Count > 0)
-                        {
-                            Biome.Comp2.ModifiedTiles.Add(index);
-                            continue;
-                        }
-
-                        // Clear it
-                        tiles.Add((index, Tile.Empty));
-                    }
-
-                    maps.SetTiles(Biome.Owner, Biome.Comp1, tiles);
-                }
-            }
-        }
-        finally
-        {
-            Biome.Comp2.Loading = false;
-        }
-
-        Biome.Comp2.PendingRemovals.Clear();
-
-        return true;
-    }
-}
index 83ff71f8b75dfaefb55e1a8c4c1224d486b1e28b..8c49a7d606a17a794863586a1b5ce5001161e550 100644 (file)
@@ -151,8 +151,7 @@ public sealed partial class DungeonJob
             if (found)
                 continue;
 
-            var ent = _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
-            AddLoadedEntity(tile, ent);
+            _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
         }
     }
 }
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs
new file mode 100644 (file)
index 0000000..48adb8a
--- /dev/null
@@ -0,0 +1,74 @@
+using System.Threading.Tasks;
+using Content.Server.Parallax;
+using Content.Shared.Maps;
+using Content.Shared.Parallax.Biomes;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.PostGeneration;
+using Robust.Shared.Map;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Procedural.DungeonJob;
+
+public sealed partial class DungeonJob
+{
+    /// <summary>
+    /// <see cref="BiomeDunGen"/>
+    /// </summary>
+    private async Task PostGen(BiomeDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
+    {
+        if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome))
+            return;
+
+        var biomeSystem = _entManager.System<BiomeSystem>();
+
+        var seed = random.Next();
+        var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
+
+        var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
+        while (tiles.MoveNext(out var tileRef))
+        {
+            var node = tileRef.Value.GridIndices;
+
+            if (reservedTiles.Contains(node))
+                continue;
+
+            if (dunGen.TileMask is not null)
+            {
+                if (!dunGen.TileMask.Contains(((ContentTileDefinition)_tileDefManager[tileRef.Value.Tile.TypeId]).ID))
+                    continue;
+            }
+
+            // Need to set per-tile to override data.
+            if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var tile))
+            {
+                _maps.SetTile(_gridUid, _grid, node, tile.Value);
+            }
+
+            if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var decals))
+            {
+                foreach (var decal in decals)
+                {
+                    _decals.TryAddDecal(decal.ID, new EntityCoordinates(_gridUid, decal.Position), out _);
+                }
+            }
+
+            if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Value.Tile, seed, (_gridUid, _grid), out var entityProto))
+            {
+                var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
+                var xform = xformQuery.Get(ent);
+
+                if (!xform.Comp.Anchored)
+                {
+                    _transform.AnchorEntity(ent, xform);
+                }
+
+                // TODO: Engine bug with SpawnAtPosition
+                DebugTools.Assert(xform.Comp.Anchored);
+            }
+
+            await SuspendDungeon();
+            if (!ValidateResume())
+                return;
+        }
+    }
+}
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.BiomeMarkerLayer.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.BiomeMarkerLayer.cs
new file mode 100644 (file)
index 0000000..abc74dd
--- /dev/null
@@ -0,0 +1,105 @@
+using System.Threading.Tasks;
+using Content.Server.Parallax;
+using Content.Shared.Parallax.Biomes;
+using Content.Shared.Parallax.Biomes.Markers;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.PostGeneration;
+using Content.Shared.Random.Helpers;
+using Robust.Shared.Map;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Procedural.DungeonJob;
+
+public sealed partial class DungeonJob
+{
+    /// <summary>
+    /// <see cref="BiomeMarkerLayerDunGen"/>
+    /// </summary>
+    private async Task PostGen(BiomeMarkerLayerDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
+    {
+        // If we're adding biome then disable it and just use for markers.
+        if (_entManager.EnsureComponent(_gridUid, out BiomeComponent biomeComp))
+        {
+            biomeComp.Enabled = false;
+        }
+
+        var biomeSystem = _entManager.System<BiomeSystem>();
+        var weightedRandom = _prototype.Index(dunGen.MarkerTemplate);
+        var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
+        var templates = new Dictionary<string, int>();
+
+        for (var i = 0; i < dunGen.Count; i++)
+        {
+            var template = weightedRandom.Pick(random);
+            var count = templates.GetOrNew(template);
+            count++;
+            templates[template] = count;
+        }
+
+        foreach (var (template, count) in templates)
+        {
+            var markerTemplate = _prototype.Index<BiomeMarkerLayerPrototype>(template);
+
+            var bounds = new Box2i();
+
+            foreach (var tile in dungeon.RoomTiles)
+            {
+                bounds = bounds.UnionTile(tile);
+            }
+
+            await SuspendDungeon();
+            if (!ValidateResume())
+                return;
+
+            biomeSystem.GetMarkerNodes(_gridUid, biomeComp, _grid, markerTemplate, true, bounds, count,
+                random, out var spawnSet, out var existing, false);
+
+            await SuspendDungeon();
+            if (!ValidateResume())
+                return;
+
+            var checkTile = reservedTiles.Count > 0;
+
+            foreach (var ent in existing)
+            {
+                if (checkTile && reservedTiles.Contains(_maps.LocalToTile(_gridUid, _grid, _xformQuery.GetComponent(ent).Coordinates)))
+                {
+                    continue;
+                }
+
+                _entManager.DeleteEntity(ent);
+
+                await SuspendDungeon();
+                if (!ValidateResume())
+                    return;
+            }
+
+            foreach (var (node, mask) in spawnSet)
+            {
+                if (reservedTiles.Contains(node))
+                    continue;
+
+                string? proto;
+
+                if (mask != null && markerTemplate.EntityMask.TryGetValue(mask, out var maskedProto))
+                {
+                    proto = maskedProto;
+                }
+                else
+                {
+                    proto = markerTemplate.Prototype;
+                }
+
+                var ent = _entManager.SpawnAtPosition(proto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
+                var xform = xformQuery.Get(ent);
+
+                if (!xform.Comp.Anchored)
+                    _transform.AnchorEntity(ent, xform);
+
+                await SuspendDungeon();
+                if (!ValidateResume())
+                    return;
+            }
+        }
+    }
+}
index 7bc7527ff5b37263551c9e26bd7b3296e552cda6..1c48a84cce55c65e63b164c67073d6fd09c5605a 100644 (file)
@@ -32,10 +32,7 @@ public sealed partial class DungeonJob
             if (!_anchorable.TileFree((_gridUid, _grid), neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
                 continue;
 
-            var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-            tiles.Add((neighbor, tile));
-            AddLoadedTile(neighbor, tile);
-            DebugTools.Assert(dungeon.AllTiles.Contains(neighbor));
+            tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
         }
 
         foreach (var index in dungeon.CorridorExteriorTiles)
@@ -46,10 +43,7 @@ public sealed partial class DungeonJob
             if (!_anchorable.TileFree((_gridUid, _grid), index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
                 continue;
 
-            var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-            tiles.Add((index, tile));
-            AddLoadedTile(index, tile);
-            DebugTools.Assert(dungeon.AllTiles.Contains(index));
+            tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random)));
         }
 
         _maps.SetTiles(_gridUid, _grid, tiles);
@@ -88,21 +82,18 @@ public sealed partial class DungeonJob
             }
 
             if (isCorner)
-            {
-                var uid = _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
-                AddLoadedEntity(index.Index, uid);
-            }
+                _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
 
             if (!isCorner)
-            {
-                var uid = _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
-                AddLoadedEntity(index.Index, uid);
-            }
+                _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
 
-            await SuspendDungeon();
+            if (i % 20 == 0)
+            {
+                await SuspendDungeon();
 
-            if (!ValidateResume())
-                return;
+                if (!ValidateResume())
+                    return;
+            }
         }
     }
 }
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs
deleted file mode 100644 (file)
index 391fb91..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-using System.Threading.Tasks;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.DungeonGenerators;
-using Content.Shared.Procedural.PostGeneration;
-
-namespace Content.Server.Procedural.DungeonJob;
-
-public sealed partial class DungeonJob
-{
-    /// <summary>
-    /// <see cref="BiomeDunGen"/>
-    /// </summary>
-    private async Task<Dungeon> PostGen(ChunkDunGen dunGen, HashSet<Vector2i> reservedTiles, Random random)
-    {
-        var dungeon = new Dungeon();
-        var tiles = new HashSet<Vector2i>();
-        var tr = _position + new Vector2i(dunGen.Size, dunGen.Size);
-        var oldSeed = dunGen.Noise?.GetSeed() ?? 0;
-        dunGen.Noise?.SetSeed(_seed + oldSeed);
-
-        for (var x = 0; x < dunGen.Size; x++)
-        {
-            for (var y = 0; y < dunGen.Size; y++)
-            {
-                var index = new Vector2i(_position.X + x, _position.Y + y);
-
-                if (reservedTiles.Contains(index))
-                    continue;
-
-                if (dunGen.Noise?.GetNoise(x, y) < dunGen.Threshold)
-                    continue;
-
-                tiles.Add(index);
-            }
-        }
-
-        dunGen.Noise?.SetSeed(oldSeed);
-        var room = new DungeonRoom(tiles, (tr - _position) / 2 + _position, new Box2i(_position, tr), new HashSet<Vector2i>());
-        dungeon.AddRoom(room);
-        return dungeon;
-    }
-}
index 8353d81f1609c1443be99ecd46a7c171ef48620c..bf9f910b94d8d29c7b35af5cc7af825bbad56bc5 100644 (file)
@@ -94,14 +94,12 @@ public sealed partial class DungeonJob
         var setTiles = new List<(Vector2i, Tile)>();
         var tileDef = (ContentTileDefinition) _tileDefManager[gen.Tile];
 
-        foreach (var node in corridorTiles)
+        foreach (var tile in corridorTiles)
         {
-            if (reservedTiles.Contains(node))
+            if (reservedTiles.Contains(tile))
                 continue;
 
-            var tile = _tile.GetVariantTile(tileDef, random);
-            setTiles.Add((node, tile));
-            AddLoadedTile(node, tile);
+            setTiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
         }
 
         _maps.SetTiles(_gridUid, _grid, setTiles);
index e28c6798f2d72a8eadc46d44ae65cbffa452a638..e0be852733d867b0ac355f2dd2b665f6ab975f66 100644 (file)
@@ -48,13 +48,7 @@ public sealed partial class DungeonJob
 
             var protos = _entTable.GetSpawns(contents, random);
             var coords = _maps.ToCenterCoordinates(_gridUid, tile, _grid);
-            var uids = _entManager.SpawnEntitiesAttachedTo(coords, protos);
-
-            foreach (var uid in uids)
-            {
-                AddLoadedEntity(tile, uid);
-            }
-
+            _entManager.SpawnEntitiesAttachedTo(coords, protos);
             await SuspendIfOutOfTime();
 
             if (!ValidateResume())
index 48088a47184e85aa3df86df4086a260d7d7500f5..cd8737e6ec21175f33551889b5b68c4ee3d047b9 100644 (file)
@@ -83,8 +83,7 @@ public sealed partial class DungeonJob
                     {
                         // Decals not being centered biting my ass again
                         var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
-                        _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
-                        AddLoadedDecal(tile, did);
+                        _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
                     }
                 }
 
@@ -97,8 +96,7 @@ public sealed partial class DungeonJob
                 {
                     // Decals not being centered biting my ass again
                     var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
-                    _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
-                    AddLoadedDecal(tile, did);
+                    _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
                 }
 
                 continue;
@@ -113,8 +111,7 @@ public sealed partial class DungeonJob
                 if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir))
                 {
                     var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
-                    _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
-                    AddLoadedDecal(tile, did);
+                    _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
                 }
             }
         }
index 10481c5b5ff09b80fdb64f5e17156b5fb82a2589..f1808ec90cd2c61e450991acf37e1493a9560944 100644 (file)
@@ -28,7 +28,7 @@ public sealed partial class DungeonJob
         Random random)
     {
         var tiles = new List<(Vector2i, Tile)>();
-        var matrix = Matrix3Helpers.CreateTranslation(_position + position);
+        var matrix = Matrix3Helpers.CreateTranslation(position);
 
         foreach (var layer in dungen.Layers)
         {
@@ -76,9 +76,7 @@ public sealed partial class DungeonJob
                         break;
                     }
 
-                    var tile = new Tile(tileDef.TileId, variant: variant);
-                    tiles.Add((adjusted, tile));
-                    AddLoadedTile(adjusted, tile);
+                    tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
                     roomTiles.Add(adjusted);
                     break;
                 }
@@ -103,8 +101,6 @@ public sealed partial class DungeonJob
     {
         switch (distance)
         {
-            case DunGenDistanceSquared:
-                return dx * dx + dy * dy;
             case DunGenEuclideanSquaredDistance:
                 return MathF.Min(1f, (dx * dx + dy * dy) / MathF.Sqrt(2));
             case DunGenSquareBump:
index f348ce730116148eed69ceb7b6e88ac5339412e4..8eb85e2cb863ebd02b49644606aa1d311bb376cd 100644 (file)
@@ -196,9 +196,7 @@ public sealed partial class DungeonJob
                                     if (reservedTiles.Contains(index))
                                         continue;
 
-                                    var tile = new Tile(_tileDefManager[fallbackTile.Value].TileId);
-                                    tiles.Add((index, tile));
-                                    AddLoadedTile(index, tile);
+                                    tiles.Add((index, new Tile(_tileDefManager[fallbackTile.Value].TileId)));
                                 }
                             }
 
@@ -232,14 +230,7 @@ public sealed partial class DungeonJob
                 var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform);
 
                 // The expensive bit yippy.
-                var data = _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
-
-                _data.Merge(data);
-
-                await SuspendDungeon();
-
-                if (!ValidateResume())
-                    return dungeon;
+                _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
 
                 var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize;
                 var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
index 4c53141bc908047083e40e30074c3d90e8b9ea8f..dfc09329151a4943a8788536c61c13737fc7d832 100644 (file)
@@ -42,7 +42,6 @@ public sealed partial class DungeonJob
                     }
 
                     replacements.Add((node, tile));
-                    AddLoadedTile(node, tile);
                     break;
                 }
 
index 35a35e965667e4b0b52d75bce923ea9b322326b0..dceeac3f1248af3f3b6ec7ae51d17f7241ffe4cd 100644 (file)
@@ -71,17 +71,14 @@ public sealed partial class DungeonJob
                     isValid = true;
 
                     // Entrance wew
-                    var tileVariant = _tile.GetVariantTile(tileDef, random);
-                    _maps.SetTile(_gridUid, _grid, tile, tileVariant);
-                    AddLoadedTile(tile, tileVariant);
+                    _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile(tileDef, random));
                     ClearDoor(dungeon, _grid, tile);
                     var gridCoords = _maps.GridTileToLocal(_gridUid, _grid, tile);
                     // Need to offset the spawn to avoid spawning in the room.
 
                     foreach (var ent in _entTable.GetSpawns(contents, random))
                     {
-                        var uid = _entManager.SpawnAtPosition(ent, gridCoords);
-                        AddLoadedEntity(tile, uid);
+                        _entManager.SpawnAtPosition(ent, gridCoords);
                     }
 
                     // Clear out any biome tiles nearby to avoid blocking it
index 02686f7a26a13764aecbd0617d7d5413d0f3acc9..6483448240b7e0dddaafa6fb944309a97a8338b5 100644 (file)
@@ -51,7 +51,6 @@ public sealed partial class DungeonJob
                 foreach (var ent in entities)
                 {
                     var uid = _entManager.SpawnAtPosition(ent, _maps.GridTileToLocal(_gridUid, _grid, tile));
-                    AddLoadedEntity(tile, uid);
                     _entManager.RemoveComponent<GhostRoleComponent>(uid);
                     _entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
                     npcs.SleepNPC(uid);
index 831081cdf1945fa3a4f36ca999620153ca51d033..1788c23cae3962aae8f885ecace50bd76dfc99f9 100644 (file)
@@ -35,9 +35,7 @@ public sealed partial class DungeonJob
                     if (reservedTiles.Contains(neighbor))
                         continue;
 
-                    var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-                    tiles.Add((neighbor, tile));
-                    AddLoadedTile(neighbor, tile);
+                    tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
                     spawnPositions.Add(neighbor);
                 }
             }
@@ -47,12 +45,7 @@ public sealed partial class DungeonJob
 
         foreach (var entrance in spawnPositions)
         {
-            var uids = _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
-
-            foreach (var uid in uids)
-            {
-                AddLoadedEntity(entrance, uid);
-            }
+            _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
         }
     }
 }
index 1248ab75058162a26c6897ea5ec24fc0833a1e47..4f2f564dedbaab9d93f574a82da0a0191130f2c3 100644 (file)
@@ -1,4 +1,3 @@
-using System.Numerics;
 using System.Threading.Tasks;
 using Content.Shared.Maps;
 using Content.Shared.NPC;
@@ -14,22 +13,15 @@ public sealed partial class DungeonJob
     /// <summary>
     /// <see cref="ExteriorDunGen"/>
     /// </summary>
-    private async Task<List<Dungeon>> GenerateExteriorDungen(int runCount, int maxRuns, Vector2i position, ExteriorDunGen dungen, HashSet<Vector2i> reservedTiles, Random random)
+    private async Task<List<Dungeon>> GenerateExteriorDungen(Vector2i position, ExteriorDunGen dungen, HashSet<Vector2i> reservedTiles, Random random)
     {
         DebugTools.Assert(_grid.ChunkCount > 0);
 
         var aabb = new Box2i(_grid.LocalAABB.BottomLeft.Floored(), _grid.LocalAABB.TopRight.Floored());
-        // TODO: Cross-layer seeding. Need this because we need to be able to spread the dungeons out.
-        var angle = new Random(_seed).NextAngle();
-        var divisors = new Angle(Angle.FromDegrees(360) / maxRuns);
-
-        // Offset each dungeon so they don't generate on top of each other.
-        for (var i = 0; i < runCount; i++)
-        {
-            angle += (random.NextFloat(0.6f, 1.4f)) * divisors;
-        }
+        var angle = random.NextAngle();
 
         var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f);
+
         var startTile = new Vector2i(0, (int) distance).Rotate(angle);
 
         Vector2i? dungeonSpawn = null;
@@ -55,19 +47,9 @@ public sealed partial class DungeonJob
             };
         }
 
-        // Move it further in based on the spawn angle.
-        if (dungen.Penetration.Y > 0)
-        {
-            var penetration = random.Next(dungen.Penetration.X, dungen.Penetration.Y);
-            var diff = dungeonSpawn.Value - startTile;
-            var diffVec = new Vector2(diff.X, diff.Y);
-            dungeonSpawn = (diffVec.Normalized() * (penetration + diffVec.Length())).Floored() + startTile;
-        }
-
-        var subConfig = _prototype.Index(dungen.Proto);
+        var config = _prototype.Index(dungen.Proto);
         var nextSeed = random.Next();
-        var (dungeons, newReserved) = await GetDungeons(dungeonSpawn.Value, subConfig, subConfig.Layers, nextSeed, new Random(nextSeed), reserved: reservedTiles);
-        reservedTiles.UnionWith(newReserved);
+        var dungeons = await GetDungeons(dungeonSpawn.Value, config, config.Layers, reservedTiles, nextSeed, new Random(nextSeed));
 
         return dungeons;
     }
index 1608e50266aec4f4bf8a1f855bc8f904e50f2af2..482cb34a564dc15a52ec0c7df9783dbe85696171 100644 (file)
@@ -105,9 +105,7 @@ public sealed partial class DungeonJob
                     if (reservedTiles.Contains(neighbor))
                         continue;
 
-                    var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-                    tiles.Add((neighbor, tileVariant));
-                    AddLoadedTile(neighbor, tileVariant);
+                    tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
                     index++;
                     takenTiles.Add(neighbor);
                 }
@@ -121,13 +119,7 @@ public sealed partial class DungeonJob
         {
             var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile.Item1);
 
-            var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
-
-            foreach (var uid in uids)
-            {
-                AddLoadedEntity(tile.Item1, uid);
-            }
-
+            _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
             await SuspendDungeon();
 
             if (!ValidateResume())
index 95ec9d56e5a566bf70d1048a5e38a35c1ddae9d0..b579d4e5a1c2ca62edf52a4ce1e4e2899020cde7 100644 (file)
@@ -18,45 +18,21 @@ public sealed partial class DungeonJob
                 if (reservedTiles.Contains(tile))
                     continue;
 
-                await SuspendDungeon();
-                if (!ValidateResume())
-                    return;
-
                 if (!_maps.TryGetTileDef(_grid, tile, out var tileDef))
                     continue;
 
                 if (fill.AllowedTiles != null && !fill.AllowedTiles.Contains(tileDef.ID))
                     continue;
 
-                // If noise then check it matches.
-                if (fill.ReservedNoise != null)
-                {
-                    var value = fill.ReservedNoise.GetNoise(tile.X, tile.Y);
-
-                    if (fill.DistanceConfig != null)
-                    {
-                        // Need to get dx - dx in a range from -1 -> 1
-                        var dx = 2 * tile.X / fill.Size.X;
-                        var dy = 2 * tile.Y / fill.Size.Y;
-
-                        var distance = GetDistance(dx, dy, fill.DistanceConfig);
-
-                        value = MathHelper.Lerp(value, 1f - distance, fill.DistanceConfig.BlendWeight);
-                    }
-
-                    value *= (fill.Invert ? -1 : 1);
-
-                    if (value < fill.Threshold)
-                        continue;
-                }
-
                 if (!_anchorable.TileFree((_gridUid, _grid), tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
                     continue;
 
                 var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile);
-                var uid = _entManager.SpawnEntity(fill.Entity, gridPos);
+                _entManager.SpawnEntity(fill.Entity, gridPos);
 
-                AddLoadedEntity(tile, uid);
+                await SuspendDungeon();
+                if (!ValidateResume())
+                    break;
             }
         }
     }
index 4ced89ba0e04e830cd2e611902fc20544da917e1..c57757b4213c6e3a8372a3ed8f0ec61340228122 100644 (file)
@@ -52,8 +52,6 @@ public sealed partial class DungeonJob
                 }
             }
         }
-
-        dungeon.RefreshAllTiles();
     }
 
     private void WidenCorridor(Dungeon dungeon, float width, ICollection<Vector2i> corridorTiles)
index c736eb60703cfc5be439474d06b29297d92d671e..f80b3face71f805cc4d80ac9696c2d42636a745f 100644 (file)
@@ -81,16 +81,9 @@ public sealed partial class DungeonJob
                 {
                     var tile = validTiles[j];
                     var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile);
-                    var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-                    _maps.SetTile(_gridUid, _grid, tile, tileVariant);
-                    AddLoadedTile(tile, tileVariant);
+                    _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
 
-                    var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
-
-                    foreach (var uid in uids)
-                    {
-                        AddLoadedEntity(tile, uid);
-                    }
+                    _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
                 }
 
                 if (validTiles.Count > 0)
index 88db2fad42eb2c9c0ac2330c19d34eaad9f6d358..28cbc9b208bf918c2ae7fe9b08772cef57326fc4 100644 (file)
@@ -113,17 +113,10 @@ public sealed partial class DungeonJob
                         if (reservedTiles.Contains(weh))
                             continue;
 
-                        var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-                        _maps.SetTile(_gridUid, _grid, weh, tileVariant);
-                        AddLoadedTile(weh, tileVariant);
+                        _maps.SetTile(_gridUid, _grid, weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
 
                         var coords = _maps.GridTileToLocal(_gridUid, _grid, weh);
-                        var uids = _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
-
-                        foreach (var uid in uids)
-                        {
-                            AddLoadedEntity(weh, uid);
-                        }
+                        _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
                     }
 
                     break;
index 52edaf343383ae3b0a7510376939bb66f11f083c..d6e3c09d6222c44685b7f0646abeb8109af2e5a6 100644 (file)
@@ -18,7 +18,6 @@ public sealed partial class DungeonJob
         // Grab all of the room bounds
         // Then, work out connections between them
         var roomBorders = new Dictionary<DungeonRoom, HashSet<Vector2i>>(dungeon.Rooms.Count);
-        var flank = gen.Flank;
 
         foreach (var room in dungeon.Rooms)
         {
@@ -108,30 +107,18 @@ public sealed partial class DungeonJob
                         continue;
 
                     width--;
-                    var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-                    _maps.SetTile(_gridUid, _grid, node, tileVariant);
-                    AddLoadedTile(node, tileVariant);
+                    _maps.SetTile(_gridUid, _grid, node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
 
                     if (flankContents != null && nodeDistances.Count - i <= 2)
                     {
-                        var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
-
-                        foreach (var uid in uids)
-                        {
-                            AddLoadedEntity(node, uid);
-                        }
+                        _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
                     }
                     else
                     {
                         // Iterate neighbors and check for blockers, if so bulldoze
                         ClearDoor(dungeon, _grid, node);
 
-                        var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
-
-                        foreach (var uid in uids)
-                        {
-                            AddLoadedEntity(node, uid);
-                        }
+                        _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
                     }
 
                     if (width == 0)
index 341dbf168ac39d982a73661b8f9a106acc89dcb9..caf6828e43c32d2a7b851b230a46cde5831cf3f9 100644 (file)
@@ -49,7 +49,6 @@ public sealed partial class DungeonJob
                     _entManager.RemoveComponent<GhostRoleComponent>(uid);
                     _entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
                     npcs.SleepNPC(uid);
-                    AddLoadedEntity(tile, uid);
                 }
 
                 break;
index 201948fc02e4b9977cf669cc4db4eb2840ea8985..b2526ec17d1b53d5ea17950048dd11fcc6c32d62 100644 (file)
@@ -98,9 +98,7 @@ public sealed partial class DungeonJob
                     var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random);
                     var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored();
 
-                    var tileVariant = new Tile(tileDef.TileId, variant: variant);
-                    tiles.Add((adjusted, tileVariant));
-                    AddLoadedTile(adjusted, tileVariant);
+                    tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
                     roomTiles.Add(adjusted);
                     tileCount++;
                     break;
@@ -129,7 +127,8 @@ public sealed partial class DungeonJob
                     }
                 }
 
-                await SuspendDungeon();
+                await SuspendIfOutOfTime();
+                ValidateResume();
             }
 
             var center = Vector2.Zero;
@@ -141,7 +140,8 @@ public sealed partial class DungeonJob
 
             center /= roomTiles.Count;
             rooms.Add(new DungeonRoom(roomTiles, center, roomArea, new HashSet<Vector2i>()));
-            await SuspendDungeon();
+            await SuspendIfOutOfTime();
+            ValidateResume();
         }
 
         _maps.SetTiles(_gridUid, _grid, tiles);
index 035820fd90a66d74b241d3c884ab11add5e54ada..78ab2b7a0d28ac54abf581b3db35dcf5eae36b20 100644 (file)
@@ -19,26 +19,12 @@ public sealed partial class DungeonJob
         HashSet<Vector2i> reservedTiles,
         Random random)
     {
-        var emptyTiles = false;
-        var replaceEntities = new Dictionary<Vector2i, EntityUid>();
-        var availableTiles = new List<Vector2i>();
-        var remapName = _entManager.ComponentFactory.GetComponentName<EntityRemapComponent>();
-        var replacementRemapping = new Dictionary<EntProtoId, EntProtoId>();
-
-        if (_prototype.TryIndex(gen.Replacement, out var replacementProto) &&
-            replacementProto.Components.TryGetComponent(remapName, out var replacementComps))
-        {
-            var remappingComp = (EntityRemapComponent) replacementComps;
-            replacementRemapping = remappingComp.Mask;
-        }
-
-        if (gen.Replacement != null)
-        {
-            replacementRemapping[gen.Replacement.Value] = gen.Entity;
-        }
-
         foreach (var dungeon in dungeons)
         {
+            var emptyTiles = false;
+            var replaceEntities = new Dictionary<Vector2i, EntityUid>();
+            var availableTiles = new List<Vector2i>();
+
             foreach (var node in dungeon.AllTiles)
             {
                 if (reservedTiles.Contains(node))
@@ -55,23 +41,19 @@ public sealed partial class DungeonJob
                 // We use existing entities as a mark to spawn in place
                 // OR
                 // We check for any existing entities to see if we can spawn there.
-                // We can't replace so just stop here.
-                if (gen.Replacement != null)
+                while (enumerator.MoveNext(out var uid))
                 {
-                    while (enumerator.MoveNext(out var uid))
-                    {
-                        var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
+                    // We can't replace so just stop here.
+                    if (gen.Replacement == null)
+                        break;
 
-                        if (string.IsNullOrEmpty(prototype?.ID))
-                            continue;
+                    var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
 
-                        // It has a valid remapping so take it over.
-                        if (replacementRemapping.ContainsKey(prototype.ID))
-                        {
-                            replaceEntities[node] = uid.Value;
-                            found = true;
-                            break;
-                        }
+                    if (prototype?.ID == gen.Replacement)
+                    {
+                        replaceEntities[node] = uid.Value;
+                        found = true;
+                        break;
                     }
                 }
 
@@ -86,86 +68,83 @@ public sealed partial class DungeonJob
                 if (!ValidateResume())
                     return;
             }
-        }
 
-        var remapping = new Dictionary<EntProtoId, EntProtoId>();
+            var remapping = new Dictionary<EntProtoId, EntProtoId>();
 
-        // TODO: Move this to engine
-        if (_prototype.TryIndex(gen.Entity, out var proto) &&
-            proto.Components.TryGetComponent(remapName, out var comps))
-        {
-            var remappingComp = (EntityRemapComponent) comps;
-            remapping = remappingComp.Mask;
-        }
-
-        var frontier = new ValueList<Vector2i>(32);
+            // TODO: Move this to engine
+            if (_prototype.TryIndex(gen.Entity, out var proto) &&
+                proto.Components.TryGetComponent("EntityRemap", out var comps))
+            {
+                var remappingComp = (EntityRemapComponent) comps;
+                remapping = remappingComp.Mask;
+            }
 
-        // Iterate the group counts and pathfind out each group.
-        for (var i = 0; i < gen.Count; i++)
-        {
-            var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1);
+            var frontier = new ValueList<Vector2i>(32);
 
-            // While we have remaining tiles keep iterating
-            while (groupSize > 0 && availableTiles.Count > 0)
+            // Iterate the group counts and pathfind out each group.
+            for (var i = 0; i < gen.Count; i++)
             {
-                var startNode = random.PickAndTake(availableTiles);
-                frontier.Clear();
-                frontier.Add(startNode);
+                await SuspendDungeon();
+
+                if (!ValidateResume())
+                    return;
+
+                var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1);
 
-                // This essentially may lead to a vein being split in multiple areas but the count matters more than position.
-                while (frontier.Count > 0 && groupSize > 0)
+                // While we have remaining tiles keep iterating
+                while (groupSize > 0 && availableTiles.Count > 0)
                 {
-                    // Need to pick a random index so we don't just get straight lines of ores.
-                    var frontierIndex = random.Next(frontier.Count);
-                    var node = frontier[frontierIndex];
-                    frontier.RemoveSwap(frontierIndex);
-                    availableTiles.Remove(node);
-
-                    // Add neighbors if they're valid, worst case we add no more and pick another random seed tile.
-                    for (var x = -1; x <= 1; x++)
+                    var startNode = random.PickAndTake(availableTiles);
+                    frontier.Clear();
+                    frontier.Add(startNode);
+
+                    // This essentially may lead to a vein being split in multiple areas but the count matters more than position.
+                    while (frontier.Count > 0 && groupSize > 0)
                     {
-                        for (var y = -1; y <= 1; y++)
+                        // Need to pick a random index so we don't just get straight lines of ores.
+                        var frontierIndex = random.Next(frontier.Count);
+                        var node = frontier[frontierIndex];
+                        frontier.RemoveSwap(frontierIndex);
+                        availableTiles.Remove(node);
+
+                        // Add neighbors if they're valid, worst case we add no more and pick another random seed tile.
+                        for (var x = -1; x <= 1; x++)
                         {
-                            var neighbor = new Vector2i(node.X + x, node.Y + y);
+                            for (var y = -1; y <= 1; y++)
+                            {
+                                var neighbor = new Vector2i(node.X + x, node.Y + y);
 
-                            if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor))
-                                continue;
+                                if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor))
+                                    continue;
 
-                            frontier.Add(neighbor);
+                                frontier.Add(neighbor);
+                            }
                         }
-                    }
 
-                    var prototype = gen.Entity;
-
-                    // May have been deleted while iteration was suspended.
-                    if (replaceEntities.TryGetValue(node, out var existingEnt) && _entManager.TryGetComponent(existingEnt, out MetaDataComponent? metadata))
-                    {
-                        var existingProto = metadata.EntityPrototype;
-                        _entManager.DeleteEntity(existingEnt);
+                        var prototype = gen.Entity;
 
-                        if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
+                        if (replaceEntities.TryGetValue(node, out var existingEnt))
                         {
-                            prototype = remapped;
-                        }
-                    }
-
-                    // Tile valid salad so add it.
-                    var uid = _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
-                    AddLoadedEntity(node, uid);
+                            var existingProto = _entManager.GetComponent<MetaDataComponent>(existingEnt).EntityPrototype;
+                            _entManager.DeleteEntity(existingEnt);
 
-                    groupSize--;
+                            if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
+                            {
+                                prototype = remapped;
+                            }
+                        }
 
-                    await SuspendDungeon();
+                        // Tile valid salad so add it.
+                        _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
 
-                    if (!ValidateResume())
-                        return;
+                        groupSize--;
+                    }
                 }
-            }
 
-            if (groupSize > 0)
-            {
-                // Not super worried depending on the gen it's fine.
-                _sawmill.Debug($"Found remaining group size for ore veins of {gen.Replacement ?? "null"} / {gen.Entity}!");
+                if (groupSize > 0)
+                {
+                    _sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!");
+                }
             }
         }
     }
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs
deleted file mode 100644 (file)
index 2d00dbe..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Threading.Tasks;
-using Content.Server.Light.EntitySystems;
-using Content.Shared.Light.Components;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.DungeonLayers;
-
-namespace Content.Server.Procedural.DungeonJob;
-
-public sealed partial class DungeonJob
-{
-    public async Task RoofGen(RoofDunGen roof, List<Dungeon> dungeons, HashSet<Vector2i> reservedTiles, Random random)
-    {
-        var roofComp = _entManager.EnsureComponent<RoofComponent>(_gridUid);
-
-        var noise = roof.Noise;
-        var oldSeed = noise?.GetSeed() ?? 0;
-        noise?.SetSeed(_seed + oldSeed);
-        var rooves = _entManager.System<RoofSystem>();
-
-        foreach (var dungeon in dungeons)
-        {
-            foreach (var tile in dungeon.AllTiles)
-            {
-                if (reservedTiles.Contains(tile))
-                    continue;
-
-                var value = noise?.GetNoise(tile.X, tile.Y) ?? 1f;
-
-                if (value < roof.Threshold)
-                    continue;
-
-                rooves.SetRoof((_gridUid, _grid, roofComp), tile, true);
-            }
-        }
-
-        noise?.SetSeed(oldSeed);
-    }
-}
index 17ad9b5e35fbd4dd727c752740ec39f2a1e9ad84..a4a01b5f0b072a4f26a05fc5eb66792bba5dae35 100644 (file)
@@ -25,9 +25,7 @@ public sealed partial class DungeonJob
                 if (reservedTiles.Contains(entrance))
                     continue;
 
-                var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
-                setTiles.Add((entrance, tileVariant));
-                AddLoadedTile(entrance, tileVariant);
+                setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
             }
         }
 
@@ -40,15 +38,10 @@ public sealed partial class DungeonJob
                 if (reservedTiles.Contains(entrance))
                     continue;
 
-                var uids = _entManager.SpawnEntitiesAttachedTo(
+                _entManager.SpawnEntitiesAttachedTo(
                     _maps.GridTileToLocal(_gridUid, _grid, entrance),
                     _entTable.GetSpawns(contents, random));
 
-                foreach (var uid in uids)
-                {
-                    AddLoadedEntity(entrance, uid);
-                }
-
                 await SuspendDungeon();
 
                 if (!ValidateResume())
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleDecal.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.SampleDecal.cs
deleted file mode 100644 (file)
index b240c0b..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-using System.Threading.Tasks;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.DungeonLayers;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-
-namespace Content.Server.Procedural.DungeonJob;
-
-public sealed partial class DungeonJob
-{
-    /// <summary>
-    /// <see cref="SampleDecalDunGen"/>
-    /// </summary>
-    private async Task PostGen(SampleDecalDunGen gen,
-        List<Dungeon> dungeons,
-        HashSet<Vector2i> reservedTiles,
-        Random random)
-    {
-        var oldSeed = gen.Noise.GetSeed();
-        gen.Noise.SetSeed(_seed + oldSeed);
-
-        foreach (var dungeon in dungeons)
-        {
-            foreach (var tile in dungeon.AllTiles)
-            {
-                if (reservedTiles.Contains(tile))
-                    continue;
-
-                var invert = gen.Invert;
-                var value = gen.Noise.GetNoise(tile.X, tile.Y);
-                value = invert ? value * -1 : value;
-
-                if (value < gen.Threshold)
-                    continue;
-
-                // Not allowed
-                if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
-                    !gen.AllowedTiles.Contains(_tileDefManager[tileRef.Tile.TypeId].ID))
-                {
-                    continue;
-                }
-
-                // Occupied?
-                if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
-                    continue;
-
-                _decals.TryAddDecal(random.Pick(gen.Decals), new EntityCoordinates(_gridUid, tile), out var did);
-                AddLoadedDecal(tile, did);
-
-                if (gen.ReserveTiles)
-                {
-                    reservedTiles.Add(tile);
-                }
-
-                await SuspendDungeon();
-
-                if (!ValidateResume())
-                    return;
-            }
-        }
-
-        gen.Noise.SetSeed(oldSeed);
-    }
-}
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleEntity.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.SampleEntity.cs
deleted file mode 100644 (file)
index 1a9fc8b..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-using System.Threading.Tasks;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.DungeonLayers;
-using Robust.Shared.Map;
-using Robust.Shared.Random;
-
-namespace Content.Server.Procedural.DungeonJob;
-
-public sealed partial class DungeonJob
-{
-    /// <summary>
-    /// <see cref="SampleEntityDunGen"/>
-    /// </summary>
-    private async Task PostGen(
-        SampleEntityDunGen gen,
-        List<Dungeon> dungeons,
-        HashSet<Vector2i> reservedTiles,
-        Random random)
-    {
-        var oldSeed = gen.Noise.GetSeed();
-        gen.Noise.SetSeed(_seed + oldSeed);
-
-        foreach (var dungeon in dungeons)
-        {
-            foreach (var tile in dungeon.AllTiles)
-            {
-                if (reservedTiles.Contains(tile))
-                    continue;
-
-                var invert = gen.Invert;
-                var value = gen.Noise.GetNoise(tile.X, tile.Y);
-                value = invert ? value * -1 : value;
-
-                if (value < gen.Threshold)
-                    continue;
-
-                // Not allowed
-                if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
-                    !gen.AllowedTiles.Contains(_tileDefManager[tileRef.Tile.TypeId].ID))
-                {
-                    continue;
-                }
-
-                var gridTile = _maps.GridTileToLocal(_gridUid, _grid, tile);
-                var uid = _entManager.SpawnAttachedTo(random.Pick(gen.Entities), gridTile);
-                AddLoadedEntity(tile, uid);
-
-                if (gen.ReserveTiles)
-                {
-                    reservedTiles.Add(tile);
-                }
-
-                await SuspendDungeon();
-
-                if (!ValidateResume())
-                    return;
-            }
-        }
-
-        gen.Noise.SetSeed(oldSeed);
-    }
-}
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleTile.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.SampleTile.cs
deleted file mode 100644 (file)
index 97875b2..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-using System.Threading.Tasks;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.DungeonLayers;
-using Robust.Shared.Map;
-using Robust.Shared.Noise;
-using Robust.Shared.Random;
-
-namespace Content.Server.Procedural.DungeonJob;
-
-public sealed partial class DungeonJob
-{
-    /// <summary>
-    /// <see cref="SampleTileDunGen"/>
-    /// </summary>
-    private async Task PostGen(SampleTileDunGen gen,
-        List<Dungeon> dungeons,
-        HashSet<Vector2i> reservedTiles,
-        Random random)
-    {
-        var noise = gen.Noise;
-        var oldSeed = noise.GetSeed();
-        noise.SetSeed(_seed + oldSeed);
-        var tiles = new List<(Vector2i Index, Tile Tile)>();
-        var tileDef = _prototype.Index(gen.Tile);
-        var variants = tileDef.PlacementVariants.Length;
-
-        foreach (var dungeon in dungeons)
-        {
-            foreach (var tile in dungeon.AllTiles)
-            {
-                if (reservedTiles.Contains(tile))
-                    continue;
-
-                var invert = gen.Invert;
-                var value = noise.GetNoise(tile.X, tile.Y);
-                value = invert ? value * -1 : value;
-
-                if (value < gen.Threshold)
-                    continue;
-
-                var variantValue = (noise.GetNoise(tile.X * 8, tile.Y * 8, variants) + 1f) * 100;
-                var variant = _tile.PickVariant(tileDef, (int)variantValue);
-                var tileVariant = new Tile(tileDef.TileId, variant: variant);
-
-                tiles.Add((tile, tileVariant));
-                AddLoadedTile(tile, tileVariant);
-
-                await SuspendDungeon();
-
-                if (!ValidateResume())
-                    return;
-            }
-        }
-
-        gen.Noise.SetSeed(oldSeed);
-        _maps.SetTiles(_gridUid, _grid, tiles);
-
-        if (gen.ReserveTiles)
-        {
-            foreach (var tile in tiles)
-            {
-                reservedTiles.Add(tile.Index);
-            }
-        }
-    }
-}
index 8635bc0f48bfecc98f6877b1de325592bfc7403a..a131efd3534b5900c3bf4da2904219e789d58d8a 100644 (file)
@@ -2,7 +2,6 @@ using System.Numerics;
 using System.Threading.Tasks;
 using Content.Server.NPC.Pathfinding;
 using Content.Shared.Procedural;
-using Content.Shared.Procedural.DungeonLayers;
 using Content.Shared.Procedural.PostGeneration;
 using Robust.Shared.Map;
 using Robust.Shared.Random;
@@ -12,10 +11,10 @@ namespace Content.Server.Procedural.DungeonJob;
 public sealed partial class DungeonJob
 {
     /// <summary>
-    /// <see cref="Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen"/>
+    /// <see cref="SplineDungeonConnectorDunGen"/>
     /// </summary>
     private async Task<Dungeon> PostGen(
-        Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen gen,
+        SplineDungeonConnectorDunGen gen,
         List<Dungeon> dungeons,
         HashSet<Vector2i> reservedTiles,
         Random random)
@@ -60,7 +59,6 @@ public sealed partial class DungeonJob
                 {
                     Start = pair.Start,
                     End = pair.End,
-                    Diagonals = false,
                     TileCost = node =>
                     {
                         // We want these to get prioritised internally and into space if it's a space dungeon.
@@ -112,7 +110,6 @@ public sealed partial class DungeonJob
                 }
 
                 tiles.Add((node, tile));
-                AddLoadedTile(node, tile);
             }
 
             _maps.SetTiles(_gridUid, _grid, tiles);
@@ -126,7 +123,6 @@ public sealed partial class DungeonJob
 
                 allTiles.Add(node);
                 tiles.Add((node, pathTile));
-                AddLoadedTile(node, pathTile);
             }
 
             _maps.SetTiles(_gridUid, _grid, tiles);
index fa2921b80a261859680a7caf4781fd0aa0fa9513..e5bb32bd0c000296c64d98a7cf5d3f6b9076102e 100644 (file)
@@ -32,18 +32,11 @@ public sealed partial class DungeonJob
             if (reservedTiles.Contains(neighbor))
                 continue;
 
-            var tileVariant = _tile.GetVariantTile(tileDef, random);
-            _maps.SetTile(_gridUid, _grid, neighbor, tileVariant);
-            AddLoadedTile(neighbor, tileVariant);
+            _maps.SetTile(_gridUid, _grid, neighbor, _tile.GetVariantTile(tileDef, random));
             var gridPos = _maps.GridTileToLocal(_gridUid, _grid, neighbor);
             var protoNames = _entTable.GetSpawns(contents, random);
 
-            var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
-
-            foreach (var uid in uids)
-            {
-                AddLoadedEntity(neighbor, uid);
-            }
+            _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
 
             await SuspendDungeon();
             if (!ValidateResume())
index 81454922059cf3cd553bf108786ffd0a89b971e5..77404fc963c17f6c673ec6477a51bb85f1f93b95 100644 (file)
@@ -1,7 +1,8 @@
-using System.Numerics;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 using Content.Server.Decals;
+using Content.Server.NPC.Components;
 using Content.Server.NPC.HTN;
 using Content.Server.NPC.Systems;
 using Content.Server.Shuttles.Systems;
@@ -22,10 +23,11 @@ using Robust.Shared.Physics.Components;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Utility;
+using IDunGenLayer = Content.Shared.Procedural.IDunGenLayer;
 
 namespace Content.Server.Procedural.DungeonJob;
 
-public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
+public sealed partial class DungeonJob : Job<List<Dungeon>>
 {
     public bool TimeSlice = true;
 
@@ -58,10 +60,6 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
 
     private readonly ISawmill _sawmill;
 
-    private DungeonData _data = new();
-
-    private HashSet<Vector2i>? _reservedTiles;
-
     public DungeonJob(
         ISawmill sawmill,
         double maxTime,
@@ -81,14 +79,12 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
         int seed,
         Vector2i position,
         EntityCoordinates? targetCoordinates = null,
-        CancellationToken cancellation = default,
-        HashSet<Vector2i>? reservedTiles = null) : base(maxTime, cancellation)
+        CancellationToken cancellation = default) : base(maxTime, cancellation)
     {
         _sawmill = sawmill;
         _entManager = entManager;
         _prototype = prototype;
         _tileDefManager = tileDefManager;
-        _reservedTiles = reservedTiles;
 
         _anchorable = anchorable;
         _decals = decals;
@@ -115,18 +111,17 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
     /// <summary>
     /// Gets the relevant dungeon, running recursively as relevant.
     /// </summary>
-    /// <param name="reservedTiles">Should we reserve tiles even if the config doesn't specify.</param>
-    private async Task<(List<Dungeon>, HashSet<Vector2i>)> GetDungeons(
+    /// <param name="reserve">Should we reserve tiles even if the config doesn't specify.</param>
+    private async Task<List<Dungeon>> GetDungeons(
         Vector2i position,
         DungeonConfig config,
         List<IDunGenLayer> layers,
+        HashSet<Vector2i> reservedTiles,
         int seed,
         Random random,
-        HashSet<Vector2i>? reserved = null,
         List<Dungeon>? existing = null)
     {
         var dungeons = new List<Dungeon>();
-        var reservedTiles = reserved == null ? new HashSet<Vector2i>() : new HashSet<Vector2i>(reserved);
 
         // Don't pass dungeons back up the "stack". They are ref types though it's a caller problem if they start trying to mutate it.
         if (existing != null)
@@ -142,8 +137,8 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
 
             foreach (var layer in layers)
             {
-               var dungCount = dungeons.Count;
-                await RunLayer(i, count, config, dungeons, position, layer, reservedTiles, seed, random);
+                var dungCount = dungeons.Count;
+                await RunLayer(dungeons, position, layer, reservedTiles, seed, random);
 
                 if (config.ReserveTiles)
                 {
@@ -157,23 +152,24 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
 
                 await SuspendDungeon();
                 if (!ValidateResume())
-                    return (new List<Dungeon>(), new HashSet<Vector2i>());
+                    return new List<Dungeon>();
             }
         }
 
-        // Only return the new dungeons and applicable reserved tiles.
-        return (dungeons[(existing?.Count ?? 0)..], config.ReturnReserved ? reservedTiles : new HashSet<Vector2i>());
+        return dungeons;
     }
 
-    protected override async Task<(List<Dungeon>, DungeonData)> Process()
+    protected override async Task<List<Dungeon>?> Process()
     {
         _sawmill.Info($"Generating dungeon {_gen} with seed {_seed} on {_entManager.ToPrettyString(_gridUid)}");
         _grid.CanSplit = false;
         var random = new Random(_seed);
-        var oldTileCount = _reservedTiles?.Count ?? 0;
         var position = (_position + random.NextPolarVector2(_gen.MinOffset, _gen.MaxOffset)).Floored();
 
-        var (dungeons, _) = await GetDungeons(position, _gen, _gen.Layers, _seed, random, reserved: _reservedTiles);
+        // Tiles we can no longer generate on due to being reserved elsewhere.
+        var reservedTiles = new HashSet<Vector2i>();
+
+        var dungeons = await GetDungeons(position, _gen, _gen.Layers, reservedTiles, _seed, random);
         // To make it slightly more deterministic treat this RNG as separate ig.
 
         // Post-processing after finishing loading.
@@ -185,7 +181,6 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
         }
 
         // Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way.
-        DebugTools.Assert(oldTileCount == (_reservedTiles?.Count ?? 0));
         _grid.CanSplit = true;
         _entManager.System<GridFixtureSystem>().CheckSplits(_gridUid);
         var npcSystem = _entManager.System<NPCSystem>();
@@ -199,13 +194,10 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
         }
 
         _sawmill.Info($"Finished generating dungeon {_gen} with seed {_seed}");
-        return (dungeons, _data);
+        return dungeons;
     }
 
     private async Task RunLayer(
-        int runCount,
-        int maxRuns,
-        DungeonConfig config,
         List<Dungeon> dungeons,
         Vector2i position,
         IDunGenLayer layer,
@@ -213,7 +205,7 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
         int seed,
         Random random)
     {
-        // _sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen} with seed {_seed}");
+        _sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen} with seed {_seed}");
 
         // If there's a way to just call the methods directly for the love of god tell me.
         // Some of these don't care about reservedtiles because they only operate on dungeon tiles (which should
@@ -227,12 +219,15 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
             case AutoCablingDunGen cabling:
                 await PostGen(cabling, dungeons[^1], reservedTiles, random);
                 break;
+            case BiomeMarkerLayerDunGen markerPost:
+                await PostGen(markerPost, dungeons[^1], reservedTiles, random);
+                break;
+            case BiomeDunGen biome:
+                await PostGen(biome, dungeons[^1], reservedTiles, random);
+                break;
             case BoundaryWallDunGen boundary:
                 await PostGen(boundary, dungeons[^1], reservedTiles, random);
                 break;
-            case ChunkDunGen chunk:
-                dungeons.Add(await PostGen(chunk, reservedTiles, random));
-                break;
             case CornerClutterDunGen clutter:
                 await PostGen(clutter, dungeons[^1], reservedTiles, random);
                 break;
@@ -249,7 +244,7 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
                 await PostGen(flank, dungeons[^1], reservedTiles, random);
                 break;
             case ExteriorDunGen exterior:
-                dungeons.AddRange(await GenerateExteriorDungen(runCount, maxRuns, position, exterior, reservedTiles, random));
+                dungeons.AddRange(await GenerateExteriorDungen(position, exterior, reservedTiles, random));
                 break;
             case FillGridDunGen fill:
                 await GenerateFillDunGen(fill, dungeons, reservedTiles);
@@ -290,67 +285,27 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
             case PrototypeDunGen prototypo:
                 var groupConfig = _prototype.Index(prototypo.Proto);
                 position = (position + random.NextPolarVector2(groupConfig.MinOffset, groupConfig.MaxOffset)).Floored();
-                List<Dungeon>? inheritedDungeons = null;
-                HashSet<Vector2i>? inheritedReserved = null;
-
-                switch (prototypo.InheritReserved)
-                {
-                    case ReservedInheritance.All:
-                        inheritedReserved = new HashSet<Vector2i>(reservedTiles);
-                        break;
-                    case ReservedInheritance.None:
-                        break;
-                    default:
-                        throw new NotImplementedException();
-                }
 
                 switch (prototypo.InheritDungeons)
                 {
                     case DungeonInheritance.All:
-                        inheritedDungeons = dungeons;
+                        dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons));
                         break;
                     case DungeonInheritance.Last:
-                        inheritedDungeons = dungeons.GetRange(dungeons.Count - 1, 1);
+                        dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons.GetRange(dungeons.Count - 1, 1)));
                         break;
                     case DungeonInheritance.None:
+                        dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random));
                         break;
-                    default:
-                        throw new NotImplementedException();
-                }
-
-                var (newDungeons, newReserved) = await GetDungeons(position,
-                    groupConfig,
-                    groupConfig.Layers,
-                    seed,
-                    random,
-                    reserved: inheritedReserved,
-                    existing: inheritedDungeons);
-                dungeons.AddRange(newDungeons);
-
-                if (groupConfig.ReturnReserved)
-                {
-                    reservedTiles.UnionWith(newReserved);
                 }
 
                 break;
             case ReplaceTileDunGen replace:
                 await GenerateTileReplacementDunGen(replace, dungeons, reservedTiles, random);
                 break;
-            case RoofDunGen roof:
-                await RoofGen(roof, dungeons, reservedTiles, random);
-                break;
             case RoomEntranceDunGen rEntrance:
                 await PostGen(rEntrance, dungeons[^1], reservedTiles, random);
                 break;
-            case SampleDecalDunGen sdec:
-                await PostGen(sdec, dungeons, reservedTiles, random);
-                break;
-            case SampleEntityDunGen sent:
-                await PostGen(sent, dungeons, reservedTiles, random);
-                break;
-            case SampleTileDunGen stile:
-                await PostGen(stile, dungeons, reservedTiles, random);
-                break;
             case SplineDungeonConnectorDunGen spline:
                 dungeons.Add(await PostGen(spline, dungeons, reservedTiles, random));
                 break;
@@ -365,6 +320,11 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
         }
     }
 
+    private void LogDataError(Type type)
+    {
+        _sawmill.Error($"Unable to find dungeon data keys for {type}");
+    }
+
     [Pure]
     private bool ValidateResume()
     {
@@ -386,19 +346,4 @@ public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
 
         await SuspendIfOutOfTime();
     }
-
-    private void AddLoadedEntity(Vector2i tile, EntityUid ent)
-    {
-        _data.Entities[ent] = tile;
-    }
-
-    private void AddLoadedDecal(Vector2 tile, uint decal)
-    {
-        _data.Decals[decal] = tile;
-    }
-
-    private void AddLoadedTile(Vector2i index, Tile tile)
-    {
-        _data.Tiles[index] = tile;
-    }
 }
index e5143454d3d7d1d0d0d0029f4cf6b6d649a1b166..e5b0981b3dbeb5db4f53010fa252544fac10de80 100644 (file)
@@ -113,7 +113,7 @@ public sealed partial class DungeonSystem
         return roomRotation;
     }
 
-    public DungeonData SpawnRoom(
+    public void SpawnRoom(
         EntityUid gridUid,
         MapGridComponent grid,
         Matrix3x2 roomTransform,
@@ -126,7 +126,6 @@ public sealed partial class DungeonSystem
         var templateMapUid = _maps.GetMapOrInvalid(roomMap);
         var templateGrid = Comp<MapGridComponent>(templateMapUid);
         var roomDimensions = room.Size;
-        var data = new DungeonData();
 
         var finalRoomRotation = roomTransform.Rotation();
 
@@ -155,7 +154,6 @@ public sealed partial class DungeonSystem
                 }
 
                 _tiles.Add((rounded, tileRef.Tile));
-                data.Tiles[rounded] = tileRef.Tile;
 
                 if (clearExisting)
                 {
@@ -188,7 +186,6 @@ public sealed partial class DungeonSystem
 
             // TODO: Copy the templated entity as is with serv
             var ent = Spawn(protoId, new EntityCoordinates(gridUid, childPos));
-            data.Entities.Add(ent, childPos.Floored());
 
             var childXform = _xformQuery.GetComponent(ent);
             var anchored = templateXform.Anchored;
@@ -259,18 +256,14 @@ public sealed partial class DungeonSystem
                 var result = _decals.TryAddDecal(
                     decal.Id,
                     new EntityCoordinates(gridUid, position),
-                    out var did,
+                    out _,
                     decal.Color,
                     angle,
                     decal.ZIndex,
                     decal.Cleanable);
 
-                data.Decals.Add(did, position);
-
                 DebugTools.Assert(result);
             }
         }
-
-        return data;
     }
 }
index b758fa2d1681db7d73f65c4f685c6ce8c0d7528f..3a0a7ab2cdb90cef141a13434ee3e571d6877046 100644 (file)
@@ -199,8 +199,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
         MapGridComponent grid,
         Vector2i position,
         int seed,
-        EntityCoordinates? coordinates = null,
-        HashSet<Vector2i>? reservedTiles = null)
+        EntityCoordinates? coordinates = null)
     {
         var cancelToken = new CancellationTokenSource();
         var job = new DungeonJob.DungeonJob(
@@ -222,20 +221,18 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             seed,
             position,
             coordinates,
-            cancelToken.Token,
-            reservedTiles);
+            cancelToken.Token);
 
         _dungeonJobs.Add(job, cancelToken);
         _dungeonJobQueue.EnqueueJob(job);
     }
 
-    public async Task<(List<Dungeon>, DungeonData)> GenerateDungeonAsync(
+    public async Task<List<Dungeon>> GenerateDungeonAsync(
         DungeonConfig gen,
         EntityUid gridUid,
         MapGridComponent grid,
         Vector2i position,
-        int seed,
-        HashSet<Vector2i>? reservedTiles = null)
+        int seed)
     {
         var cancelToken = new CancellationTokenSource();
         var job = new DungeonJob.DungeonJob(
@@ -257,8 +254,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             seed,
             position,
             null,
-            cancelToken.Token,
-            reservedTiles);
+            cancelToken.Token);
 
         _dungeonJobs.Add(job, cancelToken);
         _dungeonJobQueue.EnqueueJob(job);
@@ -269,7 +265,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             throw job.Exception;
         }
 
-        return job.Result;
+        return job.Result!;
     }
 
     public Angle GetDungeonRotation(int seed)
index 3a74a67dbf257b3420233ebbabbd8877122bbb4f..cbce4dc6926e8e83e892ca22fe7d9d278476b38c 100644 (file)
@@ -1,17 +1,22 @@
+using System.Collections;
 using System.Linq;
 using System.Numerics;
 using System.Threading;
 using System.Threading.Tasks;
+using Content.Server.Atmos;
 using Content.Server.Atmos.Components;
 using Content.Server.Atmos.EntitySystems;
 using Robust.Shared.CPUJob.JobQueues;
 using Content.Server.Ghost.Roles.Components;
+using Content.Server.Parallax;
 using Content.Server.Procedural;
 using Content.Server.Salvage.Expeditions;
+using Content.Server.Salvage.Expeditions.Structure;
 using Content.Shared.Atmos;
 using Content.Shared.Construction.EntitySystems;
 using Content.Shared.Dataset;
 using Content.Shared.Gravity;
+using Content.Shared.Parallax.Biomes;
 using Content.Shared.Physics;
 using Content.Shared.Procedural;
 using Content.Shared.Procedural.Loot;
@@ -20,14 +25,15 @@ using Content.Shared.Salvage;
 using Content.Shared.Salvage.Expeditions;
 using Content.Shared.Salvage.Expeditions.Modifiers;
 using Content.Shared.Shuttles.Components;
+using Content.Shared.Storage;
 using Robust.Shared.Collections;
+using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Random;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 using Content.Server.Shuttles.Components;
-using Content.Shared.Procedural.Components;
 
 namespace Content.Server.Salvage;
 
@@ -114,13 +120,14 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             .GetMission(difficultyProto, _missionParams.Seed);
 
         var missionBiome = _prototypeManager.Index<SalvageBiomeModPrototype>(mission.Biome);
-        BiomeComponent? biome = null;
 
         if (missionBiome.BiomePrototype != null)
         {
+            var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
             var biomeSystem = _entManager.System<BiomeSystem>();
-
-            biome = biomeSystem.AddBiome(mapUid, missionBiome.BiomePrototype.Value, mission.Seed);
+            biomeSystem.SetTemplate(mapUid, biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
+            biomeSystem.SetSeed(mapUid, biome, mission.Seed);
+            _entManager.Dirty(mapUid, biome);
 
             // Gravity
             var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
@@ -165,7 +172,7 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
         dungeonOffset = dungeonRotation.RotateVec(dungeonOffset);
         var dungeonMod = _prototypeManager.Index<SalvageDungeonModPrototype>(mission.Dungeon);
         var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto);
-        var (dungeons, data) = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
+        var dungeons = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
             _missionParams.Seed));
 
         var dungeon = dungeons.First();
@@ -176,20 +183,18 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             return false;
         }
 
-        // Don't modify any dungeon tiles with chunk gen.
-        // Have to defer biome loading until the primo dungen is generated.
-        if (biome != null)
+        expedition.DungeonLocation = dungeonOffset;
+
+        List<Vector2i> reservedTiles = new();
+
+        foreach (var tile in _map.GetTilesIntersecting(mapUid, grid, new Circle(Vector2.Zero, landingPadRadius), false))
         {
-            foreach (var tile in dungeon.AllTiles)
-            {
-                biome.ModifiedTiles.Add(tile);
-            }
+            if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
+                continue;
 
-            biome.Enabled = true;
+            reservedTiles.Add(tile.GridIndices);
         }
 
-        expedition.DungeonLocation = dungeonOffset;
-
         var budgetEntries = new List<IBudgetEntry>();
 
         /*
@@ -203,14 +208,13 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             if (!lootProto.Guaranteed)
                 continue;
 
-            foreach (var rule in lootProto.LootRules)
+            try
             {
-                switch (rule)
-                {
-                    case BiomeLoot biomeLoot:
-                        _biome.AddLayer(mapUid, $"{rule}", biomeLoot.Proto);
-                        break;
-                }
+                await SpawnDungeonLoot(lootProto, mapUid);
+            }
+            catch (Exception e)
+            {
+                _sawmill.Error($"Failed to spawn guaranteed loot {lootProto.ID}: {e}");
             }
         }
 
@@ -319,4 +323,32 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
 
         // oh noooooooooooo
     }
+
+    private async Task SpawnDungeonLoot(SalvageLootPrototype loot, EntityUid gridUid)
+    {
+        for (var i = 0; i < loot.LootRules.Count; i++)
+        {
+            var rule = loot.LootRules[i];
+
+            switch (rule)
+            {
+                case BiomeMarkerLoot biomeLoot:
+                    {
+                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
+                        {
+                            _biome.AddMarkerLayer(gridUid, biome, biomeLoot.Prototype);
+                        }
+                    }
+                    break;
+                case BiomeTemplateLoot biomeLoot:
+                    {
+                        if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
+                        {
+                            _biome.AddTemplate(gridUid, biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
+                        }
+                    }
+                    break;
+            }
+        }
+    }
 }
index d2a3157232b856f89b3a0fdedefce07833727e34..aa1c2e6dff105134303eed9d98c177bc97b2500f 100644 (file)
@@ -6,7 +6,6 @@ using Content.Server.DeviceNetwork.Systems;
 using Content.Server.GameTicking;
 using Content.Server.GameTicking.Events;
 using Content.Server.Parallax;
-using Content.Server.Procedural;
 using Content.Server.Screens.Components;
 using Content.Server.Shuttles.Components;
 using Content.Server.Shuttles.Events;
@@ -23,6 +22,7 @@ using Content.Shared.DeviceNetwork.Components;
 using Content.Shared.GameTicking;
 using Content.Shared.Mobs.Components;
 using Content.Shared.Movement.Components;
+using Content.Shared.Parallax.Biomes;
 using Content.Shared.Salvage;
 using Content.Shared.Shuttles.Components;
 using Content.Shared.Tiles;
@@ -82,11 +82,11 @@ public sealed class ArrivalsSystem : EntitySystem
     /// </summary>
     private const float RoundStartFTLDuration = 10f;
 
-    private readonly List<EntProtoId> _arrivalsBiomeOptions = new()
+    private readonly List<ProtoId<BiomeTemplatePrototype>> _arrivalsBiomeOptions = new()
     {
-        "BiomeGrasslands",
-        "BiomeLowDesert",
-        "BiomeSnow",
+        "Grasslands",
+        "LowDesert",
+        "Snow",
     };
 
     public override void Initialize()
index 9e9bcb6c4247d12b23cee0d6a83ecf54a990d191..13e13bf8f371fb996d6c6442fd920f9cb77c4982 100644 (file)
@@ -1,14 +1,12 @@
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Numerics;
-using Content.Server.Decals;
 using Content.Server.Shuttles.Components;
 using Content.Server.Shuttles.Events;
 using Content.Server.Station.Events;
 using Content.Shared.Body.Components;
 using Content.Shared.CCVar;
 using Content.Shared.Database;
-using Content.Shared.Decals;
 using Content.Shared.Ghost;
 using Content.Shared.Maps;
 using Content.Shared.Parallax;
@@ -958,7 +956,6 @@ public sealed partial class ShuttleSystem
         var transform = _physics.GetRelativePhysicsTransform((uid, xform), xform.MapUid.Value);
         var aabbs = new List<Box2>(manager.Fixtures.Count);
         var tileSet = new List<(Vector2i, Tile)>();
-        TryComp(xform.MapUid.Value, out DecalGridComponent? decalGrid);
 
         foreach (var fixture in manager.Fixtures.Values)
         {
@@ -972,15 +969,9 @@ public sealed partial class ShuttleSystem
             aabb = aabb.Enlarged(0.2f);
             aabbs.Add(aabb);
 
-            if (decalGrid != null)
-            {
-                foreach (var decal in _decals.GetDecalsIntersecting(xform.MapUid.Value, aabb))
-                {
-                    _decals.RemoveDecal(xform.MapUid.Value, decal.Index, decalGrid);
-                }
-            }
-
+            // Handle clearing biome stuff as relevant.
             tileSet.Clear();
+            _biomes.ReserveTiles(xform.MapUid.Value, aabb, tileSet);
             _lookupEnts.Clear();
             _immuneEnts.Clear();
             // TODO: Ideally we'd query first BEFORE moving grid but needs adjustments above.
index 525e16ae1a988f68909efce9ac55faae35edd5a3..cea7fbfc091f1ab136a43aac86f28d959117408b 100644 (file)
@@ -1,7 +1,6 @@
 using Content.Server.Administration.Logs;
 using Content.Server.Body.Systems;
 using Content.Server.Buckle.Systems;
-using Content.Server.Decals;
 using Content.Server.Parallax;
 using Content.Server.Procedural;
 using Content.Server.Shuttles.Components;
@@ -42,12 +41,10 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem
     [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IPrototypeManager _protoManager = default!;
     [Dependency] private readonly IRobustRandom _random = default!;
-    [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
     [Dependency] private readonly BiomeSystem _biomes = default!;
     [Dependency] private readonly BodySystem _bobby = default!;
     [Dependency] private readonly BuckleSystem _buckle = default!;
     [Dependency] private readonly DamageableSystem _damageSys = default!;
-    [Dependency] private readonly DecalSystem _decals = default!;
     [Dependency] private readonly DockingSystem _dockSystem = default!;
     [Dependency] private readonly DungeonSystem _dungeon = default!;
     [Dependency] private readonly EntityLookupSystem _lookup = default!;
index a2105d6cef4a72bd7ac9b42f45ade8ef06db8d2b..0eb64aaff8092a3d13336d063d62771617dd9eec 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Station.Systems;
+using Content.Shared.Parallax.Biomes;
 using Robust.Shared.Prototypes;
 
 namespace Content.Server.Station.Components;
@@ -10,7 +11,7 @@ namespace Content.Server.Station.Components;
 public sealed partial class StationBiomeComponent : Component
 {
     [DataField(required: true)]
-    public EntProtoId Biome = "BiomeGrasslands";
+    public ProtoId<BiomeTemplatePrototype> Biome = "Grasslands";
 
     // If null, its random
     [DataField]
index c0777f605253ee0a4e719fe6fab91340a221708c..c12e2f47e4822692cddfd9520db69c7d5e62c9f9 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Parallax;
-using Content.Server.Procedural;
 using Content.Server.Station.Components;
 using Content.Server.Station.Events;
 using Robust.Shared.Prototypes;
index 3c3065c95a688e7473923190338152a4a296e217..e771add0e41a32f3b8a2c0ed7469b95dd9973f5c 100644 (file)
@@ -191,7 +191,7 @@ namespace Content.Server.Tabletop
                 if (!TryComp(uid, out ActorComponent? actor))
                 {
                     RemComp<TabletopGamerComponent>(uid);
-                    continue;
+                    return;
                 }
 
                 if (actor.PlayerSession.Status != SessionStatus.InGame || !CanSeeTable(uid, gamer.Tabletop))
diff --git a/Content.Shared/CCVar/CCVars.Biome.cs b/Content.Shared/CCVar/CCVars.Biome.cs
deleted file mode 100644 (file)
index 13c6cd5..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-using Robust.Shared.Configuration;
-
-namespace Content.Shared.CCVar;
-
-public sealed partial class CCVars
-{
-    /// <summary>
-    /// Load range for biomes. Set this higher than PVS so server can have some time to load in before the client arrives.
-    /// </summary>
-    public static readonly CVarDef<float> BiomeLoadRange =
-        CVarDef.Create("biome.load_range", 20f, CVar.SERVERONLY);
-
-    /// <summary>
-    /// Time allocation (ms) for how long biomes are allowed to load.
-    /// </summary>
-    public static readonly CVarDef<float> BiomeLoadTime =
-        CVarDef.Create("biome.load_time", 0.03f, CVar.SERVERONLY);
-}
diff --git a/Content.Shared/Parallax/Biomes/BiomeComponent.cs b/Content.Shared/Parallax/Biomes/BiomeComponent.cs
new file mode 100644 (file)
index 0000000..af8eb88
--- /dev/null
@@ -0,0 +1,87 @@
+using Content.Shared.Parallax.Biomes.Layers;
+using Content.Shared.Parallax.Biomes.Markers;
+using Robust.Shared.GameStates;
+using Robust.Shared.Noise;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Parallax.Biomes;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedBiomeSystem))]
+public sealed partial class BiomeComponent : Component
+{
+    /// <summary>
+    /// Do we load / deload.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)]
+    public bool Enabled = true;
+
+    [ViewVariables(VVAccess.ReadWrite), DataField("seed")]
+    [AutoNetworkedField]
+    public int Seed = -1;
+
+    /// <summary>
+    /// The underlying entity, decal, and tile layers for the biome.
+    /// </summary>
+    [DataField("layers")]
+    [AutoNetworkedField]
+    public List<IBiomeLayer> Layers = new();
+
+    /// <summary>
+    /// Templates to use for <see cref="Layers"/>.
+    /// If this is set on mapinit, it will fill out layers automatically.
+    /// If not set, use <c>BiomeSystem</c> to do it.
+    /// Prototype reloading will also use this.
+    /// </summary>
+    [DataField]
+    public ProtoId<BiomeTemplatePrototype>? Template;
+
+    /// <summary>
+    /// If we've already generated a tile and couldn't deload it then we won't ever reload it in future.
+    /// Stored by [Chunkorigin, Tiles]
+    /// </summary>
+    [DataField("modifiedTiles")]
+    public Dictionary<Vector2i, HashSet<Vector2i>> ModifiedTiles = new();
+
+    /// <summary>
+    /// Decals that have been loaded as a part of this biome.
+    /// </summary>
+    [DataField("decals")]
+    public Dictionary<Vector2i, Dictionary<uint, Vector2i>> LoadedDecals = new();
+
+    [DataField("entities")]
+    public Dictionary<Vector2i, Dictionary<EntityUid, Vector2i>> LoadedEntities = new();
+
+    /// <summary>
+    /// Currently active chunks
+    /// </summary>
+    [DataField("loadedChunks")]
+    public HashSet<Vector2i> LoadedChunks = new();
+
+    #region Markers
+
+    /// <summary>
+    /// Work out entire marker tiles in advance but only load the entities when in range.
+    /// </summary>
+    [DataField("pendingMarkers")]
+    public Dictionary<Vector2i, Dictionary<string, List<Vector2i>>> PendingMarkers = new();
+
+    /// <summary>
+    /// Track what markers we've loaded already to avoid double-loading.
+    /// </summary>
+    [DataField("loadedMarkers", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<Vector2i>, BiomeMarkerLayerPrototype>))]
+    public Dictionary<string, HashSet<Vector2i>> LoadedMarkers = new();
+
+    [DataField]
+    public HashSet<ProtoId<BiomeMarkerLayerPrototype>> MarkerLayers = new();
+
+    /// <summary>
+    /// One-tick forcing of marker layers to bulldoze any entities in the way.
+    /// </summary>
+    [DataField]
+    public HashSet<ProtoId<BiomeMarkerLayerPrototype>> ForcedMarkerLayers = new();
+
+    #endregion
+}
diff --git a/Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs b/Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs
new file mode 100644 (file)
index 0000000..437ead6
--- /dev/null
@@ -0,0 +1,16 @@
+using Content.Shared.Parallax.Biomes.Layers;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Parallax.Biomes;
+
+/// <summary>
+/// A preset group of biome layers to be used for a <see cref="BiomeComponent"/>
+/// </summary>
+[Prototype]
+public sealed partial class BiomeTemplatePrototype : IPrototype
+{
+    [IdDataField] public string ID { get; private set; } = default!;
+
+    [DataField("layers")]
+    public List<IBiomeLayer> Layers = new();
+}
diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs
new file mode 100644 (file)
index 0000000..5e31e51
--- /dev/null
@@ -0,0 +1,34 @@
+using Content.Shared.Decals;
+using Content.Shared.Maps;
+using Robust.Shared.Noise;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Parallax.Biomes.Layers;
+
+[Serializable, NetSerializable]
+public sealed partial class BiomeDecalLayer : IBiomeWorldLayer
+{
+    /// <inheritdoc/>
+    [DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
+    public List<string> AllowedTiles { get; private set; } = new();
+
+    /// <summary>
+    /// Divide each tile up by this amount.
+    /// </summary>
+    [DataField("divisions")]
+    public float Divisions = 1f;
+
+    [DataField("noise")]
+    public FastNoiseLite Noise { get; private set; } = new(0);
+
+    /// <inheritdoc/>
+    [DataField("threshold")]
+    public float Threshold { get; private set; } = 0.8f;
+
+    /// <inheritdoc/>
+    [DataField("invert")] public bool Invert { get; private set; } = false;
+
+    [DataField("decals", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<DecalPrototype>))]
+    public List<string> Decals = new();
+}
diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs
new file mode 100644 (file)
index 0000000..2beeba6
--- /dev/null
@@ -0,0 +1,18 @@
+using Robust.Shared.Noise;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Parallax.Biomes.Layers;
+
+/// <summary>
+/// Dummy layer that specifies a marker to be replaced by external code.
+/// For example if they wish to add their own layers at specific points across different templates.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed partial class BiomeDummyLayer : IBiomeLayer
+{
+    [DataField("id", required: true)] public string ID = string.Empty;
+
+    public FastNoiseLite Noise { get; } = new();
+    public float Threshold { get; }
+    public bool Invert { get; }
+}
diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs
new file mode 100644 (file)
index 0000000..21ffdd9
--- /dev/null
@@ -0,0 +1,27 @@
+using Content.Shared.Maps;
+using Robust.Shared.Noise;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Parallax.Biomes.Layers;
+
+[Serializable, NetSerializable]
+public sealed partial class BiomeEntityLayer : IBiomeWorldLayer
+{
+    /// <inheritdoc/>
+    [DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
+    public List<string> AllowedTiles { get; private set; } = new();
+
+    [DataField("noise")] public FastNoiseLite Noise { get; private set; } = new(0);
+
+    /// <inheritdoc/>
+    [DataField("threshold")]
+    public float Threshold { get; private set; } = 0.5f;
+
+    /// <inheritdoc/>
+    [DataField("invert")] public bool Invert { get; private set; } = false;
+
+    [DataField("entities", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
+    public List<string> Entities = new();
+}
diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs
new file mode 100644 (file)
index 0000000..5123140
--- /dev/null
@@ -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;
+
+/// <summary>
+/// Contains more biome layers recursively via a biome template.
+/// Can be used for sub-biomes.
+/// </summary>
+[Serializable, NetSerializable]
+public sealed partial class BiomeMetaLayer : IBiomeLayer
+{
+    [DataField("noise")]
+    public FastNoiseLite Noise { get; private set; } = new(0);
+
+    /// <inheritdoc/>
+    [DataField("threshold")]
+    public float Threshold { get; private set; } = -1f;
+
+    /// <inheritdoc/>
+    [DataField("invert")]
+    public bool Invert { get; private set; }
+
+    [DataField("template", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
+    public string Template = string.Empty;
+}
similarity index 66%
rename from Content.Shared/Procedural/DungeonLayers/SampleTileDunGen.cs
rename to Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs
index 6d23d201f54e8bd5d3c47409c5e70a48c8f33708..9dee35da4e6deb57181cc62e80075b694d5a1362 100644 (file)
@@ -2,26 +2,20 @@ using Content.Shared.Maps;
 using Robust.Shared.Noise;
 using Robust.Shared.Prototypes;
 using Robust.Shared.Serialization;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
 
-namespace Content.Shared.Procedural.DungeonLayers;
+namespace Content.Shared.Parallax.Biomes.Layers;
 
-/// <summary>
-/// Samples noise and spawns the specified tile in the dungeon area.
-/// </summary>
 [Serializable, NetSerializable]
-public sealed partial class SampleTileDunGen : IDunGenLayer
+public sealed partial class BiomeTileLayer : IBiomeLayer
 {
-    /// <summary>
-    /// Reserve any tiles we update.
-    /// </summary>
-    [DataField]
-    public bool ReserveTiles = true;
-
     [DataField] public FastNoiseLite Noise { get; private set; } = new(0);
 
+    /// <inheritdoc/>
     [DataField]
     public float Threshold { get; private set; } = 0.5f;
 
+    /// <inheritdoc/>
     [DataField] public bool Invert { get; private set; } = false;
 
     /// <summary>
diff --git a/Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs b/Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs
new file mode 100644 (file)
index 0000000..3b1ad5c
--- /dev/null
@@ -0,0 +1,22 @@
+using Robust.Shared.Noise;
+
+namespace Content.Shared.Parallax.Biomes.Layers;
+
+[ImplicitDataDefinitionForInheritors]
+public partial interface IBiomeLayer
+{
+    /// <summary>
+    /// Seed is used an offset from the relevant BiomeComponent's seed.
+    /// </summary>
+    FastNoiseLite Noise { get; }
+
+    /// <summary>
+    /// Threshold for this layer to be present. If set to 0 forces it for every tile.
+    /// </summary>
+    float Threshold { get; }
+
+    /// <summary>
+    /// Is the thresold inverted so we need to be lower than it.
+    /// </summary>
+    public bool Invert { get; }
+}
diff --git a/Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs b/Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs
new file mode 100644 (file)
index 0000000..e04db91
--- /dev/null
@@ -0,0 +1,12 @@
+namespace Content.Shared.Parallax.Biomes.Layers;
+
+/// <summary>
+/// Handles actual objects such as decals and entities.
+/// </summary>
+public partial interface IBiomeWorldLayer : IBiomeLayer
+{
+    /// <summary>
+    /// What tiles we're allowed to spawn on, real or biome.
+    /// </summary>
+    List<string> AllowedTiles { get; }
+}
diff --git a/Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs b/Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs
new file mode 100644 (file)
index 0000000..fbc3a04
--- /dev/null
@@ -0,0 +1,53 @@
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Parallax.Biomes.Markers;
+
+/// <summary>
+/// Spawns entities inside of the specified area with the minimum specified radius.
+/// </summary>
+[Prototype]
+public sealed partial class BiomeMarkerLayerPrototype : IBiomeMarkerLayer
+{
+    [IdDataField] public string ID { get; private set; } = default!;
+
+    /// <summary>
+    /// Checks for the relevant entity for the tile before spawning. Useful for substituting walls with ore veins for example.
+    /// </summary>
+    [DataField]
+    public Dictionary<EntProtoId, EntProtoId> EntityMask { get; private set; } = new();
+
+    /// <summary>
+    /// Default prototype to spawn. If null will fall back to entity mask.
+    /// </summary>
+    [DataField]
+    public string? Prototype { get; private set; }
+
+    /// <summary>
+    /// Minimum radius between 2 points
+    /// </summary>
+    [DataField("radius")]
+    public float Radius = 32f;
+
+    /// <summary>
+    /// Maximum amount of group spawns
+    /// </summary>
+    [DataField("maxCount")]
+    public int MaxCount = int.MaxValue;
+
+    /// <summary>
+    /// Minimum entities to spawn in one group.
+    /// </summary>
+    [DataField]
+    public int MinGroupSize = 1;
+
+    /// <summary>
+    /// Maximum entities to spawn in one group.
+    /// </summary>
+    [DataField]
+    public int MaxGroupSize = 1;
+
+    /// <inheritdoc />
+    [DataField("size")]
+    public int Size { get; private set; } = 128;
+}
diff --git a/Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs b/Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs
new file mode 100644 (file)
index 0000000..de2913b
--- /dev/null
@@ -0,0 +1,22 @@
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Parallax.Biomes.Markers;
+
+/// <summary>
+/// Specifies one-off marker points to be used. This could be for dungeon markers, mob markers, etc.
+/// These are run outside of the tile / decal / entity layers.
+/// </summary>
+public interface IBiomeMarkerLayer : IPrototype
+{
+    /// <summary>
+    /// Biome template to use as a mask for this layer.
+    /// </summary>
+    public Dictionary<EntProtoId, EntProtoId> EntityMask { get; }
+
+    public string? Prototype { get; }
+
+    /// <summary>
+    /// How large the pre-generated points area is.
+    /// </summary>
+    public int Size { get; }
+}
diff --git a/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs b/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs
new file mode 100644 (file)
index 0000000..a5238e8
--- /dev/null
@@ -0,0 +1,386 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Numerics;
+using Content.Shared.Maps;
+using Content.Shared.Parallax.Biomes.Layers;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Noise;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Parallax.Biomes;
+
+public abstract class SharedBiomeSystem : EntitySystem
+{
+    [Dependency] protected readonly IPrototypeManager ProtoManager = default!;
+    [Dependency] private readonly ISerializationManager _serManager = default!;
+    [Dependency] protected readonly ITileDefinitionManager TileDefManager = default!;
+    [Dependency] private readonly TileSystem _tile = default!;
+    [Dependency] private readonly SharedMapSystem _map = default!;
+
+    protected const byte ChunkSize = 8;
+
+    private T Pick<T>(List<T> collection, float value)
+    {
+        // Listen I don't need this exact and I'm too lazy to finetune just for random ent picking.
+        value %= 1f;
+        value = Math.Clamp(value, 0f, 1f);
+
+        if (collection.Count == 1)
+            return collection[0];
+
+        var randValue = value * collection.Count;
+
+        foreach (var item in collection)
+        {
+            randValue -= 1f;
+
+            if (randValue <= 0f)
+            {
+                return item;
+            }
+        }
+
+        throw new ArgumentOutOfRangeException();
+    }
+
+    private int Pick(int count, float value)
+    {
+        value %= 1f;
+        value = Math.Clamp(value, 0f, 1f);
+
+        if (count == 1)
+            return 0;
+
+        value *= count;
+
+        for (var i = 0; i < count; i++)
+        {
+            value -= 1f;
+
+            if (value <= 0f)
+            {
+                return i;
+            }
+        }
+
+        throw new ArgumentOutOfRangeException();
+    }
+
+    public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
+    {
+        if (_map.TryGetTileRef(uid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
+        {
+            tile = tileRef.Tile;
+            return true;
+        }
+
+        if (!TryComp<BiomeComponent>(uid, out var biome))
+        {
+            tile = null;
+            return false;
+        }
+
+        return TryGetBiomeTile(indices, biome.Layers, biome.Seed, (uid, grid), out tile);
+    }
+
+    /// <summary>
+    /// Tries to get the tile, real or otherwise, for the specified indices.
+    /// </summary>
+    public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
+    {
+        if (grid is { } gridEnt && _map.TryGetTileRef(gridEnt, gridEnt.Comp, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
+        {
+            tile = tileRef.Tile;
+            return true;
+        }
+
+        return TryGetTile(indices, layers, seed, grid, out tile);
+    }
+
+    /// <summary>
+    /// Tries to get the tile, real or otherwise, for the specified indices.
+    /// </summary>
+    [Obsolete("Use the Entity<MapGridComponent>? overload")]
+    public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
+    {
+        return TryGetBiomeTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
+    }
+
+    /// <summary>
+    /// Gets the underlying biome tile, ignoring any existing tile that may be there.
+    /// </summary>
+    public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
+    {
+        for (var i = layers.Count - 1; i >= 0; i--)
+        {
+            var layer = layers[i];
+            var noiseCopy = GetNoise(layer.Noise, seed);
+
+            var invert = layer.Invert;
+            var value = noiseCopy.GetNoise(indices.X, indices.Y);
+            value = invert ? value * -1 : value;
+
+            if (value < layer.Threshold)
+                continue;
+
+            // Check if the tile is from meta layer, otherwise fall back to default layers.
+            if (layer is BiomeMetaLayer meta)
+            {
+                if (TryGetBiomeTile(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out tile))
+                {
+                    return true;
+                }
+
+                continue;
+            }
+
+            if (layer is not BiomeTileLayer tileLayer)
+                continue;
+
+            if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index(tileLayer.Tile), tileLayer.Variants, out tile))
+            {
+                return true;
+            }
+        }
+
+        tile = null;
+        return false;
+    }
+
+    /// <summary>
+    /// Gets the underlying biome tile, ignoring any existing tile that may be there.
+    /// </summary>
+    [Obsolete("Use the Entity<MapGridComponent>? overload")]
+    public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
+    {
+        return TryGetTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
+    }
+
+    /// <summary>
+    /// Gets the underlying biome tile, ignoring any existing tile that may be there.
+    /// </summary>
+    private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
+    {
+        var found = noise.GetNoise(indices.X, indices.Y);
+        found = invert ? found * -1 : found;
+
+        if (found < threshold)
+        {
+            tile = null;
+            return false;
+        }
+
+        byte variant = 0;
+        var variantCount = variants?.Count ?? tileDef.Variants;
+
+        // Pick a variant tile if they're available as well
+        if (variantCount > 1)
+        {
+            var variantValue = (noise.GetNoise(indices.X * 8, indices.Y * 8, variantCount) + 1f) * 100;
+            variant = _tile.PickVariant(tileDef, (int)variantValue);
+        }
+
+        tile = new Tile(tileDef.TileId, variant);
+        return true;
+    }
+
+    /// <summary>
+    /// Tries to get the relevant entity for this tile.
+    /// </summary>
+    public bool TryGetEntity(Vector2i indices, BiomeComponent component, Entity<MapGridComponent>? grid,
+        [NotNullWhen(true)] out string? entity)
+    {
+        if (!TryGetBiomeTile(indices, component.Layers, component.Seed, grid, out var tile))
+        {
+            entity = null;
+            return false;
+        }
+
+        return TryGetEntity(indices, component.Layers, tile.Value, component.Seed, grid, out entity);
+    }
+
+    /// <summary>
+    /// Tries to get the relevant entity for this tile.
+    /// </summary>
+    [Obsolete("Use the Entity<MapGridComponent>? overload")]
+    public bool TryGetEntity(Vector2i indices, BiomeComponent component, MapGridComponent grid,
+        [NotNullWhen(true)] out string? entity)
+    {
+        return TryGetEntity(indices, component, grid == null ? null : (grid.Owner, grid), out entity);
+    }
+
+    public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, Entity<MapGridComponent>? grid,
+        [NotNullWhen(true)] out string? entity)
+    {
+        var tileId = TileDefManager[tileRef.TypeId].ID;
+
+        for (var i = layers.Count - 1; i >= 0; i--)
+        {
+            var layer = layers[i];
+
+            switch (layer)
+            {
+                case BiomeDummyLayer:
+                    continue;
+                case IBiomeWorldLayer worldLayer:
+                    if (!worldLayer.AllowedTiles.Contains(tileId))
+                        continue;
+
+                    break;
+                case BiomeMetaLayer:
+                    break;
+                default:
+                    continue;
+            }
+
+            var noiseCopy = GetNoise(layer.Noise, seed);
+
+            var invert = layer.Invert;
+            var value = noiseCopy.GetNoise(indices.X, indices.Y);
+            value = invert ? value * -1 : value;
+
+            if (value < layer.Threshold)
+                continue;
+
+            if (layer is BiomeMetaLayer meta)
+            {
+                if (TryGetEntity(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, tileRef, seed, grid, out entity))
+                {
+                    return true;
+                }
+
+                continue;
+            }
+
+            // Decals might block entity so need to check if there's one in front of us.
+            if (layer is not BiomeEntityLayer biomeLayer)
+            {
+                entity = null;
+                return false;
+            }
+
+            var noiseValue = noiseCopy.GetNoise(indices.X, indices.Y, i);
+            entity = Pick(biomeLayer.Entities, (noiseValue + 1f) / 2f);
+            return true;
+        }
+
+        entity = null;
+        return false;
+    }
+
+    [Obsolete("Use the Entity<MapGridComponent>? overload")]
+    public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, MapGridComponent grid,
+        [NotNullWhen(true)] out string? entity)
+    {
+        return TryGetEntity(indices, layers, tileRef, seed, grid == null ? null : (grid.Owner, grid), out entity);
+    }
+
+    /// <summary>
+    /// Tries to get the relevant decals for this tile.
+    /// </summary>
+    public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid,
+        [NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
+    {
+        if (!TryGetBiomeTile(indices, layers, seed, grid, out var tileRef))
+        {
+            decals = null;
+            return false;
+        }
+
+        var tileId = TileDefManager[tileRef.Value.TypeId].ID;
+
+        for (var i = layers.Count - 1; i >= 0; i--)
+        {
+            var layer = layers[i];
+
+            // Entities might block decal so need to check if there's one in front of us.
+            switch (layer)
+            {
+                case BiomeDummyLayer:
+                    continue;
+                case IBiomeWorldLayer worldLayer:
+                    if (!worldLayer.AllowedTiles.Contains(tileId))
+                        continue;
+
+                    break;
+                case BiomeMetaLayer:
+                    break;
+                default:
+                    continue;
+            }
+
+            var invert = layer.Invert;
+            var noiseCopy = GetNoise(layer.Noise, seed);
+            var value = noiseCopy.GetNoise(indices.X, indices.Y);
+            value = invert ? value * -1 : value;
+
+            if (value < layer.Threshold)
+                continue;
+
+            if (layer is BiomeMetaLayer meta)
+            {
+                if (TryGetDecals(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out decals))
+                {
+                    return true;
+                }
+
+                continue;
+            }
+
+            // Check if the other layer should even render, if not then keep going.
+            if (layer is not BiomeDecalLayer decalLayer)
+            {
+                decals = null;
+                return false;
+            }
+
+            decals = new List<(string ID, Vector2 Position)>();
+
+            for (var x = 0; x < decalLayer.Divisions; x++)
+            {
+                for (var y = 0; y < decalLayer.Divisions; y++)
+                {
+                    var index = new Vector2(indices.X + x * 1f / decalLayer.Divisions, indices.Y + y * 1f / decalLayer.Divisions);
+                    var decalValue = noiseCopy.GetNoise(index.X, index.Y);
+                    decalValue = invert ? decalValue * -1 : decalValue;
+
+                    if (decalValue < decalLayer.Threshold)
+                        continue;
+
+                    decals.Add((Pick(decalLayer.Decals, (noiseCopy.GetNoise(indices.X, indices.Y, x + y * decalLayer.Divisions) + 1f) / 2f), index));
+                }
+            }
+
+            // Check other layers
+            if (decals.Count == 0)
+                continue;
+
+            return true;
+        }
+
+        decals = null;
+        return false;
+    }
+
+    /// <summary>
+    /// Tries to get the relevant decals for this tile.
+    /// </summary>
+    [Obsolete("Use the Entity<MapGridComponent>? overload")]
+    public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent grid,
+        [NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
+    {
+        return TryGetDecals(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out decals);
+    }
+
+    private FastNoiseLite GetNoise(FastNoiseLite seedNoise, int seed)
+    {
+        var noiseCopy = new FastNoiseLite();
+        _serManager.CopyTo(seedNoise, ref noiseCopy, notNullableOverride: true);
+        noiseCopy.SetSeed(noiseCopy.GetSeed() + seed);
+        // Ensure re-calculate is run.
+        noiseCopy.SetFractalOctaves(noiseCopy.GetFractalOctaves());
+        return noiseCopy;
+    }
+}
diff --git a/Content.Shared/Procedural/Components/BiomeComponent.cs b/Content.Shared/Procedural/Components/BiomeComponent.cs
deleted file mode 100644 (file)
index f79a31c..0000000
+++ /dev/null
@@ -1,86 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Procedural.Components;
-
-/// <summary>
-/// A layer inside of <see cref="BiomeComponent"/>
-/// </summary>
-[DataRecord]
-public sealed record BiomeMetaLayer
-{
-    /// <summary>
-    /// Chunk dimensions for this meta layer. Will try to infer it from the first layer of the dungeon if null.
-    /// </summary>
-    [DataField]
-    public int? Size;
-
-    /// <summary>
-    /// Meta layers that this one requires to be loaded first.
-    /// Will ensure all of the chunks for our corresponding area are loaded.
-    /// </summary>
-    public List<string>? DependsOn;
-
-    /// <summary>
-    /// Can this layer be unloaded if no one is in range.
-    /// </summary>
-    public bool CanUnload = true;
-
-    /// <summary>
-    /// Dungeon config to load inside the specified area.
-    /// </summary>
-    [DataField(required: true)]
-    public ProtoId<DungeonConfigPrototype> Dungeon = new();
-}
-
-[RegisterComponent]
-public sealed partial class BiomeComponent : Component
-{
-    /// <summary>
-    /// Can we load / unload chunks.
-    /// </summary>
-    [DataField]
-    public bool Enabled = true;
-
-    /// <summary>
-    /// Areas queued for preloading. Will add these during <see cref="BiomeLoadJob"/> and then flag as modified so they retain.
-    /// </summary>
-    [DataField]
-    public List<Box2i> PreloadAreas = new();
-
-    /// <summary>
-    /// Is there currently a job that's loading.
-    /// </summary>
-    public bool Loading = false;
-
-    [DataField]
-    public int Seed;
-
-    /// <summary>
-    /// Layer key and associated data.
-    /// </summary>
-    [DataField(required: true)]
-    public Dictionary<string, BiomeMetaLayer> Layers = new();
-
-    /// <summary>
-    /// Layer removals that are pending.
-    /// </summary>
-    [DataField]
-    public List<string> PendingRemovals = new();
-
-    /// <summary>
-    /// Data that is currently loaded.
-    /// </summary>
-    [DataField]
-    public Dictionary<string, Dictionary<Vector2i, DungeonData>> LoadedData = new();
-
-    /// <summary>
-    /// Flag modified tiles so we don't try and unload / reload them.
-    /// </summary>
-    [DataField]
-    public HashSet<Vector2i> ModifiedTiles = new();
-
-    /// <summary>
-    /// Bounds loaded by players for this tick.
-    /// </summary>
-    public List<Box2i> LoadedBounds = new();
-}
diff --git a/Content.Shared/Procedural/Components/BiomeForceUnloadComponent.cs b/Content.Shared/Procedural/Components/BiomeForceUnloadComponent.cs
deleted file mode 100644 (file)
index 215a8f3..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-using Robust.Shared.GameStates;
-
-namespace Content.Shared.Procedural.Components;
-
-/// <summary>
-/// Will forcibly unload an entity no matter what. Useful if you have consistent entities that will never be default or the likes.
-/// </summary>
-[RegisterComponent, NetworkedComponent]
-public sealed partial class BiomeForceUnloadComponent : Component;
diff --git a/Content.Shared/Procedural/Distance/DunGenDistanceSquared.cs b/Content.Shared/Procedural/Distance/DunGenDistanceSquared.cs
deleted file mode 100644 (file)
index 813f5fc..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Content.Shared.Procedural.Distance;
-
-public sealed partial class DunGenDistanceSquared : IDunGenDistance
-{
-    [DataField]
-    public float BlendWeight { get; set; } = 0.50f;
-}
index 56131e5b28045f51956815ceabe4f54e16525e8e..7c84b1a6a3b22d484979a5d66eba91bec9b6e12f 100644 (file)
@@ -12,18 +12,11 @@ public partial class DungeonConfig
     public List<IDunGenLayer> Layers = new();
 
     /// <summary>
-    /// Should we reserve the tiles generated by this config so no other layers at the same level can spawn on this tile?
+    /// Should we reserve the tiles generated by this config so no other dungeons can spawn on it within the same job?
     /// </summary>
     [DataField]
     public bool ReserveTiles;
 
-    /// <summary>
-    /// Should we return the reserved tiles to the upper level.
-    /// Set to false if you don't care if this dungeon has its tiles overwritten at higher levels.
-    /// </summary>
-    [DataField]
-    public bool ReturnReserved = true;
-
     /// <summary>
     /// Minimum times to run the config.
     /// </summary>
diff --git a/Content.Shared/Procedural/DungeonData.cs b/Content.Shared/Procedural/DungeonData.cs
deleted file mode 100644 (file)
index a0209f9..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-using System.Linq;
-using System.Numerics;
-using Robust.Shared.Map;
-
-namespace Content.Shared.Procedural;
-
-/// <summary>
-/// Contains the loaded data for a dungeon.
-/// </summary>
-[DataDefinition]
-public sealed partial class DungeonData
-{
-    [DataField]
-    public Dictionary<uint, Vector2> Decals = new();
-
-    [DataField]
-    public Dictionary<EntityUid, Vector2i> Entities = new();
-
-    [DataField]
-    public Dictionary<Vector2i, Tile> Tiles = new();
-
-    public static DungeonData Empty = new();
-
-    public void Merge(DungeonData data)
-    {
-        foreach (var did in data.Decals)
-        {
-            Decals[did.Key] = did.Value;
-        }
-
-        foreach (var ent in data.Entities)
-        {
-            Entities[ent.Key] = ent.Value;
-        }
-
-        foreach (var tile in data.Tiles)
-        {
-            Tiles[tile.Key] = tile.Value;
-        }
-    }
-}
diff --git a/Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs
deleted file mode 100644 (file)
index b48a1b3..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-using Robust.Shared.Noise;
-
-namespace Content.Shared.Procedural.DungeonGenerators;
-
-/// <summary>
-/// Turns a chunked area into a dungeon for layer purposes. Assumes the position is the BL origin.
-/// </summary>
-public sealed partial class ChunkDunGen : IDunGenLayer
-{
-    [DataField]
-    public int Size = 16;
-
-    /// <summary>
-    /// Noise to apply for each tile conditionally.
-    /// </summary>
-    [DataField]
-    public FastNoiseLite? Noise;
-
-    /// <summary>
-    /// Threshold for noise. Does nothing if <see cref="Noise"/> is null.
-    /// </summary>
-    [DataField]
-    public float Threshold = -1f;
-}
index 2ece880851b3435098625e1d0d171edaa274dcdf..e9a5181f8d07ab02fe78d87d9480f8c62bda2088 100644 (file)
@@ -10,10 +10,4 @@ public sealed partial class ExteriorDunGen : IDunGenLayer
 {
     [DataField(required: true)]
     public ProtoId<DungeonConfigPrototype> Proto;
-
-    /// <summary>
-    /// Minimum and maximum penetration.
-    /// </summary>
-    [DataField]
-    public Vector2i Penetration = new Vector2i(5, 15);
 }
index c3f7ae3f332bb27f276770c8df5f07c4e5718673..89a4ab216a1acf3561b37d4c7a799f8cc5e6b063 100644 (file)
@@ -14,12 +14,6 @@ public sealed partial class PrototypeDunGen : IDunGenLayer
     [DataField]
     public DungeonInheritance InheritDungeons = DungeonInheritance.None;
 
-    /// <summary>
-    /// Should we pass in the current level's reserved tiles to the prototype.
-    /// </summary>
-    [DataField]
-    public ReservedInheritance InheritReserved = ReservedInheritance.All;
-
     [DataField(required: true)]
     public ProtoId<DungeonConfigPrototype> Proto;
 }
@@ -41,16 +35,3 @@ public enum DungeonInheritance : byte
     /// </summary>
     All,
 }
-
-public enum ReservedInheritance : byte
-{
-    /// <summary>
-    /// Don't inherit any reserved tiles.
-    /// </summary>
-    None,
-
-    /// <summary>
-    /// Inherit reserved tiles,
-    /// </summary>
-    All,
-}
index e2506298fd7bc31ec086c00261020808ae84f8fc..363de0a511668fe7b11846ff2119cae3c3c4f294 100644 (file)
@@ -1,7 +1,4 @@
-using System.Numerics;
 using Content.Shared.Maps;
-using Content.Shared.Procedural.Distance;
-using Robust.Shared.Noise;
 using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Procedural.DungeonLayers;
@@ -9,6 +6,10 @@ namespace Content.Shared.Procedural.DungeonLayers;
 /// <summary>
 /// Fills unreserved tiles with the specified entity prototype.
 /// </summary>
+/// <remarks>
+/// DungeonData keys are:
+/// - Fill
+/// </remarks>
 public sealed partial class FillGridDunGen : IDunGenLayer
 {
     /// <summary>
@@ -19,29 +20,4 @@ public sealed partial class FillGridDunGen : IDunGenLayer
 
     [DataField(required: true)]
     public EntProtoId Entity;
-
-    #region Noise
-
-    [DataField]
-    public bool Invert;
-
-    /// <summary>
-    /// Optionally don't spawn entities if the noise value matches.
-    /// </summary>
-    [DataField]
-    public FastNoiseLite? ReservedNoise;
-
-    /// <summary>
-    /// Noise threshold for <see cref="ReservedNoise"/>. Does nothing without it.
-    /// </summary>
-    [DataField]
-    public float Threshold = -1f;
-
-    [DataField]
-    public IDunGenDistance? DistanceConfig;
-
-    [DataField]
-    public Vector2 Size;
-
-    #endregion
 }
index 1b754d3778efb0ec9d585c3cb1213c5234fdf3d1..5525341eb9896595605668c7c37658df5bd5265c 100644 (file)
@@ -1,8 +1,10 @@
 using Content.Shared.EntityTable;
+using Content.Shared.Storage;
 using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Procedural.DungeonLayers;
 
+
 /// <summary>
 /// Spawns mobs inside of the dungeon randomly.
 /// </summary>
diff --git a/Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs b/Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs
deleted file mode 100644 (file)
index fbb174d..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-using Robust.Shared.Noise;
-
-namespace Content.Shared.Procedural.DungeonLayers;
-
-/// <summary>
-/// Sets tiles as rooved.
-/// </summary>
-public sealed partial class RoofDunGen : IDunGenLayer
-{
-    [DataField]
-    public float Threshold = -1f;
-
-    [DataField]
-    public FastNoiseLite? Noise;
-}
diff --git a/Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs b/Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs
deleted file mode 100644 (file)
index 616e643..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-using Content.Shared.Decals;
-using Content.Shared.Maps;
-using Robust.Shared.Noise;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.Procedural.DungeonLayers;
-
-public sealed partial class SampleDecalDunGen : IDunGenLayer
-{
-    /// <summary>
-    /// Reserve any tiles we update.
-    /// </summary>
-    [DataField]
-    public bool ReserveTiles = true;
-
-    [DataField(customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
-    public List<string> AllowedTiles { get; private set; } = new();
-
-    /// <summary>
-    /// Divide each tile up by this amount.
-    /// </summary>
-    [DataField]
-    public float Divisions = 1f;
-
-    [DataField]
-    public FastNoiseLite Noise { get; private set; } = new(0);
-
-    [DataField]
-    public float Threshold { get; private set; } = 0.8f;
-
-    [DataField] public bool Invert { get; private set; } = false;
-
-    [DataField(required: true)]
-    public List<ProtoId<DecalPrototype>> Decals = new();
-}
diff --git a/Content.Shared/Procedural/DungeonLayers/SampleEntityDunGen.cs b/Content.Shared/Procedural/DungeonLayers/SampleEntityDunGen.cs
deleted file mode 100644 (file)
index 2daf7e7..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-using Content.Shared.Maps;
-using Robust.Shared.Noise;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.Procedural.DungeonLayers;
-
-/// <summary>
-/// Samples noise to spawn the specified entity
-/// </summary>
-public sealed partial class SampleEntityDunGen : IDunGenLayer
-{
-    /// <summary>
-    /// Reserve any tiles we update.
-    /// </summary>
-    [DataField]
-    public bool ReserveTiles = true;
-
-    [DataField(customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
-    public List<string> AllowedTiles { get; private set; } = new();
-
-    [DataField] public FastNoiseLite Noise { get; private set; } = new(0);
-
-    [DataField]
-    public float Threshold { get; private set; } = 0.5f;
-
-    [DataField] public bool Invert { get; private set; } = false;
-
-    [DataField]
-    public List<EntProtoId> Entities = new();
-}
diff --git a/Content.Shared/Procedural/Loot/BiomeLoot.cs b/Content.Shared/Procedural/Loot/BiomeLoot.cs
deleted file mode 100644 (file)
index 1330043..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Procedural.Loot;
-
-/// <summary>
-/// Adds the prototype as a biome layer.
-/// </summary>
-public sealed partial class BiomeLoot : IDungeonLoot
-{
-    [DataField(required: true)]
-    public ProtoId<DungeonConfigPrototype> Proto;
-}
diff --git a/Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs b/Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs
new file mode 100644 (file)
index 0000000..2eda4b0
--- /dev/null
@@ -0,0 +1,15 @@
+using Content.Shared.Parallax.Biomes.Markers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
+
+namespace Content.Shared.Procedural.Loot;
+
+/// <summary>
+/// Adds a biome marker layer for dungeon loot.
+/// </summary>
+public sealed partial class BiomeMarkerLoot : IDungeonLoot
+{
+    [DataField("proto", required: true)]
+    public ProtoId<BiomeMarkerLayerPrototype> Prototype = new();
+}
diff --git a/Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs b/Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs
new file mode 100644 (file)
index 0000000..e4968b6
--- /dev/null
@@ -0,0 +1,14 @@
+using Content.Shared.Parallax.Biomes;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Shared.Procedural.Loot;
+
+/// <summary>
+/// Adds a biome template layer for dungeon loot.
+/// </summary>
+public sealed partial class BiomeTemplateLoot : IDungeonLoot
+{
+    [DataField("proto", required: true)]
+    public ProtoId<BiomeTemplatePrototype> Prototype = string.Empty;
+}
diff --git a/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs
new file mode 100644 (file)
index 0000000..e21e582
--- /dev/null
@@ -0,0 +1,21 @@
+using Content.Shared.Maps;
+using Content.Shared.Parallax.Biomes;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Procedural.PostGeneration;
+
+/// <summary>
+/// Generates a biome on top of valid tiles, then removes the biome when done.
+/// Only works if no existing biome is present.
+/// </summary>
+public sealed partial class BiomeDunGen : IDunGenLayer
+{
+    [DataField(required: true)]
+    public ProtoId<BiomeTemplatePrototype> BiomeTemplate;
+
+    /// <summary>
+    /// creates a biome only on the specified tiles
+    /// </summary>
+    [DataField]
+    public HashSet<ProtoId<ContentTileDefinition>>? TileMask;
+}
diff --git a/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs
new file mode 100644 (file)
index 0000000..af5d7c5
--- /dev/null
@@ -0,0 +1,19 @@
+using Content.Shared.Random;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Procedural.PostGeneration;
+
+/// <summary>
+/// Spawns the specified marker layer on top of the dungeon rooms.
+/// </summary>
+public sealed partial class BiomeMarkerLayerDunGen : IDunGenLayer
+{
+    /// <summary>
+    /// How many times to spawn marker layers; can duplicate.
+    /// </summary>
+    [DataField]
+    public int Count = 6;
+
+    [DataField(required: true)]
+    public ProtoId<WeightedRandomPrototype> MarkerTemplate;
+}
similarity index 93%
rename from Content.Shared/Procedural/DungeonLayers/EntranceFlankDunGen.cs
rename to Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs
index cd6cf169fc551c7b4743fe32b9e84d07b2beea67..f9be6caf6ae3a1938c593bb660b042c5da72bd39 100644 (file)
@@ -1,6 +1,5 @@
 using Content.Shared.EntityTable;
 using Content.Shared.Maps;
-using Content.Shared.Storage;
 using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Procedural.PostGeneration;
similarity index 94%
rename from Content.Shared/Procedural/DungeonLayers/ExternalWindowDunGen.cs
rename to Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs
index 30b8302263d91ea6090e263561a331a60665bc06..fc992ea7b8928ece434b9d26d2cf4a6db2696bee 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.EntityTable;
 using Content.Shared.Maps;
+using Content.Shared.Storage;
 using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Procedural.PostGeneration;
similarity index 93%
rename from Content.Shared/Procedural/DungeonLayers/RoomEntranceDunGen.cs
rename to Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs
index f0fea57588f69a959c1a61fe0b887452064e31ba..1436f7473d9718a7cc4efc535da32e92cc17fc8f 100644 (file)
@@ -1,5 +1,6 @@
 using Content.Shared.EntityTable;
 using Content.Shared.Maps;
+using Content.Shared.Storage;
 using Robust.Shared.Prototypes;
 
 namespace Content.Shared.Procedural.PostGeneration;
similarity index 83%
rename from Content.Shared/Procedural/DungeonLayers/SplineDungeonConnectorDunGen.cs
rename to Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs
index 4a4bc0b36ae0bccb5a7641fb213b66e07aa0b2a7..d2f5a2126a74dcb214d4fe41c7d3421a01ddcda4 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Shared.Maps;
 using Robust.Shared.Prototypes;
 
-namespace Content.Shared.Procedural.DungeonLayers;
+namespace Content.Shared.Procedural.PostGeneration;
 
 /// <summary>
 /// Connects dungeons via points that get subdivided.
@@ -18,11 +18,11 @@ public sealed partial class SplineDungeonConnectorDunGen : IDunGenLayer
     /// Will divide the distance between the start and end points so that no subdivision is more than these metres away.
     /// </summary>
     [DataField]
-    public int DivisionDistance = 20;
+    public int DivisionDistance = 10;
 
     /// <summary>
     /// How much each subdivision can vary from the middle.
     /// </summary>
     [DataField]
-    public float VarianceMax = 0.15f;
+    public float VarianceMax = 0.35f;
 }
index 10106cd66653a2b1d75e665657ef220e14d632aa..e84223ed1feb364df6cad06810ff185b83cdbc2d 100644 (file)
@@ -1,4 +1,6 @@
+using Content.Shared.Parallax.Biomes;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Salvage.Expeditions.Modifiers;
 
@@ -24,6 +26,6 @@ public sealed partial class SalvageBiomeModPrototype : IPrototype, ISalvageMod
     [DataField("weather")]
     public bool Weather = true;
 
-    [DataField("biome", required: true)]
-    public EntProtoId? BiomePrototype;
+    [DataField("biome", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
+    public string? BiomePrototype;
 }
index 7c2a88a696086b05c4601a8bb3fb5c300182650f..d24ec7d72e68e2cbb8b1cf33e54e89e5af39b604 100644 (file)
@@ -1,2 +1,6 @@
+cmd-biome_clear-desc = Clears a biome entirely
+cmd-biome_clear-help = biome_clear <biomecomponent>
 cmd-biome_addlayer-desc = Adds another biome layer
 cmd-biome_addlayer-help = biome_addlayer <mapid> <biometemplate> [seed offset]
+cmd-biome_addmarkerlayer-desc = Adds another biome marker layer
+cmd-biome_addmarkerlayer-help = biome_addmarkerlayer <mapid> <biomemarkerlayer>
index 357bc9f98c6244f2e36efe2ee492eab105de0798..c488df231b23eaba080d30df508136800c193922 100644 (file)
     drawdepth: BelowFloor
     layers:
       - state: shoreline_water
-  - type: BiomeForceUnload
-  #- type: ContainerContainer
-  #  containers:
-  #    solution@pool: !type:ContainerSlot
   - type: SolutionContainerManager
     solutions:
       pool:
-        maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable.
+        maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable. 
         reagents:
         - ReagentId: Water
           Quantity: 9999999
index f838104f3c3d5b63def98959a6e4a39769845144..a77a6b287b199edd50ea0e44d16637995d784635 100644 (file)
@@ -18,9 +18,8 @@
           lacunarity: 2
 
   # Generate biome
-  - !type:PrototypeDunGen
-    proto: Asteroid
-    inheritDungeons: All
+  - !type:BiomeDunGen
+    biomeTemplate: Asteroid
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
@@ -48,9 +47,8 @@
           lacunarity: 2
 
   # Generate biome
-  - !type:PrototypeDunGen
-    proto: Asteroid
-    inheritDungeons: All
+  - !type:BiomeDunGen
+    biomeTemplate: Asteroid
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
@@ -78,9 +76,8 @@
           cellularDistanceFunction: Euclidean
 
   # Generate biome
-  - !type:PrototypeDunGen
-    proto: Asteroid
-    inheritDungeons: All
+  - !type:BiomeDunGen
+    biomeTemplate: Asteroid
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
           lacunarity: 2
 
   # Generate biome
-  - !type:PrototypeDunGen
-    proto: Asteroid
-    inheritDungeons: All
+  - !type:BiomeDunGen
+    biomeTemplate: Asteroid
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
index a9bd823e6b0800e68ed2525ffb53922a6747b13c..11c1afdabe9fc42fbcf50c92d252724e32375967 100644 (file)
@@ -36,9 +36,8 @@
           gain: 0.5
 
     # Generate biome
-    - !type:PrototypeDunGen
-      proto: SpaceDebris
-      inheritDungeons: All
+    - !type:BiomeDunGen
+      biomeTemplate: SpaceDebris
 
 - type: dungeonConfig
   id: ChunkDebrisSmall
@@ -78,6 +77,5 @@
         gain: 0.5
 
   # Generate biome
-  - !type:PrototypeDunGen
-    proto: SpaceDebris
-    inheritDungeons: All
+  - !type:BiomeDunGen
+    biomeTemplate: SpaceDebris
index e181f892683902a08984b08df7c7d643276e0613..47aa47fce4950ca5aaac2264d1fb8ba0f3f1d2cd 100644 (file)
@@ -1,8 +1,8 @@
 # Asteroid
-- type: dungeonConfig
+- type: biomeTemplate
   id: SpaceDebris
   layers:
-  - !type:SampleEntityDunGen
+  - !type:BiomeEntityLayer
     threshold: 0.20
     noise:
       seed: 0
@@ -23,7 +23,7 @@
     - WallReinforced
     - WallSolid
     - WallSolid
-  - !type:SampleEntityDunGen
+  - !type:BiomeEntityLayer
     threshold: 0.5
     noise:
       seed: 0
@@ -41,7 +41,7 @@
     - Grille
     - Grille
     - GrilleBroken
-  - !type:SampleDecalDunGen
+  - !type:BiomeDecalLayer
     allowedTiles:
     - FloorSteel
     threshold: -0.5
@@ -56,7 +56,7 @@
     - DirtMedium
     - DirtMedium
     - DirtLight
-  - !type:SampleEntityDunGen
+  - !type:BiomeEntityLayer
     threshold: 0.45
     noise:
       seed: 1
@@ -71,7 +71,7 @@
     - FloorSteel
     entities:
     - SalvageSpawnerStructuresVarious
-  - !type:SampleEntityDunGen
+  - !type:BiomeEntityLayer
     allowedTiles:
     - FloorSteel
     - Plating
@@ -85,7 +85,7 @@
     - SalvageSpawnerScrapCommon
     - SalvageSpawnerScrapCommon
     - SalvageSpawnerScrapCommon75
-  - !type:SampleEntityDunGen
+  - !type:BiomeEntityLayer
     allowedTiles:
     - FloorSteel
     - Plating
@@ -97,7 +97,7 @@
     - SalvageSpawnerTreasure
     - SalvageSpawnerTreasure
     - SalvageSpawnerTreasureValuable
-  - !type:SampleEntityDunGen
+  - !type:BiomeEntityLayer
     allowedTiles:
     - FloorSteel
     - Plating
index ef28b314caa84dc057effc1722dbdc1433e7aa73..5c4fdeb178f23ed52feb5ad9367f4e141a4146c4 100644 (file)
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: Lizards
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobLizard
-      amount: !type:RangeNumberSelector
-        range: 3, 5
+  prototype: MobLizard
+  minGroupSize: 3
+  maxGroupSize: 5
 
-
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: WatchersLavaland
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobWatcherLavaland
-      amount: !type:RangeNumberSelector
-        range: 3, 3
+  prototype: MobWatcherLavaland
+  minGroupSize: 3
+  maxGroupSize: 3
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: WatchersIcewing
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobWatcherIcewing
-      amount: !type:RangeNumberSelector
-        range: 3, 3
+  prototype: MobWatcherIcewing
+  minGroupSize: 3
+  maxGroupSize: 3
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: WatchersMagmawing
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobWatcherMagmawing
-      amount: !type:RangeNumberSelector
-        range: 3, 3
+  prototype: MobWatcherMagmawing
+  minGroupSize: 3
+  maxGroupSize: 3
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: Cows
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobCow
-      amount: !type:RangeNumberSelector
-        range: 1, 2
+  prototype: MobCow
+  minGroupSize: 1
+  maxGroupSize: 2
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: Chickens
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobChicken
-      amount: !type:RangeNumberSelector
-        range: 1, 2
+  prototype: MobChicken
+  minGroupSize: 1
+  maxGroupSize: 2
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: Pigs
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobPig
-      amount: !type:RangeNumberSelector
-        range: 1, 2
+  prototype: MobPig
+  minGroupSize: 1
+  maxGroupSize: 2
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: Foxes
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobFox
-      amount: !type:RangeNumberSelector
-        range: 1, 1
+  prototype: MobFox
+  minGroupSize: 1
+  maxGroupSize: 1
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: Goats
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobGoat
-      amount: !type:RangeNumberSelector
-        range: 1, 1
+  prototype: MobGoat
+  minGroupSize: 1
+  maxGroupSize: 1
+
+# TODO: Needs to be more robust
+- type: biomeMarkerLayer
+  id: Xenos
+  prototype: MobXeno
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: Carps
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobCarp
-      amount: !type:RangeNumberSelector
-        range: 1, 2
+  prototype: MobCarpDungeon
 
-- type: dungeonConfig
-  id: Xenos
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:EntityTableDunGen
-    table: !type:EntSelector
-      id: MobXeno
-      amount: !type:RangeNumberSelector
-        range: 1, 2
 
+#- type: biomeMarkerLayer
+#  id: Experiment
+#  proto: DungeonMarkerExperiment
index 16b2c7e64f0a9b2cd9ba0ecb989c00b632a96d0d..ae033230b6f6bfdc99509026435913faf69e1459 100644 (file)
 # Low value
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreIron
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockTin
-    count: 30
-    minGroupSize: 10
-    maxGroupSize: 15
+  entityMask:
+    AsteroidRock: AsteroidRockTin
+    WallRock: WallRockTin
+    WallRockBasalt: WallRockBasaltTin
+    WallRockChromite: WallRockChromiteTin
+    WallRockSand: WallRockSandTin
+    WallRockSnow: WallRockSnowTin
+  maxCount: 30
+  minGroupSize: 10
+  maxGroupSize: 15
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreQuartz
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockQuartz
-    count: 30
-    minGroupSize: 10
-    maxGroupSize: 15
+  entityMask:
+    AsteroidRock: AsteroidRockQuartz
+    WallRock: WallRockQuartz
+    WallRockBasalt: WallRockBasaltQuartz
+    WallRockChromite: WallRockChromiteQuartz
+    WallRockSnow: WallRockSnowQuartz
+  maxCount: 30
+  minGroupSize: 10
+  maxGroupSize: 15
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreCoal
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockCoal
-    count: 30
-    minGroupSize: 8
-    maxGroupSize: 12
+  entityMask:
+    AsteroidRock: AsteroidRockCoal
+    WallRock: WallRockCoal
+    WallRockBasalt: WallRockBasaltCoal
+    WallRockChromite: WallRockChromiteCoal
+    WallRockSand: WallRockSandCoal
+    WallRockSnow: WallRockSnowCoal
+  maxCount: 30
+  minGroupSize: 8
+  maxGroupSize: 12
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreSalt
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockSalt
-    count: 30
-    minGroupSize: 8
-    maxGroupSize: 12
+  entityMask:
+    AsteroidRock: AsteroidRockSalt
+    WallRock: WallRockSalt
+    WallRockBasalt: WallRockBasaltSalt
+    WallRockChromite: WallRockChromiteSalt
+    WallRockSand: WallRockSandSalt
+    WallRockSnow: WallRockSnowSalt
+  maxCount: 30
+  minGroupSize: 8
+  maxGroupSize: 12
+  radius: 4
 
 # Medium value
 # Gold
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreGold
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockGold
-    count: 20
-    minGroupSize: 5
-    maxGroupSize: 10
+  entityMask:
+    AsteroidRock: AsteroidRockGold
+    WallRock: WallRockGold
+    WallRockBasalt: WallRockBasaltGold
+    WallRockChromite: WallRockChromiteGold
+    WallRockSand: WallRockSandGold
+    WallRockSnow: WallRockSnowGold
+  maxCount: 20
+  minGroupSize: 5
+  maxGroupSize: 10
+  radius: 4
 
 # Silver
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreSilver
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockSilver
-    count: 20
-    minGroupSize: 5
-    maxGroupSize: 10
+  entityMask:
+    AsteroidRock: AsteroidRockSilver
+    WallRock: WallRockSilver
+    WallRockBasalt: WallRockBasaltSilver
+    WallRockChromite: WallRockChromiteSilver
+    WallRockSand: WallRockSandSilver
+    WallRockSnow: WallRockSnowSilver
+  maxCount: 20
+  minGroupSize: 5
+  maxGroupSize: 10
+  radius: 4
 
 # High value
 # Plasma
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OrePlasma
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockPlasma
-    count: 12
-    minGroupSize: 4
-    maxGroupSize: 8
+  entityMask:
+    AsteroidRock: AsteroidRockPlasma
+    WallRock: WallRockPlasma
+    WallRockBasalt: WallRockBasaltPlasma
+    WallRockChromite: WallRockChromitePlasma
+    WallRockSand: WallRockSandPlasma
+    WallRockSnow: WallRockSnowPlasma
+  maxCount: 12
+  minGroupSize: 4
+  maxGroupSize: 8
+  radius: 4
 
 # Uranium
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreUranium
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockUranium
-    count: 15
-    minGroupSize: 4
-    maxGroupSize: 8
+  entityMask:
+    AsteroidRock: AsteroidRockUranium
+    WallRock: WallRockUranium
+    WallRockBasalt: WallRockBasaltUranium
+    WallRockChromite: WallRockChromiteUranium
+    WallRockSand: WallRockSandUranium
+    WallRockSnow: WallRockSnowUranium
+  maxCount: 15
+  minGroupSize: 4
+  maxGroupSize: 8
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreDiamond
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockDiamond
-    count: 6
-    minGroupSize: 1
-    maxGroupSize: 2
+  entityMask:
+    AsteroidRock: AsteroidRockDiamond
+    WallRock: WallRockDiamond
+    WallRockBasalt: WallRockBasaltDiamond
+    WallRockChromite: WallRockChromiteDiamond
+    WallRockSand: WallRockSandDiamond
+    WallRockSnow: WallRockSnowDiamond
+  maxCount: 6
+  minGroupSize: 1
+  maxGroupSize: 2
+  radius: 4
 
 # Artifact Fragment
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreArtifactFragment
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockArtifactFragment
-    count: 6
-    minGroupSize: 1
-    maxGroupSize: 2
+  entityMask:
+    AsteroidRock: AsteroidRockArtifactFragment
+    WallRock: WallRockArtifactFragment
+    WallRockBasalt: WallRockBasaltArtifactFragment
+    WallRockChromite: WallRockChromiteArtifactFragment
+    WallRockSand: WallRockSandArtifactFragment
+    WallRockSnow: WallRockSnowArtifactFragment
+  maxCount: 6
+  minGroupSize: 1
+  maxGroupSize: 2
+  radius: 4
index 9af10e3c2b5c748d19e5c0d3a9566d70bb32abfb..3b0e24244692fc27d0a6b9eeb4c8e9bf525ed156 100644 (file)
 # Low value
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreIronLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockTin
-    count: 20
-    minGroupSize: 4
-    maxGroupSize: 8
+  entityMask:
+    AsteroidRock: AsteroidRockTin
+    WallRock: WallRockTin
+    WallRockBasalt: WallRockBasaltTin
+    WallRockChromite: WallRockChromiteTin
+    WallRockSand: WallRockSandTin
+    WallRockSnow: WallRockSnowTin
+  maxCount: 20
+  minGroupSize: 4
+  maxGroupSize: 8
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreQuartzLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockQuartz
-    count: 20
-    minGroupSize: 4
-    maxGroupSize: 8
+  entityMask:
+    AsteroidRock: AsteroidRockQuartz
+    WallRock: WallRockQuartz
+    WallRockBasalt: WallRockBasaltQuartz
+    WallRockChromite: WallRockChromiteQuartz
+    WallRockSnow: WallRockSnowQuartz
+  maxCount: 20
+  minGroupSize: 4
+  maxGroupSize: 8
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreCoalLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockCoal
-    count: 20
-    minGroupSize: 4
-    maxGroupSize: 6
+  entityMask:
+    AsteroidRock: AsteroidRockCoal
+    WallRock: WallRockCoal
+    WallRockBasalt: WallRockBasaltCoal
+    WallRockChromite: WallRockChromiteCoal
+    WallRockSand: WallRockSandCoal
+    WallRockSnow: WallRockSnowCoal
+  maxCount: 20
+  minGroupSize: 4
+  maxGroupSize: 6
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreSaltLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockSalt
-    count: 15
-    minGroupSize: 4
-    maxGroupSize: 6
+  entityMask:
+    AsteroidRock: AsteroidRockSalt
+    WallRock: WallRockSalt
+    WallRockBasalt: WallRockBasaltSalt
+    WallRockChromite: WallRockChromiteSalt
+    WallRockSand: WallRockSandSalt
+    WallRockSnow: WallRockSnowSalt
+  maxCount: 15
+  minGroupSize: 4
+  maxGroupSize: 6
+  radius: 4
 
 # Medium value
 # Gold
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreGoldLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockGold
-    count: 12
-    minGroupSize: 2
-    maxGroupSize: 5
+  entityMask:
+    AsteroidRock: AsteroidRockGold
+    WallRock: WallRockGold
+    WallRockBasalt: WallRockBasaltGold
+    WallRockChromite: WallRockChromiteGold
+    WallRockSand: WallRockSandGold
+    WallRockSnow: WallRockSnowGold
+  maxCount: 10
+  minGroupSize: 2
+  maxGroupSize: 5
+  radius: 4
 
 # Silver
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreSilverLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockSilver
-    count: 12
-    minGroupSize: 2
-    maxGroupSize: 5
+  entityMask:
+    AsteroidRock: AsteroidRockSilver
+    WallRock: WallRockSilver
+    WallRockBasalt: WallRockBasaltSilver
+    WallRockChromite: WallRockChromiteSilver
+    WallRockSand: WallRockSandSilver
+    WallRockSnow: WallRockSnowSilver
+  maxCount: 10
+  minGroupSize: 2
+  maxGroupSize: 5
+  radius: 4
 
 # High value
 # Plasma
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OrePlasmaLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockPlasma
-    count: 6
-    minGroupSize: 2
-    maxGroupSize: 4
+  entityMask:
+    AsteroidRock: AsteroidRockPlasma
+    WallRock: WallRockPlasma
+    WallRockBasalt: WallRockBasaltPlasma
+    WallRockChromite: WallRockChromitePlasma
+    WallRockSand: WallRockSandPlasma
+    WallRockSnow: WallRockSnowPlasma
+  maxCount: 6
+  minGroupSize: 2
+  maxGroupSize: 4
+  radius: 4
 
 # Uranium
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreUraniumLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockUranium
-    count: 7
-    minGroupSize: 2
-    maxGroupSize: 4
+  entityMask:
+    AsteroidRock: AsteroidRockUranium
+    WallRock: WallRockUranium
+    WallRockBasalt: WallRockBasaltUranium
+    WallRockChromite: WallRockChromiteUranium
+    WallRockSand: WallRockSandUranium
+    WallRockSnow: WallRockSnowUranium
+  maxCount: 7
+  minGroupSize: 2
+  maxGroupSize: 4
+  radius: 4
 
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreDiamondLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockDiamond
-    count: 3
-    minGroupSize: 1
-    maxGroupSize: 2
+  entityMask:
+    AsteroidRock: AsteroidRockDiamond
+    WallRock: WallRockDiamond
+    WallRockBasalt: WallRockBasaltDiamond
+    WallRockChromite: WallRockChromiteDiamond
+    WallRockSand: WallRockSandDiamond
+    WallRockSnow: WallRockSnowDiamond
+  maxCount: 3
+  minGroupSize: 1
+  maxGroupSize: 2
+  radius: 4
 
 # Artifact Fragment
-- type: dungeonConfig
+- type: biomeMarkerLayer
   id: OreArtifactFragmentLow
-  layers:
-  - !type:ChunkDunGen
-    size: 128
-  - !type:OreDunGen
-    replacement: WallRock
-    entity: WallRockArtifactFragment
-    count: 3
-    minGroupSize: 1
-    maxGroupSize: 2
+  entityMask:
+    AsteroidRock: AsteroidRockArtifactFragment
+    WallRock: WallRockArtifactFragment
+    WallRockBasalt: WallRockBasaltArtifactFragment
+    WallRockChromite: WallRockChromiteArtifactFragment
+    WallRockSand: WallRockSandArtifactFragment
+    WallRockSnow: WallRockSnowArtifactFragment
+  maxCount: 3
+  minGroupSize: 1
+  maxGroupSize: 2
+  radius: 4
index 747660dc8fb258f3821cce394741c8efa41a9261..d630ebad489fbd6a6986d12f7f782f812e83c8d3 100644 (file)
-# Desert
-- type: entity
-  id: BiomeLowDesert
-  categories: [ HideSpawnMenu ]
-  components:
-  - type: Biome
-    layers:
-      terrain:
-        dungeon: LowDesertTerrain
-
-- type: dungeonConfig
-  id: LowDesertTerrain
-  layers:
-  - !type:ChunkDunGen
-  - !type:PrototypeDunGen
-    proto: LowDesertTiles
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: LowDesertEntities
-    inheritDungeons: All
-
-- type: dungeonConfig
-  id: LowDesertTiles
-  returnReserved: false
+# Contains several biomes
+- type: biomeTemplate
+  id: Continental
   layers:
-  - !type:SampleTileDunGen
-    threshold: -1
-    tile: FloorAsteroidSand
-
-- type: dungeonConfig
-  id: LowDesertEntities
-  reserveTiles: true
-  layers:
-  - !type:SampleEntityDunGen
-    threshold: 0.95
-    noise:
-      seed: 0
-      frequency: 2
-      noiseType: OpenSimplex2
-    allowedTiles:
-    - FloorAsteroidSand
-    entities:
-    - FloraRockSolid
-  # Large rock areas
-  - !type:SampleEntityDunGen
-    threshold: -0.20
-    noise:
-      seed: 0
-      frequency: 0.04
-      noiseType: Cellular
-      fractalType: FBm
-      octaves: 5
-      lacunarity: 2
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-    allowedTiles:
-    - FloorAsteroidSand
-    entities:
-    - WallRockSand
-
-
-- type: entity
-  id: BiomeGrasslands
-  categories: [ HideSpawnMenu ]
-  components:
-  - type: Biome
-    layers:
-      terrain:
-        dungeon: GrasslandsTerrain
-
-- type: dungeonConfig
-  id: GrasslandsTerrain
-  layers:
-  - !type:ChunkDunGen
-  - !type:PrototypeDunGen
-    proto: GrasslandsTiles
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: GrasslandsEntities
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: GrasslandsDecals
-    inheritDungeons: All
-
-- type: dungeonConfig
-  id: GrasslandsTiles
-  returnReserved: false
-  layers:
-  # Water sand
-  - !type:SampleTileDunGen
-    tile: FloorPlanetDirt
-    threshold: 0.95
-    noise:
-      seed: 3
-      noiseType: OpenSimplex2
-      frequency: 0.003
-      lacunarity: 1.50
-      fractalType: Ridged
-      octaves: 1
-  # Rock formation sand
-  - !type:SampleTileDunGen
-    tile: FloorPlanetDirt
-    threshold: -0.30
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 0.05
-      lacunarity: 2
-      fractalType: FBm
-      octaves: 5
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-  - !type:SampleTileDunGen
-    threshold: -0.80
-    tile: FloorPlanetGrass
-    noise:
-      seed: 0
-      noiseType: OpenSimplex2
-      lacunarity: 1.50
-      frequency: 0.02
-      fractalType: None
-      octaves: 1
-  # Fill remainder with dirt.
-  - !type:SampleTileDunGen
-    threshold: -1.0
-    tile: FloorPlanetDirt
+    - !type:BiomeMetaLayer
+      template: Lava
+    - !type:BiomeMetaLayer
+      template: Caves
+      threshold: -0.5
+      noise:
+        frequency: 0.001
+        noiseType: OpenSimplex2
+        fractalType: FBm
+        octaves: 2
+        lacunarity: 2
+    - !type:BiomeMetaLayer
+      template: Grasslands
+      threshold: 0
+      noise:
+        frequency: 0.001
+        noiseType: OpenSimplex2
+        fractalType: FBm
+        octaves: 2
+        lacunarity: 2
+    - !type:BiomeMetaLayer
+      template: Snow
+      threshold: 0.5
+      noise:
+        frequency: 0.001
+        noiseType: OpenSimplex2
+        fractalType: FBm
+        octaves: 2
+        lacunarity: 2
 
-- type: dungeonConfig
-  id: GrasslandsEntities
-  reserveTiles: true
+# Desert
+# TODO: Water in desert
+- type: biomeTemplate
+  id: LowDesert
   layers:
-  # Water
-  - !type:SampleEntityDunGen
-    allowedTiles:
-    - FloorPlanetGrass
-    - FloorPlanetDirt
-    threshold: 0.95
-    noise:
-      seed: 3
-      noiseType: OpenSimplex2
-      frequency: 0.003
-      lacunarity: 1.50
-      fractalType: Ridged
-      octaves: 1
-    entities:
-    - FloorWaterEntity
-  # Rock formations
-  - !type:SampleEntityDunGen
-    allowedTiles:
-    - FloorPlanetGrass
-    - FloorPlanetDirt
-    threshold: -0.30
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 0.05
-      lacunarity: 2
-      fractalType: FBm
-      octaves: 5
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-    entities:
-    - WallRock
-  - !type:SampleEntityDunGen
-    threshold: 0.5
-    noise:
-      seed: 0
-      noiseType: OpenSimplex2
-      fractalType: FBm
-      frequency: 2
-    allowedTiles:
-    - FloorPlanetGrass
-    entities:
-    - FloraTree
-    - FloraTreeLarge
+    - !type:BiomeEntityLayer
+      threshold: 0.95
+      noise:
+        seed: 0
+        frequency: 2
+        noiseType: OpenSimplex2
+      allowedTiles:
+        - FloorAsteroidSand
+      entities:
+        - FloraRockSolid
+    # Large rock areas
+    - !type:BiomeEntityLayer
+      threshold: -0.20
+      noise:
+        seed: 0
+        frequency: 0.04
+        noiseType: Cellular
+        fractalType: FBm
+        octaves: 5
+        lacunarity: 2
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+      allowedTiles:
+        - FloorAsteroidSand
+      entities:
+        - WallRockSand
+    - !type:BiomeDummyLayer
+      id: Loot
+    # Fill layer
+    - !type:BiomeTileLayer
+      threshold: -1
+      tile: FloorAsteroidSand
 
-- type: dungeonConfig
-  id: GrasslandsDecals
-  returnReserved: false
+# Grass
+- type: biomeTemplate
+  id: Grasslands
   layers:
-  # Dense vegetation
-  - !type:SampleDecalDunGen
-    allowedTiles:
-    - FloorPlanetGrass
-    divisions: 1
-    threshold: -0.35
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 0.2
-      fractalType: FBm
-      octaves: 5
-      lacunarity: 2
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-    decals:
-    - BushAOne
-    - BushATwo
-    - BushAThree
-    - BushCOne
-    - BushCTwo
-    - BushCThree
-  - !type:SampleDecalDunGen
-    allowedTiles:
-    - FloorPlanetGrass
-    noise:
-      seed: 0
-      noiseType: OpenSimplex2
-      frequency: 1
-    divisions: 1
-    threshold: 0.8
-    decals:
-    - FlowersBROne
-    - FlowersBRTwo
-    - FlowersBRThree
-  # Sparse vegetation
-  - !type:SampleDecalDunGen
-    allowedTiles:
-    - FloorPlanetGrass
-    divisions: 2
-    threshold: -0.50
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 1
-    decals:
-    - BushDOne
-    - BushDTwo
-    - BushDThree
+    # Sparse vegetation
+    - !type:BiomeDecalLayer
+      allowedTiles:
+        - FloorPlanetGrass
+      divisions: 2
+      threshold: -0.50
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 1
+      decals:
+        - BushDOne
+        - BushDTwo
+        - BushDThree
+    - !type:BiomeDecalLayer
+      allowedTiles:
+        - FloorPlanetGrass
+      noise:
+        seed: 0
+        noiseType: OpenSimplex2
+        frequency: 1
+      divisions: 1
+      threshold: 0.8
+      decals:
+        - FlowersBROne
+        - FlowersBRTwo
+        - FlowersBRThree
+    # Dense vegetation
+    - !type:BiomeDecalLayer
+      allowedTiles:
+      - FloorPlanetGrass
+      divisions: 1
+      threshold: -0.35
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 0.2
+        fractalType: FBm
+        octaves: 5
+        lacunarity: 2
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+      decals:
+        - BushAOne
+        - BushATwo
+        - BushAThree
+        - BushCOne
+        - BushCTwo
+        - BushCThree
+    - !type:BiomeEntityLayer
+      threshold: 0.5
+      noise:
+        seed: 0
+        noiseType: OpenSimplex2
+        fractalType: FBm
+        frequency: 2
+      allowedTiles:
+        - FloorPlanetGrass
+      entities:
+        - FloraTree
+        - FloraTreeLarge
+    # Rock formations
+    - !type:BiomeEntityLayer
+      allowedTiles:
+        - FloorPlanetGrass
+        - FloorPlanetDirt
+      threshold: -0.30
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 0.05
+        lacunarity: 2
+        fractalType: FBm
+        octaves: 5
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+      entities:
+        - WallRock
+    - !type:BiomeDummyLayer
+      id: Loot
+    # Water
+    - !type:BiomeEntityLayer
+      allowedTiles:
+        - FloorPlanetGrass
+        - FloorPlanetDirt
+      threshold: 0.95
+      noise:
+        seed: 3
+        noiseType: OpenSimplex2
+        frequency: 0.003
+        lacunarity: 1.50
+        fractalType: Ridged
+        octaves: 1
+      entities:
+        - FloorWaterEntity
+    # Fill remainder with dirt.
+    - !type:BiomeTileLayer
+      threshold: -1.0
+      tile: FloorPlanetDirt
+    - !type:BiomeTileLayer
+      threshold: -0.90
+      tile: FloorPlanetGrass
+      noise:
+        seed: 0
+        frequency: 0.02
+        fractalType: None
+    # Water sand
+    - !type:BiomeTileLayer
+      tile: FloorPlanetDirt
+      threshold: 0.95
+      noise:
+        seed: 3
+        noiseType: OpenSimplex2
+        frequency: 0.003
+        lacunarity: 1.50
+        fractalType: Ridged
+        octaves: 1
+    # Rock formation sand
+    - !type:BiomeTileLayer
+      tile: FloorPlanetDirt
+      threshold: -0.30
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 0.05
+        lacunarity: 2
+        fractalType: FBm
+        octaves: 5
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
 
 # Lava
-- type: entity
-  id: BiomeLava
-  categories: [ HideSpawnMenu ]
-  components:
-  - type: Biome
-    layers:
-      terrain:
-        dungeon: LavaTerrain
-
-- type: dungeonConfig
-  id: LavaTerrain
-  layers:
-  - !type:ChunkDunGen
-  - !type:PrototypeDunGen
-    proto: LavaTiles
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: LavaEntities
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: LavaDecals
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: LavaBasalt
-    inheritDungeons: All
-
-- type: dungeonConfig
-  id: LavaTiles
-  returnReserved: false
-  layers:
-  # Fill basalt
-  - !type:SampleTileDunGen
-    threshold: -1
-    tile: FloorBasalt
-
-- type: dungeonConfig
-  id: LavaEntities
-  reserveTiles: true
+- type: biomeTemplate
+  id: Lava
   layers:
-  - !type:SampleEntityDunGen
-    threshold: 0.2
-    noise:
-      seed: 0
-      frequency: 0.02
-      fractalType: FBm
-      octaves: 5
-      lacunarity: 2
-      gain: 0.4
-    allowedTiles:
-    - FloorBasalt
-    entities:
-    - FloorLavaEntity
-  # Rock formations
-  - !type:SampleEntityDunGen
-    allowedTiles:
-      - FloorBasalt
-    threshold: -0.30
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 0.05
-      lacunarity: 2
-      fractalType: FBm
-      octaves: 5
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-    entities:
-      - WallRockBasalt
-  - !type:SampleEntityDunGen
-    threshold: 0.95
-    noise:
-      seed: 0
-      noiseType: OpenSimplex2
-      frequency: 1
-    allowedTiles:
-    - FloorBasalt
-    entities:
-    - FloraRockSolid
-
-- type: dungeonConfig
-  id: LavaDecals
-  returnReserved: false
-  layers:
-  - !type:SampleDecalDunGen
-    allowedTiles:
-    - FloorBasalt
-    threshold: 0.9
-    divisions: 1
-    noise:
-      seed: 1
-      frequency: 1
-    decals:
-    - Basalt1
-    - Basalt2
-    - Basalt3
-    - Basalt4
-    - Basalt5
-    - Basalt6
-    - Basalt7
-    - Basalt8
-    - Basalt9
-
-- type: dungeonConfig
-  id: LavaBasalt
-  returnReserved: false
-  layers:
-  - !type:SampleEntityDunGen
-    threshold: 0.9
-    noise:
-      frequency: 1
-      seed: 2
-    allowedTiles:
-    - FloorBasalt
-    entities:
-    - BasaltOne
-    - BasaltTwo
-    - BasaltThree
-    - BasaltFour
-    - BasaltFive
+    - !type:BiomeEntityLayer
+      threshold: 0.9
+      noise:
+        frequency: 1
+        seed: 2
+      allowedTiles:
+        - FloorBasalt
+      entities:
+        - BasaltOne
+        - BasaltTwo
+        - BasaltThree
+        - BasaltFour
+        - BasaltFive
+    - !type:BiomeDecalLayer
+      allowedTiles:
+        - FloorBasalt
+      threshold: 0.9
+      divisions: 1
+      noise:
+        seed: 1
+        frequency: 1
+      decals:
+        - Basalt1
+        - Basalt2
+        - Basalt3
+        - Basalt4
+        - Basalt5
+        - Basalt6
+        - Basalt7
+        - Basalt8
+        - Basalt9
+    - !type:BiomeEntityLayer
+      threshold: 0.95
+      noise:
+        seed: 0
+        noiseType: OpenSimplex2
+        frequency: 1
+      allowedTiles:
+        - FloorBasalt
+      entities:
+        - FloraRockSolid
+    - !type:BiomeEntityLayer
+      threshold: 0.2
+      noise:
+        seed: 0
+        frequency: 0.02
+        fractalType: FBm
+        octaves: 5
+        lacunarity: 2
+        gain: 0.4
+      allowedTiles:
+        - FloorBasalt
+      entities:
+        - FloorLavaEntity
+    # Rock formations
+    - !type:BiomeEntityLayer
+      allowedTiles:
+        - FloorBasalt
+      threshold: -0.30
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 0.05
+        lacunarity: 2
+        fractalType: FBm
+        octaves: 5
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+      entities:
+        - WallRockBasalt
+    - !type:BiomeDummyLayer
+      id: Loot
+    # Fill basalt
+    - !type:BiomeTileLayer
+      threshold: -1
+      tile: FloorBasalt
 
 # Snow
-- type: entity
-  id: BiomeSnow
-  categories: [ HideSpawnMenu ]
-  components:
-  - type: Biome
-    layers:
-      terrain:
-        dungeon: SnowTerrain
-
-- type: dungeonConfig
-  id: SnowTerrain
-  layers:
-  - !type:ChunkDunGen
-  - !type:PrototypeDunGen
-    proto: SnowTiles
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: SnowEntities
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: SnowDecals
-    inheritDungeons: All
-
-- type: dungeonConfig
-  id: SnowTiles
-  returnReserved: false
+- type: biomeTemplate
+  id: Snow # Similar to Grasslands... but snow
   layers:
-  - !type:SampleTileDunGen
-    threshold: -0.7
-    tile: FloorSnow
-    noise:
-      seed: 0
-      frequency: 0.02
-      fractalType: None
-  - !type:SampleTileDunGen
-    tile: FloorIce
-    threshold: -0.9
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 0.03
-      lacunarity: 2
-      fractalType: FBm
-      octaves: 5
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-
-
-- type: dungeonConfig
-  id: SnowEntities
-  reserveTiles: true
-  layers:
-  # Liquid plasma rivers. Ice moon baby
-  - !type:SampleEntityDunGen
-    allowedTiles:
-    - FloorSnow
-    - FloorIce
-    threshold: 0.95
-    noise:
-      seed: 3
-      noiseType: OpenSimplex2
-      frequency: 0.003
-      lacunarity: 1.50
-      fractalType: Ridged
-      octaves: 1
-    entities:
-    - FloorLiquidPlasmaEntity
-  # Rock formations
-  - !type:SampleEntityDunGen
-    allowedTiles:
-    - FloorSnow
-    threshold: -0.30
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 0.05
-      lacunarity: 2
-      fractalType: FBm
-      octaves: 5
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-    entities:
-    - WallRockSnow
-  - !type:SampleEntityDunGen
-    threshold: 0.5
-    noise:
-      seed: 0
-      noiseType: OpenSimplex2
-      fractalType: FBm
-      frequency: 2
-    allowedTiles:
-    - FloorSnow
-    entities:
-    - FloraTreeSnow
-
-- type: dungeonConfig
-  id: SnowDecals
-  returnReserved: false
-  layers:
-  # Sparse vegetation
-  - !type:SampleDecalDunGen
-    allowedTiles:
-    - FloorSnow
-    divisions: 2
-    threshold: -0.50
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 1
-    decals:
-    - grasssnowa1
-    - grasssnowa2
-    - grasssnowa3
-    - grasssnowb1
-    - grasssnowb2
-    - grasssnowb3
-    - grasssnowc1
-    - grasssnowc2
-    - grasssnowc3
-  # Dense, bland grass
-  - !type:SampleDecalDunGen
-    allowedTiles:
-    - FloorSnow
-    divisions: 1
-    threshold: -0.35
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 0.2
-      fractalType: FBm
-      octaves: 5
-      lacunarity: 2
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-    decals:
-    - grasssnow
-    - grasssnow01
-    - grasssnow02
-    - grasssnow03
-    - grasssnow04
-    - grasssnow05
-    - grasssnow06
-    - grasssnow07
-    - grasssnow08
-    - grasssnow09
-    - grasssnow10
-    - grasssnow11
-    - grasssnow12
-    - grasssnow13
-  # Little bit of coloured grass
-  - !type:SampleDecalDunGen
-    allowedTiles:
-    - FloorSnow
-    divisions: 1
-    threshold: -0.0
-    noise:
-      seed: 0
-      noiseType: Cellular
-      frequency: 1
-      fractalType: None
-      cellularDistanceFunction: Euclidean
-      cellularReturnType: Distance2
-    decals:
-    - bushsnowa1
-    - bushsnowa2
-    - bushsnowa3
-    - bushsnowb3
-    - bushsnowb2
-    - bushsnowb3
+    # Sparse vegetation
+    - !type:BiomeDecalLayer
+      allowedTiles:
+        - FloorSnow
+      divisions: 2
+      threshold: -0.50
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 1
+      decals:
+        - grasssnowa1
+        - grasssnowa2
+        - grasssnowa3
+        - grasssnowb1
+        - grasssnowb2
+        - grasssnowb3
+        - grasssnowc1
+        - grasssnowc2
+        - grasssnowc3
+    # Dense, bland grass
+    - !type:BiomeDecalLayer
+      allowedTiles:
+        - FloorSnow
+      divisions: 1
+      threshold: -0.35
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 0.2
+        fractalType: FBm
+        octaves: 5
+        lacunarity: 2
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+      decals:
+        - grasssnow
+        - grasssnow01
+        - grasssnow02
+        - grasssnow03
+        - grasssnow04
+        - grasssnow05
+        - grasssnow06
+        - grasssnow07
+        - grasssnow08
+        - grasssnow09
+        - grasssnow10
+        - grasssnow11
+        - grasssnow12
+        - grasssnow13
+    # Little bit of coloured grass
+    - !type:BiomeDecalLayer
+      allowedTiles:
+        - FloorSnow
+      divisions: 1
+      threshold: -0.0
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 1
+        fractalType: None
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+      decals:
+        - bushsnowa1
+        - bushsnowa2
+        - bushsnowa3
+        - bushsnowb3
+        - bushsnowb2
+        - bushsnowb3
+    - !type:BiomeEntityLayer
+      threshold: 0.5
+      noise:
+        seed: 0
+        noiseType: OpenSimplex2
+        fractalType: FBm
+        frequency: 2
+      allowedTiles:
+        - FloorSnow
+      entities:
+        - FloraTreeSnow
+    # Rock formations
+    - !type:BiomeEntityLayer
+      allowedTiles:
+        - FloorSnow
+      threshold: -0.30
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 0.05
+        lacunarity: 2
+        fractalType: FBm
+        octaves: 5
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+      entities:
+        - WallRockSnow
+    # Ice tiles
+    - !type:BiomeTileLayer
+      tile: FloorIce
+      threshold: -0.9
+      noise:
+        seed: 0
+        noiseType: Cellular
+        frequency: 0.03
+        lacunarity: 2
+        fractalType: FBm
+        octaves: 5
+        cellularDistanceFunction: Euclidean
+        cellularReturnType: Distance2
+    # Liquid plasma rivers. Ice moon baby
+    - !type:BiomeEntityLayer
+      allowedTiles:
+      - FloorSnow
+      - FloorIce
+      threshold: 0.95
+      noise:
+        seed: 3
+        noiseType: OpenSimplex2
+        frequency: 0.003
+        lacunarity: 1.50
+        fractalType: Ridged
+        octaves: 1
+      entities:
+      - FloorLiquidPlasmaEntity
+    - !type:BiomeDummyLayer
+      id: Loot
+    - !type:BiomeTileLayer
+      threshold: -0.7
+      tile: FloorSnow
+      noise:
+        seed: 0
+        frequency: 0.02
+        fractalType: None
 
 # Shadow -> Derived from lava
-- type: entity
-  id: BiomeShadow
-  categories: [ HideSpawnMenu ]
-  components:
-  - type: Biome
-    layers:
-      terrain:
-        dungeon: ShadowTerrain
-
-- type: dungeonConfig
-  id: ShadowTerrain
+- type: biomeTemplate
+  id: Shadow
   layers:
-  - !type:ChunkDunGen
-  - !type:PrototypeDunGen
-    proto: ShadowTiles
-    inheritDungeons: All
-  - !type:PrototypeDunGen
-    proto: ShadowEntities
-    inheritDungeons: All
-
-- type: dungeonConfig
-  id: ShadowEntities
-  reserveTiles: true
-  layers:
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: 0.70
       noise:
         frequency: 1
         - ShadowBasaltThree
         - ShadowBasaltFour
         - ShadowBasaltFive
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: 0.97
       noise:
         frequency: 1
         - FloorChromite
       entities:
         - CrystalPink
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: 0.97
       noise:
         seed: 1
       entities:
         - ShadowTree
     # Rock formations
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: -0.2
       invert: true
       noise:
       entities:
         - WallRockChromite
     # chasm time
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       allowedTiles:
       - FloorChromite
       threshold: 0.2
         gain: 0.4
       entities:
       - FloorChromiteChasm
-
-- type: dungeonConfig
-  id: ShadowTiles
-  returnReserved: false
-  layers:
-  # Fill chromite
-  - !type:SampleTileDunGen
-    threshold: -1
-    tile: FloorChromite
+    - !type:BiomeDummyLayer
+      id: Loot
+    # Fill chromite
+    - !type:BiomeTileLayer
+      threshold: -1
+      tile: FloorChromite
 
 # Caves
-- type: entity
-  id: BiomeCaves
-  categories: [ HideSpawnMenu ]
-  components:
-  - type: Biome
-    layers:
-      terrain:
-        dungeon: CavesTerrain
-
-- type: dungeonConfig
-  id: CavesTerrain
-  layers:
-  - !type:ChunkDunGen
-  - !type:PrototypeDunGen
-    proto: CavesTiles
-    inheritDungeons: All
-  - !type:RoofDunGen
-  - !type:PrototypeDunGen
-    proto: CavesEntities
-    inheritDungeons: All
-
-- type: dungeonConfig
-  id: CavesEntities
-  reserveTiles: true
+- type: biomeTemplate
+  id: Caves
   layers:
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: 0.85
       noise:
         seed: 2
         - CrystalBlue
         - CrystalYellow
         - CrystalCyan
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: 0.95
       noise:
         seed: 1
       - FloorAsteroidSand
       entities:
       - FloraStalagmite
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: -0.5
       invert: true
       noise:
         - FloorAsteroidSand
       entities:
         - WallRock
-
-- type: dungeonConfig
-  id: CavesTiles
-  returnReserved: false
-  layers:
-  - !type:SampleTileDunGen
-    threshold: -1.0
-    tile: FloorAsteroidSand
+    - !type:BiomeDummyLayer
+      id: Loot
+    - !type:BiomeTileLayer
+      threshold: -1.0
+      tile: FloorAsteroidSand
 
 # Asteroid
-- type: dungeonConfig
+- type: biomeTemplate
   id: Asteroid
   layers:
-    - !type:SampleTileDunGen
-      threshold: -1.0
-      tile: FloorAsteroidSand
-      reserveTiles: false
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: 0.85
       noise:
         seed: 2
         - CrystalBlue
         - CrystalYellow
         - CrystalCyan
-    - !type:SampleEntityDunGen
+    - !type:BiomeEntityLayer
       threshold: 0.95
       noise:
         seed: 1
         - FloorAsteroidSand
       entities:
         - FloraStalagmite
-    - !type:SampleEntityDunGen
-      reserveTiles: false
+    - !type:BiomeEntityLayer
       threshold: -0.6
       invert: true
       noise:
         - FloorAsteroidSand
       entities:
         - AsteroidRock
+    - !type:BiomeTileLayer
+      threshold: -1.0
+      tile: FloorAsteroidSand
index 18d50ec6cd6a3dc90a859dd3ffee1d13de0ef79b..5da95929952f3ae246906e2a90393b0e349785c1 100644 (file)
@@ -79,9 +79,6 @@
   id: Haunted
   layers:
   - !type:PrefabDunGen
-    roomWhitelist:
-      tags:
-      - Haunted
     presets:
     - Bucket
     - Wow
index 289fa5fd573e0f4eb7c0a6217ffa0d4aead98ee5..cf236019783262a25fd73b83cee45691802f0e0b 100644 (file)
 - type: salvageLoot
   id: SalvageLoot
   loots:
-  - !type:RandomSpawnsLoot
-    entries:
-      - proto: AdvMopItem
-        prob: 0.5
-      - proto: AmmoTechFabCircuitboard
-        cost: 2
-      - proto: AutolatheMachineCircuitboard
-        cost: 2
-      - proto: BiomassReclaimerMachineCircuitboard
-        cost: 2
-      - proto: BluespaceBeaker
-        cost: 2
-      - proto: CyborgEndoskeleton
-        cost: 3
-        prob: 0.5
-      - proto: ChemDispenserMachineCircuitboard
-        cost: 2
-      - proto: CircuitImprinter
-        cost: 2
-      - proto: CloningConsoleComputerCircuitboard
-        cost: 2
-      - proto: CloningPodMachineCircuitboard
-        cost: 2
-      - proto: ChemistryBottleCognizine
-      - proto: FoodBoxDonkpocketCarp
-        prob: 0.5
-      - proto: CrateSalvageEquipment
-        cost: 3
-        prob: 0.5
-      - proto: GasRecycler
-        cost: 2
-      - proto: GeneratorRTG
-        cost: 5
-      - proto: GravityGeneratorMini
-        cost: 2
-      - proto: GyroscopeUnanchored
-        cost: 2
-        prob: 0.1
-      - proto: MedicalScannerMachineCircuitboard
-        cost: 2
-      - proto: NuclearBombKeg
-        cost: 5
-      - proto: ChemistryBottleOmnizine
-        prob: 0.5
-      - proto: PortableGeneratorPacman
-        cost: 2
-      - proto: PortableGeneratorSuperPacman
-        cost: 3
-      - proto: PowerCellAntiqueProto
-        cost: 5
-        prob: 0.5
-      - proto: ProtolatheMachineCircuitboard
-      - proto: RandomArtifactSpawner
-        cost: 2
-      - proto: RandomCargoCorpseSpawner
-        cost: 2
-        prob: 0.5
-      - proto: RandomCommandCorpseSpawner
-        cost: 5
-        prob: 0.5
-      - proto: RandomEngineerCorpseSpawner
-        cost: 2
-        prob: 0.5
-      - proto: RandomMedicCorpseSpawner
-        cost: 2
-        prob: 0.5
-      - proto: RandomScienceCorpseSpawner
-        cost: 2
-        prob: 0.5
-      - proto: RandomSecurityCorpseSpawner
-        cost: 2
-        prob: 0.5
-      - proto: RandomServiceCorpseSpawner
-        cost: 2
-        prob: 0.5
-      - proto: ResearchAndDevelopmentServerMachineCircuitboard
-        cost: 5
-        prob: 0.5
-      - proto: ResearchDisk10000
-        prob: 0.5
-      - proto: ResearchDisk5000
-        prob: 0.5
-      - proto: RipleyHarness
-        cost: 3
-        prob: 0.5
-      - proto: SpaceCash1000
-      - proto: SpaceCash10000
-        cost: 10
-      - proto: SpaceCash2500
-        cost: 3
-      - proto: SpaceCash5000
-        cost: 5
-      - proto: TechnologyDiskRare
-        cost: 5
-        prob: 0.5
-      - proto: ThrusterUnanchored
-      - proto: WaterTankHighCapacity
-      - proto: WeldingFuelTankHighCapacity
-        cost: 3
-      - proto: WeaponTeslaGun
-        prob: 0.1
-        cost: 2
+    - !type:RandomSpawnsLoot
+      entries:
+        - proto: AdvMopItem
+          prob: 0.5
+        - proto: AmmoTechFabCircuitboard
+          cost: 2
+        - proto: AutolatheMachineCircuitboard
+          cost: 2
+        - proto: BiomassReclaimerMachineCircuitboard
+          cost: 2
+        - proto: BluespaceBeaker
+          cost: 2
+        - proto: CyborgEndoskeleton
+          cost: 3
+          prob: 0.5
+        - proto: ChemDispenserMachineCircuitboard
+          cost: 2
+        - proto: CircuitImprinter
+          cost: 2
+        - proto: CloningConsoleComputerCircuitboard
+          cost: 2
+        - proto: CloningPodMachineCircuitboard
+          cost: 2
+        - proto: ChemistryBottleCognizine
+        - proto: FoodBoxDonkpocketCarp
+          prob: 0.5
+        - proto: CrateSalvageEquipment
+          cost: 3
+          prob: 0.5
+        - proto: GasRecycler
+          cost: 2
+        - proto: GeneratorRTG
+          cost: 5
+        - proto: GravityGeneratorMini
+          cost: 2
+        - proto: GyroscopeUnanchored
+          cost: 2
+          prob: 0.1
+        - proto: MedicalScannerMachineCircuitboard
+          cost: 2
+        - proto: NuclearBombKeg
+          cost: 5
+        - proto: ChemistryBottleOmnizine
+          prob: 0.5
+        - proto: PortableGeneratorPacman
+          cost: 2
+        - proto: PortableGeneratorSuperPacman
+          cost: 3
+        - proto: PowerCellAntiqueProto
+          cost: 5
+          prob: 0.5
+        - proto: ProtolatheMachineCircuitboard
+        - proto: RandomArtifactSpawner
+          cost: 2
+        - proto: RandomCargoCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomCommandCorpseSpawner
+          cost: 5
+          prob: 0.5
+        - proto: RandomEngineerCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomMedicCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomScienceCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomSecurityCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: RandomServiceCorpseSpawner
+          cost: 2
+          prob: 0.5
+        - proto: ResearchAndDevelopmentServerMachineCircuitboard
+          cost: 5
+          prob: 0.5
+        - proto: ResearchDisk10000
+          prob: 0.5
+        - proto: ResearchDisk5000
+          prob: 0.5
+        - proto: RipleyHarness
+          cost: 3
+          prob: 0.5
+        - proto: SpaceCash1000
+        - proto: SpaceCash10000
+          cost: 10
+        - proto: SpaceCash2500
+          cost: 3
+        - proto: SpaceCash5000
+          cost: 5
+        - proto: TechnologyDiskRare
+          cost: 5
+          prob: 0.5
+        - proto: ThrusterUnanchored
+        - proto: WaterTankHighCapacity
+        - proto: WeldingFuelTankHighCapacity
+          cost: 3
+        - proto: WeaponTeslaGun
+          prob: 0.1
+          cost: 2
 
 # Mob loot table
 
   id: OreIron
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreIron
+    - !type:BiomeMarkerLoot
+      proto: OreIron
 
 - type: salvageLoot
   id: OreCoal
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreCoal
+    - !type:BiomeMarkerLoot
+      proto: OreCoal
 
 - type: salvageLoot
   id: OreQuartz
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreQuartz
+    - !type:BiomeMarkerLoot
+      proto: OreQuartz
 
 - type: salvageLoot
   id: OreSalt
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreSalt
+    - !type:BiomeMarkerLoot
+      proto: OreSalt
 
 # - Medium value
 - type: salvageLoot
   id: OreGold
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreGold
+    - !type:BiomeMarkerLoot
+      proto: OreGold
 
 - type: salvageLoot
   id: OreSilver
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreSilver
+    - !type:BiomeMarkerLoot
+      proto: OreSilver
 
 # - High value
 - type: salvageLoot
   id: OrePlasma
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OrePlasma
+    - !type:BiomeMarkerLoot
+      proto: OrePlasma
 
 - type: salvageLoot
   id: OreUranium
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreUranium
+    - !type:BiomeMarkerLoot
+      proto: OreUranium
 
 - type: salvageLoot
   id: OreDiamond
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreDiamond
+    - !type:BiomeMarkerLoot
+      proto: OreDiamond
 
 - type: salvageLoot
   id: OreArtifactFragment
   guaranteed: true
   loots:
-  - !type:BiomeLoot
-    proto: OreArtifactFragment
+    - !type:BiomeMarkerLoot
+      proto: OreArtifactFragment
index e8b18dd14a86b6c71bb5476614e3f2bb988d91aa..ca64d29a52acffbe375052a235cc7002b33f1796 100644 (file)
@@ -8,24 +8,24 @@
 - type: salvageBiomeMod
   id: Caves
   desc: salvage-biome-mod-caves
-  biome: BiomeCaves
+  biome: Caves
 
 - type: salvageBiomeMod
   id: Grasslands
   desc: salvage-biome-mod-grasslands
-  biome: BiomeGrasslands
+  biome: Grasslands
 
 - type: salvageBiomeMod
   id: Snow
   desc: salvage-biome-mod-snow
   cost: 1
-  biome: BiomeSnow
+  biome: Snow
 
 - type: salvageBiomeMod
   id: Lava
   desc: salvage-biome-mod-lava
   cost: 2
-  biome: BiomeLava
+  biome: Lava
 
 #- type: salvageBiomeMod
 #  id: Space
index ad759721cb97ec6d02a3630a9d2930ba4b94176d..0caa9f0e1f57d67d1d8810e6718207fbb592921f 100644 (file)
 
 - type: dungeonConfig
   id: VGRoidSmaller
-  minOffset: 60
-  maxOffset: 80
+  minOffset: 40
+  maxOffset: 60
   layers:
   - !type:NoiseDistanceDunGen
     size: 150, 150
   maxCount: 3
   layers:
   - !type:ExteriorDunGen
-    penetration: 5,15
     proto: Experiment
   - !type:EntityTableDunGen
     minCount: 25
   layers:
   - !type:FillGridDunGen
     entity: IronRock
-    threshold: -0.95
-    size: 350, 350
-    distanceConfig: !type:DunGenEuclideanSquaredDistance
-      blendWeight: -0.50
-    reservedNoise:
-      frequency: 0.080
-      noiseType: OpenSimplex2
-      fractalType: FBm
-      octaves: 5
-      lacunarity: 1.5
-      gain: 0.5
     allowedTiles:
     - FloorAsteroidSand