]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Biome rework (#37735)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Thu, 3 Jul 2025 04:36:06 +0000 (14:36 +1000)
committerGitHub <noreply@github.com>
Thu, 3 Jul 2025 04:36:06 +0000 (00:36 -0400)
* DungeonData rework

Back to fields, serializes better, just make new layers dumby.

* wawawewa

* Fix this

* Fixes

* Port the work over

* wawawewa

* zoom

* Kinda workin

* Adjust wawa

* Unloading work

* Ore + entitytable fixes

Iterate every dungeon not just last.

* Big shot

* wawawewa

* Fixes

* true

* Fixes

# Conflicts:
# Content.Server/Procedural/DungeonJob/DungeonJob.cs

* wawawewa

* Fixes

* Fix

* Lot of work

* wawawewa

* Fixing

* eh?

* a

* Fix a heap of stuff

* Better ignored check

* Reserve tile changes

* biome

* changes

* wawawewa

* Fixes & snow

* Shadow fixes

* wawawewa

* smol

* Add layer API

* More work

* wawawewa

* Preloads and running again

* wawawewa

* Modified

* Replacements and command

* Runtime support

* werk

* Fix expeds + dungeon alltiles

* reh

---------

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
116 files changed:
Content.Client/Parallax/BiomeDebugOverlay.cs [deleted file]
Content.Client/Parallax/BiomeSystem.cs [deleted file]
Content.Client/Parallax/Commands/ShowBiomeCommand.cs [deleted file]
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 [deleted file]
Content.Server/Parallax/BiomeSystem.cs [deleted file]
Content.Server/Procedural/BiomeSystem.Commands.cs [new file with mode: 0644]
Content.Server/Procedural/BiomeSystem.Planet.cs [new file with mode: 0644]
Content.Server/Procedural/BiomeSystem.cs [new file with mode: 0644]
Content.Server/Procedural/DungeonJob/DungeonJob.AutoCabling.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.BiomeMarkerLayer.cs [deleted file]
Content.Server/Procedural/DungeonJob/DungeonJob.BoundaryWall.cs
Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs [new file with mode: 0644]
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 [new file with mode: 0644]
Content.Server/Procedural/DungeonJob/DungeonJob.RoomEntrance.cs
Content.Server/Procedural/DungeonJob/DungeonJob.SampleDecal.cs [new file with mode: 0644]
Content.Server/Procedural/DungeonJob/DungeonJob.SampleEntity.cs [new file with mode: 0644]
Content.Server/Procedural/DungeonJob/DungeonJob.SampleTile.cs [new file with mode: 0644]
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 [new file with mode: 0644]
Content.Shared/Parallax/Biomes/BiomeComponent.cs [deleted file]
Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs [deleted file]
Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs [deleted file]
Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs [deleted file]
Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs [deleted file]
Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs [deleted file]
Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs [deleted file]
Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs [deleted file]
Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs [deleted file]
Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs [deleted file]
Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs [deleted file]
Content.Shared/Procedural/Components/BiomeComponent.cs [new file with mode: 0644]
Content.Shared/Procedural/Components/BiomeForceUnloadComponent.cs [new file with mode: 0644]
Content.Shared/Procedural/Distance/DunGenDistanceSquared.cs [new file with mode: 0644]
Content.Shared/Procedural/DungeonConfig.cs
Content.Shared/Procedural/DungeonData.cs [new file with mode: 0644]
Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs [new file with mode: 0644]
Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs
Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs
Content.Shared/Procedural/DungeonLayers/AutoCablingDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/BoundaryWallDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/CornerClutterDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/CorridorClutterDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/CorridorDecalSkirtingDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/CorridorDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/DungeonEntranceDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/EntranceFlankDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs with 93% similarity]
Content.Shared/Procedural/DungeonLayers/ExternalWindowDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs with 94% similarity]
Content.Shared/Procedural/DungeonLayers/FillGridDunGen.cs
Content.Shared/Procedural/DungeonLayers/InternalWindowDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/JunctionDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/MiddleConnectionDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs
Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs [new file with mode: 0644]
Content.Shared/Procedural/DungeonLayers/RoomEntranceDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs with 93% similarity]
Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs [new file with mode: 0644]
Content.Shared/Procedural/DungeonLayers/SampleEntityDunGen.cs [new file with mode: 0644]
Content.Shared/Procedural/DungeonLayers/SampleTileDunGen.cs [moved from Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs with 66% similarity]
Content.Shared/Procedural/DungeonLayers/SplineDungeonConnectorDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs with 83% similarity]
Content.Shared/Procedural/DungeonLayers/WallMountDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs with 100% similarity]
Content.Shared/Procedural/DungeonLayers/WormCorridorDunGen.cs [moved from Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs with 100% similarity]
Content.Shared/Procedural/Loot/BiomeLoot.cs [new file with mode: 0644]
Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs [deleted file]
Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs [deleted file]
Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs [deleted file]
Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs [deleted file]
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
deleted file mode 100644 (file)
index c914cb5..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-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
deleted file mode 100644 (file)
index dc326e1..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644 (file)
index 2a628dd..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-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 06f830675dadd05cf68124adb9ac2d97c995cdb9..8e3a1dddfe130597464fbe268bf75573949c8b81 100644 (file)
@@ -1,8 +1,6 @@
 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;
@@ -19,7 +17,6 @@ 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;
@@ -28,13 +25,12 @@ 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 || _entManager.HasComponent<BiomeComponent>(_mapSystem.GetMapOrInvalid(args.MapId)))
+        if (args.MapId == MapId.Nullspace)
             return false;
 
         return true;
index 7855577e69462e658549fe7fc0b88ab8e79f30aa..8d718f1be5bfcfb6340d02997dd1c555c5f50982 100644 (file)
@@ -1,23 +1,9 @@
-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 d65760e270fdc4f81fc6868d9d741838a420a472..03a7ec5c4d1dacfbd2224bda978fae3afcde7720 100644 (file)
@@ -1,4 +1,4 @@
-using Content.Shared.Parallax.Biomes.Markers;
+using Content.Shared.Procedural;
 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<BiomeMarkerLayerPrototype>> MobLayers = new()
+    public List<ProtoId<DungeonConfigPrototype>> MobLayers = new()
     {
         "Carps",
         "Xenos",
@@ -54,7 +54,7 @@ public sealed partial class GatewayGeneratorComponent : Component
     /// <summary>
     /// Loot layers to pick from.
     /// </summary>
-    public List<ProtoId<BiomeMarkerLayerPrototype>> LootLayers = new()
+    public List<ProtoId<DungeonConfigPrototype>> LootLayers = new()
     {
         "OreIron",
         "OreQuartz",
index 83471cdbc166190629dba0c42bf823fd44f4d873..7f4bdccf2ec6326eed2759ebcbf9a65c52d35658 100644 (file)
@@ -1,12 +1,11 @@
 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;
@@ -111,7 +110,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
         };
         AddComp(mapUid, restricted);
 
-        _biome.EnsurePlanet(mapUid, _protoManager.Index<BiomeTemplatePrototype>("Continental"), seed);
+        _biome.EnsurePlanet(mapUid, _protoManager.Index("BiomeContinental"), seed);
 
         var grid = Comp<MapGridComponent>(mapUid);
 
@@ -199,7 +198,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
                 var layer = lootLayers[layerIdx];
                 lootLayers.RemoveSwap(layerIdx);
 
-                _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
+                _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
             }
 
             // - Mobs
@@ -211,7 +210,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem
                 var layer = mobLayers[layerIdx];
                 mobLayers.RemoveSwap(layerIdx);
 
-                _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
+                _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
             }
         }
     }
index 8e8b5b10edb96782b6d76c366ea80af835c7129f..d3b7abc1715a35e7c38dc00d55f47d3f4926b991 100644 (file)
@@ -1,20 +1,11 @@
 using System.Linq;
 using Content.Server.Administration;
-using Content.Server.Atmos;
-using Content.Server.Atmos.Components;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Parallax;
+using Content.Server.Procedural;
 using Content.Shared.Administration;
-using Content.Shared.Atmos;
-using Content.Shared.Gravity;
-using Content.Shared.Movement.Components;
-using Content.Shared.Parallax.Biomes;
-using Robust.Shared.Audio;
+using Content.Shared.Procedural.Components;
 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;
 
@@ -52,7 +43,7 @@ public sealed class PlanetCommand : LocalizedEntityCommands
             return;
         }
 
-        if (!_protoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var biomeTemplate))
+        if (!_protoManager.TryIndex<EntityPrototype>(args[1], out var biomeTemplate))
         {
             shell.WriteError(Loc.GetString("cmd-planet-map-prototype", ("prototype", args[1])));
             return;
@@ -70,9 +61,12 @@ 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<BiomeTemplatePrototype>()
+            var options = _protoManager.EnumeratePrototypes<EntityPrototype>()
+                .Where(o => o.Components.ContainsKey(biomeName))
                 .Select(o => new CompletionOption(o.ID, "Biome"));
             return CompletionResult.FromOptions(options);
         }
index 7afd3d78df1ff510b43e06338afdfcc8b75cae8c..07d6a624c84997e66c8ee403de7cf6d48c0f2f93 100644 (file)
@@ -65,6 +65,9 @@ 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;
 
@@ -121,8 +124,7 @@ public sealed partial class PathfindingSystem
                         cameFrom[neighbor] = node;
                         costSoFar[neighbor] = gScore;
 
-                        // Still use octile even for manhattan distance.
-                        var hScore = OctileDistance(args.End, neighbor) * 1.001f;
+                        var hScore = ManhattanDistance(args.End, neighbor);
                         var fScore = gScore + hScore;
                         frontier.Enqueue(neighbor, fScore);
                     }
diff --git a/Content.Server/Parallax/BiomeSystem.Commands.cs b/Content.Server/Parallax/BiomeSystem.Commands.cs
deleted file mode 100644 (file)
index a915e17..0000000
+++ /dev/null
@@ -1,188 +0,0 @@
-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
deleted file mode 100644 (file)
index 496cb38..0000000
+++ /dev/null
@@ -1,1086 +0,0 @@
-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
new file mode 100644 (file)
index 0000000..ef90310
--- /dev/null
@@ -0,0 +1,105 @@
+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
new file mode 100644 (file)
index 0000000..c5138ca
--- /dev/null
@@ -0,0 +1,65 @@
+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
new file mode 100644 (file)
index 0000000..6d584ad
--- /dev/null
@@ -0,0 +1,661 @@
+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 8c49a7d606a17a794863586a1b5ce5001161e550..83ff71f8b75dfaefb55e1a8c4c1224d486b1e28b 100644 (file)
@@ -151,7 +151,8 @@ public sealed partial class DungeonJob
             if (found)
                 continue;
 
-            _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
+            var ent = _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
+            AddLoadedEntity(tile, ent);
         }
     }
 }
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs
deleted file mode 100644 (file)
index 48adb8a..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-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
deleted file mode 100644 (file)
index abc74dd..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-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 1c48a84cce55c65e63b164c67073d6fd09c5605a..7bc7527ff5b37263551c9e26bd7b3296e552cda6 100644 (file)
@@ -32,7 +32,10 @@ public sealed partial class DungeonJob
             if (!_anchorable.TileFree((_gridUid, _grid), neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
                 continue;
 
-            tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+            var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+            tiles.Add((neighbor, tile));
+            AddLoadedTile(neighbor, tile);
+            DebugTools.Assert(dungeon.AllTiles.Contains(neighbor));
         }
 
         foreach (var index in dungeon.CorridorExteriorTiles)
@@ -43,7 +46,10 @@ public sealed partial class DungeonJob
             if (!_anchorable.TileFree((_gridUid, _grid), index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
                 continue;
 
-            tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random)));
+            var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+            tiles.Add((index, tile));
+            AddLoadedTile(index, tile);
+            DebugTools.Assert(dungeon.AllTiles.Contains(index));
         }
 
         _maps.SetTiles(_gridUid, _grid, tiles);
@@ -82,18 +88,21 @@ public sealed partial class DungeonJob
             }
 
             if (isCorner)
-                _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
+            {
+                var uid = _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
+                AddLoadedEntity(index.Index, uid);
+            }
 
             if (!isCorner)
-                _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
-
-            if (i % 20 == 0)
             {
-                await SuspendDungeon();
-
-                if (!ValidateResume())
-                    return;
+                var uid = _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
+                AddLoadedEntity(index.Index, uid);
             }
+
+            await SuspendDungeon();
+
+            if (!ValidateResume())
+                return;
         }
     }
 }
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs
new file mode 100644 (file)
index 0000000..391fb91
--- /dev/null
@@ -0,0 +1,42 @@
+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 bf9f910b94d8d29c7b35af5cc7af825bbad56bc5..8353d81f1609c1443be99ecd46a7c171ef48620c 100644 (file)
@@ -94,12 +94,14 @@ public sealed partial class DungeonJob
         var setTiles = new List<(Vector2i, Tile)>();
         var tileDef = (ContentTileDefinition) _tileDefManager[gen.Tile];
 
-        foreach (var tile in corridorTiles)
+        foreach (var node in corridorTiles)
         {
-            if (reservedTiles.Contains(tile))
+            if (reservedTiles.Contains(node))
                 continue;
 
-            setTiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
+            var tile = _tile.GetVariantTile(tileDef, random);
+            setTiles.Add((node, tile));
+            AddLoadedTile(node, tile);
         }
 
         _maps.SetTiles(_gridUid, _grid, setTiles);
index e0be852733d867b0ac355f2dd2b665f6ab975f66..e28c6798f2d72a8eadc46d44ae65cbffa452a638 100644 (file)
@@ -48,7 +48,13 @@ public sealed partial class DungeonJob
 
             var protos = _entTable.GetSpawns(contents, random);
             var coords = _maps.ToCenterCoordinates(_gridUid, tile, _grid);
-            _entManager.SpawnEntitiesAttachedTo(coords, protos);
+            var uids = _entManager.SpawnEntitiesAttachedTo(coords, protos);
+
+            foreach (var uid in uids)
+            {
+                AddLoadedEntity(tile, uid);
+            }
+
             await SuspendIfOutOfTime();
 
             if (!ValidateResume())
index cd8737e6ec21175f33551889b5b68c4ee3d047b9..48088a47184e85aa3df86df4086a260d7d7500f5 100644 (file)
@@ -83,7 +83,8 @@ 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 _, color: decks.Color);
+                        _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
+                        AddLoadedDecal(tile, did);
                     }
                 }
 
@@ -96,7 +97,8 @@ 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 _, color: decks.Color);
+                    _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
+                    AddLoadedDecal(tile, did);
                 }
 
                 continue;
@@ -111,7 +113,8 @@ 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 _, color: decks.Color);
+                    _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
+                    AddLoadedDecal(tile, did);
                 }
             }
         }
index f1808ec90cd2c61e450991acf37e1493a9560944..10481c5b5ff09b80fdb64f5e17156b5fb82a2589 100644 (file)
@@ -28,7 +28,7 @@ public sealed partial class DungeonJob
         Random random)
     {
         var tiles = new List<(Vector2i, Tile)>();
-        var matrix = Matrix3Helpers.CreateTranslation(position);
+        var matrix = Matrix3Helpers.CreateTranslation(_position + position);
 
         foreach (var layer in dungen.Layers)
         {
@@ -76,7 +76,9 @@ public sealed partial class DungeonJob
                         break;
                     }
 
-                    tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
+                    var tile = new Tile(tileDef.TileId, variant: variant);
+                    tiles.Add((adjusted, tile));
+                    AddLoadedTile(adjusted, tile);
                     roomTiles.Add(adjusted);
                     break;
                 }
@@ -101,6 +103,8 @@ 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 8eb85e2cb863ebd02b49644606aa1d311bb376cd..f348ce730116148eed69ceb7b6e88ac5339412e4 100644 (file)
@@ -196,7 +196,9 @@ public sealed partial class DungeonJob
                                     if (reservedTiles.Contains(index))
                                         continue;
 
-                                    tiles.Add((index, new Tile(_tileDefManager[fallbackTile.Value].TileId)));
+                                    var tile = new Tile(_tileDefManager[fallbackTile.Value].TileId);
+                                    tiles.Add((index, tile));
+                                    AddLoadedTile(index, tile);
                                 }
                             }
 
@@ -230,7 +232,14 @@ public sealed partial class DungeonJob
                 var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform);
 
                 // The expensive bit yippy.
-                _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
+                var data = _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
+
+                _data.Merge(data);
+
+                await SuspendDungeon();
+
+                if (!ValidateResume())
+                    return dungeon;
 
                 var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize;
                 var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
index dfc09329151a4943a8788536c61c13737fc7d832..4c53141bc908047083e40e30074c3d90e8b9ea8f 100644 (file)
@@ -42,6 +42,7 @@ public sealed partial class DungeonJob
                     }
 
                     replacements.Add((node, tile));
+                    AddLoadedTile(node, tile);
                     break;
                 }
 
index dceeac3f1248af3f3b6ec7ae51d17f7241ffe4cd..35a35e965667e4b0b52d75bce923ea9b322326b0 100644 (file)
@@ -71,14 +71,17 @@ public sealed partial class DungeonJob
                     isValid = true;
 
                     // Entrance wew
-                    _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile(tileDef, random));
+                    var tileVariant = _tile.GetVariantTile(tileDef, random);
+                    _maps.SetTile(_gridUid, _grid, tile, tileVariant);
+                    AddLoadedTile(tile, tileVariant);
                     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))
                     {
-                        _entManager.SpawnAtPosition(ent, gridCoords);
+                        var uid = _entManager.SpawnAtPosition(ent, gridCoords);
+                        AddLoadedEntity(tile, uid);
                     }
 
                     // Clear out any biome tiles nearby to avoid blocking it
index 6483448240b7e0dddaafa6fb944309a97a8338b5..02686f7a26a13764aecbd0617d7d5413d0f3acc9 100644 (file)
@@ -51,6 +51,7 @@ 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 1788c23cae3962aae8f885ecace50bd76dfc99f9..831081cdf1945fa3a4f36ca999620153ca51d033 100644 (file)
@@ -35,7 +35,9 @@ public sealed partial class DungeonJob
                     if (reservedTiles.Contains(neighbor))
                         continue;
 
-                    tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+                    var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+                    tiles.Add((neighbor, tile));
+                    AddLoadedTile(neighbor, tile);
                     spawnPositions.Add(neighbor);
                 }
             }
@@ -45,7 +47,12 @@ public sealed partial class DungeonJob
 
         foreach (var entrance in spawnPositions)
         {
-            _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
+            var uids = _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
+
+            foreach (var uid in uids)
+            {
+                AddLoadedEntity(entrance, uid);
+            }
         }
     }
 }
index 4f2f564dedbaab9d93f574a82da0a0191130f2c3..1248ab75058162a26c6897ea5ec24fc0833a1e47 100644 (file)
@@ -1,3 +1,4 @@
+using System.Numerics;
 using System.Threading.Tasks;
 using Content.Shared.Maps;
 using Content.Shared.NPC;
@@ -13,15 +14,22 @@ public sealed partial class DungeonJob
     /// <summary>
     /// <see cref="ExteriorDunGen"/>
     /// </summary>
-    private async Task<List<Dungeon>> GenerateExteriorDungen(Vector2i position, ExteriorDunGen dungen, HashSet<Vector2i> reservedTiles, Random random)
+    private async Task<List<Dungeon>> GenerateExteriorDungen(int runCount, int maxRuns, 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());
-        var angle = random.NextAngle();
+        // 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);
 
-        var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f);
+        // 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 distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f);
         var startTile = new Vector2i(0, (int) distance).Rotate(angle);
 
         Vector2i? dungeonSpawn = null;
@@ -47,9 +55,19 @@ public sealed partial class DungeonJob
             };
         }
 
-        var config = _prototype.Index(dungen.Proto);
+        // 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 nextSeed = random.Next();
-        var dungeons = await GetDungeons(dungeonSpawn.Value, config, config.Layers, reservedTiles, nextSeed, new Random(nextSeed));
+        var (dungeons, newReserved) = await GetDungeons(dungeonSpawn.Value, subConfig, subConfig.Layers, nextSeed, new Random(nextSeed), reserved: reservedTiles);
+        reservedTiles.UnionWith(newReserved);
 
         return dungeons;
     }
index 482cb34a564dc15a52ec0c7df9783dbe85696171..1608e50266aec4f4bf8a1f855bc8f904e50f2af2 100644 (file)
@@ -105,7 +105,9 @@ public sealed partial class DungeonJob
                     if (reservedTiles.Contains(neighbor))
                         continue;
 
-                    tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+                    var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+                    tiles.Add((neighbor, tileVariant));
+                    AddLoadedTile(neighbor, tileVariant);
                     index++;
                     takenTiles.Add(neighbor);
                 }
@@ -119,7 +121,13 @@ public sealed partial class DungeonJob
         {
             var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile.Item1);
 
-            _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+            var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+
+            foreach (var uid in uids)
+            {
+                AddLoadedEntity(tile.Item1, uid);
+            }
+
             await SuspendDungeon();
 
             if (!ValidateResume())
index b579d4e5a1c2ca62edf52a4ce1e4e2899020cde7..95ec9d56e5a566bf70d1048a5e38a35c1ddae9d0 100644 (file)
@@ -18,21 +18,45 @@ 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);
-                _entManager.SpawnEntity(fill.Entity, gridPos);
+                var uid = _entManager.SpawnEntity(fill.Entity, gridPos);
 
-                await SuspendDungeon();
-                if (!ValidateResume())
-                    break;
+                AddLoadedEntity(tile, uid);
             }
         }
     }
index c57757b4213c6e3a8372a3ed8f0ec61340228122..4ced89ba0e04e830cd2e611902fc20544da917e1 100644 (file)
@@ -52,6 +52,8 @@ public sealed partial class DungeonJob
                 }
             }
         }
+
+        dungeon.RefreshAllTiles();
     }
 
     private void WidenCorridor(Dungeon dungeon, float width, ICollection<Vector2i> corridorTiles)
index f80b3face71f805cc4d80ac9696c2d42636a745f..c736eb60703cfc5be439474d06b29297d92d671e 100644 (file)
@@ -81,9 +81,16 @@ public sealed partial class DungeonJob
                 {
                     var tile = validTiles[j];
                     var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile);
-                    _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
+                    var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+                    _maps.SetTile(_gridUid, _grid, tile, tileVariant);
+                    AddLoadedTile(tile, tileVariant);
 
-                    _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+                    var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+
+                    foreach (var uid in uids)
+                    {
+                        AddLoadedEntity(tile, uid);
+                    }
                 }
 
                 if (validTiles.Count > 0)
index 28cbc9b208bf918c2ae7fe9b08772cef57326fc4..88db2fad42eb2c9c0ac2330c19d34eaad9f6d358 100644 (file)
@@ -113,10 +113,17 @@ public sealed partial class DungeonJob
                         if (reservedTiles.Contains(weh))
                             continue;
 
-                        _maps.SetTile(_gridUid, _grid, weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
+                        var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+                        _maps.SetTile(_gridUid, _grid, weh, tileVariant);
+                        AddLoadedTile(weh, tileVariant);
 
                         var coords = _maps.GridTileToLocal(_gridUid, _grid, weh);
-                        _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
+                        var uids = _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
+
+                        foreach (var uid in uids)
+                        {
+                            AddLoadedEntity(weh, uid);
+                        }
                     }
 
                     break;
index d6e3c09d6222c44685b7f0646abeb8109af2e5a6..52edaf343383ae3b0a7510376939bb66f11f083c 100644 (file)
@@ -18,6 +18,7 @@ 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)
         {
@@ -107,18 +108,30 @@ public sealed partial class DungeonJob
                         continue;
 
                     width--;
-                    _maps.SetTile(_gridUid, _grid, node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
+                    var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+                    _maps.SetTile(_gridUid, _grid, node, tileVariant);
+                    AddLoadedTile(node, tileVariant);
 
                     if (flankContents != null && nodeDistances.Count - i <= 2)
                     {
-                        _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
+                        var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
+
+                        foreach (var uid in uids)
+                        {
+                            AddLoadedEntity(node, uid);
+                        }
                     }
                     else
                     {
                         // Iterate neighbors and check for blockers, if so bulldoze
                         ClearDoor(dungeon, _grid, node);
 
-                        _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+                        var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+
+                        foreach (var uid in uids)
+                        {
+                            AddLoadedEntity(node, uid);
+                        }
                     }
 
                     if (width == 0)
index caf6828e43c32d2a7b851b230a46cde5831cf3f9..341dbf168ac39d982a73661b8f9a106acc89dcb9 100644 (file)
@@ -49,6 +49,7 @@ public sealed partial class DungeonJob
                     _entManager.RemoveComponent<GhostRoleComponent>(uid);
                     _entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
                     npcs.SleepNPC(uid);
+                    AddLoadedEntity(tile, uid);
                 }
 
                 break;
index b2526ec17d1b53d5ea17950048dd11fcc6c32d62..201948fc02e4b9977cf669cc4db4eb2840ea8985 100644 (file)
@@ -98,7 +98,9 @@ public sealed partial class DungeonJob
                     var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random);
                     var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored();
 
-                    tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
+                    var tileVariant = new Tile(tileDef.TileId, variant: variant);
+                    tiles.Add((adjusted, tileVariant));
+                    AddLoadedTile(adjusted, tileVariant);
                     roomTiles.Add(adjusted);
                     tileCount++;
                     break;
@@ -127,8 +129,7 @@ public sealed partial class DungeonJob
                     }
                 }
 
-                await SuspendIfOutOfTime();
-                ValidateResume();
+                await SuspendDungeon();
             }
 
             var center = Vector2.Zero;
@@ -140,8 +141,7 @@ public sealed partial class DungeonJob
 
             center /= roomTiles.Count;
             rooms.Add(new DungeonRoom(roomTiles, center, roomArea, new HashSet<Vector2i>()));
-            await SuspendIfOutOfTime();
-            ValidateResume();
+            await SuspendDungeon();
         }
 
         _maps.SetTiles(_gridUid, _grid, tiles);
index 78ab2b7a0d28ac54abf581b3db35dcf5eae36b20..035820fd90a66d74b241d3c884ab11add5e54ada 100644 (file)
@@ -19,12 +19,26 @@ public sealed partial class DungeonJob
         HashSet<Vector2i> reservedTiles,
         Random random)
     {
-        foreach (var dungeon in dungeons)
+        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 emptyTiles = false;
-            var replaceEntities = new Dictionary<Vector2i, EntityUid>();
-            var availableTiles = new List<Vector2i>();
+            var remappingComp = (EntityRemapComponent) replacementComps;
+            replacementRemapping = remappingComp.Mask;
+        }
+
+        if (gen.Replacement != null)
+        {
+            replacementRemapping[gen.Replacement.Value] = gen.Entity;
+        }
 
+        foreach (var dungeon in dungeons)
+        {
             foreach (var node in dungeon.AllTiles)
             {
                 if (reservedTiles.Contains(node))
@@ -41,19 +55,23 @@ 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.
-                while (enumerator.MoveNext(out var uid))
+                // We can't replace so just stop here.
+                if (gen.Replacement != null)
                 {
-                    // We can't replace so just stop here.
-                    if (gen.Replacement == null)
-                        break;
+                    while (enumerator.MoveNext(out var uid))
+                    {
+                        var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
 
-                    var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
+                        if (string.IsNullOrEmpty(prototype?.ID))
+                            continue;
 
-                    if (prototype?.ID == gen.Replacement)
-                    {
-                        replaceEntities[node] = uid.Value;
-                        found = true;
-                        break;
+                        // It has a valid remapping so take it over.
+                        if (replacementRemapping.ContainsKey(prototype.ID))
+                        {
+                            replaceEntities[node] = uid.Value;
+                            found = true;
+                            break;
+                        }
                     }
                 }
 
@@ -68,84 +86,87 @@ 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("EntityRemap", 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(remapName, 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++)
-            {
-                await SuspendDungeon();
+        var frontier = new ValueList<Vector2i>(32);
 
-                if (!ValidateResume())
-                    return;
+        // 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 groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1);
+            // While we have remaining tiles keep iterating
+            while (groupSize > 0 && availableTiles.Count > 0)
+            {
+                var startNode = random.PickAndTake(availableTiles);
+                frontier.Clear();
+                frontier.Add(startNode);
 
-                // While we have remaining tiles keep iterating
-                while (groupSize > 0 && availableTiles.Count > 0)
+                // 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)
                 {
-                    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)
+                    // 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++)
                     {
-                        // 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++)
+                        for (var y = -1; y <= 1; y++)
                         {
-                            for (var y = -1; y <= 1; y++)
-                            {
-                                var neighbor = new Vector2i(node.X + x, node.Y + 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;
+                    var prototype = gen.Entity;
 
-                        if (replaceEntities.TryGetValue(node, out var existingEnt))
-                        {
-                            var existingProto = _entManager.GetComponent<MetaDataComponent>(existingEnt).EntityPrototype;
-                            _entManager.DeleteEntity(existingEnt);
+                    // 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);
 
-                            if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
-                            {
-                                prototype = remapped;
-                            }
+                        if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
+                        {
+                            prototype = remapped;
                         }
+                    }
 
-                        // Tile valid salad so add it.
-                        _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
+                    // Tile valid salad so add it.
+                    var uid = _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
+                    AddLoadedEntity(node, uid);
 
-                        groupSize--;
-                    }
-                }
+                    groupSize--;
 
-                if (groupSize > 0)
-                {
-                    _sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!");
+                    await SuspendDungeon();
+
+                    if (!ValidateResume())
+                        return;
                 }
             }
+
+            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}!");
+            }
         }
     }
 }
diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs
new file mode 100644 (file)
index 0000000..2d00dbe
--- /dev/null
@@ -0,0 +1,38 @@
+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 a4a01b5f0b072a4f26a05fc5eb66792bba5dae35..17ad9b5e35fbd4dd727c752740ec39f2a1e9ad84 100644 (file)
@@ -25,7 +25,9 @@ public sealed partial class DungeonJob
                 if (reservedTiles.Contains(entrance))
                     continue;
 
-                setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+                var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+                setTiles.Add((entrance, tileVariant));
+                AddLoadedTile(entrance, tileVariant);
             }
         }
 
@@ -38,10 +40,15 @@ public sealed partial class DungeonJob
                 if (reservedTiles.Contains(entrance))
                     continue;
 
-                _entManager.SpawnEntitiesAttachedTo(
+                var uids = _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
new file mode 100644 (file)
index 0000000..b240c0b
--- /dev/null
@@ -0,0 +1,64 @@
+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
new file mode 100644 (file)
index 0000000..1a9fc8b
--- /dev/null
@@ -0,0 +1,62 @@
+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
new file mode 100644 (file)
index 0000000..97875b2
--- /dev/null
@@ -0,0 +1,66 @@
+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 a131efd3534b5900c3bf4da2904219e789d58d8a..8635bc0f48bfecc98f6877b1de325592bfc7403a 100644 (file)
@@ -2,6 +2,7 @@ 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;
@@ -11,10 +12,10 @@ namespace Content.Server.Procedural.DungeonJob;
 public sealed partial class DungeonJob
 {
     /// <summary>
-    /// <see cref="SplineDungeonConnectorDunGen"/>
+    /// <see cref="Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen"/>
     /// </summary>
     private async Task<Dungeon> PostGen(
-        SplineDungeonConnectorDunGen gen,
+        Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen gen,
         List<Dungeon> dungeons,
         HashSet<Vector2i> reservedTiles,
         Random random)
@@ -59,6 +60,7 @@ 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.
@@ -110,6 +112,7 @@ public sealed partial class DungeonJob
                 }
 
                 tiles.Add((node, tile));
+                AddLoadedTile(node, tile);
             }
 
             _maps.SetTiles(_gridUid, _grid, tiles);
@@ -123,6 +126,7 @@ public sealed partial class DungeonJob
 
                 allTiles.Add(node);
                 tiles.Add((node, pathTile));
+                AddLoadedTile(node, pathTile);
             }
 
             _maps.SetTiles(_gridUid, _grid, tiles);
index e5bb32bd0c000296c64d98a7cf5d3f6b9076102e..fa2921b80a261859680a7caf4781fd0aa0fa9513 100644 (file)
@@ -32,11 +32,18 @@ public sealed partial class DungeonJob
             if (reservedTiles.Contains(neighbor))
                 continue;
 
-            _maps.SetTile(_gridUid, _grid, neighbor, _tile.GetVariantTile(tileDef, random));
+            var tileVariant = _tile.GetVariantTile(tileDef, random);
+            _maps.SetTile(_gridUid, _grid, neighbor, tileVariant);
+            AddLoadedTile(neighbor, tileVariant);
             var gridPos = _maps.GridTileToLocal(_gridUid, _grid, neighbor);
             var protoNames = _entTable.GetSpawns(contents, random);
 
-            _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
+            var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
+
+            foreach (var uid in uids)
+            {
+                AddLoadedEntity(neighbor, uid);
+            }
 
             await SuspendDungeon();
             if (!ValidateResume())
index 77404fc963c17f6c673ec6477a51bb85f1f93b95..81454922059cf3cd553bf108786ffd0a89b971e5 100644 (file)
@@ -1,8 +1,7 @@
-using System.Linq;
+using System.Numerics;
 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;
@@ -23,11 +22,10 @@ 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>>
+public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
 {
     public bool TimeSlice = true;
 
@@ -60,6 +58,10 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
 
     private readonly ISawmill _sawmill;
 
+    private DungeonData _data = new();
+
+    private HashSet<Vector2i>? _reservedTiles;
+
     public DungeonJob(
         ISawmill sawmill,
         double maxTime,
@@ -79,12 +81,14 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
         int seed,
         Vector2i position,
         EntityCoordinates? targetCoordinates = null,
-        CancellationToken cancellation = default) : base(maxTime, cancellation)
+        CancellationToken cancellation = default,
+        HashSet<Vector2i>? reservedTiles = null) : base(maxTime, cancellation)
     {
         _sawmill = sawmill;
         _entManager = entManager;
         _prototype = prototype;
         _tileDefManager = tileDefManager;
+        _reservedTiles = reservedTiles;
 
         _anchorable = anchorable;
         _decals = decals;
@@ -111,17 +115,18 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
     /// <summary>
     /// Gets the relevant dungeon, running recursively as relevant.
     /// </summary>
-    /// <param name="reserve">Should we reserve tiles even if the config doesn't specify.</param>
-    private async Task<List<Dungeon>> GetDungeons(
+    /// <param name="reservedTiles">Should we reserve tiles even if the config doesn't specify.</param>
+    private async Task<(List<Dungeon>, HashSet<Vector2i>)> 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)
@@ -137,8 +142,8 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
 
             foreach (var layer in layers)
             {
-                var dungCount = dungeons.Count;
-                await RunLayer(dungeons, position, layer, reservedTiles, seed, random);
+               var dungCount = dungeons.Count;
+                await RunLayer(i, count, config, dungeons, position, layer, reservedTiles, seed, random);
 
                 if (config.ReserveTiles)
                 {
@@ -152,24 +157,23 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
 
                 await SuspendDungeon();
                 if (!ValidateResume())
-                    return new List<Dungeon>();
+                    return (new List<Dungeon>(), new HashSet<Vector2i>());
             }
         }
 
-        return dungeons;
+        // Only return the new dungeons and applicable reserved tiles.
+        return (dungeons[(existing?.Count ?? 0)..], config.ReturnReserved ? reservedTiles : new HashSet<Vector2i>());
     }
 
-    protected override async Task<List<Dungeon>?> Process()
+    protected override async Task<(List<Dungeon>, DungeonData)> 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();
 
-        // 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);
+        var (dungeons, _) = await GetDungeons(position, _gen, _gen.Layers, _seed, random, reserved: _reservedTiles);
         // To make it slightly more deterministic treat this RNG as separate ig.
 
         // Post-processing after finishing loading.
@@ -181,6 +185,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
         }
 
         // 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>();
@@ -194,10 +199,13 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
         }
 
         _sawmill.Info($"Finished generating dungeon {_gen} with seed {_seed}");
-        return dungeons;
+        return (dungeons, _data);
     }
 
     private async Task RunLayer(
+        int runCount,
+        int maxRuns,
+        DungeonConfig config,
         List<Dungeon> dungeons,
         Vector2i position,
         IDunGenLayer layer,
@@ -205,7 +213,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
         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
@@ -219,15 +227,12 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
             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;
@@ -244,7 +249,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
                 await PostGen(flank, dungeons[^1], reservedTiles, random);
                 break;
             case ExteriorDunGen exterior:
-                dungeons.AddRange(await GenerateExteriorDungen(position, exterior, reservedTiles, random));
+                dungeons.AddRange(await GenerateExteriorDungen(runCount, maxRuns, position, exterior, reservedTiles, random));
                 break;
             case FillGridDunGen fill:
                 await GenerateFillDunGen(fill, dungeons, reservedTiles);
@@ -285,27 +290,67 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
             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:
-                        dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons));
+                        inheritedDungeons = dungeons;
                         break;
                     case DungeonInheritance.Last:
-                        dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons.GetRange(dungeons.Count - 1, 1)));
+                        inheritedDungeons = 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;
@@ -320,11 +365,6 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
         }
     }
 
-    private void LogDataError(Type type)
-    {
-        _sawmill.Error($"Unable to find dungeon data keys for {type}");
-    }
-
     [Pure]
     private bool ValidateResume()
     {
@@ -346,4 +386,19 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
 
         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 e5b0981b3dbeb5db4f53010fa252544fac10de80..e5143454d3d7d1d0d0d0029f4cf6b6d649a1b166 100644 (file)
@@ -113,7 +113,7 @@ public sealed partial class DungeonSystem
         return roomRotation;
     }
 
-    public void SpawnRoom(
+    public DungeonData SpawnRoom(
         EntityUid gridUid,
         MapGridComponent grid,
         Matrix3x2 roomTransform,
@@ -126,6 +126,7 @@ 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();
 
@@ -154,6 +155,7 @@ public sealed partial class DungeonSystem
                 }
 
                 _tiles.Add((rounded, tileRef.Tile));
+                data.Tiles[rounded] = tileRef.Tile;
 
                 if (clearExisting)
                 {
@@ -186,6 +188,7 @@ 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;
@@ -256,14 +259,18 @@ public sealed partial class DungeonSystem
                 var result = _decals.TryAddDecal(
                     decal.Id,
                     new EntityCoordinates(gridUid, position),
-                    out _,
+                    out var did,
                     decal.Color,
                     angle,
                     decal.ZIndex,
                     decal.Cleanable);
 
+                data.Decals.Add(did, position);
+
                 DebugTools.Assert(result);
             }
         }
+
+        return data;
     }
 }
index 3a0a7ab2cdb90cef141a13434ee3e571d6877046..b758fa2d1681db7d73f65c4f685c6ce8c0d7528f 100644 (file)
@@ -199,7 +199,8 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
         MapGridComponent grid,
         Vector2i position,
         int seed,
-        EntityCoordinates? coordinates = null)
+        EntityCoordinates? coordinates = null,
+        HashSet<Vector2i>? reservedTiles = null)
     {
         var cancelToken = new CancellationTokenSource();
         var job = new DungeonJob.DungeonJob(
@@ -221,18 +222,20 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             seed,
             position,
             coordinates,
-            cancelToken.Token);
+            cancelToken.Token,
+            reservedTiles);
 
         _dungeonJobs.Add(job, cancelToken);
         _dungeonJobQueue.EnqueueJob(job);
     }
 
-    public async Task<List<Dungeon>> GenerateDungeonAsync(
+    public async Task<(List<Dungeon>, DungeonData)> GenerateDungeonAsync(
         DungeonConfig gen,
         EntityUid gridUid,
         MapGridComponent grid,
         Vector2i position,
-        int seed)
+        int seed,
+        HashSet<Vector2i>? reservedTiles = null)
     {
         var cancelToken = new CancellationTokenSource();
         var job = new DungeonJob.DungeonJob(
@@ -254,7 +257,8 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             seed,
             position,
             null,
-            cancelToken.Token);
+            cancelToken.Token,
+            reservedTiles);
 
         _dungeonJobs.Add(job, cancelToken);
         _dungeonJobQueue.EnqueueJob(job);
@@ -265,7 +269,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem
             throw job.Exception;
         }
 
-        return job.Result!;
+        return job.Result;
     }
 
     public Angle GetDungeonRotation(int seed)
index cbce4dc6926e8e83e892ca22fe7d9d278476b38c..3a74a67dbf257b3420233ebbabbd8877122bbb4f 100644 (file)
@@ -1,22 +1,17 @@
-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;
@@ -25,15 +20,14 @@ 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;
 
@@ -120,14 +114,13 @@ 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>();
-            biomeSystem.SetTemplate(mapUid, biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
-            biomeSystem.SetSeed(mapUid, biome, mission.Seed);
-            _entManager.Dirty(mapUid, biome);
+
+            biome = biomeSystem.AddBiome(mapUid, missionBiome.BiomePrototype.Value, mission.Seed);
 
             // Gravity
             var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
@@ -172,7 +165,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 = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
+        var (dungeons, data) = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
             _missionParams.Seed));
 
         var dungeon = dungeons.First();
@@ -183,18 +176,20 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             return false;
         }
 
-        expedition.DungeonLocation = dungeonOffset;
-
-        List<Vector2i> reservedTiles = new();
-
-        foreach (var tile in _map.GetTilesIntersecting(mapUid, grid, new Circle(Vector2.Zero, landingPadRadius), false))
+        // Don't modify any dungeon tiles with chunk gen.
+        // Have to defer biome loading until the primo dungen is generated.
+        if (biome != null)
         {
-            if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
-                continue;
+            foreach (var tile in dungeon.AllTiles)
+            {
+                biome.ModifiedTiles.Add(tile);
+            }
 
-            reservedTiles.Add(tile.GridIndices);
+            biome.Enabled = true;
         }
 
+        expedition.DungeonLocation = dungeonOffset;
+
         var budgetEntries = new List<IBudgetEntry>();
 
         /*
@@ -208,13 +203,14 @@ public sealed class SpawnSalvageMissionJob : Job<bool>
             if (!lootProto.Guaranteed)
                 continue;
 
-            try
+            foreach (var rule in lootProto.LootRules)
             {
-                await SpawnDungeonLoot(lootProto, mapUid);
-            }
-            catch (Exception e)
-            {
-                _sawmill.Error($"Failed to spawn guaranteed loot {lootProto.ID}: {e}");
+                switch (rule)
+                {
+                    case BiomeLoot biomeLoot:
+                        _biome.AddLayer(mapUid, $"{rule}", biomeLoot.Proto);
+                        break;
+                }
             }
         }
 
@@ -323,32 +319,4 @@ 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 aa1c2e6dff105134303eed9d98c177bc97b2500f..d2a3157232b856f89b3a0fdedefce07833727e34 100644 (file)
@@ -6,6 +6,7 @@ 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;
@@ -22,7 +23,6 @@ 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<ProtoId<BiomeTemplatePrototype>> _arrivalsBiomeOptions = new()
+    private readonly List<EntProtoId> _arrivalsBiomeOptions = new()
     {
-        "Grasslands",
-        "LowDesert",
-        "Snow",
+        "BiomeGrasslands",
+        "BiomeLowDesert",
+        "BiomeSnow",
     };
 
     public override void Initialize()
index 13e13bf8f371fb996d6c6442fd920f9cb77c4982..9e9bcb6c4247d12b23cee0d6a83ecf54a990d191 100644 (file)
@@ -1,12 +1,14 @@
 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;
@@ -956,6 +958,7 @@ 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)
         {
@@ -969,9 +972,15 @@ public sealed partial class ShuttleSystem
             aabb = aabb.Enlarged(0.2f);
             aabbs.Add(aabb);
 
-            // Handle clearing biome stuff as relevant.
+            if (decalGrid != null)
+            {
+                foreach (var decal in _decals.GetDecalsIntersecting(xform.MapUid.Value, aabb))
+                {
+                    _decals.RemoveDecal(xform.MapUid.Value, decal.Index, decalGrid);
+                }
+            }
+
             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 cea7fbfc091f1ab136a43aac86f28d959117408b..525e16ae1a988f68909efce9ac55faae35edd5a3 100644 (file)
@@ -1,6 +1,7 @@
 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;
@@ -41,10 +42,12 @@ 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 0eb64aaff8092a3d13336d063d62771617dd9eec..a2105d6cef4a72bd7ac9b42f45ade8ef06db8d2b 100644 (file)
@@ -1,5 +1,4 @@
 using Content.Server.Station.Systems;
-using Content.Shared.Parallax.Biomes;
 using Robust.Shared.Prototypes;
 
 namespace Content.Server.Station.Components;
@@ -11,7 +10,7 @@ namespace Content.Server.Station.Components;
 public sealed partial class StationBiomeComponent : Component
 {
     [DataField(required: true)]
-    public ProtoId<BiomeTemplatePrototype> Biome = "Grasslands";
+    public EntProtoId Biome = "BiomeGrasslands";
 
     // If null, its random
     [DataField]
index c12e2f47e4822692cddfd9520db69c7d5e62c9f9..c0777f605253ee0a4e719fe6fab91340a221708c 100644 (file)
@@ -1,4 +1,5 @@
 using Content.Server.Parallax;
+using Content.Server.Procedural;
 using Content.Server.Station.Components;
 using Content.Server.Station.Events;
 using Robust.Shared.Prototypes;
index e771add0e41a32f3b8a2c0ed7469b95dd9973f5c..3c3065c95a688e7473923190338152a4a296e217 100644 (file)
@@ -191,7 +191,7 @@ namespace Content.Server.Tabletop
                 if (!TryComp(uid, out ActorComponent? actor))
                 {
                     RemComp<TabletopGamerComponent>(uid);
-                    return;
+                    continue;
                 }
 
                 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
new file mode 100644 (file)
index 0000000..13c6cd5
--- /dev/null
@@ -0,0 +1,18 @@
+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
deleted file mode 100644 (file)
index af8eb88..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-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
deleted file mode 100644 (file)
index 437ead6..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-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
deleted file mode 100644 (file)
index 5e31e51..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-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
deleted file mode 100644 (file)
index 2beeba6..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-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
deleted file mode 100644 (file)
index 21ffdd9..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-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
deleted file mode 100644 (file)
index 5123140..0000000
+++ /dev/null
@@ -1,27 +0,0 @@
-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;
-}
diff --git a/Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs b/Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs
deleted file mode 100644 (file)
index 3b1ad5c..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-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
deleted file mode 100644 (file)
index e04db91..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-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
deleted file mode 100644 (file)
index fbc3a04..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-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
deleted file mode 100644 (file)
index de2913b..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-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
deleted file mode 100644 (file)
index a5238e8..0000000
+++ /dev/null
@@ -1,386 +0,0 @@
-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
new file mode 100644 (file)
index 0000000..f79a31c
--- /dev/null
@@ -0,0 +1,86 @@
+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
new file mode 100644 (file)
index 0000000..215a8f3
--- /dev/null
@@ -0,0 +1,9 @@
+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
new file mode 100644 (file)
index 0000000..813f5fc
--- /dev/null
@@ -0,0 +1,7 @@
+namespace Content.Shared.Procedural.Distance;
+
+public sealed partial class DunGenDistanceSquared : IDunGenDistance
+{
+    [DataField]
+    public float BlendWeight { get; set; } = 0.50f;
+}
index 7c84b1a6a3b22d484979a5d66eba91bec9b6e12f..56131e5b28045f51956815ceabe4f54e16525e8e 100644 (file)
@@ -12,11 +12,18 @@ public partial class DungeonConfig
     public List<IDunGenLayer> Layers = new();
 
     /// <summary>
-    /// Should we reserve the tiles generated by this config so no other dungeons can spawn on it within the same job?
+    /// Should we reserve the tiles generated by this config so no other layers at the same level can spawn on this tile?
     /// </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
new file mode 100644 (file)
index 0000000..a0209f9
--- /dev/null
@@ -0,0 +1,41 @@
+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
new file mode 100644 (file)
index 0000000..b48a1b3
--- /dev/null
@@ -0,0 +1,24 @@
+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 e9a5181f8d07ab02fe78d87d9480f8c62bda2088..2ece880851b3435098625e1d0d171edaa274dcdf 100644 (file)
@@ -10,4 +10,10 @@ 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 89a4ab216a1acf3561b37d4c7a799f8cc5e6b063..c3f7ae3f332bb27f276770c8df5f07c4e5718673 100644 (file)
@@ -14,6 +14,12 @@ 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;
 }
@@ -35,3 +41,16 @@ 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,
+}
similarity index 93%
rename from Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs
rename to Content.Shared/Procedural/DungeonLayers/EntranceFlankDunGen.cs
index f9be6caf6ae3a1938c593bb660b042c5da72bd39..cd6cf169fc551c7b4743fe32b9e84d07b2beea67 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 94%
rename from Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs
rename to Content.Shared/Procedural/DungeonLayers/ExternalWindowDunGen.cs
index fc992ea7b8928ece434b9d26d2cf4a6db2696bee..30b8302263d91ea6090e263561a331a60665bc06 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;
index 363de0a511668fe7b11846ff2119cae3c3c4f294..e2506298fd7bc31ec086c00261020808ae84f8fc 100644 (file)
@@ -1,4 +1,7 @@
+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;
@@ -6,10 +9,6 @@ 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>
@@ -20,4 +19,29 @@ 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 5525341eb9896595605668c7c37658df5bd5265c..1b754d3778efb0ec9d585c3cb1213c5234fdf3d1 100644 (file)
@@ -1,10 +1,8 @@
 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
new file mode 100644 (file)
index 0000000..fbb174d
--- /dev/null
@@ -0,0 +1,15 @@
+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;
+}
similarity index 93%
rename from Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs
rename to Content.Shared/Procedural/DungeonLayers/RoomEntranceDunGen.cs
index 1436f7473d9718a7cc4efc535da32e92cc17fc8f..f0fea57588f69a959c1a61fe0b887452064e31ba 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;
diff --git a/Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs b/Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs
new file mode 100644 (file)
index 0000000..616e643
--- /dev/null
@@ -0,0 +1,36 @@
+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
new file mode 100644 (file)
index 0000000..2daf7e7
--- /dev/null
@@ -0,0 +1,31 @@
+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();
+}
similarity index 66%
rename from Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs
rename to Content.Shared/Procedural/DungeonLayers/SampleTileDunGen.cs
index 9dee35da4e6deb57181cc62e80075b694d5a1362..6d23d201f54e8bd5d3c47409c5e70a48c8f33708 100644 (file)
@@ -2,20 +2,26 @@ 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.Parallax.Biomes.Layers;
+namespace Content.Shared.Procedural.DungeonLayers;
 
+/// <summary>
+/// Samples noise and spawns the specified tile in the dungeon area.
+/// </summary>
 [Serializable, NetSerializable]
-public sealed partial class BiomeTileLayer : IBiomeLayer
+public sealed partial class SampleTileDunGen : IDunGenLayer
 {
+    /// <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>
similarity index 83%
rename from Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs
rename to Content.Shared/Procedural/DungeonLayers/SplineDungeonConnectorDunGen.cs
index d2f5a2126a74dcb214d4fe41c7d3421a01ddcda4..4a4bc0b36ae0bccb5a7641fb213b66e07aa0b2a7 100644 (file)
@@ -1,7 +1,7 @@
 using Content.Shared.Maps;
 using Robust.Shared.Prototypes;
 
-namespace Content.Shared.Procedural.PostGeneration;
+namespace Content.Shared.Procedural.DungeonLayers;
 
 /// <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 = 10;
+    public int DivisionDistance = 20;
 
     /// <summary>
     /// How much each subdivision can vary from the middle.
     /// </summary>
     [DataField]
-    public float VarianceMax = 0.35f;
+    public float VarianceMax = 0.15f;
 }
diff --git a/Content.Shared/Procedural/Loot/BiomeLoot.cs b/Content.Shared/Procedural/Loot/BiomeLoot.cs
new file mode 100644 (file)
index 0000000..1330043
--- /dev/null
@@ -0,0 +1,12 @@
+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
deleted file mode 100644 (file)
index 2eda4b0..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-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
deleted file mode 100644 (file)
index e4968b6..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-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
deleted file mode 100644 (file)
index e21e582..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-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
deleted file mode 100644 (file)
index af5d7c5..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-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;
-}
index e84223ed1feb364df6cad06810ff185b83cdbc2d..10106cd66653a2b1d75e665657ef220e14d632aa 100644 (file)
@@ -1,6 +1,4 @@
-using Content.Shared.Parallax.Biomes;
 using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
 
 namespace Content.Shared.Salvage.Expeditions.Modifiers;
 
@@ -26,6 +24,6 @@ public sealed partial class SalvageBiomeModPrototype : IPrototype, ISalvageMod
     [DataField("weather")]
     public bool Weather = true;
 
-    [DataField("biome", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
-    public string? BiomePrototype;
+    [DataField("biome", required: true)]
+    public EntProtoId? BiomePrototype;
 }
index d24ec7d72e68e2cbb8b1cf33e54e89e5af39b604..7c2a88a696086b05c4601a8bb3fb5c300182650f 100644 (file)
@@ -1,6 +1,2 @@
-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 c488df231b23eaba080d30df508136800c193922..357bc9f98c6244f2e36efe2ee492eab105de0798 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 a77a6b287b199edd50ea0e44d16637995d784635..f838104f3c3d5b63def98959a6e4a39769845144 100644 (file)
@@ -18,8 +18,9 @@
           lacunarity: 2
 
   # Generate biome
-  - !type:BiomeDunGen
-    biomeTemplate: Asteroid
+  - !type:PrototypeDunGen
+    proto: Asteroid
+    inheritDungeons: All
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
@@ -47,8 +48,9 @@
           lacunarity: 2
 
   # Generate biome
-  - !type:BiomeDunGen
-    biomeTemplate: Asteroid
+  - !type:PrototypeDunGen
+    proto: Asteroid
+    inheritDungeons: All
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
@@ -76,8 +78,9 @@
           cellularDistanceFunction: Euclidean
 
   # Generate biome
-  - !type:BiomeDunGen
-    biomeTemplate: Asteroid
+  - !type:PrototypeDunGen
+    proto: Asteroid
+    inheritDungeons: All
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
           lacunarity: 2
 
   # Generate biome
-  - !type:BiomeDunGen
-    biomeTemplate: Asteroid
+  - !type:PrototypeDunGen
+    proto: Asteroid
+    inheritDungeons: All
   - !type:OreDunGen
     replacement: AsteroidRock
     entity: AsteroidRockGibtonite
index 11c1afdabe9fc42fbcf50c92d252724e32375967..a9bd823e6b0800e68ed2525ffb53922a6747b13c 100644 (file)
@@ -36,8 +36,9 @@
           gain: 0.5
 
     # Generate biome
-    - !type:BiomeDunGen
-      biomeTemplate: SpaceDebris
+    - !type:PrototypeDunGen
+      proto: SpaceDebris
+      inheritDungeons: All
 
 - type: dungeonConfig
   id: ChunkDebrisSmall
@@ -77,5 +78,6 @@
         gain: 0.5
 
   # Generate biome
-  - !type:BiomeDunGen
-    biomeTemplate: SpaceDebris
+  - !type:PrototypeDunGen
+    proto: SpaceDebris
+    inheritDungeons: All
index 47aa47fce4950ca5aaac2264d1fb8ba0f3f1d2cd..e181f892683902a08984b08df7c7d643276e0613 100644 (file)
@@ -1,8 +1,8 @@
 # Asteroid
-- type: biomeTemplate
+- type: dungeonConfig
   id: SpaceDebris
   layers:
-  - !type:BiomeEntityLayer
+  - !type:SampleEntityDunGen
     threshold: 0.20
     noise:
       seed: 0
@@ -23,7 +23,7 @@
     - WallReinforced
     - WallSolid
     - WallSolid
-  - !type:BiomeEntityLayer
+  - !type:SampleEntityDunGen
     threshold: 0.5
     noise:
       seed: 0
@@ -41,7 +41,7 @@
     - Grille
     - Grille
     - GrilleBroken
-  - !type:BiomeDecalLayer
+  - !type:SampleDecalDunGen
     allowedTiles:
     - FloorSteel
     threshold: -0.5
@@ -56,7 +56,7 @@
     - DirtMedium
     - DirtMedium
     - DirtLight
-  - !type:BiomeEntityLayer
+  - !type:SampleEntityDunGen
     threshold: 0.45
     noise:
       seed: 1
@@ -71,7 +71,7 @@
     - FloorSteel
     entities:
     - SalvageSpawnerStructuresVarious
-  - !type:BiomeEntityLayer
+  - !type:SampleEntityDunGen
     allowedTiles:
     - FloorSteel
     - Plating
@@ -85,7 +85,7 @@
     - SalvageSpawnerScrapCommon
     - SalvageSpawnerScrapCommon
     - SalvageSpawnerScrapCommon75
-  - !type:BiomeEntityLayer
+  - !type:SampleEntityDunGen
     allowedTiles:
     - FloorSteel
     - Plating
@@ -97,7 +97,7 @@
     - SalvageSpawnerTreasure
     - SalvageSpawnerTreasure
     - SalvageSpawnerTreasureValuable
-  - !type:BiomeEntityLayer
+  - !type:SampleEntityDunGen
     allowedTiles:
     - FloorSteel
     - Plating
index 5c4fdeb178f23ed52feb5ad9367f4e141a4146c4..ef28b314caa84dc057effc1722dbdc1433e7aa73 100644 (file)
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: Lizards
-  prototype: MobLizard
-  minGroupSize: 3
-  maxGroupSize: 5
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobLizard
+      amount: !type:RangeNumberSelector
+        range: 3, 5
 
-- type: biomeMarkerLayer
+
+- type: dungeonConfig
   id: WatchersLavaland
-  prototype: MobWatcherLavaland
-  minGroupSize: 3
-  maxGroupSize: 3
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobWatcherLavaland
+      amount: !type:RangeNumberSelector
+        range: 3, 3
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: WatchersIcewing
-  prototype: MobWatcherIcewing
-  minGroupSize: 3
-  maxGroupSize: 3
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobWatcherIcewing
+      amount: !type:RangeNumberSelector
+        range: 3, 3
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: WatchersMagmawing
-  prototype: MobWatcherMagmawing
-  minGroupSize: 3
-  maxGroupSize: 3
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobWatcherMagmawing
+      amount: !type:RangeNumberSelector
+        range: 3, 3
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: Cows
-  prototype: MobCow
-  minGroupSize: 1
-  maxGroupSize: 2
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobCow
+      amount: !type:RangeNumberSelector
+        range: 1, 2
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: Chickens
-  prototype: MobChicken
-  minGroupSize: 1
-  maxGroupSize: 2
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobChicken
+      amount: !type:RangeNumberSelector
+        range: 1, 2
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: Pigs
-  prototype: MobPig
-  minGroupSize: 1
-  maxGroupSize: 2
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobPig
+      amount: !type:RangeNumberSelector
+        range: 1, 2
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: Foxes
-  prototype: MobFox
-  minGroupSize: 1
-  maxGroupSize: 1
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobFox
+      amount: !type:RangeNumberSelector
+        range: 1, 1
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: Goats
-  prototype: MobGoat
-  minGroupSize: 1
-  maxGroupSize: 1
-
-# TODO: Needs to be more robust
-- type: biomeMarkerLayer
-  id: Xenos
-  prototype: MobXeno
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobGoat
+      amount: !type:RangeNumberSelector
+        range: 1, 1
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: Carps
-  prototype: MobCarpDungeon
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:EntityTableDunGen
+    table: !type:EntSelector
+      id: MobCarp
+      amount: !type:RangeNumberSelector
+        range: 1, 2
 
+- 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 ae033230b6f6bfdc99509026435913faf69e1459..16b2c7e64f0a9b2cd9ba0ecb989c00b632a96d0d 100644 (file)
 # Low value
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreIron
-  entityMask:
-    AsteroidRock: AsteroidRockTin
-    WallRock: WallRockTin
-    WallRockBasalt: WallRockBasaltTin
-    WallRockChromite: WallRockChromiteTin
-    WallRockSand: WallRockSandTin
-    WallRockSnow: WallRockSnowTin
-  maxCount: 30
-  minGroupSize: 10
-  maxGroupSize: 15
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockTin
+    count: 30
+    minGroupSize: 10
+    maxGroupSize: 15
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreQuartz
-  entityMask:
-    AsteroidRock: AsteroidRockQuartz
-    WallRock: WallRockQuartz
-    WallRockBasalt: WallRockBasaltQuartz
-    WallRockChromite: WallRockChromiteQuartz
-    WallRockSnow: WallRockSnowQuartz
-  maxCount: 30
-  minGroupSize: 10
-  maxGroupSize: 15
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockQuartz
+    count: 30
+    minGroupSize: 10
+    maxGroupSize: 15
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreCoal
-  entityMask:
-    AsteroidRock: AsteroidRockCoal
-    WallRock: WallRockCoal
-    WallRockBasalt: WallRockBasaltCoal
-    WallRockChromite: WallRockChromiteCoal
-    WallRockSand: WallRockSandCoal
-    WallRockSnow: WallRockSnowCoal
-  maxCount: 30
-  minGroupSize: 8
-  maxGroupSize: 12
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockCoal
+    count: 30
+    minGroupSize: 8
+    maxGroupSize: 12
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreSalt
-  entityMask:
-    AsteroidRock: AsteroidRockSalt
-    WallRock: WallRockSalt
-    WallRockBasalt: WallRockBasaltSalt
-    WallRockChromite: WallRockChromiteSalt
-    WallRockSand: WallRockSandSalt
-    WallRockSnow: WallRockSnowSalt
-  maxCount: 30
-  minGroupSize: 8
-  maxGroupSize: 12
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockSalt
+    count: 30
+    minGroupSize: 8
+    maxGroupSize: 12
 
 # Medium value
 # Gold
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreGold
-  entityMask:
-    AsteroidRock: AsteroidRockGold
-    WallRock: WallRockGold
-    WallRockBasalt: WallRockBasaltGold
-    WallRockChromite: WallRockChromiteGold
-    WallRockSand: WallRockSandGold
-    WallRockSnow: WallRockSnowGold
-  maxCount: 20
-  minGroupSize: 5
-  maxGroupSize: 10
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockGold
+    count: 20
+    minGroupSize: 5
+    maxGroupSize: 10
 
 # Silver
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreSilver
-  entityMask:
-    AsteroidRock: AsteroidRockSilver
-    WallRock: WallRockSilver
-    WallRockBasalt: WallRockBasaltSilver
-    WallRockChromite: WallRockChromiteSilver
-    WallRockSand: WallRockSandSilver
-    WallRockSnow: WallRockSnowSilver
-  maxCount: 20
-  minGroupSize: 5
-  maxGroupSize: 10
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockSilver
+    count: 20
+    minGroupSize: 5
+    maxGroupSize: 10
 
 # High value
 # Plasma
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OrePlasma
-  entityMask:
-    AsteroidRock: AsteroidRockPlasma
-    WallRock: WallRockPlasma
-    WallRockBasalt: WallRockBasaltPlasma
-    WallRockChromite: WallRockChromitePlasma
-    WallRockSand: WallRockSandPlasma
-    WallRockSnow: WallRockSnowPlasma
-  maxCount: 12
-  minGroupSize: 4
-  maxGroupSize: 8
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockPlasma
+    count: 12
+    minGroupSize: 4
+    maxGroupSize: 8
 
 # Uranium
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreUranium
-  entityMask:
-    AsteroidRock: AsteroidRockUranium
-    WallRock: WallRockUranium
-    WallRockBasalt: WallRockBasaltUranium
-    WallRockChromite: WallRockChromiteUranium
-    WallRockSand: WallRockSandUranium
-    WallRockSnow: WallRockSnowUranium
-  maxCount: 15
-  minGroupSize: 4
-  maxGroupSize: 8
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockUranium
+    count: 15
+    minGroupSize: 4
+    maxGroupSize: 8
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreDiamond
-  entityMask:
-    AsteroidRock: AsteroidRockDiamond
-    WallRock: WallRockDiamond
-    WallRockBasalt: WallRockBasaltDiamond
-    WallRockChromite: WallRockChromiteDiamond
-    WallRockSand: WallRockSandDiamond
-    WallRockSnow: WallRockSnowDiamond
-  maxCount: 6
-  minGroupSize: 1
-  maxGroupSize: 2
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockDiamond
+    count: 6
+    minGroupSize: 1
+    maxGroupSize: 2
 
 # Artifact Fragment
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreArtifactFragment
-  entityMask:
-    AsteroidRock: AsteroidRockArtifactFragment
-    WallRock: WallRockArtifactFragment
-    WallRockBasalt: WallRockBasaltArtifactFragment
-    WallRockChromite: WallRockChromiteArtifactFragment
-    WallRockSand: WallRockSandArtifactFragment
-    WallRockSnow: WallRockSnowArtifactFragment
-  maxCount: 6
-  minGroupSize: 1
-  maxGroupSize: 2
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockArtifactFragment
+    count: 6
+    minGroupSize: 1
+    maxGroupSize: 2
index 3b0e24244692fc27d0a6b9eeb4c8e9bf525ed156..9af10e3c2b5c748d19e5c0d3a9566d70bb32abfb 100644 (file)
 # Low value
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreIronLow
-  entityMask:
-    AsteroidRock: AsteroidRockTin
-    WallRock: WallRockTin
-    WallRockBasalt: WallRockBasaltTin
-    WallRockChromite: WallRockChromiteTin
-    WallRockSand: WallRockSandTin
-    WallRockSnow: WallRockSnowTin
-  maxCount: 20
-  minGroupSize: 4
-  maxGroupSize: 8
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockTin
+    count: 20
+    minGroupSize: 4
+    maxGroupSize: 8
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreQuartzLow
-  entityMask:
-    AsteroidRock: AsteroidRockQuartz
-    WallRock: WallRockQuartz
-    WallRockBasalt: WallRockBasaltQuartz
-    WallRockChromite: WallRockChromiteQuartz
-    WallRockSnow: WallRockSnowQuartz
-  maxCount: 20
-  minGroupSize: 4
-  maxGroupSize: 8
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockQuartz
+    count: 20
+    minGroupSize: 4
+    maxGroupSize: 8
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreCoalLow
-  entityMask:
-    AsteroidRock: AsteroidRockCoal
-    WallRock: WallRockCoal
-    WallRockBasalt: WallRockBasaltCoal
-    WallRockChromite: WallRockChromiteCoal
-    WallRockSand: WallRockSandCoal
-    WallRockSnow: WallRockSnowCoal
-  maxCount: 20
-  minGroupSize: 4
-  maxGroupSize: 6
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockCoal
+    count: 20
+    minGroupSize: 4
+    maxGroupSize: 6
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreSaltLow
-  entityMask:
-    AsteroidRock: AsteroidRockSalt
-    WallRock: WallRockSalt
-    WallRockBasalt: WallRockBasaltSalt
-    WallRockChromite: WallRockChromiteSalt
-    WallRockSand: WallRockSandSalt
-    WallRockSnow: WallRockSnowSalt
-  maxCount: 15
-  minGroupSize: 4
-  maxGroupSize: 6
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockSalt
+    count: 15
+    minGroupSize: 4
+    maxGroupSize: 6
 
 # Medium value
 # Gold
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreGoldLow
-  entityMask:
-    AsteroidRock: AsteroidRockGold
-    WallRock: WallRockGold
-    WallRockBasalt: WallRockBasaltGold
-    WallRockChromite: WallRockChromiteGold
-    WallRockSand: WallRockSandGold
-    WallRockSnow: WallRockSnowGold
-  maxCount: 10
-  minGroupSize: 2
-  maxGroupSize: 5
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockGold
+    count: 12
+    minGroupSize: 2
+    maxGroupSize: 5
 
 # Silver
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreSilverLow
-  entityMask:
-    AsteroidRock: AsteroidRockSilver
-    WallRock: WallRockSilver
-    WallRockBasalt: WallRockBasaltSilver
-    WallRockChromite: WallRockChromiteSilver
-    WallRockSand: WallRockSandSilver
-    WallRockSnow: WallRockSnowSilver
-  maxCount: 10
-  minGroupSize: 2
-  maxGroupSize: 5
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockSilver
+    count: 12
+    minGroupSize: 2
+    maxGroupSize: 5
 
 # High value
 # Plasma
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OrePlasmaLow
-  entityMask:
-    AsteroidRock: AsteroidRockPlasma
-    WallRock: WallRockPlasma
-    WallRockBasalt: WallRockBasaltPlasma
-    WallRockChromite: WallRockChromitePlasma
-    WallRockSand: WallRockSandPlasma
-    WallRockSnow: WallRockSnowPlasma
-  maxCount: 6
-  minGroupSize: 2
-  maxGroupSize: 4
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockPlasma
+    count: 6
+    minGroupSize: 2
+    maxGroupSize: 4
 
 # Uranium
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreUraniumLow
-  entityMask:
-    AsteroidRock: AsteroidRockUranium
-    WallRock: WallRockUranium
-    WallRockBasalt: WallRockBasaltUranium
-    WallRockChromite: WallRockChromiteUranium
-    WallRockSand: WallRockSandUranium
-    WallRockSnow: WallRockSnowUranium
-  maxCount: 7
-  minGroupSize: 2
-  maxGroupSize: 4
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockUranium
+    count: 7
+    minGroupSize: 2
+    maxGroupSize: 4
 
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreDiamondLow
-  entityMask:
-    AsteroidRock: AsteroidRockDiamond
-    WallRock: WallRockDiamond
-    WallRockBasalt: WallRockBasaltDiamond
-    WallRockChromite: WallRockChromiteDiamond
-    WallRockSand: WallRockSandDiamond
-    WallRockSnow: WallRockSnowDiamond
-  maxCount: 3
-  minGroupSize: 1
-  maxGroupSize: 2
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockDiamond
+    count: 3
+    minGroupSize: 1
+    maxGroupSize: 2
 
 # Artifact Fragment
-- type: biomeMarkerLayer
+- type: dungeonConfig
   id: OreArtifactFragmentLow
-  entityMask:
-    AsteroidRock: AsteroidRockArtifactFragment
-    WallRock: WallRockArtifactFragment
-    WallRockBasalt: WallRockBasaltArtifactFragment
-    WallRockChromite: WallRockChromiteArtifactFragment
-    WallRockSand: WallRockSandArtifactFragment
-    WallRockSnow: WallRockSnowArtifactFragment
-  maxCount: 3
-  minGroupSize: 1
-  maxGroupSize: 2
-  radius: 4
+  layers:
+  - !type:ChunkDunGen
+    size: 128
+  - !type:OreDunGen
+    replacement: WallRock
+    entity: WallRockArtifactFragment
+    count: 3
+    minGroupSize: 1
+    maxGroupSize: 2
index d630ebad489fbd6a6986d12f7f782f812e83c8d3..747660dc8fb258f3821cce394741c8efa41a9261 100644 (file)
-# Contains several biomes
-- type: biomeTemplate
-  id: Continental
+# Desert
+- type: entity
+  id: BiomeLowDesert
+  categories: [ HideSpawnMenu ]
+  components:
+  - type: Biome
+    layers:
+      terrain:
+        dungeon: LowDesertTerrain
+
+- type: dungeonConfig
+  id: LowDesertTerrain
   layers:
-    - !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:ChunkDunGen
+  - !type:PrototypeDunGen
+    proto: LowDesertTiles
+    inheritDungeons: All
+  - !type:PrototypeDunGen
+    proto: LowDesertEntities
+    inheritDungeons: All
 
-# Desert
-# TODO: Water in desert
-- type: biomeTemplate
-  id: LowDesert
+- type: dungeonConfig
+  id: LowDesertTiles
+  returnReserved: false
   layers:
-    - !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:SampleTileDunGen
+    threshold: -1
+    tile: FloorAsteroidSand
 
-# Grass
-- type: biomeTemplate
-  id: Grasslands
+- type: dungeonConfig
+  id: LowDesertEntities
+  reserveTiles: true
   layers:
-    # 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
+  - !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: dungeonConfig
+  id: GrasslandsEntities
+  reserveTiles: true
+  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: dungeonConfig
+  id: GrasslandsDecals
+  returnReserved: false
+  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
 
 # Lava
-- type: biomeTemplate
-  id: Lava
+- type: entity
+  id: BiomeLava
+  categories: [ HideSpawnMenu ]
+  components:
+  - type: Biome
+    layers:
+      terrain:
+        dungeon: LavaTerrain
+
+- type: dungeonConfig
+  id: LavaTerrain
   layers:
-    - !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
+  - !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
+  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
 
 # Snow
-- type: biomeTemplate
-  id: Snow # Similar to Grasslands... but snow
+- type: entity
+  id: BiomeSnow
+  categories: [ HideSpawnMenu ]
+  components:
+  - type: Biome
+    layers:
+      terrain:
+        dungeon: SnowTerrain
+
+- type: dungeonConfig
+  id: SnowTerrain
   layers:
-    # 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
+  - !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
+  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
 
 # Shadow -> Derived from lava
-- type: biomeTemplate
-  id: Shadow
+- type: entity
+  id: BiomeShadow
+  categories: [ HideSpawnMenu ]
+  components:
+  - type: Biome
+    layers:
+      terrain:
+        dungeon: ShadowTerrain
+
+- type: dungeonConfig
+  id: ShadowTerrain
   layers:
-    - !type:BiomeEntityLayer
+  - !type:ChunkDunGen
+  - !type:PrototypeDunGen
+    proto: ShadowTiles
+    inheritDungeons: All
+  - !type:PrototypeDunGen
+    proto: ShadowEntities
+    inheritDungeons: All
+
+- type: dungeonConfig
+  id: ShadowEntities
+  reserveTiles: true
+  layers:
+    - !type:SampleEntityDunGen
       threshold: 0.70
       noise:
         frequency: 1
         - ShadowBasaltThree
         - ShadowBasaltFour
         - ShadowBasaltFive
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       threshold: 0.97
       noise:
         frequency: 1
         - FloorChromite
       entities:
         - CrystalPink
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       threshold: 0.97
       noise:
         seed: 1
       entities:
         - ShadowTree
     # Rock formations
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       threshold: -0.2
       invert: true
       noise:
       entities:
         - WallRockChromite
     # chasm time
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       allowedTiles:
       - FloorChromite
       threshold: 0.2
         gain: 0.4
       entities:
       - FloorChromiteChasm
-    - !type:BiomeDummyLayer
-      id: Loot
-    # Fill chromite
-    - !type:BiomeTileLayer
-      threshold: -1
-      tile: FloorChromite
+
+- type: dungeonConfig
+  id: ShadowTiles
+  returnReserved: false
+  layers:
+  # Fill chromite
+  - !type:SampleTileDunGen
+    threshold: -1
+    tile: FloorChromite
 
 # Caves
-- type: biomeTemplate
-  id: 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
   layers:
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       threshold: 0.85
       noise:
         seed: 2
         - CrystalBlue
         - CrystalYellow
         - CrystalCyan
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       threshold: 0.95
       noise:
         seed: 1
       - FloorAsteroidSand
       entities:
       - FloraStalagmite
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       threshold: -0.5
       invert: true
       noise:
         - FloorAsteroidSand
       entities:
         - WallRock
-    - !type:BiomeDummyLayer
-      id: Loot
-    - !type:BiomeTileLayer
-      threshold: -1.0
-      tile: FloorAsteroidSand
+
+- type: dungeonConfig
+  id: CavesTiles
+  returnReserved: false
+  layers:
+  - !type:SampleTileDunGen
+    threshold: -1.0
+    tile: FloorAsteroidSand
 
 # Asteroid
-- type: biomeTemplate
+- type: dungeonConfig
   id: Asteroid
   layers:
-    - !type:BiomeEntityLayer
+    - !type:SampleTileDunGen
+      threshold: -1.0
+      tile: FloorAsteroidSand
+      reserveTiles: false
+    - !type:SampleEntityDunGen
       threshold: 0.85
       noise:
         seed: 2
         - CrystalBlue
         - CrystalYellow
         - CrystalCyan
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
       threshold: 0.95
       noise:
         seed: 1
         - FloorAsteroidSand
       entities:
         - FloraStalagmite
-    - !type:BiomeEntityLayer
+    - !type:SampleEntityDunGen
+      reserveTiles: false
       threshold: -0.6
       invert: true
       noise:
         - FloorAsteroidSand
       entities:
         - AsteroidRock
-    - !type:BiomeTileLayer
-      threshold: -1.0
-      tile: FloorAsteroidSand
index 5da95929952f3ae246906e2a90393b0e349785c1..18d50ec6cd6a3dc90a859dd3ffee1d13de0ef79b 100644 (file)
@@ -79,6 +79,9 @@
   id: Haunted
   layers:
   - !type:PrefabDunGen
+    roomWhitelist:
+      tags:
+      - Haunted
     presets:
     - Bucket
     - Wow
index cf236019783262a25fd73b83cee45691802f0e0b..289fa5fd573e0f4eb7c0a6217ffa0d4aead98ee5 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:BiomeMarkerLoot
-      proto: OreIron
+  - !type:BiomeLoot
+    proto: OreIron
 
 - type: salvageLoot
   id: OreCoal
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreCoal
+  - !type:BiomeLoot
+    proto: OreCoal
 
 - type: salvageLoot
   id: OreQuartz
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreQuartz
+  - !type:BiomeLoot
+    proto: OreQuartz
 
 - type: salvageLoot
   id: OreSalt
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreSalt
+  - !type:BiomeLoot
+    proto: OreSalt
 
 # - Medium value
 - type: salvageLoot
   id: OreGold
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreGold
+  - !type:BiomeLoot
+    proto: OreGold
 
 - type: salvageLoot
   id: OreSilver
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreSilver
+  - !type:BiomeLoot
+    proto: OreSilver
 
 # - High value
 - type: salvageLoot
   id: OrePlasma
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OrePlasma
+  - !type:BiomeLoot
+    proto: OrePlasma
 
 - type: salvageLoot
   id: OreUranium
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreUranium
+  - !type:BiomeLoot
+    proto: OreUranium
 
 - type: salvageLoot
   id: OreDiamond
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreDiamond
+  - !type:BiomeLoot
+    proto: OreDiamond
 
 - type: salvageLoot
   id: OreArtifactFragment
   guaranteed: true
   loots:
-    - !type:BiomeMarkerLoot
-      proto: OreArtifactFragment
+  - !type:BiomeLoot
+    proto: OreArtifactFragment
index ca64d29a52acffbe375052a235cc7002b33f1796..e8b18dd14a86b6c71bb5476614e3f2bb988d91aa 100644 (file)
@@ -8,24 +8,24 @@
 - type: salvageBiomeMod
   id: Caves
   desc: salvage-biome-mod-caves
-  biome: Caves
+  biome: BiomeCaves
 
 - type: salvageBiomeMod
   id: Grasslands
   desc: salvage-biome-mod-grasslands
-  biome: Grasslands
+  biome: BiomeGrasslands
 
 - type: salvageBiomeMod
   id: Snow
   desc: salvage-biome-mod-snow
   cost: 1
-  biome: Snow
+  biome: BiomeSnow
 
 - type: salvageBiomeMod
   id: Lava
   desc: salvage-biome-mod-lava
   cost: 2
-  biome: Lava
+  biome: BiomeLava
 
 #- type: salvageBiomeMod
 #  id: Space
index 0caa9f0e1f57d67d1d8810e6718207fbb592921f..ad759721cb97ec6d02a3630a9d2930ba4b94176d 100644 (file)
 
 - type: dungeonConfig
   id: VGRoidSmaller
-  minOffset: 40
-  maxOffset: 60
+  minOffset: 60
+  maxOffset: 80
   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