1
0

DungeonSystem.Rooms.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. using System.Numerics;
  2. using Content.Shared.Decals;
  3. using Content.Shared.Maps;
  4. using Content.Shared.Procedural;
  5. using Content.Shared.Random.Helpers;
  6. using Content.Shared.Whitelist;
  7. using Robust.Shared.Map;
  8. using Robust.Shared.Map.Components;
  9. using Robust.Shared.Utility;
  10. namespace Content.Server.Procedural;
  11. public sealed partial class DungeonSystem
  12. {
  13. // Temporary caches.
  14. private readonly HashSet<EntityUid> _entitySet = new();
  15. private readonly List<DungeonRoomPrototype> _availableRooms = new();
  16. /// <summary>
  17. /// Gets a random dungeon room matching the specified area, whitelist and size.
  18. /// </summary>
  19. public DungeonRoomPrototype? GetRoomPrototype(Vector2i size, Random random, EntityWhitelist? whitelist = null)
  20. {
  21. return GetRoomPrototype(random, whitelist, minSize: size, maxSize: size);
  22. }
  23. /// <summary>
  24. /// Gets a random dungeon room matching the specified area and whitelist and size range
  25. /// </summary>
  26. public DungeonRoomPrototype? GetRoomPrototype(Random random,
  27. EntityWhitelist? whitelist = null,
  28. Vector2i? minSize = null,
  29. Vector2i? maxSize = null)
  30. {
  31. // Can never be true.
  32. if (whitelist is { Tags: null })
  33. {
  34. return null;
  35. }
  36. _availableRooms.Clear();
  37. foreach (var proto in _prototype.EnumeratePrototypes<DungeonRoomPrototype>())
  38. {
  39. if (minSize is not null && (proto.Size.X < minSize.Value.X || proto.Size.Y < minSize.Value.Y))
  40. continue;
  41. if (maxSize is not null && (proto.Size.X > maxSize.Value.X || proto.Size.Y > maxSize.Value.Y))
  42. continue;
  43. if (whitelist == null)
  44. {
  45. _availableRooms.Add(proto);
  46. continue;
  47. }
  48. foreach (var tag in whitelist.Tags)
  49. {
  50. if (!proto.Tags.Contains(tag))
  51. continue;
  52. _availableRooms.Add(proto);
  53. break;
  54. }
  55. }
  56. if (_availableRooms.Count == 0)
  57. return null;
  58. var room = _availableRooms[random.Next(_availableRooms.Count)];
  59. return room;
  60. }
  61. public void SpawnRoom(
  62. EntityUid gridUid,
  63. MapGridComponent grid,
  64. Vector2i origin,
  65. DungeonRoomPrototype room,
  66. Random random,
  67. HashSet<Vector2i>? reservedTiles,
  68. bool clearExisting = false,
  69. bool rotation = false)
  70. {
  71. var originTransform = Matrix3Helpers.CreateTranslation(origin.X, origin.Y);
  72. var roomRotation = Angle.Zero;
  73. if (rotation)
  74. {
  75. roomRotation = GetRoomRotation(room, random);
  76. }
  77. var roomTransform = Matrix3Helpers.CreateTransform((Vector2) room.Size / 2f, roomRotation);
  78. var finalTransform = Matrix3x2.Multiply(roomTransform, originTransform);
  79. SpawnRoom(gridUid, grid, finalTransform, room, reservedTiles, clearExisting);
  80. }
  81. public Angle GetRoomRotation(DungeonRoomPrototype room, Random random)
  82. {
  83. var roomRotation = Angle.Zero;
  84. if (room.Size.X == room.Size.Y)
  85. {
  86. // Give it a random rotation
  87. roomRotation = random.Next(4) * Math.PI / 2;
  88. }
  89. else if (random.Next(2) == 1)
  90. {
  91. roomRotation += Math.PI;
  92. }
  93. return roomRotation;
  94. }
  95. public void SpawnRoom(
  96. EntityUid gridUid,
  97. MapGridComponent grid,
  98. Matrix3x2 roomTransform,
  99. DungeonRoomPrototype room,
  100. HashSet<Vector2i>? reservedTiles = null,
  101. bool clearExisting = false)
  102. {
  103. // Ensure the underlying template exists.
  104. var roomMap = GetOrCreateTemplate(room);
  105. var templateMapUid = _mapManager.GetMapEntityId(roomMap);
  106. var templateGrid = Comp<MapGridComponent>(templateMapUid);
  107. var roomDimensions = room.Size;
  108. var finalRoomRotation = roomTransform.Rotation();
  109. var roomCenter = (room.Offset + room.Size / 2f) * grid.TileSize;
  110. var tileOffset = -roomCenter + grid.TileSizeHalfVector;
  111. _tiles.Clear();
  112. // Load tiles
  113. for (var x = 0; x < roomDimensions.X; x++)
  114. {
  115. for (var y = 0; y < roomDimensions.Y; y++)
  116. {
  117. var indices = new Vector2i(x + room.Offset.X, y + room.Offset.Y);
  118. var tileRef = _maps.GetTileRef(templateMapUid, templateGrid, indices);
  119. var tilePos = Vector2.Transform(indices + tileOffset, roomTransform);
  120. var rounded = tilePos.Floored();
  121. if (!clearExisting && reservedTiles?.Contains(rounded) == true)
  122. continue;
  123. if (room.IgnoreTile is not null)
  124. {
  125. if (_maps.TryGetTileDef(templateGrid, indices, out var tileDef) && room.IgnoreTile == tileDef.ID)
  126. continue;
  127. }
  128. _tiles.Add((rounded, tileRef.Tile));
  129. if (clearExisting)
  130. {
  131. var anchored = _maps.GetAnchoredEntities((gridUid, grid), rounded);
  132. foreach (var ent in anchored)
  133. {
  134. QueueDel(ent);
  135. }
  136. }
  137. }
  138. }
  139. var bounds = new Box2(room.Offset, room.Offset + room.Size);
  140. _maps.SetTiles(gridUid, grid, _tiles);
  141. // Load entities
  142. // TODO: I don't think engine supports full entity copying so we do this piece of shit.
  143. foreach (var templateEnt in _lookup.GetEntitiesIntersecting(templateMapUid, bounds, LookupFlags.Uncontained))
  144. {
  145. var templateXform = _xformQuery.GetComponent(templateEnt);
  146. var childPos = Vector2.Transform(templateXform.LocalPosition - roomCenter, roomTransform);
  147. if (!clearExisting && reservedTiles?.Contains(childPos.Floored()) == true)
  148. continue;
  149. var childRot = templateXform.LocalRotation + finalRoomRotation;
  150. var protoId = _metaQuery.GetComponent(templateEnt).EntityPrototype?.ID;
  151. // TODO: Copy the templated entity as is with serv
  152. var ent = Spawn(protoId, new EntityCoordinates(gridUid, childPos));
  153. var childXform = _xformQuery.GetComponent(ent);
  154. var anchored = templateXform.Anchored;
  155. _transform.SetLocalRotation(ent, childRot, childXform);
  156. // If the templated entity was anchored then anchor us too.
  157. if (anchored && !childXform.Anchored)
  158. _transform.AnchorEntity((ent, childXform), (gridUid, grid));
  159. else if (!anchored && childXform.Anchored)
  160. _transform.Unanchor(ent, childXform);
  161. }
  162. // Load decals
  163. if (TryComp<DecalGridComponent>(templateMapUid, out var loadedDecals))
  164. {
  165. EnsureComp<DecalGridComponent>(gridUid);
  166. foreach (var (_, decal) in _decals.GetDecalsIntersecting(templateMapUid, bounds, loadedDecals))
  167. {
  168. // Offset by 0.5 because decals are offset from bot-left corner
  169. // So we convert it to center of tile then convert it back again after transform.
  170. // Do these shenanigans because 32x32 decals assume as they are centered on bottom-left of tiles.
  171. var position = Vector2.Transform(decal.Coordinates + grid.TileSizeHalfVector - roomCenter, roomTransform);
  172. position -= grid.TileSizeHalfVector;
  173. if (!clearExisting && reservedTiles?.Contains(position.Floored()) == true)
  174. continue;
  175. // Umm uhh I love decals so uhhhh idk what to do about this
  176. var angle = (decal.Angle + finalRoomRotation).Reduced();
  177. // Adjust because 32x32 so we can't rotate cleanly
  178. // Yeah idk about the uhh vectors here but it looked visually okay but they may still be off by 1.
  179. // Also EyeManager.PixelsPerMeter should really be in shared.
  180. if (angle.Equals(Math.PI))
  181. {
  182. position += new Vector2(-1f / 32f, 1f / 32f);
  183. }
  184. else if (angle.Equals(-Math.PI / 2f))
  185. {
  186. position += new Vector2(-1f / 32f, 0f);
  187. }
  188. else if (angle.Equals(Math.PI / 2f))
  189. {
  190. position += new Vector2(0f, 1f / 32f);
  191. }
  192. else if (angle.Equals(Math.PI * 1.5f))
  193. {
  194. // I hate this but decals are bottom-left rather than center position and doing the
  195. // matrix ops is a PITA hence this workaround for now; I also don't want to add a stupid
  196. // field for 1 specific op on decals
  197. if (decal.Id != "DiagonalCheckerAOverlay" &&
  198. decal.Id != "DiagonalCheckerBOverlay")
  199. {
  200. position += new Vector2(-1f / 32f, 0f);
  201. }
  202. }
  203. var tilePos = position.Floored();
  204. // Fallback because uhhhhhhhh yeah, a corner tile might look valid on the original
  205. // but place 1 nanometre off grid and fail the add.
  206. if (!_maps.TryGetTileRef(gridUid, grid, tilePos, out var tileRef) || tileRef.Tile.IsEmpty)
  207. {
  208. _maps.SetTile(gridUid, grid, tilePos, _tile.GetVariantTile((ContentTileDefinition) _tileDefManager[FallbackTileId], _random.GetRandom()));
  209. }
  210. var result = _decals.TryAddDecal(
  211. decal.Id,
  212. new EntityCoordinates(gridUid, position),
  213. out _,
  214. decal.Color,
  215. angle,
  216. decal.ZIndex,
  217. decal.Cleanable);
  218. DebugTools.Assert(result);
  219. }
  220. }
  221. }
  222. }