SharedNavMapSystem.cs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. using System.Diagnostics.CodeAnalysis;
  2. using System.Numerics;
  3. using System.Runtime.CompilerServices;
  4. using Content.Shared.Tag;
  5. using Robust.Shared.GameStates;
  6. using Robust.Shared.Network;
  7. using Robust.Shared.Prototypes;
  8. using Robust.Shared.Serialization;
  9. namespace Content.Shared.Pinpointer;
  10. public abstract class SharedNavMapSystem : EntitySystem
  11. {
  12. public const int Categories = 3;
  13. public const int Directions = 4; // Not directly tied to number of atmos directions
  14. public const int ChunkSize = 8;
  15. public const int ArraySize = ChunkSize * ChunkSize;
  16. public const int AllDirMask = (1 << Directions) - 1;
  17. public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
  18. public const int WallMask = AllDirMask << (int) NavMapChunkType.Wall;
  19. public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
  20. [Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
  21. [Robust.Shared.IoC.Dependency] private readonly INetManager _net = default!;
  22. private static readonly ProtoId<TagPrototype>[] WallTags = {"Wall", "Window"};
  23. private EntityQuery<NavMapDoorComponent> _doorQuery;
  24. public override void Initialize()
  25. {
  26. base.Initialize();
  27. // Data handling events
  28. SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
  29. _doorQuery = GetEntityQuery<NavMapDoorComponent>();
  30. }
  31. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  32. public static int GetTileIndex(Vector2i relativeTile)
  33. {
  34. return relativeTile.X * ChunkSize + relativeTile.Y;
  35. }
  36. /// <summary>
  37. /// Inverse of <see cref="GetTileIndex"/>
  38. /// </summary>
  39. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  40. public static Vector2i GetTileFromIndex(int index)
  41. {
  42. var x = index / ChunkSize;
  43. var y = index % ChunkSize;
  44. return new Vector2i(x, y);
  45. }
  46. public NavMapChunkType GetEntityType(EntityUid uid)
  47. {
  48. if (_doorQuery.HasComp(uid))
  49. return NavMapChunkType.Airlock;
  50. if (_tagSystem.HasAnyTag(uid, WallTags))
  51. return NavMapChunkType.Wall;
  52. return NavMapChunkType.Invalid;
  53. }
  54. protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, MetaDataComponent meta, [NotNullWhen(true)] out NavMapBeacon? beaconData)
  55. {
  56. beaconData = null;
  57. if (!component.Enabled || xform.GridUid == null || !xform.Anchored)
  58. return false;
  59. var name = component.Text;
  60. if (string.IsNullOrEmpty(name))
  61. name = meta.EntityName;
  62. beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition);
  63. return true;
  64. }
  65. public void AddOrUpdateNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner, NavMapRegionProperties regionProperties)
  66. {
  67. // Check if a new region has been added or an existing one has been altered
  68. var isDirty = !component.RegionProperties.TryGetValue(regionOwner, out var oldProperties) || oldProperties != regionProperties;
  69. if (isDirty)
  70. {
  71. component.RegionProperties[regionOwner] = regionProperties;
  72. if (_net.IsServer)
  73. Dirty(uid, component);
  74. }
  75. }
  76. public void RemoveNavMapRegion(EntityUid uid, NavMapComponent component, NetEntity regionOwner)
  77. {
  78. bool regionOwnerRemoved = component.RegionProperties.Remove(regionOwner) | component.RegionOverlays.Remove(regionOwner);
  79. if (regionOwnerRemoved)
  80. {
  81. if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var affectedChunks))
  82. {
  83. foreach (var affectedChunk in affectedChunks)
  84. {
  85. if (component.ChunkToRegionOwnerTable.TryGetValue(affectedChunk, out var regionOwners))
  86. regionOwners.Remove(regionOwner);
  87. }
  88. component.RegionOwnerToChunkTable.Remove(regionOwner);
  89. }
  90. if (_net.IsServer)
  91. Dirty(uid, component);
  92. }
  93. }
  94. public Dictionary<NetEntity, NavMapRegionOverlay> GetNavMapRegionOverlays(EntityUid uid, NavMapComponent component, Enum uiKey)
  95. {
  96. var regionOverlays = new Dictionary<NetEntity, NavMapRegionOverlay>();
  97. foreach (var (regionOwner, regionOverlay) in component.RegionOverlays)
  98. {
  99. if (!regionOverlay.UiKey.Equals(uiKey))
  100. continue;
  101. regionOverlays.Add(regionOwner, regionOverlay);
  102. }
  103. return regionOverlays;
  104. }
  105. #region: Event handling
  106. private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
  107. {
  108. Dictionary<Vector2i, int[]> chunks;
  109. // Should this be a full component state or a delta-state?
  110. if (args.FromTick <= component.CreationTick)
  111. {
  112. // Full state
  113. chunks = new(component.Chunks.Count);
  114. foreach (var (origin, chunk) in component.Chunks)
  115. {
  116. chunks.Add(origin, chunk.TileData);
  117. }
  118. args.State = new NavMapState(chunks, component.Beacons, component.RegionProperties);
  119. return;
  120. }
  121. chunks = new();
  122. foreach (var (origin, chunk) in component.Chunks)
  123. {
  124. if (chunk.LastUpdate < args.FromTick)
  125. continue;
  126. chunks.Add(origin, chunk.TileData);
  127. }
  128. args.State = new NavMapDeltaState(chunks, component.Beacons, component.RegionProperties, new(component.Chunks.Keys));
  129. }
  130. #endregion
  131. #region: System messages
  132. [Serializable, NetSerializable]
  133. protected sealed class NavMapState(
  134. Dictionary<Vector2i, int[]> chunks,
  135. Dictionary<NetEntity, NavMapBeacon> beacons,
  136. Dictionary<NetEntity, NavMapRegionProperties> regions)
  137. : ComponentState
  138. {
  139. public Dictionary<Vector2i, int[]> Chunks = chunks;
  140. public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
  141. public Dictionary<NetEntity, NavMapRegionProperties> Regions = regions;
  142. }
  143. [Serializable, NetSerializable]
  144. protected sealed class NavMapDeltaState(
  145. Dictionary<Vector2i, int[]> modifiedChunks,
  146. Dictionary<NetEntity, NavMapBeacon> beacons,
  147. Dictionary<NetEntity, NavMapRegionProperties> regions,
  148. HashSet<Vector2i> allChunks)
  149. : ComponentState, IComponentDeltaState<NavMapState>
  150. {
  151. public Dictionary<Vector2i, int[]> ModifiedChunks = modifiedChunks;
  152. public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
  153. public Dictionary<NetEntity, NavMapRegionProperties> Regions = regions;
  154. public HashSet<Vector2i> AllChunks = allChunks;
  155. public void ApplyToFullState(NavMapState state)
  156. {
  157. foreach (var key in state.Chunks.Keys)
  158. {
  159. if (!AllChunks!.Contains(key))
  160. state.Chunks.Remove(key);
  161. }
  162. foreach (var (index, data) in ModifiedChunks)
  163. {
  164. if (!state.Chunks.TryGetValue(index, out var stateValue))
  165. state.Chunks[index] = stateValue = new int[data.Length];
  166. Array.Copy(data, stateValue, data.Length);
  167. }
  168. state.Beacons.Clear();
  169. foreach (var (nuid, beacon) in Beacons)
  170. {
  171. state.Beacons.Add(nuid, beacon);
  172. }
  173. state.Regions.Clear();
  174. foreach (var (nuid, region) in Regions)
  175. {
  176. state.Regions.Add(nuid, region);
  177. }
  178. }
  179. public NavMapState CreateNewFullState(NavMapState state)
  180. {
  181. var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
  182. foreach (var (index, data) in state.Chunks)
  183. {
  184. if (!AllChunks!.Contains(index))
  185. continue;
  186. var newData = chunks[index] = new int[ArraySize];
  187. if (ModifiedChunks.TryGetValue(index, out var updatedData))
  188. Array.Copy(newData, updatedData, ArraySize);
  189. else
  190. Array.Copy(newData, data, ArraySize);
  191. }
  192. return new NavMapState(chunks, new(Beacons), new(Regions));
  193. }
  194. }
  195. [Serializable, NetSerializable]
  196. public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position);
  197. [Serializable, NetSerializable]
  198. public record struct NavMapRegionProperties(NetEntity Owner, Enum UiKey, HashSet<Vector2i> Seeds)
  199. {
  200. // Server defined color for the region
  201. public Color Color = Color.White;
  202. // The maximum number of tiles that can be assigned to this region
  203. public int MaxArea = 625;
  204. // The maximum distance this region can propagate from its seeds
  205. public int MaxRadius = 25;
  206. }
  207. #endregion
  208. }