diff --git a/Content.Client/Parallax/BiomeDebugOverlay.cs b/Content.Client/Parallax/BiomeDebugOverlay.cs new file mode 100644 index 0000000000..c914cb5de7 --- /dev/null +++ b/Content.Client/Parallax/BiomeDebugOverlay.cs @@ -0,0 +1,87 @@ +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(); + _maps = _entManager.System(); + + _font = new VectorFont(_cache.GetResource("/EngineFonts/NotoSans/NotoSans-Regular.ttf"), 12); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + var mapUid = _maps.GetMapOrInvalid(args.MapId); + + return _entManager.HasComponent(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()); + } +} diff --git a/Content.Client/Parallax/BiomeSystem.cs b/Content.Client/Parallax/BiomeSystem.cs new file mode 100644 index 0000000000..dc326e1fa7 --- /dev/null +++ b/Content.Client/Parallax/BiomeSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared.Parallax.Biomes; + +namespace Content.Client.Parallax; + +public sealed class BiomeSystem : SharedBiomeSystem +{ + +} diff --git a/Content.Client/Parallax/Commands/ShowBiomeCommand.cs b/Content.Client/Parallax/Commands/ShowBiomeCommand.cs new file mode 100644 index 0000000000..2a628dd931 --- /dev/null +++ b/Content.Client/Parallax/Commands/ShowBiomeCommand.cs @@ -0,0 +1,22 @@ +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()) + { + _overlayMgr.RemoveOverlay(); + } + else + { + _overlayMgr.AddOverlay(new BiomeDebugOverlay()); + } + } +} diff --git a/Content.Client/Parallax/ParallaxOverlay.cs b/Content.Client/Parallax/ParallaxOverlay.cs index 8e3a1dddfe..06f830675d 100644 --- a/Content.Client/Parallax/ParallaxOverlay.cs +++ b/Content.Client/Parallax/ParallaxOverlay.cs @@ -1,6 +1,8 @@ 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; @@ -17,6 +19,7 @@ public sealed class ParallaxOverlay : Overlay [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; @@ -25,12 +28,13 @@ public sealed class ParallaxOverlay : Overlay { ZIndex = ParallaxSystem.ParallaxZIndex; IoCManager.InjectDependencies(this); + _mapSystem = _entManager.System(); _parallax = _entManager.System(); } protected override bool BeforeDraw(in OverlayDrawArgs args) { - if (args.MapId == MapId.Nullspace) + if (args.MapId == MapId.Nullspace || _entManager.HasComponent(_mapSystem.GetMapOrInvalid(args.MapId))) return false; return true; diff --git a/Content.Client/Salvage/UI/OfferingWindowOption.xaml.cs b/Content.Client/Salvage/UI/OfferingWindowOption.xaml.cs index 8d718f1be5..7855577e69 100644 --- a/Content.Client/Salvage/UI/OfferingWindowOption.xaml.cs +++ b/Content.Client/Salvage/UI/OfferingWindowOption.xaml.cs @@ -1,9 +1,23 @@ +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; diff --git a/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs b/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs index 03a7ec5c4d..d65760e270 100644 --- a/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs +++ b/Content.Server/Gateway/Components/GatewayGeneratorComponent.cs @@ -1,4 +1,4 @@ -using Content.Shared.Procedural; +using Content.Shared.Parallax.Biomes.Markers; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; @@ -42,7 +42,7 @@ public sealed partial class GatewayGeneratorComponent : Component /// Mob layers to pick from. /// [DataField] - public List> MobLayers = new() + public List> MobLayers = new() { "Carps", "Xenos", @@ -54,7 +54,7 @@ public sealed partial class GatewayGeneratorComponent : Component /// /// Loot layers to pick from. /// - public List> LootLayers = new() + public List> LootLayers = new() { "OreIron", "OreQuartz", diff --git a/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs b/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs index 5d08247b56..83471cdbc1 100644 --- a/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs +++ b/Content.Server/Gateway/Systems/GatewayGeneratorSystem.cs @@ -1,11 +1,12 @@ 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; @@ -110,7 +111,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem }; AddComp(mapUid, restricted); - _biome.EnsurePlanet(mapUid, _protoManager.Index("BiomeGrasslands"), seed); + _biome.EnsurePlanet(mapUid, _protoManager.Index("Continental"), seed); var grid = Comp(mapUid); @@ -198,7 +199,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem var layer = lootLayers[layerIdx]; lootLayers.RemoveSwap(layerIdx); - _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id); + _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id); } // - Mobs @@ -210,7 +211,7 @@ public sealed class GatewayGeneratorSystem : EntitySystem var layer = mobLayers[layerIdx]; mobLayers.RemoveSwap(layerIdx); - _biome.AddLayer((ent.Owner, biomeComp), $"{layer.Id}-{i}", layer.Id); + _biome.AddMarkerLayer(ent.Owner, biomeComp, layer.Id); } } } diff --git a/Content.Server/Maps/PlanetCommand.cs b/Content.Server/Maps/PlanetCommand.cs index d3b7abc171..8e8b5b10ed 100644 --- a/Content.Server/Maps/PlanetCommand.cs +++ b/Content.Server/Maps/PlanetCommand.cs @@ -1,11 +1,20 @@ using System.Linq; using Content.Server.Administration; -using Content.Server.Procedural; +using Content.Server.Atmos; +using Content.Server.Atmos.Components; +using Content.Server.Atmos.EntitySystems; +using Content.Server.Parallax; using Content.Shared.Administration; -using Content.Shared.Procedural.Components; +using Content.Shared.Atmos; +using Content.Shared.Gravity; +using Content.Shared.Movement.Components; +using Content.Shared.Parallax.Biomes; +using Robust.Shared.Audio; 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; @@ -43,7 +52,7 @@ public sealed class PlanetCommand : LocalizedEntityCommands return; } - if (!_protoManager.TryIndex(args[1], out var biomeTemplate)) + if (!_protoManager.TryIndex(args[1], out var biomeTemplate)) { shell.WriteError(Loc.GetString("cmd-planet-map-prototype", ("prototype", args[1]))); return; @@ -61,12 +70,9 @@ public sealed class PlanetCommand : LocalizedEntityCommands if (args.Length == 1) return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entManager), "Map Id"); - var biomeName = _entManager.ComponentFactory.GetComponentName(); - if (args.Length == 2) { - var options = _protoManager.EnumeratePrototypes() - .Where(o => o.Components.ContainsKey(biomeName)) + var options = _protoManager.EnumeratePrototypes() .Select(o => new CompletionOption(o.ID, "Biome")); return CompletionResult.FromOptions(options); } diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs index 07d6a624c8..7afd3d78df 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Simple.cs @@ -65,9 +65,6 @@ public sealed partial class PathfindingSystem { 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; @@ -124,7 +121,8 @@ public sealed partial class PathfindingSystem cameFrom[neighbor] = node; costSoFar[neighbor] = gScore; - var hScore = ManhattanDistance(args.End, neighbor); + // Still use octile even for manhattan distance. + var hScore = OctileDistance(args.End, neighbor) * 1.001f; var fScore = gScore + hScore; frontier.Enqueue(neighbor, fScore); } diff --git a/Content.Server/Parallax/BiomeSystem.Commands.cs b/Content.Server/Parallax/BiomeSystem.Commands.cs new file mode 100644 index 0000000000..a915e17fcb --- /dev/null +++ b/Content.Server/Parallax/BiomeSystem.Commands.cs @@ -0,0 +1,188 @@ +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(mapUid, out var biome)) + { + return; + } + + ClearTemplate(mapUid, biome); + } + + private CompletionResult BiomeClearCallbackHelper(IConsoleShell shell, string[] args) + { + if (args.Length == 1) + { + return CompletionResult.FromHintOptions(CompletionHelper.Components(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(mapUid, out var biome)) + { + return; + } + + if (!ProtoManager.TryIndex(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(proto: ProtoManager), "Biome template"); + } + + if (args.Length == 3) + { + if (int.TryParse(args[0], out var mapInt)) + { + var mapId = new MapId(mapInt); + + if (TryComp(_mapSystem.GetMapOrInvalid(mapId), out var biome)) + { + var results = new List(); + + 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(_mapSystem.GetMapOrInvalid(mapId), out var biome)) + { + return; + } + + if (!ProtoManager.HasIndex(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(); + var options = new List(); + + 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(proto: ProtoManager), "Marker"); + } + + return CompletionResult.Empty; + } +} diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs new file mode 100644 index 0000000000..496cb387e8 --- /dev/null +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -0,0 +1,1086 @@ +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 _biomeQuery; + private EntityQuery _fixturesQuery; + private EntityQuery _ghostQuery; + private EntityQuery _xformQuery; + + private readonly HashSet _handledEntities = new(); + private const float DefaultLoadRange = 16f; + private float _loadRange = DefaultLoadRange; + private static readonly ProtoId AllowBiomeLoadingTag = "AllowBiomeLoading"; + + private List<(Vector2i, Tile)> _tiles = new(); + + private ObjectPool> _tilePool = + new DefaultObjectPool>(new SetPolicy(), 256); + + /// + /// Load area for chunks containing tiles, decals etc. + /// + private Box2 _loadArea = new(-DefaultLoadRange, -DefaultLoadRange, DefaultLoadRange, DefaultLoadRange); + + /// + /// Stores the chunks active for this tick temporarily. + /// + private readonly Dictionary> _activeChunks = new(); + + private readonly Dictionary>> _markerChunks = new(); + + public override void Initialize() + { + base.Initialize(); + Log.Level = LogLevel.Debug; + _biomeQuery = GetEntityQuery(); + _fixturesQuery = GetEntityQuery(); + _ghostQuery = GetEntityQuery(); + _xformQuery = GetEntityQuery(); + SubscribeLocalEvent(OnBiomeMapInit); + SubscribeLocalEvent(OnFTLStarted); + SubscribeLocalEvent(OnShuttleFlatten); + Subs.CVar(_configManager, CVars.NetMaxUpdateRange, SetLoadRange, true); + InitializeCommands(); + SubscribeLocalEvent(ProtoReload); + } + + private void ProtoReload(PrototypesReloadedEventArgs obj) + { + if (!obj.ByType.TryGetValue(typeof(BiomeTemplatePrototype), out var reloads)) + return; + + var query = AllEntityQuery(); + + 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(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 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); + } + + /// + /// Sets the and refreshes layers. + /// + 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); + } + + /// + /// Adds the specified layer at the specified marker if it exists. + /// + 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); + } + + /// + /// Adds the specified template at the specified marker if it exists, withour overriding every layer. + /// + 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(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(ev.MapUid, out var biome) || + !TryComp(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); + } + + /// + /// Preloads biome for the specified area. + /// + 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(); + + 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(); + + 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 + + /// + /// Loads all of the chunks for a particular biome, as well as handle any marker chunks. + /// + 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); + } + } + + /// + /// 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). + /// + 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>>(); + + // 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(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>(); + pending[chunkOrigin] = pendingMarkers; + } + + if (!pendingMarkers.TryGetValue(layer, out var layerMarkers)) + { + layerMarkers = new List(); + pendingMarkers[layer] = layerMarkers; + } + + layerMarkers.Add(node); + } + + lock (loadedMarkers) + { + if (!loadedMarkers.TryGetValue(layer, out var lockMobChunks)) + { + lockMobChunks = new HashSet(); + loadedMarkers[layer] = lockMobChunks; + } + + lockMobChunks.Add(chunk); + + foreach (var (chunkOrigin, layers) in pending) + { + if (!component.PendingMarkers.TryGetValue(chunkOrigin, out var lockMarkers)) + { + lockMarkers = new Dictionary>(); + component.PendingMarkers[chunkOrigin] = lockMarkers; + } + + foreach (var (lockLayer, nodes) in layers) + { + lockMarkers[lockLayer] = nodes; + } + } + } + }); + } + + component.ForcedMarkerLayers.Clear(); + } + + /// + /// Gets the marker nodes for the specified area. + /// + /// Should we include empty tiles when determine markers (e.g. if they are yet to be loaded) + public void GetMarkerNodes( + EntityUid gridUid, + BiomeComponent biome, + MapGridComponent grid, + BiomeMarkerLayerPrototype layerProto, + bool forced, + Box2i bounds, + int count, + Random rand, + out Dictionary spawnSet, + out HashSet existingEnts, + bool emptyTiles = true) + { + DebugTools.Assert(count > 0); + var remainingTiles = _tilePool.Get(); + var nodeEntities = new Dictionary(); + var nodeMask = new Dictionary(); + + // 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(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(); + existingEnts = new HashSet(); + + // 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); + } + + /// + /// Loads the pre-deteremined marker nodes for a particular chunk. + /// This is calculated in + /// + /// + /// Note that the marker chunks do not correspond to this chunk. + /// + 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(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(uid); + RemComp(uid); + EntityManager.InitializeAndStartEntity(uid); + modified.Add(node); + } + } + + if (modified.Count == 0) + { + component.ModifiedTiles.Remove(chunk); + _tilePool.Return(modified); + } + + component.PendingMarkers.Remove(chunk); + } + + /// + /// Loads a particular queued chunk for a biome. + /// + 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(); + 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(); + 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 + + /// + /// Handles all of the queued chunk unloads for a particular biome. + /// + 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); + } + } + + /// + /// Unloads a specific biome chunk. + /// + 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(); + + // 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(); + + 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 + + /// + /// Creates a simple planet setup for a map. + /// + public void EnsurePlanet(EntityUid mapUid, BiomeTemplatePrototype biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null) + { + if (!Resolve(mapUid, ref metadata)) + return; + + EnsureComp(mapUid); + var biome = EntityManager.ComponentFactory.GetComponent(); + 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(mapUid); + gravity.Enabled = true; + gravity.Inherent = true; + Dirty(mapUid, gravity, metadata); + + // Day lighting + // Daylight: #D8B059 + // Midday: #E6CB8B + // Moonlight: #2b3143 + // Lava: #A34931 + var light = EnsureComp(mapUid); + light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059"); + Dirty(mapUid, light, metadata); + + EnsureComp(mapUid); + + EnsureComp(mapUid); + + EnsureComp(mapUid); + EnsureComp(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); + } + + /// + /// Sets the specified tiles as relevant and marks them as modified. + /// + 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 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); + } +} diff --git a/Content.Server/Procedural/BiomeSystem.Commands.cs b/Content.Server/Procedural/BiomeSystem.Commands.cs deleted file mode 100644 index ef90310483..0000000000 --- a/Content.Server/Procedural/BiomeSystem.Commands.cs +++ /dev/null @@ -1,105 +0,0 @@ -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(); - 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(args[0])); - case 2: - return CompletionResult.FromHint("layerId"); - case 3: - return CompletionResult.FromOptions(CompletionHelper.PrototypeIDs()); - } - - 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(); - system.RemoveLayer((entId, biome), args[1]); - } - - public override CompletionResult GetCompletion(IConsoleShell shell, string[] args) - { - switch (args.Length) - { - case 0: - return CompletionResult.FromOptions(CompletionHelper.Components(args[0])); - case 1: - return CompletionResult.FromHint("layerId"); - } - - return CompletionResult.Empty; - } - } -} diff --git a/Content.Server/Procedural/BiomeSystem.Planet.cs b/Content.Server/Procedural/BiomeSystem.Planet.cs deleted file mode 100644 index c5138ca04e..0000000000 --- a/Content.Server/Procedural/BiomeSystem.Planet.cs +++ /dev/null @@ -1,65 +0,0 @@ -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 -{ - /// - /// Copies the biomecomponent to the specified map. - /// - public BiomeComponent? AddBiome(Entity mapUid, EntProtoId biomeTemplate, int? seed = null) - { - if (!_protomanager.Index(biomeTemplate).Components.TryGetComponent(Factory.GetComponentName(), out var template)) - { - return null; - } - - var biome = Factory.GetComponent(); - var biomeObj = (object)biome; - _serManager.CopyTo(template, ref biomeObj, notNullableOverride: true); - seed ??= _random.Next(); - biome.Seed = seed.Value; - AddComp(mapUid, biome, true); - return biome; - } - - /// - /// Creates a simple planet setup for a map. - /// - public void EnsurePlanet(EntityUid mapUid, EntProtoId biomeTemplate, int? seed = null, MetaDataComponent? metadata = null, Color? mapLight = null) - { - if (!Resolve(mapUid, ref metadata)) - return; - - EnsureComp(mapUid); - AddBiome(mapUid, biomeTemplate, seed); - var gravity = EnsureComp(mapUid); - gravity.Enabled = true; - gravity.Inherent = true; - Dirty(mapUid, gravity, metadata); - - var light = EnsureComp(mapUid); - light.AmbientLightColor = mapLight ?? Color.FromHex("#D8B059"); - Dirty(mapUid, light, metadata); - - EnsureComp(mapUid); - - EnsureComp(mapUid); - - EnsureComp(mapUid); - EnsureComp(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); - } -} diff --git a/Content.Server/Procedural/BiomeSystem.cs b/Content.Server/Procedural/BiomeSystem.cs deleted file mode 100644 index 6d584ad3b2..0000000000 --- a/Content.Server/Procedural/BiomeSystem.cs +++ /dev/null @@ -1,661 +0,0 @@ -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!; - - /// - /// Ignored components for default checks - /// - public static readonly List IgnoredComponents = new(); - - /// - /// Jobs for biomes to load. - /// - private JobQueue _biomeQueue = default!; - - private float _loadRange = 1f; - private float _loadTime; - - private EntityQuery _ghostQuery; - private EntityQuery _biomeQuery; - - private static readonly ProtoId AllowBiomeLoadingTag = "AllowBiomeLoading"; - - public override void Initialize() - { - base.Initialize(); - _ghostQuery = GetEntityQuery(); - _biomeQuery = GetEntityQuery(); - - IgnoredComponents.Add(Factory.GetComponentName()); - - Subs.CVar(_cfgManager, CCVars.BiomeLoadRange, OnLoadRange, true); - Subs.CVar(_cfgManager, CCVars.BiomeLoadTime, OnLoadTime, true); - - SubscribeLocalEvent(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(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); - } - - /// - /// Preloads biome for the specified area. - /// - 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 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 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 biome, string label, ProtoId 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(); - - 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(); - - // 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(); - - 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>(); - - 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); - } - } - - /// - /// Gets the full bounds to be loaded. Considers layer dependencies where they may have different chunk sizes. - /// - 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; - } - - /// - /// Tries to add the viewer bounds of this entity for loading. - /// - 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 - { - [Dependency] private IEntityManager _entManager = default!; - [Dependency] private IPrototypeManager _protoManager = default!; - - private BiomeSystem System = default!; - private DungeonSystem DungeonSystem = default!; - - public Entity Grid; - - internal ISawmill _sawmill = default!; - - public BiomeLoadJob(double maxTime, CancellationToken cancellation = default) : base(maxTime, cancellation) - { - IoCManager.InjectDependencies(this); - System = _entManager.System(); - DungeonSystem = _entManager.System(); - } - - protected override async Task 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 -{ - [Dependency] private EntityManager _entManager = default!; - - public Entity Biome; - public Dictionary> 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 Process() - { - try - { - var grid = Biome.Comp1; - var biome = Biome.Comp2; - DebugTools.Assert(biome.Loading); - var maps = _entManager.System(); - var decals = _entManager.System(); - var lookup = _entManager.System(); - _entManager.TryGetComponent(Biome.Owner, out DecalGridComponent? decalGrid); - var forceUnload = _entManager.GetEntityQuery(); - var entities = new HashSet(); - 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; - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.AutoCabling.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.AutoCabling.cs index 83ff71f8b7..8c49a7d606 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.AutoCabling.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.AutoCabling.cs @@ -151,8 +151,7 @@ public sealed partial class DungeonJob if (found) continue; - var ent = _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile)); - AddLoadedEntity(tile, ent); + _entManager.SpawnEntity(gen.Entity, _maps.GridTileToLocal(_gridUid, _grid, tile)); } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs new file mode 100644 index 0000000000..48adb8af18 --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Biome.cs @@ -0,0 +1,74 @@ +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 +{ + /// + /// + /// + private async Task PostGen(BiomeDunGen dunGen, Dungeon dungeon, HashSet reservedTiles, Random random) + { + if (!_prototype.TryIndex(dunGen.BiomeTemplate, out var indexedBiome)) + return; + + var biomeSystem = _entManager.System(); + + var seed = random.Next(); + var xformQuery = _entManager.GetEntityQuery(); + + 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; + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.BiomeMarkerLayer.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.BiomeMarkerLayer.cs new file mode 100644 index 0000000000..abc74ddc4f --- /dev/null +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.BiomeMarkerLayer.cs @@ -0,0 +1,105 @@ +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 +{ + /// + /// + /// + private async Task PostGen(BiomeMarkerLayerDunGen dunGen, Dungeon dungeon, HashSet 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(); + var weightedRandom = _prototype.Index(dunGen.MarkerTemplate); + var xformQuery = _entManager.GetEntityQuery(); + var templates = new Dictionary(); + + 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(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; + } + } + } +} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.BoundaryWall.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.BoundaryWall.cs index 7bc7527ff5..1c48a84cce 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.BoundaryWall.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.BoundaryWall.cs @@ -32,10 +32,7 @@ public sealed partial class DungeonJob if (!_anchorable.TileFree((_gridUid, _grid), neighbor, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) continue; - var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - tiles.Add((neighbor, tile)); - AddLoadedTile(neighbor, tile); - DebugTools.Assert(dungeon.AllTiles.Contains(neighbor)); + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); } foreach (var index in dungeon.CorridorExteriorTiles) @@ -46,10 +43,7 @@ public sealed partial class DungeonJob if (!_anchorable.TileFree((_gridUid, _grid), index, DungeonSystem.CollisionLayer, DungeonSystem.CollisionMask)) continue; - var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - tiles.Add((index, tile)); - AddLoadedTile(index, tile); - DebugTools.Assert(dungeon.AllTiles.Contains(index)); + tiles.Add((index, _tile.GetVariantTile((ContentTileDefinition)tileDef, random))); } _maps.SetTiles(_gridUid, _grid, tiles); @@ -88,21 +82,18 @@ public sealed partial class DungeonJob } if (isCorner) - { - var uid = _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); - AddLoadedEntity(index.Index, uid); - } + _entManager.SpawnEntity(cornerWall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); if (!isCorner) + _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); + + if (i % 20 == 0) { - var uid = _entManager.SpawnEntity(wall, _maps.GridTileToLocal(_gridUid, _grid, index.Index)); - AddLoadedEntity(index.Index, uid); + await SuspendDungeon(); + + if (!ValidateResume()) + return; } - - await SuspendDungeon(); - - if (!ValidateResume()) - return; } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs deleted file mode 100644 index 391fb9161b..0000000000 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Chunk.cs +++ /dev/null @@ -1,42 +0,0 @@ -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 -{ - /// - /// - /// - private async Task PostGen(ChunkDunGen dunGen, HashSet reservedTiles, Random random) - { - var dungeon = new Dungeon(); - var tiles = new HashSet(); - 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()); - dungeon.AddRoom(room); - return dungeon; - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Corridor.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Corridor.cs index 8353d81f16..bf9f910b94 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Corridor.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Corridor.cs @@ -94,14 +94,12 @@ public sealed partial class DungeonJob var setTiles = new List<(Vector2i, Tile)>(); var tileDef = (ContentTileDefinition) _tileDefManager[gen.Tile]; - foreach (var node in corridorTiles) + foreach (var tile in corridorTiles) { - if (reservedTiles.Contains(node)) + if (reservedTiles.Contains(tile)) continue; - var tile = _tile.GetVariantTile(tileDef, random); - setTiles.Add((node, tile)); - AddLoadedTile(node, tile); + setTiles.Add((tile, _tile.GetVariantTile(tileDef, random))); } _maps.SetTiles(_gridUid, _grid, setTiles); diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorClutter.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorClutter.cs index e28c6798f2..e0be852733 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorClutter.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorClutter.cs @@ -48,13 +48,7 @@ public sealed partial class DungeonJob var protos = _entTable.GetSpawns(contents, random); var coords = _maps.ToCenterCoordinates(_gridUid, tile, _grid); - var uids = _entManager.SpawnEntitiesAttachedTo(coords, protos); - - foreach (var uid in uids) - { - AddLoadedEntity(tile, uid); - } - + _entManager.SpawnEntitiesAttachedTo(coords, protos); await SuspendIfOutOfTime(); if (!ValidateResume()) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorDecalSkirting.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorDecalSkirting.cs index 48088a4718..cd8737e6ec 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorDecalSkirting.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.CorridorDecalSkirting.cs @@ -83,8 +83,7 @@ public sealed partial class DungeonJob { // Decals not being centered biting my ass again var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color); - AddLoadedDecal(tile, did); + _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color); } } @@ -97,8 +96,7 @@ public sealed partial class DungeonJob { // Decals not being centered biting my ass again var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color); - AddLoadedDecal(tile, did); + _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color); } continue; @@ -113,8 +111,7 @@ public sealed partial class DungeonJob if (decks.CornerDecals.TryGetValue(dirFlag, out var cDir)) { var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile).Offset(offset); - _decals.TryAddDecal(cDir, gridPos, out var did, color: decks.Color); - AddLoadedDecal(tile, did); + _decals.TryAddDecal(cDir, gridPos, out _, color: decks.Color); } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs index 10481c5b5f..f1808ec90c 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenNoiseDistance.cs @@ -28,7 +28,7 @@ public sealed partial class DungeonJob Random random) { var tiles = new List<(Vector2i, Tile)>(); - var matrix = Matrix3Helpers.CreateTranslation(_position + position); + var matrix = Matrix3Helpers.CreateTranslation(position); foreach (var layer in dungen.Layers) { @@ -76,9 +76,7 @@ public sealed partial class DungeonJob break; } - var tile = new Tile(tileDef.TileId, variant: variant); - tiles.Add((adjusted, tile)); - AddLoadedTile(adjusted, tile); + tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant))); roomTiles.Add(adjusted); break; } @@ -103,8 +101,6 @@ public sealed partial class DungeonJob { switch (distance) { - case DunGenDistanceSquared: - return dx * dx + dy * dy; case DunGenEuclideanSquaredDistance: return MathF.Min(1f, (dx * dx + dy * dy) / MathF.Sqrt(2)); case DunGenSquareBump: diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs index f348ce7301..8eb85e2cb8 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenPrefab.cs @@ -196,9 +196,7 @@ public sealed partial class DungeonJob if (reservedTiles.Contains(index)) continue; - var tile = new Tile(_tileDefManager[fallbackTile.Value].TileId); - tiles.Add((index, tile)); - AddLoadedTile(index, tile); + tiles.Add((index, new Tile(_tileDefManager[fallbackTile.Value].TileId))); } } @@ -232,14 +230,7 @@ public sealed partial class DungeonJob var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform); // The expensive bit yippy. - var data = _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles); - - _data.Merge(data); - - await SuspendDungeon(); - - if (!ValidateResume()) - return dungeon; + _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles); var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize; var roomTiles = new HashSet(room.Size.X * room.Size.Y); diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs index 4c53141bc9..dfc0932915 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DunGenReplaceTile.cs @@ -42,7 +42,6 @@ public sealed partial class DungeonJob } replacements.Add((node, tile)); - AddLoadedTile(node, tile); break; } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.DungeonEntrance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.DungeonEntrance.cs index 35a35e9656..dceeac3f12 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.DungeonEntrance.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.DungeonEntrance.cs @@ -71,17 +71,14 @@ public sealed partial class DungeonJob isValid = true; // Entrance wew - var tileVariant = _tile.GetVariantTile(tileDef, random); - _maps.SetTile(_gridUid, _grid, tile, tileVariant); - AddLoadedTile(tile, tileVariant); + _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile(tileDef, random)); 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)) { - var uid = _entManager.SpawnAtPosition(ent, gridCoords); - AddLoadedEntity(tile, uid); + _entManager.SpawnAtPosition(ent, gridCoords); } // Clear out any biome tiles nearby to avoid blocking it diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs index 02686f7a26..6483448240 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs @@ -51,7 +51,6 @@ public sealed partial class DungeonJob foreach (var ent in entities) { var uid = _entManager.SpawnAtPosition(ent, _maps.GridTileToLocal(_gridUid, _grid, tile)); - AddLoadedEntity(tile, uid); _entManager.RemoveComponent(uid); _entManager.RemoveComponent(uid); npcs.SleepNPC(uid); diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.EntranceFlank.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.EntranceFlank.cs index 831081cdf1..1788c23cae 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.EntranceFlank.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.EntranceFlank.cs @@ -35,9 +35,7 @@ public sealed partial class DungeonJob if (reservedTiles.Contains(neighbor)) continue; - var tile = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - tiles.Add((neighbor, tile)); - AddLoadedTile(neighbor, tile); + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); spawnPositions.Add(neighbor); } } @@ -47,12 +45,7 @@ public sealed partial class DungeonJob foreach (var entrance in spawnPositions) { - var uids = _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random)); - - foreach (var uid in uids) - { - AddLoadedEntity(entrance, uid); - } + _entManager.SpawnEntitiesAttachedTo(_maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random)); } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Exterior.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Exterior.cs index 1248ab7505..4f2f564ded 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Exterior.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Exterior.cs @@ -1,4 +1,3 @@ -using System.Numerics; using System.Threading.Tasks; using Content.Shared.Maps; using Content.Shared.NPC; @@ -14,22 +13,15 @@ public sealed partial class DungeonJob /// /// /// - private async Task> GenerateExteriorDungen(int runCount, int maxRuns, Vector2i position, ExteriorDunGen dungen, HashSet reservedTiles, Random random) + private async Task> GenerateExteriorDungen(Vector2i position, ExteriorDunGen dungen, HashSet reservedTiles, Random random) { DebugTools.Assert(_grid.ChunkCount > 0); var aabb = new Box2i(_grid.LocalAABB.BottomLeft.Floored(), _grid.LocalAABB.TopRight.Floored()); - // 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); - - // 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 angle = random.NextAngle(); var distance = Math.Max(aabb.Width / 2f + 1f, aabb.Height / 2f + 1f); + var startTile = new Vector2i(0, (int) distance).Rotate(angle); Vector2i? dungeonSpawn = null; @@ -55,19 +47,9 @@ public sealed partial class DungeonJob }; } - // 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 config = _prototype.Index(dungen.Proto); var nextSeed = random.Next(); - var (dungeons, newReserved) = await GetDungeons(dungeonSpawn.Value, subConfig, subConfig.Layers, nextSeed, new Random(nextSeed), reserved: reservedTiles); - reservedTiles.UnionWith(newReserved); + var dungeons = await GetDungeons(dungeonSpawn.Value, config, config.Layers, reservedTiles, nextSeed, new Random(nextSeed)); return dungeons; } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.ExternalWindow.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.ExternalWindow.cs index 1608e50266..482cb34a56 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.ExternalWindow.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.ExternalWindow.cs @@ -105,9 +105,7 @@ public sealed partial class DungeonJob if (reservedTiles.Contains(neighbor)) continue; - var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - tiles.Add((neighbor, tileVariant)); - AddLoadedTile(neighbor, tileVariant); + tiles.Add((neighbor, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); index++; takenTiles.Add(neighbor); } @@ -121,13 +119,7 @@ public sealed partial class DungeonJob { var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile.Item1); - var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random)); - - foreach (var uid in uids) - { - AddLoadedEntity(tile.Item1, uid); - } - + _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random)); await SuspendDungeon(); if (!ValidateResume()) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Fill.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Fill.cs index 95ec9d56e5..b579d4e5a1 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Fill.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Fill.cs @@ -18,45 +18,21 @@ public sealed partial class DungeonJob 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); - var uid = _entManager.SpawnEntity(fill.Entity, gridPos); + _entManager.SpawnEntity(fill.Entity, gridPos); - AddLoadedEntity(tile, uid); + await SuspendDungeon(); + if (!ValidateResume()) + break; } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Helpers.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Helpers.cs index 4ced89ba0e..c57757b421 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Helpers.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Helpers.cs @@ -52,8 +52,6 @@ public sealed partial class DungeonJob } } } - - dungeon.RefreshAllTiles(); } private void WidenCorridor(Dungeon dungeon, float width, ICollection corridorTiles) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.InternalWindow.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.InternalWindow.cs index c736eb6070..f80b3face7 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.InternalWindow.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.InternalWindow.cs @@ -81,16 +81,9 @@ public sealed partial class DungeonJob { var tile = validTiles[j]; var gridPos = _maps.GridTileToLocal(_gridUid, _grid, tile); - var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - _maps.SetTile(_gridUid, _grid, tile, tileVariant); - AddLoadedTile(tile, tileVariant); + _maps.SetTile(_gridUid, _grid, tile, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); - var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random)); - - foreach (var uid in uids) - { - AddLoadedEntity(tile, uid); - } + _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random)); } if (validTiles.Count > 0) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Junction.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Junction.cs index 88db2fad42..28cbc9b208 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Junction.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Junction.cs @@ -113,17 +113,10 @@ public sealed partial class DungeonJob if (reservedTiles.Contains(weh)) continue; - var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - _maps.SetTile(_gridUid, _grid, weh, tileVariant); - AddLoadedTile(weh, tileVariant); + _maps.SetTile(_gridUid, _grid, weh, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); var coords = _maps.GridTileToLocal(_gridUid, _grid, weh); - var uids = _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random)); - - foreach (var uid in uids) - { - AddLoadedEntity(weh, uid); - } + _entManager.SpawnEntitiesAttachedTo(coords, _entTable.GetSpawns(contents, random)); } break; diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.MiddleConnection.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.MiddleConnection.cs index 52edaf3433..d6e3c09d62 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.MiddleConnection.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.MiddleConnection.cs @@ -18,7 +18,6 @@ public sealed partial class DungeonJob // Grab all of the room bounds // Then, work out connections between them var roomBorders = new Dictionary>(dungeon.Rooms.Count); - var flank = gen.Flank; foreach (var room in dungeon.Rooms) { @@ -108,30 +107,18 @@ public sealed partial class DungeonJob continue; width--; - var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - _maps.SetTile(_gridUid, _grid, node, tileVariant); - AddLoadedTile(node, tileVariant); + _maps.SetTile(_gridUid, _grid, node, _tile.GetVariantTile((ContentTileDefinition) tileDef, random)); if (flankContents != null && nodeDistances.Count - i <= 2) { - var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random)); - - foreach (var uid in uids) - { - AddLoadedEntity(node, uid); - } + _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(flankContents, random)); } else { // Iterate neighbors and check for blockers, if so bulldoze ClearDoor(dungeon, _grid, node); - var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random)); - - foreach (var uid in uids) - { - AddLoadedEntity(node, uid); - } + _entManager.SpawnEntitiesAttachedTo(gridPos, _entTable.GetSpawns(contents, random)); } if (width == 0) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Mobs.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Mobs.cs index 341dbf168a..caf6828e43 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Mobs.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Mobs.cs @@ -49,7 +49,6 @@ public sealed partial class DungeonJob _entManager.RemoveComponent(uid); _entManager.RemoveComponent(uid); npcs.SleepNPC(uid); - AddLoadedEntity(tile, uid); } break; diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Noise.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Noise.cs index 201948fc02..b2526ec17d 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Noise.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Noise.cs @@ -98,9 +98,7 @@ public sealed partial class DungeonJob var variant = _tile.PickVariant((ContentTileDefinition) tileDef, random); var adjusted = Vector2.Transform(node + _grid.TileSizeHalfVector, matrix).Floored(); - var tileVariant = new Tile(tileDef.TileId, variant: variant); - tiles.Add((adjusted, tileVariant)); - AddLoadedTile(adjusted, tileVariant); + tiles.Add((adjusted, new Tile(tileDef.TileId, variant: variant))); roomTiles.Add(adjusted); tileCount++; break; @@ -129,7 +127,8 @@ public sealed partial class DungeonJob } } - await SuspendDungeon(); + await SuspendIfOutOfTime(); + ValidateResume(); } var center = Vector2.Zero; @@ -141,7 +140,8 @@ public sealed partial class DungeonJob center /= roomTiles.Count; rooms.Add(new DungeonRoom(roomTiles, center, roomArea, new HashSet())); - await SuspendDungeon(); + await SuspendIfOutOfTime(); + ValidateResume(); } _maps.SetTiles(_gridUid, _grid, tiles); diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs index 035820fd90..78ab2b7a0d 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs @@ -19,26 +19,12 @@ public sealed partial class DungeonJob HashSet reservedTiles, Random random) { - var emptyTiles = false; - var replaceEntities = new Dictionary(); - var availableTiles = new List(); - var remapName = _entManager.ComponentFactory.GetComponentName(); - var replacementRemapping = new Dictionary(); - - if (_prototype.TryIndex(gen.Replacement, out var replacementProto) && - replacementProto.Components.TryGetComponent(remapName, out var replacementComps)) - { - var remappingComp = (EntityRemapComponent) replacementComps; - replacementRemapping = remappingComp.Mask; - } - - if (gen.Replacement != null) - { - replacementRemapping[gen.Replacement.Value] = gen.Entity; - } - foreach (var dungeon in dungeons) { + var emptyTiles = false; + var replaceEntities = new Dictionary(); + var availableTiles = new List(); + foreach (var node in dungeon.AllTiles) { if (reservedTiles.Contains(node)) @@ -55,23 +41,19 @@ public sealed partial class DungeonJob // 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. - // We can't replace so just stop here. - if (gen.Replacement != null) + while (enumerator.MoveNext(out var uid)) { - while (enumerator.MoveNext(out var uid)) + // We can't replace so just stop here. + if (gen.Replacement == null) + break; + + var prototype = _entManager.GetComponent(uid.Value).EntityPrototype; + + if (prototype?.ID == gen.Replacement) { - var prototype = _entManager.GetComponent(uid.Value).EntityPrototype; - - if (string.IsNullOrEmpty(prototype?.ID)) - continue; - - // It has a valid remapping so take it over. - if (replacementRemapping.ContainsKey(prototype.ID)) - { - replaceEntities[node] = uid.Value; - found = true; - break; - } + replaceEntities[node] = uid.Value; + found = true; + break; } } @@ -86,86 +68,83 @@ public sealed partial class DungeonJob if (!ValidateResume()) return; } - } - var remapping = new Dictionary(); + var remapping = new Dictionary(); - // 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; - } - - var frontier = new ValueList(32); - - // 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); - - // While we have remaining tiles keep iterating - while (groupSize > 0 && availableTiles.Count > 0) + // TODO: Move this to engine + if (_prototype.TryIndex(gen.Entity, out var proto) && + proto.Components.TryGetComponent("EntityRemap", out var comps)) { - 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++) - { - for (var y = -1; y <= 1; y++) - { - var neighbor = new Vector2i(node.X + x, node.Y + y); - - if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor)) - continue; - - frontier.Add(neighbor); - } - } - - var prototype = gen.Entity; - - // 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; - } - } - - // Tile valid salad so add it. - var uid = _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node)); - AddLoadedEntity(node, uid); - - groupSize--; - - await SuspendDungeon(); - - if (!ValidateResume()) - return; - } + var remappingComp = (EntityRemapComponent) comps; + remapping = remappingComp.Mask; } - if (groupSize > 0) + var frontier = new ValueList(32); + + // Iterate the group counts and pathfind out each group. + for (var i = 0; i < gen.Count; i++) { - // 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}!"); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + 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); + + // 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++) + { + for (var y = -1; y <= 1; y++) + { + var neighbor = new Vector2i(node.X + x, node.Y + y); + + if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor)) + continue; + + frontier.Add(neighbor); + } + } + + var prototype = gen.Entity; + + if (replaceEntities.TryGetValue(node, out var existingEnt)) + { + var existingProto = _entManager.GetComponent(existingEnt).EntityPrototype; + _entManager.DeleteEntity(existingEnt); + + 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)); + + groupSize--; + } + } + + if (groupSize > 0) + { + _sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!"); + } } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs deleted file mode 100644 index 2d00dbe2ab..0000000000 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Roof.cs +++ /dev/null @@ -1,38 +0,0 @@ -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 dungeons, HashSet reservedTiles, Random random) - { - var roofComp = _entManager.EnsureComponent(_gridUid); - - var noise = roof.Noise; - var oldSeed = noise?.GetSeed() ?? 0; - noise?.SetSeed(_seed + oldSeed); - var rooves = _entManager.System(); - - 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); - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.RoomEntrance.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.RoomEntrance.cs index 17ad9b5e35..a4a01b5f0b 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.RoomEntrance.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.RoomEntrance.cs @@ -25,9 +25,7 @@ public sealed partial class DungeonJob if (reservedTiles.Contains(entrance)) continue; - var tileVariant = _tile.GetVariantTile((ContentTileDefinition)tileDef, random); - setTiles.Add((entrance, tileVariant)); - AddLoadedTile(entrance, tileVariant); + setTiles.Add((entrance, _tile.GetVariantTile((ContentTileDefinition) tileDef, random))); } } @@ -40,15 +38,10 @@ public sealed partial class DungeonJob if (reservedTiles.Contains(entrance)) continue; - var uids = _entManager.SpawnEntitiesAttachedTo( + _entManager.SpawnEntitiesAttachedTo( _maps.GridTileToLocal(_gridUid, _grid, entrance), _entTable.GetSpawns(contents, random)); - foreach (var uid in uids) - { - AddLoadedEntity(entrance, uid); - } - await SuspendDungeon(); if (!ValidateResume()) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleDecal.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.SampleDecal.cs deleted file mode 100644 index b240c0b84c..0000000000 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleDecal.cs +++ /dev/null @@ -1,64 +0,0 @@ -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 -{ - /// - /// - /// - private async Task PostGen(SampleDecalDunGen gen, - List dungeons, - HashSet 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); - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleEntity.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.SampleEntity.cs deleted file mode 100644 index 1a9fc8b2e2..0000000000 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleEntity.cs +++ /dev/null @@ -1,62 +0,0 @@ -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 -{ - /// - /// - /// - private async Task PostGen( - SampleEntityDunGen gen, - List dungeons, - HashSet 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); - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleTile.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.SampleTile.cs deleted file mode 100644 index 97875b22fc..0000000000 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.SampleTile.cs +++ /dev/null @@ -1,66 +0,0 @@ -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 -{ - /// - /// - /// - private async Task PostGen(SampleTileDunGen gen, - List dungeons, - HashSet 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); - } - } - } -} diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.SplineDungeonConnector.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.SplineDungeonConnector.cs index 8635bc0f48..a131efd353 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.SplineDungeonConnector.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.SplineDungeonConnector.cs @@ -2,7 +2,6 @@ using System.Numerics; 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; @@ -12,10 +11,10 @@ namespace Content.Server.Procedural.DungeonJob; public sealed partial class DungeonJob { /// - /// + /// /// private async Task PostGen( - Shared.Procedural.DungeonLayers.SplineDungeonConnectorDunGen gen, + SplineDungeonConnectorDunGen gen, List dungeons, HashSet reservedTiles, Random random) @@ -60,7 +59,6 @@ public sealed partial class DungeonJob { 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. @@ -112,7 +110,6 @@ public sealed partial class DungeonJob } tiles.Add((node, tile)); - AddLoadedTile(node, tile); } _maps.SetTiles(_gridUid, _grid, tiles); @@ -126,7 +123,6 @@ public sealed partial class DungeonJob allTiles.Add(node); tiles.Add((node, pathTile)); - AddLoadedTile(node, pathTile); } _maps.SetTiles(_gridUid, _grid, tiles); diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.WallMount.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.WallMount.cs index fa2921b80a..e5bb32bd0c 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.WallMount.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.WallMount.cs @@ -32,18 +32,11 @@ public sealed partial class DungeonJob if (reservedTiles.Contains(neighbor)) continue; - var tileVariant = _tile.GetVariantTile(tileDef, random); - _maps.SetTile(_gridUid, _grid, neighbor, tileVariant); - AddLoadedTile(neighbor, tileVariant); + _maps.SetTile(_gridUid, _grid, neighbor, _tile.GetVariantTile(tileDef, random)); var gridPos = _maps.GridTileToLocal(_gridUid, _grid, neighbor); var protoNames = _entTable.GetSpawns(contents, random); - var uids = _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames); - - foreach (var uid in uids) - { - AddLoadedEntity(neighbor, uid); - } + _entManager.SpawnEntitiesAttachedTo(gridPos, protoNames); await SuspendDungeon(); if (!ValidateResume()) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.cs index 8145492205..77404fc963 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.cs @@ -1,7 +1,8 @@ -using System.Numerics; +using System.Linq; 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; @@ -22,10 +23,11 @@ using Robust.Shared.Physics.Components; 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, DungeonData)> +public sealed partial class DungeonJob : Job> { public bool TimeSlice = true; @@ -58,10 +60,6 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> private readonly ISawmill _sawmill; - private DungeonData _data = new(); - - private HashSet? _reservedTiles; - public DungeonJob( ISawmill sawmill, double maxTime, @@ -81,14 +79,12 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> int seed, Vector2i position, EntityCoordinates? targetCoordinates = null, - CancellationToken cancellation = default, - HashSet? reservedTiles = null) : base(maxTime, cancellation) + CancellationToken cancellation = default) : base(maxTime, cancellation) { _sawmill = sawmill; _entManager = entManager; _prototype = prototype; _tileDefManager = tileDefManager; - _reservedTiles = reservedTiles; _anchorable = anchorable; _decals = decals; @@ -115,18 +111,17 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> /// /// Gets the relevant dungeon, running recursively as relevant. /// - /// Should we reserve tiles even if the config doesn't specify. - private async Task<(List, HashSet)> GetDungeons( + /// Should we reserve tiles even if the config doesn't specify. + private async Task> GetDungeons( Vector2i position, DungeonConfig config, List layers, + HashSet reservedTiles, int seed, Random random, - HashSet? reserved = null, List? existing = null) { var dungeons = new List(); - var reservedTiles = reserved == null ? new HashSet() : new HashSet(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) @@ -142,8 +137,8 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> foreach (var layer in layers) { - var dungCount = dungeons.Count; - await RunLayer(i, count, config, dungeons, position, layer, reservedTiles, seed, random); + var dungCount = dungeons.Count; + await RunLayer(dungeons, position, layer, reservedTiles, seed, random); if (config.ReserveTiles) { @@ -157,23 +152,24 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> await SuspendDungeon(); if (!ValidateResume()) - return (new List(), new HashSet()); + return new List(); } } - // Only return the new dungeons and applicable reserved tiles. - return (dungeons[(existing?.Count ?? 0)..], config.ReturnReserved ? reservedTiles : new HashSet()); + return dungeons; } - protected override async Task<(List, DungeonData)> Process() + protected override async Task?> 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(); - var (dungeons, _) = await GetDungeons(position, _gen, _gen.Layers, _seed, random, reserved: _reservedTiles); + // Tiles we can no longer generate on due to being reserved elsewhere. + var reservedTiles = new HashSet(); + + var dungeons = await GetDungeons(position, _gen, _gen.Layers, reservedTiles, _seed, random); // To make it slightly more deterministic treat this RNG as separate ig. // Post-processing after finishing loading. @@ -185,7 +181,6 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> } // 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().CheckSplits(_gridUid); var npcSystem = _entManager.System(); @@ -199,13 +194,10 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> } _sawmill.Info($"Finished generating dungeon {_gen} with seed {_seed}"); - return (dungeons, _data); + return dungeons; } private async Task RunLayer( - int runCount, - int maxRuns, - DungeonConfig config, List dungeons, Vector2i position, IDunGenLayer layer, @@ -213,7 +205,7 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> 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 @@ -227,12 +219,15 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> 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; @@ -249,7 +244,7 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> await PostGen(flank, dungeons[^1], reservedTiles, random); break; case ExteriorDunGen exterior: - dungeons.AddRange(await GenerateExteriorDungen(runCount, maxRuns, position, exterior, reservedTiles, random)); + dungeons.AddRange(await GenerateExteriorDungen(position, exterior, reservedTiles, random)); break; case FillGridDunGen fill: await GenerateFillDunGen(fill, dungeons, reservedTiles); @@ -290,67 +285,27 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> case PrototypeDunGen prototypo: var groupConfig = _prototype.Index(prototypo.Proto); position = (position + random.NextPolarVector2(groupConfig.MinOffset, groupConfig.MaxOffset)).Floored(); - List? inheritedDungeons = null; - HashSet? inheritedReserved = null; - - switch (prototypo.InheritReserved) - { - case ReservedInheritance.All: - inheritedReserved = new HashSet(reservedTiles); - break; - case ReservedInheritance.None: - break; - default: - throw new NotImplementedException(); - } switch (prototypo.InheritDungeons) { case DungeonInheritance.All: - inheritedDungeons = dungeons; + dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: dungeons)); break; case DungeonInheritance.Last: - inheritedDungeons = dungeons.GetRange(dungeons.Count - 1, 1); + dungeons.AddRange(await GetDungeons(position, groupConfig, groupConfig.Layers, reservedTiles, seed, random, existing: 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; @@ -365,6 +320,11 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> } } + private void LogDataError(Type type) + { + _sawmill.Error($"Unable to find dungeon data keys for {type}"); + } + [Pure] private bool ValidateResume() { @@ -386,19 +346,4 @@ public sealed partial class DungeonJob : Job<(List, DungeonData)> 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; - } } diff --git a/Content.Server/Procedural/DungeonSystem.Rooms.cs b/Content.Server/Procedural/DungeonSystem.Rooms.cs index e5143454d3..e5b0981b3d 100644 --- a/Content.Server/Procedural/DungeonSystem.Rooms.cs +++ b/Content.Server/Procedural/DungeonSystem.Rooms.cs @@ -113,7 +113,7 @@ public sealed partial class DungeonSystem return roomRotation; } - public DungeonData SpawnRoom( + public void SpawnRoom( EntityUid gridUid, MapGridComponent grid, Matrix3x2 roomTransform, @@ -126,7 +126,6 @@ public sealed partial class DungeonSystem var templateMapUid = _maps.GetMapOrInvalid(roomMap); var templateGrid = Comp(templateMapUid); var roomDimensions = room.Size; - var data = new DungeonData(); var finalRoomRotation = roomTransform.Rotation(); @@ -155,7 +154,6 @@ public sealed partial class DungeonSystem } _tiles.Add((rounded, tileRef.Tile)); - data.Tiles[rounded] = tileRef.Tile; if (clearExisting) { @@ -188,7 +186,6 @@ public sealed partial class DungeonSystem // 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; @@ -259,18 +256,14 @@ public sealed partial class DungeonSystem var result = _decals.TryAddDecal( decal.Id, new EntityCoordinates(gridUid, position), - out var did, + out _, decal.Color, angle, decal.ZIndex, decal.Cleanable); - data.Decals.Add(did, position); - DebugTools.Assert(result); } } - - return data; } } diff --git a/Content.Server/Procedural/DungeonSystem.cs b/Content.Server/Procedural/DungeonSystem.cs index b758fa2d16..3a0a7ab2cd 100644 --- a/Content.Server/Procedural/DungeonSystem.cs +++ b/Content.Server/Procedural/DungeonSystem.cs @@ -199,8 +199,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem MapGridComponent grid, Vector2i position, int seed, - EntityCoordinates? coordinates = null, - HashSet? reservedTiles = null) + EntityCoordinates? coordinates = null) { var cancelToken = new CancellationTokenSource(); var job = new DungeonJob.DungeonJob( @@ -222,20 +221,18 @@ public sealed partial class DungeonSystem : SharedDungeonSystem seed, position, coordinates, - cancelToken.Token, - reservedTiles); + cancelToken.Token); _dungeonJobs.Add(job, cancelToken); _dungeonJobQueue.EnqueueJob(job); } - public async Task<(List, DungeonData)> GenerateDungeonAsync( + public async Task> GenerateDungeonAsync( DungeonConfig gen, EntityUid gridUid, MapGridComponent grid, Vector2i position, - int seed, - HashSet? reservedTiles = null) + int seed) { var cancelToken = new CancellationTokenSource(); var job = new DungeonJob.DungeonJob( @@ -257,8 +254,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem seed, position, null, - cancelToken.Token, - reservedTiles); + cancelToken.Token); _dungeonJobs.Add(job, cancelToken); _dungeonJobQueue.EnqueueJob(job); @@ -269,7 +265,7 @@ public sealed partial class DungeonSystem : SharedDungeonSystem throw job.Exception; } - return job.Result; + return job.Result!; } public Angle GetDungeonRotation(int seed) diff --git a/Content.Server/Salvage/SpawnSalvageMissionJob.cs b/Content.Server/Salvage/SpawnSalvageMissionJob.cs index 3a74a67dbf..cbce4dc692 100644 --- a/Content.Server/Salvage/SpawnSalvageMissionJob.cs +++ b/Content.Server/Salvage/SpawnSalvageMissionJob.cs @@ -1,17 +1,22 @@ +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; @@ -20,14 +25,15 @@ using Content.Shared.Salvage; 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; @@ -114,13 +120,14 @@ public sealed class SpawnSalvageMissionJob : Job .GetMission(difficultyProto, _missionParams.Seed); var missionBiome = _prototypeManager.Index(mission.Biome); - BiomeComponent? biome = null; if (missionBiome.BiomePrototype != null) { + var biome = _entManager.AddComponent(mapUid); var biomeSystem = _entManager.System(); - - biome = biomeSystem.AddBiome(mapUid, missionBiome.BiomePrototype.Value, mission.Seed); + biomeSystem.SetTemplate(mapUid, biome, _prototypeManager.Index(missionBiome.BiomePrototype)); + biomeSystem.SetSeed(mapUid, biome, mission.Seed); + _entManager.Dirty(mapUid, biome); // Gravity var gravity = _entManager.EnsureComponent(mapUid); @@ -165,7 +172,7 @@ public sealed class SpawnSalvageMissionJob : Job dungeonOffset = dungeonRotation.RotateVec(dungeonOffset); var dungeonMod = _prototypeManager.Index(mission.Dungeon); var dungeonConfig = _prototypeManager.Index(dungeonMod.Proto); - var (dungeons, data) = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset, + var dungeons = await WaitAsyncTask(_dungeon.GenerateDungeonAsync(dungeonConfig, mapUid, grid, (Vector2i)dungeonOffset, _missionParams.Seed)); var dungeon = dungeons.First(); @@ -176,20 +183,18 @@ public sealed class SpawnSalvageMissionJob : Job return false; } - // Don't modify any dungeon tiles with chunk gen. - // Have to defer biome loading until the primo dungen is generated. - if (biome != null) - { - foreach (var tile in dungeon.AllTiles) - { - biome.ModifiedTiles.Add(tile); - } - - biome.Enabled = true; - } - expedition.DungeonLocation = dungeonOffset; + List reservedTiles = new(); + + foreach (var tile in _map.GetTilesIntersecting(mapUid, grid, new Circle(Vector2.Zero, landingPadRadius), false)) + { + if (!_biome.TryGetBiomeTile(mapUid, grid, tile.GridIndices, out _)) + continue; + + reservedTiles.Add(tile.GridIndices); + } + var budgetEntries = new List(); /* @@ -203,14 +208,13 @@ public sealed class SpawnSalvageMissionJob : Job if (!lootProto.Guaranteed) continue; - foreach (var rule in lootProto.LootRules) + try { - switch (rule) - { - case BiomeLoot biomeLoot: - _biome.AddLayer(mapUid, $"{rule}", biomeLoot.Proto); - break; - } + await SpawnDungeonLoot(lootProto, mapUid); + } + catch (Exception e) + { + _sawmill.Error($"Failed to spawn guaranteed loot {lootProto.ID}: {e}"); } } @@ -319,4 +323,32 @@ public sealed class SpawnSalvageMissionJob : Job // 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(gridUid, out var biome)) + { + _biome.AddMarkerLayer(gridUid, biome, biomeLoot.Prototype); + } + } + break; + case BiomeTemplateLoot biomeLoot: + { + if (_entManager.TryGetComponent(gridUid, out var biome)) + { + _biome.AddTemplate(gridUid, biome, "Loot", _prototypeManager.Index(biomeLoot.Prototype), i); + } + } + break; + } + } + } } diff --git a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs index d2a3157232..aa1c2e6dff 100644 --- a/Content.Server/Shuttles/Systems/ArrivalsSystem.cs +++ b/Content.Server/Shuttles/Systems/ArrivalsSystem.cs @@ -6,7 +6,6 @@ using Content.Server.DeviceNetwork.Systems; 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; @@ -23,6 +22,7 @@ using Content.Shared.DeviceNetwork.Components; 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; @@ -82,11 +82,11 @@ public sealed class ArrivalsSystem : EntitySystem /// private const float RoundStartFTLDuration = 10f; - private readonly List _arrivalsBiomeOptions = new() + private readonly List> _arrivalsBiomeOptions = new() { - "BiomeGrasslands", - "BiomeLowDesert", - "BiomeSnow", + "Grasslands", + "LowDesert", + "Snow", }; public override void Initialize() diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs index 9e9bcb6c42..13e13bf8f3 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs @@ -1,14 +1,12 @@ 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; @@ -958,7 +956,6 @@ public sealed partial class ShuttleSystem var transform = _physics.GetRelativePhysicsTransform((uid, xform), xform.MapUid.Value); var aabbs = new List(manager.Fixtures.Count); var tileSet = new List<(Vector2i, Tile)>(); - TryComp(xform.MapUid.Value, out DecalGridComponent? decalGrid); foreach (var fixture in manager.Fixtures.Values) { @@ -972,15 +969,9 @@ public sealed partial class ShuttleSystem aabb = aabb.Enlarged(0.2f); aabbs.Add(aabb); - if (decalGrid != null) - { - foreach (var decal in _decals.GetDecalsIntersecting(xform.MapUid.Value, aabb)) - { - _decals.RemoveDecal(xform.MapUid.Value, decal.Index, decalGrid); - } - } - + // Handle clearing biome stuff as relevant. 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. diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 525e16ae1a..cea7fbfc09 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -1,7 +1,6 @@ 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; @@ -42,12 +41,10 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [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!; diff --git a/Content.Server/Station/Components/StationBiomeComponent.cs b/Content.Server/Station/Components/StationBiomeComponent.cs index a2105d6cef..0eb64aaff8 100644 --- a/Content.Server/Station/Components/StationBiomeComponent.cs +++ b/Content.Server/Station/Components/StationBiomeComponent.cs @@ -1,4 +1,5 @@ using Content.Server.Station.Systems; +using Content.Shared.Parallax.Biomes; using Robust.Shared.Prototypes; namespace Content.Server.Station.Components; @@ -10,7 +11,7 @@ namespace Content.Server.Station.Components; public sealed partial class StationBiomeComponent : Component { [DataField(required: true)] - public EntProtoId Biome = "BiomeGrasslands"; + public ProtoId Biome = "Grasslands"; // If null, its random [DataField] diff --git a/Content.Server/Station/Systems/StationBiomeSystem.cs b/Content.Server/Station/Systems/StationBiomeSystem.cs index c0777f6052..c12e2f47e4 100644 --- a/Content.Server/Station/Systems/StationBiomeSystem.cs +++ b/Content.Server/Station/Systems/StationBiomeSystem.cs @@ -1,5 +1,4 @@ using Content.Server.Parallax; -using Content.Server.Procedural; using Content.Server.Station.Components; using Content.Server.Station.Events; using Robust.Shared.Prototypes; diff --git a/Content.Server/Tabletop/TabletopSystem.cs b/Content.Server/Tabletop/TabletopSystem.cs index 3c3065c95a..e771add0e4 100644 --- a/Content.Server/Tabletop/TabletopSystem.cs +++ b/Content.Server/Tabletop/TabletopSystem.cs @@ -191,7 +191,7 @@ namespace Content.Server.Tabletop if (!TryComp(uid, out ActorComponent? actor)) { RemComp(uid); - continue; + return; } if (actor.PlayerSession.Status != SessionStatus.InGame || !CanSeeTable(uid, gamer.Tabletop)) diff --git a/Content.Shared/CCVar/CCVars.Biome.cs b/Content.Shared/CCVar/CCVars.Biome.cs deleted file mode 100644 index 13c6cd548e..0000000000 --- a/Content.Shared/CCVar/CCVars.Biome.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Robust.Shared.Configuration; - -namespace Content.Shared.CCVar; - -public sealed partial class CCVars -{ - /// - /// Load range for biomes. Set this higher than PVS so server can have some time to load in before the client arrives. - /// - public static readonly CVarDef BiomeLoadRange = - CVarDef.Create("biome.load_range", 20f, CVar.SERVERONLY); - - /// - /// Time allocation (ms) for how long biomes are allowed to load. - /// - public static readonly CVarDef BiomeLoadTime = - CVarDef.Create("biome.load_time", 0.03f, CVar.SERVERONLY); -} diff --git a/Content.Shared/Parallax/Biomes/BiomeComponent.cs b/Content.Shared/Parallax/Biomes/BiomeComponent.cs new file mode 100644 index 0000000000..af8eb88683 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/BiomeComponent.cs @@ -0,0 +1,87 @@ +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 +{ + /// + /// Do we load / deload. + /// + [DataField, ViewVariables(VVAccess.ReadWrite), Access(Other = AccessPermissions.ReadWriteExecute)] + public bool Enabled = true; + + [ViewVariables(VVAccess.ReadWrite), DataField("seed")] + [AutoNetworkedField] + public int Seed = -1; + + /// + /// The underlying entity, decal, and tile layers for the biome. + /// + [DataField("layers")] + [AutoNetworkedField] + public List Layers = new(); + + /// + /// Templates to use for . + /// If this is set on mapinit, it will fill out layers automatically. + /// If not set, use BiomeSystem to do it. + /// Prototype reloading will also use this. + /// + [DataField] + public ProtoId? Template; + + /// + /// 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] + /// + [DataField("modifiedTiles")] + public Dictionary> ModifiedTiles = new(); + + /// + /// Decals that have been loaded as a part of this biome. + /// + [DataField("decals")] + public Dictionary> LoadedDecals = new(); + + [DataField("entities")] + public Dictionary> LoadedEntities = new(); + + /// + /// Currently active chunks + /// + [DataField("loadedChunks")] + public HashSet LoadedChunks = new(); + + #region Markers + + /// + /// Work out entire marker tiles in advance but only load the entities when in range. + /// + [DataField("pendingMarkers")] + public Dictionary>> PendingMarkers = new(); + + /// + /// Track what markers we've loaded already to avoid double-loading. + /// + [DataField("loadedMarkers", customTypeSerializer:typeof(PrototypeIdDictionarySerializer, BiomeMarkerLayerPrototype>))] + public Dictionary> LoadedMarkers = new(); + + [DataField] + public HashSet> MarkerLayers = new(); + + /// + /// One-tick forcing of marker layers to bulldoze any entities in the way. + /// + [DataField] + public HashSet> ForcedMarkerLayers = new(); + + #endregion +} diff --git a/Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs b/Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs new file mode 100644 index 0000000000..437ead63a7 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/BiomeTemplatePrototype.cs @@ -0,0 +1,16 @@ +using Content.Shared.Parallax.Biomes.Layers; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Parallax.Biomes; + +/// +/// A preset group of biome layers to be used for a +/// +[Prototype] +public sealed partial class BiomeTemplatePrototype : IPrototype +{ + [IdDataField] public string ID { get; private set; } = default!; + + [DataField("layers")] + public List Layers = new(); +} diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs new file mode 100644 index 0000000000..5e31e513a9 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Layers/BiomeDecalLayer.cs @@ -0,0 +1,34 @@ +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 +{ + /// + [DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer))] + public List AllowedTiles { get; private set; } = new(); + + /// + /// Divide each tile up by this amount. + /// + [DataField("divisions")] + public float Divisions = 1f; + + [DataField("noise")] + public FastNoiseLite Noise { get; private set; } = new(0); + + /// + [DataField("threshold")] + public float Threshold { get; private set; } = 0.8f; + + /// + [DataField("invert")] public bool Invert { get; private set; } = false; + + [DataField("decals", required: true, customTypeSerializer:typeof(PrototypeIdListSerializer))] + public List Decals = new(); +} diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs new file mode 100644 index 0000000000..2beeba6e03 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Layers/BiomeDummyLayer.cs @@ -0,0 +1,18 @@ +using Robust.Shared.Noise; +using Robust.Shared.Serialization; + +namespace Content.Shared.Parallax.Biomes.Layers; + +/// +/// 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. +/// +[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; } +} diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs new file mode 100644 index 0000000000..21ffdd96e5 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Layers/BiomeEntityLayer.cs @@ -0,0 +1,27 @@ +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 +{ + /// + [DataField("allowedTiles", customTypeSerializer:typeof(PrototypeIdListSerializer))] + public List AllowedTiles { get; private set; } = new(); + + [DataField("noise")] public FastNoiseLite Noise { get; private set; } = new(0); + + /// + [DataField("threshold")] + public float Threshold { get; private set; } = 0.5f; + + /// + [DataField("invert")] public bool Invert { get; private set; } = false; + + [DataField("entities", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer))] + public List Entities = new(); +} diff --git a/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs new file mode 100644 index 0000000000..51231405a8 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Layers/BiomeMetaLayer.cs @@ -0,0 +1,27 @@ +using Robust.Shared.Noise; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Parallax.Biomes.Layers; + +/// +/// Contains more biome layers recursively via a biome template. +/// Can be used for sub-biomes. +/// +[Serializable, NetSerializable] +public sealed partial class BiomeMetaLayer : IBiomeLayer +{ + [DataField("noise")] + public FastNoiseLite Noise { get; private set; } = new(0); + + /// + [DataField("threshold")] + public float Threshold { get; private set; } = -1f; + + /// + [DataField("invert")] + public bool Invert { get; private set; } + + [DataField("template", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Template = string.Empty; +} diff --git a/Content.Shared/Procedural/DungeonLayers/SampleTileDunGen.cs b/Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs similarity index 66% rename from Content.Shared/Procedural/DungeonLayers/SampleTileDunGen.cs rename to Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs index 6d23d201f5..9dee35da4e 100644 --- a/Content.Shared/Procedural/DungeonLayers/SampleTileDunGen.cs +++ b/Content.Shared/Parallax/Biomes/Layers/BiomeTileLayer.cs @@ -2,26 +2,20 @@ using Content.Shared.Maps; using Robust.Shared.Noise; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Shared.Procedural.DungeonLayers; +namespace Content.Shared.Parallax.Biomes.Layers; -/// -/// Samples noise and spawns the specified tile in the dungeon area. -/// [Serializable, NetSerializable] -public sealed partial class SampleTileDunGen : IDunGenLayer +public sealed partial class BiomeTileLayer : IBiomeLayer { - /// - /// Reserve any tiles we update. - /// - [DataField] - public bool ReserveTiles = true; - [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; /// diff --git a/Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs b/Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs new file mode 100644 index 0000000000..3b1ad5c76c --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Layers/IBiomeLayer.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Noise; + +namespace Content.Shared.Parallax.Biomes.Layers; + +[ImplicitDataDefinitionForInheritors] +public partial interface IBiomeLayer +{ + /// + /// Seed is used an offset from the relevant BiomeComponent's seed. + /// + FastNoiseLite Noise { get; } + + /// + /// Threshold for this layer to be present. If set to 0 forces it for every tile. + /// + float Threshold { get; } + + /// + /// Is the thresold inverted so we need to be lower than it. + /// + public bool Invert { get; } +} diff --git a/Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs b/Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs new file mode 100644 index 0000000000..e04db913b7 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Layers/IBiomeWorldLayer.cs @@ -0,0 +1,12 @@ +namespace Content.Shared.Parallax.Biomes.Layers; + +/// +/// Handles actual objects such as decals and entities. +/// +public partial interface IBiomeWorldLayer : IBiomeLayer +{ + /// + /// What tiles we're allowed to spawn on, real or biome. + /// + List AllowedTiles { get; } +} diff --git a/Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs b/Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs new file mode 100644 index 0000000000..fbc3a04eb4 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Markers/BiomeMarkerLayerPrototype.cs @@ -0,0 +1,53 @@ +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Parallax.Biomes.Markers; + +/// +/// Spawns entities inside of the specified area with the minimum specified radius. +/// +[Prototype] +public sealed partial class BiomeMarkerLayerPrototype : IBiomeMarkerLayer +{ + [IdDataField] public string ID { get; private set; } = default!; + + /// + /// Checks for the relevant entity for the tile before spawning. Useful for substituting walls with ore veins for example. + /// + [DataField] + public Dictionary EntityMask { get; private set; } = new(); + + /// + /// Default prototype to spawn. If null will fall back to entity mask. + /// + [DataField] + public string? Prototype { get; private set; } + + /// + /// Minimum radius between 2 points + /// + [DataField("radius")] + public float Radius = 32f; + + /// + /// Maximum amount of group spawns + /// + [DataField("maxCount")] + public int MaxCount = int.MaxValue; + + /// + /// Minimum entities to spawn in one group. + /// + [DataField] + public int MinGroupSize = 1; + + /// + /// Maximum entities to spawn in one group. + /// + [DataField] + public int MaxGroupSize = 1; + + /// + [DataField("size")] + public int Size { get; private set; } = 128; +} diff --git a/Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs b/Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs new file mode 100644 index 0000000000..de2913bb09 --- /dev/null +++ b/Content.Shared/Parallax/Biomes/Markers/IBiomeMarkerLayer.cs @@ -0,0 +1,22 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Parallax.Biomes.Markers; + +/// +/// 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. +/// +public interface IBiomeMarkerLayer : IPrototype +{ + /// + /// Biome template to use as a mask for this layer. + /// + public Dictionary EntityMask { get; } + + public string? Prototype { get; } + + /// + /// How large the pre-generated points area is. + /// + public int Size { get; } +} diff --git a/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs b/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs new file mode 100644 index 0000000000..a5238e8c6e --- /dev/null +++ b/Content.Shared/Parallax/Biomes/SharedBiomeSystem.cs @@ -0,0 +1,386 @@ +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(List 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(uid, out var biome)) + { + tile = null; + return false; + } + + return TryGetBiomeTile(indices, biome.Layers, biome.Seed, (uid, grid), out tile); + } + + /// + /// Tries to get the tile, real or otherwise, for the specified indices. + /// + public bool TryGetBiomeTile(Vector2i indices, List layers, int seed, Entity? 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); + } + + /// + /// Tries to get the tile, real or otherwise, for the specified indices. + /// + [Obsolete("Use the Entity? overload")] + public bool TryGetBiomeTile(Vector2i indices, List layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile) + { + return TryGetBiomeTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile); + } + + /// + /// Gets the underlying biome tile, ignoring any existing tile that may be there. + /// + public bool TryGetTile(Vector2i indices, List layers, int seed, Entity? 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(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; + } + + /// + /// Gets the underlying biome tile, ignoring any existing tile that may be there. + /// + [Obsolete("Use the Entity? overload")] + public bool TryGetTile(Vector2i indices, List layers, int seed, MapGridComponent? grid, [NotNullWhen(true)] out Tile? tile) + { + return TryGetTile(indices, layers, seed, grid == null ? null : (grid.Owner, grid), out tile); + } + + /// + /// Gets the underlying biome tile, ignoring any existing tile that may be there. + /// + private bool TryGetTile(Vector2i indices, FastNoiseLite noise, bool invert, float threshold, ContentTileDefinition tileDef, List? 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; + } + + /// + /// Tries to get the relevant entity for this tile. + /// + public bool TryGetEntity(Vector2i indices, BiomeComponent component, Entity? 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); + } + + /// + /// Tries to get the relevant entity for this tile. + /// + [Obsolete("Use the Entity? 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 layers, Tile tileRef, int seed, Entity? 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(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? overload")] + public bool TryGetEntity(Vector2i indices, List 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); + } + + /// + /// Tries to get the relevant decals for this tile. + /// + public bool TryGetDecals(Vector2i indices, List layers, int seed, Entity? 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(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; + } + + /// + /// Tries to get the relevant decals for this tile. + /// + [Obsolete("Use the Entity? overload")] + public bool TryGetDecals(Vector2i indices, List 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; + } +} diff --git a/Content.Shared/Procedural/Components/BiomeComponent.cs b/Content.Shared/Procedural/Components/BiomeComponent.cs deleted file mode 100644 index f79a31c414..0000000000 --- a/Content.Shared/Procedural/Components/BiomeComponent.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.Procedural.Components; - -/// -/// A layer inside of -/// -[DataRecord] -public sealed record BiomeMetaLayer -{ - /// - /// Chunk dimensions for this meta layer. Will try to infer it from the first layer of the dungeon if null. - /// - [DataField] - public int? Size; - - /// - /// Meta layers that this one requires to be loaded first. - /// Will ensure all of the chunks for our corresponding area are loaded. - /// - public List? DependsOn; - - /// - /// Can this layer be unloaded if no one is in range. - /// - public bool CanUnload = true; - - /// - /// Dungeon config to load inside the specified area. - /// - [DataField(required: true)] - public ProtoId Dungeon = new(); -} - -[RegisterComponent] -public sealed partial class BiomeComponent : Component -{ - /// - /// Can we load / unload chunks. - /// - [DataField] - public bool Enabled = true; - - /// - /// Areas queued for preloading. Will add these during and then flag as modified so they retain. - /// - [DataField] - public List PreloadAreas = new(); - - /// - /// Is there currently a job that's loading. - /// - public bool Loading = false; - - [DataField] - public int Seed; - - /// - /// Layer key and associated data. - /// - [DataField(required: true)] - public Dictionary Layers = new(); - - /// - /// Layer removals that are pending. - /// - [DataField] - public List PendingRemovals = new(); - - /// - /// Data that is currently loaded. - /// - [DataField] - public Dictionary> LoadedData = new(); - - /// - /// Flag modified tiles so we don't try and unload / reload them. - /// - [DataField] - public HashSet ModifiedTiles = new(); - - /// - /// Bounds loaded by players for this tick. - /// - public List LoadedBounds = new(); -} diff --git a/Content.Shared/Procedural/Components/BiomeForceUnloadComponent.cs b/Content.Shared/Procedural/Components/BiomeForceUnloadComponent.cs deleted file mode 100644 index 215a8f345d..0000000000 --- a/Content.Shared/Procedural/Components/BiomeForceUnloadComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Procedural.Components; - -/// -/// Will forcibly unload an entity no matter what. Useful if you have consistent entities that will never be default or the likes. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class BiomeForceUnloadComponent : Component; diff --git a/Content.Shared/Procedural/Distance/DunGenDistanceSquared.cs b/Content.Shared/Procedural/Distance/DunGenDistanceSquared.cs deleted file mode 100644 index 813f5fc1d4..0000000000 --- a/Content.Shared/Procedural/Distance/DunGenDistanceSquared.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Content.Shared.Procedural.Distance; - -public sealed partial class DunGenDistanceSquared : IDunGenDistance -{ - [DataField] - public float BlendWeight { get; set; } = 0.50f; -} diff --git a/Content.Shared/Procedural/DungeonConfig.cs b/Content.Shared/Procedural/DungeonConfig.cs index 56131e5b28..7c84b1a6a3 100644 --- a/Content.Shared/Procedural/DungeonConfig.cs +++ b/Content.Shared/Procedural/DungeonConfig.cs @@ -12,18 +12,11 @@ public partial class DungeonConfig public List Layers = new(); /// - /// Should we reserve the tiles generated by this config so no other layers at the same level can spawn on this tile? + /// Should we reserve the tiles generated by this config so no other dungeons can spawn on it within the same job? /// [DataField] public bool ReserveTiles; - /// - /// 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. - /// - [DataField] - public bool ReturnReserved = true; - /// /// Minimum times to run the config. /// diff --git a/Content.Shared/Procedural/DungeonData.cs b/Content.Shared/Procedural/DungeonData.cs deleted file mode 100644 index a0209f91a6..0000000000 --- a/Content.Shared/Procedural/DungeonData.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Linq; -using System.Numerics; -using Robust.Shared.Map; - -namespace Content.Shared.Procedural; - -/// -/// Contains the loaded data for a dungeon. -/// -[DataDefinition] -public sealed partial class DungeonData -{ - [DataField] - public Dictionary Decals = new(); - - [DataField] - public Dictionary Entities = new(); - - [DataField] - public Dictionary 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; - } - } -} diff --git a/Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs deleted file mode 100644 index b48a1b3fd6..0000000000 --- a/Content.Shared/Procedural/DungeonGenerators/ChunkDunGen.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Robust.Shared.Noise; - -namespace Content.Shared.Procedural.DungeonGenerators; - -/// -/// Turns a chunked area into a dungeon for layer purposes. Assumes the position is the BL origin. -/// -public sealed partial class ChunkDunGen : IDunGenLayer -{ - [DataField] - public int Size = 16; - - /// - /// Noise to apply for each tile conditionally. - /// - [DataField] - public FastNoiseLite? Noise; - - /// - /// Threshold for noise. Does nothing if is null. - /// - [DataField] - public float Threshold = -1f; -} diff --git a/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs index 2ece880851..e9a5181f8d 100644 --- a/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs +++ b/Content.Shared/Procedural/DungeonGenerators/ExteriorDunGen.cs @@ -10,10 +10,4 @@ public sealed partial class ExteriorDunGen : IDunGenLayer { [DataField(required: true)] public ProtoId Proto; - - /// - /// Minimum and maximum penetration. - /// - [DataField] - public Vector2i Penetration = new Vector2i(5, 15); } diff --git a/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs b/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs index c3f7ae3f33..89a4ab216a 100644 --- a/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs +++ b/Content.Shared/Procedural/DungeonGenerators/PrototypeDunGen.cs @@ -14,12 +14,6 @@ public sealed partial class PrototypeDunGen : IDunGenLayer [DataField] public DungeonInheritance InheritDungeons = DungeonInheritance.None; - /// - /// Should we pass in the current level's reserved tiles to the prototype. - /// - [DataField] - public ReservedInheritance InheritReserved = ReservedInheritance.All; - [DataField(required: true)] public ProtoId Proto; } @@ -41,16 +35,3 @@ public enum DungeonInheritance : byte /// All, } - -public enum ReservedInheritance : byte -{ - /// - /// Don't inherit any reserved tiles. - /// - None, - - /// - /// Inherit reserved tiles, - /// - All, -} diff --git a/Content.Shared/Procedural/DungeonLayers/FillGridDunGen.cs b/Content.Shared/Procedural/DungeonLayers/FillGridDunGen.cs index e2506298fd..363de0a511 100644 --- a/Content.Shared/Procedural/DungeonLayers/FillGridDunGen.cs +++ b/Content.Shared/Procedural/DungeonLayers/FillGridDunGen.cs @@ -1,7 +1,4 @@ -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; @@ -9,6 +6,10 @@ namespace Content.Shared.Procedural.DungeonLayers; /// /// Fills unreserved tiles with the specified entity prototype. /// +/// +/// DungeonData keys are: +/// - Fill +/// public sealed partial class FillGridDunGen : IDunGenLayer { /// @@ -19,29 +20,4 @@ public sealed partial class FillGridDunGen : IDunGenLayer [DataField(required: true)] public EntProtoId Entity; - - #region Noise - - [DataField] - public bool Invert; - - /// - /// Optionally don't spawn entities if the noise value matches. - /// - [DataField] - public FastNoiseLite? ReservedNoise; - - /// - /// Noise threshold for . Does nothing without it. - /// - [DataField] - public float Threshold = -1f; - - [DataField] - public IDunGenDistance? DistanceConfig; - - [DataField] - public Vector2 Size; - - #endregion } diff --git a/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs b/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs index 1b754d3778..5525341eb9 100644 --- a/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs +++ b/Content.Shared/Procedural/DungeonLayers/MobsDunGen.cs @@ -1,8 +1,10 @@ using Content.Shared.EntityTable; +using Content.Shared.Storage; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.DungeonLayers; + /// /// Spawns mobs inside of the dungeon randomly. /// diff --git a/Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs b/Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs deleted file mode 100644 index fbb174dc14..0000000000 --- a/Content.Shared/Procedural/DungeonLayers/RoofDunGen.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Robust.Shared.Noise; - -namespace Content.Shared.Procedural.DungeonLayers; - -/// -/// Sets tiles as rooved. -/// -public sealed partial class RoofDunGen : IDunGenLayer -{ - [DataField] - public float Threshold = -1f; - - [DataField] - public FastNoiseLite? Noise; -} diff --git a/Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs b/Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs deleted file mode 100644 index 616e643b52..0000000000 --- a/Content.Shared/Procedural/DungeonLayers/SampleDecalDunGen.cs +++ /dev/null @@ -1,36 +0,0 @@ -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 -{ - /// - /// Reserve any tiles we update. - /// - [DataField] - public bool ReserveTiles = true; - - [DataField(customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List AllowedTiles { get; private set; } = new(); - - /// - /// Divide each tile up by this amount. - /// - [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> Decals = new(); -} diff --git a/Content.Shared/Procedural/DungeonLayers/SampleEntityDunGen.cs b/Content.Shared/Procedural/DungeonLayers/SampleEntityDunGen.cs deleted file mode 100644 index 2daf7e7aa7..0000000000 --- a/Content.Shared/Procedural/DungeonLayers/SampleEntityDunGen.cs +++ /dev/null @@ -1,31 +0,0 @@ -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; - -/// -/// Samples noise to spawn the specified entity -/// -public sealed partial class SampleEntityDunGen : IDunGenLayer -{ - /// - /// Reserve any tiles we update. - /// - [DataField] - public bool ReserveTiles = true; - - [DataField(customTypeSerializer:typeof(PrototypeIdListSerializer))] - public List 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 Entities = new(); -} diff --git a/Content.Shared/Procedural/Loot/BiomeLoot.cs b/Content.Shared/Procedural/Loot/BiomeLoot.cs deleted file mode 100644 index 1330043493..0000000000 --- a/Content.Shared/Procedural/Loot/BiomeLoot.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Robust.Shared.Prototypes; - -namespace Content.Shared.Procedural.Loot; - -/// -/// Adds the prototype as a biome layer. -/// -public sealed partial class BiomeLoot : IDungeonLoot -{ - [DataField(required: true)] - public ProtoId Proto; -} diff --git a/Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs b/Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs new file mode 100644 index 0000000000..2eda4b059c --- /dev/null +++ b/Content.Shared/Procedural/Loot/BiomeMarkerLoot.cs @@ -0,0 +1,15 @@ +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; + +/// +/// Adds a biome marker layer for dungeon loot. +/// +public sealed partial class BiomeMarkerLoot : IDungeonLoot +{ + [DataField("proto", required: true)] + public ProtoId Prototype = new(); +} diff --git a/Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs b/Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs new file mode 100644 index 0000000000..e4968b6e42 --- /dev/null +++ b/Content.Shared/Procedural/Loot/BiomeTemplateLoot.cs @@ -0,0 +1,14 @@ +using Content.Shared.Parallax.Biomes; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Shared.Procedural.Loot; + +/// +/// Adds a biome template layer for dungeon loot. +/// +public sealed partial class BiomeTemplateLoot : IDungeonLoot +{ + [DataField("proto", required: true)] + public ProtoId Prototype = string.Empty; +} diff --git a/Content.Shared/Procedural/DungeonLayers/AutoCablingDunGen.cs b/Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/AutoCablingDunGen.cs rename to Content.Shared/Procedural/PostGeneration/AutoCablingDunGen.cs diff --git a/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs new file mode 100644 index 0000000000..e21e582211 --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/BiomeDunGen.cs @@ -0,0 +1,21 @@ +using Content.Shared.Maps; +using Content.Shared.Parallax.Biomes; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Generates a biome on top of valid tiles, then removes the biome when done. +/// Only works if no existing biome is present. +/// +public sealed partial class BiomeDunGen : IDunGenLayer +{ + [DataField(required: true)] + public ProtoId BiomeTemplate; + + /// + /// creates a biome only on the specified tiles + /// + [DataField] + public HashSet>? TileMask; +} diff --git a/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs b/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs new file mode 100644 index 0000000000..af5d7c5d8f --- /dev/null +++ b/Content.Shared/Procedural/PostGeneration/BiomeMarkerLayerDunGen.cs @@ -0,0 +1,19 @@ +using Content.Shared.Random; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Procedural.PostGeneration; + +/// +/// Spawns the specified marker layer on top of the dungeon rooms. +/// +public sealed partial class BiomeMarkerLayerDunGen : IDunGenLayer +{ + /// + /// How many times to spawn marker layers; can duplicate. + /// + [DataField] + public int Count = 6; + + [DataField(required: true)] + public ProtoId MarkerTemplate; +} diff --git a/Content.Shared/Procedural/DungeonLayers/BoundaryWallDunGen.cs b/Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/BoundaryWallDunGen.cs rename to Content.Shared/Procedural/PostGeneration/BoundaryWallDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/CornerClutterDunGen.cs b/Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/CornerClutterDunGen.cs rename to Content.Shared/Procedural/PostGeneration/CornerClutterDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/CorridorClutterDunGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/CorridorClutterDunGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorClutterDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/CorridorDecalSkirtingDunGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/CorridorDecalSkirtingDunGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorDecalSkirtingDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/CorridorDunGen.cs b/Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/CorridorDunGen.cs rename to Content.Shared/Procedural/PostGeneration/CorridorDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/DungeonEntranceDunGen.cs b/Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/DungeonEntranceDunGen.cs rename to Content.Shared/Procedural/PostGeneration/DungeonEntranceDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/EntranceFlankDunGen.cs b/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs similarity index 93% rename from Content.Shared/Procedural/DungeonLayers/EntranceFlankDunGen.cs rename to Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs index cd6cf169fc..f9be6caf6a 100644 --- a/Content.Shared/Procedural/DungeonLayers/EntranceFlankDunGen.cs +++ b/Content.Shared/Procedural/PostGeneration/EntranceFlankDunGen.cs @@ -1,6 +1,5 @@ using Content.Shared.EntityTable; using Content.Shared.Maps; -using Content.Shared.Storage; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.PostGeneration; diff --git a/Content.Shared/Procedural/DungeonLayers/ExternalWindowDunGen.cs b/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs similarity index 94% rename from Content.Shared/Procedural/DungeonLayers/ExternalWindowDunGen.cs rename to Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs index 30b8302263..fc992ea7b8 100644 --- a/Content.Shared/Procedural/DungeonLayers/ExternalWindowDunGen.cs +++ b/Content.Shared/Procedural/PostGeneration/ExternalWindowDunGen.cs @@ -1,5 +1,6 @@ using Content.Shared.EntityTable; using Content.Shared.Maps; +using Content.Shared.Storage; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.PostGeneration; diff --git a/Content.Shared/Procedural/DungeonLayers/InternalWindowDunGen.cs b/Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/InternalWindowDunGen.cs rename to Content.Shared/Procedural/PostGeneration/InternalWindowDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/JunctionDunGen.cs b/Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/JunctionDunGen.cs rename to Content.Shared/Procedural/PostGeneration/JunctionDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/MiddleConnectionDunGen.cs b/Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/MiddleConnectionDunGen.cs rename to Content.Shared/Procedural/PostGeneration/MiddleConnectionDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/RoomEntranceDunGen.cs b/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs similarity index 93% rename from Content.Shared/Procedural/DungeonLayers/RoomEntranceDunGen.cs rename to Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs index f0fea57588..1436f7473d 100644 --- a/Content.Shared/Procedural/DungeonLayers/RoomEntranceDunGen.cs +++ b/Content.Shared/Procedural/PostGeneration/RoomEntranceDunGen.cs @@ -1,5 +1,6 @@ using Content.Shared.EntityTable; using Content.Shared.Maps; +using Content.Shared.Storage; using Robust.Shared.Prototypes; namespace Content.Shared.Procedural.PostGeneration; diff --git a/Content.Shared/Procedural/DungeonLayers/SplineDungeonConnectorDunGen.cs b/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs similarity index 83% rename from Content.Shared/Procedural/DungeonLayers/SplineDungeonConnectorDunGen.cs rename to Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs index 4a4bc0b36a..d2f5a2126a 100644 --- a/Content.Shared/Procedural/DungeonLayers/SplineDungeonConnectorDunGen.cs +++ b/Content.Shared/Procedural/PostGeneration/SplineDungeonConnectorDunGen.cs @@ -1,7 +1,7 @@ using Content.Shared.Maps; using Robust.Shared.Prototypes; -namespace Content.Shared.Procedural.DungeonLayers; +namespace Content.Shared.Procedural.PostGeneration; /// /// Connects dungeons via points that get subdivided. @@ -18,11 +18,11 @@ public sealed partial class SplineDungeonConnectorDunGen : IDunGenLayer /// Will divide the distance between the start and end points so that no subdivision is more than these metres away. /// [DataField] - public int DivisionDistance = 20; + public int DivisionDistance = 10; /// /// How much each subdivision can vary from the middle. /// [DataField] - public float VarianceMax = 0.15f; + public float VarianceMax = 0.35f; } diff --git a/Content.Shared/Procedural/DungeonLayers/WallMountDunGen.cs b/Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/WallMountDunGen.cs rename to Content.Shared/Procedural/PostGeneration/WallMountDunGen.cs diff --git a/Content.Shared/Procedural/DungeonLayers/WormCorridorDunGen.cs b/Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs similarity index 100% rename from Content.Shared/Procedural/DungeonLayers/WormCorridorDunGen.cs rename to Content.Shared/Procedural/PostGeneration/WormCorridorDunGen.cs diff --git a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs index 10106cd666..e84223ed1f 100644 --- a/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs +++ b/Content.Shared/Salvage/Expeditions/Modifiers/SalvageBiomeModPrototype.cs @@ -1,4 +1,6 @@ +using Content.Shared.Parallax.Biomes; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Salvage.Expeditions.Modifiers; @@ -24,6 +26,6 @@ public sealed partial class SalvageBiomeModPrototype : IPrototype, ISalvageMod [DataField("weather")] public bool Weather = true; - [DataField("biome", required: true)] - public EntProtoId? BiomePrototype; + [DataField("biome", required: true, customTypeSerializer:typeof(PrototypeIdSerializer))] + public string? BiomePrototype; } diff --git a/Resources/Locale/en-US/procedural/biome.ftl b/Resources/Locale/en-US/procedural/biome.ftl index 7c2a88a696..d24ec7d72e 100644 --- a/Resources/Locale/en-US/procedural/biome.ftl +++ b/Resources/Locale/en-US/procedural/biome.ftl @@ -1,2 +1,6 @@ +cmd-biome_clear-desc = Clears a biome entirely +cmd-biome_clear-help = biome_clear cmd-biome_addlayer-desc = Adds another biome layer cmd-biome_addlayer-help = biome_addlayer [seed offset] +cmd-biome_addmarkerlayer-desc = Adds another biome marker layer +cmd-biome_addmarkerlayer-help = biome_addmarkerlayer diff --git a/Resources/Prototypes/Entities/Tiles/water.yml b/Resources/Prototypes/Entities/Tiles/water.yml index 357bc9f98c..c488df231b 100644 --- a/Resources/Prototypes/Entities/Tiles/water.yml +++ b/Resources/Prototypes/Entities/Tiles/water.yml @@ -17,14 +17,10 @@ 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 diff --git a/Resources/Prototypes/Procedural/Magnet/asteroid.yml b/Resources/Prototypes/Procedural/Magnet/asteroid.yml index f838104f3c..a77a6b287b 100644 --- a/Resources/Prototypes/Procedural/Magnet/asteroid.yml +++ b/Resources/Prototypes/Procedural/Magnet/asteroid.yml @@ -18,9 +18,8 @@ lacunarity: 2 # Generate biome - - !type:PrototypeDunGen - proto: Asteroid - inheritDungeons: All + - !type:BiomeDunGen + biomeTemplate: Asteroid - !type:OreDunGen replacement: AsteroidRock entity: AsteroidRockGibtonite @@ -48,9 +47,8 @@ lacunarity: 2 # Generate biome - - !type:PrototypeDunGen - proto: Asteroid - inheritDungeons: All + - !type:BiomeDunGen + biomeTemplate: Asteroid - !type:OreDunGen replacement: AsteroidRock entity: AsteroidRockGibtonite @@ -78,9 +76,8 @@ cellularDistanceFunction: Euclidean # Generate biome - - !type:PrototypeDunGen - proto: Asteroid - inheritDungeons: All + - !type:BiomeDunGen + biomeTemplate: Asteroid - !type:OreDunGen replacement: AsteroidRock entity: AsteroidRockGibtonite @@ -107,9 +104,8 @@ lacunarity: 2 # Generate biome - - !type:PrototypeDunGen - proto: Asteroid - inheritDungeons: All + - !type:BiomeDunGen + biomeTemplate: Asteroid - !type:OreDunGen replacement: AsteroidRock entity: AsteroidRockGibtonite diff --git a/Resources/Prototypes/Procedural/Magnet/space_debris.yml b/Resources/Prototypes/Procedural/Magnet/space_debris.yml index a9bd823e6b..11c1afdabe 100644 --- a/Resources/Prototypes/Procedural/Magnet/space_debris.yml +++ b/Resources/Prototypes/Procedural/Magnet/space_debris.yml @@ -36,9 +36,8 @@ gain: 0.5 # Generate biome - - !type:PrototypeDunGen - proto: SpaceDebris - inheritDungeons: All + - !type:BiomeDunGen + biomeTemplate: SpaceDebris - type: dungeonConfig id: ChunkDebrisSmall @@ -78,6 +77,5 @@ gain: 0.5 # Generate biome - - !type:PrototypeDunGen - proto: SpaceDebris - inheritDungeons: All + - !type:BiomeDunGen + biomeTemplate: SpaceDebris diff --git a/Resources/Prototypes/Procedural/Magnet/space_debris_templates.yml b/Resources/Prototypes/Procedural/Magnet/space_debris_templates.yml index e181f89268..47aa47fce4 100644 --- a/Resources/Prototypes/Procedural/Magnet/space_debris_templates.yml +++ b/Resources/Prototypes/Procedural/Magnet/space_debris_templates.yml @@ -1,8 +1,8 @@ # Asteroid -- type: dungeonConfig +- type: biomeTemplate id: SpaceDebris layers: - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.20 noise: seed: 0 @@ -23,7 +23,7 @@ - WallReinforced - WallSolid - WallSolid - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.5 noise: seed: 0 @@ -41,7 +41,7 @@ - Grille - Grille - GrilleBroken - - !type:SampleDecalDunGen + - !type:BiomeDecalLayer allowedTiles: - FloorSteel threshold: -0.5 @@ -56,7 +56,7 @@ - DirtMedium - DirtMedium - DirtLight - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.45 noise: seed: 1 @@ -71,7 +71,7 @@ - FloorSteel entities: - SalvageSpawnerStructuresVarious - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer allowedTiles: - FloorSteel - Plating @@ -85,7 +85,7 @@ - SalvageSpawnerScrapCommon - SalvageSpawnerScrapCommon - SalvageSpawnerScrapCommon75 - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer allowedTiles: - FloorSteel - Plating @@ -97,7 +97,7 @@ - SalvageSpawnerTreasure - SalvageSpawnerTreasure - SalvageSpawnerTreasureValuable - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer allowedTiles: - FloorSteel - Plating diff --git a/Resources/Prototypes/Procedural/biome_markers.yml b/Resources/Prototypes/Procedural/biome_markers.yml index ef28b314ca..5c4fdeb178 100644 --- a/Resources/Prototypes/Procedural/biome_markers.yml +++ b/Resources/Prototypes/Procedural/biome_markers.yml @@ -1,122 +1,67 @@ -- type: dungeonConfig +- type: biomeMarkerLayer id: Lizards - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobLizard - amount: !type:RangeNumberSelector - range: 3, 5 + prototype: MobLizard + minGroupSize: 3 + maxGroupSize: 5 - -- type: dungeonConfig +- type: biomeMarkerLayer id: WatchersLavaland - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobWatcherLavaland - amount: !type:RangeNumberSelector - range: 3, 3 + prototype: MobWatcherLavaland + minGroupSize: 3 + maxGroupSize: 3 -- type: dungeonConfig +- type: biomeMarkerLayer id: WatchersIcewing - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobWatcherIcewing - amount: !type:RangeNumberSelector - range: 3, 3 + prototype: MobWatcherIcewing + minGroupSize: 3 + maxGroupSize: 3 -- type: dungeonConfig +- type: biomeMarkerLayer id: WatchersMagmawing - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobWatcherMagmawing - amount: !type:RangeNumberSelector - range: 3, 3 + prototype: MobWatcherMagmawing + minGroupSize: 3 + maxGroupSize: 3 -- type: dungeonConfig +- type: biomeMarkerLayer id: Cows - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobCow - amount: !type:RangeNumberSelector - range: 1, 2 + prototype: MobCow + minGroupSize: 1 + maxGroupSize: 2 -- type: dungeonConfig +- type: biomeMarkerLayer id: Chickens - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobChicken - amount: !type:RangeNumberSelector - range: 1, 2 + prototype: MobChicken + minGroupSize: 1 + maxGroupSize: 2 -- type: dungeonConfig +- type: biomeMarkerLayer id: Pigs - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobPig - amount: !type:RangeNumberSelector - range: 1, 2 + prototype: MobPig + minGroupSize: 1 + maxGroupSize: 2 -- type: dungeonConfig +- type: biomeMarkerLayer id: Foxes - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobFox - amount: !type:RangeNumberSelector - range: 1, 1 + prototype: MobFox + minGroupSize: 1 + maxGroupSize: 1 -- type: dungeonConfig +- type: biomeMarkerLayer id: Goats - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobGoat - amount: !type:RangeNumberSelector - range: 1, 1 + prototype: MobGoat + minGroupSize: 1 + maxGroupSize: 1 -- type: dungeonConfig - id: Carps - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobCarp - amount: !type:RangeNumberSelector - range: 1, 2 - -- type: dungeonConfig +# TODO: Needs to be more robust +- type: biomeMarkerLayer id: Xenos - layers: - - !type:ChunkDunGen - size: 128 - - !type:EntityTableDunGen - table: !type:EntSelector - id: MobXeno - amount: !type:RangeNumberSelector - range: 1, 2 + prototype: MobXeno +- type: biomeMarkerLayer + id: Carps + prototype: MobCarpDungeon + + +#- type: biomeMarkerLayer +# id: Experiment +# proto: DungeonMarkerExperiment diff --git a/Resources/Prototypes/Procedural/biome_ore_templates.yml b/Resources/Prototypes/Procedural/biome_ore_templates.yml index 16b2c7e64f..ae033230b6 100644 --- a/Resources/Prototypes/Procedural/biome_ore_templates.yml +++ b/Resources/Prototypes/Procedural/biome_ore_templates.yml @@ -1,127 +1,146 @@ # Low value -- type: dungeonConfig +- type: biomeMarkerLayer id: OreIron - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockTin - count: 30 - minGroupSize: 10 - maxGroupSize: 15 + entityMask: + AsteroidRock: AsteroidRockTin + WallRock: WallRockTin + WallRockBasalt: WallRockBasaltTin + WallRockChromite: WallRockChromiteTin + WallRockSand: WallRockSandTin + WallRockSnow: WallRockSnowTin + maxCount: 30 + minGroupSize: 10 + maxGroupSize: 15 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreQuartz - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockQuartz - count: 30 - minGroupSize: 10 - maxGroupSize: 15 + entityMask: + AsteroidRock: AsteroidRockQuartz + WallRock: WallRockQuartz + WallRockBasalt: WallRockBasaltQuartz + WallRockChromite: WallRockChromiteQuartz + WallRockSnow: WallRockSnowQuartz + maxCount: 30 + minGroupSize: 10 + maxGroupSize: 15 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreCoal - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockCoal - count: 30 - minGroupSize: 8 - maxGroupSize: 12 + entityMask: + AsteroidRock: AsteroidRockCoal + WallRock: WallRockCoal + WallRockBasalt: WallRockBasaltCoal + WallRockChromite: WallRockChromiteCoal + WallRockSand: WallRockSandCoal + WallRockSnow: WallRockSnowCoal + maxCount: 30 + minGroupSize: 8 + maxGroupSize: 12 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreSalt - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockSalt - count: 30 - minGroupSize: 8 - maxGroupSize: 12 + entityMask: + AsteroidRock: AsteroidRockSalt + WallRock: WallRockSalt + WallRockBasalt: WallRockBasaltSalt + WallRockChromite: WallRockChromiteSalt + WallRockSand: WallRockSandSalt + WallRockSnow: WallRockSnowSalt + maxCount: 30 + minGroupSize: 8 + maxGroupSize: 12 + radius: 4 # Medium value # Gold -- type: dungeonConfig +- type: biomeMarkerLayer id: OreGold - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockGold - count: 20 - minGroupSize: 5 - maxGroupSize: 10 + entityMask: + AsteroidRock: AsteroidRockGold + WallRock: WallRockGold + WallRockBasalt: WallRockBasaltGold + WallRockChromite: WallRockChromiteGold + WallRockSand: WallRockSandGold + WallRockSnow: WallRockSnowGold + maxCount: 20 + minGroupSize: 5 + maxGroupSize: 10 + radius: 4 # Silver -- type: dungeonConfig +- type: biomeMarkerLayer id: OreSilver - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockSilver - count: 20 - minGroupSize: 5 - maxGroupSize: 10 + entityMask: + AsteroidRock: AsteroidRockSilver + WallRock: WallRockSilver + WallRockBasalt: WallRockBasaltSilver + WallRockChromite: WallRockChromiteSilver + WallRockSand: WallRockSandSilver + WallRockSnow: WallRockSnowSilver + maxCount: 20 + minGroupSize: 5 + maxGroupSize: 10 + radius: 4 # High value # Plasma -- type: dungeonConfig +- type: biomeMarkerLayer id: OrePlasma - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockPlasma - count: 12 - minGroupSize: 4 - maxGroupSize: 8 + entityMask: + AsteroidRock: AsteroidRockPlasma + WallRock: WallRockPlasma + WallRockBasalt: WallRockBasaltPlasma + WallRockChromite: WallRockChromitePlasma + WallRockSand: WallRockSandPlasma + WallRockSnow: WallRockSnowPlasma + maxCount: 12 + minGroupSize: 4 + maxGroupSize: 8 + radius: 4 # Uranium -- type: dungeonConfig +- type: biomeMarkerLayer id: OreUranium - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockUranium - count: 15 - minGroupSize: 4 - maxGroupSize: 8 + entityMask: + AsteroidRock: AsteroidRockUranium + WallRock: WallRockUranium + WallRockBasalt: WallRockBasaltUranium + WallRockChromite: WallRockChromiteUranium + WallRockSand: WallRockSandUranium + WallRockSnow: WallRockSnowUranium + maxCount: 15 + minGroupSize: 4 + maxGroupSize: 8 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreDiamond - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockDiamond - count: 6 - minGroupSize: 1 - maxGroupSize: 2 + entityMask: + AsteroidRock: AsteroidRockDiamond + WallRock: WallRockDiamond + WallRockBasalt: WallRockBasaltDiamond + WallRockChromite: WallRockChromiteDiamond + WallRockSand: WallRockSandDiamond + WallRockSnow: WallRockSnowDiamond + maxCount: 6 + minGroupSize: 1 + maxGroupSize: 2 + radius: 4 # Artifact Fragment -- type: dungeonConfig +- type: biomeMarkerLayer id: OreArtifactFragment - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockArtifactFragment - count: 6 - minGroupSize: 1 - maxGroupSize: 2 + entityMask: + AsteroidRock: AsteroidRockArtifactFragment + WallRock: WallRockArtifactFragment + WallRockBasalt: WallRockBasaltArtifactFragment + WallRockChromite: WallRockChromiteArtifactFragment + WallRockSand: WallRockSandArtifactFragment + WallRockSnow: WallRockSnowArtifactFragment + maxCount: 6 + minGroupSize: 1 + maxGroupSize: 2 + radius: 4 diff --git a/Resources/Prototypes/Procedural/biome_ore_templates_low.yml b/Resources/Prototypes/Procedural/biome_ore_templates_low.yml index 9af10e3c2b..3b0e242446 100644 --- a/Resources/Prototypes/Procedural/biome_ore_templates_low.yml +++ b/Resources/Prototypes/Procedural/biome_ore_templates_low.yml @@ -1,127 +1,146 @@ # Low value -- type: dungeonConfig +- type: biomeMarkerLayer id: OreIronLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockTin - count: 20 - minGroupSize: 4 - maxGroupSize: 8 + entityMask: + AsteroidRock: AsteroidRockTin + WallRock: WallRockTin + WallRockBasalt: WallRockBasaltTin + WallRockChromite: WallRockChromiteTin + WallRockSand: WallRockSandTin + WallRockSnow: WallRockSnowTin + maxCount: 20 + minGroupSize: 4 + maxGroupSize: 8 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreQuartzLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockQuartz - count: 20 - minGroupSize: 4 - maxGroupSize: 8 + entityMask: + AsteroidRock: AsteroidRockQuartz + WallRock: WallRockQuartz + WallRockBasalt: WallRockBasaltQuartz + WallRockChromite: WallRockChromiteQuartz + WallRockSnow: WallRockSnowQuartz + maxCount: 20 + minGroupSize: 4 + maxGroupSize: 8 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreCoalLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockCoal - count: 20 - minGroupSize: 4 - maxGroupSize: 6 + entityMask: + AsteroidRock: AsteroidRockCoal + WallRock: WallRockCoal + WallRockBasalt: WallRockBasaltCoal + WallRockChromite: WallRockChromiteCoal + WallRockSand: WallRockSandCoal + WallRockSnow: WallRockSnowCoal + maxCount: 20 + minGroupSize: 4 + maxGroupSize: 6 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreSaltLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockSalt - count: 15 - minGroupSize: 4 - maxGroupSize: 6 + entityMask: + AsteroidRock: AsteroidRockSalt + WallRock: WallRockSalt + WallRockBasalt: WallRockBasaltSalt + WallRockChromite: WallRockChromiteSalt + WallRockSand: WallRockSandSalt + WallRockSnow: WallRockSnowSalt + maxCount: 15 + minGroupSize: 4 + maxGroupSize: 6 + radius: 4 # Medium value # Gold -- type: dungeonConfig +- type: biomeMarkerLayer id: OreGoldLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockGold - count: 12 - minGroupSize: 2 - maxGroupSize: 5 + entityMask: + AsteroidRock: AsteroidRockGold + WallRock: WallRockGold + WallRockBasalt: WallRockBasaltGold + WallRockChromite: WallRockChromiteGold + WallRockSand: WallRockSandGold + WallRockSnow: WallRockSnowGold + maxCount: 10 + minGroupSize: 2 + maxGroupSize: 5 + radius: 4 # Silver -- type: dungeonConfig +- type: biomeMarkerLayer id: OreSilverLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockSilver - count: 12 - minGroupSize: 2 - maxGroupSize: 5 + entityMask: + AsteroidRock: AsteroidRockSilver + WallRock: WallRockSilver + WallRockBasalt: WallRockBasaltSilver + WallRockChromite: WallRockChromiteSilver + WallRockSand: WallRockSandSilver + WallRockSnow: WallRockSnowSilver + maxCount: 10 + minGroupSize: 2 + maxGroupSize: 5 + radius: 4 # High value # Plasma -- type: dungeonConfig +- type: biomeMarkerLayer id: OrePlasmaLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockPlasma - count: 6 - minGroupSize: 2 - maxGroupSize: 4 + entityMask: + AsteroidRock: AsteroidRockPlasma + WallRock: WallRockPlasma + WallRockBasalt: WallRockBasaltPlasma + WallRockChromite: WallRockChromitePlasma + WallRockSand: WallRockSandPlasma + WallRockSnow: WallRockSnowPlasma + maxCount: 6 + minGroupSize: 2 + maxGroupSize: 4 + radius: 4 # Uranium -- type: dungeonConfig +- type: biomeMarkerLayer id: OreUraniumLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockUranium - count: 7 - minGroupSize: 2 - maxGroupSize: 4 + entityMask: + AsteroidRock: AsteroidRockUranium + WallRock: WallRockUranium + WallRockBasalt: WallRockBasaltUranium + WallRockChromite: WallRockChromiteUranium + WallRockSand: WallRockSandUranium + WallRockSnow: WallRockSnowUranium + maxCount: 7 + minGroupSize: 2 + maxGroupSize: 4 + radius: 4 -- type: dungeonConfig +- type: biomeMarkerLayer id: OreDiamondLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockDiamond - count: 3 - minGroupSize: 1 - maxGroupSize: 2 + entityMask: + AsteroidRock: AsteroidRockDiamond + WallRock: WallRockDiamond + WallRockBasalt: WallRockBasaltDiamond + WallRockChromite: WallRockChromiteDiamond + WallRockSand: WallRockSandDiamond + WallRockSnow: WallRockSnowDiamond + maxCount: 3 + minGroupSize: 1 + maxGroupSize: 2 + radius: 4 # Artifact Fragment -- type: dungeonConfig +- type: biomeMarkerLayer id: OreArtifactFragmentLow - layers: - - !type:ChunkDunGen - size: 128 - - !type:OreDunGen - replacement: WallRock - entity: WallRockArtifactFragment - count: 3 - minGroupSize: 1 - maxGroupSize: 2 + entityMask: + AsteroidRock: AsteroidRockArtifactFragment + WallRock: WallRockArtifactFragment + WallRockBasalt: WallRockBasaltArtifactFragment + WallRockChromite: WallRockChromiteArtifactFragment + WallRockSand: WallRockSandArtifactFragment + WallRockSnow: WallRockSnowArtifactFragment + maxCount: 3 + minGroupSize: 1 + maxGroupSize: 2 + radius: 4 diff --git a/Resources/Prototypes/Procedural/biome_templates.yml b/Resources/Prototypes/Procedural/biome_templates.yml index 747660dc8f..d630ebad48 100644 --- a/Resources/Prototypes/Procedural/biome_templates.yml +++ b/Resources/Prototypes/Procedural/biome_templates.yml @@ -1,551 +1,435 @@ +# Contains several biomes +- type: biomeTemplate + id: Continental + 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 + # Desert -- type: entity - id: BiomeLowDesert - categories: [ HideSpawnMenu ] - components: - - type: Biome - layers: - terrain: - dungeon: LowDesertTerrain - -- type: dungeonConfig - id: LowDesertTerrain +# TODO: Water in desert +- type: biomeTemplate + id: LowDesert layers: - - !type:ChunkDunGen - - !type:PrototypeDunGen - proto: LowDesertTiles - inheritDungeons: All - - !type:PrototypeDunGen - proto: LowDesertEntities - inheritDungeons: All + - !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: dungeonConfig - id: LowDesertTiles - returnReserved: false +# Grass +- type: biomeTemplate + id: Grasslands layers: - - !type:SampleTileDunGen - threshold: -1 - tile: FloorAsteroidSand - -- type: dungeonConfig - id: LowDesertEntities - reserveTiles: true - layers: - - !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 + # 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 # Lava -- type: entity - id: BiomeLava - categories: [ HideSpawnMenu ] - components: - - type: Biome - layers: - terrain: - dungeon: LavaTerrain - -- type: dungeonConfig - id: LavaTerrain +- type: biomeTemplate + id: Lava layers: - - !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 + - !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 # Snow -- type: entity - id: BiomeSnow - categories: [ HideSpawnMenu ] - components: - - type: Biome - layers: - terrain: - dungeon: SnowTerrain - -- type: dungeonConfig - id: SnowTerrain +- type: biomeTemplate + id: Snow # Similar to Grasslands... but snow layers: - - !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 + # 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 # Shadow -> Derived from lava -- type: entity - id: BiomeShadow - categories: [ HideSpawnMenu ] - components: - - type: Biome - layers: - terrain: - dungeon: ShadowTerrain - -- type: dungeonConfig - id: ShadowTerrain +- type: biomeTemplate + id: Shadow layers: - - !type:ChunkDunGen - - !type:PrototypeDunGen - proto: ShadowTiles - inheritDungeons: All - - !type:PrototypeDunGen - proto: ShadowEntities - inheritDungeons: All - -- type: dungeonConfig - id: ShadowEntities - reserveTiles: true - layers: - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.70 noise: frequency: 1 @@ -558,7 +442,7 @@ - ShadowBasaltThree - ShadowBasaltFour - ShadowBasaltFive - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.97 noise: frequency: 1 @@ -567,7 +451,7 @@ - FloorChromite entities: - CrystalPink - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.97 noise: seed: 1 @@ -578,7 +462,7 @@ entities: - ShadowTree # Rock formations - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: -0.2 invert: true noise: @@ -593,7 +477,7 @@ entities: - WallRockChromite # chasm time - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer allowedTiles: - FloorChromite threshold: 0.2 @@ -606,43 +490,18 @@ gain: 0.4 entities: - FloorChromiteChasm - -- type: dungeonConfig - id: ShadowTiles - returnReserved: false - layers: - # Fill chromite - - !type:SampleTileDunGen - threshold: -1 - tile: FloorChromite + - !type:BiomeDummyLayer + id: Loot + # Fill chromite + - !type:BiomeTileLayer + threshold: -1 + tile: FloorChromite # Caves -- type: entity - id: BiomeCaves - categories: [ HideSpawnMenu ] - components: - - type: Biome - layers: - terrain: - dungeon: CavesTerrain - -- type: dungeonConfig - id: CavesTerrain +- type: biomeTemplate + id: Caves 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:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.85 noise: seed: 2 @@ -658,7 +517,7 @@ - CrystalBlue - CrystalYellow - CrystalCyan - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.95 noise: seed: 1 @@ -668,7 +527,7 @@ - FloorAsteroidSand entities: - FloraStalagmite - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: -0.5 invert: true noise: @@ -682,24 +541,17 @@ - FloorAsteroidSand entities: - WallRock - -- type: dungeonConfig - id: CavesTiles - returnReserved: false - layers: - - !type:SampleTileDunGen - threshold: -1.0 - tile: FloorAsteroidSand - -# Asteroid -- type: dungeonConfig - id: Asteroid - layers: - - !type:SampleTileDunGen + - !type:BiomeDummyLayer + id: Loot + - !type:BiomeTileLayer threshold: -1.0 tile: FloorAsteroidSand - reserveTiles: false - - !type:SampleEntityDunGen + +# Asteroid +- type: biomeTemplate + id: Asteroid + layers: + - !type:BiomeEntityLayer threshold: 0.85 noise: seed: 2 @@ -715,7 +567,7 @@ - CrystalBlue - CrystalYellow - CrystalCyan - - !type:SampleEntityDunGen + - !type:BiomeEntityLayer threshold: 0.95 noise: seed: 1 @@ -725,8 +577,7 @@ - FloorAsteroidSand entities: - FloraStalagmite - - !type:SampleEntityDunGen - reserveTiles: false + - !type:BiomeEntityLayer threshold: -0.6 invert: true noise: @@ -740,3 +591,6 @@ - FloorAsteroidSand entities: - AsteroidRock + - !type:BiomeTileLayer + threshold: -1.0 + tile: FloorAsteroidSand diff --git a/Resources/Prototypes/Procedural/dungeon_configs.yml b/Resources/Prototypes/Procedural/dungeon_configs.yml index 18d50ec6cd..5da9592995 100644 --- a/Resources/Prototypes/Procedural/dungeon_configs.yml +++ b/Resources/Prototypes/Procedural/dungeon_configs.yml @@ -79,9 +79,6 @@ id: Haunted layers: - !type:PrefabDunGen - roomWhitelist: - tags: - - Haunted presets: - Bucket - Wow diff --git a/Resources/Prototypes/Procedural/salvage_loot.yml b/Resources/Prototypes/Procedural/salvage_loot.yml index 289fa5fd57..cf23601978 100644 --- a/Resources/Prototypes/Procedural/salvage_loot.yml +++ b/Resources/Prototypes/Procedural/salvage_loot.yml @@ -3,108 +3,108 @@ - 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 @@ -117,70 +117,70 @@ id: OreIron guaranteed: true loots: - - !type:BiomeLoot - proto: OreIron + - !type:BiomeMarkerLoot + proto: OreIron - type: salvageLoot id: OreCoal guaranteed: true loots: - - !type:BiomeLoot - proto: OreCoal + - !type:BiomeMarkerLoot + proto: OreCoal - type: salvageLoot id: OreQuartz guaranteed: true loots: - - !type:BiomeLoot - proto: OreQuartz + - !type:BiomeMarkerLoot + proto: OreQuartz - type: salvageLoot id: OreSalt guaranteed: true loots: - - !type:BiomeLoot - proto: OreSalt + - !type:BiomeMarkerLoot + proto: OreSalt # - Medium value - type: salvageLoot id: OreGold guaranteed: true loots: - - !type:BiomeLoot - proto: OreGold + - !type:BiomeMarkerLoot + proto: OreGold - type: salvageLoot id: OreSilver guaranteed: true loots: - - !type:BiomeLoot - proto: OreSilver + - !type:BiomeMarkerLoot + proto: OreSilver # - High value - type: salvageLoot id: OrePlasma guaranteed: true loots: - - !type:BiomeLoot - proto: OrePlasma + - !type:BiomeMarkerLoot + proto: OrePlasma - type: salvageLoot id: OreUranium guaranteed: true loots: - - !type:BiomeLoot - proto: OreUranium + - !type:BiomeMarkerLoot + proto: OreUranium - type: salvageLoot id: OreDiamond guaranteed: true loots: - - !type:BiomeLoot - proto: OreDiamond + - !type:BiomeMarkerLoot + proto: OreDiamond - type: salvageLoot id: OreArtifactFragment guaranteed: true loots: - - !type:BiomeLoot - proto: OreArtifactFragment + - !type:BiomeMarkerLoot + proto: OreArtifactFragment diff --git a/Resources/Prototypes/Procedural/salvage_mods.yml b/Resources/Prototypes/Procedural/salvage_mods.yml index e8b18dd14a..ca64d29a52 100644 --- a/Resources/Prototypes/Procedural/salvage_mods.yml +++ b/Resources/Prototypes/Procedural/salvage_mods.yml @@ -8,24 +8,24 @@ - type: salvageBiomeMod id: Caves desc: salvage-biome-mod-caves - biome: BiomeCaves + biome: Caves - type: salvageBiomeMod id: Grasslands desc: salvage-biome-mod-grasslands - biome: BiomeGrasslands + biome: Grasslands - type: salvageBiomeMod id: Snow desc: salvage-biome-mod-snow cost: 1 - biome: BiomeSnow + biome: Snow - type: salvageBiomeMod id: Lava desc: salvage-biome-mod-lava cost: 2 - biome: BiomeLava + biome: Lava #- type: salvageBiomeMod # id: Space diff --git a/Resources/Prototypes/Procedural/vgroid.yml b/Resources/Prototypes/Procedural/vgroid.yml index ad759721cb..0caa9f0e1f 100644 --- a/Resources/Prototypes/Procedural/vgroid.yml +++ b/Resources/Prototypes/Procedural/vgroid.yml @@ -117,8 +117,8 @@ - type: dungeonConfig id: VGRoidSmaller - minOffset: 60 - maxOffset: 80 + minOffset: 40 + maxOffset: 60 layers: - !type:NoiseDistanceDunGen size: 150, 150 @@ -151,7 +151,6 @@ maxCount: 3 layers: - !type:ExteriorDunGen - penetration: 5,15 proto: Experiment - !type:EntityTableDunGen minCount: 25 @@ -229,16 +228,5 @@ 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