Biome rework (#37735)

* 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>
This commit is contained in:
metalgearsloth
2025-07-03 14:36:06 +10:00
committed by GitHub
parent fdf3d6715e
commit fe7b96147c
116 changed files with 3005 additions and 3537 deletions

View 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;
}
}