GasTileOverlay.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. using System.Numerics;
  2. using Content.Client.Atmos.Components;
  3. using Content.Client.Atmos.EntitySystems;
  4. using Content.Shared.Atmos;
  5. using Content.Shared.Atmos.Components;
  6. using Content.Shared.Atmos.Prototypes;
  7. using Robust.Client.GameObjects;
  8. using Robust.Client.Graphics;
  9. using Robust.Client.ResourceManagement;
  10. using Robust.Shared.Enums;
  11. using Robust.Shared.Graphics.RSI;
  12. using Robust.Shared.Map;
  13. using Robust.Shared.Map.Components;
  14. using Robust.Shared.Prototypes;
  15. using Robust.Shared.Timing;
  16. using Robust.Shared.Utility;
  17. namespace Content.Client.Atmos.Overlays
  18. {
  19. public sealed class GasTileOverlay : Overlay
  20. {
  21. private readonly IEntityManager _entManager;
  22. private readonly IMapManager _mapManager;
  23. private readonly SharedTransformSystem _xformSys;
  24. public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities | OverlaySpace.WorldSpaceBelowWorld;
  25. private readonly ShaderInstance _shader;
  26. // Gas overlays
  27. private readonly float[] _timer;
  28. private readonly float[][] _frameDelays;
  29. private readonly int[] _frameCounter;
  30. // TODO combine textures into a single texture atlas.
  31. private readonly Texture[][] _frames;
  32. // Fire overlays
  33. private const int FireStates = 3;
  34. private const string FireRsiPath = "/Textures/Effects/fire.rsi";
  35. private readonly float[] _fireTimer = new float[FireStates];
  36. private readonly float[][] _fireFrameDelays = new float[FireStates][];
  37. private readonly int[] _fireFrameCounter = new int[FireStates];
  38. private readonly Texture[][] _fireFrames = new Texture[FireStates][];
  39. private int _gasCount;
  40. public const int GasOverlayZIndex = (int) Shared.DrawDepth.DrawDepth.Effects; // Under ghosts, above mostly everything else
  41. public GasTileOverlay(GasTileOverlaySystem system, IEntityManager entManager, IResourceCache resourceCache, IPrototypeManager protoMan, SpriteSystem spriteSys, SharedTransformSystem xformSys)
  42. {
  43. _entManager = entManager;
  44. _mapManager = IoCManager.Resolve<IMapManager>();
  45. _xformSys = xformSys;
  46. _shader = protoMan.Index<ShaderPrototype>("unshaded").Instance();
  47. ZIndex = GasOverlayZIndex;
  48. _gasCount = system.VisibleGasId.Length;
  49. _timer = new float[_gasCount];
  50. _frameDelays = new float[_gasCount][];
  51. _frameCounter = new int[_gasCount];
  52. _frames = new Texture[_gasCount][];
  53. for (var i = 0; i < _gasCount; i++)
  54. {
  55. var gasPrototype = protoMan.Index<GasPrototype>(system.VisibleGasId[i].ToString());
  56. SpriteSpecifier overlay;
  57. if (!string.IsNullOrEmpty(gasPrototype.GasOverlaySprite) && !string.IsNullOrEmpty(gasPrototype.GasOverlayState))
  58. overlay = new SpriteSpecifier.Rsi(new (gasPrototype.GasOverlaySprite), gasPrototype.GasOverlayState);
  59. else if (!string.IsNullOrEmpty(gasPrototype.GasOverlayTexture))
  60. overlay = new SpriteSpecifier.Texture(new (gasPrototype.GasOverlayTexture));
  61. else
  62. continue;
  63. switch (overlay)
  64. {
  65. case SpriteSpecifier.Rsi animated:
  66. var rsi = resourceCache.GetResource<RSIResource>(animated.RsiPath).RSI;
  67. var stateId = animated.RsiState;
  68. if (!rsi.TryGetState(stateId, out var state))
  69. continue;
  70. _frames[i] = state.GetFrames(RsiDirection.South);
  71. _frameDelays[i] = state.GetDelays();
  72. _frameCounter[i] = 0;
  73. break;
  74. case SpriteSpecifier.Texture texture:
  75. _frames[i] = new[] { spriteSys.Frame0(texture) };
  76. _frameDelays[i] = Array.Empty<float>();
  77. break;
  78. }
  79. }
  80. var fire = resourceCache.GetResource<RSIResource>(FireRsiPath).RSI;
  81. for (var i = 0; i < FireStates; i++)
  82. {
  83. if (!fire.TryGetState((i + 1).ToString(), out var state))
  84. throw new ArgumentOutOfRangeException($"Fire RSI doesn't have state \"{i}\"!");
  85. _fireFrames[i] = state.GetFrames(RsiDirection.South);
  86. _fireFrameDelays[i] = state.GetDelays();
  87. _fireFrameCounter[i] = 0;
  88. }
  89. }
  90. protected override void FrameUpdate(FrameEventArgs args)
  91. {
  92. base.FrameUpdate(args);
  93. for (var i = 0; i < _gasCount; i++)
  94. {
  95. var delays = _frameDelays[i];
  96. if (delays.Length == 0)
  97. continue;
  98. var frameCount = _frameCounter[i];
  99. _timer[i] += args.DeltaSeconds;
  100. var time = delays[frameCount];
  101. if (_timer[i] < time)
  102. continue;
  103. _timer[i] -= time;
  104. _frameCounter[i] = (frameCount + 1) % _frames[i].Length;
  105. }
  106. for (var i = 0; i < FireStates; i++)
  107. {
  108. var delays = _fireFrameDelays[i];
  109. if (delays.Length == 0)
  110. continue;
  111. var frameCount = _fireFrameCounter[i];
  112. _fireTimer[i] += args.DeltaSeconds;
  113. var time = delays[frameCount];
  114. if (_fireTimer[i] < time) continue;
  115. _fireTimer[i] -= time;
  116. _fireFrameCounter[i] = (frameCount + 1) % _fireFrames[i].Length;
  117. }
  118. }
  119. protected override void Draw(in OverlayDrawArgs args)
  120. {
  121. if (args.MapId == MapId.Nullspace)
  122. return;
  123. var drawHandle = args.WorldHandle;
  124. var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
  125. var overlayQuery = _entManager.GetEntityQuery<GasTileOverlayComponent>();
  126. var gridState = (args.WorldBounds,
  127. args.WorldHandle,
  128. _gasCount,
  129. _frames,
  130. _frameCounter,
  131. _fireFrames,
  132. _fireFrameCounter,
  133. _shader,
  134. overlayQuery,
  135. xformQuery,
  136. _xformSys);
  137. var mapUid = _mapManager.GetMapEntityId(args.MapId);
  138. if (_entManager.TryGetComponent<MapAtmosphereComponent>(mapUid, out var atmos))
  139. DrawMapOverlay(drawHandle, args, mapUid, atmos);
  140. if (args.Space != OverlaySpace.WorldSpaceEntities)
  141. return;
  142. // TODO: WorldBounds callback.
  143. _mapManager.FindGridsIntersecting(args.MapId, args.WorldAABB, ref gridState,
  144. static (EntityUid uid, MapGridComponent grid,
  145. ref (Box2Rotated WorldBounds,
  146. DrawingHandleWorld drawHandle,
  147. int gasCount,
  148. Texture[][] frames,
  149. int[] frameCounter,
  150. Texture[][] fireFrames,
  151. int[] fireFrameCounter,
  152. ShaderInstance shader,
  153. EntityQuery<GasTileOverlayComponent> overlayQuery,
  154. EntityQuery<TransformComponent> xformQuery,
  155. SharedTransformSystem xformSys) state) =>
  156. {
  157. if (!state.overlayQuery.TryGetComponent(uid, out var comp) ||
  158. !state.xformQuery.TryGetComponent(uid, out var gridXform))
  159. {
  160. return true;
  161. }
  162. var (_, _, worldMatrix, invMatrix) = state.xformSys.GetWorldPositionRotationMatrixWithInv(gridXform);
  163. state.drawHandle.SetTransform(worldMatrix);
  164. var floatBounds = invMatrix.TransformBox(state.WorldBounds).Enlarged(grid.TileSize);
  165. var localBounds = new Box2i(
  166. (int) MathF.Floor(floatBounds.Left),
  167. (int) MathF.Floor(floatBounds.Bottom),
  168. (int) MathF.Ceiling(floatBounds.Right),
  169. (int) MathF.Ceiling(floatBounds.Top));
  170. // Currently it would be faster to group drawing by gas rather than by chunk, but if the textures are
  171. // ever moved to a single atlas, that should no longer be the case. So this is just grouping draw calls
  172. // by chunk, even though its currently slower.
  173. state.drawHandle.UseShader(null);
  174. foreach (var chunk in comp.Chunks.Values)
  175. {
  176. var enumerator = new GasChunkEnumerator(chunk);
  177. while (enumerator.MoveNext(out var gas))
  178. {
  179. if (gas.Opacity == null!)
  180. continue;
  181. var tilePosition = chunk.Origin + (enumerator.X, enumerator.Y);
  182. if (!localBounds.Contains(tilePosition))
  183. continue;
  184. for (var i = 0; i < state.gasCount; i++)
  185. {
  186. var opacity = gas.Opacity[i];
  187. if (opacity > 0)
  188. state.drawHandle.DrawTexture(state.frames[i][state.frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
  189. }
  190. }
  191. }
  192. // And again for fire, with the unshaded shader
  193. state.drawHandle.UseShader(state.shader);
  194. foreach (var chunk in comp.Chunks.Values)
  195. {
  196. var enumerator = new GasChunkEnumerator(chunk);
  197. while (enumerator.MoveNext(out var gas))
  198. {
  199. if (gas.FireState == 0)
  200. continue;
  201. var index = chunk.Origin + (enumerator.X, enumerator.Y);
  202. if (!localBounds.Contains(index))
  203. continue;
  204. var fireState = gas.FireState - 1;
  205. var texture = state.fireFrames[fireState][state.fireFrameCounter[fireState]];
  206. state.drawHandle.DrawTexture(texture, index);
  207. }
  208. }
  209. return true;
  210. });
  211. drawHandle.UseShader(null);
  212. drawHandle.SetTransform(Matrix3x2.Identity);
  213. }
  214. private void DrawMapOverlay(
  215. DrawingHandleWorld handle,
  216. OverlayDrawArgs args,
  217. EntityUid map,
  218. MapAtmosphereComponent atmos)
  219. {
  220. var mapGrid = _entManager.HasComponent<MapGridComponent>(map);
  221. // map-grid atmospheres get drawn above grids
  222. if (mapGrid && args.Space != OverlaySpace.WorldSpaceEntities)
  223. return;
  224. // Normal map atmospheres get drawn below grids
  225. if (!mapGrid && args.Space != OverlaySpace.WorldSpaceBelowWorld)
  226. return;
  227. var bottomLeft = args.WorldAABB.BottomLeft.Floored();
  228. var topRight = args.WorldAABB.TopRight.Ceiled();
  229. for (var x = bottomLeft.X; x <= topRight.X; x++)
  230. {
  231. for (var y = bottomLeft.Y; y <= topRight.Y; y++)
  232. {
  233. var tilePosition = new Vector2(x, y);
  234. for (var i = 0; i < atmos.OverlayData.Opacity.Length; i++)
  235. {
  236. var opacity = atmos.OverlayData.Opacity[i];
  237. if (opacity > 0)
  238. handle.DrawTexture(_frames[i][_frameCounter[i]], tilePosition, Color.White.WithAlpha(opacity));
  239. }
  240. }
  241. }
  242. }
  243. }
  244. }