Dynamic space world generation and debris. (#15120)

* World generation (squash)

* Test fixes.

* command

* o

* Access cleanup.

* Documentation touchups.

* Use a prototype serializer for BiomeSelectionComponent

* Struct enumerator in SimpleFloorPlanPopulatorSystem

* Safety margins around PoissonDiskSampler, cookie acquisition methodologies

* Struct enumerating PoissonDiskSampler; internal side

* Struct enumerating PoissonDiskSampler: Finish it

* Update WorldgenConfigSystem.cs

awa

---------

Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
Co-authored-by: 20kdc <asdd2808@gmail.com>
This commit is contained in:
Moony
2023-05-16 06:36:45 -05:00
committed by GitHub
parent cdb46778dc
commit e91fc652a3
54 changed files with 2748 additions and 1 deletions

View File

@@ -0,0 +1,21 @@
using Content.Server.Worldgen.Systems.Biomes;
using Content.Server.Worldgen.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.Worldgen.Components;
/// <summary>
/// This is used for selecting the biome(s) to be used during world generation.
/// </summary>
[RegisterComponent]
[Access(typeof(BiomeSelectionSystem))]
public sealed class BiomeSelectionComponent : Component
{
/// <summary>
/// The list of biomes available to this selector.
/// </summary>
/// <remarks>This is always sorted by priority after ComponentStartup.</remarks>
[DataField("biomes", required: true,
customTypeSerializer: typeof(PrototypeIdListSerializer<BiomePrototype>))] public List<string> Biomes = new();
}

View File

@@ -0,0 +1,27 @@
using Content.Server.Worldgen.Prototypes;
using Content.Server.Worldgen.Systems.Carvers;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Worldgen.Components.Carvers;
/// <summary>
/// This is used for carving out empty space in the game world, providing byways through the debris field.
/// </summary>
[RegisterComponent]
[Access(typeof(NoiseRangeCarverSystem))]
public sealed class NoiseRangeCarverComponent : Component
{
/// <summary>
/// The noise channel to use as a density controller.
/// </summary>
/// <remarks>This noise channel should be mapped to exactly the range [0, 1] unless you want a lot of warnings in the log.</remarks>
[DataField("noiseChannel", customTypeSerializer: typeof(PrototypeIdSerializer<NoiseChannelPrototype>))]
public string NoiseChannel { get; } = default!;
/// <summary>
/// The index of ranges in which to cut debris generation.
/// </summary>
[DataField("ranges", required: true)]
public List<Vector2> Ranges { get; } = default!;
}

View File

@@ -0,0 +1,37 @@
using Content.Server.Worldgen.Systems.Debris;
using Content.Shared.Maps;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
namespace Content.Server.Worldgen.Components.Debris;
/// <summary>
/// This is used for constructing asteroid debris.
/// </summary>
[RegisterComponent]
[Access(typeof(BlobFloorPlanBuilderSystem))]
public sealed class BlobFloorPlanBuilderComponent : Component
{
/// <summary>
/// The probability that placing a floor tile will add up to three-four neighboring tiles as well.
/// </summary>
[DataField("blobDrawProb")] public float BlobDrawProb;
/// <summary>
/// The maximum radius for the structure.
/// </summary>
[DataField("radius", required: true)] public float Radius;
/// <summary>
/// The tiles to be used for the floor plan.
/// </summary>
[DataField("floorTileset", required: true,
customTypeSerializer: typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
public List<string> FloorTileset { get; } = default!;
/// <summary>
/// The number of floor tiles to place when drawing the asteroid layout.
/// </summary>
[DataField("floorPlacements", required: true)]
public int FloorPlacements { get; }
}

View File

@@ -0,0 +1,42 @@
using Content.Server.Worldgen.Prototypes;
using Content.Server.Worldgen.Systems.Debris;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Worldgen.Components.Debris;
/// <summary>
/// This is used for controlling the debris feature placer.
/// </summary>
[RegisterComponent]
[Access(typeof(DebrisFeaturePlacerSystem))]
public sealed class DebrisFeaturePlacerControllerComponent : Component
{
/// <summary>
/// Whether or not to clip debris that would spawn at a location that has a density of zero.
/// </summary>
[DataField("densityClip")] public bool DensityClip = true;
/// <summary>
/// Whether or not entities are already spawned.
/// </summary>
public bool DoSpawns = true;
[DataField("ownedDebris")] public Dictionary<Vector2, EntityUid?> OwnedDebris = new();
/// <summary>
/// The chance spawning a piece of debris will just be cancelled randomly.
/// </summary>
[DataField("randomCancelChance")] public float RandomCancellationChance = 0.1f;
/// <summary>
/// Radius in which there should be no objects for debris to spawn.
/// </summary>
[DataField("safetyZoneRadius")] public float SafetyZoneRadius = 16.0f;
/// <summary>
/// The noise channel to use as a density controller.
/// </summary>
[DataField("densityNoiseChannel", customTypeSerializer: typeof(PrototypeIdSerializer<NoiseChannelPrototype>))]
public string DensityNoiseChannel { get; } = default!;
}

View File

@@ -0,0 +1,44 @@
using Content.Server.Worldgen.Prototypes;
using Content.Server.Worldgen.Systems.Debris;
using Content.Server.Worldgen.Tools;
using Content.Shared.Storage;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Worldgen.Components.Debris;
/// <summary>
/// This is used for selecting debris with a probability determined by a noise channel.
/// Takes priority over SimpleDebrisSelectorComponent and should likely be used in combination.
/// </summary>
[RegisterComponent]
[Access(typeof(NoiseDrivenDebrisSelectorSystem))]
public sealed class NoiseDrivenDebrisSelectorComponent : Component
{
private EntitySpawnCollectionCache? _cache;
/// <summary>
/// The prototype-facing debris table entries.
/// </summary>
[DataField("debrisTable", required: true)]
private List<EntitySpawnEntry> _entries = default!;
/// <summary>
/// The debris entity spawn collection.
/// </summary>
public EntitySpawnCollectionCache CachedDebrisTable
{
get
{
_cache ??= new EntitySpawnCollectionCache(_entries);
return _cache;
}
}
/// <summary>
/// The noise channel to use as a density controller.
/// </summary>
/// <remarks>This noise channel should be mapped to exactly the range [0, 1] unless you want a lot of warnings in the log.</remarks>
[DataField("noiseChannel", customTypeSerializer: typeof(PrototypeIdSerializer<NoiseChannelPrototype>))]
public string NoiseChannel { get; } = default!;
}

View File

@@ -0,0 +1,23 @@
using Content.Server.Worldgen.Systems.Debris;
namespace Content.Server.Worldgen.Components.Debris;
/// <summary>
/// This is used for attaching a piece of debris to it's owning controller.
/// Mostly just syncs deletion.
/// </summary>
[RegisterComponent]
[Access(typeof(DebrisFeaturePlacerSystem))]
public sealed class OwnedDebrisComponent : Component
{
/// <summary>
/// The last location in the controller's internal structure for this debris.
/// </summary>
[DataField("lastKey")] public Vector2 LastKey;
/// <summary>
/// The DebrisFeaturePlacerController-having entity that owns this.
/// </summary>
[DataField("owningController")] public EntityUid OwningController;
}

View File

@@ -0,0 +1,34 @@
using Content.Server.Worldgen.Systems.Debris;
using Content.Server.Worldgen.Tools;
using Content.Shared.Storage;
namespace Content.Server.Worldgen.Components.Debris;
/// <summary>
/// This is used for a very simple debris selection for simple biomes. Just uses a spawn table.
/// </summary>
[RegisterComponent]
[Access(typeof(DebrisFeaturePlacerSystem))]
public sealed class SimpleDebrisSelectorComponent : Component
{
private EntitySpawnCollectionCache? _cache;
/// <summary>
/// The prototype-facing debris table entries.
/// </summary>
[DataField("debrisTable", required: true)]
private List<EntitySpawnEntry> _entries = default!;
/// <summary>
/// The debris entity spawn collection.
/// </summary>
public EntitySpawnCollectionCache CachedDebrisTable
{
get
{
_cache ??= new EntitySpawnCollectionCache(_entries);
return _cache;
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Linq;
using Content.Server.Worldgen.Systems.Debris;
using Content.Server.Worldgen.Tools;
using Content.Shared.Maps;
using Content.Shared.Storage;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
namespace Content.Server.Worldgen.Components.Debris;
/// <summary>
/// This is used for populating a grid with random entities automatically.
/// </summary>
[RegisterComponent]
[Access(typeof(SimpleFloorPlanPopulatorSystem))]
public sealed class SimpleFloorPlanPopulatorComponent : Component
{
private Dictionary<string, EntitySpawnCollectionCache>? _caches;
/// <summary>
/// The prototype facing floor plan populator entries.
/// </summary>
[DataField("entries", required: true,
customTypeSerializer: typeof(PrototypeIdDictionarySerializer<List<EntitySpawnEntry>, ContentTileDefinition>))]
private Dictionary<string, List<EntitySpawnEntry>> _entries = default!;
/// <summary>
/// The spawn collections used to place entities on different tile types.
/// </summary>
[ViewVariables]
public Dictionary<string, EntitySpawnCollectionCache> Caches
{
get
{
if (_caches is null)
{
_caches = _entries
.Select(x =>
new KeyValuePair<string, EntitySpawnCollectionCache>(x.Key,
new EntitySpawnCollectionCache(x.Value)))
.ToDictionary(x => x.Key, x => x.Value);
}
return _caches;
}
}
}

View File

@@ -0,0 +1,21 @@
using Content.Server.Worldgen.Prototypes;
using Content.Server.Worldgen.Systems.GC;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Worldgen.Components.GC;
/// <summary>
/// This is used for whether or not a GCable object is "dirty". Firing GCDirtyEvent on the object is the correct way to
/// set this up.
/// </summary>
[RegisterComponent]
[Access(typeof(GCQueueSystem))]
public sealed class GCAbleObjectComponent : Component
{
/// <summary>
/// Which queue to insert this object into when GCing
/// </summary>
[DataField("queue", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<GCQueuePrototype>))]
public string Queue = default!;
}

View File

@@ -0,0 +1,17 @@
using Content.Server.Worldgen.Systems;
namespace Content.Server.Worldgen.Components;
/// <summary>
/// This is used for marking a chunk as loaded.
/// </summary>
[RegisterComponent]
[Access(typeof(WorldControllerSystem))]
public sealed class LoadedChunkComponent : Component
{
/// <summary>
/// The current list of entities loading this chunk.
/// </summary>
[ViewVariables] public List<EntityUid>? Loaders = null;
}

View File

@@ -0,0 +1,19 @@
using Content.Server.Worldgen.Systems;
namespace Content.Server.Worldgen.Components;
/// <summary>
/// This is used for sending a signal to the entity it's on to load contents whenever a loader gets close enough.
/// Does not support unloading.
/// </summary>
[RegisterComponent]
[Access(typeof(LocalityLoaderSystem))]
public sealed class LocalityLoaderComponent : Component
{
/// <summary>
/// The maximum distance an entity can be from the loader for it to not load.
/// Once a loader is closer than this, the event is fired and this component removed.
/// </summary>
[DataField("loadingDistance")] public int LoadingDistance = 32;
}

View File

@@ -0,0 +1,20 @@
using Content.Server.Worldgen.Prototypes;
using Content.Server.Worldgen.Systems;
namespace Content.Server.Worldgen.Components;
/// <summary>
/// This is used for containing configured noise generators.
/// </summary>
[RegisterComponent]
[Access(typeof(NoiseIndexSystem))]
public sealed class NoiseIndexComponent : Component
{
/// <summary>
/// An index of generators, to avoid having to recreate them every time a noise channel is used.
/// Keyed by noise generator prototype ID.
/// </summary>
[Access(typeof(NoiseIndexSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.None)]
public Dictionary<string, NoiseGenerator> Generators { get; } = new();
}

View File

@@ -0,0 +1,22 @@
using Content.Server.Worldgen.Systems;
namespace Content.Server.Worldgen.Components;
/// <summary>
/// This is used for marking an entity as being a world chunk.
/// </summary>
[RegisterComponent]
[Access(typeof(WorldControllerSystem))]
public sealed class WorldChunkComponent : Component
{
/// <summary>
/// The coordinates of the chunk, in chunk space.
/// </summary>
[DataField("coordinates")] public Vector2i Coordinates;
/// <summary>
/// The map this chunk belongs to.
/// </summary>
[DataField("map")] public EntityUid Map;
}

View File

@@ -0,0 +1,25 @@
using Content.Server.Worldgen.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Worldgen.Components;
/// <summary>
/// This is used for controlling overall world loading, containing an index of all chunks in the map.
/// </summary>
[RegisterComponent]
[Access(typeof(WorldControllerSystem))]
public sealed class WorldControllerComponent : Component
{
/// <summary>
/// The prototype to use for chunks on this world map.
/// </summary>
[DataField("chunkProto", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string ChunkProto = "WorldChunk";
/// <summary>
/// An index of chunks owned by the controller.
/// </summary>
[DataField("chunks")] public Dictionary<Vector2i, EntityUid> Chunks = new();
}

View File

@@ -0,0 +1,18 @@
using Content.Server.Worldgen.Systems;
namespace Content.Server.Worldgen.Components;
/// <summary>
/// This is used for allowing some objects to load the game world.
/// </summary>
[RegisterComponent]
[Access(typeof(WorldControllerSystem))]
public sealed class WorldLoaderComponent : Component
{
/// <summary>
/// The radius in which the loader loads the world.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField("radius")]
public int Radius = 128;
}