VGRoid support (#27659)

* Dungeon spawn support for grid spawns

* Recursive dungeons working

* Mask approach working

* zack

* More work

* Fix recursive dungeons

* Heap of work

* weh

* the cud

* rar

* Job

* weh

* weh

* weh

* Master merges

* orch

* weh

* vgroid most of the work

* Tweaks

* Tweaks

* weh

* do do do do do do

* Basic layout

* Ore spawning working

* Big breaking changes

* Mob gen working

* weh

* Finalising

* emo

* More finalising

* reverty

* Reduce distance
This commit is contained in:
metalgearsloth
2024-07-03 22:23:11 +10:00
committed by GitHub
parent 1faa1b5df6
commit a2f99cc69e
103 changed files with 4928 additions and 2627 deletions

View File

@@ -0,0 +1,13 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.Components;
/// <summary>
/// Indicates this entity prototype should be re-mapped to another
/// </summary>
[RegisterComponent]
public sealed partial class EntityRemapComponent : Component
{
[DataField(required: true)]
public Dictionary<EntProtoId, EntProtoId> Mask = new();
}

View File

@@ -0,0 +1,10 @@
namespace Content.Shared.Procedural.Distance;
/// <summary>
/// Produces a rounder shape useful for more natural areas.
/// </summary>
public sealed partial class DunGenEuclideanSquaredDistance : IDunGenDistance
{
[DataField]
public float BlendWeight { get; set; } = 0.50f;
}

View File

@@ -0,0 +1,10 @@
namespace Content.Shared.Procedural.Distance;
/// <summary>
/// Produces a squarish-shape that's better for filling in most of the area.
/// </summary>
public sealed partial class DunGenSquareBump : IDunGenDistance
{
[DataField]
public float BlendWeight { get; set; } = 0.50f;
}

View File

@@ -0,0 +1,14 @@
namespace Content.Shared.Procedural.Distance;
/// <summary>
/// Used if you want to limit the distance noise is generated by some arbitrary config
/// </summary>
[ImplicitDataDefinitionForInheritors]
public partial interface IDunGenDistance
{
/// <summary>
/// How much to blend between the original noise value and the adjusted one.
/// </summary>
float BlendWeight { get; }
}

View File

@@ -1,8 +1,16 @@
namespace Content.Shared.Procedural;
/// <summary>
/// Procedurally generated dungeon data.
/// </summary>
public sealed class Dungeon
{
public readonly List<DungeonRoom> Rooms;
public static Dungeon Empty = new Dungeon();
private List<DungeonRoom> _rooms;
private HashSet<Vector2i> _allTiles = new();
public IReadOnlyList<DungeonRoom> Rooms => _rooms;
/// <summary>
/// Hashset of the tiles across all rooms.
@@ -17,18 +25,64 @@ public sealed class Dungeon
public readonly HashSet<Vector2i> Entrances = new();
public Dungeon()
public IReadOnlySet<Vector2i> AllTiles => _allTiles;
public Dungeon() : this(new List<DungeonRoom>())
{
Rooms = new List<DungeonRoom>();
}
public Dungeon(List<DungeonRoom> rooms)
{
Rooms = rooms;
// This reftype is mine now.
_rooms = rooms;
foreach (var room in Rooms)
foreach (var room in _rooms)
{
Entrances.UnionWith(room.Entrances);
InternalAddRoom(room);
}
RefreshAllTiles();
}
public void RefreshAllTiles()
{
_allTiles.Clear();
_allTiles.UnionWith(RoomTiles);
_allTiles.UnionWith(RoomExteriorTiles);
_allTiles.UnionWith(CorridorTiles);
_allTiles.UnionWith(CorridorExteriorTiles);
_allTiles.UnionWith(Entrances);
}
public void Rebuild()
{
_allTiles.Clear();
RoomTiles.Clear();
RoomExteriorTiles.Clear();
Entrances.Clear();
foreach (var room in _rooms)
{
InternalAddRoom(room, false);
}
RefreshAllTiles();
}
public void AddRoom(DungeonRoom room)
{
_rooms.Add(room);
InternalAddRoom(room);
}
private void InternalAddRoom(DungeonRoom room, bool refreshAll = true)
{
Entrances.UnionWith(room.Entrances);
RoomTiles.UnionWith(room.Tiles);
RoomExteriorTiles.UnionWith(room.Exterior);
if (refreshAll)
RefreshAllTiles();
}
}

View File

@@ -1,21 +1,53 @@
using Content.Shared.Procedural.DungeonGenerators;
using Content.Shared.Procedural.PostGeneration;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural;
[Prototype("dungeonConfig")]
[Prototype]
public sealed partial class DungeonConfigPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
[DataField("generator", required: true)]
public IDunGen Generator = default!;
/// <summary>
/// <see cref="Data"/>
/// </summary>
[DataField]
public DungeonData Data = DungeonData.Empty;
/// <summary>
/// Ran after the main dungeon is created.
/// The secret sauce, procedural generation layers that get run.
/// </summary>
[DataField("postGeneration")]
public List<IPostDunGen> PostGeneration = new();
[DataField(required: true)]
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?
/// </summary>
[DataField]
public bool ReserveTiles;
/// <summary>
/// Minimum times to run the config.
/// </summary>
[DataField]
public int MinCount = 1;
/// <summary>
/// Maximum times to run the config.
/// </summary>
[DataField]
public int MaxCount = 1;
/// <summary>
/// Minimum amount we can offset the dungeon by.
/// </summary>
[DataField]
public int MinOffset;
/// <summary>
/// Maximum amount we can offset the dungeon by.
/// </summary>
[DataField]
public int MaxOffset;
}

View File

@@ -0,0 +1,105 @@
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 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

@@ -0,0 +1,13 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
/// <summary>
/// Generates the specified config on an exterior tile of the attached dungeon.
/// Useful if you're using <see cref="GroupDunGen"/> or otherwise want a dungeon on the outside of a grid.
/// </summary>
public sealed partial class ExteriorDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<DungeonConfigPrototype> Proto;
}

View File

@@ -0,0 +1,10 @@
namespace Content.Shared.Procedural.DungeonGenerators;
/// <summary>
/// Fills unreserved tiles with the specified entity prototype.
/// </summary>
/// <remarks>
/// DungeonData keys are:
/// - Fill
/// </remarks>
public sealed partial class FillGridDunGen : IDunGenLayer;

View File

@@ -1,7 +0,0 @@
namespace Content.Shared.Procedural.DungeonGenerators;
[ImplicitDataDefinitionForInheritors]
public partial interface IDunGen
{
}

View File

@@ -0,0 +1,18 @@
using Content.Shared.Procedural.Distance;
namespace Content.Shared.Procedural.DungeonGenerators;
/// <summary>
/// Like <see cref="Content.Shared.Procedural.DungeonGenerators.NoiseDunGenLayer"/> except with maximum dimensions
/// </summary>
public sealed partial class NoiseDistanceDunGen : IDunGenLayer
{
[DataField]
public IDunGenDistance? DistanceConfig;
[DataField]
public Vector2i Size;
[DataField(required: true)]
public List<NoiseDunGenLayer> Layers = new();
}

View File

@@ -1,15 +1,12 @@
using Content.Shared.Maps;
using Content.Shared.Procedural.Distance;
using Robust.Shared.Noise;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.DungeonGenerators;
/// <summary>
/// Generates dungeon flooring based on the specified noise.
/// </summary>
public sealed partial class NoiseDunGen : IDunGen
public sealed partial class NoiseDunGen : IDunGenLayer
{
/*
* Floodfills out from 0 until it finds a valid tile.

View File

@@ -1,30 +1,20 @@
using Content.Shared.Maps;
using Content.Shared.Tag;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
/// <summary>
/// Places rooms in pre-selected pack layouts. Chooses rooms from the specified whitelist.
/// </summary>
public sealed partial class PrefabDunGen : IDunGen
/// <remarks>
/// DungeonData keys are:
/// - FallbackTile
/// - Rooms
/// </remarks>
public sealed partial class PrefabDunGen : IDunGenLayer
{
/// <summary>
/// Rooms need to match any of these tags
/// </summary>
[DataField("roomWhitelist", customTypeSerializer:typeof(PrototypeIdListSerializer<TagPrototype>))]
public List<string> RoomWhitelist = new();
/// <summary>
/// Room pack presets we can use for this prefab.
/// </summary>
[DataField("presets", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<DungeonPresetPrototype>))]
public List<string> Presets = new();
/// <summary>
/// Fallback tile.
/// </summary>
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
[DataField(required: true)]
public List<ProtoId<DungeonPresetPrototype>> Presets = new();
}

View File

@@ -0,0 +1,13 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
/// <summary>
/// Runs another <see cref="DungeonConfigPrototype"/>.
/// Used for storing data on 1 system.
/// </summary>
public sealed partial class PrototypeDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<DungeonConfigPrototype> Proto;
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Maps;
using Robust.Shared.Noise;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonGenerators;
/// <summary>
/// Replaces existing tiles if they're not empty.
/// </summary>
public sealed partial class ReplaceTileDunGen : IDunGenLayer
{
/// <summary>
/// Chance for a non-variant tile to be used, in case they're too noisy.
/// </summary>
[DataField]
public float VariantWeight = 0.1f;
[DataField(required: true)]
public List<ReplaceTileLayer> Layers = new();
}
[DataRecord]
public record struct ReplaceTileLayer
{
public ProtoId<ContentTileDefinition> Tile;
public float Threshold;
public FastNoiseLite Noise;
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.Storage;
namespace Content.Shared.Procedural.DungeonLayers;
/// <summary>
/// Spawns mobs inside of the dungeon randomly.
/// </summary>
public sealed partial class MobsDunGen : IDunGenLayer
{
// Counts separate to config to avoid some duplication.
[DataField]
public int MinCount = 1;
[DataField]
public int MaxCount = 1;
[DataField(required: true)]
public List<EntitySpawnEntry> Groups = new();
}

View File

@@ -0,0 +1,42 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonLayers;
/// <summary>
/// Generates veins inside of the specified dungeon.
/// </summary>
/// <remarks>
/// Generates on top of existing entities for sanity reasons moreso than performance.
/// </remarks>
public sealed partial class OreDunGen : IDunGenLayer
{
/// <summary>
/// If the vein generation should occur on top of existing entities what are we replacing.
/// </summary>
[DataField]
public EntProtoId? Replacement;
/// <summary>
/// Entity to spawn.
/// </summary>
[DataField(required: true)]
public EntProtoId Entity;
/// <summary>
/// Maximum amount of group spawns
/// </summary>
[DataField]
public int Count = 10;
/// <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;
}

View File

@@ -2,6 +2,7 @@ using System.Numerics;
namespace Content.Shared.Procedural;
// TODO: Cache center and bounds and shit and don't make the caller deal with it.
public sealed record DungeonRoom(HashSet<Vector2i> Tiles, Vector2 Center, Box2i Bounds, HashSet<Vector2i> Exterior)
{
public readonly List<Vector2i> Entrances = new();

View File

@@ -0,0 +1,7 @@
namespace Content.Shared.Procedural;
[ImplicitDataDefinitionForInheritors]
public partial interface IDunGenLayer
{
}

View File

@@ -0,0 +1,10 @@
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Runs cables throughout the dungeon.
/// </summary>
/// <remarks>
/// DungeonData keys are:
/// - Cabling
/// </remarks>
public sealed partial class AutoCablingDunGen : IDunGenLayer;

View File

@@ -1,12 +0,0 @@
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Runs cables throughout the dungeon.
/// </summary>
public sealed partial class AutoCablingPostGen : IPostDunGen
{
[DataField]
public EntProtoId Entity = "CableApcExtension";
}

View File

@@ -1,5 +1,4 @@
using Content.Shared.Parallax.Biomes;
using Content.Shared.Procedural.PostGeneration;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
@@ -8,7 +7,7 @@ namespace Content.Shared.Procedural.PostGeneration;
/// 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 BiomePostGen : IPostDunGen
public sealed partial class BiomeDunGen : IDunGenLayer
{
[DataField(required: true)]
public ProtoId<BiomeTemplatePrototype> BiomeTemplate;

View File

@@ -1,5 +1,3 @@
using Content.Shared.Parallax.Biomes.Markers;
using Content.Shared.Procedural.PostGeneration;
using Content.Shared.Random;
using Robust.Shared.Prototypes;
@@ -8,7 +6,7 @@ namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Spawns the specified marker layer on top of the dungeon rooms.
/// </summary>
public sealed partial class BiomeMarkerLayerPostGen : IPostDunGen
public sealed partial class BiomeMarkerLayerDunGen : IDunGenLayer
{
/// <summary>
/// How many times to spawn marker layers; can duplicate.

View File

@@ -0,0 +1,23 @@
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Iterates room edges and places the relevant tiles and walls on any free indices.
/// </summary>
/// <remarks>
/// Dungeon data keys are:
/// - CornerWalls (Optional)
/// - FallbackTile
/// - Walls
/// </remarks>
public sealed partial class BoundaryWallDunGen : IDunGenLayer
{
[DataField]
public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms;
}
[Flags]
public enum BoundaryWallFlags : byte
{
Rooms = 1 << 0,
Corridors = 1 << 1,
}

View File

@@ -1,33 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Iterates room edges and places the relevant tiles and walls on any free indices.
/// </summary>
public sealed partial class BoundaryWallPostGen : IPostDunGen
{
[DataField]
public ProtoId<ContentTileDefinition> Tile = "FloorSteel";
[DataField]
public EntProtoId Wall = "WallSolid";
/// <summary>
/// Walls to use in corners if applicable.
/// </summary>
[DataField]
public string? CornerWall;
[DataField]
public BoundaryWallFlags Flags = BoundaryWallFlags.Corridors | BoundaryWallFlags.Rooms;
}
[Flags]
public enum BoundaryWallFlags : byte
{
Rooms = 1 << 0,
Corridors = 1 << 1,
}

View File

@@ -0,0 +1,14 @@
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;
}

View File

@@ -1,18 +0,0 @@
using Content.Shared.Storage;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Spawns entities inside corners.
/// </summary>
public sealed partial class CornerClutterPostGen : IPostDunGen
{
[DataField]
public float Chance = 0.50f;
/// <summary>
/// The default starting bulbs
/// </summary>
[DataField(required: true)]
public List<EntitySpawnEntry> Contents = new();
}

View File

@@ -5,7 +5,7 @@ namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Adds entities randomly to the corridors.
/// </summary>
public sealed partial class CorridorClutterPostGen : IPostDunGen
public sealed partial class CorridorClutterDunGen : IDunGenLayer
{
[DataField]
public float Chance = 0.05f;

View File

@@ -7,29 +7,23 @@ namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Applies decal skirting to corridors.
/// </summary>
public sealed partial class CorridorDecalSkirtingPostGen : IPostDunGen
public sealed partial class CorridorDecalSkirtingDunGen : IDunGenLayer
{
/// <summary>
/// Color to apply to decals.
/// </summary>
[DataField("color")]
public Color? Color;
/// <summary>
/// Decal where 1 edge is found.
/// </summary>
[DataField("cardinalDecals")]
[DataField]
public Dictionary<DirectionFlag, string> CardinalDecals = new();
/// <summary>
/// Decal where 1 corner edge is found.
/// </summary>
[DataField("pocketDecals")]
[DataField]
public Dictionary<Direction, string> PocketDecals = new();
/// <summary>
/// Decal where 2 or 3 edges are found.
/// </summary>
[DataField("cornerDecals")]
[DataField]
public Dictionary<DirectionFlag, string> CornerDecals = new();
}

View File

@@ -1,12 +1,13 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Connects room entrances via corridor segments.
/// </summary>
public sealed partial class CorridorPostGen : IPostDunGen
/// <remarks>
/// Dungeon data keys are:
/// - FallbackTile
/// </remarks>
public sealed partial class CorridorDunGen : IDunGenLayer
{
/// <summary>
/// How far we're allowed to generate a corridor before calling it.
@@ -17,9 +18,6 @@ public sealed partial class CorridorPostGen : IPostDunGen
[DataField]
public int PathLimit = 2048;
[DataField]
public ProtoId<ContentTileDefinition> Tile = "FloorSteel";
/// <summary>
/// How wide to make the corridor.
/// </summary>

View File

@@ -0,0 +1,18 @@
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>
/// How many rooms we place doors on.
/// </summary>
[DataField]
public int Count = 1;
}

View File

@@ -1,28 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Selects [count] rooms and places external doors to them.
/// </summary>
public sealed partial class DungeonEntrancePostGen : IPostDunGen
{
/// <summary>
/// How many rooms we place doors on.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string?> Entities = new()
{
"CableApcExtension",
"AirlockGlass",
};
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
}

View File

@@ -0,0 +1,11 @@
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;

View File

@@ -1,16 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Spawns entities on either side of an entrance.
/// </summary>
public sealed partial class EntranceFlankPostGen : IPostDunGen
{
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
[DataField("entities")]
public List<string?> Entities = new();
}

View File

@@ -0,0 +1,11 @@
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;

View File

@@ -1,22 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// If external areas are found will try to generate windows.
/// </summary>
public sealed partial class ExternalWindowPostGen : IPostDunGen
{
[DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string?> Entities = new()
{
"Grille",
"Window",
};
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
}

View File

@@ -1,10 +0,0 @@
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Ran after generating dungeon rooms. Can be used for additional loot, contents, etc.
/// </summary>
[ImplicitDataDefinitionForInheritors]
public partial interface IPostDunGen
{
}

View File

@@ -0,0 +1,11 @@
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;

View File

@@ -1,22 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// If internal areas are found will try to generate windows.
/// </summary>
public sealed partial class InternalWindowPostGen : IPostDunGen
{
[DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string?> Entities = new()
{
"Grille",
"Window",
};
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
}

View File

@@ -0,0 +1,18 @@
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>
/// Width to check for junctions.
/// </summary>
[DataField]
public int Width = 3;
}

View File

@@ -1,28 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Places the specified entities at junction areas.
/// </summary>
public sealed partial class JunctionPostGen : IPostDunGen
{
/// <summary>
/// Width to check for junctions.
/// </summary>
[DataField("width")]
public int Width = 3;
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
[DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string?> Entities = new()
{
"CableApcExtension",
"AirlockGlass"
};
}

View File

@@ -0,0 +1,19 @@
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Places the specified entities on the middle connections between rooms
/// </summary>
public sealed partial class MiddleConnectionDunGen : IDunGenLayer
{
/// <summary>
/// How much overlap there needs to be between 2 rooms exactly.
/// </summary>
[DataField]
public int OverlapCount = -1;
/// <summary>
/// How many connections to spawn between rooms.
/// </summary>
[DataField]
public int Count = 1;
}

View File

@@ -1,39 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Places the specified entities on the middle connections between rooms
/// </summary>
public sealed partial class MiddleConnectionPostGen : IPostDunGen
{
/// <summary>
/// How much overlap there needs to be between 2 rooms exactly.
/// </summary>
[DataField("overlapCount")]
public int OverlapCount = -1;
/// <summary>
/// How many connections to spawn between rooms.
/// </summary>
[DataField("count")]
public int Count = 1;
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
[DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string?> Entities = new()
{
"CableApcExtension",
"AirlockGlass"
};
/// <summary>
/// If overlap > 1 then what should spawn on the edges.
/// </summary>
[DataField("edgeEntities")] public List<string?> EdgeEntities = new();
}

View File

@@ -0,0 +1,11 @@
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;

View File

@@ -1,22 +0,0 @@
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Places tiles / entities onto room entrances.
/// </summary>
public sealed partial class RoomEntrancePostGen : IPostDunGen
{
[DataField("entities", customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string?> Entities = new()
{
"CableApcExtension",
"AirlockGlass",
};
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
}

View File

@@ -0,0 +1,19 @@
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Connects dungeons via points that get subdivided.
/// </summary>
public sealed partial class SplineDungeonConnectorDunGen : IDunGenLayer
{
/// <summary>
/// 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;
/// <summary>
/// How much each subdivision can vary from the middle.
/// </summary>
[DataField]
public float VarianceMax = 0.35f;
}

View File

@@ -0,0 +1,13 @@
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Spawns on the boundary tiles of rooms.
/// </summary>
public sealed partial class WallMountDunGen : IDunGenLayer
{
/// <summary>
/// Chance per free tile to spawn a wallmount.
/// </summary>
[DataField]
public double Prob = 0.1;
}

View File

@@ -1,23 +0,0 @@
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Procedural.PostGeneration;
/// <summary>
/// Spawns on the boundary tiles of rooms.
/// </summary>
public sealed partial class WallMountPostGen : IPostDunGen
{
[DataField("tile", customTypeSerializer:typeof(PrototypeIdSerializer<ContentTileDefinition>))]
public string Tile = "FloorSteel";
[DataField("spawns")]
public List<EntitySpawnEntry> Spawns = new();
/// <summary>
/// Chance per free tile to spawn a wallmount.
/// </summary>
[DataField("prob")]
public double Prob = 0.1;
}

View File

@@ -1,14 +1,10 @@
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
public sealed partial class WormCorridorDunGen : IDunGenLayer
{
[DataField]
public int PathLimit = 2048;
@@ -31,9 +27,6 @@ public sealed partial class WormCorridorPostGen : IPostDunGen
[DataField]
public Angle MaxAngleChange = Angle.FromDegrees(45);
[DataField]
public ProtoId<ContentTileDefinition> Tile = "FloorSteel";
/// <summary>
/// How wide to make the corridor.
/// </summary>

View File

@@ -32,14 +32,14 @@ public abstract partial class SharedSalvageSystem
var layers = new Dictionary<string, int>();
// If we ever add more random layers will need to Next on these.
foreach (var layer in configProto.PostGeneration)
foreach (var layer in configProto.Layers)
{
switch (layer)
{
case BiomePostGen:
case BiomeDunGen:
rand.Next();
break;
case BiomeMarkerLayerPostGen marker:
case BiomeMarkerLayerDunGen marker:
for (var i = 0; i < marker.Count; i++)
{
var proto = _proto.Index(marker.MarkerTemplate).Pick(rand);

View File

@@ -18,7 +18,7 @@ public abstract partial class SharedShuttleSystem : EntitySystem
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
[Dependency] private readonly EntityWhitelistSystem _whitelistSystem = default!;
public const float FTLRange = 512f;
public const float FTLRange = 256f;
public const float FTLBufferRange = 8f;
private EntityQuery<MapGridComponent> _gridQuery;

View File

@@ -5,6 +5,19 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Shared.Storage;
/// <summary>
/// Prototype wrapper around <see cref="EntitySpawnEntry"/>
/// </summary>
[Prototype]
public sealed class EntitySpawnEntryPrototype : IPrototype
{
[IdDataField]
public string ID { get; } = string.Empty;
[DataField]
public List<EntitySpawnEntry> Entries = new();
}
/// <summary>
/// Dictates a list of items that can be spawned.
/// </summary>