From 298f821bec0c29ecde82b165a63e44792162eda3 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 22 May 2025 02:43:17 +1000 Subject: [PATCH] Ore + entitytable fixes (#37675) * Ore + entitytable fixes Iterate every dungeon not just last. * Big shot * Fixes --- .../DungeonJob.EntityTableDunGen.cs | 36 +++- .../Procedural/DungeonJob/DungeonJob.Ore.cs | 189 +++++++++--------- .../Procedural/DungeonJob/DungeonJob.cs | 25 +-- .../DungeonLayers/EntityTableDunGen.cs | 6 + 4 files changed, 135 insertions(+), 121 deletions(-) diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs index 8f9be82272..05d33a4b8c 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.EntityTableDunGen.cs @@ -14,22 +14,31 @@ public sealed partial class DungeonJob { private async Task PostGen( EntityTableDunGen gen, - Dungeon dungeon, + List dungeons, + HashSet reservedTiles, Random random) { - var availableRooms = new ValueList(); - availableRooms.AddRange(dungeon.Rooms); - var availableTiles = new ValueList(dungeon.AllTiles); - var count = random.Next(gen.MinCount, gen.MaxCount + 1); var npcs = _entManager.System(); - for (var i = 0; i < count; i++) + foreach (var dungeon in dungeons) { - while (availableTiles.Count > 0) + var availableRooms = new ValueList(); + availableRooms.AddRange(dungeon.Rooms); + var availableTiles = new ValueList(dungeon.AllTiles); + + while (availableTiles.Count > 0 && count > 0) { var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count)); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + if (reservedTiles.Contains(tile)) + continue; + if (!_anchorable.TileFree(_grid, tile, (int) CollisionGroup.MachineLayer, @@ -47,13 +56,18 @@ public sealed partial class DungeonJob npcs.SleepNPC(uid); } - break; + count--; } - await SuspendDungeon(); - - if (!ValidateResume()) + if (gen.PerDungeon) + { + count = random.Next(gen.MinCount, gen.MaxCount + 1); + } + // Stop if count is 0, otherwise go to next dungeon. + else if (count == 0) + { return; + } } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs index 679eecb4f7..78ab2b7a0d 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.Ore.cs @@ -15,131 +15,136 @@ public sealed partial class DungeonJob /// private async Task PostGen( OreDunGen gen, - Dungeon dungeon, + List dungeons, + HashSet reservedTiles, Random random) { - // Doesn't use dungeon data because layers and we don't need top-down support at the moment. - - var emptyTiles = false; - var replaceEntities = new Dictionary(); - var availableTiles = new List(); - - foreach (var node in dungeon.AllTiles) + foreach (var dungeon in dungeons) { - // Empty tile, skip if relevant. - if (!emptyTiles && (!_maps.TryGetTile(_grid, node, out var tile) || tile.IsEmpty)) - continue; - - // Check if it's a valid spawn, if so then use it. - var enumerator = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, node); - var found = false; - - // We use existing entities as a mark to spawn in place - // OR - // We check for any existing entities to see if we can spawn there. - while (enumerator.MoveNext(out var uid)) + var emptyTiles = false; + var replaceEntities = new Dictionary(); + var availableTiles = new List(); + + foreach (var node in dungeon.AllTiles) { - // We can't replace so just stop here. - if (gen.Replacement == null) - break; + if (reservedTiles.Contains(node)) + continue; - var prototype = _entManager.GetComponent(uid.Value).EntityPrototype; + // Empty tile, skip if relevant. + if (!emptyTiles && (!_maps.TryGetTile(_grid, node, out var tile) || tile.IsEmpty)) + continue; - if (prototype?.ID == gen.Replacement) - { - replaceEntities[node] = uid.Value; - found = true; - break; - } - } + // Check if it's a valid spawn, if so then use it. + var enumerator = _maps.GetAnchoredEntitiesEnumerator(_gridUid, _grid, node); + var found = false; - if (!found) - continue; + // We use existing entities as a mark to spawn in place + // OR + // We check for any existing entities to see if we can spawn there. + while (enumerator.MoveNext(out var uid)) + { + // We can't replace so just stop here. + if (gen.Replacement == null) + break; - // Add it to valid nodes. - availableTiles.Add(node); + var prototype = _entManager.GetComponent(uid.Value).EntityPrototype; - await SuspendDungeon(); + if (prototype?.ID == gen.Replacement) + { + replaceEntities[node] = uid.Value; + found = true; + break; + } + } - if (!ValidateResume()) - return; - } + if (!found) + continue; - var remapping = new Dictionary(); + // Add it to valid nodes. + availableTiles.Add(node); - // TODO: Move this to engine - if (_prototype.TryIndex(gen.Entity, out var proto) && - proto.Components.TryGetComponent("EntityRemap", out var comps)) - { - var remappingComp = (EntityRemapComponent) comps; - remapping = remappingComp.Mask; - } + await SuspendDungeon(); - var frontier = new ValueList(32); + if (!ValidateResume()) + return; + } - // Iterate the group counts and pathfind out each group. - for (var i = 0; i < gen.Count; i++) - { - await SuspendDungeon(); + var remapping = new Dictionary(); - if (!ValidateResume()) - return; + // TODO: Move this to engine + if (_prototype.TryIndex(gen.Entity, out var proto) && + proto.Components.TryGetComponent("EntityRemap", out var comps)) + { + var remappingComp = (EntityRemapComponent) comps; + remapping = remappingComp.Mask; + } - var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1); + var frontier = new ValueList(32); - // While we have remaining tiles keep iterating - while (groupSize > 0 && availableTiles.Count > 0) + // Iterate the group counts and pathfind out each group. + for (var i = 0; i < gen.Count; i++) { - var startNode = random.PickAndTake(availableTiles); - frontier.Clear(); - frontier.Add(startNode); + await SuspendDungeon(); + + if (!ValidateResume()) + return; + + var groupSize = random.Next(gen.MinGroupSize, gen.MaxGroupSize + 1); - // 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) + // While we have remaining tiles keep iterating + while (groupSize > 0 && availableTiles.Count > 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++) + 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) { - for (var y = -1; y <= 1; y++) + // 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++) { - var neighbor = new Vector2i(node.X + x, node.Y + y); + 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; + if (frontier.Contains(neighbor) || !availableTiles.Contains(neighbor)) + continue; - frontier.Add(neighbor); + frontier.Add(neighbor); + } } - } - var prototype = gen.Entity; - - if (replaceEntities.TryGetValue(node, out var existingEnt)) - { - var existingProto = _entManager.GetComponent(existingEnt).EntityPrototype; - _entManager.DeleteEntity(existingEnt); + var prototype = gen.Entity; - if (existingProto != null && remapping.TryGetValue(existingProto.ID, out var remapped)) + if (replaceEntities.TryGetValue(node, out var existingEnt)) { - prototype = remapped; + 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)); + // Tile valid salad so add it. + _entManager.SpawnAtPosition(prototype, _maps.GridTileToLocal(_gridUid, _grid, node)); - groupSize--; + groupSize--; + } } - } - if (groupSize > 0) - { - _sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!"); + if (groupSize > 0) + { + _sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!"); + } } } } diff --git a/Content.Server/Procedural/DungeonJob/DungeonJob.cs b/Content.Server/Procedural/DungeonJob/DungeonJob.cs index 58583f833d..f2bd5a4394 100644 --- a/Content.Server/Procedural/DungeonJob/DungeonJob.cs +++ b/Content.Server/Procedural/DungeonJob/DungeonJob.cs @@ -134,27 +134,15 @@ public sealed partial class DungeonJob : Job> foreach (var layer in layers) { + var dungCount = dungeons.Count; await RunLayer(dungeons, position, layer, reservedTiles, seed, random); if (config.ReserveTiles) { - // Remove any dungeons passed in so we don't interfere with them - // This is kinda goofy but okay for now. - if (existing != null) - { - for (var j = 0; j < dungeons.Count; j++) - { - var dung = dungeons[j]; - - if (existing.Contains(dung)) - { - dungeons.RemoveSwap(j); - } - } - } - - foreach (var dungeon in dungeons) + // Reserve tiles on any new dungeons. + for (var d = dungCount; d < dungeons.Count; d++) { + var dungeon = dungeons[d]; reservedTiles.UnionWith(dungeon.AllTiles); } } @@ -202,6 +190,7 @@ public sealed partial class DungeonJob : Job> npcSystem.WakeNPC(npc.Owner, npc.Comp); } + _sawmill.Info($"Finished generating dungeon {_gen} with seed {_seed}"); return dungeons; } @@ -276,7 +265,7 @@ public sealed partial class DungeonJob : Job> await PostGen(mob, dungeons[^1], random); break; case EntityTableDunGen entityTable: - await PostGen(entityTable, dungeons[^1], random); + await PostGen(entityTable, dungeons, reservedTiles, random); break; case NoiseDistanceDunGen distance: dungeons.Add(await GenerateNoiseDistanceDunGen(position, distance, reservedTiles, seed, random)); @@ -285,7 +274,7 @@ public sealed partial class DungeonJob : Job> dungeons.Add(await GenerateNoiseDunGen(position, noise, reservedTiles, seed, random)); break; case OreDunGen ore: - await PostGen(ore, dungeons[^1], random); + await PostGen(ore, dungeons, reservedTiles, random); break; case PrefabDunGen prefab: dungeons.Add(await GeneratePrefabDunGen(position, prefab, reservedTiles, random)); diff --git a/Content.Shared/Procedural/DungeonLayers/EntityTableDunGen.cs b/Content.Shared/Procedural/DungeonLayers/EntityTableDunGen.cs index 71e9bae0cc..9f19909569 100644 --- a/Content.Shared/Procedural/DungeonLayers/EntityTableDunGen.cs +++ b/Content.Shared/Procedural/DungeonLayers/EntityTableDunGen.cs @@ -18,4 +18,10 @@ public sealed partial class EntityTableDunGen : IDunGenLayer [DataField(required: true)] public EntityTableSelector Table; + + /// + /// Should the count be per dungeon or across all dungeons. + /// + [DataField] + public bool PerDungeon; } -- 2.51.2