DungeonData rework (#37172)

* DungeonData rework

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

* wawawewa

* Fix this

* Fixes

* review

* thanks fork

* fix
This commit is contained in:
metalgearsloth
2025-05-18 03:10:30 +10:00
committed by GitHub
parent d72939ba4b
commit 4afccdd5db
57 changed files with 759 additions and 696 deletions

View File

@@ -9,6 +9,12 @@ public sealed class EntityTableSystem : EntitySystem
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
public IEnumerable<EntProtoId> GetSpawns(EntityTablePrototype entTableProto, System.Random? rand = null)
{
// convenient
return GetSpawns(entTableProto.Table, rand);
}
public IEnumerable<EntProtoId> GetSpawns(EntityTableSelector? table, System.Random? rand = null)
{
if (table == null)

View File

@@ -5,12 +5,6 @@ namespace Content.Shared.Procedural;
[Virtual, DataDefinition]
public partial class DungeonConfig
{
/// <summary>
/// <see cref="Data"/>
/// </summary>
[DataField]
public DungeonData Data = DungeonData.Empty;
/// <summary>
/// The secret sauce, procedural generation layers that get run.
/// </summary>

View File

@@ -1,105 +0,0 @@
using System.Linq;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Procedural;
/// <summary>
/// Used to set dungeon values for all layers.
/// </summary>
/// <remarks>
/// This lets us share data between different dungeon configs without having to repeat entire configs.
/// </remarks>
[DataRecord]
public sealed partial class DungeonData
{
// I hate this but it also significantly reduces yaml bloat if we add like 10 variations on the same set of layers
// e.g. science rooms, engi rooms, cargo rooms all under PlanetBase for example.
// without having to do weird nesting. It also means we don't need to copy-paste the same prototype across several layers
// The alternative is doing like,
// 2 layer prototype, 1 layer with the specified data, 3 layer prototype, 2 layers with specified data, etc.
// As long as we just keep the code clean over time it won't be bad to maintain.
public static DungeonData Empty = new();
public Dictionary<DungeonDataKey, Color> Colors = new();
public Dictionary<DungeonDataKey, EntProtoId> Entities = new();
public Dictionary<DungeonDataKey, ProtoId<EntitySpawnEntryPrototype>> SpawnGroups = new();
public Dictionary<DungeonDataKey, ProtoId<ContentTileDefinition>> Tiles = new();
public Dictionary<DungeonDataKey, EntityWhitelist> Whitelists = new();
/// <summary>
/// Applies the specified data to this data.
/// </summary>
public void Apply(DungeonData data)
{
// Copy-paste moment.
foreach (var color in data.Colors)
{
Colors[color.Key] = color.Value;
}
foreach (var color in data.Entities)
{
Entities[color.Key] = color.Value;
}
foreach (var color in data.SpawnGroups)
{
SpawnGroups[color.Key] = color.Value;
}
foreach (var color in data.Tiles)
{
Tiles[color.Key] = color.Value;
}
foreach (var color in data.Whitelists)
{
Whitelists[color.Key] = color.Value;
}
}
public DungeonData Clone()
{
return new DungeonData
{
// Only shallow clones but won't matter for DungeonJob purposes.
Colors = Colors.ShallowClone(),
Entities = Entities.ShallowClone(),
SpawnGroups = SpawnGroups.ShallowClone(),
Tiles = Tiles.ShallowClone(),
Whitelists = Whitelists.ShallowClone(),
};
}
}
public enum DungeonDataKey : byte
{
// Colors
Decals,
// Entities
Cabling,
CornerWalls,
Fill,
Junction,
Walls,
// SpawnGroups
CornerClutter,
Entrance,
EntranceFlank,
WallMounts,
Window,
// Tiles
FallbackTile,
WidenTile,
// Whitelists
Rooms,
}

View File

@@ -1,3 +1,5 @@
using Content.Shared.Maps;
using Content.Shared.Whitelist;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
@@ -6,10 +8,6 @@ namespace Content.Shared.Procedural.DungeonGenerators;
/// Places rooms in pre-selected pack layouts. Chooses rooms from the specified whitelist.
/// </summary>
/// <remarks>
/// DungeonData keys are:
/// - FallbackTile
/// - Rooms
/// </remarks>
public sealed partial class PrefabDunGen : IDunGenLayer
{
/// <summary>
@@ -17,4 +15,10 @@ public sealed partial class PrefabDunGen : IDunGenLayer
/// </summary>
[DataField(required: true)]
public List<ProtoId<DungeonPresetPrototype>> Presets = new();
[DataField]
public EntityWhitelist? RoomWhitelist;
[DataField]
public ProtoId<ContentTileDefinition>? FallbackTile;
}

View File

@@ -8,6 +8,30 @@ namespace Content.Shared.Procedural.DungeonGenerators;
/// </summary>
public sealed partial class PrototypeDunGen : IDunGenLayer
{
/// <summary>
/// Should we pass in the current level's dungeons to the prototype.
/// </summary>
[DataField]
public DungeonInheritance InheritDungeons = DungeonInheritance.None;
[DataField(required: true)]
public ProtoId<DungeonConfigPrototype> Proto;
}
public enum DungeonInheritance : byte
{
/// <summary>
/// Don't inherit any of the current layer's dungeons for this <see cref="PrototypeDunGen"/>
/// </summary>
None,
/// <summary>
/// Inherit only the last dungeon ran.
/// </summary>
Last,
/// <summary>
/// Inherit all of the current layer's dungeons.
/// </summary>
All,
}

View File

@@ -1,7 +1,7 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
namespace Content.Shared.Procedural.DungeonLayers;
/// <summary>
/// Fills unreserved tiles with the specified entity prototype.
@@ -17,4 +17,7 @@ public sealed partial class FillGridDunGen : IDunGenLayer
/// </summary>
[DataField]
public HashSet<ProtoId<ContentTileDefinition>>? AllowedTiles;
[DataField(required: true)]
public EntProtoId Entity;
}

View File

@@ -1,4 +1,6 @@
using Content.Shared.EntityTable;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonLayers;
@@ -17,5 +19,5 @@ public sealed partial class MobsDunGen : IDunGenLayer
public int MaxCount = 1;
[DataField(required: true)]
public List<EntitySpawnEntry> Groups = new();
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Parallax.Biomes;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Procedural.Loot;
@@ -8,6 +9,6 @@ namespace Content.Shared.Procedural.Loot;
/// </summary>
public sealed partial class BiomeTemplateLoot : IDungeonLoot
{
[DataField("proto", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
public string Prototype = string.Empty;
[DataField("proto", required: true)]
public ProtoId<BiomeTemplatePrototype> Prototype = string.Empty;
}

View File

@@ -1,10 +1,12 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Runs cables throughout the dungeon.
/// </summary>
/// <remarks>
/// DungeonData keys are:
/// - Cabling
/// </remarks>
public sealed partial class AutoCablingDunGen : IDunGenLayer;
public sealed partial class AutoCablingDunGen : IDunGenLayer
{
[DataField(required: true)]
public EntProtoId Entity;
}

View File

@@ -1,3 +1,6 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
@@ -13,6 +16,15 @@ public sealed partial class BoundaryWallDunGen : IDunGenLayer
{
[DataField]
public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms;
[DataField(required: true)]
public EntProtoId Wall;
[DataField]
public EntProtoId? CornerWall;
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
}
[Flags]

View File

@@ -1,14 +1,17 @@
using Content.Shared.EntityTable;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Spawns entities inside corners.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - CornerClutter
/// </remarks>
public sealed partial class CornerClutterDunGen : IDunGenLayer
{
[DataField]
public float Chance = 0.50f;
[DataField(required:true)]
public ProtoId<EntityTablePrototype> Contents = new();
}

View File

@@ -1,4 +1,6 @@
using Content.Shared.EntityTable;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
@@ -14,5 +16,5 @@ public sealed partial class CorridorClutterDunGen : IDunGenLayer
/// The default starting bulbs
/// </summary>
[DataField(required: true)]
public List<EntitySpawnEntry> Contents = new();
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,7 +1,3 @@
using Content.Shared.Decals;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
@@ -26,4 +22,10 @@ public sealed partial class CorridorDecalSkirtingDunGen : IDunGenLayer
/// </summary>
[DataField]
public Dictionary<DirectionFlag, string> CornerDecals = new();
/// <summary>
/// Optional color to apply to the decals.
/// </summary>
[DataField]
public Color? Color;
}

View File

@@ -1,12 +1,11 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Connects room entrances via corridor segments.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - FallbackTile
/// </remarks>
public sealed partial class CorridorDunGen : IDunGenLayer
{
/// <summary>
@@ -23,4 +22,7 @@ public sealed partial class CorridorDunGen : IDunGenLayer
/// </summary>
[DataField]
public float Width = 3f;
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
}

View File

@@ -1,13 +1,13 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Selects [count] rooms and places external doors to them.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - Entrance
/// - FallbackTile
/// </remarks>
public sealed partial class DungeonEntranceDunGen : IDunGenLayer
{
/// <summary>
@@ -15,4 +15,10 @@ public sealed partial class DungeonEntranceDunGen : IDunGenLayer
/// </summary>
[DataField]
public int Count = 1;
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField(required: true)]
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,11 +1,17 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Spawns entities on either side of an entrance.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - FallbackTile
/// -
/// </remarks>
public sealed partial class EntranceFlankDunGen : IDunGenLayer;
public sealed partial class EntranceFlankDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField(required: true)]
public ProtoId<EntityTablePrototype> Contents = new();
}

View File

@@ -1,11 +1,18 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// If external areas are found will try to generate windows.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - EntranceFlank
/// - FallbackTile
/// </remarks>
public sealed partial class ExternalWindowDunGen : IDunGenLayer;
public sealed partial class ExternalWindowDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField(required: true)]
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,11 +1,17 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// If internal areas are found will try to generate windows.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - FallbackTile
/// - Window
/// </remarks>
public sealed partial class InternalWindowDunGen : IDunGenLayer;
public sealed partial class InternalWindowDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField(required: true)]
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,13 +1,13 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Places the specified entities at junction areas.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - Entrance
/// - FallbackTile
/// </remarks>
public sealed partial class JunctionDunGen : IDunGenLayer
{
/// <summary>
@@ -15,4 +15,10 @@ public sealed partial class JunctionDunGen : IDunGenLayer
/// </summary>
[DataField]
public int Width = 3;
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField(required: true)]
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,3 +1,8 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
@@ -16,4 +21,13 @@ public sealed partial class MiddleConnectionDunGen : IDunGenLayer
/// </summary>
[DataField]
public int Count = 1;
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField(required: true)]
public ProtoId<EntityTablePrototype> Contents;
[DataField]
public ProtoId<EntityTablePrototype>? Flank;
}

View File

@@ -1,11 +1,18 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Places tiles / entities onto room entrances.
/// </summary>
/// <remarks>
/// DungeonData keys are:
/// - Entrance
/// - FallbackTile
/// </remarks>
public sealed partial class RoomEntranceDunGen : IDunGenLayer;
public sealed partial class RoomEntranceDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField]
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,3 +1,6 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
@@ -5,6 +8,12 @@ namespace Content.Shared.Procedural.PostGeneration;
/// </summary>
public sealed partial class SplineDungeonConnectorDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField]
public ProtoId<ContentTileDefinition>? WidenTile;
/// <summary>
/// Will divide the distance between the start and end points so that no subdivision is more than these metres away.
/// </summary>

View File

@@ -1,3 +1,8 @@
using Content.Shared.EntityTable;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
@@ -10,4 +15,10 @@ public sealed partial class WallMountDunGen : IDunGenLayer
/// </summary>
[DataField]
public double Prob = 0.1;
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
[DataField(required: true)]
public ProtoId<EntityTablePrototype> Contents;
}

View File

@@ -1,3 +1,6 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
// Ime a worm
@@ -32,4 +35,7 @@ public sealed partial class WormCorridorDunGen : IDunGenLayer
/// </summary>
[DataField]
public float Width = 3f;
[DataField(required: true)]
public ProtoId<ContentTileDefinition> Tile;
}

View File

@@ -50,12 +50,8 @@ public abstract partial class SharedSalvageSystem
var configProto =_proto.Index(configId);
var layers = new Dictionary<string, int>();
var data = new DungeonData();
data.Apply(configProto.Data);
var config = new DungeonConfig
{
Data = data,
Layers = new(configProto.Layers),
MaxCount = configProto.MaxCount,
MaxOffset = configProto.MaxOffset,

View File

@@ -76,6 +76,18 @@ public static class EntitySpawnCollection
public float CumulativeProbability { get; set; } = 0f;
}
public static List<string> GetSpawns(ProtoId<EntitySpawnEntryPrototype> proto, IPrototypeManager? protoManager = null, IRobustRandom? random = null)
{
IoCManager.Resolve(ref protoManager, ref random);
return GetSpawns(protoManager.Index(proto).Entries, random);
}
public static List<string?> GetSpawns(ProtoId<EntitySpawnEntryPrototype> proto, System.Random random, IPrototypeManager? protoManager = null)
{
IoCManager.Resolve(ref protoManager);
return GetSpawns(protoManager.Index(proto).Entries, random);
}
/// <summary>
/// Using a collection of entity spawn entries, picks a random list of entity prototypes to spawn from that collection.
/// </summary>