From 1fa5aaf92e39e764b079635eef4d35dd1b70272a Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 18 Nov 2023 18:08:20 +1100 Subject: [PATCH] Fix biome marker layer command (#21278) --- .../Parallax/BiomeSystem.Commands.cs | 19 ++- Content.Server/Parallax/BiomeSystem.cs | 130 +++++++++++------- .../Parallax/Biomes/BiomeComponent.cs | 11 +- 3 files changed, 107 insertions(+), 53 deletions(-) diff --git a/Content.Server/Parallax/BiomeSystem.Commands.cs b/Content.Server/Parallax/BiomeSystem.Commands.cs index 2792581292..0360c1deb7 100644 --- a/Content.Server/Parallax/BiomeSystem.Commands.cs +++ b/Content.Server/Parallax/BiomeSystem.Commands.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Parallax.Biomes; @@ -5,6 +6,7 @@ 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; @@ -151,14 +153,27 @@ public sealed partial class BiomeSystem return; } - biome.MarkerLayers.Add(args[1]); + if (!biome.MarkerLayers.Add(args[1])) + { + return; + } + + biome.ForcedMarkerLayers.Add(args[1]); } private CompletionResult AddMarkerLayerCallbackHelper(IConsoleShell shell, string[] args) { if (args.Length == 1) { - return CompletionResult.FromHintOptions(CompletionHelper.Components(args[0], EntityManager), "Biome"); + 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) diff --git a/Content.Server/Parallax/BiomeSystem.cs b/Content.Server/Parallax/BiomeSystem.cs index 2687c8a690..9a2357e708 100644 --- a/Content.Server/Parallax/BiomeSystem.cs +++ b/Content.Server/Parallax/BiomeSystem.cs @@ -44,6 +44,8 @@ public sealed partial class BiomeSystem : SharedBiomeSystem [Dependency] private readonly SharedMapSystem _mapSystem = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; + private EntityQuery _xformQuery; + private readonly HashSet _handledEntities = new(); private const float DefaultLoadRange = 16f; private float _loadRange = DefaultLoadRange; @@ -68,6 +70,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem { base.Initialize(); Log.Level = LogLevel.Debug; + _xformQuery = GetEntityQuery(); SubscribeLocalEvent(OnBiomeMapInit); SubscribeLocalEvent(OnFTLStarted); SubscribeLocalEvent(OnShuttleFlatten); @@ -393,6 +396,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem return; // Get the set of spawned nodes to avoid overlap. + var forced = component.ForcedMarkerLayers.Contains(layer); var spawnSet = _tilePool.Get(); var frontier = new ValueList(32); @@ -460,8 +464,9 @@ public sealed partial class BiomeSystem : SharedBiomeSystem // 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 (enumerator.MoveNext(out _)) + if (!forced && existing != null) continue; // Check if mask matches // anything blocking. @@ -499,6 +504,15 @@ public sealed partial class BiomeSystem : SharedBiomeSystem layerMarkers.Add(node); groupSize--; spawnSet.Add(node); + + if (forced && existing != null) + { + // Just lock anything so we can dump this + lock (component.PendingMarkers) + { + Del(existing.Value); + } + } } } @@ -531,20 +545,83 @@ public sealed partial class BiomeSystem : SharedBiomeSystem }); } + component.ForcedMarkerLayers.Clear(); var active = _activeChunks[component]; List<(Vector2i, Tile)>? tiles = null; foreach (var chunk in active) { + LoadMarkerChunk(component, gridUid, grid, chunk, seed); + if (!component.LoadedChunks.Add(chunk)) continue; tiles ??= new List<(Vector2i, Tile)>(ChunkSize * ChunkSize); // Load NOW! - LoadChunk(component, gridUid, grid, chunk, seed, tiles, xformQuery); + LoadChunk(component, gridUid, grid, chunk, seed, tiles); } } + private void LoadMarkerChunk( + 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, grid, out var tile)) + { + _mapSystem.SetTile(gridUid, grid, node, tile.Value); + } + + string? prototype; + + if (TryGetEntity(node, component, 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) + _tilePool.Return(modified); + + component.PendingMarkers.Remove(chunk); + } + /// /// Loads a particular queued chunk for a biome. /// @@ -554,56 +631,11 @@ public sealed partial class BiomeSystem : SharedBiomeSystem MapGridComponent grid, Vector2i chunk, int seed, - List<(Vector2i, Tile)> tiles, - EntityQuery xformQuery) + List<(Vector2i, Tile)> tiles) { component.ModifiedTiles.TryGetValue(chunk, out var modified); modified ??= _tilePool.Get(); - // Load any pending marker tiles first. - if (component.PendingMarkers.TryGetValue(chunk, out var layers)) - { - 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, grid, out var tile)) - { - _mapSystem.SetTile(gridUid, grid, node, tile.Value); - } - - string? prototype; - - if (TryGetEntity(node, component, 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); - } - } - - component.PendingMarkers.Remove(chunk); - } - // Set tiles first for (var x = 0; x < ChunkSize; x++) { @@ -653,7 +685,7 @@ public sealed partial class BiomeSystem : SharedBiomeSystem 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) + if (_xformQuery.TryGetComponent(ent, out var xform) && !xform.Anchored) { _transform.AnchorEntity(ent, xform, gridUid, grid, indices); } diff --git a/Content.Shared/Parallax/Biomes/BiomeComponent.cs b/Content.Shared/Parallax/Biomes/BiomeComponent.cs index 498619a17d..18aa55312c 100644 --- a/Content.Shared/Parallax/Biomes/BiomeComponent.cs +++ b/Content.Shared/Parallax/Biomes/BiomeComponent.cs @@ -2,6 +2,7 @@ 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; @@ -68,8 +69,14 @@ public sealed partial class BiomeComponent : Component [DataField("loadedMarkers", customTypeSerializer:typeof(PrototypeIdDictionarySerializer, BiomeMarkerLayerPrototype>))] public Dictionary> LoadedMarkers = new(); - [DataField("markerLayers", customTypeSerializer: typeof(PrototypeIdListSerializer))] - public List MarkerLayers = 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 }