1
0

DungeonJob.DunGenPrefab.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. using System.Numerics;
  2. using System.Threading.Tasks;
  3. using Content.Shared.Procedural;
  4. using Content.Shared.Procedural.DungeonGenerators;
  5. using Content.Shared.Whitelist;
  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="PrefabDunGen"/>
  14. /// </summary>
  15. private async Task<Dungeon> GeneratePrefabDunGen(Vector2i position, DungeonData data, PrefabDunGen prefab, HashSet<Vector2i> reservedTiles, Random random)
  16. {
  17. if (!data.Tiles.TryGetValue(DungeonDataKey.FallbackTile, out var tileProto) ||
  18. !data.Whitelists.TryGetValue(DungeonDataKey.Rooms, out var roomWhitelist))
  19. {
  20. LogDataError(typeof(PrefabDunGen));
  21. return Dungeon.Empty;
  22. }
  23. var preset = prefab.Presets[random.Next(prefab.Presets.Count)];
  24. var gen = _prototype.Index(preset);
  25. var dungeonRotation = _dungeon.GetDungeonRotation(random.Next());
  26. var dungeonTransform = Matrix3Helpers.CreateTransform(position, dungeonRotation);
  27. var roomPackProtos = new Dictionary<Vector2i, List<DungeonRoomPackPrototype>>();
  28. foreach (var pack in _prototype.EnumeratePrototypes<DungeonRoomPackPrototype>())
  29. {
  30. var size = pack.Size;
  31. var sizePacks = roomPackProtos.GetOrNew(size);
  32. sizePacks.Add(pack);
  33. }
  34. // Need to sort to make the RNG deterministic (at least without prototype changes).
  35. foreach (var roomA in roomPackProtos.Values)
  36. {
  37. roomA.Sort((x, y) =>
  38. string.Compare(x.ID, y.ID, StringComparison.Ordinal));
  39. }
  40. var roomProtos = new Dictionary<Vector2i, List<DungeonRoomPrototype>>(_prototype.Count<DungeonRoomPrototype>());
  41. foreach (var proto in _prototype.EnumeratePrototypes<DungeonRoomPrototype>())
  42. {
  43. var whitelisted = false;
  44. if (roomWhitelist?.Tags != null)
  45. {
  46. foreach (var tag in roomWhitelist.Tags)
  47. {
  48. if (proto.Tags.Contains(tag))
  49. {
  50. whitelisted = true;
  51. break;
  52. }
  53. }
  54. }
  55. if (!whitelisted)
  56. continue;
  57. var size = proto.Size;
  58. var sizeRooms = roomProtos.GetOrNew(size);
  59. sizeRooms.Add(proto);
  60. }
  61. foreach (var roomA in roomProtos.Values)
  62. {
  63. roomA.Sort((x, y) =>
  64. string.Compare(x.ID, y.ID, StringComparison.Ordinal));
  65. }
  66. var tiles = new List<(Vector2i, Tile)>();
  67. var dungeon = new Dungeon();
  68. var availablePacks = new List<DungeonRoomPackPrototype>();
  69. var chosenPacks = new DungeonRoomPackPrototype?[gen.RoomPacks.Count];
  70. var packTransforms = new Matrix3x2[gen.RoomPacks.Count];
  71. var packRotations = new Angle[gen.RoomPacks.Count];
  72. // Actually pick the room packs and rooms
  73. for (var i = 0; i < gen.RoomPacks.Count; i++)
  74. {
  75. var bounds = gen.RoomPacks[i];
  76. var dimensions = new Vector2i(bounds.Width, bounds.Height);
  77. // Try every pack rotation
  78. if (roomPackProtos.TryGetValue(dimensions, out var roomPacks))
  79. {
  80. availablePacks.AddRange(roomPacks);
  81. }
  82. // Try rotated versions if there are any.
  83. if (dimensions.X != dimensions.Y)
  84. {
  85. var rotatedDimensions = new Vector2i(dimensions.Y, dimensions.X);
  86. if (roomPackProtos.TryGetValue(rotatedDimensions, out roomPacks))
  87. {
  88. availablePacks.AddRange(roomPacks);
  89. }
  90. }
  91. // Iterate every pack
  92. random.Shuffle(availablePacks);
  93. Matrix3x2 packTransform = default!;
  94. var found = false;
  95. DungeonRoomPackPrototype pack = default!;
  96. foreach (var aPack in availablePacks)
  97. {
  98. var startIndex = random.Next(4);
  99. for (var j = 0; j < 4; j++)
  100. {
  101. var index = (startIndex + j) % 4;
  102. var dir = (DirectionFlag) Math.Pow(2, index);
  103. Vector2i aPackDimensions;
  104. if ((dir & (DirectionFlag.East | DirectionFlag.West)) != 0x0)
  105. {
  106. aPackDimensions = new Vector2i(aPack.Size.Y, aPack.Size.X);
  107. }
  108. else
  109. {
  110. aPackDimensions = aPack.Size;
  111. }
  112. // Rotation doesn't match.
  113. if (aPackDimensions != bounds.Size)
  114. continue;
  115. found = true;
  116. var aRotation = dir.AsDir().ToAngle();
  117. // Use this pack
  118. packTransform = Matrix3Helpers.CreateTransform(bounds.Center, aRotation);
  119. packRotations[i] = aRotation;
  120. pack = aPack;
  121. break;
  122. }
  123. if (found)
  124. break;
  125. }
  126. availablePacks.Clear();
  127. // Oop
  128. if (!found)
  129. {
  130. continue;
  131. }
  132. // If we're not the first pack then connect to our edges.
  133. chosenPacks[i] = pack;
  134. packTransforms[i] = packTransform;
  135. }
  136. // Then for overlaps choose either 1x1 / 3x1
  137. // Pick a random tile for it and then expand outwards as relevant (weighted towards middle?)
  138. for (var i = 0; i < chosenPacks.Length; i++)
  139. {
  140. var pack = chosenPacks[i]!;
  141. var packTransform = packTransforms[i];
  142. // Actual spawn cud here.
  143. // Pickout the room pack template to get the room dimensions we need.
  144. // TODO: Need to be able to load entities on top of other entities but das a lot of effo
  145. var packCenter = (Vector2) pack.Size / 2;
  146. foreach (var roomSize in pack.Rooms)
  147. {
  148. var roomDimensions = new Vector2i(roomSize.Width, roomSize.Height);
  149. Angle roomRotation = Angle.Zero;
  150. Matrix3x2 matty;
  151. if (!roomProtos.TryGetValue(roomDimensions, out var roomProto))
  152. {
  153. roomDimensions = new Vector2i(roomDimensions.Y, roomDimensions.X);
  154. if (!roomProtos.TryGetValue(roomDimensions, out roomProto))
  155. {
  156. matty = Matrix3x2.Multiply(packTransform, dungeonTransform);
  157. for (var x = roomSize.Left; x < roomSize.Right; x++)
  158. {
  159. for (var y = roomSize.Bottom; y < roomSize.Top; y++)
  160. {
  161. var index = Vector2.Transform(new Vector2(x, y) + _grid.TileSizeHalfVector - packCenter, matty).Floored();
  162. if (reservedTiles.Contains(index))
  163. continue;
  164. tiles.Add((index, new Tile(_tileDefManager[tileProto].TileId)));
  165. }
  166. }
  167. _maps.SetTiles(_gridUid, _grid, tiles);
  168. tiles.Clear();
  169. _sawmill.Error($"Unable to find room variant for {roomDimensions}, leaving empty.");
  170. continue;
  171. }
  172. roomRotation = new Angle(Math.PI / 2);
  173. _sawmill.Debug($"Using rotated variant for room");
  174. }
  175. var room = roomProto[random.Next(roomProto.Count)];
  176. if (roomDimensions.X == roomDimensions.Y)
  177. {
  178. // Give it a random rotation
  179. roomRotation = random.Next(4) * Math.PI / 2;
  180. }
  181. else if (random.Next(2) == 1)
  182. {
  183. roomRotation += Math.PI;
  184. }
  185. var roomTransform = Matrix3Helpers.CreateTransform(roomSize.Center - packCenter, roomRotation);
  186. matty = Matrix3x2.Multiply(roomTransform, packTransform);
  187. var dungeonMatty = Matrix3x2.Multiply(matty, dungeonTransform);
  188. // The expensive bit yippy.
  189. _dungeon.SpawnRoom(_gridUid, _grid, dungeonMatty, room, reservedTiles);
  190. var roomCenter = (room.Offset + room.Size / 2f) * _grid.TileSize;
  191. var roomTiles = new HashSet<Vector2i>(room.Size.X * room.Size.Y);
  192. var exterior = new HashSet<Vector2i>(room.Size.X * 2 + room.Size.Y * 2);
  193. var tileOffset = -roomCenter + _grid.TileSizeHalfVector;
  194. Box2i? mapBounds = null;
  195. for (var x = -1; x <= room.Size.X; x++)
  196. {
  197. for (var y = -1; y <= room.Size.Y; y++)
  198. {
  199. if (x != -1 && y != -1 && x != room.Size.X && y != room.Size.Y)
  200. {
  201. continue;
  202. }
  203. var tilePos = Vector2.Transform(new Vector2i(x + room.Offset.X, y + room.Offset.Y) + tileOffset, dungeonMatty).Floored();
  204. if (reservedTiles.Contains(tilePos))
  205. continue;
  206. exterior.Add(tilePos);
  207. }
  208. }
  209. var center = Vector2.Zero;
  210. for (var x = 0; x < room.Size.X; x++)
  211. {
  212. for (var y = 0; y < room.Size.Y; y++)
  213. {
  214. var roomTile = new Vector2i(x + room.Offset.X, y + room.Offset.Y);
  215. var tilePos = Vector2.Transform(roomTile + tileOffset, dungeonMatty);
  216. var tileIndex = tilePos.Floored();
  217. roomTiles.Add(tileIndex);
  218. mapBounds = mapBounds?.Union(tileIndex) ?? new Box2i(tileIndex, tileIndex);
  219. center += tilePos + _grid.TileSizeHalfVector;
  220. }
  221. }
  222. center /= roomTiles.Count;
  223. dungeon.AddRoom(new DungeonRoom(roomTiles, center, mapBounds!.Value, exterior));
  224. await SuspendDungeon();
  225. if (!ValidateResume())
  226. return Dungeon.Empty;
  227. }
  228. }
  229. // Calculate center and do entrances
  230. var dungeonCenter = Vector2.Zero;
  231. foreach (var room in dungeon.Rooms)
  232. {
  233. dungeonCenter += room.Center;
  234. SetDungeonEntrance(dungeon, room, reservedTiles, random);
  235. }
  236. dungeon.Rebuild();
  237. return dungeon;
  238. }
  239. private void SetDungeonEntrance(Dungeon dungeon, DungeonRoom room, HashSet<Vector2i> reservedTiles, Random random)
  240. {
  241. // TODO: Move to dungeonsystem.
  242. // TODO: Look at markers and use that.
  243. // Pick midpoints as fallback
  244. if (room.Entrances.Count == 0)
  245. {
  246. var offset = random.Next(4);
  247. // Pick an entrance that isn't taken.
  248. for (var i = 0; i < 4; i++)
  249. {
  250. var dir = (Direction) ((i + offset) * 2 % 8);
  251. Vector2i entrancePos;
  252. switch (dir)
  253. {
  254. case Direction.East:
  255. entrancePos = new Vector2i(room.Bounds.Right + 1, room.Bounds.Bottom + room.Bounds.Height / 2);
  256. break;
  257. case Direction.North:
  258. entrancePos = new Vector2i(room.Bounds.Left + room.Bounds.Width / 2, room.Bounds.Top + 1);
  259. break;
  260. case Direction.West:
  261. entrancePos = new Vector2i(room.Bounds.Left - 1, room.Bounds.Bottom + room.Bounds.Height / 2);
  262. break;
  263. case Direction.South:
  264. entrancePos = new Vector2i(room.Bounds.Left + room.Bounds.Width / 2, room.Bounds.Bottom - 1);
  265. break;
  266. default:
  267. throw new NotImplementedException();
  268. }
  269. // Check if it's not blocked
  270. var blockPos = entrancePos + dir.ToIntVec() * 2;
  271. if (i != 3 && dungeon.RoomTiles.Contains(blockPos))
  272. {
  273. continue;
  274. }
  275. if (reservedTiles.Contains(entrancePos))
  276. continue;
  277. room.Entrances.Add(entrancePos);
  278. break;
  279. }
  280. }
  281. }
  282. }