Rooms demiplane generation improvement (#1583)

* Add CP14RoomsDunGen for procedural room placement

Introduces the CP14RoomsDunGen layer for generating rooms across the grid based on tags and tile masks. Updates DungeonJob and DungeonSystem to support the new layer, and modifies relevant YAML prototypes to use CP14RoomsDunGen instead of OreDunGen for room spawning in demiplane modifiers.

* some fixes

* its done done done

* exit room

* additional entry points

* fix

* Update migration.yml
This commit is contained in:
Red
2025-07-27 16:45:16 +03:00
committed by GitHub
parent 5020be8ec6
commit fcbe2cce6b
22 changed files with 2606 additions and 291 deletions

View File

@@ -220,6 +220,9 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
case CP14OreDunGen cp14OreDunGen:
await PostGen(cp14OreDunGen, dungeons, reservedTiles, random);
break;
case CP14RoomsDunGen cp14RoomsDunGen:
await PostGen(cp14RoomsDunGen, dungeons, reservedTiles, random);
break;
//CP14 zone end
case AutoCablingDunGen cabling:
await PostGen(cabling, dungeons[^1], reservedTiles, random);

View File

@@ -78,7 +78,7 @@ public sealed partial class DungeonSystem
Vector2i origin,
DungeonRoomPrototype room,
Random random,
HashSet<Vector2i>? reservedTiles,
HashSet<Vector2i>? reservedTiles = null, //CP14 default null
bool clearExisting = false,
bool rotation = false)
{
@@ -129,29 +129,6 @@ public sealed partial class DungeonSystem
var finalRoomRotation = roomTransform.Rotation();
// go BRRNNTTT on existing stuff
if (clearExisting)
{
var gridBounds = new Box2(Vector2.Transform(-room.Size/2, roomTransform), Vector2.Transform(room.Size/2, roomTransform));
_entitySet.Clear();
// Polygon skin moment
gridBounds = gridBounds.Enlarged(-0.05f);
_lookup.GetLocalEntitiesIntersecting(gridUid, gridBounds, _entitySet, LookupFlags.Uncontained);
foreach (var templateEnt in _entitySet)
{
Del(templateEnt);
}
if (TryComp(gridUid, out DecalGridComponent? decalGrid))
{
foreach (var decal in _decals.GetDecalsIntersecting(gridUid, gridBounds, decalGrid))
{
_decals.RemoveDecal(gridUid, decal.Index, decalGrid);
}
}
}
var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
var tileOffset = -roomCenter + grid.TileSizeHalfVector;
_tiles.Clear();

View File

@@ -20,62 +20,59 @@ public sealed partial class DungeonJob
HashSet<Vector2i> reservedTiles,
Random random)
{
// Doesn't use dungeon data because layers and we don't need top-down support at the moment.
var replaceEntities = new Dictionary<Vector2i, EntityUid>();
var availableTiles = new List<Vector2i>();
var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
// WARNING:
// This DunGen handles not only the tiles of the passed dungeon, but ALL the tiles of the current grid
// So don't run it anywhere
while (tiles.MoveNext(out var tileRef))
foreach (var dun in dungeons)
{
var tile = tileRef.Value.GridIndices;
//Tile mask filtering
if (gen.TileMask is not null)
foreach (var tile in dun.AllTiles)
{
if (!gen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Value.Tile.TypeId]).ID))
continue;
}
var tileRef = _maps.GetTileRef(_gridUid, _grid, tile);
//Entity mask filtering
if (gen.EntityMask is not null)
{
var found = false;
var enumerator2 = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile);
while (enumerator2.MoveNext(out var uid))
//Tile mask filtering
if (gen.TileMask is not null)
{
var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
if (prototype?.ID is null)
if (!gen.TileMask.Contains(((ContentTileDefinition) _tileDefManager[tileRef.Tile.TypeId]).ID))
continue;
if (!gen.EntityMask.Contains(prototype.ID))
continue;
replaceEntities[tile] = uid.Value;
found = true;
}
if (!found)
continue;
//Entity mask filtering
if (gen.EntityMask is not null)
{
var found = false;
var enumerator2 = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile);
while (enumerator2.MoveNext(out var uid))
{
var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
if (prototype?.ID is null)
continue;
if (!gen.EntityMask.Contains(prototype.ID))
continue;
replaceEntities[tile] = uid.Value;
found = true;
}
if (!found)
continue;
}
else
{
//If entity mask null - we ignore the tiles that have anything on them.
if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
continue;
}
// Add it to valid nodes.
availableTiles.Add(tile);
await SuspendDungeon();
if (!ValidateResume())
return;
}
else
{
//If entity mask null - we ignore the tiles that have anything on them.
if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
continue;
}
// Add it to valid nodes.
availableTiles.Add(tile);
await SuspendDungeon();
if (!ValidateResume())
return;
}
var remapping = new Dictionary<EntProtoId, EntProtoId>();
@@ -132,9 +129,9 @@ public sealed partial class DungeonJob
var prototype = gen.Entity;
if (replaceEntities.TryGetValue(node, out var existingEnt))
if (replaceEntities.TryGetValue(node, out var existingEnt) && _entManager.TryGetComponent<MetaDataComponent>(existingEnt, out var metaData))
{
var existingProto = _entManager.GetComponent<MetaDataComponent>(existingEnt).EntityPrototype;
var existingProto = metaData.EntityPrototype;
_entManager.DeleteEntity(existingEnt);
if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))

View File

@@ -0,0 +1,114 @@
using System.Threading.Tasks;
using Content.Shared.Maps;
using Content.Shared.Procedural;
using Content.Shared.Procedural.DungeonLayers;
using Robust.Shared.Random;
namespace Content.Server.Procedural.DungeonJob;
public sealed partial class DungeonJob
{
/// <summary>
/// <see cref="OreDunGen"/>
/// </summary>
private async Task PostGen(
CP14RoomsDunGen gen,
List<Dungeon> dungeons,
HashSet<Vector2i> reservedTiles,
Random random)
{
List<DungeonRoomPrototype> availableRooms = new();
var availableTiles = new List<Vector2i>();
foreach (var room in _prototype.EnumeratePrototypes<DungeonRoomPrototype>())
{
foreach (var tag in room.Tags)
{
if (gen.Tags.Contains(tag))
{
availableRooms.Add(room);
break;
}
}
}
if (availableRooms.Count == 0)
return;
foreach (var dun in dungeons)
{
foreach (var tile in dun.AllTiles)
{
var tileRef = _maps.GetTileRef(_gridUid, _grid, tile);
if (reservedTiles.Contains(tile))
continue;
//Tile mask filtering
if (gen.TileMask is not null)
{
if (!gen.TileMask.Contains(((ContentTileDefinition)_tileDefManager[tileRef.Tile.TypeId]).ID))
continue;
}
else
{
//If entity mask null - we ignore the tiles that have anything on them.
if (!_anchorable.TileFree((_gridUid, _grid),
tile,
DungeonSystem.CollisionLayer,
DungeonSystem.CollisionMask))
continue;
}
// Add it to valid nodes.
availableTiles.Add(tile);
await SuspendDungeon();
if (!ValidateResume())
return;
}
}
for (var i = 0; i < gen.Count && availableTiles.Count > 0; i++)
{
await SuspendDungeon();
if (!ValidateResume())
return;
var selectedTile = random.PickAndTake(availableTiles);
availableTiles.Remove(selectedTile);
var room = random.Pick(availableRooms);
var roomBounds = new List<Vector2i>();
var conflict = false;
for (int x = 0; x < room.Size.X; x++)
{
for (int y = 0; y < room.Size.Y; y++)
{
var pos = selectedTile + new Vector2i(x, y);
if (reservedTiles.Contains(pos))
{
conflict = true;
break;
}
roomBounds.Add(pos);
}
if (conflict) break;
}
if (conflict)
continue;
_dungeon.SpawnRoom(_gridUid, _grid, selectedTile, room, random, clearExisting: true, rotation: true);
foreach (var pos in roomBounds)
{
reservedTiles.Add(pos);
}
}
}
}