+++ /dev/null
-using System.Numerics;
-using System.Text;
-using Content.Shared.Parallax.Biomes;
-using Robust.Client.Graphics;
-using Robust.Client.Input;
-using Robust.Client.ResourceManagement;
-using Robust.Shared.Enums;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-
-namespace Content.Client.Parallax;
-
-public sealed class BiomeDebugOverlay : Overlay
-{
- public override OverlaySpace Space => OverlaySpace.ScreenSpace;
-
- [Dependency] private readonly IEntityManager _entManager = default!;
- [Dependency] private readonly IEyeManager _eyeManager = default!;
- [Dependency] private readonly IInputManager _inputManager = default!;
- [Dependency] private readonly IResourceCache _cache = default!;
- [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
-
- private BiomeSystem _biomes;
- private SharedMapSystem _maps;
-
- private Font _font;
-
- public BiomeDebugOverlay()
- {
- IoCManager.InjectDependencies(this);
-
- _biomes = _entManager.System<BiomeSystem>();
- _maps = _entManager.System<SharedMapSystem>();
-
- _font = new VectorFont(_cache.GetResource<FontResource>("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 12);
- }
-
- protected override bool BeforeDraw(in OverlayDrawArgs args)
- {
- var mapUid = _maps.GetMapOrInvalid(args.MapId);
-
- return _entManager.HasComponent<BiomeComponent>(mapUid);
- }
-
- protected override void Draw(in OverlayDrawArgs args)
- {
- var mouseScreenPos = _inputManager.MouseScreenPosition;
- var mousePos = _eyeManager.ScreenToMap(mouseScreenPos);
-
- if (mousePos.MapId == MapId.Nullspace || mousePos.MapId != args.MapId)
- return;
-
- var mapUid = _maps.GetMapOrInvalid(args.MapId);
-
- if (!_entManager.TryGetComponent(mapUid, out BiomeComponent? biomeComp) || !_entManager.TryGetComponent(mapUid, out MapGridComponent? grid))
- return;
-
- var sb = new StringBuilder();
- var nodePos = _maps.WorldToTile(mapUid, grid, mousePos.Position);
-
- if (_biomes.TryGetEntity(nodePos, biomeComp, (mapUid, grid), out var ent))
- {
- var text = $"Entity: {ent}";
- sb.AppendLine(text);
- }
-
- if (_biomes.TryGetDecals(nodePos, biomeComp.Layers, biomeComp.Seed, (mapUid, grid), out var decals))
- {
- var text = $"Decals: {decals.Count}";
- sb.AppendLine(text);
-
- foreach (var decal in decals)
- {
- var decalText = $"- {decal.ID}";
- sb.AppendLine(decalText);
- }
- }
-
- if (_biomes.TryGetBiomeTile(nodePos, biomeComp.Layers, biomeComp.Seed, (mapUid, grid), out var tile))
- {
- var tileText = $"Tile: {_tileDefManager[tile.Value.TypeId].ID}";
- sb.AppendLine(tileText);
- }
-
- args.ScreenHandle.DrawString(_font, mouseScreenPos.Position + new Vector2(0f, 32f), sb.ToString());
- }
-}
+++ /dev/null
-using Content.Shared.Parallax.Biomes;
-
-namespace Content.Client.Parallax;
-
-public sealed class BiomeSystem : SharedBiomeSystem
-{
-
-}
+++ /dev/null
-using Robust.Client.Graphics;
-using Robust.Shared.Console;
-
-namespace Content.Client.Parallax.Commands;
-
-public sealed class ShowBiomeCommand : LocalizedCommands
-{
- [Dependency] private readonly IOverlayManager _overlayMgr = default!;
-
- public override string Command => "showbiome";
- public override void Execute(IConsoleShell shell, string argStr, string[] args)
- {
- if (_overlayMgr.HasOverlay<BiomeDebugOverlay>())
- {
- _overlayMgr.RemoveOverlay<BiomeDebugOverlay>();
- }
- else
- {
- _overlayMgr.AddOverlay(new BiomeDebugOverlay());
- }
- }
-}
using System.Numerics;
using Content.Client.Parallax.Managers;
using Content.Shared.CCVar;
-using Content.Shared.Parallax.Biomes;
-using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IParallaxManager _manager = default!;
- private readonly SharedMapSystem _mapSystem;
private readonly ParallaxSystem _parallax;
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowWorld;
{
ZIndex = ParallaxSystem.ParallaxZIndex;
IoCManager.InjectDependencies(this);
- _mapSystem = _entManager.System<SharedMapSystem>();
_parallax = _entManager.System<ParallaxSystem>();
}
protected override bool BeforeDraw(in OverlayDrawArgs args)
{
- if (args.MapId == MapId.Nullspace || _entManager.HasComponent<BiomeComponent>(_mapSystem.GetMapOrInvalid(args.MapId)))
+ if (args.MapId == MapId.Nullspace)
return false;
return true;
-using System.Linq;
-using Content.Client.Computer;
using Content.Client.Stylesheets;
-using Content.Client.UserInterface.Controls;
-using Content.Shared.CCVar;
-using Content.Shared.Parallax.Biomes;
-using Content.Shared.Procedural;
-using Content.Shared.Salvage;
-using Content.Shared.Salvage.Expeditions;
-using Content.Shared.Salvage.Expeditions.Modifiers;
-using Content.Shared.Shuttles.BUIStates;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Configuration;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Timing;
-using Robust.Shared.Utility;
namespace Content.Client.Salvage.UI;
-using Content.Shared.Parallax.Biomes.Markers;
+using Content.Shared.Procedural;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
/// Mob layers to pick from.
/// </summary>
[DataField]
- public List<ProtoId<BiomeMarkerLayerPrototype>> MobLayers = new()
+ public List<ProtoId<DungeonConfigPrototype>> MobLayers = new()
{
"Carps",
"Xenos",
/// <summary>
/// Loot layers to pick from.
/// </summary>
- public List<ProtoId<BiomeMarkerLayerPrototype>> LootLayers = new()
+ public List<ProtoId<DungeonConfigPrototype>> LootLayers = new()
{
"OreIron",
"OreQuartz",
using System.Linq;
using Content.Server.Gateway.Components;
-using Content.Server.Parallax;
using Content.Server.Procedural;
using Content.Shared.CCVar;
using Content.Shared.Dataset;
using Content.Shared.Maps;
-using Content.Shared.Parallax.Biomes;
using Content.Shared.Procedural;
+using Content.Shared.Procedural.Components;
using Content.Shared.Salvage;
using Robust.Shared.Configuration;
using Robust.Shared.Map;
};
AddComp(mapUid, restricted);
- _biome.EnsurePlanet(mapUid, _protoManager.Index<BiomeTemplatePrototype>("Continental"), seed);
+ _biome.EnsurePlanet(mapUid, _protoManager.Index("BiomeContinental"), seed);
var grid = Comp<MapGridComponent>(mapUid);
var layer = lootLayers[layerIdx];
lootLayers.RemoveSwap(layerIdx);
- _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
+ _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
}
// - Mobs
var layer = mobLayers[layerIdx];
mobLayers.RemoveSwap(layerIdx);
- _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id);
+ _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id);
}
}
}
using System.Linq;
using Content.Server.Administration;
-using Content.Server.Atmos;
-using Content.Server.Atmos.Components;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Parallax;
+using Content.Server.Procedural;
using Content.Shared.Administration;
-using Content.Shared.Atmos;
-using Content.Shared.Gravity;
-using Content.Shared.Movement.Components;
-using Content.Shared.Parallax.Biomes;
-using Robust.Shared.Audio;
+using Content.Shared.Procedural.Components;
using Robust.Shared.Console;
using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
namespace Content.Server.Maps;
return;
}
- if (!_protoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var biomeTemplate))
+ if (!_protoManager.TryIndex<EntityPrototype>(args[1], out var biomeTemplate))
{
shell.WriteError(Loc.GetString("cmd-planet-map-prototype", ("prototype", args[1])));
return;
if (args.Length == 1)
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entManager), "Map Id");
+ var biomeName = _entManager.ComponentFactory.GetComponentName<BiomeComponent>();
+
if (args.Length == 2)
{
- var options = _protoManager.EnumeratePrototypes<BiomeTemplatePrototype>()
+ var options = _protoManager.EnumeratePrototypes<EntityPrototype>()
+ .Where(o => o.Components.ContainsKey(biomeName))
.Select(o => new CompletionOption(o.ID, "Biome"));
return CompletionResult.FromOptions(options);
}
{
for (var y = -1; y <= 1; y++)
{
+ if (x == 0 && y == 0)
+ continue;
+
var neighbor = node + new Vector2i(x, y);
var neighborCost = OctileDistance(node, neighbor) * args.TileCost?.Invoke(neighbor) ?? 1f;
cameFrom[neighbor] = node;
costSoFar[neighbor] = gScore;
- // Still use octile even for manhattan distance.
- var hScore = OctileDistance(args.End, neighbor) * 1.001f;
+ var hScore = ManhattanDistance(args.End, neighbor);
var fScore = gScore + hScore;
frontier.Enqueue(neighbor, fScore);
}
+++ /dev/null
-using Content.Server.Administration;
-using Content.Shared.Administration;
-using Content.Shared.Parallax.Biomes;
-using Content.Shared.Parallax.Biomes.Layers;
-using Content.Shared.Parallax.Biomes.Markers;
-using Robust.Shared.Console;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-
-namespace Content.Server.Parallax;
-
-public sealed partial class BiomeSystem
-{
- private void InitializeCommands()
- {
- _console.RegisterCommand("biome_clear", Loc.GetString("cmd-biome_clear-desc"), Loc.GetString("cmd-biome_clear-help"), BiomeClearCallback, BiomeClearCallbackHelper);
- _console.RegisterCommand("biome_addlayer", Loc.GetString("cmd-biome_addlayer-desc"), Loc.GetString("cmd-biome_addlayer-help"), AddLayerCallback, AddLayerCallbackHelp);
- _console.RegisterCommand("biome_addmarkerlayer", Loc.GetString("cmd-biome_addmarkerlayer-desc"), Loc.GetString("cmd-biome_addmarkerlayer-desc"), AddMarkerLayerCallback, AddMarkerLayerCallbackHelper);
- }
-
- [AdminCommand(AdminFlags.Fun)]
- private void BiomeClearCallback(IConsoleShell shell, string argstr, string[] args)
- {
- if (args.Length != 1)
- {
- return;
- }
-
- int.TryParse(args[0], out var mapInt);
- var mapId = new MapId(mapInt);
- var mapUid = _mapSystem.GetMapOrInvalid(mapId);
-
- if (_mapSystem.MapExists(mapId) ||
- !TryComp<BiomeComponent>(mapUid, out var biome))
- {
- return;
- }
-
- ClearTemplate(mapUid, biome);
- }
-
- private CompletionResult BiomeClearCallbackHelper(IConsoleShell shell, string[] args)
- {
- if (args.Length == 1)
- {
- return CompletionResult.FromHintOptions(CompletionHelper.Components<BiomeComponent>(args[0], EntityManager), "Biome");
- }
-
- return CompletionResult.Empty;
- }
-
- [AdminCommand(AdminFlags.Fun)]
- private void AddLayerCallback(IConsoleShell shell, string argstr, string[] args)
- {
- if (args.Length < 3 || args.Length > 4)
- {
- return;
- }
-
- if (!int.TryParse(args[0], out var mapInt))
- {
- return;
- }
-
- var mapId = new MapId(mapInt);
- var mapUid = _mapSystem.GetMapOrInvalid(mapId);
-
- if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(mapUid, out var biome))
- {
- return;
- }
-
- if (!ProtoManager.TryIndex<BiomeTemplatePrototype>(args[1], out var template))
- {
- return;
- }
-
- var offset = 0;
-
- if (args.Length == 4)
- {
- int.TryParse(args[3], out offset);
- }
-
- AddTemplate(mapUid, biome, args[2], template, offset);
- }
-
- private CompletionResult AddLayerCallbackHelp(IConsoleShell shell, string[] args)
- {
- if (args.Length == 1)
- {
- return CompletionResult.FromHintOptions(CompletionHelper.MapIds(EntityManager), "Map ID");
- }
-
- if (args.Length == 2)
- {
- return CompletionResult.FromHintOptions(
- CompletionHelper.PrototypeIDs<BiomeTemplatePrototype>(proto: ProtoManager), "Biome template");
- }
-
- if (args.Length == 3)
- {
- if (int.TryParse(args[0], out var mapInt))
- {
- var mapId = new MapId(mapInt);
-
- if (TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
- {
- var results = new List<string>();
-
- foreach (var layer in biome.Layers)
- {
- if (layer is not BiomeDummyLayer dummy)
- continue;
-
- results.Add(dummy.ID);
- }
-
- return CompletionResult.FromHintOptions(results, "Dummy layer ID");
- }
- }
- }
-
- if (args.Length == 4)
- {
- return CompletionResult.FromHint("Seed offset");
- }
-
- return CompletionResult.Empty;
- }
-
- [AdminCommand(AdminFlags.Fun)]
- private void AddMarkerLayerCallback(IConsoleShell shell, string argstr, string[] args)
- {
- if (args.Length != 2)
- {
- return;
- }
-
- if (!int.TryParse(args[0], out var mapInt))
- {
- return;
- }
-
- var mapId = new MapId(mapInt);
-
- if (!_mapSystem.MapExists(mapId) || !TryComp<BiomeComponent>(_mapSystem.GetMapOrInvalid(mapId), out var biome))
- {
- return;
- }
-
- if (!ProtoManager.HasIndex<BiomeMarkerLayerPrototype>(args[1]))
- {
- return;
- }
-
- if (!biome.MarkerLayers.Add(args[1]))
- {
- return;
- }
-
- biome.ForcedMarkerLayers.Add(args[1]);
- }
-
- private CompletionResult AddMarkerLayerCallbackHelper(IConsoleShell shell, string[] args)
- {
- if (args.Length == 1)
- {
- var allQuery = AllEntityQuery<MapComponent, BiomeComponent>();
- var options = new List<CompletionOption>();
-
- while (allQuery.MoveNext(out var mapComp, out _))
- {
- options.Add(new CompletionOption(mapComp.MapId.ToString()));
- }
-
- return CompletionResult.FromHintOptions(options, "Biome");
- }
-
- if (args.Length == 2)
- {
- return CompletionResult.FromHintOptions(
- CompletionHelper.PrototypeIDs<BiomeMarkerLayerPrototype>(proto: ProtoManager), "Marker");
- }
-
- return CompletionResult.Empty;
- }
-}
+++ /dev/null
-using System.Linq;
-using System.Numerics;
-using System.Threading.Tasks;
-using Content.Server.Atmos;
-using Content.Server.Atmos.Components;
-using Content.Server.Atmos.EntitySystems;
-using Content.Server.Decals;
-using Content.Server.Ghost.Roles.Components;
-using Content.Server.Shuttles.Events;
-using Content.Server.Shuttles.Systems;
-using Content.Shared.Atmos;
-using Content.Shared.Decals;
-using Content.Shared.Ghost;
-using Content.Shared.Gravity;
-using Content.Shared.Light.Components;
-using Content.Shared.Parallax.Biomes;
-using Content.Shared.Parallax.Biomes.Layers;
-using Content.Shared.Parallax.Biomes.Markers;
-using Content.Shared.Tag;
-using Microsoft.Extensions.ObjectPool;
-using Robust.Server.Player;
-using Robust.Shared;
-using Robust.Shared.Collections;
-using Robust.Shared.Configuration;
-using Robust.Shared.Console;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Physics;
-using Robust.Shared.Physics.Systems;
-using Robust.Shared.Player;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Threading;
-using Robust.Shared.Utility;
-using ChunkIndicesEnumerator = Robust.Shared.Map.Enumerators.ChunkIndicesEnumerator;
-
-namespace Content.Server.Parallax;
-
-public sealed partial class BiomeSystem : SharedBiomeSystem
-{
- [Dependency] private readonly IConfigurationManager _configManager = default!;
- [Dependency] private readonly IConsoleHost _console = default!;
- [Dependency] private readonly IMapManager _mapManager = default!;
- [Dependency] private readonly IParallelManager _parallel = default!;
- [Dependency] private readonly IPrototypeManager _proto = default!;
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly AtmosphereSystem _atmos = default!;
- [Dependency] private readonly DecalSystem _decals = default!;
- [Dependency] private readonly SharedMapSystem _mapSystem = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly ShuttleSystem _shuttles = default!;
- [Dependency] private readonly TagSystem _tags = default!;
-
- private EntityQuery<BiomeComponent> _biomeQuery;
- private EntityQuery<FixturesComponent> _fixturesQuery;
- private EntityQuery<GhostComponent> _ghostQuery;
- private EntityQuery<TransformComponent> _xformQuery;
-
- private readonly HashSet<EntityUid> _handledEntities = new();
- private const float DefaultLoadRange = 16f;
- private float _loadRange = DefaultLoadRange;
- private static readonly ProtoId<TagPrototype> AllowBiomeLoadingTag = "AllowBiomeLoading";
-
- private List<(Vector2i, Tile)> _tiles = new();
-
- private ObjectPool<HashSet<Vector2i>> _tilePool =
- new DefaultObjectPool<HashSet<Vector2i>>(new SetPolicy<Vector2i>(), 256);
-
- /// <summary>
- /// Load area for chunks containing tiles, decals etc.
- /// </summary>
- private Box2 _loadArea = new(-DefaultLoadRange, -DefaultLoadRange, DefaultLoadRange, DefaultLoadRange);
-
- /// <summary>
- /// Stores the chunks active for this tick temporarily.
- /// </summary>
- private readonly Dictionary<BiomeComponent, HashSet<Vector2i>> _activeChunks = new();
-
- private readonly Dictionary<BiomeComponent,
- Dictionary<string, HashSet<Vector2i>>> _markerChunks = new();
-
- public override void Initialize()
- {
- base.Initialize();
- Log.Level = LogLevel.Debug;
- _biomeQuery = GetEntityQuery<BiomeComponent>();
- _fixturesQuery = GetEntityQuery<FixturesComponent>();
- _ghostQuery = GetEntityQuery<GhostComponent>();
- _xformQuery = GetEntityQuery<TransformComponent>();
- SubscribeLocalEvent<BiomeComponent, MapInitEvent>(OnBiomeMapInit);
- SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
- SubscribeLocalEvent<ShuttleFlattenEvent>(OnShuttleFlatten);
- Subs.CVar(_configManager, CVars.NetMaxUpdateRange, SetLoadRange, true);
- InitializeCommands();
- SubscribeLocalEvent<PrototypesReloadedEventArgs>(ProtoReload);
- }
-
- private void ProtoReload(PrototypesReloadedEventArgs obj)
- {
- if (!obj.ByType.TryGetValue(typeof(BiomeTemplatePrototype), out var reloads))
- return;
-
- var query = AllEntityQuery<BiomeComponent>();
-
- while (query.MoveNext(out var uid, out var biome))
- {
- if (biome.Template == null || !reloads.Modified.TryGetValue(biome.Template, out var proto))
- continue;
-
- SetTemplate(uid, biome, (BiomeTemplatePrototype)proto);
- }
- }
-
- private void SetLoadRange(float obj)
- {
- // Round it up
- _loadRange = MathF.Ceiling(obj / ChunkSize) * ChunkSize;
- _loadArea = new Box2(-_loadRange, -_loadRange, _loadRange, _loadRange);
- }
-
- private void OnBiomeMapInit(EntityUid uid, BiomeComponent component, MapInitEvent args)
- {
- if (component.Seed == -1)
- {
- SetSeed(uid, component, _random.Next());
- }
-
- if (_proto.TryIndex(component.Template, out var biome))
- SetTemplate(uid, component, biome);
-
- var xform = Transform(uid);
- var mapId = xform.MapID;
-
- if (mapId != MapId.Nullspace && HasComp<MapGridComponent>(uid))
- {
- var setTiles = new List<(Vector2i Index, Tile tile)>();
-
- foreach (var grid in _mapManager.GetAllGrids(mapId))
- {
- if (!_fixturesQuery.TryGetComponent(grid.Owner, out var fixtures))
- continue;
-
- // Don't want shuttles flying around now do we.
- _shuttles.Disable(grid.Owner);
- var pTransform = _physics.GetPhysicsTransform(grid.Owner);
-
- foreach (var fixture in fixtures.Fixtures.Values)
- {
- for (var i = 0; i < fixture.Shape.ChildCount; i++)
- {
- var aabb = fixture.Shape.ComputeAABB(pTransform, i);
-
- setTiles.Clear();
- ReserveTiles(uid, aabb, setTiles);
- }
- }
- }
- }
- }
-
- public void SetEnabled(Entity<BiomeComponent?> ent, bool enabled = true)
- {
- if (!Resolve(ent, ref ent.Comp) || ent.Comp.Enabled == enabled)
- return;
-
- ent.Comp.Enabled = enabled;
- Dirty(ent, ent.Comp);
- }
-
- public void SetSeed(EntityUid uid, BiomeComponent component, int seed, bool dirty = true)
- {
- component.Seed = seed;
-
- if (dirty)
- Dirty(uid, component);
- }
-
- public void ClearTemplate(EntityUid uid, BiomeComponent component, bool dirty = true)
- {
- component.Layers.Clear();
- component.Template = null;
-
- if (dirty)
- Dirty(uid, component);
- }
-
- /// <summary>
- /// Sets the <see cref="BiomeComponent.Template"/> and refreshes layers.
- /// </summary>
- public void SetTemplate(EntityUid uid, BiomeComponent component, BiomeTemplatePrototype template, bool dirty = true)
- {
- component.Layers.Clear();
- component.Template = template.ID;
-
- foreach (var layer in template.Layers)
- {
- component.Layers.Add(layer);
- }
-
- if (dirty)
- Dirty(uid, component);
- }
-
- /// <summary>
- /// Adds the specified layer at the specified marker if it exists.
- /// </summary>
- public void AddLayer(EntityUid uid, BiomeComponent component, string id, IBiomeLayer addedLayer, int seedOffset = 0)
- {
- for (var i = 0; i < component.Layers.Count; i++)
- {
- var layer = component.Layers[i];
-
- if (layer is not BiomeDummyLayer dummy || dummy.ID != id)
- continue;
-
- addedLayer.Noise.SetSeed(addedLayer.Noise.GetSeed() + seedOffset);
- component.Layers.Insert(i, addedLayer);
- break;
- }
-
- Dirty(uid, component);
- }
-
- public void AddMarkerLayer(EntityUid uid, BiomeComponent component, string marker)
- {
- component.MarkerLayers.Add(marker);
- Dirty(uid, component);
- }
-
- /// <summary>
- /// Adds the specified template at the specified marker if it exists, withour overriding every layer.
- /// </summary>
- public void AddTemplate(EntityUid uid, BiomeComponent component, string id, BiomeTemplatePrototype template, int seedOffset = 0)
- {
- for (var i = 0; i < component.Layers.Count; i++)
- {
- var layer = component.Layers[i];
-
- if (layer is not BiomeDummyLayer dummy || dummy.ID != id)
- continue;
-
- for (var j = template.Layers.Count - 1; j >= 0; j--)
- {
- var addedLayer = template.Layers[j];
- addedLayer.Noise.SetSeed(addedLayer.Noise.GetSeed() + seedOffset);
- component.Layers.Insert(i, addedLayer);
- }
-
- break;
- }
-
- Dirty(uid, component);
- }
-
- private void OnFTLStarted(ref FTLStartedEvent ev)
- {
- var targetMap = _transform.ToMapCoordinates(ev.TargetCoordinates);
- var targetMapUid = _mapSystem.GetMapOrInvalid(targetMap.MapId);
-
- if (!TryComp<BiomeComponent>(targetMapUid, out var biome))
- return;
-
- var preloadArea = new Vector2(32f, 32f);
- var targetArea = new Box2(targetMap.Position - preloadArea, targetMap.Position + preloadArea);
- Preload(targetMapUid, biome, targetArea);
- }
-
- private void OnShuttleFlatten(ref ShuttleFlattenEvent ev)
- {
- if (!TryComp<BiomeComponent>(ev.MapUid, out var biome) ||
- !TryComp<MapGridComponent>(ev.MapUid, out var grid))
- {
- return;
- }
-
- var tiles = new List<(Vector2i Index, Tile Tile)>();
-
- foreach (var aabb in ev.AABBs)
- {
- for (var x = Math.Floor(aabb.Left); x <= Math.Ceiling(aabb.Right); x++)
- {
- for (var y = Math.Floor(aabb.Bottom); y <= Math.Ceiling(aabb.Top); y++)
- {
- var index = new Vector2i((int)x, (int)y);
- var chunk = SharedMapSystem.GetChunkIndices(index, ChunkSize);
-
- var mod = biome.ModifiedTiles.GetOrNew(chunk * ChunkSize);
-
- if (!mod.Add(index) || !TryGetBiomeTile(index, biome.Layers, biome.Seed, (ev.MapUid, grid), out var tile))
- continue;
-
- // If we flag it as modified then the tile is never set so need to do it ourselves.
- tiles.Add((index, tile.Value));
- }
- }
- }
-
- _mapSystem.SetTiles(ev.MapUid, grid, tiles);
- }
-
- /// <summary>
- /// Preloads biome for the specified area.
- /// </summary>
- public void Preload(EntityUid uid, BiomeComponent component, Box2 area)
- {
- var markers = component.MarkerLayers;
- var goobers = _markerChunks.GetOrNew(component);
-
- foreach (var layer in markers)
- {
- var proto = ProtoManager.Index(layer);
- var enumerator = new ChunkIndicesEnumerator(area, proto.Size);
-
- while (enumerator.MoveNext(out var chunk))
- {
- var chunkOrigin = chunk * proto.Size;
- var layerChunks = goobers.GetOrNew(proto.ID);
- layerChunks.Add(chunkOrigin.Value);
- }
- }
- }
-
- private bool CanLoad(EntityUid uid)
- {
- return !_ghostQuery.HasComp(uid) || _tags.HasTag(uid, AllowBiomeLoadingTag);
- }
-
- public override void Update(float frameTime)
- {
- base.Update(frameTime);
- var biomes = AllEntityQuery<BiomeComponent>();
-
- while (biomes.MoveNext(out var biome))
- {
- if (biome.LifeStage < ComponentLifeStage.Running)
- continue;
-
- _activeChunks.Add(biome, _tilePool.Get());
- _markerChunks.GetOrNew(biome);
- }
-
- // Get chunks in range
- foreach (var pSession in Filter.GetAllPlayers(_playerManager))
- {
- if (_xformQuery.TryGetComponent(pSession.AttachedEntity, out var xform) &&
- _handledEntities.Add(pSession.AttachedEntity.Value) &&
- _biomeQuery.TryGetComponent(xform.MapUid, out var biome) &&
- biome.Enabled &&
- CanLoad(pSession.AttachedEntity.Value))
- {
- var worldPos = _transform.GetWorldPosition(xform);
- AddChunksInRange(biome, worldPos);
-
- foreach (var layer in biome.MarkerLayers)
- {
- var layerProto = ProtoManager.Index(layer);
- AddMarkerChunksInRange(biome, worldPos, layerProto);
- }
- }
-
- foreach (var viewer in pSession.ViewSubscriptions)
- {
- if (!_handledEntities.Add(viewer) ||
- !_xformQuery.TryGetComponent(viewer, out xform) ||
- !_biomeQuery.TryGetComponent(xform.MapUid, out biome) ||
- !biome.Enabled ||
- !CanLoad(viewer))
- {
- continue;
- }
-
- var worldPos = _transform.GetWorldPosition(xform);
- AddChunksInRange(biome, worldPos);
-
- foreach (var layer in biome.MarkerLayers)
- {
- var layerProto = ProtoManager.Index(layer);
- AddMarkerChunksInRange(biome, worldPos, layerProto);
- }
- }
- }
-
- var loadBiomes = AllEntityQuery<BiomeComponent, MapGridComponent>();
-
- while (loadBiomes.MoveNext(out var gridUid, out var biome, out var grid))
- {
- // If not MapInit don't run it.
- if (biome.LifeStage < ComponentLifeStage.Running)
- continue;
-
- if (!biome.Enabled)
- continue;
-
- // Load new chunks
- LoadChunks(biome, gridUid, grid, biome.Seed);
- // Unload old chunks
- UnloadChunks(biome, gridUid, grid, biome.Seed);
- }
-
- _handledEntities.Clear();
-
- foreach (var tiles in _activeChunks.Values)
- {
- _tilePool.Return(tiles);
- }
-
- _activeChunks.Clear();
- _markerChunks.Clear();
- }
-
- private void AddChunksInRange(BiomeComponent biome, Vector2 worldPos)
- {
- var enumerator = new ChunkIndicesEnumerator(_loadArea.Translated(worldPos), ChunkSize);
-
- while (enumerator.MoveNext(out var chunkOrigin))
- {
- _activeChunks[biome].Add(chunkOrigin.Value * ChunkSize);
- }
- }
-
- private void AddMarkerChunksInRange(BiomeComponent biome, Vector2 worldPos, IBiomeMarkerLayer layer)
- {
- // Offset the load area so it's centralised.
- var loadArea = new Box2(0, 0, layer.Size, layer.Size);
- var halfLayer = new Vector2(layer.Size / 2f);
-
- var enumerator = new ChunkIndicesEnumerator(loadArea.Translated(worldPos - halfLayer), layer.Size);
-
- while (enumerator.MoveNext(out var chunkOrigin))
- {
- var lay = _markerChunks[biome].GetOrNew(layer.ID);
- lay.Add(chunkOrigin.Value * layer.Size);
- }
- }
-
- #region Load
-
- /// <summary>
- /// Loads all of the chunks for a particular biome, as well as handle any marker chunks.
- /// </summary>
- private void LoadChunks(
- BiomeComponent component,
- EntityUid gridUid,
- MapGridComponent grid,
- int seed)
- {
- BuildMarkerChunks(component, gridUid, grid, seed);
-
- var active = _activeChunks[component];
-
- foreach (var chunk in active)
- {
- LoadChunkMarkers(component, gridUid, grid, chunk, seed);
-
- if (!component.LoadedChunks.Add(chunk))
- continue;
-
- // Load NOW!
- LoadChunk(component, gridUid, grid, chunk, seed);
- }
- }
-
- /// <summary>
- /// Goes through all marker chunks that haven't been calculated, then calculates what spawns there are and
- /// allocates them to the relevant actual chunks in the biome (marker chunks may be many times larger than biome chunks).
- /// </summary>
- private void BuildMarkerChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, int seed)
- {
- var markers = _markerChunks[component];
- var loadedMarkers = component.LoadedMarkers;
- var idx = 0;
-
- foreach (var (layer, chunks) in markers)
- {
- // I know dictionary ordering isn't guaranteed but I just need something to differentiate seeds.
- idx++;
- var localIdx = idx;
-
- Parallel.ForEach(chunks, new ParallelOptions() { MaxDegreeOfParallelism = _parallel.ParallelProcessCount }, chunk =>
- {
- if (loadedMarkers.TryGetValue(layer, out var mobChunks) && mobChunks.Contains(chunk))
- return;
-
- var forced = component.ForcedMarkerLayers.Contains(layer);
-
- // Make a temporary version and copy back in later.
- var pending = new Dictionary<Vector2i, Dictionary<string, List<Vector2i>>>();
-
- // Essentially get the seed + work out a buffer to adjacent chunks so we don't
- // inadvertantly spawn too many near the edges.
- var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
- var markerSeed = seed + chunk.X * ChunkSize + chunk.Y + localIdx;
- var rand = new Random(markerSeed);
- var buffer = (int)(layerProto.Radius / 2f);
- var bounds = new Box2i(chunk + buffer, chunk + layerProto.Size - buffer);
- var count = (int)(bounds.Area / (layerProto.Radius * layerProto.Radius));
- count = Math.Min(count, layerProto.MaxCount);
-
- GetMarkerNodes(gridUid, component, grid, layerProto, forced, bounds, count, rand,
- out var spawnSet, out var existing);
-
- // Forcing markers to spawn so delete any that were found to be in the way.
- if (forced && existing.Count > 0)
- {
- // Lock something so we can delete these safely.
- lock (component.PendingMarkers)
- {
- foreach (var ent in existing)
- {
- Del(ent);
- }
- }
- }
-
- foreach (var node in spawnSet.Keys)
- {
- var chunkOrigin = SharedMapSystem.GetChunkIndices(node, ChunkSize) * ChunkSize;
-
- if (!pending.TryGetValue(chunkOrigin, out var pendingMarkers))
- {
- pendingMarkers = new Dictionary<string, List<Vector2i>>();
- pending[chunkOrigin] = pendingMarkers;
- }
-
- if (!pendingMarkers.TryGetValue(layer, out var layerMarkers))
- {
- layerMarkers = new List<Vector2i>();
- pendingMarkers[layer] = layerMarkers;
- }
-
- layerMarkers.Add(node);
- }
-
- lock (loadedMarkers)
- {
- if (!loadedMarkers.TryGetValue(layer, out var lockMobChunks))
- {
- lockMobChunks = new HashSet<Vector2i>();
- loadedMarkers[layer] = lockMobChunks;
- }
-
- lockMobChunks.Add(chunk);
-
- foreach (var (chunkOrigin, layers) in pending)
- {
- if (!component.PendingMarkers.TryGetValue(chunkOrigin, out var lockMarkers))
- {
- lockMarkers = new Dictionary<string, List<Vector2i>>();
- component.PendingMarkers[chunkOrigin] = lockMarkers;
- }
-
- foreach (var (lockLayer, nodes) in layers)
- {
- lockMarkers[lockLayer] = nodes;
- }
- }
- }
- });
- }
-
- component.ForcedMarkerLayers.Clear();
- }
-
- /// <summary>
- /// Gets the marker nodes for the specified area.
- /// </summary>
- /// <param name="emptyTiles">Should we include empty tiles when determine markers (e.g. if they are yet to be loaded)</param>
- public void GetMarkerNodes(
- EntityUid gridUid,
- BiomeComponent biome,
- MapGridComponent grid,
- BiomeMarkerLayerPrototype layerProto,
- bool forced,
- Box2i bounds,
- int count,
- Random rand,
- out Dictionary<Vector2i, string?> spawnSet,
- out HashSet<EntityUid> existingEnts,
- bool emptyTiles = true)
- {
- DebugTools.Assert(count > 0);
- var remainingTiles = _tilePool.Get();
- var nodeEntities = new Dictionary<Vector2i, EntityUid?>();
- var nodeMask = new Dictionary<Vector2i, string?>();
-
- // Okay so originally we picked a random tile and BFS outwards
- // the problem is if you somehow get a cooked frontier then it might drop entire veins
- // hence we'll grab all valid tiles up front and use that as possible seeds.
- // It's hella more expensive but stops issues.
- for (var x = bounds.Left; x < bounds.Right; x++)
- {
- for (var y = bounds.Bottom; y < bounds.Top; y++)
- {
- var node = new Vector2i(x, y);
-
- // Empty tile, skip if relevant.
- if (!emptyTiles && (!_mapSystem.TryGetTile(grid, node, out var tile) || tile.IsEmpty))
- continue;
-
- // Check if it's a valid spawn, if so then use it.
- var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, node);
- enumerator.MoveNext(out var existing);
-
- if (!forced && existing != null)
- continue;
-
- // Check if mask matches // anything blocking.
- TryGetEntity(node, biome, (gridUid, grid), out var proto);
-
- // If there's an existing entity and it doesn't match the mask then skip.
- if (layerProto.EntityMask.Count > 0 &&
- (proto == null ||
- !layerProto.EntityMask.ContainsKey(proto)))
- {
- continue;
- }
-
- // If it's just a flat spawn then just check for anything blocking.
- if (proto != null && layerProto.Prototype != null)
- {
- continue;
- }
-
- DebugTools.Assert(layerProto.EntityMask.Count == 0 || !string.IsNullOrEmpty(proto));
- remainingTiles.Add(node);
- nodeEntities.Add(node, existing);
- nodeMask.Add(node, proto);
- }
- }
-
- var frontier = new ValueList<Vector2i>(32);
- // TODO: Need poisson but crashes whenever I use moony's due to inputs or smth idk
- // Get the total amount of groups to spawn across the entire chunk.
- // We treat a null entity mask as requiring nothing else on the tile
-
- spawnSet = new Dictionary<Vector2i, string?>();
- existingEnts = new HashSet<EntityUid>();
-
- // Iterate the group counts and pathfind out each group.
- for (var i = 0; i < count; i++)
- {
- var groupSize = rand.Next(layerProto.MinGroupSize, layerProto.MaxGroupSize + 1);
-
- // While we have remaining tiles keep iterating
- while (groupSize > 0 && remainingTiles.Count > 0)
- {
- var startNode = rand.PickAndTake(remainingTiles);
- frontier.Clear();
- frontier.Add(startNode);
-
- // This essentially may lead to a vein being split in multiple areas but the count matters more than position.
- while (frontier.Count > 0 && groupSize > 0)
- {
- // Need to pick a random index so we don't just get straight lines of ores.
- var frontierIndex = rand.Next(frontier.Count);
- var node = frontier[frontierIndex];
- frontier.RemoveSwap(frontierIndex);
- remainingTiles.Remove(node);
-
- // Add neighbors if they're valid, worst case we add no more and pick another random seed tile.
- for (var x = -1; x <= 1; x++)
- {
- for (var y = -1; y <= 1; y++)
- {
- var neighbor = new Vector2i(node.X + x, node.Y + y);
-
- if (frontier.Contains(neighbor) || !remainingTiles.Contains(neighbor))
- continue;
-
- frontier.Add(neighbor);
- }
- }
-
- // Tile valid salad so add it.
- var mask = nodeMask[node];
- spawnSet.Add(node, mask);
- groupSize--;
-
- if (nodeEntities.TryGetValue(node, out var existing))
- {
- Del(existing);
- }
- }
- }
-
- if (groupSize > 0)
- {
- Log.Warning($"Found remaining group size for ore veins!");
- }
- }
-
- _tilePool.Return(remainingTiles);
- }
-
- /// <summary>
- /// Loads the pre-deteremined marker nodes for a particular chunk.
- /// This is calculated in <see cref="BuildMarkerChunks"/>
- /// </summary>
- /// <remarks>
- /// Note that the marker chunks do not correspond to this chunk.
- /// </remarks>
- private void LoadChunkMarkers(
- BiomeComponent component,
- EntityUid gridUid,
- MapGridComponent grid,
- Vector2i chunk,
- int seed)
- {
- // Load any pending marker tiles first.
- if (!component.PendingMarkers.TryGetValue(chunk, out var layers))
- return;
-
- // This needs to be done separately in case we try to add a marker layer and want to force it on existing
- // loaded chunks.
- component.ModifiedTiles.TryGetValue(chunk, out var modified);
- modified ??= _tilePool.Get();
-
- foreach (var (layer, nodes) in layers)
- {
- var layerProto = ProtoManager.Index<BiomeMarkerLayerPrototype>(layer);
-
- foreach (var node in nodes)
- {
- if (modified.Contains(node))
- continue;
-
- // Need to ensure the tile under it has loaded for anchoring.
- if (TryGetBiomeTile(node, component.Layers, seed, (gridUid, grid), out var tile))
- {
- _mapSystem.SetTile(gridUid, grid, node, tile.Value);
- }
-
- string? prototype;
-
- if (TryGetEntity(node, component, (gridUid, grid), out var proto) &&
- layerProto.EntityMask.TryGetValue(proto, out var maskedProto))
- {
- prototype = maskedProto;
- }
- else
- {
- prototype = layerProto.Prototype;
- }
-
- // If it is a ghost role then purge it
- // TODO: This is *kind* of a bandaid but natural mobs spawns needs a lot more work.
- // Ideally we'd just have ghost role and non-ghost role variants for some stuff.
- var uid = EntityManager.CreateEntityUninitialized(prototype, _mapSystem.GridTileToLocal(gridUid, grid, node));
- RemComp<GhostTakeoverAvailableComponent>(uid);
- RemComp<GhostRoleComponent>(uid);
- EntityManager.InitializeAndStartEntity(uid);
- modified.Add(node);
- }
- }
-
- if (modified.Count == 0)
- {
- component.ModifiedTiles.Remove(chunk);
- _tilePool.Return(modified);
- }
-
- component.PendingMarkers.Remove(chunk);
- }
-
- /// <summary>
- /// Loads a particular queued chunk for a biome.
- /// </summary>
- private void LoadChunk(
- BiomeComponent component,
- EntityUid gridUid,
- MapGridComponent grid,
- Vector2i chunk,
- int seed)
- {
- component.ModifiedTiles.TryGetValue(chunk, out var modified);
- modified ??= _tilePool.Get();
- _tiles.Clear();
-
- // Set tiles first
- for (var x = 0; x < ChunkSize; x++)
- {
- for (var y = 0; y < ChunkSize; y++)
- {
- var indices = new Vector2i(x + chunk.X, y + chunk.Y);
-
- // Pass in null so we don't try to get the tileref.
- if (modified.Contains(indices))
- continue;
-
- // If there's existing data then don't overwrite it.
- if (_mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
- continue;
-
- if (!TryGetBiomeTile(indices, component.Layers, seed, (gridUid, grid), out var biomeTile))
- continue;
-
- _tiles.Add((indices, biomeTile.Value));
- }
- }
-
- _mapSystem.SetTiles(gridUid, grid, _tiles);
- _tiles.Clear();
-
- // Now do entities
- var loadedEntities = new Dictionary<EntityUid, Vector2i>();
- component.LoadedEntities.Add(chunk, loadedEntities);
-
- for (var x = 0; x < ChunkSize; x++)
- {
- for (var y = 0; y < ChunkSize; y++)
- {
- var indices = new Vector2i(x + chunk.X, y + chunk.Y);
-
- if (modified.Contains(indices))
- continue;
-
- // Don't mess with anything that's potentially anchored.
- var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
-
- if (anchored.MoveNext(out _) || !TryGetEntity(indices, component, (gridUid, grid), out var entPrototype))
- continue;
-
- // TODO: Fix non-anchored ents spawning.
- // Just track loaded chunks for now.
- var ent = Spawn(entPrototype, _mapSystem.GridTileToLocal(gridUid, grid, indices));
-
- // At least for now unless we do lookups or smth, only work with anchoring.
- if (_xformQuery.TryGetComponent(ent, out var xform) && !xform.Anchored)
- {
- _transform.AnchorEntity((ent, xform), (gridUid, grid), indices);
- }
-
- loadedEntities.Add(ent, indices);
- }
- }
-
- // Decals
- var loadedDecals = new Dictionary<uint, Vector2i>();
- component.LoadedDecals.Add(chunk, loadedDecals);
-
- for (var x = 0; x < ChunkSize; x++)
- {
- for (var y = 0; y < ChunkSize; y++)
- {
- var indices = new Vector2i(x + chunk.X, y + chunk.Y);
-
- if (modified.Contains(indices))
- continue;
-
- // Don't mess with anything that's potentially anchored.
- var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
-
- if (anchored.MoveNext(out _) || !TryGetDecals(indices, component.Layers, seed, (gridUid, grid), out var decals))
- continue;
-
- foreach (var decal in decals)
- {
- if (!_decals.TryAddDecal(decal.ID, new EntityCoordinates(gridUid, decal.Position), out var dec))
- continue;
-
- loadedDecals.Add(dec, indices);
- }
- }
- }
-
- if (modified.Count == 0)
- {
- _tilePool.Return(modified);
- component.ModifiedTiles.Remove(chunk);
- }
- else
- {
- component.ModifiedTiles[chunk] = modified;
- }
- }
-
- #endregion
-
- #region Unload
-
- /// <summary>
- /// Handles all of the queued chunk unloads for a particular biome.
- /// </summary>
- private void UnloadChunks(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, int seed)
- {
- var active = _activeChunks[component];
- List<(Vector2i, Tile)>? tiles = null;
-
- foreach (var chunk in component.LoadedChunks)
- {
- if (active.Contains(chunk) || !component.LoadedChunks.Remove(chunk))
- continue;
-
- // Unload NOW!
- tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize);
- UnloadChunk(component, gridUid, grid, chunk, seed, tiles);
- }
- }
-
- /// <summary>
- /// Unloads a specific biome chunk.
- /// </summary>
- private void UnloadChunk(BiomeComponent component, EntityUid gridUid, MapGridComponent grid, Vector2i chunk, int seed, List<(Vector2i, Tile)> tiles)
- {
- // Reverse order to loading
- component.ModifiedTiles.TryGetValue(chunk, out var modified);
- modified ??= new HashSet<Vector2i>();
-
- // Delete decals
- foreach (var (dec, indices) in component.LoadedDecals[chunk])
- {
- // If we couldn't remove it then flag the tile to never be touched.
- if (!_decals.RemoveDecal(gridUid, dec))
- {
- modified.Add(indices);
- }
- }
-
- component.LoadedDecals.Remove(chunk);
-
- // Delete entities
- // Ideally any entities that aren't modified just get deleted and re-generated later
- // This is because if we want to save the map (e.g. persistent server) it makes the file much smaller
- // and also if the map is enormous will make stuff like physics broadphase much faster
- var xformQuery = GetEntityQuery<TransformComponent>();
-
- foreach (var (ent, tile) in component.LoadedEntities[chunk])
- {
- if (Deleted(ent) || !xformQuery.TryGetComponent(ent, out var xform))
- {
- modified.Add(tile);
- continue;
- }
-
- // It's moved
- var entTile = _mapSystem.LocalToTile(gridUid, grid, xform.Coordinates);
-
- if (!xform.Anchored || entTile != tile)
- {
- modified.Add(tile);
- continue;
- }
-
- if (!EntityManager.IsDefault(ent))
- {
- modified.Add(tile);
- continue;
- }
-
- Del(ent);
- }
-
- component.LoadedEntities.Remove(chunk);
-
- // Unset tiles (if the data is custom)
-
- for (var x = 0; x < ChunkSize; x++)
- {
- for (var y = 0; y < ChunkSize; y++)
- {
- var indices = new Vector2i(x + chunk.X, y + chunk.Y);
-
- if (modified.Contains(indices))
- continue;
-
- // Don't mess with anything that's potentially anchored.
- var anchored = _mapSystem.GetAnchoredEntitiesEnumerator(gridUid, grid, indices);
-
- if (anchored.MoveNext(out _))
- {
- modified.Add(indices);
- continue;
- }
-
- // If it's default data unload the tile.
- if (!TryGetBiomeTile(indices, component.Layers, seed, null, out var biomeTile) ||
- _mapSystem.TryGetTileRef(gridUid, grid, indices, out var tileRef) && tileRef.Tile != biomeTile.Value)
- {
- modified.Add(indices);
- continue;
- }
-
- tiles.Add((indices, Tile.Empty));
- }
- }
-
- _mapSystem.SetTiles(gridUid, grid, tiles);
- tiles.Clear();
- component.LoadedChunks.Remove(chunk);
-
- if (modified.Count == 0)
- {
- component.ModifiedTiles.Remove(chunk);
- }
- else
- {
- component.ModifiedTiles[chunk] = modified;
- }
- }
-
- #endregion
-
- /// <summary>
- /// Creates a simple planet setup for a map.
- /// </summary>
- public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null)
- {
- if (!Resolve(mapUid, ref metadata))
- return;
-
- EnsureComp<MapGridComponent>(mapUid);
- var biome = EntityManager.ComponentFactory.GetComponent<BiomeComponent>();
- seed ??= _random.Next();
- SetSeed(mapUid, biome, seed.Value, false);
- SetTemplate(mapUid, biome, biomeTemplate, false);
- AddComp(mapUid, biome, true);
- Dirty(mapUid, biome, metadata);
-
- var gravity = EnsureComp<GravityComponent>(mapUid);
- gravity.Enabled = true;
- gravity.Inherent = true;
- Dirty(mapUid, gravity, metadata);
-
- // Day lighting
- // Daylight: #D8B059
- // Midday: #E6CB8B
- // Moonlight: #2b3143
- // Lava: #A34931
- var light = EnsureComp<MapLightComponent>(mapUid);
- light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059");
- Dirty(mapUid, light, metadata);
-
- EnsureComp<RoofComponent>(mapUid);
-
- EnsureComp<LightCycleComponent>(mapUid);
-
- EnsureComp<SunShadowComponent>(mapUid);
- EnsureComp<SunShadowCycleComponent>(mapUid);
-
- var moles = new float[Atmospherics.AdjustedNumberOfGases];
- moles[(int)Gas.Oxygen] = 21.824779f;
- moles[(int)Gas.Nitrogen] = 82.10312f;
-
- var mixture = new GasMixture(moles, Atmospherics.T20C);
-
- _atmos.SetMapAtmosphere(mapUid, false, mixture);
- }
-
- /// <summary>
- /// Sets the specified tiles as relevant and marks them as modified.
- /// </summary>
- public void ReserveTiles(EntityUid mapUid, Box2 bounds, List<(Vector2i Index, Tile Tile)> tiles, BiomeComponent? biome = null, MapGridComponent? mapGrid = null)
- {
- if (!Resolve(mapUid, ref biome, ref mapGrid, false))
- return;
-
- foreach (var tileSet in _mapSystem.GetLocalTilesIntersecting(mapUid, mapGrid, bounds, false))
- {
- Vector2i chunkOrigin;
- HashSet<Vector2i> modified;
-
- // Existing, ignore
- if (_mapSystem.TryGetTileRef(mapUid, mapGrid, tileSet.GridIndices, out var existingRef) && !existingRef.Tile.IsEmpty)
- {
- chunkOrigin = SharedMapSystem.GetChunkIndices(tileSet.GridIndices, ChunkSize) * ChunkSize;
- modified = biome.ModifiedTiles.GetOrNew(chunkOrigin);
- modified.Add(tileSet.GridIndices);
- continue;
- }
-
- if (!TryGetBiomeTile(tileSet.GridIndices, biome.Layers, biome.Seed, (mapUid, mapGrid), out var tile))
- {
- continue;
- }
-
- chunkOrigin = SharedMapSystem.GetChunkIndices(tileSet.GridIndices, ChunkSize) * ChunkSize;
- modified = biome.ModifiedTiles.GetOrNew(chunkOrigin);
- modified.Add(tileSet.GridIndices);
- tiles.Add((tileSet.GridIndices, tile.Value));
- }
-
- _mapSystem.SetTiles(mapUid, mapGrid, tiles);
- }
-}
--- /dev/null
+using Content.Server.Administration;
+using Content.Shared.Administration;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.Components;
+using Robust.Shared.Console;
+using Robust.Shared.Map;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Procedural;
+
+public sealed partial class BiomeSystem
+{
+ [AdminCommand(AdminFlags.Mapping)]
+ public sealed class BiomeAddLayerCommand : LocalizedEntityCommands
+ {
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
+ public override string Command => "biome_addlayer";
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length != 3)
+ {
+ shell.WriteError(Help);
+ return;
+ }
+
+ if (!int.TryParse(args[0], out var entInt))
+ {
+ return;
+ }
+
+ var entId = new EntityUid(entInt);
+
+ if (!EntityManager.TryGetComponent(entId, out BiomeComponent? biome))
+ {
+ return;
+ }
+
+ if (!_protoManager.TryIndex(args[2], out DungeonConfigPrototype? config))
+ {
+ return;
+ }
+
+ var system = EntityManager.System<BiomeSystem>();
+ system.AddLayer((entId, biome), args[1], config);
+ }
+
+ public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ {
+ switch (args.Length)
+ {
+ case 1:
+ return CompletionResult.FromOptions(CompletionHelper.Components<BiomeComponent>(args[0]));
+ case 2:
+ return CompletionResult.FromHint("layerId");
+ case 3:
+ return CompletionResult.FromOptions(CompletionHelper.PrototypeIDs<DungeonConfigPrototype>());
+ }
+
+ return CompletionResult.Empty;
+ }
+ }
+
+ [AdminCommand(AdminFlags.Mapping)]
+ public sealed class BiomeRemoveLayerCommand : LocalizedEntityCommands
+ {
+ public override string Command => "biome_removelayer";
+ public override void Execute(IConsoleShell shell, string argStr, string[] args)
+ {
+ if (args.Length != 2)
+ {
+ shell.WriteError(Help);
+ return;
+ }
+
+ if (!int.TryParse(args[0], out var entInt))
+ {
+ return;
+ }
+
+ var entId = new EntityUid(entInt);
+
+ if (!EntityManager.TryGetComponent(entId, out BiomeComponent? biome))
+ {
+ return;
+ }
+
+ var system = EntityManager.System<BiomeSystem>();
+ system.RemoveLayer((entId, biome), args[1]);
+ }
+
+ public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
+ {
+ switch (args.Length)
+ {
+ case 0:
+ return CompletionResult.FromOptions(CompletionHelper.Components<BiomeComponent>(args[0]));
+ case 1:
+ return CompletionResult.FromHint("layerId");
+ }
+
+ return CompletionResult.Empty;
+ }
+ }
+}
--- /dev/null
+using Content.Shared.Atmos;
+using Content.Shared.Gravity;
+using Content.Shared.Light.Components;
+using Content.Shared.Procedural.Components;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server.Procedural;
+
+public sealed partial class BiomeSystem
+{
+ /// <summary>
+ /// Copies the biomecomponent to the specified map.
+ /// </summary>
+ public BiomeComponent? AddBiome(Entity<BiomeComponent?> mapUid, EntProtoId biomeTemplate, int? seed = null)
+ {
+ if (!_protomanager.Index(biomeTemplate).Components.TryGetComponent(Factory.GetComponentName<BiomeComponent>(), out var template))
+ {
+ return null;
+ }
+
+ var biome = Factory.GetComponent<BiomeComponent>();
+ var biomeObj = (object)biome;
+ _serManager.CopyTo(template, ref biomeObj, notNullableOverride: true);
+ seed ??= _random.Next();
+ biome.Seed = seed.Value;
+ AddComp(mapUid, biome, true);
+ return biome;
+ }
+
+ /// <summary>
+ /// Creates a simple planet setup for a map.
+ /// </summary>
+ public void EnsurePlanet(EntityUid mapUid, EntProtoId biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null)
+ {
+ if (!Resolve(mapUid, ref metadata))
+ return;
+
+ EnsureComp<MapGridComponent>(mapUid);
+ AddBiome(mapUid, biomeTemplate, seed);
+ var gravity = EnsureComp<GravityComponent>(mapUid);
+ gravity.Enabled = true;
+ gravity.Inherent = true;
+ Dirty(mapUid, gravity, metadata);
+
+ var light = EnsureComp<MapLightComponent>(mapUid);
+ light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059");
+ Dirty(mapUid, light, metadata);
+
+ EnsureComp<RoofComponent>(mapUid);
+
+ EnsureComp<LightCycleComponent>(mapUid);
+
+ EnsureComp<SunShadowComponent>(mapUid);
+ EnsureComp<SunShadowCycleComponent>(mapUid);
+
+ var moles = new float[Atmospherics.AdjustedNumberOfGases];
+ moles[(int)Gas.Oxygen] = 21.824779f;
+ moles[(int)Gas.Nitrogen] = 82.10312f;
+
+ var mixture = new GasMixture(moles, Atmospherics.T20C);
+
+ _atmos.SetMapAtmosphere(mapUid, false, mixture);
+ }
+}
--- /dev/null
+using System.Numerics;
+using System.Threading;
+using System.Threading.Tasks;
+using Content.Server.Atmos.EntitySystems;
+using Content.Server.Decals;
+using Content.Server.Shuttles.Events;
+using Content.Shared.CCVar;
+using Content.Shared.Decals;
+using Content.Shared.Ghost;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.Components;
+using Content.Shared.Procedural.DungeonGenerators;
+using Content.Shared.Sprite;
+using Content.Shared.Tag;
+using Robust.Server.Player;
+using Robust.Shared.Configuration;
+using Robust.Shared.CPUJob.JobQueues;
+using Robust.Shared.CPUJob.JobQueues.Queues;
+using Robust.Shared.Map;
+using Robust.Shared.Map.Components;
+using Robust.Shared.Map.Enumerators;
+using Robust.Shared.Physics.Components;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Serialization.Manager;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Procedural;
+
+public sealed partial class BiomeSystem : EntitySystem
+{
+ /*
+ * Handles loading in biomes around players.
+ * These are essentially chunked-areas that load in dungeons and can also be unloaded.
+ */
+
+ [Dependency] private readonly IConfigurationManager _cfgManager = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IPrototypeManager _protomanager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly ISerializationManager _serManager = default!;
+ [Dependency] private readonly AtmosphereSystem _atmos = default!;
+ [Dependency] private readonly SharedMapSystem _maps = default!;
+ [Dependency] private readonly TagSystem _tags = default!;
+ [Dependency] private readonly SharedTransformSystem _xforms = default!;
+
+ /// <summary>
+ /// Ignored components for default checks
+ /// </summary>
+ public static readonly List<string> IgnoredComponents = new();
+
+ /// <summary>
+ /// Jobs for biomes to load.
+ /// </summary>
+ private JobQueue _biomeQueue = default!;
+
+ private float _loadRange = 1f;
+ private float _loadTime;
+
+ private EntityQuery<GhostComponent> _ghostQuery;
+ private EntityQuery<BiomeComponent> _biomeQuery;
+
+ private static readonly ProtoId<TagPrototype> AllowBiomeLoadingTag = "AllowBiomeLoading";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _ghostQuery = GetEntityQuery<GhostComponent>();
+ _biomeQuery = GetEntityQuery<BiomeComponent>();
+
+ IgnoredComponents.Add(Factory.GetComponentName<RandomSpriteComponent>());
+
+ Subs.CVar(_cfgManager, CCVars.BiomeLoadRange, OnLoadRange, true);
+ Subs.CVar(_cfgManager, CCVars.BiomeLoadTime, OnLoadTime, true);
+
+ SubscribeLocalEvent<FTLStartedEvent>(OnFTLStarted);
+ }
+
+ private void OnLoadTime(float obj)
+ {
+ _biomeQueue = new JobQueue(obj);
+ _loadTime = obj;
+ }
+
+ private void OnLoadRange(float obj)
+ {
+ _loadRange = obj;
+ }
+
+ private void OnFTLStarted(ref FTLStartedEvent ev)
+ {
+ var targetMap = _xforms.ToMapCoordinates(ev.TargetCoordinates);
+ var targetMapUid = _maps.GetMapOrInvalid(targetMap.MapId);
+
+ if (!TryComp<BiomeComponent>(targetMapUid, out var biome))
+ return;
+
+ var preloadArea = new Vector2(32f, 32f);
+ var targetArea = new Box2(targetMap.Position - preloadArea, targetMap.Position + preloadArea);
+ Preload(targetMapUid, biome, (Box2i) targetArea);
+ }
+
+ /// <summary>
+ /// Preloads biome for the specified area.
+ /// </summary>
+ public void Preload(EntityUid uid, BiomeComponent component, Box2i area)
+ {
+ component.PreloadAreas.Add(area);
+ }
+
+ private bool CanLoad(EntityUid uid)
+ {
+ return !_ghostQuery.HasComp(uid) || _tags.HasTag(uid, AllowBiomeLoadingTag);
+ }
+
+ public bool RemoveLayer(Entity<BiomeComponent?> biome, string label)
+ {
+ if (!Resolve(biome.Owner, ref biome.Comp))
+ return false;
+
+ if (!biome.Comp.Layers.ContainsKey(label))
+ {
+ return false;
+ }
+
+ // Technically this can race-condition with adds but uhh tell people to not do that.
+ biome.Comp.PendingRemovals.Add(label);
+ return true;
+ }
+
+ public void AddLayer(Entity<BiomeComponent?> biome, string label, BiomeMetaLayer layer)
+ {
+ if (!Resolve(biome.Owner, ref biome.Comp))
+ return;
+
+ if (!biome.Comp.Layers.TryAdd(label, layer))
+ {
+ Log.Warning($"Tried to add layer {label} to biome {ToPrettyString(biome)} that already has it?");
+ return;
+ }
+ }
+
+ public void AddLayer(Entity<BiomeComponent?> biome, string label, ProtoId<DungeonConfigPrototype> layer)
+ {
+ if (!Resolve(biome.Owner, ref biome.Comp))
+ return;
+
+ var metaLayer = new BiomeMetaLayer()
+ {
+ Dungeon = layer,
+ };
+
+ AddLayer(biome, label, metaLayer);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = AllEntityQuery<BiomeComponent>();
+
+ while (query.MoveNext(out var biome))
+ {
+ // If it's still loading then don't touch the observer bounds.
+ if (biome.Loading)
+ continue;
+
+ biome.LoadedBounds.Clear();
+
+ // Make sure preloads go in.
+ foreach (var preload in biome.PreloadAreas)
+ {
+ biome.LoadedBounds.Add(preload);
+ }
+ }
+
+ // Get all relevant players.
+ foreach (var player in _player.Sessions)
+ {
+ if (player.AttachedEntity != null)
+ {
+ TryAddBiomeBounds(player.AttachedEntity.Value);
+ }
+
+ foreach (var viewer in player.ViewSubscriptions)
+ {
+ TryAddBiomeBounds(viewer);
+ }
+ }
+
+ // Unload first in case we can't catch up.
+ UnloadChunks();
+
+ var loadQuery = AllEntityQuery<BiomeComponent, MapGridComponent>();
+
+ // Check if any biomes are intersected and queue up loads.
+ while (loadQuery.MoveNext(out var uid, out var biome, out var grid))
+ {
+ if (biome.Loading || biome.LoadedBounds.Count == 0)
+ continue;
+
+ biome.Loading = true;
+ var job = new BiomeLoadJob(_loadTime)
+ {
+ Grid = (uid, biome, grid),
+ };
+ _biomeQueue.EnqueueJob(job);
+ }
+
+ // Process jobs.
+ _biomeQueue.Process();
+ }
+
+ private void UnloadChunks()
+ {
+ var query = AllEntityQuery<BiomeComponent, MapGridComponent>();
+
+ while (query.MoveNext(out var uid, out var biome, out var grid))
+ {
+ // Only start unloading if it's currently not loading anything.
+ if (biome.Loading)
+ continue;
+
+ var toUnload = new Dictionary<string, List<Vector2i>>();
+
+ foreach (var (layerId, loadedLayer) in biome.LoadedData)
+ {
+ var layer = biome.Layers[layerId];
+
+ // If it can't unload then ignore it.
+ if (!layer.CanUnload)
+ continue;
+
+ var size = GetSize(_protomanager.Index(layer.Dungeon), layer);
+
+ if (size == null)
+ continue;
+
+ // Go through each loaded chunk and check if they can be unloaded by checking if any players are in range.
+ foreach (var chunk in loadedLayer.Keys)
+ {
+ var chunkBounds = new Box2i(chunk, chunk + size.Value);
+ var canUnload = true;
+
+ foreach (var playerView in biome.LoadedBounds)
+ {
+ // Give a buffer range so we don't immediately unload if we wiggle, we'll just double the load area.
+ var enlarged = playerView.Enlarged((int) _loadRange);
+
+ // Still relevant
+ if (chunkBounds.Intersects(enlarged))
+ {
+ canUnload = false;
+ break;
+ }
+ }
+
+ if (!canUnload)
+ continue;
+
+ toUnload.GetOrNew(layerId).Add(chunk);
+ }
+ }
+
+ if (biome.PendingRemovals.Count > 0)
+ {
+ foreach (var label in biome.PendingRemovals)
+ {
+ var bounds = toUnload.GetOrNew(label);
+
+ foreach (var chunkOrigin in biome.LoadedData[label].Keys)
+ {
+ bounds.Add(chunkOrigin);
+ }
+ }
+ }
+
+ if (toUnload.Count == 0)
+ continue;
+
+ // Queue up unloads.
+ biome.Loading = true;
+ var job = new BiomeUnloadJob(_loadTime)
+ {
+ Biome = (uid, grid, biome),
+ ToUnload = toUnload,
+ };
+ _biomeQueue.EnqueueJob(job);
+ }
+ }
+
+ /// <summary>
+ /// Gets the full bounds to be loaded. Considers layer dependencies where they may have different chunk sizes.
+ /// </summary>
+ private Box2i GetFullBounds(BiomeComponent component, Box2i bounds)
+ {
+ var result = bounds;
+
+ foreach (var layer in component.Layers.Values)
+ {
+ var layerBounds = GetLayerBounds(layer, result);
+
+ if (layer.DependsOn != null)
+ {
+ foreach (var sub in layer.DependsOn)
+ {
+ var depLayer = component.Layers[sub];
+
+ layerBounds = layerBounds.Union(GetLayerBounds(depLayer, layerBounds));
+ }
+ }
+
+ result = result.Union(layerBounds);
+ }
+
+ return result;
+ }
+
+ /// <summary>
+ /// Tries to add the viewer bounds of this entity for loading.
+ /// </summary>
+ private void TryAddBiomeBounds(EntityUid uid)
+ {
+ if (!CanLoad(uid))
+ return;
+
+ var xform = Transform(uid);
+
+ // No biome to load
+ if (!_biomeQuery.TryComp(xform.MapUid, out var biome))
+ return;
+
+ // Currently already loading.
+ if (biome.Loading)
+ return;
+
+ var center = _xforms.GetWorldPosition(uid);
+
+ var bounds = new Box2i((center - new Vector2(_loadRange, _loadRange)).Floored(), (center + new Vector2(_loadRange, _loadRange)).Floored());
+
+ // If it's moving then preload in that direction
+ if (TryComp(uid, out PhysicsComponent? physics))
+ {
+ bounds = bounds.Union(bounds.Translated((physics.LinearVelocity * 2f).Floored()));
+ }
+
+ var adjustedBounds = GetFullBounds(biome, bounds);
+ biome.LoadedBounds.Add(adjustedBounds);
+ }
+
+ public int? GetSize(DungeonConfigPrototype config, BiomeMetaLayer layer)
+ {
+ var size = layer.Size;
+
+ if (size == null && config.Layers[0] is ChunkDunGen chunkGen)
+ {
+ size = chunkGen.Size;
+ }
+ // No size
+ else
+ {
+ Log.Warning($"Unable to infer chunk size for biome {layer} / config {config.ID}");
+ return null;
+ }
+
+ return size.Value;
+ }
+
+ public Box2i GetLayerBounds(BiomeMetaLayer layer, Box2i layerBounds)
+ {
+ var size = GetSize(_protomanager.Index(layer.Dungeon), layer);
+
+ if (size == null)
+ return Box2i.Empty;
+
+ var chunkSize = new Vector2(size.Value, size.Value);
+
+ // Need to round the bounds to our chunk size to ensure we load whole chunks.
+ // We also need to know the minimum bounds for our dependencies to load.
+ var layerBL = (layerBounds.BottomLeft / chunkSize).Floored() * chunkSize;
+ var layerTR = (layerBounds.TopRight / chunkSize).Ceiled() * chunkSize;
+
+ var loadBounds = new Box2i(layerBL.Floored(), layerTR.Ceiled());
+ return loadBounds;
+ }
+}
+
+ public sealed class BiomeLoadJob : Job<bool>
+ {
+ [Dependency] private IEntityManager _entManager = default!;
+ [Dependency] private IPrototypeManager _protoManager = default!;
+
+ private BiomeSystem System = default!;
+ private DungeonSystem DungeonSystem = default!;
+
+ public Entity<BiomeComponent, MapGridComponent> Grid;
+
+ internal ISawmill _sawmill = default!;
+
+ public BiomeLoadJob(double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation)
+ {
+ IoCManager.InjectDependencies(this);
+ System = _entManager.System<BiomeSystem>();
+ DungeonSystem = _entManager.System<DungeonSystem>();
+ }
+
+ protected override async Task<bool> Process()
+ {
+ try
+ {
+ foreach (var bound in Grid.Comp1.LoadedBounds)
+ {
+ foreach (var (layerId, layer) in Grid.Comp1.Layers)
+ {
+ await LoadLayer(layerId, layer, bound);
+ }
+ }
+ }
+ finally
+ {
+ // Finished
+ DebugTools.Assert(Grid.Comp1.Loading);
+ Grid.Comp1.Loading = false;
+ }
+
+ // If we have any preloads then mark those as modified so they persist.
+ foreach (var preload in Grid.Comp1.PreloadAreas)
+ {
+ for (var x = preload.Left; x <= preload.Right; x++)
+ {
+ for (var y = preload.Bottom; y <= preload.Top; y++)
+ {
+ var index = new Vector2i(x, y);
+ Grid.Comp1.ModifiedTiles.Add(index);
+ }
+ }
+
+ await SuspendIfOutOfTime();
+ }
+
+ Grid.Comp1.PreloadAreas.Clear();
+
+ return true;
+ }
+
+ private async Task LoadLayer(string layerId, BiomeMetaLayer layer, Box2i parentBounds)
+ {
+ // Nothing to do
+ var dungeon = _protoManager.Index(layer.Dungeon);
+
+ if (dungeon.Layers.Count == 0)
+ return;
+
+ var loadBounds = System.GetLayerBounds(layer, parentBounds);
+
+ // Make sure our dependencies are loaded first.
+ if (layer.DependsOn != null)
+ {
+ foreach (var sub in layer.DependsOn)
+ {
+ var actualLayer = Grid.Comp1.Layers[sub];
+
+ await LoadLayer(sub, actualLayer, loadBounds);
+ }
+ }
+
+ var size = System.GetSize(dungeon, layer);
+
+ if (size == null)
+ return;
+
+ // The reason we do this is so if we dynamically add similar layers (e.g. we add 3 mob layers at runtime)
+ // they don't all have the same seeds.
+ var layerSeed = Grid.Comp1.Seed + layerId.GetHashCode();
+
+ // Okay all of our dependencies loaded so we can send it.
+ var chunkEnumerator = new NearestChunkEnumerator(loadBounds, size.Value);
+
+ while (chunkEnumerator.MoveNext(out var chunk))
+ {
+ var chunkOrigin = chunk.Value;
+ var layerLoaded = Grid.Comp1.LoadedData.GetOrNew(layerId);
+
+ // Layer already loaded for this chunk.
+ // This can potentially happen if we're moving and the player's bounds changed but some existing chunks remain.
+ if (layerLoaded.ContainsKey(chunkOrigin))
+ {
+ continue;
+ }
+
+ // Load dungeon here async await and all that jaz.
+ var (_, data) = await WaitAsyncTask(DungeonSystem
+ .GenerateDungeonAsync(dungeon, Grid.Owner, Grid.Comp2, chunkOrigin, layerSeed, reservedTiles: Grid.Comp1.ModifiedTiles));
+
+ // If we can unload it then store the data to check for later.
+ if (layer.CanUnload)
+ {
+ layerLoaded.Add(chunkOrigin, data);
+ }
+ }
+ }
+}
+
+public sealed class BiomeUnloadJob : Job<bool>
+{
+ [Dependency] private EntityManager _entManager = default!;
+
+ public Entity<MapGridComponent, BiomeComponent> Biome;
+ public Dictionary<string, List<Vector2i>> ToUnload = default!;
+
+ public BiomeUnloadJob(double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation)
+ {
+ IoCManager.InjectDependencies(this);
+ }
+
+ public BiomeUnloadJob(double maxTime, IStopwatch stopwatch, CancellationToken cancellation = default) : base(maxTime, stopwatch, cancellation)
+ {
+ }
+
+ protected override async Task<bool> Process()
+ {
+ try
+ {
+ var grid = Biome.Comp1;
+ var biome = Biome.Comp2;
+ DebugTools.Assert(biome.Loading);
+ var maps = _entManager.System<SharedMapSystem>();
+ var decals = _entManager.System<DecalSystem>();
+ var lookup = _entManager.System<EntityLookupSystem>();
+ _entManager.TryGetComponent(Biome.Owner, out DecalGridComponent? decalGrid);
+ var forceUnload = _entManager.GetEntityQuery<BiomeForceUnloadComponent>();
+ var entities = new HashSet<EntityUid>();
+ var tiles = new List<(Vector2i, Tile)>();
+
+ foreach (var (layer, chunkOrigins) in ToUnload)
+ {
+ if (!biome.Layers.TryGetValue(layer, out var meta))
+ continue;
+
+ if (!biome.LoadedData.TryGetValue(layer, out var data))
+ continue;
+
+ DebugTools.Assert(meta.CanUnload);
+
+ foreach (var chunk in chunkOrigins)
+ {
+ // Not loaded anymore?
+ if (!data.Remove(chunk, out var loaded))
+ continue;
+
+ tiles.Clear();
+
+ foreach (var (ent, pos) in loaded.Entities)
+ {
+ // Already flagged as modified so go next.
+ if (biome.ModifiedTiles.Contains(pos))
+ {
+ continue;
+ }
+
+ // IsDefault is actually super expensive so really need to run this check in the loop.
+ await SuspendIfOutOfTime();
+
+ if (forceUnload.HasComp(ent))
+ {
+ _entManager.DeleteEntity(ent);
+ continue;
+ }
+
+ // Deleted so counts as modified.
+ if (!_entManager.TransformQuery.TryComp(ent, out var xform))
+ {
+ biome.ModifiedTiles.Add(pos);
+ continue;
+ }
+
+ // If it stayed still and had no data change then keep it.
+ if (pos == xform.LocalPosition.Floored() && xform.GridUid == Biome.Owner && _entManager.IsDefault(ent, BiomeSystem.IgnoredComponents))
+ {
+ _entManager.DeleteEntity(ent);
+ continue;
+ }
+
+ // Need the entity's current tile to be flagged for unloading.
+ if (Biome.Owner == xform.GridUid)
+ {
+ var entTile = maps.LocalToTile(Biome.Owner, grid, xform.Coordinates);
+ biome.ModifiedTiles.Add(entTile);
+ }
+ }
+
+ foreach (var (decal, pos) in loaded.Decals)
+ {
+ // Modified go NEXT
+ if (biome.ModifiedTiles.Contains(pos.Floored()))
+ continue;
+
+ // Should just be able to remove them as you can't actually edit a decal.
+ if (!decals.RemoveDecal(Biome.Owner, decal, decalGrid))
+ {
+ biome.ModifiedTiles.Add(pos.Floored());
+ }
+ }
+
+ await SuspendIfOutOfTime();
+
+ foreach (var (index, tile) in loaded.Tiles)
+ {
+ await SuspendIfOutOfTime();
+
+ if (Biome.Comp2.ModifiedTiles.Contains(index))
+ {
+ continue;
+ }
+
+ if (!maps.TryGetTileRef(Biome.Owner, Biome.Comp1, index, out var tileRef) ||
+ tileRef.Tile != tile)
+ {
+ Biome.Comp2.ModifiedTiles.Add(index);
+ continue;
+ }
+
+ entities.Clear();
+ var tileBounds = lookup.GetLocalBounds(index, Biome.Comp1.TileSize).Enlarged(-0.05f);
+
+ lookup.GetEntitiesIntersecting(Biome.Owner,
+ tileBounds,
+ entities);
+
+ // Still entities remaining so just leave the tile.
+ if (entities.Count > 0)
+ {
+ Biome.Comp2.ModifiedTiles.Add(index);
+ continue;
+ }
+
+ if (decals.GetDecalsIntersecting(Biome.Owner, tileBounds, component: decalGrid).Count > 0)
+ {
+ Biome.Comp2.ModifiedTiles.Add(index);
+ continue;
+ }
+
+ // Clear it
+ tiles.Add((index, Tile.Empty));
+ }
+
+ maps.SetTiles(Biome.Owner, Biome.Comp1, tiles);
+ }
+ }
+ }
+ finally
+ {
+ Biome.Comp2.Loading = false;
+ }
+
+ Biome.Comp2.PendingRemovals.Clear();
+
+ return true;
+ }
+}
if (found)
continue;
- _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
+ var ent = _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile));
+ AddLoadedEntity(tile, ent);
}
}
}
+++ /dev/null
-using System.Threading.Tasks;
-using Content.Server.Parallax;
-using Content.Shared.Maps;
-using Content.Shared.Parallax.Biomes;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.PostGeneration;
-using Robust.Shared.Map;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Procedural.DungeonJob;
-
-public sealed partial class DungeonJob
-{
- /// <summary>
- /// <see cref="BiomeDunGen"/>
- /// </summary>
- private async Task PostGen(BiomeDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
- {
- if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome))
- return;
-
- var biomeSystem = _entManager.System<BiomeSystem>();
-
- var seed = random.Next();
- var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
-
- var tiles = _maps.GetAllTilesEnumerator(_gridUid, _grid);
- while (tiles.MoveNext(out var tileRef))
- {
- var node = tileRef.Value.GridIndices;
-
- if (reservedTiles.Contains(node))
- continue;
-
- if (dunGen.TileMask is not null)
- {
- if (!dunGen.TileMask.Contains(((ContentTileDefinition)_tileDefManager[tileRef.Value.Tile.TypeId]).ID))
- continue;
- }
-
- // Need to set per-tile to override data.
- if (biomeSystem.TryGetTile(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var tile))
- {
- _maps.SetTile(_gridUid, _grid, node, tile.Value);
- }
-
- if (biomeSystem.TryGetDecals(node, indexedBiome.Layers, seed, (_gridUid, _grid), out var decals))
- {
- foreach (var decal in decals)
- {
- _decals.TryAddDecal(decal.ID, new EntityCoordinates(_gridUid, decal.Position), out _);
- }
- }
-
- if (biomeSystem.TryGetEntity(node, indexedBiome.Layers, tile ?? tileRef.Value.Tile, seed, (_gridUid, _grid), out var entityProto))
- {
- var ent = _entManager.SpawnEntity(entityProto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
- var xform = xformQuery.Get(ent);
-
- if (!xform.Comp.Anchored)
- {
- _transform.AnchorEntity(ent, xform);
- }
-
- // TODO: Engine bug with SpawnAtPosition
- DebugTools.Assert(xform.Comp.Anchored);
- }
-
- await SuspendDungeon();
- if (!ValidateResume())
- return;
- }
- }
-}
+++ /dev/null
-using System.Threading.Tasks;
-using Content.Server.Parallax;
-using Content.Shared.Parallax.Biomes;
-using Content.Shared.Parallax.Biomes.Markers;
-using Content.Shared.Procedural;
-using Content.Shared.Procedural.PostGeneration;
-using Content.Shared.Random.Helpers;
-using Robust.Shared.Map;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Procedural.DungeonJob;
-
-public sealed partial class DungeonJob
-{
- /// <summary>
- /// <see cref="BiomeMarkerLayerDunGen"/>
- /// </summary>
- private async Task PostGen(BiomeMarkerLayerDunGen dunGen, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
- {
- // If we're adding biome then disable it and just use for markers.
- if (_entManager.EnsureComponent(_gridUid, out BiomeComponent biomeComp))
- {
- biomeComp.Enabled = false;
- }
-
- var biomeSystem = _entManager.System<BiomeSystem>();
- var weightedRandom = _prototype.Index(dunGen.MarkerTemplate);
- var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
- var templates = new Dictionary<string, int>();
-
- for (var i = 0; i < dunGen.Count; i++)
- {
- var template = weightedRandom.Pick(random);
- var count = templates.GetOrNew(template);
- count++;
- templates[template] = count;
- }
-
- foreach (var (template, count) in templates)
- {
- var markerTemplate = _prototype.Index<BiomeMarkerLayerPrototype>(template);
-
- var bounds = new Box2i();
-
- foreach (var tile in dungeon.RoomTiles)
- {
- bounds = bounds.UnionTile(tile);
- }
-
- await SuspendDungeon();
- if (!ValidateResume())
- return;
-
- biomeSystem.GetMarkerNodes(_gridUid, biomeComp, _grid, markerTemplate, true, bounds, count,
- random, out var spawnSet, out var existing, false);
-
- await SuspendDungeon();
- if (!ValidateResume())
- return;
-
- var checkTile = reservedTiles.Count > 0;
-
- foreach (var ent in existing)
- {
- if (checkTile && reservedTiles.Contains(_maps.LocalToTile(_gridUid, _grid, _xformQuery.GetComponent(ent).Coordinates)))
- {
- continue;
- }
-
- _entManager.DeleteEntity(ent);
-
- await SuspendDungeon();
- if (!ValidateResume())
- return;
- }
-
- foreach (var (node, mask) in spawnSet)
- {
- if (reservedTiles.Contains(node))
- continue;
-
- string? proto;
-
- if (mask != null && markerTemplate.EntityMask.TryGetValue(mask, out var maskedProto))
- {
- proto = maskedProto;
- }
- else
- {
- proto = markerTemplate.Prototype;
- }
-
- var ent = _entManager.SpawnAtPosition(proto, new EntityCoordinates(_gridUid, node + _grid.TileSizeHalfVector));
- var xform = xformQuery.Get(ent);
-
- if (!xform.Comp.Anchored)
- _transform.AnchorEntity(ent, xform);
-
- await SuspendDungeon();
- if (!ValidateResume())
- return;
- }
- }
- }
-}
if (!_anchorable.TileFree((_gridUid, _grid), neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
continue;
- tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+ var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ tiles.Add((neighbor, tile));
+ AddLoadedTile(neighbor, tile);
+ DebugTools.Assert(dungeon.AllTiles.Contains(neighbor));
}
foreach (var index in dungeon.CorridorExteriorTiles)
if (!_anchorable.TileFree((_gridUid, _grid), index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
continue;
- tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random)));
+ var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ tiles.Add((index, tile));
+ AddLoadedTile(index, tile);
+ DebugTools.Assert(dungeon.AllTiles.Contains(index));
}
_maps.SetTiles(_gridUid, _grid, tiles);
}
if (isCorner)
- _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
+ {
+ var uid = _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
+ AddLoadedEntity(index.Index, uid);
+ }
if (!isCorner)
- _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
-
- if (i % 20 == 0)
{
- await SuspendDungeon();
-
- if (!ValidateResume())
- return;
+ var uid = _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index));
+ AddLoadedEntity(index.Index, uid);
}
+
+ await SuspendDungeon();
+
+ if (!ValidateResume())
+ return;
}
}
}
--- /dev/null
+using System.Threading.Tasks;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.DungeonGenerators;
+using Content.Shared.Procedural.PostGeneration;
+
+namespace Content.Server.Procedural.DungeonJob;
+
+public sealed partial class DungeonJob
+{
+ /// <summary>
+ /// <see cref="BiomeDunGen"/>
+ /// </summary>
+ private async Task<Dungeon> PostGen(ChunkDunGen dunGen, HashSet<Vector2i> reservedTiles, Random random)
+ {
+ var dungeon = new Dungeon();
+ var tiles = new HashSet<Vector2i>();
+ var tr = _position + new Vector2i(dunGen.Size, dunGen.Size);
+ var oldSeed = dunGen.Noise?.GetSeed() ?? 0;
+ dunGen.Noise?.SetSeed(_seed + oldSeed);
+
+ for (var x = 0; x < dunGen.Size; x++)
+ {
+ for (var y = 0; y < dunGen.Size; y++)
+ {
+ var index = new Vector2i(_position.X + x, _position.Y + y);
+
+ if (reservedTiles.Contains(index))
+ continue;
+
+ if (dunGen.Noise?.GetNoise(x, y) < dunGen.Threshold)
+ continue;
+
+ tiles.Add(index);
+ }
+ }
+
+ dunGen.Noise?.SetSeed(oldSeed);
+ var room = new DungeonRoom(tiles, (tr - _position) / 2 + _position, new Box2i(_position, tr), new HashSet<Vector2i>());
+ dungeon.AddRoom(room);
+ return dungeon;
+ }
+}
var setTiles = new List<(Vector2i, Tile)>();
var tileDef = (ContentTileDefinition) _tileDefManager[gen.Tile];
- foreach (var tile in corridorTiles)
+ foreach (var node in corridorTiles)
{
- if (reservedTiles.Contains(tile))
+ if (reservedTiles.Contains(node))
continue;
- setTiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
+ var tile = _tile.GetVariantTile(tileDef, random);
+ setTiles.Add((node, tile));
+ AddLoadedTile(node, tile);
}
_maps.SetTiles(_gridUid, _grid, setTiles);
var protos = _entTable.GetSpawns(contents, random);
var coords = _maps.ToCenterCoordinates(_gridUid, tile, _grid);
- _entManager.SpawnEntitiesAttachedTo(coords, protos);
+ var uids = _entManager.SpawnEntitiesAttachedTo(coords, protos);
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(tile, uid);
+ }
+
await SuspendIfOutOfTime();
if (!ValidateResume())
{
// Decals not being centered biting my ass again
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
- _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
+ _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
+ AddLoadedDecal(tile, did);
}
}
{
// Decals not being centered biting my ass again
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
- _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
+ _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
+ AddLoadedDecal(tile, did);
}
continue;
if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir))
{
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset);
- _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color);
+ _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color);
+ AddLoadedDecal(tile, did);
}
}
}
Random random)
{
var tiles = new List<(Vector2i, Tile)>();
- var matrix = Matrix3Helpers.CreateTranslation(position);
+ var matrix = Matrix3Helpers.CreateTranslation(_position + position);
foreach (var layer in dungen.Layers)
{
break;
}
- tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
+ var tile = new Tile(tileDef.TileId, variant: variant);
+ tiles.Add((adjusted, tile));
+ AddLoadedTile(adjusted, tile);
roomTiles.Add(adjusted);
break;
}
{
switch (distance)
{
+ case DunGenDistanceSquared:
+ return dx * dx + dy * dy;
case DunGenEuclideanSquaredDistance:
return MathF.Min(1f, (dx * dx + dy * dy) / MathF.Sqrt(2));
case DunGenSquareBump:
if (reservedTiles.Contains(index))
continue;
- tiles.Add((index, new Tile(_tileDefManager[fallbackTile.Value].TileId)));
+ var tile = new Tile(_tileDefManager[fallbackTile.Value].TileId);
+ tiles.Add((index, tile));
+ AddLoadedTile(index, tile);
}
}
var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform);
// The expensive bit yippy.
- _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
+ var data = _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
+
+ _data.Merge(data);
+
+ await SuspendDungeon();
+
+ if (!ValidateResume())
+ return dungeon;
var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize;
var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
}
replacements.Add((node, tile));
+ AddLoadedTile(node, tile);
break;
}
isValid = true;
// Entrance wew
- _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile(tileDef, random));
+ var tileVariant = _tile.GetVariantTile(tileDef, random);
+ _maps.SetTile(_gridUid, _grid, tile, tileVariant);
+ AddLoadedTile(tile, tileVariant);
ClearDoor(dungeon, _grid, tile);
var gridCoords = _maps.GridTileToLocal(_gridUid, _grid, tile);
// Need to offset the spawn to avoid spawning in the room.
foreach (var ent in _entTable.GetSpawns(contents, random))
{
- _entManager.SpawnAtPosition(ent, gridCoords);
+ var uid = _entManager.SpawnAtPosition(ent, gridCoords);
+ AddLoadedEntity(tile, uid);
}
// Clear out any biome tiles nearby to avoid blocking it
foreach (var ent in entities)
{
var uid = _entManager.SpawnAtPosition(ent, _maps.GridTileToLocal(_gridUid, _grid, tile));
+ AddLoadedEntity(tile, uid);
_entManager.RemoveComponent<GhostRoleComponent>(uid);
_entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
npcs.SleepNPC(uid);
if (reservedTiles.Contains(neighbor))
continue;
- tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+ var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ tiles.Add((neighbor, tile));
+ AddLoadedTile(neighbor, tile);
spawnPositions.Add(neighbor);
}
}
foreach (var entrance in spawnPositions)
{
- _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
+ var uids = _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random));
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(entrance, uid);
+ }
}
}
}
+using System.Numerics;
using System.Threading.Tasks;
using Content.Shared.Maps;
using Content.Shared.NPC;
/// <summary>
/// <see cref="ExteriorDunGen"/>
/// </summary>
- private async Task<List<Dungeon>> GenerateExteriorDungen(Vector2i position, ExteriorDunGen dungen, HashSet<Vector2i> reservedTiles, Random random)
+ private async Task<List<Dungeon>> GenerateExteriorDungen(int runCount, int maxRuns, Vector2i position, ExteriorDunGen dungen, HashSet<Vector2i> reservedTiles, Random random)
{
DebugTools.Assert(_grid.ChunkCount > 0);
var aabb = new Box2i(_grid.LocalAABB.BottomLeft.Floored(), _grid.LocalAABB.TopRight.Floored());
- var angle = random.NextAngle();
+ // TODO: Cross-layer seeding. Need this because we need to be able to spread the dungeons out.
+ var angle = new Random(_seed).NextAngle();
+ var divisors = new Angle(Angle.FromDegrees(360) / maxRuns);
- var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f);
+ // Offset each dungeon so they don't generate on top of each other.
+ for (var i = 0; i < runCount; i++)
+ {
+ angle += (random.NextFloat(0.6f, 1.4f)) * divisors;
+ }
+ var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f);
var startTile = new Vector2i(0, (int) distance).Rotate(angle);
Vector2i? dungeonSpawn = null;
};
}
- var config = _prototype.Index(dungen.Proto);
+ // Move it further in based on the spawn angle.
+ if (dungen.Penetration.Y > 0)
+ {
+ var penetration = random.Next(dungen.Penetration.X, dungen.Penetration.Y);
+ var diff = dungeonSpawn.Value - startTile;
+ var diffVec = new Vector2(diff.X, diff.Y);
+ dungeonSpawn = (diffVec.Normalized() * (penetration + diffVec.Length())).Floored() + startTile;
+ }
+
+ var subConfig = _prototype.Index(dungen.Proto);
var nextSeed = random.Next();
- var dungeons = await GetDungeons(dungeonSpawn.Value, config, config.Layers, reservedTiles, nextSeed, new Random(nextSeed));
+ var (dungeons, newReserved) = await GetDungeons(dungeonSpawn.Value, subConfig, subConfig.Layers, nextSeed, new Random(nextSeed), reserved: reservedTiles);
+ reservedTiles.UnionWith(newReserved);
return dungeons;
}
if (reservedTiles.Contains(neighbor))
continue;
- tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+ var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ tiles.Add((neighbor, tileVariant));
+ AddLoadedTile(neighbor, tileVariant);
index++;
takenTiles.Add(neighbor);
}
{
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile.Item1);
- _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+ var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(tile.Item1, uid);
+ }
+
await SuspendDungeon();
if (!ValidateResume())
if (reservedTiles.Contains(tile))
continue;
+ await SuspendDungeon();
+ if (!ValidateResume())
+ return;
+
if (!_maps.TryGetTileDef(_grid, tile, out var tileDef))
continue;
if (fill.AllowedTiles != null && !fill.AllowedTiles.Contains(tileDef.ID))
continue;
+ // If noise then check it matches.
+ if (fill.ReservedNoise != null)
+ {
+ var value = fill.ReservedNoise.GetNoise(tile.X, tile.Y);
+
+ if (fill.DistanceConfig != null)
+ {
+ // Need to get dx - dx in a range from -1 -> 1
+ var dx = 2 * tile.X / fill.Size.X;
+ var dy = 2 * tile.Y / fill.Size.Y;
+
+ var distance = GetDistance(dx, dy, fill.DistanceConfig);
+
+ value = MathHelper.Lerp(value, 1f - distance, fill.DistanceConfig.BlendWeight);
+ }
+
+ value *= (fill.Invert ? -1 : 1);
+
+ if (value < fill.Threshold)
+ continue;
+ }
+
if (!_anchorable.TileFree((_gridUid, _grid), tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
continue;
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile);
- _entManager.SpawnEntity(fill.Entity, gridPos);
+ var uid = _entManager.SpawnEntity(fill.Entity, gridPos);
- await SuspendDungeon();
- if (!ValidateResume())
- break;
+ AddLoadedEntity(tile, uid);
}
}
}
}
}
}
+
+ dungeon.RefreshAllTiles();
}
private void WidenCorridor(Dungeon dungeon, float width, ICollection<Vector2i> corridorTiles)
{
var tile = validTiles[j];
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile);
- _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
+ var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ _maps.SetTile(_gridUid, _grid, tile, tileVariant);
+ AddLoadedTile(tile, tileVariant);
- _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+ var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(tile, uid);
+ }
}
if (validTiles.Count > 0)
if (reservedTiles.Contains(weh))
continue;
- _maps.SetTile(_gridUid, _grid, weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
+ var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ _maps.SetTile(_gridUid, _grid, weh, tileVariant);
+ AddLoadedTile(weh, tileVariant);
var coords = _maps.GridTileToLocal(_gridUid, _grid, weh);
- _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
+ var uids = _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random));
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(weh, uid);
+ }
}
break;
// Grab all of the room bounds
// Then, work out connections between them
var roomBorders = new Dictionary<DungeonRoom, HashSet<Vector2i>>(dungeon.Rooms.Count);
+ var flank = gen.Flank;
foreach (var room in dungeon.Rooms)
{
continue;
width--;
- _maps.SetTile(_gridUid, _grid, node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random));
+ var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ _maps.SetTile(_gridUid, _grid, node, tileVariant);
+ AddLoadedTile(node, tileVariant);
if (flankContents != null && nodeDistances.Count - i <= 2)
{
- _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
+ var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random));
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(node, uid);
+ }
}
else
{
// Iterate neighbors and check for blockers, if so bulldoze
ClearDoor(dungeon, _grid, node);
- _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+ var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random));
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(node, uid);
+ }
}
if (width == 0)
_entManager.RemoveComponent<GhostRoleComponent>(uid);
_entManager.RemoveComponent<GhostTakeoverAvailableComponent>(uid);
npcs.SleepNPC(uid);
+ AddLoadedEntity(tile, uid);
}
break;
var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random);
var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored();
- tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant)));
+ var tileVariant = new Tile(tileDef.TileId, variant: variant);
+ tiles.Add((adjusted, tileVariant));
+ AddLoadedTile(adjusted, tileVariant);
roomTiles.Add(adjusted);
tileCount++;
break;
}
}
- await SuspendIfOutOfTime();
- ValidateResume();
+ await SuspendDungeon();
}
var center = Vector2.Zero;
center /= roomTiles.Count;
rooms.Add(new DungeonRoom(roomTiles, center, roomArea, new HashSet<Vector2i>()));
- await SuspendIfOutOfTime();
- ValidateResume();
+ await SuspendDungeon();
}
_maps.SetTiles(_gridUid, _grid, tiles);
HashSet<Vector2i> reservedTiles,
Random random)
{
- foreach (var dungeon in dungeons)
+ var emptyTiles = false;
+ var replaceEntities = new Dictionary<Vector2i, EntityUid>();
+ var availableTiles = new List<Vector2i>();
+ var remapName = _entManager.ComponentFactory.GetComponentName<EntityRemapComponent>();
+ var replacementRemapping = new Dictionary<EntProtoId, EntProtoId>();
+
+ if (_prototype.TryIndex(gen.Replacement, out var replacementProto) &&
+ replacementProto.Components.TryGetComponent(remapName, out var replacementComps))
{
- var emptyTiles = false;
- var replaceEntities = new Dictionary<Vector2i, EntityUid>();
- var availableTiles = new List<Vector2i>();
+ var remappingComp = (EntityRemapComponent) replacementComps;
+ replacementRemapping = remappingComp.Mask;
+ }
+
+ if (gen.Replacement != null)
+ {
+ replacementRemapping[gen.Replacement.Value] = gen.Entity;
+ }
+ foreach (var dungeon in dungeons)
+ {
foreach (var node in dungeon.AllTiles)
{
if (reservedTiles.Contains(node))
// We use existing entities as a mark to spawn in place
// OR
// We check for any existing entities to see if we can spawn there.
- while (enumerator.MoveNext(out var uid))
+ // We can't replace so just stop here.
+ if (gen.Replacement != null)
{
- // We can't replace so just stop here.
- if (gen.Replacement == null)
- break;
+ while (enumerator.MoveNext(out var uid))
+ {
+ var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
- var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
+ if (string.IsNullOrEmpty(prototype?.ID))
+ continue;
- if (prototype?.ID == gen.Replacement)
- {
- replaceEntities[node] = uid.Value;
- found = true;
- break;
+ // It has a valid remapping so take it over.
+ if (replacementRemapping.ContainsKey(prototype.ID))
+ {
+ replaceEntities[node] = uid.Value;
+ found = true;
+ break;
+ }
}
}
if (!ValidateResume())
return;
}
+ }
- var remapping = new Dictionary<EntProtoId, EntProtoId>();
+ var remapping = new Dictionary<EntProtoId, EntProtoId>();
- // TODO: Move this to engine
- if (_prototype.TryIndex(gen.Entity, out var proto) &&
- proto.Components.TryGetComponent("EntityRemap", out var comps))
- {
- var remappingComp = (EntityRemapComponent) comps;
- remapping = remappingComp.Mask;
- }
-
- var frontier = new ValueList<Vector2i>(32);
+ // TODO: Move this to engine
+ if (_prototype.TryIndex(gen.Entity, out var proto) &&
+ proto.Components.TryGetComponent(remapName, out var comps))
+ {
+ var remappingComp = (EntityRemapComponent) comps;
+ remapping = remappingComp.Mask;
+ }
- // Iterate the group counts and pathfind out each group.
- for (var i = 0; i < gen.Count; i++)
- {
- await SuspendDungeon();
+ var frontier = new ValueList<Vector2i>(32);
- if (!ValidateResume())
- return;
+ // Iterate the group counts and pathfind out each group.
+ for (var i = 0; i < gen.Count; i++)
+ {
+ var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1);
- var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1);
+ // While we have remaining tiles keep iterating
+ while (groupSize > 0 && availableTiles.Count > 0)
+ {
+ var startNode = random.PickAndTake(availableTiles);
+ frontier.Clear();
+ frontier.Add(startNode);
- // While we have remaining tiles keep iterating
- while (groupSize > 0 && availableTiles.Count > 0)
+ // This essentially may lead to a vein being split in multiple areas but the count matters more than position.
+ while (frontier.Count > 0 && groupSize > 0)
{
- var startNode = random.PickAndTake(availableTiles);
- frontier.Clear();
- frontier.Add(startNode);
-
- // This essentially may lead to a vein being split in multiple areas but the count matters more than position.
- while (frontier.Count > 0 && groupSize > 0)
+ // Need to pick a random index so we don't just get straight lines of ores.
+ var frontierIndex = random.Next(frontier.Count);
+ var node = frontier[frontierIndex];
+ frontier.RemoveSwap(frontierIndex);
+ availableTiles.Remove(node);
+
+ // Add neighbors if they're valid, worst case we add no more and pick another random seed tile.
+ for (var x = -1; x <= 1; x++)
{
- // Need to pick a random index so we don't just get straight lines of ores.
- var frontierIndex = random.Next(frontier.Count);
- var node = frontier[frontierIndex];
- frontier.RemoveSwap(frontierIndex);
- availableTiles.Remove(node);
-
- // Add neighbors if they're valid, worst case we add no more and pick another random seed tile.
- for (var x = -1; x <= 1; x++)
+ for (var y = -1; y <= 1; y++)
{
- for (var y = -1; y <= 1; y++)
- {
- var neighbor = new Vector2i(node.X + x, node.Y + y);
+ var neighbor = new Vector2i(node.X + x, node.Y + y);
- if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor))
- continue;
+ if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor))
+ continue;
- frontier.Add(neighbor);
- }
+ frontier.Add(neighbor);
}
+ }
- var prototype = gen.Entity;
+ var prototype = gen.Entity;
- if (replaceEntities.TryGetValue(node, out var existingEnt))
- {
- var existingProto = _entManager.GetComponent<MetaDataComponent>(existingEnt).EntityPrototype;
- _entManager.DeleteEntity(existingEnt);
+ // May have been deleted while iteration was suspended.
+ if (replaceEntities.TryGetValue(node, out var existingEnt) && _entManager.TryGetComponent(existingEnt, out MetaDataComponent? metadata))
+ {
+ var existingProto = metadata.EntityPrototype;
+ _entManager.DeleteEntity(existingEnt);
- if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
- {
- prototype = remapped;
- }
+ if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped))
+ {
+ prototype = remapped;
}
+ }
- // Tile valid salad so add it.
- _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
+ // Tile valid salad so add it.
+ var uid = _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node));
+ AddLoadedEntity(node, uid);
- groupSize--;
- }
- }
+ groupSize--;
- if (groupSize > 0)
- {
- _sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!");
+ await SuspendDungeon();
+
+ if (!ValidateResume())
+ return;
}
}
+
+ if (groupSize > 0)
+ {
+ // Not super worried depending on the gen it's fine.
+ _sawmill.Debug($"Found remaining group size for ore veins of {gen.Replacement ?? "null"} / {gen.Entity}!");
+ }
}
}
}
--- /dev/null
+using System.Threading.Tasks;
+using Content.Server.Light.EntitySystems;
+using Content.Shared.Light.Components;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.DungeonLayers;
+
+namespace Content.Server.Procedural.DungeonJob;
+
+public sealed partial class DungeonJob
+{
+ public async Task RoofGen(RoofDunGen roof, List<Dungeon> dungeons, HashSet<Vector2i> reservedTiles, Random random)
+ {
+ var roofComp = _entManager.EnsureComponent<RoofComponent>(_gridUid);
+
+ var noise = roof.Noise;
+ var oldSeed = noise?.GetSeed() ?? 0;
+ noise?.SetSeed(_seed + oldSeed);
+ var rooves = _entManager.System<RoofSystem>();
+
+ foreach (var dungeon in dungeons)
+ {
+ foreach (var tile in dungeon.AllTiles)
+ {
+ if (reservedTiles.Contains(tile))
+ continue;
+
+ var value = noise?.GetNoise(tile.X, tile.Y) ?? 1f;
+
+ if (value < roof.Threshold)
+ continue;
+
+ rooves.SetRoof((_gridUid, _grid, roofComp), tile, true);
+ }
+ }
+
+ noise?.SetSeed(oldSeed);
+ }
+}
if (reservedTiles.Contains(entrance))
continue;
- setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)));
+ var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random);
+ setTiles.Add((entrance, tileVariant));
+ AddLoadedTile(entrance, tileVariant);
}
}
if (reservedTiles.Contains(entrance))
continue;
- _entManager.SpawnEntitiesAttachedTo(
+ var uids = _entManager.SpawnEntitiesAttachedTo(
_maps.GridTileToLocal(_gridUid, _grid, entrance),
_entTable.GetSpawns(contents, random));
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(entrance, uid);
+ }
+
await SuspendDungeon();
if (!ValidateResume())
--- /dev/null
+using System.Threading.Tasks;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.DungeonLayers;
+using Robust.Shared.Map;
+using Robust.Shared.Random;
+
+namespace Content.Server.Procedural.DungeonJob;
+
+public sealed partial class DungeonJob
+{
+ /// <summary>
+ /// <see cref="SampleDecalDunGen"/>
+ /// </summary>
+ private async Task PostGen(SampleDecalDunGen gen,
+ List<Dungeon> dungeons,
+ HashSet<Vector2i> reservedTiles,
+ Random random)
+ {
+ var oldSeed = gen.Noise.GetSeed();
+ gen.Noise.SetSeed(_seed + oldSeed);
+
+ foreach (var dungeon in dungeons)
+ {
+ foreach (var tile in dungeon.AllTiles)
+ {
+ if (reservedTiles.Contains(tile))
+ continue;
+
+ var invert = gen.Invert;
+ var value = gen.Noise.GetNoise(tile.X, tile.Y);
+ value = invert ? value * -1 : value;
+
+ if (value < gen.Threshold)
+ continue;
+
+ // Not allowed
+ if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
+ !gen.AllowedTiles.Contains(_tileDefManager[tileRef.Tile.TypeId].ID))
+ {
+ continue;
+ }
+
+ // Occupied?
+ if (!_anchorable.TileFree(_grid, tile, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask))
+ continue;
+
+ _decals.TryAddDecal(random.Pick(gen.Decals), new EntityCoordinates(_gridUid, tile), out var did);
+ AddLoadedDecal(tile, did);
+
+ if (gen.ReserveTiles)
+ {
+ reservedTiles.Add(tile);
+ }
+
+ await SuspendDungeon();
+
+ if (!ValidateResume())
+ return;
+ }
+ }
+
+ gen.Noise.SetSeed(oldSeed);
+ }
+}
--- /dev/null
+using System.Threading.Tasks;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.DungeonLayers;
+using Robust.Shared.Map;
+using Robust.Shared.Random;
+
+namespace Content.Server.Procedural.DungeonJob;
+
+public sealed partial class DungeonJob
+{
+ /// <summary>
+ /// <see cref="SampleEntityDunGen"/>
+ /// </summary>
+ private async Task PostGen(
+ SampleEntityDunGen gen,
+ List<Dungeon> dungeons,
+ HashSet<Vector2i> reservedTiles,
+ Random random)
+ {
+ var oldSeed = gen.Noise.GetSeed();
+ gen.Noise.SetSeed(_seed + oldSeed);
+
+ foreach (var dungeon in dungeons)
+ {
+ foreach (var tile in dungeon.AllTiles)
+ {
+ if (reservedTiles.Contains(tile))
+ continue;
+
+ var invert = gen.Invert;
+ var value = gen.Noise.GetNoise(tile.X, tile.Y);
+ value = invert ? value * -1 : value;
+
+ if (value < gen.Threshold)
+ continue;
+
+ // Not allowed
+ if (!_maps.TryGetTileRef(_gridUid, _grid, tile, out var tileRef) ||
+ !gen.AllowedTiles.Contains(_tileDefManager[tileRef.Tile.TypeId].ID))
+ {
+ continue;
+ }
+
+ var gridTile = _maps.GridTileToLocal(_gridUid, _grid, tile);
+ var uid = _entManager.SpawnAttachedTo(random.Pick(gen.Entities), gridTile);
+ AddLoadedEntity(tile, uid);
+
+ if (gen.ReserveTiles)
+ {
+ reservedTiles.Add(tile);
+ }
+
+ await SuspendDungeon();
+
+ if (!ValidateResume())
+ return;
+ }
+ }
+
+ gen.Noise.SetSeed(oldSeed);
+ }
+}
--- /dev/null
+using System.Threading.Tasks;
+using Content.Shared.Procedural;
+using Content.Shared.Procedural.DungeonLayers;
+using Robust.Shared.Map;
+using Robust.Shared.Noise;
+using Robust.Shared.Random;
+
+namespace Content.Server.Procedural.DungeonJob;
+
+public sealed partial class DungeonJob
+{
+ /// <summary>
+ /// <see cref="SampleTileDunGen"/>
+ /// </summary>
+ private async Task PostGen(SampleTileDunGen gen,
+ List<Dungeon> dungeons,
+ HashSet<Vector2i> reservedTiles,
+ Random random)
+ {
+ var noise = gen.Noise;
+ var oldSeed = noise.GetSeed();
+ noise.SetSeed(_seed + oldSeed);
+ var tiles = new List<(Vector2i Index, Tile Tile)>();
+ var tileDef = _prototype.Index(gen.Tile);
+ var variants = tileDef.PlacementVariants.Length;
+
+ foreach (var dungeon in dungeons)
+ {
+ foreach (var tile in dungeon.AllTiles)
+ {
+ if (reservedTiles.Contains(tile))
+ continue;
+
+ var invert = gen.Invert;
+ var value = noise.GetNoise(tile.X, tile.Y);
+ value = invert ? value * -1 : value;
+
+ if (value < gen.Threshold)
+ continue;
+
+ var variantValue = (noise.GetNoise(tile.X * 8, tile.Y * 8, variants) + 1f) * 100;
+ var variant = _tile.PickVariant(tileDef, (int)variantValue);
+ var tileVariant = new Tile(tileDef.TileId, variant: variant);
+
+ tiles.Add((tile, tileVariant));
+ AddLoadedTile(tile, tileVariant);
+
+ await SuspendDungeon();
+
+ if (!ValidateResume())
+ return;
+ }
+ }
+
+ gen.Noise.SetSeed(oldSeed);
+ _maps.SetTiles(_gridUid, _grid, tiles);
+
+ if (gen.ReserveTiles)
+ {
+ foreach (var tile in tiles)
+ {
+ reservedTiles.Add(tile.Index);
+ }
+ }
+ }
+}
using System.Threading.Tasks;
using Content.Server.NPC.Pathfinding;
using Content.Shared.Procedural;
+using Content.Shared.Procedural.DungeonLayers;
using Content.Shared.Procedural.PostGeneration;
using Robust.Shared.Map;
using Robust.Shared.Random;
public sealed partial class DungeonJob
{
/// <summary>
- /// <see cref="SplineDungeonConnectorDunGen"/>
+ /// <see cref="Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen"/>
/// </summary>
private async Task<Dungeon> PostGen(
- SplineDungeonConnectorDunGen gen,
+ Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen gen,
List<Dungeon> dungeons,
HashSet<Vector2i> reservedTiles,
Random random)
{
Start = pair.Start,
End = pair.End,
+ Diagonals = false,
TileCost = node =>
{
// We want these to get prioritised internally and into space if it's a space dungeon.
}
tiles.Add((node, tile));
+ AddLoadedTile(node, tile);
}
_maps.SetTiles(_gridUid, _grid, tiles);
allTiles.Add(node);
tiles.Add((node, pathTile));
+ AddLoadedTile(node, pathTile);
}
_maps.SetTiles(_gridUid, _grid, tiles);
if (reservedTiles.Contains(neighbor))
continue;
- _maps.SetTile(_gridUid, _grid, neighbor, _tile.GetVariantTile(tileDef, random));
+ var tileVariant = _tile.GetVariantTile(tileDef, random);
+ _maps.SetTile(_gridUid, _grid, neighbor, tileVariant);
+ AddLoadedTile(neighbor, tileVariant);
var gridPos = _maps.GridTileToLocal(_gridUid, _grid, neighbor);
var protoNames = _entTable.GetSpawns(contents, random);
- _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
+ var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames);
+
+ foreach (var uid in uids)
+ {
+ AddLoadedEntity(neighbor, uid);
+ }
await SuspendDungeon();
if (!ValidateResume())
-using System.Linq;
+using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Content.Server.Decals;
-using Content.Server.NPC.Components;
using Content.Server.NPC.HTN;
using Content.Server.NPC.Systems;
using Content.Server.Shuttles.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
-using IDunGenLayer = Content.Shared.Procedural.IDunGenLayer;
namespace Content.Server.Procedural.DungeonJob;
-public sealed partial class DungeonJob : Job<List<Dungeon>>
+public sealed partial class DungeonJob : Job<(List<Dungeon>, DungeonData)>
{
public bool TimeSlice = true;
private readonly ISawmill _sawmill;
+ private DungeonData _data = new();
+
+ private HashSet<Vector2i>? _reservedTiles;
+
public DungeonJob(
ISawmill sawmill,
double maxTime,
int seed,
Vector2i position,
EntityCoordinates? targetCoordinates = null,
- CancellationToken cancellation = default) : base(maxTime, cancellation)
+ CancellationToken cancellation = default,
+ HashSet<Vector2i>? reservedTiles = null) : base(maxTime, cancellation)
{
_sawmill = sawmill;
_entManager = entManager;
_prototype = prototype;
_tileDefManager = tileDefManager;
+ _reservedTiles = reservedTiles;
_anchorable = anchorable;
_decals = decals;
/// <summary>
/// Gets the relevant dungeon, running recursively as relevant.
/// </summary>
- /// <param name="reserve">Should we reserve tiles even if the config doesn't specify.</param>
- private async Task<List<Dungeon>> GetDungeons(
+ /// <param name="reservedTiles">Should we reserve tiles even if the config doesn't specify.</param>
+ private async Task<(List<Dungeon>, HashSet<Vector2i>)> GetDungeons(
Vector2i position,
DungeonConfig config,
List<IDunGenLayer> layers,
- HashSet<Vector2i> reservedTiles,
int seed,
Random random,
+ HashSet<Vector2i>? reserved = null,
List<Dungeon>? existing = null)
{
var dungeons = new List<Dungeon>();
+ var reservedTiles = reserved == null ? new HashSet<Vector2i>() : new HashSet<Vector2i>(reserved);
// Don't pass dungeons back up the "stack". They are ref types though it's a caller problem if they start trying to mutate it.
if (existing != null)
foreach (var layer in layers)
{
- var dungCount = dungeons.Count;
- await RunLayer(dungeons, position, layer, reservedTiles, seed, random);
+ var dungCount = dungeons.Count;
+ await RunLayer(i, count, config, dungeons, position, layer, reservedTiles, seed, random);
if (config.ReserveTiles)
{
await SuspendDungeon();
if (!ValidateResume())
- return new List<Dungeon>();
+ return (new List<Dungeon>(), new HashSet<Vector2i>());
}
}
- return dungeons;
+ // Only return the new dungeons and applicable reserved tiles.
+ return (dungeons[(existing?.Count ?? 0)..], config.ReturnReserved ? reservedTiles : new HashSet<Vector2i>());
}
- protected override async Task<List<Dungeon>?> Process()
+ protected override async Task<(List<Dungeon>, DungeonData)> Process()
{
_sawmill.Info($"Generating dungeon {_gen} with seed {_seed} on {_entManager.ToPrettyString(_gridUid)}");
_grid.CanSplit = false;
var random = new Random(_seed);
+ var oldTileCount = _reservedTiles?.Count ?? 0;
var position = (_position + random.NextPolarVector2(_gen.MinOffset, _gen.MaxOffset)).Floored();
- // Tiles we can no longer generate on due to being reserved elsewhere.
- var reservedTiles = new HashSet<Vector2i>();
-
- var dungeons = await GetDungeons(position, _gen, _gen.Layers, reservedTiles, _seed, random);
+ var (dungeons, _) = await GetDungeons(position, _gen, _gen.Layers, _seed, random, reserved: _reservedTiles);
// To make it slightly more deterministic treat this RNG as separate ig.
// Post-processing after finishing loading.
}
// Defer splitting so they don't get spammed and so we don't have to worry about tracking the grid along the way.
+ DebugTools.Assert(oldTileCount == (_reservedTiles?.Count ?? 0));
_grid.CanSplit = true;
_entManager.System<GridFixtureSystem>().CheckSplits(_gridUid);
var npcSystem = _entManager.System<NPCSystem>();
}
_sawmill.Info($"Finished generating dungeon {_gen} with seed {_seed}");
- return dungeons;
+ return (dungeons, _data);
}
private async Task RunLayer(
+ int runCount,
+ int maxRuns,
+ DungeonConfig config,
List<Dungeon> dungeons,
Vector2i position,
IDunGenLayer layer,
int seed,
Random random)
{
- _sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen} with seed {_seed}");
+ // _sawmill.Debug($"Doing postgen {layer.GetType()} for {_gen} with seed {_seed}");
// If there's a way to just call the methods directly for the love of god tell me.
// Some of these don't care about reservedtiles because they only operate on dungeon tiles (which should
case AutoCablingDunGen cabling:
await PostGen(cabling, dungeons[^1], reservedTiles, random);
break;
- case BiomeMarkerLayerDunGen markerPost:
- await PostGen(markerPost, dungeons[^1], reservedTiles, random);
- break;
- case BiomeDunGen biome:
- await PostGen(biome, dungeons[^1], reservedTiles, random);
- break;
case BoundaryWallDunGen boundary:
await PostGen(boundary, dungeons[^1], reservedTiles, random);
break;
+ case ChunkDunGen chunk:
+ dungeons.Add(await PostGen(chunk, reservedTiles, random));
+ break;
case CornerClutterDunGen clutter:
await PostGen(clutter, dungeons[^1], reservedTiles, random);
break;
await PostGen(flank, dungeons[^1], reservedTiles, random);
break;
case ExteriorDunGen exterior:
- dungeons.AddRange(await GenerateExteriorDungen(position, exterior, reservedTiles, random));
+ dungeons.AddRange(await GenerateExteriorDungen(runCount, maxRuns, position, exterior, reservedTiles, random));
break;
case FillGridDunGen fill:
await GenerateFillDunGen(fill, dungeons, reservedTiles);
case PrototypeDunGen prototypo:
var groupConfig = _prototype.Index(prototypo.Proto);
position = (position + random.NextPolarVector2(groupConfig.MinOffset, groupConfig.MaxOffset)).Floored();
+ List<Dungeon>? inheritedDungeons = null;
+ HashSet<Vector2i>? inheritedReserved = null;
+
+ switch (prototypo.InheritReserved)
+ {
+ case ReservedInheritance.All:
+ inheritedReserved = new HashSet<Vector2i>(reservedTiles);
+ break;
+ case ReservedInheritance.None:
+ break;
+ default:
+ throw new NotImplementedException();
+ }
switch (prototypo.InheritDungeons)
{
case DungeonInheritance.All:
- dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons));
+ inheritedDungeons = dungeons;
break;
case DungeonInheritance.Last:
- dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons.GetRange(dungeons.Count - 1, 1)));
+ inheritedDungeons = dungeons.GetRange(dungeons.Count - 1, 1);
break;
case DungeonInheritance.None:
- dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random));
break;
+ default:
+ throw new NotImplementedException();
+ }
+
+ var (newDungeons, newReserved) = await GetDungeons(position,
+ groupConfig,
+ groupConfig.Layers,
+ seed,
+ random,
+ reserved: inheritedReserved,
+ existing: inheritedDungeons);
+ dungeons.AddRange(newDungeons);
+
+ if (groupConfig.ReturnReserved)
+ {
+ reservedTiles.UnionWith(newReserved);
}
break;
case ReplaceTileDunGen replace:
await GenerateTileReplacementDunGen(replace, dungeons, reservedTiles, random);
break;
+ case RoofDunGen roof:
+ await RoofGen(roof, dungeons, reservedTiles, random);
+ break;
case RoomEntranceDunGen rEntrance:
await PostGen(rEntrance, dungeons[^1], reservedTiles, random);
break;
+ case SampleDecalDunGen sdec:
+ await PostGen(sdec, dungeons, reservedTiles, random);
+ break;
+ case SampleEntityDunGen sent:
+ await PostGen(sent, dungeons, reservedTiles, random);
+ break;
+ case SampleTileDunGen stile:
+ await PostGen(stile, dungeons, reservedTiles, random);
+ break;
case SplineDungeonConnectorDunGen spline:
dungeons.Add(await PostGen(spline, dungeons, reservedTiles, random));
break;
}
}
- private void LogDataError(Type type)
- {
- _sawmill.Error($"Unable to find dungeon data keys for {type}");
- }
-
[Pure]
private bool ValidateResume()
{
await SuspendIfOutOfTime();
}
+
+ private void AddLoadedEntity(Vector2i tile, EntityUid ent)
+ {
+ _data.Entities[ent] = tile;
+ }
+
+ private void AddLoadedDecal(Vector2 tile, uint decal)
+ {
+ _data.Decals[decal] = tile;
+ }
+
+ private void AddLoadedTile(Vector2i index, Tile tile)
+ {
+ _data.Tiles[index] = tile;
+ }
}
return roomRotation;
}
- public void SpawnRoom(
+ public DungeonData SpawnRoom(
EntityUid gridUid,
MapGridComponent grid,
Matrix3x2 roomTransform,
var templateMapUid = _maps.GetMapOrInvalid(roomMap);
var templateGrid = Comp<MapGridComponent>(templateMapUid);
var roomDimensions = room.Size;
+ var data = new DungeonData();
var finalRoomRotation = roomTransform.Rotation();
}
_tiles.Add((rounded, tileRef.Tile));
+ data.Tiles[rounded] = tileRef.Tile;
if (clearExisting)
{
// TODO: Copy the templated entity as is with serv
var ent = Spawn(protoId, new EntityCoordinates(gridUid, childPos));
+ data.Entities.Add(ent, childPos.Floored());
var childXform = _xformQuery.GetComponent(ent);
var anchored = templateXform.Anchored;
var result = _decals.TryAddDecal(
decal.Id,
new EntityCoordinates(gridUid, position),
- out _,
+ out var did,
decal.Color,
angle,
decal.ZIndex,
decal.Cleanable);
+ data.Decals.Add(did, position);
+
DebugTools.Assert(result);
}
}
+
+ return data;
}
}
MapGridComponent grid,
Vector2i position,
int seed,
- EntityCoordinates? coordinates = null)
+ EntityCoordinates? coordinates = null,
+ HashSet<Vector2i>? reservedTiles = null)
{
var cancelToken = new CancellationTokenSource();
var job = new DungeonJob.DungeonJob(
seed,
position,
coordinates,
- cancelToken.Token);
+ cancelToken.Token,
+ reservedTiles);
_dungeonJobs.Add(job, cancelToken);
_dungeonJobQueue.EnqueueJob(job);
}
- public async Task<List<Dungeon>> GenerateDungeonAsync(
+ public async Task<(List<Dungeon>, DungeonData)> GenerateDungeonAsync(
DungeonConfig gen,
EntityUid gridUid,
MapGridComponent grid,
Vector2i position,
- int seed)
+ int seed,
+ HashSet<Vector2i>? reservedTiles = null)
{
var cancelToken = new CancellationTokenSource();
var job = new DungeonJob.DungeonJob(
seed,
position,
null,
- cancelToken.Token);
+ cancelToken.Token,
+ reservedTiles);
_dungeonJobs.Add(job, cancelToken);
_dungeonJobQueue.EnqueueJob(job);
throw job.Exception;
}
- return job.Result!;
+ return job.Result;
}
public Angle GetDungeonRotation(int seed)
-using System.Collections;
using System.Linq;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
-using Content.Server.Atmos;
using Content.Server.Atmos.Components;
using Content.Server.Atmos.EntitySystems;
using Robust.Shared.CPUJob.JobQueues;
using Content.Server.Ghost.Roles.Components;
-using Content.Server.Parallax;
using Content.Server.Procedural;
using Content.Server.Salvage.Expeditions;
-using Content.Server.Salvage.Expeditions.Structure;
using Content.Shared.Atmos;
using Content.Shared.Construction.EntitySystems;
using Content.Shared.Dataset;
using Content.Shared.Gravity;
-using Content.Shared.Parallax.Biomes;
using Content.Shared.Physics;
using Content.Shared.Procedural;
using Content.Shared.Procedural.Loot;
using Content.Shared.Salvage.Expeditions;
using Content.Shared.Salvage.Expeditions.Modifiers;
using Content.Shared.Shuttles.Components;
-using Content.Shared.Storage;
using Robust.Shared.Collections;
-using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Content.Server.Shuttles.Components;
+using Content.Shared.Procedural.Components;
namespace Content.Server.Salvage;
.GetMission(difficultyProto, _missionParams.Seed);
var missionBiome = _prototypeManager.Index<SalvageBiomeModPrototype>(mission.Biome);
+ BiomeComponent? biome = null;
if (missionBiome.BiomePrototype != null)
{
- var biome = _entManager.AddComponent<BiomeComponent>(mapUid);
var biomeSystem = _entManager.System<BiomeSystem>();
- biomeSystem.SetTemplate(mapUid, biome, _prototypeManager.Index<BiomeTemplatePrototype>(missionBiome.BiomePrototype));
- biomeSystem.SetSeed(mapUid, biome, mission.Seed);
- _entManager.Dirty(mapUid, biome);
+
+ biome = biomeSystem.AddBiome(mapUid, missionBiome.BiomePrototype.Value, mission.Seed);
// Gravity
var gravity = _entManager.EnsureComponent<GravityComponent>(mapUid);
dungeonOffset = dungeonRotation.RotateVec(dungeonOffset);
var dungeonMod = _prototypeManager.Index<SalvageDungeonModPrototype>(mission.Dungeon);
var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto);
- var dungeons = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
+ var (dungeons, data) = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset,
_missionParams.Seed));
var dungeon = dungeons.First();
return false;
}
- expedition.DungeonLocation = dungeonOffset;
-
- List<Vector2i> reservedTiles = new();
-
- foreach (var tile in _map.GetTilesIntersecting(mapUid, grid, new Circle(Vector2.Zero, landingPadRadius), false))
+ // Don't modify any dungeon tiles with chunk gen.
+ // Have to defer biome loading until the primo dungen is generated.
+ if (biome != null)
{
- if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _))
- continue;
+ foreach (var tile in dungeon.AllTiles)
+ {
+ biome.ModifiedTiles.Add(tile);
+ }
- reservedTiles.Add(tile.GridIndices);
+ biome.Enabled = true;
}
+ expedition.DungeonLocation = dungeonOffset;
+
var budgetEntries = new List<IBudgetEntry>();
/*
if (!lootProto.Guaranteed)
continue;
- try
+ foreach (var rule in lootProto.LootRules)
{
- await SpawnDungeonLoot(lootProto, mapUid);
- }
- catch (Exception e)
- {
- _sawmill.Error($"Failed to spawn guaranteed loot {lootProto.ID}: {e}");
+ switch (rule)
+ {
+ case BiomeLoot biomeLoot:
+ _biome.AddLayer(mapUid, $"{rule}", biomeLoot.Proto);
+ break;
+ }
}
}
// oh noooooooooooo
}
-
- private async Task SpawnDungeonLoot(SalvageLootPrototype loot, EntityUid gridUid)
- {
- for (var i = 0; i < loot.LootRules.Count; i++)
- {
- var rule = loot.LootRules[i];
-
- switch (rule)
- {
- case BiomeMarkerLoot biomeLoot:
- {
- if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
- {
- _biome.AddMarkerLayer(gridUid, biome, biomeLoot.Prototype);
- }
- }
- break;
- case BiomeTemplateLoot biomeLoot:
- {
- if (_entManager.TryGetComponent<BiomeComponent>(gridUid, out var biome))
- {
- _biome.AddTemplate(gridUid, biome, "Loot", _prototypeManager.Index<BiomeTemplatePrototype>(biomeLoot.Prototype), i);
- }
- }
- break;
- }
- }
- }
}
using Content.Server.GameTicking;
using Content.Server.GameTicking.Events;
using Content.Server.Parallax;
+using Content.Server.Procedural;
using Content.Server.Screens.Components;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Shared.GameTicking;
using Content.Shared.Mobs.Components;
using Content.Shared.Movement.Components;
-using Content.Shared.Parallax.Biomes;
using Content.Shared.Salvage;
using Content.Shared.Shuttles.Components;
using Content.Shared.Tiles;
/// </summary>
private const float RoundStartFTLDuration = 10f;
- private readonly List<ProtoId<BiomeTemplatePrototype>> _arrivalsBiomeOptions = new()
+ private readonly List<EntProtoId> _arrivalsBiomeOptions = new()
{
- "Grasslands",
- "LowDesert",
- "Snow",
+ "BiomeGrasslands",
+ "BiomeLowDesert",
+ "BiomeSnow",
};
public override void Initialize()
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
+using Content.Server.Decals;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Station.Events;
using Content.Shared.Body.Components;
using Content.Shared.CCVar;
using Content.Shared.Database;
+using Content.Shared.Decals;
using Content.Shared.Ghost;
using Content.Shared.Maps;
using Content.Shared.Parallax;
var transform = _physics.GetRelativePhysicsTransform((uid, xform), xform.MapUid.Value);
var aabbs = new List<Box2>(manager.Fixtures.Count);
var tileSet = new List<(Vector2i, Tile)>();
+ TryComp(xform.MapUid.Value, out DecalGridComponent? decalGrid);
foreach (var fixture in manager.Fixtures.Values)
{
aabb = aabb.Enlarged(0.2f);
aabbs.Add(aabb);
- // Handle clearing biome stuff as relevant.
+ if (decalGrid != null)
+ {
+ foreach (var decal in _decals.GetDecalsIntersecting(xform.MapUid.Value, aabb))
+ {
+ _decals.RemoveDecal(xform.MapUid.Value, decal.Index, decalGrid);
+ }
+ }
+
tileSet.Clear();
- _biomes.ReserveTiles(xform.MapUid.Value, aabb, tileSet);
_lookupEnts.Clear();
_immuneEnts.Clear();
// TODO: Ideally we'd query first BEFORE moving grid but needs adjustments above.
using Content.Server.Administration.Logs;
using Content.Server.Body.Systems;
using Content.Server.Buckle.Systems;
+using Content.Server.Decals;
using Content.Server.Parallax;
using Content.Server.Procedural;
using Content.Server.Shuttles.Components;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
[Dependency] private readonly BiomeSystem _biomes = default!;
[Dependency] private readonly BodySystem _bobby = default!;
[Dependency] private readonly BuckleSystem _buckle = default!;
[Dependency] private readonly DamageableSystem _damageSys = default!;
+ [Dependency] private readonly DecalSystem _decals = default!;
[Dependency] private readonly DockingSystem _dockSystem = default!;
[Dependency] private readonly DungeonSystem _dungeon = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
using Content.Server.Station.Systems;
-using Content.Shared.Parallax.Biomes;
using Robust.Shared.Prototypes;
namespace Content.Server.Station.Components;
public sealed partial class StationBiomeComponent : Component
{
[DataField(required: true)]
- public ProtoId<BiomeTemplatePrototype> Biome = "Grasslands";
+ public EntProtoId Biome = "BiomeGrasslands";
// If null, its random
[DataField]
using Content.Server.Parallax;
+using Content.Server.Procedural;
using Content.Server.Station.Components;
using Content.Server.Station.Events;
using Robust.Shared.Prototypes;
if (!TryComp(uid, out ActorComponent? actor))
{
RemComp<TabletopGamerComponent>(uid);
- return;
+ continue;
}
if (actor.PlayerSession.Status != SessionStatus.InGame || !CanSeeTable(uid, gamer.Tabletop))
--- /dev/null
+using Robust.Shared.Configuration;
+
+namespace Content.Shared.CCVar;
+
+public sealed partial class CCVars
+{
+ /// <summary>
+ /// Load range for biomes. Set this higher than PVS so server can have some time to load in before the client arrives.
+ /// </summary>
+ public static readonly CVarDef<float> BiomeLoadRange =
+ CVarDef.Create("biome.load_range", 20f, CVar.SERVERONLY);
+
+ /// <summary>
+ /// Time allocation (ms) for how long biomes are allowed to load.
+ /// </summary>
+ public static readonly CVarDef<float> BiomeLoadTime =
+ CVarDef.Create("biome.load_time", 0.03f, CVar.SERVERONLY);
+}
+++ /dev/null
-using Content.Shared.Parallax.Biomes.Layers;
-using Content.Shared.Parallax.Biomes.Markers;
-using Robust.Shared.GameStates;
-using Robust.Shared.Noise;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.Parallax.Biomes;
-
-[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true), Access(typeof(SharedBiomeSystem))]
-public sealed partial class BiomeComponent : Component
-{
- /// <summary>
- /// Do we load / deload.
- /// </summary>
- [DataField, ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)]
- public bool Enabled = true;
-
- [ViewVariables(VVAccess.ReadWrite), DataField("seed")]
- [AutoNetworkedField]
- public int Seed = -1;
-
- /// <summary>
- /// The underlying entity, decal, and tile layers for the biome.
- /// </summary>
- [DataField("layers")]
- [AutoNetworkedField]
- public List<IBiomeLayer> Layers = new();
-
- /// <summary>
- /// Templates to use for <see cref="Layers"/>.
- /// If this is set on mapinit, it will fill out layers automatically.
- /// If not set, use <c>BiomeSystem</c> to do it.
- /// Prototype reloading will also use this.
- /// </summary>
- [DataField]
- public ProtoId<BiomeTemplatePrototype>? Template;
-
- /// <summary>
- /// If we've already generated a tile and couldn't deload it then we won't ever reload it in future.
- /// Stored by [Chunkorigin, Tiles]
- /// </summary>
- [DataField("modifiedTiles")]
- public Dictionary<Vector2i, HashSet<Vector2i>> ModifiedTiles = new();
-
- /// <summary>
- /// Decals that have been loaded as a part of this biome.
- /// </summary>
- [DataField("decals")]
- public Dictionary<Vector2i, Dictionary<uint, Vector2i>> LoadedDecals = new();
-
- [DataField("entities")]
- public Dictionary<Vector2i, Dictionary<EntityUid, Vector2i>> LoadedEntities = new();
-
- /// <summary>
- /// Currently active chunks
- /// </summary>
- [DataField("loadedChunks")]
- public HashSet<Vector2i> LoadedChunks = new();
-
- #region Markers
-
- /// <summary>
- /// Work out entire marker tiles in advance but only load the entities when in range.
- /// </summary>
- [DataField("pendingMarkers")]
- public Dictionary<Vector2i, Dictionary<string, List<Vector2i>>> PendingMarkers = new();
-
- /// <summary>
- /// Track what markers we've loaded already to avoid double-loading.
- /// </summary>
- [DataField("loadedMarkers", customTypeSerializer:typeof(PrototypeIdDictionarySerializer<HashSet<Vector2i>, BiomeMarkerLayerPrototype>))]
- public Dictionary<string, HashSet<Vector2i>> LoadedMarkers = new();
-
- [DataField]
- public HashSet<ProtoId<BiomeMarkerLayerPrototype>> MarkerLayers = new();
-
- /// <summary>
- /// One-tick forcing of marker layers to bulldoze any entities in the way.
- /// </summary>
- [DataField]
- public HashSet<ProtoId<BiomeMarkerLayerPrototype>> ForcedMarkerLayers = new();
-
- #endregion
-}
+++ /dev/null
-using Content.Shared.Parallax.Biomes.Layers;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Parallax.Biomes;
-
-/// <summary>
-/// A preset group of biome layers to be used for a <see cref="BiomeComponent"/>
-/// </summary>
-[Prototype]
-public sealed partial class BiomeTemplatePrototype : IPrototype
-{
- [IdDataField] public string ID { get; private set; } = default!;
-
- [DataField("layers")]
- public List<IBiomeLayer> Layers = new();
-}
+++ /dev/null
-using Content.Shared.Decals;
-using Content.Shared.Maps;
-using Robust.Shared.Noise;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.Parallax.Biomes.Layers;
-
-[Serializable, NetSerializable]
-public sealed partial class BiomeDecalLayer : IBiomeWorldLayer
-{
- /// <inheritdoc/>
- [DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
- public List<string> AllowedTiles { get; private set; } = new();
-
- /// <summary>
- /// Divide each tile up by this amount.
- /// </summary>
- [DataField("divisions")]
- public float Divisions = 1f;
-
- [DataField("noise")]
- public FastNoiseLite Noise { get; private set; } = new(0);
-
- /// <inheritdoc/>
- [DataField("threshold")]
- public float Threshold { get; private set; } = 0.8f;
-
- /// <inheritdoc/>
- [DataField("invert")] public bool Invert { get; private set; } = false;
-
- [DataField("decals", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer<DecalPrototype>))]
- public List<string> Decals = new();
-}
+++ /dev/null
-using Robust.Shared.Noise;
-using Robust.Shared.Serialization;
-
-namespace Content.Shared.Parallax.Biomes.Layers;
-
-/// <summary>
-/// Dummy layer that specifies a marker to be replaced by external code.
-/// For example if they wish to add their own layers at specific points across different templates.
-/// </summary>
-[Serializable, NetSerializable]
-public sealed partial class BiomeDummyLayer : IBiomeLayer
-{
- [DataField("id", required: true)] public string ID = string.Empty;
-
- public FastNoiseLite Noise { get; } = new();
- public float Threshold { get; }
- public bool Invert { get; }
-}
+++ /dev/null
-using Content.Shared.Maps;
-using Robust.Shared.Noise;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
-
-namespace Content.Shared.Parallax.Biomes.Layers;
-
-[Serializable, NetSerializable]
-public sealed partial class BiomeEntityLayer : IBiomeWorldLayer
-{
- /// <inheritdoc/>
- [DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
- public List<string> AllowedTiles { get; private set; } = new();
-
- [DataField("noise")] public FastNoiseLite Noise { get; private set; } = new(0);
-
- /// <inheritdoc/>
- [DataField("threshold")]
- public float Threshold { get; private set; } = 0.5f;
-
- /// <inheritdoc/>
- [DataField("invert")] public bool Invert { get; private set; } = false;
-
- [DataField("entities", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
- public List<string> Entities = new();
-}
+++ /dev/null
-using Robust.Shared.Noise;
-using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.Parallax.Biomes.Layers;
-
-/// <summary>
-/// Contains more biome layers recursively via a biome template.
-/// Can be used for sub-biomes.
-/// </summary>
-[Serializable, NetSerializable]
-public sealed partial class BiomeMetaLayer : IBiomeLayer
-{
- [DataField("noise")]
- public FastNoiseLite Noise { get; private set; } = new(0);
-
- /// <inheritdoc/>
- [DataField("threshold")]
- public float Threshold { get; private set; } = -1f;
-
- /// <inheritdoc/>
- [DataField("invert")]
- public bool Invert { get; private set; }
-
- [DataField("template", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
- public string Template = string.Empty;
-}
+++ /dev/null
-using Robust.Shared.Noise;
-
-namespace Content.Shared.Parallax.Biomes.Layers;
-
-[ImplicitDataDefinitionForInheritors]
-public partial interface IBiomeLayer
-{
- /// <summary>
- /// Seed is used an offset from the relevant BiomeComponent's seed.
- /// </summary>
- FastNoiseLite Noise { get; }
-
- /// <summary>
- /// Threshold for this layer to be present. If set to 0 forces it for every tile.
- /// </summary>
- float Threshold { get; }
-
- /// <summary>
- /// Is the thresold inverted so we need to be lower than it.
- /// </summary>
- public bool Invert { get; }
-}
+++ /dev/null
-namespace Content.Shared.Parallax.Biomes.Layers;
-
-/// <summary>
-/// Handles actual objects such as decals and entities.
-/// </summary>
-public partial interface IBiomeWorldLayer : IBiomeLayer
-{
- /// <summary>
- /// What tiles we're allowed to spawn on, real or biome.
- /// </summary>
- List<string> AllowedTiles { get; }
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.Parallax.Biomes.Markers;
-
-/// <summary>
-/// Spawns entities inside of the specified area with the minimum specified radius.
-/// </summary>
-[Prototype]
-public sealed partial class BiomeMarkerLayerPrototype : IBiomeMarkerLayer
-{
- [IdDataField] public string ID { get; private set; } = default!;
-
- /// <summary>
- /// Checks for the relevant entity for the tile before spawning. Useful for substituting walls with ore veins for example.
- /// </summary>
- [DataField]
- public Dictionary<EntProtoId, EntProtoId> EntityMask { get; private set; } = new();
-
- /// <summary>
- /// Default prototype to spawn. If null will fall back to entity mask.
- /// </summary>
- [DataField]
- public string? Prototype { get; private set; }
-
- /// <summary>
- /// Minimum radius between 2 points
- /// </summary>
- [DataField("radius")]
- public float Radius = 32f;
-
- /// <summary>
- /// Maximum amount of group spawns
- /// </summary>
- [DataField("maxCount")]
- public int MaxCount = int.MaxValue;
-
- /// <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;
-
- /// <inheritdoc />
- [DataField("size")]
- public int Size { get; private set; } = 128;
-}
+++ /dev/null
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Parallax.Biomes.Markers;
-
-/// <summary>
-/// Specifies one-off marker points to be used. This could be for dungeon markers, mob markers, etc.
-/// These are run outside of the tile / decal / entity layers.
-/// </summary>
-public interface IBiomeMarkerLayer : IPrototype
-{
- /// <summary>
- /// Biome template to use as a mask for this layer.
- /// </summary>
- public Dictionary<EntProtoId, EntProtoId> EntityMask { get; }
-
- public string? Prototype { get; }
-
- /// <summary>
- /// How large the pre-generated points area is.
- /// </summary>
- public int Size { get; }
-}
+++ /dev/null
-using System.Diagnostics.CodeAnalysis;
-using System.Numerics;
-using Content.Shared.Maps;
-using Content.Shared.Parallax.Biomes.Layers;
-using Robust.Shared.Map;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Noise;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.Manager;
-using Robust.Shared.Utility;
-
-namespace Content.Shared.Parallax.Biomes;
-
-public abstract class SharedBiomeSystem : EntitySystem
-{
- [Dependency] protected readonly IPrototypeManager ProtoManager = default!;
- [Dependency] private readonly ISerializationManager _serManager = default!;
- [Dependency] protected readonly ITileDefinitionManager TileDefManager = default!;
- [Dependency] private readonly TileSystem _tile = default!;
- [Dependency] private readonly SharedMapSystem _map = default!;
-
- protected const byte ChunkSize = 8;
-
- private T Pick<T>(List<T> collection, float value)
- {
- // Listen I don't need this exact and I'm too lazy to finetune just for random ent picking.
- value %= 1f;
- value = Math.Clamp(value, 0f, 1f);
-
- if (collection.Count == 1)
- return collection[0];
-
- var randValue = value * collection.Count;
-
- foreach (var item in collection)
- {
- randValue -= 1f;
-
- if (randValue <= 0f)
- {
- return item;
- }
- }
-
- throw new ArgumentOutOfRangeException();
- }
-
- private int Pick(int count, float value)
- {
- value %= 1f;
- value = Math.Clamp(value, 0f, 1f);
-
- if (count == 1)
- return 0;
-
- value *= count;
-
- for (var i = 0; i < count; i++)
- {
- value -= 1f;
-
- if (value <= 0f)
- {
- return i;
- }
- }
-
- throw new ArgumentOutOfRangeException();
- }
-
- public bool TryGetBiomeTile(EntityUid uid, MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out Tile? tile)
- {
- if (_map.TryGetTileRef(uid, grid, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
- {
- tile = tileRef.Tile;
- return true;
- }
-
- if (!TryComp<BiomeComponent>(uid, out var biome))
- {
- tile = null;
- return false;
- }
-
- return TryGetBiomeTile(indices, biome.Layers, biome.Seed, (uid, grid), out tile);
- }
-
- /// <summary>
- /// Tries to get the tile, real or otherwise, for the specified indices.
- /// </summary>
- public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
- {
- if (grid is { } gridEnt && _map.TryGetTileRef(gridEnt, gridEnt.Comp, indices, out var tileRef) && !tileRef.Tile.IsEmpty)
- {
- tile = tileRef.Tile;
- return true;
- }
-
- return TryGetTile(indices, layers, seed, grid, out tile);
- }
-
- /// <summary>
- /// Tries to get the tile, real or otherwise, for the specified indices.
- /// </summary>
- [Obsolete("Use the Entity<MapGridComponent>? overload")]
- public bool TryGetBiomeTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
- {
- return TryGetBiomeTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
- }
-
- /// <summary>
- /// Gets the underlying biome tile, ignoring any existing tile that may be there.
- /// </summary>
- public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid, [NotNullWhen(true)] out Tile? tile)
- {
- for (var i = layers.Count - 1; i >= 0; i--)
- {
- var layer = layers[i];
- var noiseCopy = GetNoise(layer.Noise, seed);
-
- var invert = layer.Invert;
- var value = noiseCopy.GetNoise(indices.X, indices.Y);
- value = invert ? value * -1 : value;
-
- if (value < layer.Threshold)
- continue;
-
- // Check if the tile is from meta layer, otherwise fall back to default layers.
- if (layer is BiomeMetaLayer meta)
- {
- if (TryGetBiomeTile(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out tile))
- {
- return true;
- }
-
- continue;
- }
-
- if (layer is not BiomeTileLayer tileLayer)
- continue;
-
- if (TryGetTile(indices, noiseCopy, tileLayer.Invert, tileLayer.Threshold, ProtoManager.Index(tileLayer.Tile), tileLayer.Variants, out tile))
- {
- return true;
- }
- }
-
- tile = null;
- return false;
- }
-
- /// <summary>
- /// Gets the underlying biome tile, ignoring any existing tile that may be there.
- /// </summary>
- [Obsolete("Use the Entity<MapGridComponent>? overload")]
- public bool TryGetTile(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile)
- {
- return TryGetTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile);
- }
-
- /// <summary>
- /// Gets the underlying biome tile, ignoring any existing tile that may be there.
- /// </summary>
- private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List<byte>? variants, [NotNullWhen(true)] out Tile? tile)
- {
- var found = noise.GetNoise(indices.X, indices.Y);
- found = invert ? found * -1 : found;
-
- if (found < threshold)
- {
- tile = null;
- return false;
- }
-
- byte variant = 0;
- var variantCount = variants?.Count ?? tileDef.Variants;
-
- // Pick a variant tile if they're available as well
- if (variantCount > 1)
- {
- var variantValue = (noise.GetNoise(indices.X * 8, indices.Y * 8, variantCount) + 1f) * 100;
- variant = _tile.PickVariant(tileDef, (int)variantValue);
- }
-
- tile = new Tile(tileDef.TileId, variant);
- return true;
- }
-
- /// <summary>
- /// Tries to get the relevant entity for this tile.
- /// </summary>
- public bool TryGetEntity(Vector2i indices, BiomeComponent component, Entity<MapGridComponent>? grid,
- [NotNullWhen(true)] out string? entity)
- {
- if (!TryGetBiomeTile(indices, component.Layers, component.Seed, grid, out var tile))
- {
- entity = null;
- return false;
- }
-
- return TryGetEntity(indices, component.Layers, tile.Value, component.Seed, grid, out entity);
- }
-
- /// <summary>
- /// Tries to get the relevant entity for this tile.
- /// </summary>
- [Obsolete("Use the Entity<MapGridComponent>? overload")]
- public bool TryGetEntity(Vector2i indices, BiomeComponent component, MapGridComponent grid,
- [NotNullWhen(true)] out string? entity)
- {
- return TryGetEntity(indices, component, grid == null ? null : (grid.Owner, grid), out entity);
- }
-
- public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, Entity<MapGridComponent>? grid,
- [NotNullWhen(true)] out string? entity)
- {
- var tileId = TileDefManager[tileRef.TypeId].ID;
-
- for (var i = layers.Count - 1; i >= 0; i--)
- {
- var layer = layers[i];
-
- switch (layer)
- {
- case BiomeDummyLayer:
- continue;
- case IBiomeWorldLayer worldLayer:
- if (!worldLayer.AllowedTiles.Contains(tileId))
- continue;
-
- break;
- case BiomeMetaLayer:
- break;
- default:
- continue;
- }
-
- var noiseCopy = GetNoise(layer.Noise, seed);
-
- var invert = layer.Invert;
- var value = noiseCopy.GetNoise(indices.X, indices.Y);
- value = invert ? value * -1 : value;
-
- if (value < layer.Threshold)
- continue;
-
- if (layer is BiomeMetaLayer meta)
- {
- if (TryGetEntity(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, tileRef, seed, grid, out entity))
- {
- return true;
- }
-
- continue;
- }
-
- // Decals might block entity so need to check if there's one in front of us.
- if (layer is not BiomeEntityLayer biomeLayer)
- {
- entity = null;
- return false;
- }
-
- var noiseValue = noiseCopy.GetNoise(indices.X, indices.Y, i);
- entity = Pick(biomeLayer.Entities, (noiseValue + 1f) / 2f);
- return true;
- }
-
- entity = null;
- return false;
- }
-
- [Obsolete("Use the Entity<MapGridComponent>? overload")]
- public bool TryGetEntity(Vector2i indices, List<IBiomeLayer> layers, Tile tileRef, int seed, MapGridComponent grid,
- [NotNullWhen(true)] out string? entity)
- {
- return TryGetEntity(indices, layers, tileRef, seed, grid == null ? null : (grid.Owner, grid), out entity);
- }
-
- /// <summary>
- /// Tries to get the relevant decals for this tile.
- /// </summary>
- public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, Entity<MapGridComponent>? grid,
- [NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
- {
- if (!TryGetBiomeTile(indices, layers, seed, grid, out var tileRef))
- {
- decals = null;
- return false;
- }
-
- var tileId = TileDefManager[tileRef.Value.TypeId].ID;
-
- for (var i = layers.Count - 1; i >= 0; i--)
- {
- var layer = layers[i];
-
- // Entities might block decal so need to check if there's one in front of us.
- switch (layer)
- {
- case BiomeDummyLayer:
- continue;
- case IBiomeWorldLayer worldLayer:
- if (!worldLayer.AllowedTiles.Contains(tileId))
- continue;
-
- break;
- case BiomeMetaLayer:
- break;
- default:
- continue;
- }
-
- var invert = layer.Invert;
- var noiseCopy = GetNoise(layer.Noise, seed);
- var value = noiseCopy.GetNoise(indices.X, indices.Y);
- value = invert ? value * -1 : value;
-
- if (value < layer.Threshold)
- continue;
-
- if (layer is BiomeMetaLayer meta)
- {
- if (TryGetDecals(indices, ProtoManager.Index<BiomeTemplatePrototype>(meta.Template).Layers, seed, grid, out decals))
- {
- return true;
- }
-
- continue;
- }
-
- // Check if the other layer should even render, if not then keep going.
- if (layer is not BiomeDecalLayer decalLayer)
- {
- decals = null;
- return false;
- }
-
- decals = new List<(string ID, Vector2 Position)>();
-
- for (var x = 0; x < decalLayer.Divisions; x++)
- {
- for (var y = 0; y < decalLayer.Divisions; y++)
- {
- var index = new Vector2(indices.X + x * 1f / decalLayer.Divisions, indices.Y + y * 1f / decalLayer.Divisions);
- var decalValue = noiseCopy.GetNoise(index.X, index.Y);
- decalValue = invert ? decalValue * -1 : decalValue;
-
- if (decalValue < decalLayer.Threshold)
- continue;
-
- decals.Add((Pick(decalLayer.Decals, (noiseCopy.GetNoise(indices.X, indices.Y, x + y * decalLayer.Divisions) + 1f) / 2f), index));
- }
- }
-
- // Check other layers
- if (decals.Count == 0)
- continue;
-
- return true;
- }
-
- decals = null;
- return false;
- }
-
- /// <summary>
- /// Tries to get the relevant decals for this tile.
- /// </summary>
- [Obsolete("Use the Entity<MapGridComponent>? overload")]
- public bool TryGetDecals(Vector2i indices, List<IBiomeLayer> layers, int seed, MapGridComponent grid,
- [NotNullWhen(true)] out List<(string ID, Vector2 Position)>? decals)
- {
- return TryGetDecals(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out decals);
- }
-
- private FastNoiseLite GetNoise(FastNoiseLite seedNoise, int seed)
- {
- var noiseCopy = new FastNoiseLite();
- _serManager.CopyTo(seedNoise, ref noiseCopy, notNullableOverride: true);
- noiseCopy.SetSeed(noiseCopy.GetSeed() + seed);
- // Ensure re-calculate is run.
- noiseCopy.SetFractalOctaves(noiseCopy.GetFractalOctaves());
- return noiseCopy;
- }
-}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Procedural.Components;
+
+/// <summary>
+/// A layer inside of <see cref="BiomeComponent"/>
+/// </summary>
+[DataRecord]
+public sealed record BiomeMetaLayer
+{
+ /// <summary>
+ /// Chunk dimensions for this meta layer. Will try to infer it from the first layer of the dungeon if null.
+ /// </summary>
+ [DataField]
+ public int? Size;
+
+ /// <summary>
+ /// Meta layers that this one requires to be loaded first.
+ /// Will ensure all of the chunks for our corresponding area are loaded.
+ /// </summary>
+ public List<string>? DependsOn;
+
+ /// <summary>
+ /// Can this layer be unloaded if no one is in range.
+ /// </summary>
+ public bool CanUnload = true;
+
+ /// <summary>
+ /// Dungeon config to load inside the specified area.
+ /// </summary>
+ [DataField(required: true)]
+ public ProtoId<DungeonConfigPrototype> Dungeon = new();
+}
+
+[RegisterComponent]
+public sealed partial class BiomeComponent : Component
+{
+ /// <summary>
+ /// Can we load / unload chunks.
+ /// </summary>
+ [DataField]
+ public bool Enabled = true;
+
+ /// <summary>
+ /// Areas queued for preloading. Will add these during <see cref="BiomeLoadJob"/> and then flag as modified so they retain.
+ /// </summary>
+ [DataField]
+ public List<Box2i> PreloadAreas = new();
+
+ /// <summary>
+ /// Is there currently a job that's loading.
+ /// </summary>
+ public bool Loading = false;
+
+ [DataField]
+ public int Seed;
+
+ /// <summary>
+ /// Layer key and associated data.
+ /// </summary>
+ [DataField(required: true)]
+ public Dictionary<string, BiomeMetaLayer> Layers = new();
+
+ /// <summary>
+ /// Layer removals that are pending.
+ /// </summary>
+ [DataField]
+ public List<string> PendingRemovals = new();
+
+ /// <summary>
+ /// Data that is currently loaded.
+ /// </summary>
+ [DataField]
+ public Dictionary<string, Dictionary<Vector2i, DungeonData>> LoadedData = new();
+
+ /// <summary>
+ /// Flag modified tiles so we don't try and unload / reload them.
+ /// </summary>
+ [DataField]
+ public HashSet<Vector2i> ModifiedTiles = new();
+
+ /// <summary>
+ /// Bounds loaded by players for this tick.
+ /// </summary>
+ public List<Box2i> LoadedBounds = new();
+}
--- /dev/null
+using Robust.Shared.GameStates;
+
+namespace Content.Shared.Procedural.Components;
+
+/// <summary>
+/// Will forcibly unload an entity no matter what. Useful if you have consistent entities that will never be default or the likes.
+/// </summary>
+[RegisterComponent, NetworkedComponent]
+public sealed partial class BiomeForceUnloadComponent : Component;
--- /dev/null
+namespace Content.Shared.Procedural.Distance;
+
+public sealed partial class DunGenDistanceSquared : IDunGenDistance
+{
+ [DataField]
+ public float BlendWeight { get; set; } = 0.50f;
+}
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?
+ /// Should we reserve the tiles generated by this config so no other layers at the same level can spawn on this tile?
/// </summary>
[DataField]
public bool ReserveTiles;
+ /// <summary>
+ /// Should we return the reserved tiles to the upper level.
+ /// Set to false if you don't care if this dungeon has its tiles overwritten at higher levels.
+ /// </summary>
+ [DataField]
+ public bool ReturnReserved = true;
+
/// <summary>
/// Minimum times to run the config.
/// </summary>
--- /dev/null
+using System.Linq;
+using System.Numerics;
+using Robust.Shared.Map;
+
+namespace Content.Shared.Procedural;
+
+/// <summary>
+/// Contains the loaded data for a dungeon.
+/// </summary>
+[DataDefinition]
+public sealed partial class DungeonData
+{
+ [DataField]
+ public Dictionary<uint, Vector2> Decals = new();
+
+ [DataField]
+ public Dictionary<EntityUid, Vector2i> Entities = new();
+
+ [DataField]
+ public Dictionary<Vector2i, Tile> Tiles = new();
+
+ public static DungeonData Empty = new();
+
+ public void Merge(DungeonData data)
+ {
+ foreach (var did in data.Decals)
+ {
+ Decals[did.Key] = did.Value;
+ }
+
+ foreach (var ent in data.Entities)
+ {
+ Entities[ent.Key] = ent.Value;
+ }
+
+ foreach (var tile in data.Tiles)
+ {
+ Tiles[tile.Key] = tile.Value;
+ }
+ }
+}
--- /dev/null
+using Robust.Shared.Noise;
+
+namespace Content.Shared.Procedural.DungeonGenerators;
+
+/// <summary>
+/// Turns a chunked area into a dungeon for layer purposes. Assumes the position is the BL origin.
+/// </summary>
+public sealed partial class ChunkDunGen : IDunGenLayer
+{
+ [DataField]
+ public int Size = 16;
+
+ /// <summary>
+ /// Noise to apply for each tile conditionally.
+ /// </summary>
+ [DataField]
+ public FastNoiseLite? Noise;
+
+ /// <summary>
+ /// Threshold for noise. Does nothing if <see cref="Noise"/> is null.
+ /// </summary>
+ [DataField]
+ public float Threshold = -1f;
+}
{
[DataField(required: true)]
public ProtoId<DungeonConfigPrototype> Proto;
+
+ /// <summary>
+ /// Minimum and maximum penetration.
+ /// </summary>
+ [DataField]
+ public Vector2i Penetration = new Vector2i(5, 15);
}
[DataField]
public DungeonInheritance InheritDungeons = DungeonInheritance.None;
+ /// <summary>
+ /// Should we pass in the current level's reserved tiles to the prototype.
+ /// </summary>
+ [DataField]
+ public ReservedInheritance InheritReserved = ReservedInheritance.All;
+
[DataField(required: true)]
public ProtoId<DungeonConfigPrototype> Proto;
}
/// </summary>
All,
}
+
+public enum ReservedInheritance : byte
+{
+ /// <summary>
+ /// Don't inherit any reserved tiles.
+ /// </summary>
+ None,
+
+ /// <summary>
+ /// Inherit reserved tiles,
+ /// </summary>
+ All,
+}
using Content.Shared.EntityTable;
using Content.Shared.Maps;
+using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
using Content.Shared.EntityTable;
using Content.Shared.Maps;
-using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
+using System.Numerics;
using Content.Shared.Maps;
+using Content.Shared.Procedural.Distance;
+using Robust.Shared.Noise;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonLayers;
/// <summary>
/// Fills unreserved tiles with the specified entity prototype.
/// </summary>
-/// <remarks>
-/// DungeonData keys are:
-/// - Fill
-/// </remarks>
public sealed partial class FillGridDunGen : IDunGenLayer
{
/// <summary>
[DataField(required: true)]
public EntProtoId Entity;
+
+ #region Noise
+
+ [DataField]
+ public bool Invert;
+
+ /// <summary>
+ /// Optionally don't spawn entities if the noise value matches.
+ /// </summary>
+ [DataField]
+ public FastNoiseLite? ReservedNoise;
+
+ /// <summary>
+ /// Noise threshold for <see cref="ReservedNoise"/>. Does nothing without it.
+ /// </summary>
+ [DataField]
+ public float Threshold = -1f;
+
+ [DataField]
+ public IDunGenDistance? DistanceConfig;
+
+ [DataField]
+ public Vector2 Size;
+
+ #endregion
}
using Content.Shared.EntityTable;
-using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.DungeonLayers;
-
/// <summary>
/// Spawns mobs inside of the dungeon randomly.
/// </summary>
--- /dev/null
+using Robust.Shared.Noise;
+
+namespace Content.Shared.Procedural.DungeonLayers;
+
+/// <summary>
+/// Sets tiles as rooved.
+/// </summary>
+public sealed partial class RoofDunGen : IDunGenLayer
+{
+ [DataField]
+ public float Threshold = -1f;
+
+ [DataField]
+ public FastNoiseLite? Noise;
+}
using Content.Shared.EntityTable;
using Content.Shared.Maps;
-using Content.Shared.Storage;
using Robust.Shared.Prototypes;
namespace Content.Shared.Procedural.PostGeneration;
--- /dev/null
+using Content.Shared.Decals;
+using Content.Shared.Maps;
+using Robust.Shared.Noise;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Procedural.DungeonLayers;
+
+public sealed partial class SampleDecalDunGen : IDunGenLayer
+{
+ /// <summary>
+ /// Reserve any tiles we update.
+ /// </summary>
+ [DataField]
+ public bool ReserveTiles = true;
+
+ [DataField(customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
+ public List<string> AllowedTiles { get; private set; } = new();
+
+ /// <summary>
+ /// Divide each tile up by this amount.
+ /// </summary>
+ [DataField]
+ public float Divisions = 1f;
+
+ [DataField]
+ public FastNoiseLite Noise { get; private set; } = new(0);
+
+ [DataField]
+ public float Threshold { get; private set; } = 0.8f;
+
+ [DataField] public bool Invert { get; private set; } = false;
+
+ [DataField(required: true)]
+ public List<ProtoId<DecalPrototype>> Decals = new();
+}
--- /dev/null
+using Content.Shared.Maps;
+using Robust.Shared.Noise;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared.Procedural.DungeonLayers;
+
+/// <summary>
+/// Samples noise to spawn the specified entity
+/// </summary>
+public sealed partial class SampleEntityDunGen : IDunGenLayer
+{
+ /// <summary>
+ /// Reserve any tiles we update.
+ /// </summary>
+ [DataField]
+ public bool ReserveTiles = true;
+
+ [DataField(customTypeSerializer:typeof(PrototypeIdListSerializer<ContentTileDefinition>))]
+ public List<string> AllowedTiles { get; private set; } = new();
+
+ [DataField] public FastNoiseLite Noise { get; private set; } = new(0);
+
+ [DataField]
+ public float Threshold { get; private set; } = 0.5f;
+
+ [DataField] public bool Invert { get; private set; } = false;
+
+ [DataField]
+ public List<EntProtoId> Entities = new();
+}
using Robust.Shared.Noise;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
-namespace Content.Shared.Parallax.Biomes.Layers;
+namespace Content.Shared.Procedural.DungeonLayers;
+/// <summary>
+/// Samples noise and spawns the specified tile in the dungeon area.
+/// </summary>
[Serializable, NetSerializable]
-public sealed partial class BiomeTileLayer : IBiomeLayer
+public sealed partial class SampleTileDunGen : IDunGenLayer
{
+ /// <summary>
+ /// Reserve any tiles we update.
+ /// </summary>
+ [DataField]
+ public bool ReserveTiles = true;
+
[DataField] public FastNoiseLite Noise { get; private set; } = new(0);
- /// <inheritdoc/>
[DataField]
public float Threshold { get; private set; } = 0.5f;
- /// <inheritdoc/>
[DataField] public bool Invert { get; private set; } = false;
/// <summary>
using Content.Shared.Maps;
using Robust.Shared.Prototypes;
-namespace Content.Shared.Procedural.PostGeneration;
+namespace Content.Shared.Procedural.DungeonLayers;
/// <summary>
/// Connects dungeons via points that get subdivided.
/// 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;
+ public int DivisionDistance = 20;
/// <summary>
/// How much each subdivision can vary from the middle.
/// </summary>
[DataField]
- public float VarianceMax = 0.35f;
+ public float VarianceMax = 0.15f;
}
--- /dev/null
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared.Procedural.Loot;
+
+/// <summary>
+/// Adds the prototype as a biome layer.
+/// </summary>
+public sealed partial class BiomeLoot : IDungeonLoot
+{
+ [DataField(required: true)]
+ public ProtoId<DungeonConfigPrototype> Proto;
+}
+++ /dev/null
-using Content.Shared.Parallax.Biomes.Markers;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Dictionary;
-
-namespace Content.Shared.Procedural.Loot;
-
-/// <summary>
-/// Adds a biome marker layer for dungeon loot.
-/// </summary>
-public sealed partial class BiomeMarkerLoot : IDungeonLoot
-{
- [DataField("proto", required: true)]
- public ProtoId<BiomeMarkerLayerPrototype> Prototype = new();
-}
+++ /dev/null
-using Content.Shared.Parallax.Biomes;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Shared.Procedural.Loot;
-
-/// <summary>
-/// Adds a biome template layer for dungeon loot.
-/// </summary>
-public sealed partial class BiomeTemplateLoot : IDungeonLoot
-{
- [DataField("proto", required: true)]
- public ProtoId<BiomeTemplatePrototype> Prototype = string.Empty;
-}
+++ /dev/null
-using Content.Shared.Maps;
-using Content.Shared.Parallax.Biomes;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Procedural.PostGeneration;
-
-/// <summary>
-/// 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 BiomeDunGen : IDunGenLayer
-{
- [DataField(required: true)]
- public ProtoId<BiomeTemplatePrototype> BiomeTemplate;
-
- /// <summary>
- /// creates a biome only on the specified tiles
- /// </summary>
- [DataField]
- public HashSet<ProtoId<ContentTileDefinition>>? TileMask;
-}
+++ /dev/null
-using Content.Shared.Random;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Procedural.PostGeneration;
-
-/// <summary>
-/// Spawns the specified marker layer on top of the dungeon rooms.
-/// </summary>
-public sealed partial class BiomeMarkerLayerDunGen : IDunGenLayer
-{
- /// <summary>
- /// How many times to spawn marker layers; can duplicate.
- /// </summary>
- [DataField]
- public int Count = 6;
-
- [DataField(required: true)]
- public ProtoId<WeightedRandomPrototype> MarkerTemplate;
-}
-using Content.Shared.Parallax.Biomes;
using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Shared.Salvage.Expeditions.Modifiers;
[DataField("weather")]
public bool Weather = true;
- [DataField("biome", required: true, customTypeSerializer:typeof(PrototypeIdSerializer<BiomeTemplatePrototype>))]
- public string? BiomePrototype;
+ [DataField("biome", required: true)]
+ public EntProtoId? BiomePrototype;
}
-cmd-biome_clear-desc = Clears a biome entirely
-cmd-biome_clear-help = biome_clear <biomecomponent>
cmd-biome_addlayer-desc = Adds another biome layer
cmd-biome_addlayer-help = biome_addlayer <mapid> <biometemplate> [seed offset]
-cmd-biome_addmarkerlayer-desc = Adds another biome marker layer
-cmd-biome_addmarkerlayer-help = biome_addmarkerlayer <mapid> <biomemarkerlayer>
drawdepth: BelowFloor
layers:
- state: shoreline_water
+ - type: BiomeForceUnload
+ #- type: ContainerContainer
+ # containers:
+ # solution@pool: !type:ContainerSlot
- type: SolutionContainerManager
solutions:
pool:
- maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable.
+ maxVol: 9999999 #.inf seems to break the whole yaml file, but would definitely be preferable.
reagents:
- ReagentId: Water
Quantity: 9999999
lacunarity: 2
# Generate biome
- - !type:BiomeDunGen
- biomeTemplate: Asteroid
+ - !type:PrototypeDunGen
+ proto: Asteroid
+ inheritDungeons: All
- !type:OreDunGen
replacement: AsteroidRock
entity: AsteroidRockGibtonite
lacunarity: 2
# Generate biome
- - !type:BiomeDunGen
- biomeTemplate: Asteroid
+ - !type:PrototypeDunGen
+ proto: Asteroid
+ inheritDungeons: All
- !type:OreDunGen
replacement: AsteroidRock
entity: AsteroidRockGibtonite
cellularDistanceFunction: Euclidean
# Generate biome
- - !type:BiomeDunGen
- biomeTemplate: Asteroid
+ - !type:PrototypeDunGen
+ proto: Asteroid
+ inheritDungeons: All
- !type:OreDunGen
replacement: AsteroidRock
entity: AsteroidRockGibtonite
lacunarity: 2
# Generate biome
- - !type:BiomeDunGen
- biomeTemplate: Asteroid
+ - !type:PrototypeDunGen
+ proto: Asteroid
+ inheritDungeons: All
- !type:OreDunGen
replacement: AsteroidRock
entity: AsteroidRockGibtonite
gain: 0.5
# Generate biome
- - !type:BiomeDunGen
- biomeTemplate: SpaceDebris
+ - !type:PrototypeDunGen
+ proto: SpaceDebris
+ inheritDungeons: All
- type: dungeonConfig
id: ChunkDebrisSmall
gain: 0.5
# Generate biome
- - !type:BiomeDunGen
- biomeTemplate: SpaceDebris
+ - !type:PrototypeDunGen
+ proto: SpaceDebris
+ inheritDungeons: All
# Asteroid
-- type: biomeTemplate
+- type: dungeonConfig
id: SpaceDebris
layers:
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.20
noise:
seed: 0
- WallReinforced
- WallSolid
- WallSolid
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.5
noise:
seed: 0
- Grille
- Grille
- GrilleBroken
- - !type:BiomeDecalLayer
+ - !type:SampleDecalDunGen
allowedTiles:
- FloorSteel
threshold: -0.5
- DirtMedium
- DirtMedium
- DirtLight
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.45
noise:
seed: 1
- FloorSteel
entities:
- SalvageSpawnerStructuresVarious
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
allowedTiles:
- FloorSteel
- Plating
- SalvageSpawnerScrapCommon
- SalvageSpawnerScrapCommon
- SalvageSpawnerScrapCommon75
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
allowedTiles:
- FloorSteel
- Plating
- SalvageSpawnerTreasure
- SalvageSpawnerTreasure
- SalvageSpawnerTreasureValuable
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
allowedTiles:
- FloorSteel
- Plating
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: Lizards
- prototype: MobLizard
- minGroupSize: 3
- maxGroupSize: 5
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobLizard
+ amount: !type:RangeNumberSelector
+ range: 3, 5
-- type: biomeMarkerLayer
+
+- type: dungeonConfig
id: WatchersLavaland
- prototype: MobWatcherLavaland
- minGroupSize: 3
- maxGroupSize: 3
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobWatcherLavaland
+ amount: !type:RangeNumberSelector
+ range: 3, 3
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: WatchersIcewing
- prototype: MobWatcherIcewing
- minGroupSize: 3
- maxGroupSize: 3
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobWatcherIcewing
+ amount: !type:RangeNumberSelector
+ range: 3, 3
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: WatchersMagmawing
- prototype: MobWatcherMagmawing
- minGroupSize: 3
- maxGroupSize: 3
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobWatcherMagmawing
+ amount: !type:RangeNumberSelector
+ range: 3, 3
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: Cows
- prototype: MobCow
- minGroupSize: 1
- maxGroupSize: 2
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobCow
+ amount: !type:RangeNumberSelector
+ range: 1, 2
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: Chickens
- prototype: MobChicken
- minGroupSize: 1
- maxGroupSize: 2
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobChicken
+ amount: !type:RangeNumberSelector
+ range: 1, 2
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: Pigs
- prototype: MobPig
- minGroupSize: 1
- maxGroupSize: 2
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobPig
+ amount: !type:RangeNumberSelector
+ range: 1, 2
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: Foxes
- prototype: MobFox
- minGroupSize: 1
- maxGroupSize: 1
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobFox
+ amount: !type:RangeNumberSelector
+ range: 1, 1
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: Goats
- prototype: MobGoat
- minGroupSize: 1
- maxGroupSize: 1
-
-# TODO: Needs to be more robust
-- type: biomeMarkerLayer
- id: Xenos
- prototype: MobXeno
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobGoat
+ amount: !type:RangeNumberSelector
+ range: 1, 1
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: Carps
- prototype: MobCarpDungeon
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobCarp
+ amount: !type:RangeNumberSelector
+ range: 1, 2
+- type: dungeonConfig
+ id: Xenos
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:EntityTableDunGen
+ table: !type:EntSelector
+ id: MobXeno
+ amount: !type:RangeNumberSelector
+ range: 1, 2
-#- type: biomeMarkerLayer
-# id: Experiment
-# proto: DungeonMarkerExperiment
# Low value
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreIron
- entityMask:
- AsteroidRock: AsteroidRockTin
- WallRock: WallRockTin
- WallRockBasalt: WallRockBasaltTin
- WallRockChromite: WallRockChromiteTin
- WallRockSand: WallRockSandTin
- WallRockSnow: WallRockSnowTin
- maxCount: 30
- minGroupSize: 10
- maxGroupSize: 15
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockTin
+ count: 30
+ minGroupSize: 10
+ maxGroupSize: 15
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreQuartz
- entityMask:
- AsteroidRock: AsteroidRockQuartz
- WallRock: WallRockQuartz
- WallRockBasalt: WallRockBasaltQuartz
- WallRockChromite: WallRockChromiteQuartz
- WallRockSnow: WallRockSnowQuartz
- maxCount: 30
- minGroupSize: 10
- maxGroupSize: 15
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockQuartz
+ count: 30
+ minGroupSize: 10
+ maxGroupSize: 15
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreCoal
- entityMask:
- AsteroidRock: AsteroidRockCoal
- WallRock: WallRockCoal
- WallRockBasalt: WallRockBasaltCoal
- WallRockChromite: WallRockChromiteCoal
- WallRockSand: WallRockSandCoal
- WallRockSnow: WallRockSnowCoal
- maxCount: 30
- minGroupSize: 8
- maxGroupSize: 12
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockCoal
+ count: 30
+ minGroupSize: 8
+ maxGroupSize: 12
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreSalt
- entityMask:
- AsteroidRock: AsteroidRockSalt
- WallRock: WallRockSalt
- WallRockBasalt: WallRockBasaltSalt
- WallRockChromite: WallRockChromiteSalt
- WallRockSand: WallRockSandSalt
- WallRockSnow: WallRockSnowSalt
- maxCount: 30
- minGroupSize: 8
- maxGroupSize: 12
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockSalt
+ count: 30
+ minGroupSize: 8
+ maxGroupSize: 12
# Medium value
# Gold
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreGold
- entityMask:
- AsteroidRock: AsteroidRockGold
- WallRock: WallRockGold
- WallRockBasalt: WallRockBasaltGold
- WallRockChromite: WallRockChromiteGold
- WallRockSand: WallRockSandGold
- WallRockSnow: WallRockSnowGold
- maxCount: 20
- minGroupSize: 5
- maxGroupSize: 10
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockGold
+ count: 20
+ minGroupSize: 5
+ maxGroupSize: 10
# Silver
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreSilver
- entityMask:
- AsteroidRock: AsteroidRockSilver
- WallRock: WallRockSilver
- WallRockBasalt: WallRockBasaltSilver
- WallRockChromite: WallRockChromiteSilver
- WallRockSand: WallRockSandSilver
- WallRockSnow: WallRockSnowSilver
- maxCount: 20
- minGroupSize: 5
- maxGroupSize: 10
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockSilver
+ count: 20
+ minGroupSize: 5
+ maxGroupSize: 10
# High value
# Plasma
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OrePlasma
- entityMask:
- AsteroidRock: AsteroidRockPlasma
- WallRock: WallRockPlasma
- WallRockBasalt: WallRockBasaltPlasma
- WallRockChromite: WallRockChromitePlasma
- WallRockSand: WallRockSandPlasma
- WallRockSnow: WallRockSnowPlasma
- maxCount: 12
- minGroupSize: 4
- maxGroupSize: 8
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockPlasma
+ count: 12
+ minGroupSize: 4
+ maxGroupSize: 8
# Uranium
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreUranium
- entityMask:
- AsteroidRock: AsteroidRockUranium
- WallRock: WallRockUranium
- WallRockBasalt: WallRockBasaltUranium
- WallRockChromite: WallRockChromiteUranium
- WallRockSand: WallRockSandUranium
- WallRockSnow: WallRockSnowUranium
- maxCount: 15
- minGroupSize: 4
- maxGroupSize: 8
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockUranium
+ count: 15
+ minGroupSize: 4
+ maxGroupSize: 8
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreDiamond
- entityMask:
- AsteroidRock: AsteroidRockDiamond
- WallRock: WallRockDiamond
- WallRockBasalt: WallRockBasaltDiamond
- WallRockChromite: WallRockChromiteDiamond
- WallRockSand: WallRockSandDiamond
- WallRockSnow: WallRockSnowDiamond
- maxCount: 6
- minGroupSize: 1
- maxGroupSize: 2
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockDiamond
+ count: 6
+ minGroupSize: 1
+ maxGroupSize: 2
# Artifact Fragment
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreArtifactFragment
- entityMask:
- AsteroidRock: AsteroidRockArtifactFragment
- WallRock: WallRockArtifactFragment
- WallRockBasalt: WallRockBasaltArtifactFragment
- WallRockChromite: WallRockChromiteArtifactFragment
- WallRockSand: WallRockSandArtifactFragment
- WallRockSnow: WallRockSnowArtifactFragment
- maxCount: 6
- minGroupSize: 1
- maxGroupSize: 2
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockArtifactFragment
+ count: 6
+ minGroupSize: 1
+ maxGroupSize: 2
# Low value
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreIronLow
- entityMask:
- AsteroidRock: AsteroidRockTin
- WallRock: WallRockTin
- WallRockBasalt: WallRockBasaltTin
- WallRockChromite: WallRockChromiteTin
- WallRockSand: WallRockSandTin
- WallRockSnow: WallRockSnowTin
- maxCount: 20
- minGroupSize: 4
- maxGroupSize: 8
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockTin
+ count: 20
+ minGroupSize: 4
+ maxGroupSize: 8
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreQuartzLow
- entityMask:
- AsteroidRock: AsteroidRockQuartz
- WallRock: WallRockQuartz
- WallRockBasalt: WallRockBasaltQuartz
- WallRockChromite: WallRockChromiteQuartz
- WallRockSnow: WallRockSnowQuartz
- maxCount: 20
- minGroupSize: 4
- maxGroupSize: 8
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockQuartz
+ count: 20
+ minGroupSize: 4
+ maxGroupSize: 8
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreCoalLow
- entityMask:
- AsteroidRock: AsteroidRockCoal
- WallRock: WallRockCoal
- WallRockBasalt: WallRockBasaltCoal
- WallRockChromite: WallRockChromiteCoal
- WallRockSand: WallRockSandCoal
- WallRockSnow: WallRockSnowCoal
- maxCount: 20
- minGroupSize: 4
- maxGroupSize: 6
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockCoal
+ count: 20
+ minGroupSize: 4
+ maxGroupSize: 6
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreSaltLow
- entityMask:
- AsteroidRock: AsteroidRockSalt
- WallRock: WallRockSalt
- WallRockBasalt: WallRockBasaltSalt
- WallRockChromite: WallRockChromiteSalt
- WallRockSand: WallRockSandSalt
- WallRockSnow: WallRockSnowSalt
- maxCount: 15
- minGroupSize: 4
- maxGroupSize: 6
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockSalt
+ count: 15
+ minGroupSize: 4
+ maxGroupSize: 6
# Medium value
# Gold
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreGoldLow
- entityMask:
- AsteroidRock: AsteroidRockGold
- WallRock: WallRockGold
- WallRockBasalt: WallRockBasaltGold
- WallRockChromite: WallRockChromiteGold
- WallRockSand: WallRockSandGold
- WallRockSnow: WallRockSnowGold
- maxCount: 10
- minGroupSize: 2
- maxGroupSize: 5
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockGold
+ count: 12
+ minGroupSize: 2
+ maxGroupSize: 5
# Silver
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreSilverLow
- entityMask:
- AsteroidRock: AsteroidRockSilver
- WallRock: WallRockSilver
- WallRockBasalt: WallRockBasaltSilver
- WallRockChromite: WallRockChromiteSilver
- WallRockSand: WallRockSandSilver
- WallRockSnow: WallRockSnowSilver
- maxCount: 10
- minGroupSize: 2
- maxGroupSize: 5
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockSilver
+ count: 12
+ minGroupSize: 2
+ maxGroupSize: 5
# High value
# Plasma
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OrePlasmaLow
- entityMask:
- AsteroidRock: AsteroidRockPlasma
- WallRock: WallRockPlasma
- WallRockBasalt: WallRockBasaltPlasma
- WallRockChromite: WallRockChromitePlasma
- WallRockSand: WallRockSandPlasma
- WallRockSnow: WallRockSnowPlasma
- maxCount: 6
- minGroupSize: 2
- maxGroupSize: 4
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockPlasma
+ count: 6
+ minGroupSize: 2
+ maxGroupSize: 4
# Uranium
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreUraniumLow
- entityMask:
- AsteroidRock: AsteroidRockUranium
- WallRock: WallRockUranium
- WallRockBasalt: WallRockBasaltUranium
- WallRockChromite: WallRockChromiteUranium
- WallRockSand: WallRockSandUranium
- WallRockSnow: WallRockSnowUranium
- maxCount: 7
- minGroupSize: 2
- maxGroupSize: 4
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockUranium
+ count: 7
+ minGroupSize: 2
+ maxGroupSize: 4
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreDiamondLow
- entityMask:
- AsteroidRock: AsteroidRockDiamond
- WallRock: WallRockDiamond
- WallRockBasalt: WallRockBasaltDiamond
- WallRockChromite: WallRockChromiteDiamond
- WallRockSand: WallRockSandDiamond
- WallRockSnow: WallRockSnowDiamond
- maxCount: 3
- minGroupSize: 1
- maxGroupSize: 2
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockDiamond
+ count: 3
+ minGroupSize: 1
+ maxGroupSize: 2
# Artifact Fragment
-- type: biomeMarkerLayer
+- type: dungeonConfig
id: OreArtifactFragmentLow
- entityMask:
- AsteroidRock: AsteroidRockArtifactFragment
- WallRock: WallRockArtifactFragment
- WallRockBasalt: WallRockBasaltArtifactFragment
- WallRockChromite: WallRockChromiteArtifactFragment
- WallRockSand: WallRockSandArtifactFragment
- WallRockSnow: WallRockSnowArtifactFragment
- maxCount: 3
- minGroupSize: 1
- maxGroupSize: 2
- radius: 4
+ layers:
+ - !type:ChunkDunGen
+ size: 128
+ - !type:OreDunGen
+ replacement: WallRock
+ entity: WallRockArtifactFragment
+ count: 3
+ minGroupSize: 1
+ maxGroupSize: 2
-# Contains several biomes
-- type: biomeTemplate
- id: Continental
+# Desert
+- type: entity
+ id: BiomeLowDesert
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: Biome
+ layers:
+ terrain:
+ dungeon: LowDesertTerrain
+
+- type: dungeonConfig
+ id: LowDesertTerrain
layers:
- - !type:BiomeMetaLayer
- template: Lava
- - !type:BiomeMetaLayer
- template: Caves
- threshold: -0.5
- noise:
- frequency: 0.001
- noiseType: OpenSimplex2
- fractalType: FBm
- octaves: 2
- lacunarity: 2
- - !type:BiomeMetaLayer
- template: Grasslands
- threshold: 0
- noise:
- frequency: 0.001
- noiseType: OpenSimplex2
- fractalType: FBm
- octaves: 2
- lacunarity: 2
- - !type:BiomeMetaLayer
- template: Snow
- threshold: 0.5
- noise:
- frequency: 0.001
- noiseType: OpenSimplex2
- fractalType: FBm
- octaves: 2
- lacunarity: 2
+ - !type:ChunkDunGen
+ - !type:PrototypeDunGen
+ proto: LowDesertTiles
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: LowDesertEntities
+ inheritDungeons: All
-# Desert
-# TODO: Water in desert
-- type: biomeTemplate
- id: LowDesert
+- type: dungeonConfig
+ id: LowDesertTiles
+ returnReserved: false
layers:
- - !type:BiomeEntityLayer
- threshold: 0.95
- noise:
- seed: 0
- frequency: 2
- noiseType: OpenSimplex2
- allowedTiles:
- - FloorAsteroidSand
- entities:
- - FloraRockSolid
- # Large rock areas
- - !type:BiomeEntityLayer
- threshold: -0.20
- noise:
- seed: 0
- frequency: 0.04
- noiseType: Cellular
- fractalType: FBm
- octaves: 5
- lacunarity: 2
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- allowedTiles:
- - FloorAsteroidSand
- entities:
- - WallRockSand
- - !type:BiomeDummyLayer
- id: Loot
- # Fill layer
- - !type:BiomeTileLayer
- threshold: -1
- tile: FloorAsteroidSand
+ - !type:SampleTileDunGen
+ threshold: -1
+ tile: FloorAsteroidSand
-# Grass
-- type: biomeTemplate
- id: Grasslands
+- type: dungeonConfig
+ id: LowDesertEntities
+ reserveTiles: true
layers:
- # Sparse vegetation
- - !type:BiomeDecalLayer
- allowedTiles:
- - FloorPlanetGrass
- divisions: 2
- threshold: -0.50
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 1
- decals:
- - BushDOne
- - BushDTwo
- - BushDThree
- - !type:BiomeDecalLayer
- allowedTiles:
- - FloorPlanetGrass
- noise:
- seed: 0
- noiseType: OpenSimplex2
- frequency: 1
- divisions: 1
- threshold: 0.8
- decals:
- - FlowersBROne
- - FlowersBRTwo
- - FlowersBRThree
- # Dense vegetation
- - !type:BiomeDecalLayer
- allowedTiles:
- - FloorPlanetGrass
- divisions: 1
- threshold: -0.35
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 0.2
- fractalType: FBm
- octaves: 5
- lacunarity: 2
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- decals:
- - BushAOne
- - BushATwo
- - BushAThree
- - BushCOne
- - BushCTwo
- - BushCThree
- - !type:BiomeEntityLayer
- threshold: 0.5
- noise:
- seed: 0
- noiseType: OpenSimplex2
- fractalType: FBm
- frequency: 2
- allowedTiles:
- - FloorPlanetGrass
- entities:
- - FloraTree
- - FloraTreeLarge
- # Rock formations
- - !type:BiomeEntityLayer
- allowedTiles:
- - FloorPlanetGrass
- - FloorPlanetDirt
- threshold: -0.30
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 0.05
- lacunarity: 2
- fractalType: FBm
- octaves: 5
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- entities:
- - WallRock
- - !type:BiomeDummyLayer
- id: Loot
- # Water
- - !type:BiomeEntityLayer
- allowedTiles:
- - FloorPlanetGrass
- - FloorPlanetDirt
- threshold: 0.95
- noise:
- seed: 3
- noiseType: OpenSimplex2
- frequency: 0.003
- lacunarity: 1.50
- fractalType: Ridged
- octaves: 1
- entities:
- - FloorWaterEntity
- # Fill remainder with dirt.
- - !type:BiomeTileLayer
- threshold: -1.0
- tile: FloorPlanetDirt
- - !type:BiomeTileLayer
- threshold: -0.90
- tile: FloorPlanetGrass
- noise:
- seed: 0
- frequency: 0.02
- fractalType: None
- # Water sand
- - !type:BiomeTileLayer
- tile: FloorPlanetDirt
- threshold: 0.95
- noise:
- seed: 3
- noiseType: OpenSimplex2
- frequency: 0.003
- lacunarity: 1.50
- fractalType: Ridged
- octaves: 1
- # Rock formation sand
- - !type:BiomeTileLayer
- tile: FloorPlanetDirt
- threshold: -0.30
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 0.05
- lacunarity: 2
- fractalType: FBm
- octaves: 5
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
+ - !type:SampleEntityDunGen
+ threshold: 0.95
+ noise:
+ seed: 0
+ frequency: 2
+ noiseType: OpenSimplex2
+ allowedTiles:
+ - FloorAsteroidSand
+ entities:
+ - FloraRockSolid
+ # Large rock areas
+ - !type:SampleEntityDunGen
+ threshold: -0.20
+ noise:
+ seed: 0
+ frequency: 0.04
+ noiseType: Cellular
+ fractalType: FBm
+ octaves: 5
+ lacunarity: 2
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ allowedTiles:
+ - FloorAsteroidSand
+ entities:
+ - WallRockSand
+
+
+- type: entity
+ id: BiomeGrasslands
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: Biome
+ layers:
+ terrain:
+ dungeon: GrasslandsTerrain
+
+- type: dungeonConfig
+ id: GrasslandsTerrain
+ layers:
+ - !type:ChunkDunGen
+ - !type:PrototypeDunGen
+ proto: GrasslandsTiles
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: GrasslandsEntities
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: GrasslandsDecals
+ inheritDungeons: All
+
+- type: dungeonConfig
+ id: GrasslandsTiles
+ returnReserved: false
+ layers:
+ # Water sand
+ - !type:SampleTileDunGen
+ tile: FloorPlanetDirt
+ threshold: 0.95
+ noise:
+ seed: 3
+ noiseType: OpenSimplex2
+ frequency: 0.003
+ lacunarity: 1.50
+ fractalType: Ridged
+ octaves: 1
+ # Rock formation sand
+ - !type:SampleTileDunGen
+ tile: FloorPlanetDirt
+ threshold: -0.30
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 0.05
+ lacunarity: 2
+ fractalType: FBm
+ octaves: 5
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ - !type:SampleTileDunGen
+ threshold: -0.80
+ tile: FloorPlanetGrass
+ noise:
+ seed: 0
+ noiseType: OpenSimplex2
+ lacunarity: 1.50
+ frequency: 0.02
+ fractalType: None
+ octaves: 1
+ # Fill remainder with dirt.
+ - !type:SampleTileDunGen
+ threshold: -1.0
+ tile: FloorPlanetDirt
+
+- type: dungeonConfig
+ id: GrasslandsEntities
+ reserveTiles: true
+ layers:
+ # Water
+ - !type:SampleEntityDunGen
+ allowedTiles:
+ - FloorPlanetGrass
+ - FloorPlanetDirt
+ threshold: 0.95
+ noise:
+ seed: 3
+ noiseType: OpenSimplex2
+ frequency: 0.003
+ lacunarity: 1.50
+ fractalType: Ridged
+ octaves: 1
+ entities:
+ - FloorWaterEntity
+ # Rock formations
+ - !type:SampleEntityDunGen
+ allowedTiles:
+ - FloorPlanetGrass
+ - FloorPlanetDirt
+ threshold: -0.30
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 0.05
+ lacunarity: 2
+ fractalType: FBm
+ octaves: 5
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ entities:
+ - WallRock
+ - !type:SampleEntityDunGen
+ threshold: 0.5
+ noise:
+ seed: 0
+ noiseType: OpenSimplex2
+ fractalType: FBm
+ frequency: 2
+ allowedTiles:
+ - FloorPlanetGrass
+ entities:
+ - FloraTree
+ - FloraTreeLarge
+
+- type: dungeonConfig
+ id: GrasslandsDecals
+ returnReserved: false
+ layers:
+ # Dense vegetation
+ - !type:SampleDecalDunGen
+ allowedTiles:
+ - FloorPlanetGrass
+ divisions: 1
+ threshold: -0.35
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 0.2
+ fractalType: FBm
+ octaves: 5
+ lacunarity: 2
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ decals:
+ - BushAOne
+ - BushATwo
+ - BushAThree
+ - BushCOne
+ - BushCTwo
+ - BushCThree
+ - !type:SampleDecalDunGen
+ allowedTiles:
+ - FloorPlanetGrass
+ noise:
+ seed: 0
+ noiseType: OpenSimplex2
+ frequency: 1
+ divisions: 1
+ threshold: 0.8
+ decals:
+ - FlowersBROne
+ - FlowersBRTwo
+ - FlowersBRThree
+ # Sparse vegetation
+ - !type:SampleDecalDunGen
+ allowedTiles:
+ - FloorPlanetGrass
+ divisions: 2
+ threshold: -0.50
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 1
+ decals:
+ - BushDOne
+ - BushDTwo
+ - BushDThree
# Lava
-- type: biomeTemplate
- id: Lava
+- type: entity
+ id: BiomeLava
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: Biome
+ layers:
+ terrain:
+ dungeon: LavaTerrain
+
+- type: dungeonConfig
+ id: LavaTerrain
layers:
- - !type:BiomeEntityLayer
- threshold: 0.9
- noise:
- frequency: 1
- seed: 2
- allowedTiles:
- - FloorBasalt
- entities:
- - BasaltOne
- - BasaltTwo
- - BasaltThree
- - BasaltFour
- - BasaltFive
- - !type:BiomeDecalLayer
- allowedTiles:
- - FloorBasalt
- threshold: 0.9
- divisions: 1
- noise:
- seed: 1
- frequency: 1
- decals:
- - Basalt1
- - Basalt2
- - Basalt3
- - Basalt4
- - Basalt5
- - Basalt6
- - Basalt7
- - Basalt8
- - Basalt9
- - !type:BiomeEntityLayer
- threshold: 0.95
- noise:
- seed: 0
- noiseType: OpenSimplex2
- frequency: 1
- allowedTiles:
- - FloorBasalt
- entities:
- - FloraRockSolid
- - !type:BiomeEntityLayer
- threshold: 0.2
- noise:
- seed: 0
- frequency: 0.02
- fractalType: FBm
- octaves: 5
- lacunarity: 2
- gain: 0.4
- allowedTiles:
- - FloorBasalt
- entities:
- - FloorLavaEntity
- # Rock formations
- - !type:BiomeEntityLayer
- allowedTiles:
- - FloorBasalt
- threshold: -0.30
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 0.05
- lacunarity: 2
- fractalType: FBm
- octaves: 5
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- entities:
- - WallRockBasalt
- - !type:BiomeDummyLayer
- id: Loot
- # Fill basalt
- - !type:BiomeTileLayer
- threshold: -1
- tile: FloorBasalt
+ - !type:ChunkDunGen
+ - !type:PrototypeDunGen
+ proto: LavaTiles
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: LavaEntities
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: LavaDecals
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: LavaBasalt
+ inheritDungeons: All
+
+- type: dungeonConfig
+ id: LavaTiles
+ returnReserved: false
+ layers:
+ # Fill basalt
+ - !type:SampleTileDunGen
+ threshold: -1
+ tile: FloorBasalt
+
+- type: dungeonConfig
+ id: LavaEntities
+ reserveTiles: true
+ layers:
+ - !type:SampleEntityDunGen
+ threshold: 0.2
+ noise:
+ seed: 0
+ frequency: 0.02
+ fractalType: FBm
+ octaves: 5
+ lacunarity: 2
+ gain: 0.4
+ allowedTiles:
+ - FloorBasalt
+ entities:
+ - FloorLavaEntity
+ # Rock formations
+ - !type:SampleEntityDunGen
+ allowedTiles:
+ - FloorBasalt
+ threshold: -0.30
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 0.05
+ lacunarity: 2
+ fractalType: FBm
+ octaves: 5
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ entities:
+ - WallRockBasalt
+ - !type:SampleEntityDunGen
+ threshold: 0.95
+ noise:
+ seed: 0
+ noiseType: OpenSimplex2
+ frequency: 1
+ allowedTiles:
+ - FloorBasalt
+ entities:
+ - FloraRockSolid
+
+- type: dungeonConfig
+ id: LavaDecals
+ returnReserved: false
+ layers:
+ - !type:SampleDecalDunGen
+ allowedTiles:
+ - FloorBasalt
+ threshold: 0.9
+ divisions: 1
+ noise:
+ seed: 1
+ frequency: 1
+ decals:
+ - Basalt1
+ - Basalt2
+ - Basalt3
+ - Basalt4
+ - Basalt5
+ - Basalt6
+ - Basalt7
+ - Basalt8
+ - Basalt9
+
+- type: dungeonConfig
+ id: LavaBasalt
+ returnReserved: false
+ layers:
+ - !type:SampleEntityDunGen
+ threshold: 0.9
+ noise:
+ frequency: 1
+ seed: 2
+ allowedTiles:
+ - FloorBasalt
+ entities:
+ - BasaltOne
+ - BasaltTwo
+ - BasaltThree
+ - BasaltFour
+ - BasaltFive
# Snow
-- type: biomeTemplate
- id: Snow # Similar to Grasslands... but snow
+- type: entity
+ id: BiomeSnow
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: Biome
+ layers:
+ terrain:
+ dungeon: SnowTerrain
+
+- type: dungeonConfig
+ id: SnowTerrain
layers:
- # Sparse vegetation
- - !type:BiomeDecalLayer
- allowedTiles:
- - FloorSnow
- divisions: 2
- threshold: -0.50
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 1
- decals:
- - grasssnowa1
- - grasssnowa2
- - grasssnowa3
- - grasssnowb1
- - grasssnowb2
- - grasssnowb3
- - grasssnowc1
- - grasssnowc2
- - grasssnowc3
- # Dense, bland grass
- - !type:BiomeDecalLayer
- allowedTiles:
- - FloorSnow
- divisions: 1
- threshold: -0.35
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 0.2
- fractalType: FBm
- octaves: 5
- lacunarity: 2
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- decals:
- - grasssnow
- - grasssnow01
- - grasssnow02
- - grasssnow03
- - grasssnow04
- - grasssnow05
- - grasssnow06
- - grasssnow07
- - grasssnow08
- - grasssnow09
- - grasssnow10
- - grasssnow11
- - grasssnow12
- - grasssnow13
- # Little bit of coloured grass
- - !type:BiomeDecalLayer
- allowedTiles:
- - FloorSnow
- divisions: 1
- threshold: -0.0
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 1
- fractalType: None
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- decals:
- - bushsnowa1
- - bushsnowa2
- - bushsnowa3
- - bushsnowb3
- - bushsnowb2
- - bushsnowb3
- - !type:BiomeEntityLayer
- threshold: 0.5
- noise:
- seed: 0
- noiseType: OpenSimplex2
- fractalType: FBm
- frequency: 2
- allowedTiles:
- - FloorSnow
- entities:
- - FloraTreeSnow
- # Rock formations
- - !type:BiomeEntityLayer
- allowedTiles:
- - FloorSnow
- threshold: -0.30
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 0.05
- lacunarity: 2
- fractalType: FBm
- octaves: 5
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- entities:
- - WallRockSnow
- # Ice tiles
- - !type:BiomeTileLayer
- tile: FloorIce
- threshold: -0.9
- noise:
- seed: 0
- noiseType: Cellular
- frequency: 0.03
- lacunarity: 2
- fractalType: FBm
- octaves: 5
- cellularDistanceFunction: Euclidean
- cellularReturnType: Distance2
- # Liquid plasma rivers. Ice moon baby
- - !type:BiomeEntityLayer
- allowedTiles:
- - FloorSnow
- - FloorIce
- threshold: 0.95
- noise:
- seed: 3
- noiseType: OpenSimplex2
- frequency: 0.003
- lacunarity: 1.50
- fractalType: Ridged
- octaves: 1
- entities:
- - FloorLiquidPlasmaEntity
- - !type:BiomeDummyLayer
- id: Loot
- - !type:BiomeTileLayer
- threshold: -0.7
- tile: FloorSnow
- noise:
- seed: 0
- frequency: 0.02
- fractalType: None
+ - !type:ChunkDunGen
+ - !type:PrototypeDunGen
+ proto: SnowTiles
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: SnowEntities
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: SnowDecals
+ inheritDungeons: All
+
+- type: dungeonConfig
+ id: SnowTiles
+ returnReserved: false
+ layers:
+ - !type:SampleTileDunGen
+ threshold: -0.7
+ tile: FloorSnow
+ noise:
+ seed: 0
+ frequency: 0.02
+ fractalType: None
+ - !type:SampleTileDunGen
+ tile: FloorIce
+ threshold: -0.9
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 0.03
+ lacunarity: 2
+ fractalType: FBm
+ octaves: 5
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+
+
+- type: dungeonConfig
+ id: SnowEntities
+ reserveTiles: true
+ layers:
+ # Liquid plasma rivers. Ice moon baby
+ - !type:SampleEntityDunGen
+ allowedTiles:
+ - FloorSnow
+ - FloorIce
+ threshold: 0.95
+ noise:
+ seed: 3
+ noiseType: OpenSimplex2
+ frequency: 0.003
+ lacunarity: 1.50
+ fractalType: Ridged
+ octaves: 1
+ entities:
+ - FloorLiquidPlasmaEntity
+ # Rock formations
+ - !type:SampleEntityDunGen
+ allowedTiles:
+ - FloorSnow
+ threshold: -0.30
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 0.05
+ lacunarity: 2
+ fractalType: FBm
+ octaves: 5
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ entities:
+ - WallRockSnow
+ - !type:SampleEntityDunGen
+ threshold: 0.5
+ noise:
+ seed: 0
+ noiseType: OpenSimplex2
+ fractalType: FBm
+ frequency: 2
+ allowedTiles:
+ - FloorSnow
+ entities:
+ - FloraTreeSnow
+
+- type: dungeonConfig
+ id: SnowDecals
+ returnReserved: false
+ layers:
+ # Sparse vegetation
+ - !type:SampleDecalDunGen
+ allowedTiles:
+ - FloorSnow
+ divisions: 2
+ threshold: -0.50
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 1
+ decals:
+ - grasssnowa1
+ - grasssnowa2
+ - grasssnowa3
+ - grasssnowb1
+ - grasssnowb2
+ - grasssnowb3
+ - grasssnowc1
+ - grasssnowc2
+ - grasssnowc3
+ # Dense, bland grass
+ - !type:SampleDecalDunGen
+ allowedTiles:
+ - FloorSnow
+ divisions: 1
+ threshold: -0.35
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 0.2
+ fractalType: FBm
+ octaves: 5
+ lacunarity: 2
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ decals:
+ - grasssnow
+ - grasssnow01
+ - grasssnow02
+ - grasssnow03
+ - grasssnow04
+ - grasssnow05
+ - grasssnow06
+ - grasssnow07
+ - grasssnow08
+ - grasssnow09
+ - grasssnow10
+ - grasssnow11
+ - grasssnow12
+ - grasssnow13
+ # Little bit of coloured grass
+ - !type:SampleDecalDunGen
+ allowedTiles:
+ - FloorSnow
+ divisions: 1
+ threshold: -0.0
+ noise:
+ seed: 0
+ noiseType: Cellular
+ frequency: 1
+ fractalType: None
+ cellularDistanceFunction: Euclidean
+ cellularReturnType: Distance2
+ decals:
+ - bushsnowa1
+ - bushsnowa2
+ - bushsnowa3
+ - bushsnowb3
+ - bushsnowb2
+ - bushsnowb3
# Shadow -> Derived from lava
-- type: biomeTemplate
- id: Shadow
+- type: entity
+ id: BiomeShadow
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: Biome
+ layers:
+ terrain:
+ dungeon: ShadowTerrain
+
+- type: dungeonConfig
+ id: ShadowTerrain
layers:
- - !type:BiomeEntityLayer
+ - !type:ChunkDunGen
+ - !type:PrototypeDunGen
+ proto: ShadowTiles
+ inheritDungeons: All
+ - !type:PrototypeDunGen
+ proto: ShadowEntities
+ inheritDungeons: All
+
+- type: dungeonConfig
+ id: ShadowEntities
+ reserveTiles: true
+ layers:
+ - !type:SampleEntityDunGen
threshold: 0.70
noise:
frequency: 1
- ShadowBasaltThree
- ShadowBasaltFour
- ShadowBasaltFive
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.97
noise:
frequency: 1
- FloorChromite
entities:
- CrystalPink
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.97
noise:
seed: 1
entities:
- ShadowTree
# Rock formations
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: -0.2
invert: true
noise:
entities:
- WallRockChromite
# chasm time
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
allowedTiles:
- FloorChromite
threshold: 0.2
gain: 0.4
entities:
- FloorChromiteChasm
- - !type:BiomeDummyLayer
- id: Loot
- # Fill chromite
- - !type:BiomeTileLayer
- threshold: -1
- tile: FloorChromite
+
+- type: dungeonConfig
+ id: ShadowTiles
+ returnReserved: false
+ layers:
+ # Fill chromite
+ - !type:SampleTileDunGen
+ threshold: -1
+ tile: FloorChromite
# Caves
-- type: biomeTemplate
- id: Caves
+- type: entity
+ id: BiomeCaves
+ categories: [ HideSpawnMenu ]
+ components:
+ - type: Biome
+ layers:
+ terrain:
+ dungeon: CavesTerrain
+
+- type: dungeonConfig
+ id: CavesTerrain
+ layers:
+ - !type:ChunkDunGen
+ - !type:PrototypeDunGen
+ proto: CavesTiles
+ inheritDungeons: All
+ - !type:RoofDunGen
+ - !type:PrototypeDunGen
+ proto: CavesEntities
+ inheritDungeons: All
+
+- type: dungeonConfig
+ id: CavesEntities
+ reserveTiles: true
layers:
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.85
noise:
seed: 2
- CrystalBlue
- CrystalYellow
- CrystalCyan
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.95
noise:
seed: 1
- FloorAsteroidSand
entities:
- FloraStalagmite
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: -0.5
invert: true
noise:
- FloorAsteroidSand
entities:
- WallRock
- - !type:BiomeDummyLayer
- id: Loot
- - !type:BiomeTileLayer
- threshold: -1.0
- tile: FloorAsteroidSand
+
+- type: dungeonConfig
+ id: CavesTiles
+ returnReserved: false
+ layers:
+ - !type:SampleTileDunGen
+ threshold: -1.0
+ tile: FloorAsteroidSand
# Asteroid
-- type: biomeTemplate
+- type: dungeonConfig
id: Asteroid
layers:
- - !type:BiomeEntityLayer
+ - !type:SampleTileDunGen
+ threshold: -1.0
+ tile: FloorAsteroidSand
+ reserveTiles: false
+ - !type:SampleEntityDunGen
threshold: 0.85
noise:
seed: 2
- CrystalBlue
- CrystalYellow
- CrystalCyan
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
threshold: 0.95
noise:
seed: 1
- FloorAsteroidSand
entities:
- FloraStalagmite
- - !type:BiomeEntityLayer
+ - !type:SampleEntityDunGen
+ reserveTiles: false
threshold: -0.6
invert: true
noise:
- FloorAsteroidSand
entities:
- AsteroidRock
- - !type:BiomeTileLayer
- threshold: -1.0
- tile: FloorAsteroidSand
id: Haunted
layers:
- !type:PrefabDunGen
+ roomWhitelist:
+ tags:
+ - Haunted
presets:
- Bucket
- Wow
- type: salvageLoot
id: SalvageLoot
loots:
- - !type:RandomSpawnsLoot
- entries:
- - proto: AdvMopItem
- prob: 0.5
- - proto: AmmoTechFabCircuitboard
- cost: 2
- - proto: AutolatheMachineCircuitboard
- cost: 2
- - proto: BiomassReclaimerMachineCircuitboard
- cost: 2
- - proto: BluespaceBeaker
- cost: 2
- - proto: CyborgEndoskeleton
- cost: 3
- prob: 0.5
- - proto: ChemDispenserMachineCircuitboard
- cost: 2
- - proto: CircuitImprinter
- cost: 2
- - proto: CloningConsoleComputerCircuitboard
- cost: 2
- - proto: CloningPodMachineCircuitboard
- cost: 2
- - proto: ChemistryBottleCognizine
- - proto: FoodBoxDonkpocketCarp
- prob: 0.5
- - proto: CrateSalvageEquipment
- cost: 3
- prob: 0.5
- - proto: GasRecycler
- cost: 2
- - proto: GeneratorRTG
- cost: 5
- - proto: GravityGeneratorMini
- cost: 2
- - proto: GyroscopeUnanchored
- cost: 2
- prob: 0.1
- - proto: MedicalScannerMachineCircuitboard
- cost: 2
- - proto: NuclearBombKeg
- cost: 5
- - proto: ChemistryBottleOmnizine
- prob: 0.5
- - proto: PortableGeneratorPacman
- cost: 2
- - proto: PortableGeneratorSuperPacman
- cost: 3
- - proto: PowerCellAntiqueProto
- cost: 5
- prob: 0.5
- - proto: ProtolatheMachineCircuitboard
- - proto: RandomArtifactSpawner
- cost: 2
- - proto: RandomCargoCorpseSpawner
- cost: 2
- prob: 0.5
- - proto: RandomCommandCorpseSpawner
- cost: 5
- prob: 0.5
- - proto: RandomEngineerCorpseSpawner
- cost: 2
- prob: 0.5
- - proto: RandomMedicCorpseSpawner
- cost: 2
- prob: 0.5
- - proto: RandomScienceCorpseSpawner
- cost: 2
- prob: 0.5
- - proto: RandomSecurityCorpseSpawner
- cost: 2
- prob: 0.5
- - proto: RandomServiceCorpseSpawner
- cost: 2
- prob: 0.5
- - proto: ResearchAndDevelopmentServerMachineCircuitboard
- cost: 5
- prob: 0.5
- - proto: ResearchDisk10000
- prob: 0.5
- - proto: ResearchDisk5000
- prob: 0.5
- - proto: RipleyHarness
- cost: 3
- prob: 0.5
- - proto: SpaceCash1000
- - proto: SpaceCash10000
- cost: 10
- - proto: SpaceCash2500
- cost: 3
- - proto: SpaceCash5000
- cost: 5
- - proto: TechnologyDiskRare
- cost: 5
- prob: 0.5
- - proto: ThrusterUnanchored
- - proto: WaterTankHighCapacity
- - proto: WeldingFuelTankHighCapacity
- cost: 3
- - proto: WeaponTeslaGun
- prob: 0.1
- cost: 2
+ - !type:RandomSpawnsLoot
+ entries:
+ - proto: AdvMopItem
+ prob: 0.5
+ - proto: AmmoTechFabCircuitboard
+ cost: 2
+ - proto: AutolatheMachineCircuitboard
+ cost: 2
+ - proto: BiomassReclaimerMachineCircuitboard
+ cost: 2
+ - proto: BluespaceBeaker
+ cost: 2
+ - proto: CyborgEndoskeleton
+ cost: 3
+ prob: 0.5
+ - proto: ChemDispenserMachineCircuitboard
+ cost: 2
+ - proto: CircuitImprinter
+ cost: 2
+ - proto: CloningConsoleComputerCircuitboard
+ cost: 2
+ - proto: CloningPodMachineCircuitboard
+ cost: 2
+ - proto: ChemistryBottleCognizine
+ - proto: FoodBoxDonkpocketCarp
+ prob: 0.5
+ - proto: CrateSalvageEquipment
+ cost: 3
+ prob: 0.5
+ - proto: GasRecycler
+ cost: 2
+ - proto: GeneratorRTG
+ cost: 5
+ - proto: GravityGeneratorMini
+ cost: 2
+ - proto: GyroscopeUnanchored
+ cost: 2
+ prob: 0.1
+ - proto: MedicalScannerMachineCircuitboard
+ cost: 2
+ - proto: NuclearBombKeg
+ cost: 5
+ - proto: ChemistryBottleOmnizine
+ prob: 0.5
+ - proto: PortableGeneratorPacman
+ cost: 2
+ - proto: PortableGeneratorSuperPacman
+ cost: 3
+ - proto: PowerCellAntiqueProto
+ cost: 5
+ prob: 0.5
+ - proto: ProtolatheMachineCircuitboard
+ - proto: RandomArtifactSpawner
+ cost: 2
+ - proto: RandomCargoCorpseSpawner
+ cost: 2
+ prob: 0.5
+ - proto: RandomCommandCorpseSpawner
+ cost: 5
+ prob: 0.5
+ - proto: RandomEngineerCorpseSpawner
+ cost: 2
+ prob: 0.5
+ - proto: RandomMedicCorpseSpawner
+ cost: 2
+ prob: 0.5
+ - proto: RandomScienceCorpseSpawner
+ cost: 2
+ prob: 0.5
+ - proto: RandomSecurityCorpseSpawner
+ cost: 2
+ prob: 0.5
+ - proto: RandomServiceCorpseSpawner
+ cost: 2
+ prob: 0.5
+ - proto: ResearchAndDevelopmentServerMachineCircuitboard
+ cost: 5
+ prob: 0.5
+ - proto: ResearchDisk10000
+ prob: 0.5
+ - proto: ResearchDisk5000
+ prob: 0.5
+ - proto: RipleyHarness
+ cost: 3
+ prob: 0.5
+ - proto: SpaceCash1000
+ - proto: SpaceCash10000
+ cost: 10
+ - proto: SpaceCash2500
+ cost: 3
+ - proto: SpaceCash5000
+ cost: 5
+ - proto: TechnologyDiskRare
+ cost: 5
+ prob: 0.5
+ - proto: ThrusterUnanchored
+ - proto: WaterTankHighCapacity
+ - proto: WeldingFuelTankHighCapacity
+ cost: 3
+ - proto: WeaponTeslaGun
+ prob: 0.1
+ cost: 2
# Mob loot table
id: OreIron
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreIron
+ - !type:BiomeLoot
+ proto: OreIron
- type: salvageLoot
id: OreCoal
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreCoal
+ - !type:BiomeLoot
+ proto: OreCoal
- type: salvageLoot
id: OreQuartz
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreQuartz
+ - !type:BiomeLoot
+ proto: OreQuartz
- type: salvageLoot
id: OreSalt
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreSalt
+ - !type:BiomeLoot
+ proto: OreSalt
# - Medium value
- type: salvageLoot
id: OreGold
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreGold
+ - !type:BiomeLoot
+ proto: OreGold
- type: salvageLoot
id: OreSilver
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreSilver
+ - !type:BiomeLoot
+ proto: OreSilver
# - High value
- type: salvageLoot
id: OrePlasma
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OrePlasma
+ - !type:BiomeLoot
+ proto: OrePlasma
- type: salvageLoot
id: OreUranium
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreUranium
+ - !type:BiomeLoot
+ proto: OreUranium
- type: salvageLoot
id: OreDiamond
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreDiamond
+ - !type:BiomeLoot
+ proto: OreDiamond
- type: salvageLoot
id: OreArtifactFragment
guaranteed: true
loots:
- - !type:BiomeMarkerLoot
- proto: OreArtifactFragment
+ - !type:BiomeLoot
+ proto: OreArtifactFragment
- type: salvageBiomeMod
id: Caves
desc: salvage-biome-mod-caves
- biome: Caves
+ biome: BiomeCaves
- type: salvageBiomeMod
id: Grasslands
desc: salvage-biome-mod-grasslands
- biome: Grasslands
+ biome: BiomeGrasslands
- type: salvageBiomeMod
id: Snow
desc: salvage-biome-mod-snow
cost: 1
- biome: Snow
+ biome: BiomeSnow
- type: salvageBiomeMod
id: Lava
desc: salvage-biome-mod-lava
cost: 2
- biome: Lava
+ biome: BiomeLava
#- type: salvageBiomeMod
# id: Space
- type: dungeonConfig
id: VGRoidSmaller
- minOffset: 40
- maxOffset: 60
+ minOffset: 60
+ maxOffset: 80
layers:
- !type:NoiseDistanceDunGen
size: 150, 150
maxCount: 3
layers:
- !type:ExteriorDunGen
+ penetration: 5,15
proto: Experiment
- !type:EntityTableDunGen
minCount: 25
layers:
- !type:FillGridDunGen
entity: IronRock
+ threshold: -0.95
+ size: 350, 350
+ distanceConfig: !type:DunGenEuclideanSquaredDistance
+ blendWeight: -0.50
+ reservedNoise:
+ frequency: 0.080
+ noiseType: OpenSimplex2
+ fractalType: FBm
+ octaves: 5
+ lacunarity: 1.5
+ gain: 0.5
allowedTiles:
- FloorAsteroidSand