DungeonJob.PostGenWorm.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. using System.Linq;
  2. using System.Threading.Tasks;
  3. using Content.Shared.Procedural;
  4. using Content.Shared.Procedural.PostGeneration;
  5. using Robust.Shared.Collections;
  6. using Robust.Shared.Map;
  7. using Robust.Shared.Random;
  8. using Robust.Shared.Utility;
  9. namespace Content.Server.Procedural.DungeonJob;
  10. public sealed partial class DungeonJob
  11. {
  12. /// <summary>
  13. /// <see cref="WormCorridorDunGen"/>
  14. /// </summary>
  15. private async Task PostGen(WormCorridorDunGen gen, DungeonData data, Dungeon dungeon, HashSet<Vector2i> reservedTiles, Random random)
  16. {
  17. if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) || !_prototype.TryIndex(tileProto, out var tileDef))
  18. {
  19. _sawmill.Error($"Tried to run {nameof(WormCorridorDunGen)} without any dungeon data set which is unsupported");
  20. return;
  21. }
  22. var networks = new List<(Vector2i Start, HashSet<Vector2i> Network)>();
  23. // List of places to start from.
  24. var worm = new ValueList<Vector2i>();
  25. var startAngles = new Dictionary<Vector2i, Angle>();
  26. foreach (var room in dungeon.Rooms)
  27. {
  28. foreach (var entrance in room.Entrances)
  29. {
  30. var network = new HashSet<Vector2i> { entrance };
  31. networks.Add((entrance, network));
  32. // Point away from the room to start with.
  33. startAngles.Add(entrance, (entrance + _grid.TileSizeHalfVector - room.Center).ToAngle());
  34. }
  35. }
  36. // There's a lot of ways to handle this, e.g. pathfinding towards each room
  37. // For simplicity we'll go through each entrance randomly and generate worms from it
  38. // then as a final step we will connect all of their networks.
  39. random.Shuffle(networks);
  40. for (var i = 0; i < gen.Count; i++)
  41. {
  42. // Find a random network to worm from.
  43. var startIndex = (i % networks.Count);
  44. var startPos = networks[startIndex].Start;
  45. var position = startPos + _grid.TileSizeHalfVector;
  46. var remainingLength = gen.Length;
  47. worm.Clear();
  48. var angle = startAngles[startPos];
  49. for (var x = remainingLength; x >= 0; x--)
  50. {
  51. position += angle.ToVec();
  52. angle += random.NextAngle(-gen.MaxAngleChange, gen.MaxAngleChange);
  53. var roundedPos = position.Floored();
  54. // Check if the tile doesn't overlap something it shouldn't
  55. if (dungeon.RoomTiles.Contains(roundedPos) ||
  56. dungeon.RoomExteriorTiles.Contains(roundedPos))
  57. {
  58. continue;
  59. }
  60. worm.Add(roundedPos);
  61. }
  62. // Uhh yeah.
  63. if (worm.Count == 0)
  64. {
  65. continue;
  66. }
  67. // Find a random part on the existing worm to start.
  68. var value = random.Pick(worm);
  69. networks[startIndex].Network.UnionWith(worm);
  70. startAngles[value] = random.NextAngle();
  71. }
  72. // Now to ensure they all connect we'll pathfind each network to one another
  73. // Simple BFS pathfinder
  74. var main = networks[0];
  75. var frontier = new PriorityQueue<Vector2i, float>();
  76. var cameFrom = new Dictionary<Vector2i, Vector2i>();
  77. var costSoFar = new Dictionary<Vector2i, float>();
  78. // How many times we try to patch the networks together
  79. var attempts = 3;
  80. for (var attempt = 0; attempt < attempts; attempt++)
  81. {
  82. // Skip index 0
  83. for (var i = networks.Count - 1; i > 0; i--)
  84. {
  85. cameFrom.Clear();
  86. frontier.Clear();
  87. costSoFar.Clear();
  88. var targetNode = random.Pick(main.Network);
  89. var other = networks[i];
  90. var startNode = other.Network.First();
  91. frontier.Enqueue(startNode, 0f);
  92. costSoFar[startNode] = 0f;
  93. var count = 0;
  94. await SuspendDungeon();
  95. if (!ValidateResume())
  96. return;
  97. while (frontier.TryDequeue(out var node, out _) && count < gen.PathLimit)
  98. {
  99. count++;
  100. // Found
  101. if (main.Network.Contains(node))
  102. {
  103. // found, rebuild
  104. frontier.Clear();
  105. main.Network.Add(node);
  106. main.Network.UnionWith(other.Network);
  107. var target = node;
  108. // Rebuild
  109. while (cameFrom.TryGetValue(target, out var source))
  110. {
  111. target = source;
  112. main.Network.Add(target);
  113. }
  114. networks.RemoveSwap(i);
  115. continue;
  116. }
  117. for (var x = -1; x <= 1; x++)
  118. {
  119. for (var y = -1; y <= 1; y++)
  120. {
  121. if (x == 0 && y == 0)
  122. continue;
  123. var neighbor = node + new Vector2i(x, y);
  124. // Exclude room tiles.
  125. if (dungeon.RoomTiles.Contains(neighbor) ||
  126. dungeon.RoomExteriorTiles.Contains(neighbor))
  127. {
  128. continue;
  129. }
  130. var tileCost = (neighbor - node).Length;
  131. var gScore = costSoFar[node] + tileCost;
  132. if (costSoFar.TryGetValue(neighbor, out var nextValue) && gScore >= nextValue)
  133. {
  134. continue;
  135. }
  136. cameFrom[neighbor] = node;
  137. costSoFar[neighbor] = gScore;
  138. var hScore = (targetNode - neighbor).Length + gScore;
  139. frontier.Enqueue(neighbor, hScore);
  140. }
  141. }
  142. }
  143. }
  144. }
  145. WidenCorridor(dungeon, gen.Width, main.Network);
  146. dungeon.CorridorTiles.UnionWith(main.Network);
  147. BuildCorridorExterior(dungeon);
  148. dungeon.RefreshAllTiles();
  149. var tiles = new List<(Vector2i Index, Tile Tile)>();
  150. foreach (var tile in dungeon.CorridorTiles)
  151. {
  152. tiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
  153. }
  154. foreach (var tile in dungeon.CorridorExteriorTiles)
  155. {
  156. tiles.Add((tile, _tile.GetVariantTile(tileDef, random)));
  157. }
  158. _maps.SetTiles(_gridUid, _grid, tiles);
  159. }
  160. }