Haunted dungeon template (#23768)
* haunted dungeon * Initial work Still needs prefab gen work to make it interesting. * ime a worm * weh * Work * Slight tweaks --------- Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
This commit is contained in:
53
Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs
Normal file
53
Content.Server/Procedural/DungeonJob.CorridorClutterPost.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Procedural;
|
||||||
|
using Content.Shared.Procedural.PostGeneration;
|
||||||
|
using Content.Shared.Storage;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Physics.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
|
||||||
|
namespace Content.Server.Procedural;
|
||||||
|
|
||||||
|
public sealed partial class DungeonJob
|
||||||
|
{
|
||||||
|
private async Task PostGen(CorridorClutterPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid,
|
||||||
|
Random random)
|
||||||
|
{
|
||||||
|
var physicsQuery = _entManager.GetEntityQuery<PhysicsComponent>();
|
||||||
|
var count = (int) Math.Ceiling(dungeon.CorridorTiles.Count * gen.Chance);
|
||||||
|
|
||||||
|
while (count > 0)
|
||||||
|
{
|
||||||
|
var tile = random.Pick(dungeon.CorridorTiles);
|
||||||
|
|
||||||
|
var enumerator = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile);
|
||||||
|
var blocked = false;
|
||||||
|
|
||||||
|
while (enumerator.MoveNext(out var ent))
|
||||||
|
{
|
||||||
|
if (!physicsQuery.TryGetComponent(ent, out var physics) ||
|
||||||
|
!physics.CanCollide ||
|
||||||
|
!physics.Hard)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
blocked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocked)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
count--;
|
||||||
|
|
||||||
|
var protos = EntitySpawnCollection.GetSpawns(gen.Contents, random);
|
||||||
|
var coords = _maps.ToCenterCoordinates(_gridUid, tile, _grid);
|
||||||
|
_entManager.SpawnEntities(coords, protos);
|
||||||
|
await SuspendIfOutOfTime();
|
||||||
|
|
||||||
|
if (!ValidateResume())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ public sealed partial class DungeonJob
|
|||||||
|
|
||||||
private bool HasWall(MapGridComponent grid, Vector2i tile)
|
private bool HasWall(MapGridComponent grid, Vector2i tile)
|
||||||
{
|
{
|
||||||
var anchored = grid.GetAnchoredEntitiesEnumerator(tile);
|
var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile);
|
||||||
|
|
||||||
while (anchored.MoveNext(out var uid))
|
while (anchored.MoveNext(out var uid))
|
||||||
{
|
{
|
||||||
@@ -52,7 +52,7 @@ public sealed partial class DungeonJob
|
|||||||
// Gather existing nodes
|
// Gather existing nodes
|
||||||
foreach (var tile in allTiles)
|
foreach (var tile in allTiles)
|
||||||
{
|
{
|
||||||
var anchored = grid.GetAnchoredEntitiesEnumerator(tile);
|
var anchored = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, tile);
|
||||||
|
|
||||||
while (anchored.MoveNext(out var anc))
|
while (anchored.MoveNext(out var anc))
|
||||||
{
|
{
|
||||||
@@ -186,7 +186,9 @@ public sealed partial class DungeonJob
|
|||||||
// - Tiles first
|
// - Tiles first
|
||||||
foreach (var neighbor in dungeon.RoomExteriorTiles)
|
foreach (var neighbor in dungeon.RoomExteriorTiles)
|
||||||
{
|
{
|
||||||
if (dungeon.RoomTiles.Contains(neighbor))
|
DebugTools.Assert(!dungeon.RoomTiles.Contains(neighbor));
|
||||||
|
|
||||||
|
if (dungeon.Entrances.Contains(neighbor))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
|
if (!_anchorable.TileFree(grid, neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
|
||||||
@@ -265,7 +267,6 @@ public sealed partial class DungeonJob
|
|||||||
Random random)
|
Random random)
|
||||||
{
|
{
|
||||||
var physicsQuery = _entManager.GetEntityQuery<PhysicsComponent>();
|
var physicsQuery = _entManager.GetEntityQuery<PhysicsComponent>();
|
||||||
var tagQuery = _entManager.GetEntityQuery<TagComponent>();
|
|
||||||
|
|
||||||
foreach (var tile in dungeon.CorridorTiles)
|
foreach (var tile in dungeon.CorridorTiles)
|
||||||
{
|
{
|
||||||
@@ -771,7 +772,7 @@ public sealed partial class DungeonJob
|
|||||||
{
|
{
|
||||||
for (var y = -expansion; y <= expansion; y++)
|
for (var y = -expansion; y <= expansion; y++)
|
||||||
{
|
{
|
||||||
var neighbor = new Vector2i(tile.X + x, tile.Y + y);
|
var neighbor = new Vector2(tile.X + x, tile.Y + y).Floored();
|
||||||
|
|
||||||
if (dungeon.RoomTiles.Contains(neighbor) ||
|
if (dungeon.RoomTiles.Contains(neighbor) ||
|
||||||
dungeon.RoomExteriorTiles.Contains(neighbor) ||
|
dungeon.RoomExteriorTiles.Contains(neighbor) ||
|
||||||
@@ -817,6 +818,52 @@ public sealed partial class DungeonJob
|
|||||||
return mod;
|
return mod;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
WidenCorridor(dungeon, gen.Width, corridorTiles);
|
||||||
|
|
||||||
|
var setTiles = new List<(Vector2i, Tile)>();
|
||||||
|
var tileDef = _prototype.Index(gen.Tile);
|
||||||
|
|
||||||
|
foreach (var tile in corridorTiles)
|
||||||
|
{
|
||||||
|
setTiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.SetTiles(setTiles);
|
||||||
|
dungeon.CorridorTiles.UnionWith(corridorTiles);
|
||||||
|
BuildCorridorExterior(dungeon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildCorridorExterior(Dungeon dungeon)
|
||||||
|
{
|
||||||
|
var exterior = dungeon.CorridorExteriorTiles;
|
||||||
|
|
||||||
|
// Just ignore entrances or whatever for now.
|
||||||
|
foreach (var tile in dungeon.CorridorTiles)
|
||||||
|
{
|
||||||
|
for (var x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (var y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
var neighbor = new Vector2i(tile.X + x, tile.Y + y);
|
||||||
|
|
||||||
|
if (dungeon.CorridorTiles.Contains(neighbor) ||
|
||||||
|
dungeon.RoomExteriorTiles.Contains(neighbor) ||
|
||||||
|
dungeon.RoomTiles.Contains(neighbor) ||
|
||||||
|
dungeon.Entrances.Contains(neighbor))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
exterior.Add(neighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WidenCorridor(Dungeon dungeon, float width, ICollection<Vector2i> corridorTiles)
|
||||||
|
{
|
||||||
|
var expansion = width - 2;
|
||||||
|
|
||||||
// Widen the path
|
// Widen the path
|
||||||
if (expansion >= 1)
|
if (expansion >= 1)
|
||||||
{
|
{
|
||||||
@@ -831,7 +878,7 @@ public sealed partial class DungeonJob
|
|||||||
{
|
{
|
||||||
for (var y = -expansion; y <= expansion; y++)
|
for (var y = -expansion; y <= expansion; y++)
|
||||||
{
|
{
|
||||||
var neighbor = new Vector2i(node.X + x, node.Y + y);
|
var neighbor = new Vector2(node.X + x, node.Y + y).Floored();
|
||||||
|
|
||||||
// Diagonals still matter here.
|
// Diagonals still matter here.
|
||||||
if (dungeon.RoomTiles.Contains(neighbor) ||
|
if (dungeon.RoomTiles.Contains(neighbor) ||
|
||||||
@@ -852,36 +899,6 @@ public sealed partial class DungeonJob
|
|||||||
corridorTiles.Add(node);
|
corridorTiles.Add(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var setTiles = new List<(Vector2i, Tile)>();
|
|
||||||
var tileDef = _prototype.Index(gen.Tile);
|
|
||||||
|
|
||||||
foreach (var tile in corridorTiles)
|
|
||||||
{
|
|
||||||
setTiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
|
|
||||||
}
|
|
||||||
|
|
||||||
grid.SetTiles(setTiles);
|
|
||||||
dungeon.CorridorTiles.UnionWith(corridorTiles);
|
|
||||||
|
|
||||||
var exterior = dungeon.CorridorExteriorTiles;
|
|
||||||
|
|
||||||
// Just ignore entrances or whatever for now.
|
|
||||||
foreach (var tile in dungeon.CorridorTiles)
|
|
||||||
{
|
|
||||||
for (var x = -1; x <= 1; x++)
|
|
||||||
{
|
|
||||||
for (var y = -1; y <= 1; y++)
|
|
||||||
{
|
|
||||||
var neighbor = new Vector2i(tile.X + x, tile.Y + y);
|
|
||||||
|
|
||||||
if (dungeon.CorridorTiles.Contains(neighbor))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
exterior.Add(neighbor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PostGen(EntranceFlankPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid,
|
private async Task PostGen(EntranceFlankPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid,
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ public sealed partial class DungeonJob
|
|||||||
var dungeonRotation = _dungeon.GetDungeonRotation(seed);
|
var dungeonRotation = _dungeon.GetDungeonRotation(seed);
|
||||||
var dungeonTransform = Matrix3.CreateTransform(_position, dungeonRotation);
|
var dungeonTransform = Matrix3.CreateTransform(_position, dungeonRotation);
|
||||||
var roomPackProtos = new Dictionary<Vector2i, List<DungeonRoomPackPrototype>>();
|
var roomPackProtos = new Dictionary<Vector2i, List<DungeonRoomPackPrototype>>();
|
||||||
var fallbackTile = new Tile(_tileDefManager[prefab.Tile].TileId);
|
|
||||||
|
|
||||||
foreach (var pack in _prototype.EnumeratePrototypes<DungeonRoomPackPrototype>())
|
foreach (var pack in _prototype.EnumeratePrototypes<DungeonRoomPackPrototype>())
|
||||||
{
|
{
|
||||||
@@ -325,6 +324,7 @@ public sealed partial class DungeonJob
|
|||||||
}
|
}
|
||||||
|
|
||||||
room.Entrances.Add(entrancePos);
|
room.Entrances.Add(entrancePos);
|
||||||
|
dungeon.Entrances.Add(entrancePos);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
193
Content.Server/Procedural/DungeonJob.WormPost.cs
Normal file
193
Content.Server/Procedural/DungeonJob.WormPost.cs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Procedural;
|
||||||
|
using Content.Shared.Procedural.PostGeneration;
|
||||||
|
using Robust.Shared.Collections;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Map.Components;
|
||||||
|
using Robust.Shared.Random;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Server.Procedural;
|
||||||
|
|
||||||
|
public sealed partial class DungeonJob
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to connect rooms via worm-like corridors.
|
||||||
|
/// </summary>
|
||||||
|
private async Task PostGen(WormCorridorPostGen gen, Dungeon dungeon, EntityUid gridUid, MapGridComponent grid, Random random)
|
||||||
|
{
|
||||||
|
var networks = new List<(Vector2i Start, HashSet<Vector2i> Network)>();
|
||||||
|
|
||||||
|
// List of places to start from.
|
||||||
|
var worm = new ValueList<Vector2i>();
|
||||||
|
var startAngles = new Dictionary<Vector2i, Angle>();
|
||||||
|
|
||||||
|
foreach (var room in dungeon.Rooms)
|
||||||
|
{
|
||||||
|
foreach (var entrance in room.Entrances)
|
||||||
|
{
|
||||||
|
var network = new HashSet<Vector2i> { entrance };
|
||||||
|
networks.Add((entrance, network));
|
||||||
|
|
||||||
|
// Point away from the room to start with.
|
||||||
|
startAngles.Add(entrance, (entrance + grid.TileSizeHalfVector - room.Center).ToAngle());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// There's a lot of ways to handle this, e.g. pathfinding towards each room
|
||||||
|
// For simplicity we'll go through each entrance randomly and generate worms from it
|
||||||
|
// then as a final step we will connect all of their networks.
|
||||||
|
random.Shuffle(networks);
|
||||||
|
|
||||||
|
for (var i = 0; i < gen.Count; i++)
|
||||||
|
{
|
||||||
|
// Find a random network to worm from.
|
||||||
|
var startIndex = (i % networks.Count);
|
||||||
|
var startPos = networks[startIndex].Start;
|
||||||
|
var position = startPos + grid.TileSizeHalfVector;
|
||||||
|
|
||||||
|
var remainingLength = gen.Length;
|
||||||
|
worm.Clear();
|
||||||
|
var angle = startAngles[startPos];
|
||||||
|
|
||||||
|
for (var x = remainingLength; x >= 0; x--)
|
||||||
|
{
|
||||||
|
position += angle.ToVec();
|
||||||
|
angle += random.NextAngle(-gen.MaxAngleChange, gen.MaxAngleChange);
|
||||||
|
var roundedPos = position.Floored();
|
||||||
|
|
||||||
|
// Check if the tile doesn't overlap something it shouldn't
|
||||||
|
if (dungeon.RoomTiles.Contains(roundedPos) ||
|
||||||
|
dungeon.RoomExteriorTiles.Contains(roundedPos))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
worm.Add(roundedPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uhh yeah.
|
||||||
|
if (worm.Count == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a random part on the existing worm to start.
|
||||||
|
var value = random.Pick(worm);
|
||||||
|
networks[startIndex].Network.UnionWith(worm);
|
||||||
|
startAngles[value] = random.NextAngle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now to ensure they all connect we'll pathfind each network to one another
|
||||||
|
// Simple BFS pathfinder
|
||||||
|
var main = networks[0];
|
||||||
|
|
||||||
|
var frontier = new PriorityQueue<Vector2i, float>();
|
||||||
|
var cameFrom = new Dictionary<Vector2i, Vector2i>();
|
||||||
|
var costSoFar = new Dictionary<Vector2i, float>();
|
||||||
|
|
||||||
|
// How many times we try to patch the networks together
|
||||||
|
var attempts = 3;
|
||||||
|
|
||||||
|
for (var attempt = 0; attempt < attempts; attempt++)
|
||||||
|
{
|
||||||
|
// Skip index 0
|
||||||
|
for (var i = networks.Count - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
cameFrom.Clear();
|
||||||
|
frontier.Clear();
|
||||||
|
costSoFar.Clear();
|
||||||
|
|
||||||
|
var targetNode = random.Pick(main.Network);
|
||||||
|
|
||||||
|
var other = networks[i];
|
||||||
|
var startNode = other.Network.First();
|
||||||
|
frontier.Enqueue(startNode, 0f);
|
||||||
|
costSoFar[startNode] = 0f;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
await SuspendIfOutOfTime();
|
||||||
|
if (!ValidateResume())
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (frontier.TryDequeue(out var node, out _) && count < gen.PathLimit)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
|
||||||
|
// Found
|
||||||
|
if (main.Network.Contains(node))
|
||||||
|
{
|
||||||
|
// found, rebuild
|
||||||
|
frontier.Clear();
|
||||||
|
main.Network.Add(node);
|
||||||
|
main.Network.UnionWith(other.Network);
|
||||||
|
var target = node;
|
||||||
|
|
||||||
|
// Rebuild
|
||||||
|
while (cameFrom.TryGetValue(target, out var source))
|
||||||
|
{
|
||||||
|
target = source;
|
||||||
|
main.Network.Add(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
networks.RemoveSwap(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var x = -1; x <= 1; x++)
|
||||||
|
{
|
||||||
|
for (var y = -1; y <= 1; y++)
|
||||||
|
{
|
||||||
|
if (x == 0 && y == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var neighbor = node + new Vector2i(x, y);
|
||||||
|
|
||||||
|
// Exclude room tiles.
|
||||||
|
if (dungeon.RoomTiles.Contains(neighbor) ||
|
||||||
|
dungeon.RoomExteriorTiles.Contains(neighbor))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tileCost = (neighbor - node).Length;
|
||||||
|
var gScore = costSoFar[node] + tileCost;
|
||||||
|
|
||||||
|
if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cameFrom[neighbor] = node;
|
||||||
|
costSoFar[neighbor] = gScore;
|
||||||
|
var hScore = (targetNode - neighbor).Length + gScore;
|
||||||
|
|
||||||
|
frontier.Enqueue(neighbor, hScore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WidenCorridor(dungeon, gen.Width, main.Network);
|
||||||
|
dungeon.CorridorTiles.UnionWith(main.Network);
|
||||||
|
BuildCorridorExterior(dungeon);
|
||||||
|
|
||||||
|
var tiles = new List<(Vector2i Index, Tile Tile)>();
|
||||||
|
var tileDef = _prototype.Index(gen.Tile);
|
||||||
|
|
||||||
|
foreach (var tile in dungeon.CorridorTiles)
|
||||||
|
{
|
||||||
|
tiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var tile in dungeon.CorridorExteriorTiles)
|
||||||
|
{
|
||||||
|
tiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_maps.SetTiles(_gridUid, _grid, tiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,6 +126,9 @@ public sealed partial class DungeonJob : Job<Dungeon>
|
|||||||
case CornerClutterPostGen clutter:
|
case CornerClutterPostGen clutter:
|
||||||
await PostGen(clutter, dungeon, _gridUid, _grid, random);
|
await PostGen(clutter, dungeon, _gridUid, _grid, random);
|
||||||
break;
|
break;
|
||||||
|
case CorridorClutterPostGen corClutter:
|
||||||
|
await PostGen(corClutter, dungeon, _gridUid, _grid, random);
|
||||||
|
break;
|
||||||
case CorridorPostGen cordor:
|
case CorridorPostGen cordor:
|
||||||
await PostGen(cordor, dungeon, _gridUid, _grid, random);
|
await PostGen(cordor, dungeon, _gridUid, _grid, random);
|
||||||
break;
|
break;
|
||||||
@@ -159,6 +162,9 @@ public sealed partial class DungeonJob : Job<Dungeon>
|
|||||||
case WallMountPostGen wall:
|
case WallMountPostGen wall:
|
||||||
await PostGen(wall, dungeon, _gridUid, _grid, random);
|
await PostGen(wall, dungeon, _gridUid, _grid, random);
|
||||||
break;
|
break;
|
||||||
|
case WormCorridorPostGen worm:
|
||||||
|
await PostGen(worm, dungeon, _gridUid, _grid, random);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ public sealed class Dungeon
|
|||||||
|
|
||||||
public readonly HashSet<Vector2i> CorridorExteriorTiles = new();
|
public readonly HashSet<Vector2i> CorridorExteriorTiles = new();
|
||||||
|
|
||||||
|
public readonly HashSet<Vector2i> Entrances = new();
|
||||||
|
|
||||||
public Dungeon()
|
public Dungeon()
|
||||||
{
|
{
|
||||||
Rooms = new List<DungeonRoom>();
|
Rooms = new List<DungeonRoom>();
|
||||||
@@ -23,5 +25,10 @@ public sealed class Dungeon
|
|||||||
public Dungeon(List<DungeonRoom> rooms)
|
public Dungeon(List<DungeonRoom> rooms)
|
||||||
{
|
{
|
||||||
Rooms = rooms;
|
Rooms = rooms;
|
||||||
|
|
||||||
|
foreach (var room in Rooms)
|
||||||
|
{
|
||||||
|
Entrances.UnionWith(room.Entrances);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace Content.Shared.Procedural;
|
|||||||
|
|
||||||
public sealed record DungeonRoom(HashSet<Vector2i> Tiles, Vector2 Center, Box2i Bounds, HashSet<Vector2i> Exterior)
|
public sealed record DungeonRoom(HashSet<Vector2i> Tiles, Vector2 Center, Box2i Bounds, HashSet<Vector2i> Exterior)
|
||||||
{
|
{
|
||||||
public List<Vector2i> Entrances = new();
|
public readonly List<Vector2i> Entrances = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Nodes adjacent to tiles, including the corners.
|
/// Nodes adjacent to tiles, including the corners.
|
||||||
|
|||||||
@@ -9,15 +9,25 @@ namespace Content.Shared.Procedural.PostGeneration;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class BoundaryWallPostGen : IPostDunGen
|
public sealed partial class BoundaryWallPostGen : IPostDunGen
|
||||||
{
|
{
|
||||||
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
|
[DataField]
|
||||||
public string Tile = "FloorSteel";
|
public ProtoId<ContentTileDefinition> Tile = "FloorSteel";
|
||||||
|
|
||||||
[DataField("wall", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
[DataField]
|
||||||
public string Wall = "WallSolid";
|
public EntProtoId Wall = "WallSolid";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Walls to use in corners if applicable.
|
/// Walls to use in corners if applicable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("cornerWall", customTypeSerializer:typeof(PrototypeIdSerializer<EntityPrototype>))]
|
[DataField]
|
||||||
public string? CornerWall;
|
public string? CornerWall;
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum BoundaryWallFlags : byte
|
||||||
|
{
|
||||||
|
Rooms = 1 << 0,
|
||||||
|
Corridors = 1 << 1,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ namespace Content.Shared.Procedural.PostGeneration;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class CornerClutterPostGen : IPostDunGen
|
public sealed partial class CornerClutterPostGen : IPostDunGen
|
||||||
{
|
{
|
||||||
[DataField("chance")]
|
[DataField]
|
||||||
public float Chance = 0.50f;
|
public float Chance = 0.50f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default starting bulbs
|
/// The default starting bulbs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("contents", required: true)]
|
[DataField(required: true)]
|
||||||
public List<EntitySpawnEntry> Contents = new();
|
public List<EntitySpawnEntry> Contents = new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Content.Shared.Storage;
|
||||||
|
|
||||||
|
namespace Content.Shared.Procedural.PostGeneration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds entities randomly to the corridors.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class CorridorClutterPostGen : IPostDunGen
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public float Chance = 0.05f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The default starting bulbs
|
||||||
|
/// </summary>
|
||||||
|
[DataField(required: true)]
|
||||||
|
public List<EntitySpawnEntry> Contents = new();
|
||||||
|
}
|
||||||
@@ -14,24 +14,15 @@ public sealed partial class CorridorPostGen : IPostDunGen
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Given the heavy weightings this needs to be fairly large for larger dungeons.
|
/// Given the heavy weightings this needs to be fairly large for larger dungeons.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[DataField("pathLimit")]
|
[DataField]
|
||||||
public int PathLimit = 2048;
|
public int PathLimit = 2048;
|
||||||
|
|
||||||
[DataField("method")]
|
|
||||||
public CorridorPostGenMethod Method = CorridorPostGenMethod.MinimumSpanningTree;
|
|
||||||
|
|
||||||
[DataField]
|
[DataField]
|
||||||
public ProtoId<ContentTileDefinition> Tile = "FloorSteel";
|
public ProtoId<ContentTileDefinition> Tile = "FloorSteel";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How wide to make the corridor.
|
/// How wide to make the corridor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("width")]
|
[DataField]
|
||||||
public int Width = 3;
|
public float Width = 3f;
|
||||||
}
|
|
||||||
|
|
||||||
public enum CorridorPostGenMethod : byte
|
|
||||||
{
|
|
||||||
Invalid,
|
|
||||||
MinimumSpanningTree,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Content.Shared.Maps;
|
||||||
|
using Content.Shared.Procedural.DungeonGenerators;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Shared.Procedural.PostGeneration;
|
||||||
|
|
||||||
|
// Ime a worm
|
||||||
|
/// <summary>
|
||||||
|
/// Generates worm corridors.
|
||||||
|
/// </summary>
|
||||||
|
public sealed partial class WormCorridorPostGen : IPostDunGen
|
||||||
|
{
|
||||||
|
[DataField]
|
||||||
|
public int PathLimit = 2048;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many times to run the worm
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int Count = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How long to make each worm
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public int Length = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum amount the angle can change in a single step.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public Angle MaxAngleChange = Angle.FromDegrees(45);
|
||||||
|
|
||||||
|
[DataField]
|
||||||
|
public ProtoId<ContentTileDefinition> Tile = "FloorSteel";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How wide to make the corridor.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float Width = 3f;
|
||||||
|
}
|
||||||
3251
Resources/Maps/Dungeon/haunted.yml
Normal file
3251
Resources/Maps/Dungeon/haunted.yml
Normal file
File diff suppressed because it is too large
Load Diff
308
Resources/Prototypes/Procedural/Themes/haunted.yml
Normal file
308
Resources/Prototypes/Procedural/Themes/haunted.yml
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
# Rooms
|
||||||
|
# Large
|
||||||
|
# - 17x5
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted17x5a
|
||||||
|
size: 17,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,0
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted17x5b
|
||||||
|
size: 17,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 18,0
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# - 7x7
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x7a
|
||||||
|
size: 7,7
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,42
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x7b
|
||||||
|
size: 7,7
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 8,42
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x7c
|
||||||
|
size: 7,7
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 16,42
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x7d
|
||||||
|
size: 7,7
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 24,42
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# Medium
|
||||||
|
# - 11x5
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted11x5a
|
||||||
|
size: 11,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,6
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted11x5b
|
||||||
|
size: 11,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 12,6
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted11x5c
|
||||||
|
size: 11,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 24,6
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# - 7x5
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x5a
|
||||||
|
size: 7,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,12
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x5b
|
||||||
|
size: 7,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 8,12
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x5c
|
||||||
|
size: 7,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 16,12
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x5d
|
||||||
|
size: 7,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 24,12
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# - 13x3
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted13x3a
|
||||||
|
size: 13,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,30
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted13x3b
|
||||||
|
size: 13,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 14,30
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# - 11x3
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted11x3a
|
||||||
|
size: 11,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,34
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted11x3b
|
||||||
|
size: 11,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 12,34
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted11x3c
|
||||||
|
size: 11,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 24,34
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# - 7x3
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x3a
|
||||||
|
size: 7,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,38
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x3b
|
||||||
|
size: 7,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 8,38
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x3c
|
||||||
|
size: 7,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 16,38
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted7x3d
|
||||||
|
size: 7,3
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 24,38
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# Small
|
||||||
|
# - 5x5
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted5x5a
|
||||||
|
size: 5,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,18
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted5x5b
|
||||||
|
size: 5,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 6,18
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted5x5c
|
||||||
|
size: 5,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 12,18
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted5x5d
|
||||||
|
size: 5,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 18,18
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted5x5e
|
||||||
|
size: 5,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 24,18
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted5x5f
|
||||||
|
size: 5,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 30,18
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
# - 3x5
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5a
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 0,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5b
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 4,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5c
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 8,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5d
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 12,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5e
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 16,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5f
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 20,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5g
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 24,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5h
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 28,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
|
|
||||||
|
- type: dungeonRoom
|
||||||
|
id: Haunted3x5i
|
||||||
|
size: 3,5
|
||||||
|
atlas: /Maps/Dungeon/haunted.yml
|
||||||
|
offset: 32,24
|
||||||
|
tags:
|
||||||
|
- Haunted
|
||||||
@@ -322,3 +322,40 @@
|
|||||||
SouthEast: BrickTileWhiteInnerSe
|
SouthEast: BrickTileWhiteInnerSe
|
||||||
NorthWest: BrickTileWhiteInnerNw
|
NorthWest: BrickTileWhiteInnerNw
|
||||||
NorthEast: BrickTileWhiteInnerNe
|
NorthEast: BrickTileWhiteInnerNe
|
||||||
|
|
||||||
|
# todo: Add a biome dungeon generator
|
||||||
|
# Add corridor first gens that place rooms on top
|
||||||
|
# Add a worm corridor gen (place subsequent corridors somewhere randomly along the path)
|
||||||
|
# Place room entrances on ends of corridors touching a tile
|
||||||
|
# Remove all room tiles from corridors
|
||||||
|
# Fix paths up and try to reconnect all corridor tiles
|
||||||
|
# Add a postgen step to spread rooms out, though it shouldn't spread into corridor exteriors
|
||||||
|
|
||||||
|
- type: dungeonConfig
|
||||||
|
id: Haunted
|
||||||
|
generator: !type:PrefabDunGen
|
||||||
|
tile: FloorCaveDrought
|
||||||
|
roomWhitelist:
|
||||||
|
- Mineshaft
|
||||||
|
presets:
|
||||||
|
- Bucket
|
||||||
|
- Wow
|
||||||
|
- SpaceShip
|
||||||
|
- Tall
|
||||||
|
postGeneration:
|
||||||
|
- !type:WormCorridorPostGen
|
||||||
|
width: 3
|
||||||
|
tile: FloorCaveDrought
|
||||||
|
|
||||||
|
- !type:CorridorClutterPostGen
|
||||||
|
contents:
|
||||||
|
- id: FloraStalagmite1
|
||||||
|
- id: FloraStalagmite2
|
||||||
|
- id: FloraStalagmite3
|
||||||
|
- id: FloraStalagmite4
|
||||||
|
- id: FloraStalagmite5
|
||||||
|
- id: FloraStalagmite6
|
||||||
|
|
||||||
|
- !type:BoundaryWallPostGen
|
||||||
|
tile: FloorCaveDrought
|
||||||
|
wall: WallRock
|
||||||
|
|||||||
@@ -242,3 +242,9 @@
|
|||||||
proto: SnowyLabs
|
proto: SnowyLabs
|
||||||
biomes:
|
biomes:
|
||||||
- Snow
|
- Snow
|
||||||
|
|
||||||
|
- type: salvageDungeonMod
|
||||||
|
id: Haunted
|
||||||
|
proto: Haunted
|
||||||
|
biomes:
|
||||||
|
- Caves
|
||||||
|
|||||||
@@ -673,6 +673,9 @@
|
|||||||
- type: Tag
|
- type: Tag
|
||||||
id: Hardsuit # Prevent melee injectors that can't penetrate hardsuits from injecting the wearer (nettles)
|
id: Hardsuit # Prevent melee injectors that can't penetrate hardsuits from injecting the wearer (nettles)
|
||||||
|
|
||||||
|
- type: Tag
|
||||||
|
id: Haunted
|
||||||
|
|
||||||
- type: Tag
|
- type: Tag
|
||||||
id: Head
|
id: Head
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user