WeatherSystem.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. using System.Numerics;
  2. using Content.Shared.Light.Components;
  3. using Content.Shared.Weather;
  4. using Robust.Client.Audio;
  5. using Robust.Client.GameObjects;
  6. using Robust.Client.Player;
  7. using Robust.Shared.Audio.Systems;
  8. using Robust.Shared.GameStates;
  9. using Robust.Shared.Map;
  10. using Robust.Shared.Map.Components;
  11. using Robust.Shared.Player;
  12. using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
  13. namespace Content.Client.Weather;
  14. public sealed class WeatherSystem : SharedWeatherSystem
  15. {
  16. [Dependency] private readonly IPlayerManager _playerManager = default!;
  17. [Dependency] private readonly AudioSystem _audio = default!;
  18. [Dependency] private readonly MapSystem _mapSystem = default!;
  19. [Dependency] private readonly SharedTransformSystem _transform = default!;
  20. public override void Initialize()
  21. {
  22. base.Initialize();
  23. SubscribeLocalEvent<WeatherComponent, ComponentHandleState>(OnWeatherHandleState);
  24. }
  25. protected override void Run(EntityUid uid, WeatherData weather, WeatherPrototype weatherProto, float frameTime)
  26. {
  27. base.Run(uid, weather, weatherProto, frameTime);
  28. var ent = _playerManager.LocalEntity;
  29. if (ent == null)
  30. return;
  31. var mapUid = Transform(uid).MapUid;
  32. var entXform = Transform(ent.Value);
  33. // Maybe have the viewports manage this?
  34. if (mapUid == null || entXform.MapUid != mapUid)
  35. {
  36. weather.Stream = _audio.Stop(weather.Stream);
  37. return;
  38. }
  39. if (!Timing.IsFirstTimePredicted || weatherProto.Sound == null)
  40. return;
  41. weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
  42. if (!TryComp(weather.Stream, out AudioComponent? comp))
  43. return;
  44. var occlusion = 0f;
  45. // Work out tiles nearby to determine volume.
  46. if (TryComp<MapGridComponent>(entXform.GridUid, out var grid))
  47. {
  48. TryComp(entXform.GridUid, out RoofComponent? roofComp);
  49. var gridId = entXform.GridUid.Value;
  50. // FloodFill to the nearest tile and use that for audio.
  51. var seed = _mapSystem.GetTileRef(gridId, grid, entXform.Coordinates);
  52. var frontier = new Queue<TileRef>();
  53. frontier.Enqueue(seed);
  54. // If we don't have a nearest node don't play any sound.
  55. EntityCoordinates? nearestNode = null;
  56. var visited = new HashSet<Vector2i>();
  57. while (frontier.TryDequeue(out var node))
  58. {
  59. if (!visited.Add(node.GridIndices))
  60. continue;
  61. if (!CanWeatherAffect(entXform.GridUid.Value, grid, node, roofComp))
  62. {
  63. // Add neighbors
  64. // TODO: Ideally we pick some deterministically random direction and use that
  65. // We can't just do that naively here because it will flicker between nearby tiles.
  66. for (var x = -1; x <= 1; x++)
  67. {
  68. for (var y = -1; y <= 1; y++)
  69. {
  70. if (Math.Abs(x) == 1 && Math.Abs(y) == 1 ||
  71. x == 0 && y == 0 ||
  72. (new Vector2(x, y) + node.GridIndices - seed.GridIndices).Length() > 3)
  73. {
  74. continue;
  75. }
  76. frontier.Enqueue(_mapSystem.GetTileRef(gridId, grid, new Vector2i(x, y) + node.GridIndices));
  77. }
  78. }
  79. continue;
  80. }
  81. nearestNode = new EntityCoordinates(entXform.GridUid.Value,
  82. node.GridIndices + grid.TileSizeHalfVector);
  83. break;
  84. }
  85. // Get occlusion to the targeted node if it exists, otherwise set a default occlusion.
  86. if (nearestNode != null)
  87. {
  88. var entPos = _transform.GetMapCoordinates(entXform);
  89. var nodePosition = _transform.ToMapCoordinates(nearestNode.Value).Position;
  90. var delta = nodePosition - entPos.Position;
  91. var distance = delta.Length();
  92. occlusion = _audio.GetOcclusion(entPos, delta, distance);
  93. }
  94. else
  95. {
  96. occlusion = 3f;
  97. }
  98. }
  99. var alpha = GetPercent(weather, uid);
  100. alpha *= SharedAudioSystem.VolumeToGain(weatherProto.Sound.Params.Volume);
  101. _audio.SetGain(weather.Stream, alpha, comp);
  102. comp.Occlusion = occlusion;
  103. }
  104. protected override bool SetState(EntityUid uid, WeatherState state, WeatherComponent comp, WeatherData weather, WeatherPrototype weatherProto)
  105. {
  106. if (!base.SetState(uid, state, comp, weather, weatherProto))
  107. return false;
  108. if (!Timing.IsFirstTimePredicted)
  109. return true;
  110. // TODO: Fades (properly)
  111. weather.Stream = _audio.Stop(weather.Stream);
  112. weather.Stream = _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
  113. return true;
  114. }
  115. private void OnWeatherHandleState(EntityUid uid, WeatherComponent component, ref ComponentHandleState args)
  116. {
  117. if (args.Current is not WeatherComponentState state)
  118. return;
  119. foreach (var (proto, weather) in component.Weather)
  120. {
  121. // End existing one
  122. if (!state.Weather.TryGetValue(proto, out var stateData))
  123. {
  124. EndWeather(uid, component, proto);
  125. continue;
  126. }
  127. // Data update?
  128. weather.StartTime = stateData.StartTime;
  129. weather.EndTime = stateData.EndTime;
  130. weather.State = stateData.State;
  131. }
  132. foreach (var (proto, weather) in state.Weather)
  133. {
  134. if (component.Weather.ContainsKey(proto))
  135. continue;
  136. // New weather
  137. StartWeather(uid, component, ProtoMan.Index<WeatherPrototype>(proto), weather.EndTime);
  138. }
  139. }
  140. }