Ore + entitytable fixes (#37675)

* Ore + entitytable fixes

Iterate every dungeon not just last.

* Big shot

* Fixes
This commit is contained in:
metalgearsloth
2025-05-22 02:43:17 +10:00
committed by GitHub
parent ecf9e855f6
commit 298f821bec
4 changed files with 152 additions and 138 deletions

View File

@@ -14,22 +14,31 @@ public sealed partial class DungeonJob
{ {
private async Task PostGen( private async Task PostGen(
EntityTableDunGen gen, EntityTableDunGen gen,
Dungeon dungeon, List<Dungeon> dungeons,
HashSet<Vector2i> reservedTiles,
Random random) Random random)
{ {
var availableRooms = new ValueList<DungeonRoom>();
availableRooms.AddRange(dungeon.Rooms);
var availableTiles = new ValueList<Vector2i>(dungeon.AllTiles);
var count = random.Next(gen.MinCount, gen.MaxCount + 1); var count = random.Next(gen.MinCount, gen.MaxCount + 1);
var npcs = _entManager.System<NPCSystem>(); var npcs = _entManager.System<NPCSystem>();
for (var i = 0; i < count; i++) foreach (var dungeon in dungeons)
{ {
while (availableTiles.Count > 0) var availableRooms = new ValueList<DungeonRoom>();
availableRooms.AddRange(dungeon.Rooms);
var availableTiles = new ValueList<Vector2i>(dungeon.AllTiles);
while (availableTiles.Count > 0 && count > 0)
{ {
var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count)); var tile = availableTiles.RemoveSwap(random.Next(availableTiles.Count));
await SuspendDungeon();
if (!ValidateResume())
return;
if (reservedTiles.Contains(tile))
continue;
if (!_anchorable.TileFree(_grid, if (!_anchorable.TileFree(_grid,
tile, tile,
(int) CollisionGroup.MachineLayer, (int) CollisionGroup.MachineLayer,
@@ -47,13 +56,18 @@ public sealed partial class DungeonJob
npcs.SleepNPC(uid); npcs.SleepNPC(uid);
} }
break; count--;
} }
await SuspendDungeon(); if (gen.PerDungeon)
{
if (!ValidateResume()) count = random.Next(gen.MinCount, gen.MaxCount + 1);
}
// Stop if count is 0, otherwise go to next dungeon.
else if (count == 0)
{
return; return;
}
} }
} }
} }

View File

@@ -15,131 +15,136 @@ public sealed partial class DungeonJob
/// </summary> /// </summary>
private async Task PostGen( private async Task PostGen(
OreDunGen gen, OreDunGen gen,
Dungeon dungeon, List<Dungeon> dungeons,
HashSet<Vector2i> reservedTiles,
Random random) Random random)
{ {
// Doesn't use dungeon data because layers and we don't need top-down support at the moment. foreach (var dungeon in dungeons)
var emptyTiles = false;
var replaceEntities = new Dictionary<Vector2i, EntityUid>();
var availableTiles = new List<Vector2i>();
foreach (var node in dungeon.AllTiles)
{ {
// Empty tile, skip if relevant. var emptyTiles = false;
if (!emptyTiles && (!_maps.TryGetTile(_grid, node, out var tile) || tile.IsEmpty)) var replaceEntities = new Dictionary<Vector2i, EntityUid>();
continue; var availableTiles = new List<Vector2i>();
// Check if it's a valid spawn, if so then use it. foreach (var node in dungeon.AllTiles)
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))
{ {
// We can't replace so just stop here. if (reservedTiles.Contains(node))
if (gen.Replacement == null) continue;
break;
var prototype = _entManager.GetComponent<MetaDataComponent>(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) // 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))
{ {
replaceEntities[node] = uid.Value; // We can't replace so just stop here.
found = true; if (gen.Replacement == null)
break; break;
var prototype = _entManager.GetComponent<MetaDataComponent>(uid.Value).EntityPrototype;
if (prototype?.ID == gen.Replacement)
{
replaceEntities[node] = uid.Value;
found = true;
break;
}
} }
if (!found)
continue;
// Add it to valid nodes.
availableTiles.Add(node);
await SuspendDungeon();
if (!ValidateResume())
return;
} }
if (!found) var remapping = new Dictionary<EntProtoId, EntProtoId>();
continue;
// Add it to valid nodes. // TODO: Move this to engine
availableTiles.Add(node); if (_prototype.TryIndex(gen.Entity, out var proto) &&
proto.Components.TryGetComponent("EntityRemap", out var comps))
await SuspendDungeon();
if (!ValidateResume())
return;
}
var remapping = new Dictionary<EntProtoId, EntProtoId>();
// TODO: Move this to engine
if (_prototype.TryIndex(gen.Entity, out var proto) &&
proto.Components.TryGetComponent("EntityRemap", out var comps))
{
var remappingComp = (EntityRemapComponent) comps;
remapping = remappingComp.Mask;
}
var frontier = new ValueList<Vector2i>(32);
// Iterate the group counts and pathfind out each group.
for (var i = 0; i < gen.Count; i++)
{
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); var remappingComp = (EntityRemapComponent) comps;
frontier.Clear(); remapping = remappingComp.Mask;
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<MetaDataComponent>(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) var frontier = new ValueList<Vector2i>(32);
// Iterate the group counts and pathfind out each group.
for (var i = 0; i < gen.Count; i++)
{ {
_sawmill.Warning($"Found remaining group size for ore veins of {gen.Replacement ?? "null"}!"); 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<MetaDataComponent>(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"}!");
}
} }
} }
} }

View File

@@ -134,27 +134,15 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
foreach (var layer in layers) foreach (var layer in layers)
{ {
var dungCount = dungeons.Count;
await RunLayer(dungeons, position, layer, reservedTiles, seed, random); await RunLayer(dungeons, position, layer, reservedTiles, seed, random);
if (config.ReserveTiles) if (config.ReserveTiles)
{ {
// Remove any dungeons passed in so we don't interfere with them // Reserve tiles on any new dungeons.
// This is kinda goofy but okay for now. for (var d = dungCount; d < dungeons.Count; d++)
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)
{ {
var dungeon = dungeons[d];
reservedTiles.UnionWith(dungeon.AllTiles); reservedTiles.UnionWith(dungeon.AllTiles);
} }
} }
@@ -202,6 +190,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
npcSystem.WakeNPC(npc.Owner, npc.Comp); npcSystem.WakeNPC(npc.Owner, npc.Comp);
} }
_sawmill.Info($"Finished generating dungeon {_gen} with seed {_seed}");
return dungeons; return dungeons;
} }
@@ -276,7 +265,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
await PostGen(mob, dungeons[^1], random); await PostGen(mob, dungeons[^1], random);
break; break;
case EntityTableDunGen entityTable: case EntityTableDunGen entityTable:
await PostGen(entityTable, dungeons[^1], random); await PostGen(entityTable, dungeons, reservedTiles, random);
break; break;
case NoiseDistanceDunGen distance: case NoiseDistanceDunGen distance:
dungeons.Add(await GenerateNoiseDistanceDunGen(position, distance, reservedTiles, seed, random)); dungeons.Add(await GenerateNoiseDistanceDunGen(position, distance, reservedTiles, seed, random));
@@ -285,7 +274,7 @@ public sealed partial class DungeonJob : Job<List<Dungeon>>
dungeons.Add(await GenerateNoiseDunGen(position, noise, reservedTiles, seed, random)); dungeons.Add(await GenerateNoiseDunGen(position, noise, reservedTiles, seed, random));
break; break;
case OreDunGen ore: case OreDunGen ore:
await PostGen(ore, dungeons[^1], random); await PostGen(ore, dungeons, reservedTiles, random);
break; break;
case PrefabDunGen prefab: case PrefabDunGen prefab:
dungeons.Add(await GeneratePrefabDunGen(position, prefab, reservedTiles, random)); dungeons.Add(await GeneratePrefabDunGen(position, prefab, reservedTiles, random));

View File

@@ -18,4 +18,10 @@ public sealed partial class EntityTableDunGen : IDunGenLayer
[DataField(required: true)] [DataField(required: true)]
public EntityTableSelector Table; public EntityTableSelector Table;
/// <summary>
/// Should the count be per dungeon or across all dungeons.
/// </summary>
[DataField]
public bool PerDungeon;
} }