mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-02-15 00:54:51 +01:00
Merge remote-tracking branch 'wizards/master' into upstream-sync
# Conflicts: # Content.Client/Options/UI/Tabs/AudioTab.xaml.cs # Content.Server/Chat/Managers/ChatManager.cs # Content.Server/Chat/Systems/ChatSystem.cs # Content.Server/GameTicking/GameTicker.StatusShell.cs # Content.Server/RoundEnd/RoundEndSystem.cs # Resources/Prototypes/Datasets/Names/Operation_suffix.yml # Resources/Prototypes/Entities/Mobs/Cyborgs/base_borg_chassis.yml # Resources/Prototypes/Maps/core.yml # Resources/ServerInfo/Guidebook/Cargo/Salvage.xml # Resources/ServerInfo/Guidebook/Science/Robotics.xml
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
//TODO: This is using a incomplete version of the whole "only play nearest sounds" algo, that breaks down a bit should the ambient sound cap get hit.
|
||||
@@ -41,14 +46,18 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
private TimeSpan _targetTime = TimeSpan.Zero;
|
||||
private float _ambienceVolume = 0.0f;
|
||||
|
||||
private static AudioParams _params = AudioParams.Default.WithVariation(0.01f).WithLoop(true).WithAttenuation(Attenuation.LinearDistance);
|
||||
private static AudioParams _params = AudioParams.Default
|
||||
.WithVariation(0.01f)
|
||||
.WithLoop(true)
|
||||
.WithAttenuation(Attenuation.LinearDistance)
|
||||
.WithMaxDistance(7f);
|
||||
|
||||
/// <summary>
|
||||
/// How many times we can be playing 1 particular sound at once.
|
||||
/// </summary>
|
||||
private int MaxSingleSound => (int) (_maxAmbientCount / (16.0f / 6.0f));
|
||||
|
||||
private readonly Dictionary<Entity<AmbientSoundComponent>, (IPlayingAudioStream? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new();
|
||||
private readonly Dictionary<AmbientSoundComponent, (EntityUid? Stream, SoundSpecifier Sound, string Path)> _playingSounds = new();
|
||||
private readonly Dictionary<string, int> _playingCount = new();
|
||||
|
||||
public bool OverlayEnabled
|
||||
@@ -98,10 +107,10 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
private void OnShutdown(EntityUid uid, AmbientSoundComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (!_playingSounds.Remove((uid, component), out var sound))
|
||||
if (!_playingSounds.Remove(component, out var sound))
|
||||
return;
|
||||
|
||||
sound.Stream?.Stop();
|
||||
_audio.Stop(sound.Stream);
|
||||
_playingCount[sound.Path] -= 1;
|
||||
if (_playingCount[sound.Path] == 0)
|
||||
_playingCount.Remove(sound.Path);
|
||||
@@ -111,13 +120,13 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
{
|
||||
_ambienceVolume = value;
|
||||
|
||||
foreach (var ((_, comp), values) in _playingSounds)
|
||||
foreach (var (comp, values) in _playingSounds)
|
||||
{
|
||||
if (values.Stream == null)
|
||||
continue;
|
||||
|
||||
var stream = (AudioSystem.PlayingStream) values.Stream;
|
||||
stream.Volume = _params.Volume + comp.Volume + _ambienceVolume;
|
||||
var stream = values.Stream;
|
||||
_audio.SetVolume(stream, _params.Volume + comp.Volume + _ambienceVolume);
|
||||
}
|
||||
}
|
||||
private void SetCooldown(float value) => _cooldown = value;
|
||||
@@ -177,7 +186,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
{
|
||||
foreach (var (stream, _, _) in _playingSounds.Values)
|
||||
{
|
||||
stream?.Stop();
|
||||
_audio.Stop(stream);
|
||||
}
|
||||
|
||||
_playingSounds.Clear();
|
||||
@@ -186,7 +195,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
private readonly struct QueryState
|
||||
{
|
||||
public readonly Dictionary<string, List<(float Importance, Entity<AmbientSoundComponent>)>> SourceDict = new();
|
||||
public readonly Dictionary<string, List<(float Importance, AmbientSoundComponent)>> SourceDict = new();
|
||||
public readonly Vector2 MapPos;
|
||||
public readonly TransformComponent Player;
|
||||
public readonly EntityQuery<TransformComponent> Query;
|
||||
@@ -224,7 +233,7 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
// Prioritize far away & loud sounds.
|
||||
var importance = range * (ambientComp.Volume + 32);
|
||||
state.SourceDict.GetOrNew(key).Add((importance, (ambientComp.Owner, ambientComp)));
|
||||
state.SourceDict.GetOrNew(key).Add((importance, ambientComp));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,10 +247,9 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
var mapPos = playerXform.MapPosition;
|
||||
|
||||
// Remove out-of-range ambiences
|
||||
foreach (var (ent, sound) in _playingSounds)
|
||||
foreach (var (comp, sound) in _playingSounds)
|
||||
{
|
||||
var entity = ent.Owner;
|
||||
var comp = ent.Comp;
|
||||
var entity = comp.Owner;
|
||||
|
||||
if (comp.Enabled &&
|
||||
// Don't keep playing sounds that have changed since.
|
||||
@@ -258,8 +266,8 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
sound.Stream?.Stop();
|
||||
_playingSounds.Remove((entity, comp));
|
||||
_audio.Stop(sound.Stream);
|
||||
_playingSounds.Remove(comp);
|
||||
_playingCount[sound.Path] -= 1;
|
||||
if (_playingCount[sound.Path] == 0)
|
||||
_playingCount.Remove(sound.Path);
|
||||
@@ -284,12 +292,11 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
|
||||
sources.Sort(static (a, b) => b.Importance.CompareTo(a.Importance));
|
||||
|
||||
foreach (var (_, ent) in sources)
|
||||
foreach (var (_, comp) in sources)
|
||||
{
|
||||
var uid = ent.Owner;
|
||||
var comp = ent.Comp;
|
||||
var uid = comp.Owner;
|
||||
|
||||
if (_playingSounds.ContainsKey(ent) ||
|
||||
if (_playingSounds.ContainsKey(comp) ||
|
||||
metaQuery.GetComponent(uid).EntityPaused)
|
||||
continue;
|
||||
|
||||
@@ -299,11 +306,8 @@ public sealed class AmbientSoundSystem : SharedAmbientSoundSystem
|
||||
.WithPlayOffset(_random.NextFloat(0.0f, 100.0f))
|
||||
.WithMaxDistance(comp.Range);
|
||||
|
||||
var stream = _audio.PlayPvs(comp.Sound, uid, audioParams);
|
||||
if (stream == null)
|
||||
continue;
|
||||
|
||||
_playingSounds[ent] = (stream, comp.Sound, key);
|
||||
var stream = _audio.PlayEntity(comp.Sound, Filter.Local(), uid, false, audioParams);
|
||||
_playingSounds[comp] = (stream.Value.Entity, comp.Sound, key);
|
||||
playingCount++;
|
||||
|
||||
if (_playingSounds.Count >= _maxAmbientCount)
|
||||
|
||||
@@ -5,6 +5,7 @@ using JetBrains.Annotations;
|
||||
using Robust.Client;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -21,7 +22,7 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
|
||||
private readonly AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, 0, true, 0f);
|
||||
|
||||
private IPlayingAudioStream? _lobbyStream;
|
||||
private EntityUid? _lobbyStream;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -118,12 +119,11 @@ public sealed class BackgroundAudioSystem : EntitySystem
|
||||
}
|
||||
|
||||
_lobbyStream = _audio.PlayGlobal(file, Filter.Local(), false,
|
||||
_lobbyParams.WithVolume(_lobbyParams.Volume + _configManager.GetCVar(CCVars.LobbyMusicVolume)));
|
||||
_lobbyParams.WithVolume(_lobbyParams.Volume + _configManager.GetCVar(CCVars.LobbyMusicVolume)))?.Entity;
|
||||
}
|
||||
|
||||
private void EndLobbyMusic()
|
||||
{
|
||||
_lobbyStream?.Stop();
|
||||
_lobbyStream = null;
|
||||
_lobbyStream = _audio.Stop(_lobbyStream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
@@ -14,11 +15,11 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
|
||||
// Admin music
|
||||
private bool _adminAudioEnabled = true;
|
||||
private List<IPlayingAudioStream?> _adminAudio = new(1);
|
||||
private List<EntityUid?> _adminAudio = new(1);
|
||||
|
||||
// Event sounds (e.g. nuke timer)
|
||||
private bool _eventAudioEnabled = true;
|
||||
private Dictionary<StationEventMusicType, IPlayingAudioStream?> _eventAudio = new(1);
|
||||
private Dictionary<StationEventMusicType, EntityUid?> _eventAudio = new(1);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -49,13 +50,13 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
{
|
||||
foreach (var stream in _adminAudio)
|
||||
{
|
||||
stream?.Stop();
|
||||
_audio.Stop(stream);
|
||||
}
|
||||
_adminAudio.Clear();
|
||||
|
||||
foreach (var (_, stream) in _eventAudio)
|
||||
foreach (var stream in _eventAudio.Values)
|
||||
{
|
||||
stream?.Stop();
|
||||
_audio.Stop(stream);
|
||||
}
|
||||
|
||||
_eventAudio.Clear();
|
||||
@@ -66,7 +67,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if(!_adminAudioEnabled) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_adminAudio.Add(stream);
|
||||
_adminAudio.Add(stream.Value.Entity);
|
||||
}
|
||||
|
||||
private void PlayStationEventMusic(StationEventMusicEvent soundEvent)
|
||||
@@ -75,7 +76,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if(!_eventAudioEnabled || _eventAudio.ContainsKey(soundEvent.Type)) return;
|
||||
|
||||
var stream = _audio.PlayGlobal(soundEvent.Filename, Filter.Local(), false, soundEvent.AudioParams);
|
||||
_eventAudio.Add(soundEvent.Type, stream);
|
||||
_eventAudio.Add(soundEvent.Type, stream.Value.Entity);
|
||||
}
|
||||
|
||||
private void PlayGameSound(GameGlobalSoundEvent soundEvent)
|
||||
@@ -85,8 +86,10 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
|
||||
private void StopStationEventMusic(StopStationEventMusic soundEvent)
|
||||
{
|
||||
if (!_eventAudio.TryGetValue(soundEvent.Type, out var stream)) return;
|
||||
stream?.Stop();
|
||||
if (!_eventAudio.TryGetValue(soundEvent.Type, out var stream))
|
||||
return;
|
||||
|
||||
_audio.Stop(stream);
|
||||
_eventAudio.Remove(soundEvent.Type);
|
||||
}
|
||||
|
||||
@@ -96,7 +99,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if (_adminAudioEnabled) return;
|
||||
foreach (var stream in _adminAudio)
|
||||
{
|
||||
stream?.Stop();
|
||||
_audio.Stop(stream);
|
||||
}
|
||||
_adminAudio.Clear();
|
||||
}
|
||||
@@ -107,7 +110,7 @@ public sealed class ClientGlobalSoundSystem : SharedGlobalSoundSystem
|
||||
if (_eventAudioEnabled) return;
|
||||
foreach (var stream in _eventAudio)
|
||||
{
|
||||
stream.Value?.Stop();
|
||||
_audio.Stop(stream.Value);
|
||||
}
|
||||
_eventAudio.Clear();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ using Robust.Client.Player;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Components;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -39,7 +41,7 @@ public sealed partial class ContentAudioSystem
|
||||
// Don't need to worry about this being serializable or pauseable as it doesn't affect the sim.
|
||||
private TimeSpan _nextAudio;
|
||||
|
||||
private AudioSystem.PlayingStream? _ambientMusicStream;
|
||||
private EntityUid? _ambientMusicStream;
|
||||
private AmbientMusicPrototype? _musicProto;
|
||||
|
||||
/// <summary>
|
||||
@@ -58,12 +60,6 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
private void InitializeAmbientMusic()
|
||||
{
|
||||
// TODO: Shitty preload
|
||||
foreach (var audio in _proto.Index<SoundCollectionPrototype>("AmbienceSpace").PickFiles)
|
||||
{
|
||||
_resource.GetResource<AudioResource>(audio.ToString());
|
||||
}
|
||||
|
||||
_configManager.OnValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
|
||||
_sawmill = IoCManager.Resolve<ILogManager>().GetSawmill("audio.ambience");
|
||||
|
||||
@@ -83,7 +79,7 @@ public sealed partial class ContentAudioSystem
|
||||
|
||||
if (_ambientMusicStream != null && _musicProto != null)
|
||||
{
|
||||
_ambientMusicStream.Volume = _musicProto.Sound.Params.Volume + _volumeSlider;
|
||||
_audio.SetVolume(_ambientMusicStream, _musicProto.Sound.Params.Volume + _volumeSlider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +88,7 @@ public sealed partial class ContentAudioSystem
|
||||
_configManager.UnsubValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged);
|
||||
_proto.PrototypesReloaded -= OnProtoReload;
|
||||
_state.OnStateChanged -= OnStateChange;
|
||||
_ambientMusicStream?.Stop();
|
||||
_ambientMusicStream = _audio.Stop(_ambientMusicStream);
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
@@ -129,8 +125,7 @@ public sealed partial class ContentAudioSystem
|
||||
private void OnRoundEndMessage(RoundEndMessageEvent ev)
|
||||
{
|
||||
// If scoreboard shows then just stop the music
|
||||
_ambientMusicStream?.Stop();
|
||||
_ambientMusicStream = null;
|
||||
_ambientMusicStream = _audio.Stop(_ambientMusicStream);
|
||||
_nextAudio = TimeSpan.FromMinutes(3);
|
||||
}
|
||||
|
||||
@@ -170,15 +165,20 @@ public sealed partial class ContentAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var isDone = _ambientMusicStream?.Done;
|
||||
bool? isDone = null;
|
||||
|
||||
if (TryComp(_ambientMusicStream, out AudioComponent? audioComp))
|
||||
{
|
||||
isDone = !audioComp.Playing;
|
||||
}
|
||||
|
||||
if (_interruptable)
|
||||
{
|
||||
var player = _player.LocalPlayer?.ControlledEntity;
|
||||
var player = _player.LocalSession?.AttachedEntity;
|
||||
|
||||
if (player == null || _musicProto == null || !_rules.IsTrue(player.Value, _proto.Index<RulesPrototype>(_musicProto.Rules)))
|
||||
{
|
||||
FadeOut(_ambientMusicStream, AmbientMusicFadeTime);
|
||||
FadeOut(_ambientMusicStream, duration: AmbientMusicFadeTime);
|
||||
_musicProto = null;
|
||||
_interruptable = false;
|
||||
isDone = true;
|
||||
@@ -221,14 +221,11 @@ public sealed partial class ContentAudioSystem
|
||||
false,
|
||||
AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
|
||||
|
||||
if (strim != null)
|
||||
{
|
||||
_ambientMusicStream = (AudioSystem.PlayingStream) strim;
|
||||
_ambientMusicStream = strim.Value.Entity;
|
||||
|
||||
if (_musicProto.FadeIn)
|
||||
{
|
||||
FadeIn(_ambientMusicStream, AmbientMusicFadeTime);
|
||||
}
|
||||
if (_musicProto.FadeIn)
|
||||
{
|
||||
FadeIn(_ambientMusicStream, strim.Value.Component, AmbientMusicFadeTime);
|
||||
}
|
||||
|
||||
// Refresh the list
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
using Content.Shared.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
|
||||
|
||||
namespace Content.Client.Audio;
|
||||
|
||||
public sealed partial class ContentAudioSystem : SharedContentAudioSystem
|
||||
{
|
||||
// Need how much volume to change per tick and just remove it when it drops below "0"
|
||||
private readonly Dictionary<AudioSystem.PlayingStream, float> _fadingOut = new();
|
||||
private readonly Dictionary<EntityUid, float> _fadingOut = new();
|
||||
|
||||
// Need volume change per tick + target volume.
|
||||
private readonly Dictionary<AudioSystem.PlayingStream, (float VolumeChange, float TargetVolume)> _fadingIn = new();
|
||||
private readonly Dictionary<EntityUid, (float VolumeChange, float TargetVolume)> _fadingIn = new();
|
||||
|
||||
private readonly List<AudioSystem.PlayingStream> _fadeToRemove = new();
|
||||
private readonly List<EntityUid> _fadeToRemove = new();
|
||||
|
||||
private const float MinVolume = -32f;
|
||||
private const float DefaultDuration = 2f;
|
||||
@@ -42,28 +44,28 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
|
||||
|
||||
#region Fades
|
||||
|
||||
public void FadeOut(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
|
||||
public void FadeOut(EntityUid? stream, AudioComponent? component = null, float duration = DefaultDuration)
|
||||
{
|
||||
if (stream == null || duration <= 0f)
|
||||
if (stream == null || duration <= 0f || !Resolve(stream.Value, ref component))
|
||||
return;
|
||||
|
||||
// Just in case
|
||||
// TODO: Maybe handle the removals by making it seamless?
|
||||
_fadingIn.Remove(stream);
|
||||
var diff = stream.Volume - MinVolume;
|
||||
_fadingOut.Add(stream, diff / duration);
|
||||
_fadingIn.Remove(stream.Value);
|
||||
var diff = component.Volume - MinVolume;
|
||||
_fadingOut.Add(stream.Value, diff / duration);
|
||||
}
|
||||
|
||||
public void FadeIn(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
|
||||
public void FadeIn(EntityUid? stream, AudioComponent? component = null, float duration = DefaultDuration)
|
||||
{
|
||||
if (stream == null || duration <= 0f || stream.Volume < MinVolume)
|
||||
if (stream == null || duration <= 0f || !Resolve(stream.Value, ref component) || component.Volume < MinVolume)
|
||||
return;
|
||||
|
||||
_fadingOut.Remove(stream);
|
||||
var curVolume = stream.Volume;
|
||||
_fadingOut.Remove(stream.Value);
|
||||
var curVolume = component.Volume;
|
||||
var change = (curVolume - MinVolume) / duration;
|
||||
_fadingIn.Add(stream, (change, stream.Volume));
|
||||
stream.Volume = MinVolume;
|
||||
_fadingIn.Add(stream.Value, (change, component.Volume));
|
||||
component.Volume = MinVolume;
|
||||
}
|
||||
|
||||
private void UpdateFades(float frameTime)
|
||||
@@ -72,19 +74,18 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
|
||||
|
||||
foreach (var (stream, change) in _fadingOut)
|
||||
{
|
||||
// Cancelled elsewhere
|
||||
if (stream.Done)
|
||||
if (!TryComp(stream, out AudioComponent? component))
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
var volume = stream.Volume - change * frameTime;
|
||||
stream.Volume = MathF.Max(MinVolume, volume);
|
||||
var volume = component.Volume - change * frameTime;
|
||||
component.Volume = MathF.Max(MinVolume, volume);
|
||||
|
||||
if (stream.Volume.Equals(MinVolume))
|
||||
if (component.Volume.Equals(MinVolume))
|
||||
{
|
||||
stream.Stop();
|
||||
_audio.Stop(stream);
|
||||
_fadeToRemove.Add(stream);
|
||||
}
|
||||
}
|
||||
@@ -99,16 +100,16 @@ public sealed partial class ContentAudioSystem : SharedContentAudioSystem
|
||||
foreach (var (stream, (change, target)) in _fadingIn)
|
||||
{
|
||||
// Cancelled elsewhere
|
||||
if (stream.Done)
|
||||
if (!TryComp(stream, out AudioComponent? component))
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
continue;
|
||||
}
|
||||
|
||||
var volume = stream.Volume + change * frameTime;
|
||||
stream.Volume = MathF.Min(target, volume);
|
||||
var volume = component.Volume + change * frameTime;
|
||||
component.Volume = MathF.Min(target, volume);
|
||||
|
||||
if (stream.Volume.Equals(target))
|
||||
if (component.Volume.Equals(target))
|
||||
{
|
||||
_fadeToRemove.Add(stream);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
using static Content.Client.Changelog.ChangelogManager;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -23,7 +24,7 @@ namespace Content.Client.Credits
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class CreditsWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private static readonly Dictionary<string, int> PatronTierPriority = new()
|
||||
@@ -49,7 +50,7 @@ namespace Content.Client.Credits
|
||||
|
||||
private void PopulateLicenses(BoxContainer licensesContainer)
|
||||
{
|
||||
foreach (var entry in CreditsManager.GetLicenses().OrderBy(p => p.Name))
|
||||
foreach (var entry in CreditsManager.GetLicenses(_resourceManager).OrderBy(p => p.Name))
|
||||
{
|
||||
licensesContainer.AddChild(new Label {StyleClasses = {StyleBase.StyleClassLabelHeading}, Text = entry.Name});
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ using Content.Shared.Emag.Systems;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using static Content.Shared.Disposal.Components.SharedDisposalUnitComponent;
|
||||
|
||||
@@ -138,6 +138,6 @@ public sealed class DoorSystem : SharedDoorSystem
|
||||
protected override void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
|
||||
{
|
||||
if (GameTiming.InPrediction && GameTiming.IsFirstTimePredicted)
|
||||
Audio.Play(soundSpecifier, Filter.Local(), uid, false, audioParams);
|
||||
Audio.PlayEntity(soundSpecifier, Filter.Local(), uid, false, audioParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ using Content.Shared.GameWindow;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.State;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Shared.Camera;
|
||||
using Content.Shared.Gravity;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ using Content.Shared.Tag;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Content.Client.Info
|
||||
{
|
||||
public sealed class RulesAndInfoWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IResourceCache _resourceManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resourceManager = default!;
|
||||
[Dependency] private readonly RulesManager _rules = default!;
|
||||
|
||||
public RulesAndInfoWindow()
|
||||
|
||||
@@ -124,7 +124,6 @@ public sealed class InstrumentSystem : SharedInstrumentSystem
|
||||
|
||||
instrument.SequenceDelay = 0;
|
||||
instrument.SequenceStartTick = 0;
|
||||
_midiManager.OcclusionCollisionMask = (int) CollisionGroup.Impassable;
|
||||
instrument.Renderer = _midiManager.GetNewRenderer();
|
||||
|
||||
if (instrument.Renderer != null)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Client.IoC
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed partial class ExpendableLightComponent : SharedExpendableLightComp
|
||||
/// The sound that plays when the expendable light is lit.
|
||||
/// </summary>
|
||||
[Access(typeof(ExpendableLightSystem))]
|
||||
public IPlayingAudioStream? PlayingStream;
|
||||
public EntityUid? PlayingStream;
|
||||
}
|
||||
|
||||
public enum ExpendableLightVisualLayers : byte
|
||||
|
||||
@@ -2,6 +2,8 @@ using Content.Client.Light.Components;
|
||||
using Content.Shared.Light.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Client.Light.EntitySystems;
|
||||
|
||||
@@ -19,7 +21,7 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
|
||||
|
||||
private void OnLightShutdown(EntityUid uid, ExpendableLightComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.PlayingStream?.Stop();
|
||||
component.PlayingStream = _audioSystem.Stop(component.PlayingStream);
|
||||
}
|
||||
|
||||
protected override void OnAppearanceChange(EntityUid uid, ExpendableLightComponent comp, ref AppearanceChangeEvent args)
|
||||
@@ -48,12 +50,10 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
|
||||
switch (state)
|
||||
{
|
||||
case ExpendableLightState.Lit:
|
||||
comp.PlayingStream?.Stop();
|
||||
_audioSystem.Stop(comp.PlayingStream);
|
||||
comp.PlayingStream = _audioSystem.PlayPvs(
|
||||
comp.LoopedSound,
|
||||
uid,
|
||||
SharedExpendableLightComponent.LoopedSoundParams
|
||||
);
|
||||
comp.LoopedSound, uid, SharedExpendableLightComponent.LoopedSoundParams)?.Entity;
|
||||
|
||||
if (args.Sprite.LayerMapTryGet(ExpendableLightVisualLayers.Overlay, out var layerIdx, true))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(comp.IconStateLit))
|
||||
@@ -73,7 +73,7 @@ public sealed class ExpendableLightSystem : VisualizerSystem<ExpendableLightComp
|
||||
|
||||
break;
|
||||
case ExpendableLightState.Dead:
|
||||
comp.PlayingStream?.Stop();
|
||||
comp.PlayingStream = _audioSystem.Stop(comp.PlayingStream);
|
||||
if (args.Sprite.LayerMapTryGet(ExpendableLightVisualLayers.Overlay, out layerIdx, true))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(comp.IconStateSpent))
|
||||
|
||||
@@ -2,6 +2,8 @@ using Content.Shared.Light;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Client.Light.Visualizers;
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace Content.Client.Lobby.UI
|
||||
OverrideDirection = Direction.South,
|
||||
Scale = new Vector2(4f, 4f),
|
||||
MaxSize = new Vector2(112, 112),
|
||||
Stretch = SpriteView.StretchMode.None,
|
||||
Stretch = SpriteView.StretchMode.Fill,
|
||||
};
|
||||
spriteView.SetEntity(_previewDummy.Value);
|
||||
_viewBox.AddChild(spriteView);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Corvax.CCCVars;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
@@ -15,13 +17,14 @@ namespace Content.Client.Options.UI.Tabs
|
||||
public sealed partial class AudioTab : Control
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClydeAudio _clydeAudio = default!;
|
||||
private readonly AudioSystem _audio;
|
||||
|
||||
public AudioTab()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_audio = IoCManager.Resolve<IEntityManager>().System<AudioSystem>();
|
||||
LobbyMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.LobbyMusicEnabled);
|
||||
RestartSoundsCheckBox.Pressed = _cfg.GetCVar(CCVars.RestartSoundsEnabled);
|
||||
EventMusicCheckBox.Pressed = _cfg.GetCVar(CCVars.EventMusicEnabled);
|
||||
@@ -82,7 +85,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
|
||||
private void OnMasterVolumeSliderChanged(Range range)
|
||||
{
|
||||
_clydeAudio.SetMasterVolume(MasterVolumeSlider.Value / 100);
|
||||
_audio.SetMasterVolume(MasterVolumeSlider.Value / 100);
|
||||
UpdateChanges();
|
||||
}
|
||||
|
||||
@@ -118,7 +121,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
|
||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
_cfg.SetCVar(CVars.AudioMasterVolume, MasterVolumeSlider.Value / 100);
|
||||
_cfg.SetCVar(CVars.AudioMasterVolume, LV100ToDB(MasterVolumeSlider.Value, CCVars.MasterMultiplier));
|
||||
// Want the CVar updated values to have the multiplier applied
|
||||
// For the UI we just display 0-100 still elsewhere
|
||||
_cfg.SetCVar(CVars.MidiVolume, LV100ToDB(MidiVolumeSlider.Value, CCVars.MidiMultiplier));
|
||||
@@ -143,7 +146,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
MasterVolumeSlider.Value = _cfg.GetCVar(CVars.AudioMasterVolume) * 100;
|
||||
MasterVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CVars.AudioMasterVolume), CCVars.MasterMultiplier);
|
||||
MidiVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CVars.MidiVolume), CCVars.MidiMultiplier);
|
||||
AmbienceVolumeSlider.Value = DBToLV100(_cfg.GetCVar(CCVars.AmbienceVolume), CCVars.AmbienceMultiplier);
|
||||
AmbientMusicVolumeSlider.Value =
|
||||
@@ -162,8 +165,8 @@ namespace Content.Client.Options.UI.Tabs
|
||||
// Do be sure to rename the setting though
|
||||
private float DBToLV100(float db, float multiplier = 1f)
|
||||
{
|
||||
var weh = (float) (Math.Pow(10, db / 10) * 100 / multiplier);
|
||||
return weh;
|
||||
var beri = (float) (Math.Pow(10, db / 10) * 100 / multiplier);
|
||||
return beri;
|
||||
}
|
||||
|
||||
private float LV100ToDB(float lv100, float multiplier = 1f)
|
||||
@@ -176,7 +179,7 @@ namespace Content.Client.Options.UI.Tabs
|
||||
private void UpdateChanges()
|
||||
{
|
||||
var isMasterVolumeSame =
|
||||
Math.Abs(MasterVolumeSlider.Value - _cfg.GetCVar(CVars.AudioMasterVolume) * 100) < 0.01f;
|
||||
Math.Abs(MasterVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CVars.AudioMasterVolume), CCVars.MasterMultiplier)) < 0.01f;
|
||||
var isMidiVolumeSame =
|
||||
Math.Abs(MidiVolumeSlider.Value - DBToLV100(_cfg.GetCVar(CVars.MidiVolume), CCVars.MidiMultiplier)) < 0.01f;
|
||||
var isAmbientVolumeSame =
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Mindshield.Components;
|
||||
using Content.Shared.Overlays;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.StatusIcon;
|
||||
@@ -66,6 +67,12 @@ public sealed class ShowSecurityIconsSystem : EquipmentHudSystem<ShowSecurityIco
|
||||
else
|
||||
Log.Error($"Invalid job icon prototype: {jobIcon}");
|
||||
|
||||
if (TryComp<MindShieldComponent>(uid, out var comp))
|
||||
{
|
||||
if (_prototypeMan.TryIndex<StatusIconPrototype>(comp.MindShieldStatusIcon.Id, out var icon))
|
||||
result.Add(icon);
|
||||
}
|
||||
|
||||
// Add arrest icons here, WYCI.
|
||||
|
||||
return result;
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Content.Client.Overlays;
|
||||
|
||||
public sealed partial class StencilOverlay
|
||||
{
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto, float alpha, Matrix3 invMatrix)
|
||||
{
|
||||
var worldHandle = args.WorldHandle;
|
||||
@@ -21,15 +23,13 @@ public sealed partial class StencilOverlay
|
||||
// particularly for planet maps or stations.
|
||||
worldHandle.RenderInRenderTarget(_blep!, () =>
|
||||
{
|
||||
var bodyQuery = _entManager.GetEntityQuery<PhysicsComponent>();
|
||||
var xformQuery = _entManager.GetEntityQuery<TransformComponent>();
|
||||
var weatherIgnoreQuery = _entManager.GetEntityQuery<IgnoreWeatherComponent>();
|
||||
_grids.Clear();
|
||||
|
||||
// idk if this is safe to cache in a field and clear sloth help
|
||||
var grids = new List<Entity<MapGridComponent>>();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref grids);
|
||||
_mapManager.FindGridsIntersecting(mapId, worldAABB, ref _grids);
|
||||
|
||||
foreach (var grid in grids)
|
||||
foreach (var grid in _grids)
|
||||
{
|
||||
var matrix = _transform.GetWorldMatrix(grid, xformQuery);
|
||||
Matrix3.Multiply(in matrix, in invMatrix, out var matty);
|
||||
@@ -38,7 +38,7 @@ public sealed partial class StencilOverlay
|
||||
foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
|
||||
{
|
||||
// Ignored tiles for stencil
|
||||
if (_weather.CanWeatherAffect(grid, tile, weatherIgnoreQuery, bodyQuery))
|
||||
if (_weather.CanWeatherAffect(grid, tile))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -57,16 +57,17 @@ public sealed partial class GeneratedParallaxTextureSource : IParallaxTextureSou
|
||||
}
|
||||
|
||||
var debugParallax = IoCManager.Resolve<IConfigurationManager>().GetCVar(CCVars.ParallaxDebug);
|
||||
var resManager = IoCManager.Resolve<IResourceManager>();
|
||||
|
||||
if (debugParallax
|
||||
|| !StaticIoC.ResC.UserData.TryReadAllText(PreviousParallaxConfigPath, out var previousParallaxConfig)
|
||||
|| !resManager.UserData.TryReadAllText(PreviousParallaxConfigPath, out var previousParallaxConfig)
|
||||
|| previousParallaxConfig != parallaxConfig)
|
||||
{
|
||||
var table = Toml.ReadString(parallaxConfig);
|
||||
await UpdateCachedTexture(table, debugParallax, cancel);
|
||||
|
||||
//Update the previous config
|
||||
using var writer = StaticIoC.ResC.UserData.OpenWriteText(PreviousParallaxConfigPath);
|
||||
using var writer = resManager.UserData.OpenWriteText(PreviousParallaxConfigPath);
|
||||
writer.Write(parallaxConfig);
|
||||
}
|
||||
|
||||
@@ -81,7 +82,7 @@ public sealed partial class GeneratedParallaxTextureSource : IParallaxTextureSou
|
||||
try
|
||||
{
|
||||
// Also try to at least sort of fix this if we've been fooled by a config backup
|
||||
StaticIoC.ResC.UserData.Delete(PreviousParallaxConfigPath);
|
||||
resManager.UserData.Delete(PreviousParallaxConfigPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -104,31 +105,34 @@ public sealed partial class GeneratedParallaxTextureSource : IParallaxTextureSou
|
||||
// And load it in the main thread for safety reasons.
|
||||
// But before spending time saving it, make sure to exit out early if it's not wanted.
|
||||
cancel.ThrowIfCancellationRequested();
|
||||
var resManager = IoCManager.Resolve<IResourceManager>();
|
||||
|
||||
// Store it and CRC so further game starts don't need to regenerate it.
|
||||
using var imageStream = StaticIoC.ResC.UserData.OpenWrite(ParallaxCachedImagePath);
|
||||
newParallexImage.SaveAsPng(imageStream);
|
||||
await using var imageStream = resManager.UserData.OpenWrite(ParallaxCachedImagePath);
|
||||
await newParallexImage.SaveAsPngAsync(imageStream, cancel);
|
||||
|
||||
if (saveDebugLayers)
|
||||
{
|
||||
for (var i = 0; i < debugImages!.Count; i++)
|
||||
{
|
||||
var debugImage = debugImages[i];
|
||||
using var debugImageStream = StaticIoC.ResC.UserData.OpenWrite(new ResPath($"/parallax_{Identifier}debug_{i}.png"));
|
||||
debugImage.SaveAsPng(debugImageStream);
|
||||
await using var debugImageStream = resManager.UserData.OpenWrite(new ResPath($"/parallax_{Identifier}debug_{i}.png"));
|
||||
await debugImage.SaveAsPngAsync(debugImageStream, cancel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Texture GetCachedTexture()
|
||||
{
|
||||
using var imageStream = StaticIoC.ResC.UserData.OpenRead(ParallaxCachedImagePath);
|
||||
var resManager = IoCManager.Resolve<IResourceManager>();
|
||||
using var imageStream = resManager.UserData.OpenRead(ParallaxCachedImagePath);
|
||||
return Texture.LoadFromPNGStream(imageStream, "Parallax");
|
||||
}
|
||||
|
||||
private string? GetParallaxConfig()
|
||||
{
|
||||
if (!StaticIoC.ResC.TryContentFileRead(ParallaxConfigPath, out var configStream))
|
||||
var resManager = IoCManager.Resolve<IResourceManager>();
|
||||
if (!resManager.TryContentFileRead(ParallaxConfigPath, out var configStream))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,6 @@ public sealed class ContentReplayPlaybackManager
|
||||
{
|
||||
case RoundEndMessageEvent:
|
||||
case PopupEvent:
|
||||
case AudioMessage:
|
||||
case PickupAnimationEvent:
|
||||
case MeleeLungeEvent:
|
||||
case SharedGunSystem.HitscanEvent:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Content.Shared.Research.Prototypes;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -12,6 +12,9 @@ namespace Content.Client.Research.UI;
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class MiniTechnologyCardControl : Control
|
||||
{
|
||||
/// The technology that this control represents
|
||||
public readonly TechnologyPrototype Technology;
|
||||
|
||||
public MiniTechnologyCardControl(TechnologyPrototype technology, IPrototypeManager prototypeManager, SpriteSystem spriteSys, FormattedMessage description)
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
@@ -24,5 +27,6 @@ public sealed partial class MiniTechnologyCardControl : Control
|
||||
var tooltip = new Tooltip();
|
||||
tooltip.SetMessage(description);
|
||||
Main.TooltipSupplier = _ => tooltip;
|
||||
Technology = technology;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Client.UserInterface.Controls;
|
||||
using Content.Shared.Access.Components;
|
||||
@@ -48,16 +49,10 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
|
||||
|
||||
public void UpdatePanels(ResearchConsoleBoundInterfaceState state)
|
||||
{
|
||||
var allTech = _research.GetAvailableTechnologies(Entity);
|
||||
AvailableCardsContainer.Children.Clear();
|
||||
TechnologyCardsContainer.Children.Clear();
|
||||
UnlockedCardsContainer.Children.Clear();
|
||||
|
||||
foreach (var tech in allTech)
|
||||
{
|
||||
var mini = new MiniTechnologyCardControl(tech, _prototype, _sprite, _research.GetTechnologyDescription(tech));
|
||||
AvailableCardsContainer.AddChild(mini);
|
||||
}
|
||||
var availableTech = _research.GetAvailableTechnologies(Entity);
|
||||
SyncTechnologyList(AvailableCardsContainer, availableTech);
|
||||
|
||||
if (_technologyDatabase == null)
|
||||
return;
|
||||
@@ -79,12 +74,8 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
|
||||
TechnologyCardsContainer.AddChild(cardControl);
|
||||
}
|
||||
|
||||
foreach (var unlocked in _technologyDatabase.UnlockedTechnologies)
|
||||
{
|
||||
var tech = _prototype.Index<TechnologyPrototype>(unlocked);
|
||||
var cardControl = new MiniTechnologyCardControl(tech, _prototype, _sprite, _research.GetTechnologyDescription(tech, false));
|
||||
UnlockedCardsContainer.AddChild(cardControl);
|
||||
}
|
||||
var unlockedTech = _technologyDatabase.UnlockedTechnologies.Select(x => _prototype.Index<TechnologyPrototype>(x));
|
||||
SyncTechnologyList(UnlockedCardsContainer, unlockedTech);
|
||||
}
|
||||
|
||||
public void UpdateInformationPanel(ResearchConsoleBoundInterfaceState state)
|
||||
@@ -146,5 +137,46 @@ public sealed partial class ResearchConsoleMenu : FancyWindow
|
||||
TierDisplayContainer.AddChild(control);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronize a container for technology cards with a list of technologies,
|
||||
/// creating or removing UI cards as appropriate.
|
||||
/// </summary>
|
||||
/// <param name="container">The container which contains the UI cards</param>
|
||||
/// <param name="technologies">The current set of technologies for which there should be cards</param>
|
||||
private void SyncTechnologyList(BoxContainer container, IEnumerable<TechnologyPrototype> technologies)
|
||||
{
|
||||
// For the cards which already exist, build a map from technology prototype to the UI card
|
||||
var currentTechControls = new Dictionary<TechnologyPrototype, Control>();
|
||||
foreach (var child in container.Children)
|
||||
{
|
||||
if (child is MiniTechnologyCardControl)
|
||||
{
|
||||
currentTechControls.Add((child as MiniTechnologyCardControl)!.Technology, child);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var tech in technologies)
|
||||
{
|
||||
if (!currentTechControls.ContainsKey(tech))
|
||||
{
|
||||
// Create a card for any technology which doesn't already have one.
|
||||
var mini = new MiniTechnologyCardControl(tech, _prototype, _sprite, _research.GetTechnologyDescription(tech));
|
||||
container.AddChild(mini);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The tech already exists in the UI; remove it from the set, so we won't revisit it below
|
||||
currentTechControls.Remove(tech);
|
||||
}
|
||||
}
|
||||
|
||||
// Now, any items left in the dictionary are technologies which were previously
|
||||
// available, but now are not. Remove them.
|
||||
foreach (var (tech, techControl) in currentTechControls)
|
||||
{
|
||||
container.Children.Remove(techControl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ using Content.Shared.Traits.Assorted;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.Traits;
|
||||
@@ -41,7 +43,7 @@ public sealed class ParacusiaSystem : SharedParacusiaSystem
|
||||
|
||||
private void OnPlayerDetach(EntityUid uid, ParacusiaComponent component, LocalPlayerDetachedEvent args)
|
||||
{
|
||||
component.Stream?.Stop();
|
||||
component.Stream = _audio.Stop(component.Stream);
|
||||
}
|
||||
|
||||
private void PlayParacusiaSounds(EntityUid uid)
|
||||
@@ -67,7 +69,7 @@ public sealed class ParacusiaSystem : SharedParacusiaSystem
|
||||
var newCoords = Transform(uid).Coordinates.Offset(randomOffset);
|
||||
|
||||
// Play the sound
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords);
|
||||
paracusia.Stream = _audio.PlayStatic(paracusia.Sounds, uid, newCoords).Value.Entity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using Content.Shared.Trigger;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.Trigger;
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Client.UserInterface.Systems.MenuBar.Widgets;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Input;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
@@ -20,6 +21,7 @@ using Robust.Client.UserInterface.Controllers;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Input.Binding;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
@@ -34,6 +36,7 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
[UISystemDependency] private readonly AudioSystem _audio = default!;
|
||||
|
||||
private BwoinkSystem? _bwoinkSystem;
|
||||
private MenuButton? GameAHelpButton => UIManager.GetActiveUIWidgetOrNull<GameTopMenuBar>()?.AHelpButton;
|
||||
@@ -121,14 +124,14 @@ public sealed class AHelpUIController: UIController, IOnSystemChanged<BwoinkSyst
|
||||
private void ReceivedBwoink(object? sender, SharedBwoinkSystem.BwoinkTextMessage message)
|
||||
{
|
||||
Logger.InfoS("c.s.go.es.bwoink", $"@{message.UserId}: {message.Text}");
|
||||
var localPlayer = _playerManager.LocalPlayer;
|
||||
var localPlayer = _playerManager.LocalSession;
|
||||
if (localPlayer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (localPlayer.UserId != message.TrueSender)
|
||||
{
|
||||
SoundSystem.Play("/Audio/Effects/adminhelp.ogg", Filter.Local());
|
||||
_audio.PlayGlobal("/Audio/Effects/adminhelp.ogg", Filter.Local(), false);
|
||||
_clyde.RequestWindowAttention();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using Content.Client.UserInterface.Systems.Chat.Controls;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
@@ -19,6 +21,7 @@ public partial class ChatBox : UIWidget
|
||||
#pragma warning restore RA0003
|
||||
{
|
||||
private readonly ChatUIController _controller;
|
||||
private readonly IEntityManager _entManager;
|
||||
|
||||
public bool Main { get; set; }
|
||||
|
||||
@@ -27,6 +30,7 @@ public partial class ChatBox : UIWidget
|
||||
public ChatBox()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
_entManager = IoCManager.Resolve<IEntityManager>();
|
||||
|
||||
ChatInput.Input.OnTextEntered += OnTextEntered;
|
||||
ChatInput.Input.OnKeyBindDown += OnKeyBindDown;
|
||||
@@ -54,8 +58,8 @@ public partial class ChatBox : UIWidget
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg is { Read: false, AudioPath: not null })
|
||||
SoundSystem.Play(msg.AudioPath, Filter.Local(), new AudioParams().WithVolume(msg.AudioVolume));
|
||||
if (msg is { Read: false, AudioPath: { } })
|
||||
_entManager.System<AudioSystem>().PlayGlobal(msg.AudioPath, Filter.Local(), false, AudioParams.Default.WithVolume(msg.AudioVolume));
|
||||
|
||||
msg.Read = true;
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.Voting;
|
||||
using Robust.Client;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
@@ -2,6 +2,8 @@ using Content.Shared.Projectiles;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
using Content.Shared.Weapons.Ranged.Systems;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using System.Numerics;
|
||||
using Content.Shared.Weather;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -10,6 +13,7 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using AudioComponent = Robust.Shared.Audio.Components.AudioComponent;
|
||||
|
||||
namespace Content.Client.Weather;
|
||||
|
||||
@@ -21,9 +25,6 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private const float OcclusionLerpRate = 4f;
|
||||
private const float AlphaLerpRate = 4f;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -34,7 +35,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
{
|
||||
base.Run(uid, weather, weatherProto, frameTime);
|
||||
|
||||
var ent = _playerManager.LocalPlayer?.ControlledEntity;
|
||||
var ent = _playerManager.LocalEntity;
|
||||
|
||||
if (ent == null)
|
||||
return;
|
||||
@@ -45,26 +46,18 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
// Maybe have the viewports manage this?
|
||||
if (mapUid == null || entXform.MapUid != mapUid)
|
||||
{
|
||||
weather.LastOcclusion = 0f;
|
||||
weather.LastAlpha = 0f;
|
||||
weather.Stream?.Stop();
|
||||
weather.Stream = null;
|
||||
weather.Stream = _audio.Stop(weather.Stream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Timing.IsFirstTimePredicted || weatherProto.Sound == null)
|
||||
return;
|
||||
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true);
|
||||
var volumeMod = MathF.Pow(10, weatherProto.Sound.Params.Volume / 10f);
|
||||
weather.Stream ??= _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true).Value.Entity;
|
||||
|
||||
var stream = (AudioSystem.PlayingStream) weather.Stream!;
|
||||
var alpha = weather.LastAlpha;
|
||||
alpha = MathF.Pow(alpha, 2f) * volumeMod;
|
||||
// TODO: Lerp this occlusion.
|
||||
var stream = weather.Stream.Value;
|
||||
var comp = Comp<AudioComponent>(stream);
|
||||
var occlusion = 0f;
|
||||
// TODO: Fade-out needs to be slower
|
||||
// TODO: HELPER PLZ
|
||||
|
||||
// Work out tiles nearby to determine volume.
|
||||
if (TryComp<MapGridComponent>(entXform.GridUid, out var grid))
|
||||
@@ -76,8 +69,6 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
frontier.Enqueue(seed);
|
||||
// If we don't have a nearest node don't play any sound.
|
||||
EntityCoordinates? nearestNode = null;
|
||||
var bodyQuery = GetEntityQuery<PhysicsComponent>();
|
||||
var weatherIgnoreQuery = GetEntityQuery<IgnoreWeatherComponent>();
|
||||
var visited = new HashSet<Vector2i>();
|
||||
|
||||
while (frontier.TryDequeue(out var node))
|
||||
@@ -85,7 +76,7 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
if (!visited.Add(node.GridIndices))
|
||||
continue;
|
||||
|
||||
if (!CanWeatherAffect(grid, node, weatherIgnoreQuery, bodyQuery))
|
||||
if (!CanWeatherAffect(grid, node))
|
||||
{
|
||||
// Add neighbors
|
||||
// TODO: Ideally we pick some deterministically random direction and use that
|
||||
@@ -109,50 +100,29 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
}
|
||||
|
||||
nearestNode = new EntityCoordinates(entXform.GridUid.Value,
|
||||
(Vector2) node.GridIndices + (grid.TileSizeHalfVector));
|
||||
node.GridIndices + grid.TileSizeHalfVector);
|
||||
break;
|
||||
}
|
||||
|
||||
if (nearestNode == null)
|
||||
alpha = 0f;
|
||||
// Get occlusion to the targeted node if it exists, otherwise set a default occlusion.
|
||||
if (nearestNode != null)
|
||||
{
|
||||
var entPos = _transform.GetMapCoordinates(entXform);
|
||||
var nodePosition = nearestNode.Value.ToMap(EntityManager, _transform).Position;
|
||||
var delta = nodePosition - entPos.Position;
|
||||
var distance = delta.Length();
|
||||
occlusion = _audio.GetOcclusion(entPos, delta, distance);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entPos = _transform.GetWorldPosition(entXform);
|
||||
var sourceRelative = nearestNode.Value.ToMap(EntityManager).Position - entPos;
|
||||
|
||||
if (sourceRelative.LengthSquared() > 1f)
|
||||
{
|
||||
occlusion = _physics.IntersectRayPenetration(entXform.MapID,
|
||||
new CollisionRay(entPos, sourceRelative.Normalized(), _audio.OcclusionCollisionMask),
|
||||
sourceRelative.Length(), stream.TrackingEntity);
|
||||
}
|
||||
occlusion = 3f;
|
||||
}
|
||||
}
|
||||
|
||||
if (MathHelper.CloseTo(weather.LastOcclusion, occlusion, 0.01f))
|
||||
weather.LastOcclusion = occlusion;
|
||||
else
|
||||
weather.LastOcclusion += (occlusion - weather.LastOcclusion) * OcclusionLerpRate * frameTime;
|
||||
|
||||
if (MathHelper.CloseTo(weather.LastAlpha, alpha, 0.01f))
|
||||
weather.LastAlpha = alpha;
|
||||
else
|
||||
weather.LastAlpha += (alpha - weather.LastAlpha) * AlphaLerpRate * frameTime;
|
||||
|
||||
// Full volume if not on grid
|
||||
stream.Source.SetVolumeDirect(weather.LastAlpha);
|
||||
stream.Source.SetOcclusion(weather.LastOcclusion);
|
||||
}
|
||||
|
||||
protected override void EndWeather(EntityUid uid, WeatherComponent component, string proto)
|
||||
{
|
||||
base.EndWeather(uid, component, proto);
|
||||
|
||||
if (!component.Weather.TryGetValue(proto, out var weather))
|
||||
return;
|
||||
|
||||
weather.LastAlpha = 0f;
|
||||
weather.LastOcclusion = 0f;
|
||||
var alpha = GetPercent(weather, uid);
|
||||
alpha *= SharedAudioSystem.VolumeToGain(weatherProto.Sound.Params.Volume);
|
||||
_audio.SetGain(stream, alpha, comp);
|
||||
comp.Occlusion = occlusion;
|
||||
}
|
||||
|
||||
protected override bool SetState(WeatherState state, WeatherComponent comp, WeatherData weather, WeatherPrototype weatherProto)
|
||||
@@ -164,9 +134,8 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
return true;
|
||||
|
||||
// TODO: Fades (properly)
|
||||
weather.Stream?.Stop();
|
||||
weather.Stream = null;
|
||||
weather.Stream = _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true);
|
||||
weather.Stream = _audio.Stop(weather.Stream);
|
||||
weather.Stream = _audio.PlayGlobal(weatherProto.Sound, Filter.Local(), true)?.Entity;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -197,7 +166,6 @@ public sealed class WeatherSystem : SharedWeatherSystem
|
||||
|
||||
// New weather
|
||||
StartWeather(component, ProtoMan.Index<WeatherPrototype>(proto), weather.EndTime);
|
||||
weather.LastAlpha = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ public static partial class PoolManager
|
||||
options.BeforeStart += () =>
|
||||
{
|
||||
var entSysMan = IoCManager.Resolve<IEntitySystemManager>();
|
||||
var compFactory = IoCManager.Resolve<IComponentFactory>();
|
||||
entSysMan.LoadExtraSystemType<ResettingEntitySystemTests.TestRoundRestartCleanupEvent>();
|
||||
entSysMan.LoadExtraSystemType<InteractionSystemTests.TestInteractionSystem>();
|
||||
entSysMan.LoadExtraSystemType<DeviceNetworkTestSystem>();
|
||||
|
||||
@@ -6,6 +6,8 @@ using Content.Server.Destructible.Thresholds.Triggers;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using static Content.IntegrationTests.Tests.Destructible.DestructibleTestPrototypes;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Server.Humanoid.Components;
|
||||
using Content.Shared.Coordinates;
|
||||
using Content.Shared.Prototypes;
|
||||
@@ -215,6 +216,7 @@ namespace Content.IntegrationTests.Tests
|
||||
{
|
||||
var settings = new PoolSettings { Connected = true, Dirty = true };
|
||||
await using var pair = await PoolManager.GetServerClient(settings);
|
||||
var mapManager = pair.Server.ResolveDependency<IMapManager>();
|
||||
var server = pair.Server;
|
||||
var client = pair.Client;
|
||||
|
||||
@@ -244,16 +246,18 @@ namespace Content.IntegrationTests.Tests
|
||||
.Select(p => p.ID)
|
||||
.ToList();
|
||||
|
||||
MapCoordinates coords = default;
|
||||
protoIds.Sort();
|
||||
var mapId = MapId.Nullspace;
|
||||
|
||||
await server.WaitPost(() =>
|
||||
{
|
||||
var map = server.MapMan.CreateMap();
|
||||
coords = new MapCoordinates(default, map);
|
||||
mapId = mapManager.CreateMap();
|
||||
});
|
||||
|
||||
var coords = new MapCoordinates(Vector2.Zero, mapId);
|
||||
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
List<string> badPrototypes = new();
|
||||
foreach (var protoId in protoIds)
|
||||
{
|
||||
// TODO fix ninja
|
||||
@@ -270,27 +274,50 @@ namespace Content.IntegrationTests.Tests
|
||||
// If the entity deleted itself, check that it didn't spawn other entities
|
||||
if (!server.EntMan.EntityExists(uid))
|
||||
{
|
||||
if (server.EntMan.EntityCount != count || client.EntMan.EntityCount != clientCount)
|
||||
badPrototypes.Add(protoId);
|
||||
if (server.EntMan.EntityCount != count)
|
||||
{
|
||||
Assert.Fail($"Server prototype {protoId} failed on deleting itself");
|
||||
}
|
||||
|
||||
if (client.EntMan.EntityCount != clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on deleting itself\n" +
|
||||
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
|
||||
$"Server was {count}.");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that the number of entities has increased.
|
||||
if (server.EntMan.EntityCount <= count || client.EntMan.EntityCount <= clientCount)
|
||||
if (server.EntMan.EntityCount <= count)
|
||||
{
|
||||
badPrototypes.Add(protoId);
|
||||
continue;
|
||||
Assert.Fail($"Server prototype {protoId} failed on spawning as entity count didn't increase");
|
||||
}
|
||||
|
||||
if (client.EntMan.EntityCount <= clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on spawning as entity count didn't increase" +
|
||||
$"Expected at least {clientCount} and found {client.EntMan.EntityCount}. " +
|
||||
$"Server was {count}");
|
||||
}
|
||||
|
||||
await server.WaitPost(() => server.EntMan.DeleteEntity(uid));
|
||||
await pair.RunTicksSync(3);
|
||||
|
||||
// Check that the number of entities has gone back to the original value.
|
||||
if (server.EntMan.EntityCount != count || client.EntMan.EntityCount != clientCount)
|
||||
badPrototypes.Add(protoId);
|
||||
if (server.EntMan.EntityCount != count)
|
||||
{
|
||||
Assert.Fail($"Server prototype {protoId} failed on deletion count didn't reset properly");
|
||||
}
|
||||
|
||||
if (client.EntMan.EntityCount != clientCount)
|
||||
{
|
||||
Assert.Fail($"Client prototype {protoId} failed on deletion count didn't reset properly:\n" +
|
||||
$"Expected {clientCount} and found {client.EntMan.EntityCount}.\n" +
|
||||
$"Server was {count}.");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(badPrototypes, Is.Empty);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
@@ -303,7 +330,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"DebugExceptionExposeData",
|
||||
"DebugExceptionInitialize",
|
||||
"DebugExceptionStartup",
|
||||
"GridFillComponent",
|
||||
"GridFill",
|
||||
"Map", // We aren't testing a map entity in this test
|
||||
"MapGrid",
|
||||
"Broadphase",
|
||||
|
||||
@@ -619,6 +619,9 @@ public abstract partial class InteractionTest
|
||||
{
|
||||
foreach (var (proto, quantity) in expected.Entities)
|
||||
{
|
||||
if (proto == "Audio")
|
||||
continue;
|
||||
|
||||
if (quantity < 0 && failOnExcess)
|
||||
Assert.Fail($"Unexpected entity/stack: {proto}, quantity: {-quantity}");
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ public abstract partial class InteractionTest
|
||||
[SetUp]
|
||||
public virtual async Task Setup()
|
||||
{
|
||||
Pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true });
|
||||
Pair = await PoolManager.GetServerClient(new PoolSettings { Connected = true, Dirty = true});
|
||||
|
||||
// server dependencies
|
||||
SEntMan = Server.ResolveDependency<IEntityManager>();
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace Content.IntegrationTests.Tests
|
||||
"Core",
|
||||
"Marathon",
|
||||
"Kettle",
|
||||
"Gemini",
|
||||
"MeteorArena",
|
||||
"Atlas"
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using Content.Shared.Decals;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -16,7 +17,7 @@ namespace Content.MapRenderer.Painters;
|
||||
|
||||
public sealed class DecalPainter
|
||||
{
|
||||
private readonly IResourceCache _cResourceCache;
|
||||
private readonly IResourceManager _resManager;
|
||||
|
||||
private readonly IPrototypeManager _sPrototypeManager;
|
||||
|
||||
@@ -24,7 +25,7 @@ public sealed class DecalPainter
|
||||
|
||||
public DecalPainter(ClientIntegrationInstance client, ServerIntegrationInstance server)
|
||||
{
|
||||
_cResourceCache = client.ResolveDependency<IResourceCache>();
|
||||
_resManager = client.ResolveDependency<IResourceManager>();
|
||||
_sPrototypeManager = server.ResolveDependency<IPrototypeManager>();
|
||||
}
|
||||
|
||||
@@ -63,7 +64,7 @@ public sealed class DecalPainter
|
||||
Stream stream;
|
||||
if (sprite is SpriteSpecifier.Texture texture)
|
||||
{
|
||||
stream = _cResourceCache.ContentFileRead(texture.TexturePath);
|
||||
stream = _resManager.ContentFileRead(texture.TexturePath);
|
||||
}
|
||||
else if (sprite is SpriteSpecifier.Rsi rsi)
|
||||
{
|
||||
@@ -73,7 +74,7 @@ public sealed class DecalPainter
|
||||
path = $"/Textures/{path}";
|
||||
}
|
||||
|
||||
stream = _cResourceCache.ContentFileRead(path);
|
||||
stream = _resManager.ContentFileRead(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
@@ -14,7 +15,7 @@ namespace Content.MapRenderer.Painters;
|
||||
|
||||
public sealed class EntityPainter
|
||||
{
|
||||
private readonly IResourceCache _cResourceCache;
|
||||
private readonly IResourceManager _resManager;
|
||||
|
||||
private readonly Dictionary<(string path, string state), Image> _images;
|
||||
private readonly Image _errorImage;
|
||||
@@ -23,12 +24,12 @@ public sealed class EntityPainter
|
||||
|
||||
public EntityPainter(ClientIntegrationInstance client, ServerIntegrationInstance server)
|
||||
{
|
||||
_cResourceCache = client.ResolveDependency<IResourceCache>();
|
||||
_resManager = client.ResolveDependency<IResourceManager>();
|
||||
|
||||
_sEntityManager = server.ResolveDependency<IEntityManager>();
|
||||
|
||||
_images = new Dictionary<(string path, string state), Image>();
|
||||
_errorImage = Image.Load<Rgba32>(_cResourceCache.ContentFileRead("/Textures/error.rsi/error.png"));
|
||||
_errorImage = Image.Load<Rgba32>(_resManager.ContentFileRead("/Textures/error.rsi/error.png"));
|
||||
}
|
||||
|
||||
public void Run(Image canvas, List<EntityData> entities)
|
||||
@@ -81,7 +82,7 @@ public sealed class EntityPainter
|
||||
|
||||
if (!_images.TryGetValue(key, out image!))
|
||||
{
|
||||
var stream = _cResourceCache.ContentFileRead($"{rsi.Path}/{state.StateId}.png");
|
||||
var stream = _resManager.ContentFileRead($"{rsi.Path}/{state.StateId}.png");
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
|
||||
_images[key] = image;
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -19,12 +20,12 @@ namespace Content.MapRenderer.Painters
|
||||
public const int TileImageSize = EyeManager.PixelsPerMeter;
|
||||
|
||||
private readonly ITileDefinitionManager _sTileDefinitionManager;
|
||||
private readonly IResourceCache _cResourceCache;
|
||||
private readonly IResourceManager _resManager;
|
||||
|
||||
public TilePainter(ClientIntegrationInstance client, ServerIntegrationInstance server)
|
||||
{
|
||||
_sTileDefinitionManager = server.ResolveDependency<ITileDefinitionManager>();
|
||||
_cResourceCache = client.ResolveDependency<IResourceCache>();
|
||||
_resManager = client.ResolveDependency<IResourceManager>();
|
||||
}
|
||||
|
||||
public void Run(Image gridCanvas, EntityUid gridUid, MapGridComponent grid)
|
||||
@@ -37,7 +38,7 @@ namespace Content.MapRenderer.Painters
|
||||
var yOffset = -bounds.Bottom;
|
||||
var tileSize = grid.TileSize * TileImageSize;
|
||||
|
||||
var images = GetTileImages(_sTileDefinitionManager, _cResourceCache, tileSize);
|
||||
var images = GetTileImages(_sTileDefinitionManager, _resManager, tileSize);
|
||||
var i = 0;
|
||||
|
||||
grid.GetAllTiles().AsParallel().ForAll(tile =>
|
||||
@@ -61,7 +62,7 @@ namespace Content.MapRenderer.Painters
|
||||
|
||||
private Dictionary<string, List<Image>> GetTileImages(
|
||||
ITileDefinitionManager tileDefinitionManager,
|
||||
IResourceCache resourceCache,
|
||||
IResourceManager resManager,
|
||||
int tileSize)
|
||||
{
|
||||
var stopwatch = new Stopwatch();
|
||||
@@ -78,7 +79,7 @@ namespace Content.MapRenderer.Painters
|
||||
|
||||
images[path] = new List<Image>(definition.Variants);
|
||||
|
||||
using var stream = resourceCache.ContentFileRead(path);
|
||||
using var stream = resManager.ContentFileRead(path);
|
||||
Image tileSheet = Image.Load<Rgba32>(stream);
|
||||
|
||||
if (tileSheet.Width != tileSize * definition.Variants || tileSheet.Height != tileSize)
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ServerGarbageCollection>True</ServerGarbageCollection>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NVorbis" Version="0.10.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.5" PrivateAssets="compile" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobustToolbox\Robust.Packaging\Robust.Packaging.csproj" />
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using Robust.Packaging;
|
||||
using Robust.Packaging.AssetProcessing;
|
||||
using Robust.Packaging.AssetProcessing.Passes;
|
||||
using Robust.Packaging.Utility;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using YamlDotNet.Core;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Packaging;
|
||||
|
||||
@@ -197,7 +192,7 @@ public static class ServerPackaging
|
||||
bool hybridAcz,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var graph = new RobustClientAssetGraph();
|
||||
var graph = new RobustServerAssetGraph();
|
||||
var passes = graph.AllPasses.ToList();
|
||||
|
||||
pass.Dependencies.Add(new AssetPassDependency(graph.Output.Name));
|
||||
@@ -205,7 +200,8 @@ public static class ServerPackaging
|
||||
|
||||
AssetGraph.CalculateGraph(passes, logger);
|
||||
|
||||
var inputPass = graph.Input;
|
||||
var inputPassCore = graph.InputCore;
|
||||
var inputPassResources = graph.InputResources;
|
||||
var contentAssemblies = new List<string>(ServerContentAssemblies);
|
||||
// Corvax-Secrets-Start
|
||||
if (UseSecrets)
|
||||
@@ -232,26 +228,26 @@ public static class ServerPackaging
|
||||
Path.Combine("RobustToolbox", "bin", "Server",
|
||||
platform.Rid,
|
||||
"publish"),
|
||||
inputPass,
|
||||
inputPassCore,
|
||||
BinSkipFolders,
|
||||
cancel: cancel);
|
||||
|
||||
await RobustSharedPackaging.WriteContentAssemblies(
|
||||
inputPass,
|
||||
inputPassResources,
|
||||
contentDir,
|
||||
"Content.Server",
|
||||
contentAssemblies,
|
||||
Path.Combine("Resources", "Assemblies"),
|
||||
cancel);
|
||||
cancel: cancel);
|
||||
|
||||
await RobustServerPackaging.WriteServerResources(contentDir, inputPass, cancel);
|
||||
await RobustServerPackaging.WriteServerResources(contentDir, inputPassResources, cancel);
|
||||
|
||||
if (hybridAcz)
|
||||
{
|
||||
inputPass.InjectFileFromDisk("Content.Client.zip", Path.Combine("release", "SS14.Client.zip"));
|
||||
inputPassCore.InjectFileFromDisk("Content.Client.zip", Path.Combine("release", "SS14.Client.zip"));
|
||||
}
|
||||
|
||||
inputPass.InjectFinished();
|
||||
inputPassCore.InjectFinished();
|
||||
inputPassResources.InjectFinished();
|
||||
}
|
||||
|
||||
private readonly record struct PlatformReg(string Rid, string TargetOs, bool BuildByDefault);
|
||||
|
||||
@@ -8,6 +8,8 @@ using Content.Shared.DoAfter;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using static Content.Shared.Access.Components.AccessOverriderComponent;
|
||||
|
||||
@@ -6,7 +6,6 @@ using Robust.Shared.Audio;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Administration.Commands;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ using Content.Shared.Throwing;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Network;
|
||||
@@ -40,7 +41,6 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly IChatManager _chat = default!;
|
||||
[Dependency] private readonly IConfigurationManager _config = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly AudioSystem _audio = default!;
|
||||
[Dependency] private readonly HandsSystem _hands = default!;
|
||||
[Dependency] private readonly SharedJobSystem _jobs = default!;
|
||||
[Dependency] private readonly InventorySystem _inventory = default!;
|
||||
@@ -50,6 +50,7 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly PlayTimeTrackingManager _playTime = default!;
|
||||
[Dependency] private readonly SharedRoleSystem _role = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StationRecordsSystem _stationRecords = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
@@ -350,7 +351,7 @@ namespace Content.Server.Administration.Systems
|
||||
_popup.PopupCoordinates(Loc.GetString("admin-erase-popup", ("user", name)), coordinates, PopupType.LargeCaution);
|
||||
var filter = Filter.Pvs(coordinates, 1, EntityManager, _playerManager);
|
||||
var audioParams = new AudioParams().WithVolume(3);
|
||||
_audio.Play("/Audio/Effects/pop_high.ogg", filter, coordinates, true, audioParams);
|
||||
_audio.PlayStatic("/Audio/Effects/pop_high.ogg", filter, coordinates, true, audioParams);
|
||||
}
|
||||
|
||||
foreach (var item in _inventory.GetHandOrInventoryEntities(entity.Value))
|
||||
|
||||
@@ -20,7 +20,6 @@ public sealed partial class AdminVerbSystem
|
||||
[Dependency] private readonly PiratesRuleSystem _piratesRule = default!;
|
||||
[Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!;
|
||||
[Dependency] private readonly SharedMindSystem _minds = default!;
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
|
||||
// All antag verbs have names so invokeverb works.
|
||||
private void AddAntagVerbs(GetVerbsEvent<Verb> args)
|
||||
|
||||
@@ -5,10 +5,12 @@ using Content.Server.Administration.UI;
|
||||
using Content.Server.Disposal.Tube;
|
||||
using Content.Server.Disposal.Tube.Components;
|
||||
using Content.Server.EUI;
|
||||
using Content.Server.GameTicking;
|
||||
using Content.Server.Ghost.Roles;
|
||||
using Content.Server.Mind;
|
||||
using Content.Server.Mind.Commands;
|
||||
using Content.Server.Prayer;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts;
|
||||
using Content.Server.Xenoarchaeology.XenoArtifacts.Triggers.Components;
|
||||
using Content.Shared.Administration;
|
||||
@@ -50,6 +52,7 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly AdminSystem _adminSystem = default!;
|
||||
[Dependency] private readonly DisposalTubeSystem _disposalTubes = default!;
|
||||
[Dependency] private readonly EuiManager _euiManager = default!;
|
||||
[Dependency] private readonly GameTicker _ticker = default!;
|
||||
[Dependency] private readonly GhostRoleSystem _ghostRoleSystem = default!;
|
||||
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
|
||||
@@ -59,6 +62,8 @@ namespace Content.Server.Administration.Systems
|
||||
[Dependency] private readonly ToolshedManager _toolshed = default!;
|
||||
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly StationSystem _stations = default!;
|
||||
[Dependency] private readonly StationSpawningSystem _spawning = default!;
|
||||
|
||||
private readonly Dictionary<ICommonSession, EditSolutionsEui> _openSolutionUis = new();
|
||||
|
||||
@@ -156,6 +161,69 @@ namespace Content.Server.Administration.Systems
|
||||
Impact = LogImpact.Extreme,
|
||||
ConfirmationPopup = true
|
||||
});
|
||||
|
||||
// Respawn
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("admin-player-actions-respawn"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
_console.ExecuteCommand(player, $"respawn {targetActor.PlayerSession.Name}");
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
// No logimpact as the command does it internally.
|
||||
});
|
||||
|
||||
// Spawn - Like respawn but on the spot.
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("admin-player-actions-spawn"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
if (!_transformSystem.TryGetMapOrGridCoordinates(args.Target, out var coords))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("admin-player-spawn-failed"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
var stationUid = _stations.GetOwningStation(args.Target);
|
||||
|
||||
var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
|
||||
var mobUid = _spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
|
||||
var targetMind = _mindSystem.GetMind(args.Target);
|
||||
|
||||
if (targetMind != null)
|
||||
{
|
||||
_mindSystem.TransferTo(targetMind.Value, mobUid);
|
||||
}
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
Impact = LogImpact.High,
|
||||
});
|
||||
|
||||
// Clone - Spawn but without the mind transfer, also spawns at the user's coordinates not the target's
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("admin-player-actions-clone"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
if (!_transformSystem.TryGetMapOrGridCoordinates(args.User, out var coords))
|
||||
{
|
||||
_popup.PopupEntity(Loc.GetString("admin-player-spawn-failed"), args.User, args.User);
|
||||
return;
|
||||
}
|
||||
|
||||
var stationUid = _stations.GetOwningStation(args.Target);
|
||||
|
||||
var profile = _ticker.GetPlayerProfile(targetActor.PlayerSession);
|
||||
_spawning.SpawnPlayerMob(coords.Value, null, profile, stationUid);
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
Impact = LogImpact.High,
|
||||
});
|
||||
}
|
||||
|
||||
// Admin Logs
|
||||
@@ -211,21 +279,6 @@ namespace Content.Server.Administration.Systems
|
||||
Impact = LogImpact.Low
|
||||
});
|
||||
|
||||
// Respawn
|
||||
if (HasComp<ActorComponent>(args.Target))
|
||||
{
|
||||
args.Verbs.Add(new Verb()
|
||||
{
|
||||
Text = Loc.GetString("admin-player-actions-respawn"),
|
||||
Category = VerbCategory.Admin,
|
||||
Act = () =>
|
||||
{
|
||||
_console.ExecuteCommand(player, $"respawn {actor.PlayerSession.Name}");
|
||||
},
|
||||
ConfirmationPopup = true,
|
||||
// No logimpact as the command does it internally.
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.Chat.Systems;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Shared.CCVar;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -10,10 +11,11 @@ namespace Content.Server.AlertLevel;
|
||||
|
||||
public sealed class AlertLevelSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly ChatSystem _chatSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly StationSystem _stationSystem = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
// Until stations are a prototype, this is how it's going to have to be.
|
||||
public const string DefaultAlertLevelSet = "stationAlerts";
|
||||
@@ -174,7 +176,7 @@ public sealed class AlertLevelSystem : EntitySystem
|
||||
if (detail.Sound != null)
|
||||
{
|
||||
var filter = _stationSystem.GetInOwningStation(station);
|
||||
SoundSystem.Play(detail.Sound.GetSound(), filter, detail.Sound.Params);
|
||||
_audio.PlayGlobal(detail.Sound.GetSound(), filter, true, detail.Sound.Params);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ using Content.Shared.Popups;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -6,6 +6,8 @@ using Content.Server.Tools;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Hands.Components;
|
||||
using Content.Shared.Interaction;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Content.Server.Ame.EntitySystems;
|
||||
|
||||
@@ -5,6 +5,7 @@ using Content.Shared.Actions.Events;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Storage;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
using System.Linq;
|
||||
using Content.Server.Anomaly.Components;
|
||||
using Content.Server.DeviceLinking.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Power.EntitySystems;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Content.Shared.Verbs;
|
||||
|
||||
namespace Content.Server.Anomaly;
|
||||
|
||||
@@ -21,86 +25,129 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly PowerReceiverSystem _power = default!;
|
||||
|
||||
private const float AttachRange = 0.15f; // The radius of one tile. It must not be set higher, otherwise the anomaly can be moved from tile to tile.
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AnomalySynchronizerComponent, InteractHandEvent>(OnInteractHand);
|
||||
SubscribeLocalEvent<AnomalySynchronizerComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<AnomalySynchronizerComponent, ExaminedEvent>(OnExamined);
|
||||
SubscribeLocalEvent<AnomalySynchronizerComponent, GetVerbsEvent<InteractionVerb>>(OnGetInteractionVerbs);
|
||||
|
||||
SubscribeLocalEvent<AnomalyPulseEvent>(OnAnomalyPulse);
|
||||
SubscribeLocalEvent<AnomalySeverityChangedEvent>(OnAnomalySeverityChanged);
|
||||
SubscribeLocalEvent<AnomalyStabilityChangedEvent>(OnAnomalyStabilityChanged);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(EntityUid uid, AnomalySynchronizerComponent component, ref PowerChangedEvent args)
|
||||
/// <summary>
|
||||
/// If powered, try to attach a nearby anomaly.
|
||||
/// </summary>
|
||||
public bool TryAttachNearbyAnomaly(Entity<AnomalySynchronizerComponent> ent, EntityUid? user = null)
|
||||
{
|
||||
if (!_power.IsPowered(ent))
|
||||
{
|
||||
if (user is not null)
|
||||
_popup.PopupEntity(Loc.GetString("base-computer-ui-component-not-powered", ("machine", ent)), ent, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var coords = _transform.GetMapCoordinates(ent);
|
||||
var anomaly = _entityLookup.GetEntitiesInRange<AnomalyComponent>(coords, AttachRange).FirstOrDefault();
|
||||
|
||||
if (anomaly.Owner is {Valid: false}) // no anomaly in range
|
||||
{
|
||||
if (user is not null)
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-no-anomaly"), ent, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ConnectToAnomaly(ent, anomaly);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnPowerChanged(Entity<AnomalySynchronizerComponent> ent, ref PowerChangedEvent args)
|
||||
{
|
||||
if (args.Powered)
|
||||
return;
|
||||
|
||||
if (!TryComp<AnomalyComponent>(component.ConnectedAnomaly, out var anomaly))
|
||||
if (!TryComp<AnomalyComponent>(ent.Comp.ConnectedAnomaly, out var anomaly))
|
||||
return;
|
||||
|
||||
_anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly);
|
||||
DisconneсtFromAnomaly(uid, component, anomaly);
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
DisconneсtFromAnomaly(ent, anomaly);
|
||||
}
|
||||
|
||||
private void OnInteractHand(EntityUid uid, AnomalySynchronizerComponent component, InteractHandEvent args)
|
||||
private void OnExamined(Entity<AnomalySynchronizerComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (!_power.IsPowered(uid))
|
||||
return;
|
||||
|
||||
foreach (var entity in _entityLookup.GetEntitiesInRange(uid, 0.15f)) //is the radius of one tile. It must not be set higher, otherwise the anomaly can be moved from tile to tile
|
||||
{
|
||||
if (!TryComp<AnomalyComponent>(entity, out var anomaly))
|
||||
continue;
|
||||
|
||||
|
||||
ConnectToAnomaly(uid, component, entity, anomaly);
|
||||
break;
|
||||
}
|
||||
args.PushMarkup(Loc.GetString(ent.Comp.ConnectedAnomaly.HasValue ? "anomaly-sync-examine-connected" : "anomaly-sync-examine-not-connected"));
|
||||
}
|
||||
|
||||
private void ConnectToAnomaly(EntityUid uid, AnomalySynchronizerComponent component, EntityUid auid, AnomalyComponent anomaly)
|
||||
private void OnGetInteractionVerbs(Entity<AnomalySynchronizerComponent> ent, ref GetVerbsEvent<InteractionVerb> args)
|
||||
{
|
||||
if (component.ConnectedAnomaly == auid)
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands is null || ent.Comp.ConnectedAnomaly.HasValue)
|
||||
return;
|
||||
|
||||
component.ConnectedAnomaly = auid;
|
||||
var user = args.User;
|
||||
args.Verbs.Add(new() {
|
||||
Act = () =>
|
||||
{
|
||||
TryAttachNearbyAnomaly(ent, user);
|
||||
},
|
||||
Message = Loc.GetString("anomaly-sync-connect-verb-message", ("machine", ent)),
|
||||
Text = Loc.GetString("anomaly-sync-connect-verb-text"),
|
||||
});
|
||||
}
|
||||
|
||||
private void OnInteractHand(Entity<AnomalySynchronizerComponent> ent, ref InteractHandEvent args)
|
||||
{
|
||||
TryAttachNearbyAnomaly(ent, args.User);
|
||||
}
|
||||
|
||||
private void ConnectToAnomaly(Entity<AnomalySynchronizerComponent> ent, Entity<AnomalyComponent> anomaly)
|
||||
{
|
||||
if (ent.Comp.ConnectedAnomaly == anomaly)
|
||||
return;
|
||||
|
||||
ent.Comp.ConnectedAnomaly = anomaly;
|
||||
//move the anomaly to the center of the synchronizer, for aesthetics.
|
||||
var targetXform = _transform.GetWorldPosition(uid);
|
||||
_transform.SetWorldPosition(auid, targetXform);
|
||||
var targetXform = _transform.GetWorldPosition(ent);
|
||||
_transform.SetWorldPosition(anomaly, targetXform);
|
||||
|
||||
_anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly);
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-connected"), uid, PopupType.Medium);
|
||||
_audio.PlayPvs(component.ConnectedSound, uid);
|
||||
_anomaly.DoAnomalyPulse(anomaly, anomaly);
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-connected"), ent, PopupType.Medium);
|
||||
_audio.PlayPvs(ent.Comp.ConnectedSound, ent);
|
||||
}
|
||||
|
||||
//TO DO: disconnection from the anomaly should also be triggered if the anomaly is far away from the synchronizer.
|
||||
//Currently only bluespace anomaly can do this, but for some reason it is the only one that cannot be connected to the synchronizer.
|
||||
private void DisconneсtFromAnomaly(EntityUid uid, AnomalySynchronizerComponent component, AnomalyComponent anomaly)
|
||||
private void DisconneсtFromAnomaly(Entity<AnomalySynchronizerComponent> ent, AnomalyComponent anomaly)
|
||||
{
|
||||
if (component.ConnectedAnomaly == null)
|
||||
if (ent.Comp.ConnectedAnomaly == null)
|
||||
return;
|
||||
|
||||
_anomaly.DoAnomalyPulse(component.ConnectedAnomaly.Value, anomaly);
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), uid, PopupType.Large);
|
||||
_audio.PlayPvs(component.ConnectedSound, uid);
|
||||
_anomaly.DoAnomalyPulse(ent.Comp.ConnectedAnomaly.Value, anomaly);
|
||||
_popup.PopupEntity(Loc.GetString("anomaly-sync-disconnected"), ent, PopupType.Large);
|
||||
_audio.PlayPvs(ent.Comp.ConnectedSound, ent);
|
||||
|
||||
component.ConnectedAnomaly = default!;
|
||||
ent.Comp.ConnectedAnomaly = null;
|
||||
}
|
||||
|
||||
private void OnAnomalyPulse(ref AnomalyPulseEvent args)
|
||||
{
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
|
||||
while (query.MoveNext(out var ent, out var component))
|
||||
while (query.MoveNext(out var uid, out var component))
|
||||
{
|
||||
if (args.Anomaly != component.ConnectedAnomaly)
|
||||
continue;
|
||||
if (!_power.IsPowered(ent))
|
||||
|
||||
if (!_power.IsPowered(uid))
|
||||
continue;
|
||||
|
||||
_signal.InvokePort(ent, component.PulsePort);
|
||||
_signal.InvokePort(uid, component.PulsePort);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,8 +158,10 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
{
|
||||
if (args.Anomaly != component.ConnectedAnomaly)
|
||||
continue;
|
||||
|
||||
if (!_power.IsPowered(ent))
|
||||
continue;
|
||||
|
||||
//The superscritical port is invoked not at the AnomalySupercriticalEvent,
|
||||
//but at the moment the growth animation starts. Otherwise, there is no point in this port.
|
||||
//ATTENTION! the console command supercriticalanomaly does not work here,
|
||||
@@ -121,21 +170,25 @@ public sealed partial class AnomalySynchronizerSystem : EntitySystem
|
||||
_signal.InvokePort(ent, component.SupercritPort);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnomalyStabilityChanged(ref AnomalyStabilityChangedEvent args)
|
||||
{
|
||||
Entity<AnomalyComponent> anomaly = (args.Anomaly, Comp<AnomalyComponent>(args.Anomaly));
|
||||
|
||||
var query = EntityQueryEnumerator<AnomalySynchronizerComponent>();
|
||||
while (query.MoveNext(out var ent, out var component))
|
||||
{
|
||||
if (args.Anomaly != component.ConnectedAnomaly)
|
||||
continue;
|
||||
if (TryComp<ApcPowerReceiverComponent>(ent, out var apcPower) && !apcPower.Powered)
|
||||
if (component.ConnectedAnomaly != anomaly)
|
||||
continue;
|
||||
|
||||
if (args.Stability < 0.25f) //I couldn't find where these values are stored, so I hardcoded them. Tell me where these variables are stored and I'll fix it
|
||||
if (!_power.IsPowered(ent))
|
||||
continue;
|
||||
|
||||
if (args.Stability < anomaly.Comp.DecayThreshold)
|
||||
{
|
||||
_signal.InvokePort(ent, component.DecayingPort);
|
||||
}
|
||||
else if (args.Stability > 0.5f) //I couldn't find where these values are stored, so I hardcoded them. Tell me where these variables are stored and I'll fix it
|
||||
else if (args.Stability > anomaly.Comp.GrowthThreshold)
|
||||
{
|
||||
_signal.InvokePort(ent, component.GrowingPort);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public sealed partial class AnomalySystem
|
||||
|
||||
var generating = EnsureComp<GeneratingAnomalyGeneratorComponent>(uid);
|
||||
generating.EndTime = Timing.CurTime + component.GenerationLength;
|
||||
generating.AudioStream = Audio.PlayPvs(component.GeneratingSound, uid, AudioParams.Default.WithLoop(true));
|
||||
generating.AudioStream = Audio.PlayPvs(component.GeneratingSound, uid, AudioParams.Default.WithLoop(true))?.Entity;
|
||||
component.CooldownEndTime = Timing.CurTime + component.CooldownLength;
|
||||
UpdateGeneratorUi(uid, component);
|
||||
}
|
||||
@@ -174,7 +174,8 @@ public sealed partial class AnomalySystem
|
||||
{
|
||||
if (Timing.CurTime < active.EndTime)
|
||||
continue;
|
||||
active.AudioStream?.Stop();
|
||||
|
||||
active.AudioStream = _audio.Stop(active.AudioStream);
|
||||
OnGeneratingFinished(ent, gen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,8 @@ public sealed partial class AnomalySystem
|
||||
return;
|
||||
if (!HasComp<AnomalyComponent>(target))
|
||||
return;
|
||||
if (!args.CanReach)
|
||||
return;
|
||||
|
||||
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.ScanDoAfterDuration, new ScannerDoAfterEvent(), uid, target: target, used: uid)
|
||||
{
|
||||
|
||||
@@ -9,6 +9,8 @@ using Content.Shared.Anomaly;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.DoAfter;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Physics.Events;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -31,6 +33,7 @@ public sealed partial class AnomalySystem : SharedAnomalySystem
|
||||
[Dependency] private readonly SharedPointLightSystem _pointLight = default!;
|
||||
[Dependency] private readonly StationSystem _station = default!;
|
||||
[Dependency] private readonly RadioSystem _radio = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly UserInterfaceSystem _ui = default!;
|
||||
|
||||
public const float MinParticleVariation = 0.8f;
|
||||
|
||||
@@ -13,5 +13,5 @@ public sealed partial class GeneratingAnomalyGeneratorComponent : Component
|
||||
[DataField("endTime", customTypeSerializer: typeof(TimeOffsetSerializer))]
|
||||
public TimeSpan EndTime = TimeSpan.Zero;
|
||||
|
||||
public IPlayingAudioStream? AudioStream;
|
||||
public EntityUid? AudioStream;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using Content.Server.Anomaly.Components;
|
||||
using Content.Shared.Anomaly.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Teleportation.Components;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
@@ -65,7 +65,7 @@ public sealed class GasProducerAnomalySystem : EntitySystem
|
||||
if (tilerefs.Length == 0)
|
||||
return;
|
||||
|
||||
var mixture = _atmosphere.GetTileMixture(xform.GridUid, xform.MapUid, _xform.GetGridOrMapTilePosition(uid, xform), true);
|
||||
var mixture = _atmosphere.GetTileMixture((uid, xform), grid, true);
|
||||
if (mixture != null)
|
||||
{
|
||||
mixture.AdjustMoles(gas, mols);
|
||||
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Chemistry.EntitySystems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Content.Shared.Sprite;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Anomaly.Effects;
|
||||
|
||||
@@ -75,7 +76,7 @@ public sealed class ReagentProducerAnomalySystem : EntitySystem
|
||||
if (anomaly.Severity >= 0.97) reagentProducingAmount *= component.SupercriticalReagentProducingModifier;
|
||||
|
||||
newSol.AddReagent(component.ProducingReagent, reagentProducingAmount);
|
||||
_solutionContainer.TryAddSolution(uid, producerSol, newSol); //TO DO - the container is not fully filled.
|
||||
_solutionContainer.TryAddSolution(uid, producerSol, newSol); //TO DO - the container is not fully filled.
|
||||
|
||||
component.AccumulatedFrametime = 0;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class TempAffectingAnomalySystem : EntitySystem
|
||||
{
|
||||
var grid = xform.GridUid;
|
||||
var map = xform.MapUid;
|
||||
var indices = _xform.GetGridOrMapTilePosition(ent, xform);
|
||||
var indices = _xform.GetGridTilePositionOrDefault((ent, xform));
|
||||
var mixture = _atmosphere.GetTileMixture(grid, map, indices, true);
|
||||
|
||||
if (mixture is { })
|
||||
|
||||
@@ -21,6 +21,7 @@ using Content.Shared.Mobs.Components;
|
||||
using Content.Server.Station.Systems;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Mobs;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Content.Server.UserInterface;
|
||||
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Arcade.SpaceVillain;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Arcade.SpaceVillain;
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace Content.Server.Atmos.Components
|
||||
|
||||
// Cancel toggles sounds if we re-toggle again.
|
||||
|
||||
public IPlayingAudioStream? ConnectStream;
|
||||
public IPlayingAudioStream? DisconnectStream;
|
||||
public EntityUid? ConnectStream;
|
||||
public EntityUid? DisconnectStream;
|
||||
|
||||
[DataField("air"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public GasMixture Air { get; set; } = new();
|
||||
|
||||
@@ -127,9 +127,18 @@ public partial class AtmosphereSystem
|
||||
return ev.Mixtures;
|
||||
}
|
||||
|
||||
public GasMixture? GetTileMixture(EntityUid? gridUid, EntityUid? mapUid, Vector2i tile, bool excite = false)
|
||||
public GasMixture? GetTileMixture (Entity<TransformComponent?> entity, MapGridComponent? grid = null, bool excite = false)
|
||||
{
|
||||
var ev = new GetTileMixtureMethodEvent(gridUid, mapUid, tile, excite);
|
||||
if (!Resolve(entity.Owner, ref entity.Comp))
|
||||
return null;
|
||||
|
||||
var indices = _transformSystem.GetGridTilePositionOrDefault(entity);
|
||||
return GetTileMixture(entity.Comp.GridUid, entity.Comp.MapUid, indices, excite);
|
||||
}
|
||||
|
||||
public GasMixture? GetTileMixture(EntityUid? gridUid, EntityUid? mapUid, Vector2i gridTile, bool excite = false)
|
||||
{
|
||||
var ev = new GetTileMixtureMethodEvent(gridUid, mapUid, gridTile, excite);
|
||||
|
||||
// If we've been passed a grid, try to let it handle it.
|
||||
if(gridUid.HasValue)
|
||||
|
||||
@@ -101,8 +101,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if(_spaceWindSoundCooldown == 0 && !string.IsNullOrEmpty(SpaceWindSound))
|
||||
{
|
||||
var coordinates = tile.GridIndices.ToEntityCoordinates(tile.GridIndex, _mapManager);
|
||||
SoundSystem.Play(SpaceWindSound, Filter.Pvs(coordinates),
|
||||
coordinates, AudioHelpers.WithVariation(0.125f).WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100)));
|
||||
_audio.PlayPvs(SpaceWindSound, coordinates, AudioParams.Default.WithVariation(0.125f).WithVolume(MathHelper.Clamp(tile.PressureDifference / 10, 10, 100)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -85,8 +85,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// A few details on the audio parameters for fire.
|
||||
// The greater the fire state, the lesser the pitch variation.
|
||||
// The greater the fire state, the greater the volume.
|
||||
SoundSystem.Play(HotspotSound, Filter.Pvs(coordinates),
|
||||
coordinates, AudioHelpers.WithVariation(0.15f/tile.Hotspot.State).WithVolume(-5f + 5f * tile.Hotspot.State));
|
||||
_audio.PlayPvs(HotspotSound, coordinates, AudioParams.Default.WithVariation(0.15f/tile.Hotspot.State).WithVolume(-5f + 5f * tile.Hotspot.State));
|
||||
}
|
||||
|
||||
if (_hotspotSoundCooldown > HotspotSoundCooldownCycles)
|
||||
|
||||
@@ -7,6 +7,8 @@ using Content.Shared.Atmos.EntitySystems;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
@@ -28,6 +30,7 @@ public sealed partial class AtmosphereSystem : SharedAtmosphereSystem
|
||||
[Dependency] private readonly SharedContainerSystem _containers = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly GasTileOverlaySystem _gasTileOverlaySystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly TransformSystem _transformSystem = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ using Content.Shared.Throwing;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Toggleable;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Events;
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Shared.Verbs;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Player;
|
||||
@@ -239,10 +240,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if (!component.IsConnected)
|
||||
return;
|
||||
|
||||
component.ConnectStream?.Stop();
|
||||
|
||||
if (component.ConnectSound != null)
|
||||
component.ConnectStream = _audioSys.PlayPvs(component.ConnectSound, owner);
|
||||
component.ConnectStream = _audioSys.Stop(component.ConnectStream);
|
||||
component.ConnectStream = _audioSys.PlayPvs(component.ConnectSound, component.Owner)?.Entity;
|
||||
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
@@ -259,10 +258,8 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
_actions.SetToggled(component.ToggleActionEntity, false);
|
||||
|
||||
_internals.DisconnectTank(internals);
|
||||
component.DisconnectStream?.Stop();
|
||||
|
||||
if (component.DisconnectSound != null)
|
||||
component.DisconnectStream = _audioSys.PlayPvs(component.DisconnectSound, owner);
|
||||
component.DisconnectStream = _audioSys.Stop(component.DisconnectStream);
|
||||
component.DisconnectStream = _audioSys.PlayPvs(component.DisconnectSound, component.Owner)?.Entity;
|
||||
|
||||
UpdateUserInterface(ent);
|
||||
}
|
||||
@@ -322,7 +319,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
if(environment != null)
|
||||
_atmosphereSystem.Merge(environment, component.Air);
|
||||
|
||||
_audioSys.Play(component.RuptureSound, Filter.Pvs(owner), Transform(owner).Coordinates, true, AudioParams.Default.WithVariation(0.125f));
|
||||
_audioSys.PlayPvs(component.RuptureSound, Transform(component.Owner).Coordinates, AudioParams.Default.WithVariation(0.125f));
|
||||
|
||||
QueueDel(owner);
|
||||
return;
|
||||
|
||||
@@ -36,6 +36,12 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
[Robust.Shared.IoC.Dependency] private readonly AtmosphereSystem _atmosphereSystem = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly ChunkingSystem _chunkingSys = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Per-tick cache of sessions.
|
||||
/// </summary>
|
||||
private readonly List<ICommonSession> _sessions = new();
|
||||
private UpdatePlayerJob _updateJob;
|
||||
|
||||
private readonly Dictionary<ICommonSession, Dictionary<NetEntity, HashSet<Vector2i>>> _lastSentChunks = new();
|
||||
|
||||
// Oh look its more duplicated decal system code!
|
||||
@@ -56,6 +62,19 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_updateJob = new UpdatePlayerJob()
|
||||
{
|
||||
EntManager = EntityManager,
|
||||
System = this,
|
||||
ChunkIndexPool = _chunkIndexPool,
|
||||
Sessions = _sessions,
|
||||
ChunkingSys = _chunkingSys,
|
||||
MapManager = _mapManager,
|
||||
ChunkViewerPool = _chunkViewerPool,
|
||||
LastSentChunks = _lastSentChunks,
|
||||
};
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_confMan.OnValueChanged(CCVars.NetGasOverlayTickRate, UpdateTickRate, true);
|
||||
_confMan.OnValueChanged(CCVars.GasOverlayThresholds, UpdateThresholds, true);
|
||||
@@ -69,7 +88,7 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
{
|
||||
// This **shouldn't** be required, but just in case we ever get entity prototypes that have gas overlays, we
|
||||
// need to ensure that we send an initial full state to players.
|
||||
Dirty(component);
|
||||
Dirty(uid, component);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -287,87 +306,21 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
// Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range
|
||||
// If they are, check if they need the new data to send (i.e. if there's an overlay for the gas).
|
||||
// Afterwards we reset all the chunk data for the next time we tick.
|
||||
var players = _playerManager.Sessions.Where(x => x.Status == SessionStatus.InGame).ToArray();
|
||||
var opts = new ParallelOptions { MaxDegreeOfParallelism = _parMan.ParallelProcessCount };
|
||||
Parallel.ForEach(players, opts, p => UpdatePlayer(p, curTick));
|
||||
}
|
||||
_sessions.Clear();
|
||||
|
||||
private void UpdatePlayer(ICommonSession playerSession, GameTick curTick)
|
||||
{
|
||||
var chunksInRange = _chunkingSys.GetChunksForSession(playerSession, ChunkSize, _chunkIndexPool, _chunkViewerPool);
|
||||
var previouslySent = _lastSentChunks[playerSession];
|
||||
|
||||
var ev = new GasOverlayUpdateEvent();
|
||||
|
||||
foreach (var (netGrid, oldIndices) in previouslySent)
|
||||
foreach (var player in _playerManager.Sessions)
|
||||
{
|
||||
// Mark the whole grid as stale and flag for removal.
|
||||
if (!chunksInRange.TryGetValue(netGrid, out var chunks))
|
||||
{
|
||||
previouslySent.Remove(netGrid);
|
||||
|
||||
// If grid was deleted then don't worry about sending it to the client.
|
||||
if (!TryGetEntity(netGrid, out var gridId) || !_mapManager.IsGrid(gridId.Value))
|
||||
ev.RemovedChunks[netGrid] = oldIndices;
|
||||
else
|
||||
{
|
||||
oldIndices.Clear();
|
||||
_chunkIndexPool.Return(oldIndices);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var old = _chunkIndexPool.Get();
|
||||
DebugTools.Assert(old.Count == 0);
|
||||
foreach (var chunk in oldIndices)
|
||||
{
|
||||
if (!chunks.Contains(chunk))
|
||||
old.Add(chunk);
|
||||
}
|
||||
|
||||
if (old.Count == 0)
|
||||
_chunkIndexPool.Return(old);
|
||||
else
|
||||
ev.RemovedChunks.Add(netGrid, old);
|
||||
}
|
||||
|
||||
foreach (var (netGrid, gridChunks) in chunksInRange)
|
||||
{
|
||||
// Not all grids have atmospheres.
|
||||
if (!TryGetEntity(netGrid, out var grid) || !TryComp(grid, out GasTileOverlayComponent? overlay))
|
||||
if (player.Status != SessionStatus.InGame)
|
||||
continue;
|
||||
|
||||
List<GasOverlayChunk> dataToSend = new();
|
||||
ev.UpdatedChunks[netGrid] = dataToSend;
|
||||
|
||||
previouslySent.TryGetValue(netGrid, out var previousChunks);
|
||||
|
||||
foreach (var index in gridChunks)
|
||||
{
|
||||
if (!overlay.Chunks.TryGetValue(index, out var value))
|
||||
continue;
|
||||
|
||||
if (previousChunks != null &&
|
||||
previousChunks.Contains(index) &&
|
||||
value.LastUpdate != curTick)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dataToSend.Add(value);
|
||||
}
|
||||
|
||||
previouslySent[netGrid] = gridChunks;
|
||||
if (previousChunks != null)
|
||||
{
|
||||
previousChunks.Clear();
|
||||
_chunkIndexPool.Return(previousChunks);
|
||||
}
|
||||
_sessions.Add(player);
|
||||
}
|
||||
|
||||
if (ev.UpdatedChunks.Count != 0 || ev.RemovedChunks.Count != 0)
|
||||
RaiseNetworkEvent(ev, playerSession.ConnectedClient);
|
||||
if (_sessions.Count > 0)
|
||||
{
|
||||
_updateJob.CurrentTick = curTick;
|
||||
_parMan.ProcessNow(_updateJob, _sessions.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset(RoundRestartCleanupEvent ev)
|
||||
@@ -383,5 +336,107 @@ namespace Content.Server.Atmos.EntitySystems
|
||||
data.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
/// <summary>
|
||||
/// Updates per player gas overlay data.
|
||||
/// </summary>
|
||||
private record struct UpdatePlayerJob : IParallelRobustJob
|
||||
{
|
||||
public int BatchSize => 2;
|
||||
|
||||
public IEntityManager EntManager;
|
||||
public IMapManager MapManager;
|
||||
public ChunkingSystem ChunkingSys;
|
||||
public GasTileOverlaySystem System;
|
||||
public ObjectPool<HashSet<Vector2i>> ChunkIndexPool;
|
||||
public ObjectPool<Dictionary<NetEntity, HashSet<Vector2i>>> ChunkViewerPool;
|
||||
|
||||
public GameTick CurrentTick;
|
||||
public Dictionary<ICommonSession, Dictionary<NetEntity, HashSet<Vector2i>>> LastSentChunks;
|
||||
public List<ICommonSession> Sessions;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var playerSession = Sessions[index];
|
||||
var chunksInRange = ChunkingSys.GetChunksForSession(playerSession, ChunkSize, ChunkIndexPool, ChunkViewerPool);
|
||||
var previouslySent = LastSentChunks[playerSession];
|
||||
|
||||
var ev = new GasOverlayUpdateEvent();
|
||||
|
||||
foreach (var (netGrid, oldIndices) in previouslySent)
|
||||
{
|
||||
// Mark the whole grid as stale and flag for removal.
|
||||
if (!chunksInRange.TryGetValue(netGrid, out var chunks))
|
||||
{
|
||||
previouslySent.Remove(netGrid);
|
||||
|
||||
// If grid was deleted then don't worry about sending it to the client.
|
||||
if (!EntManager.TryGetEntity(netGrid, out var gridId) || !MapManager.IsGrid(gridId.Value))
|
||||
ev.RemovedChunks[netGrid] = oldIndices;
|
||||
else
|
||||
{
|
||||
oldIndices.Clear();
|
||||
ChunkIndexPool.Return(oldIndices);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var old = ChunkIndexPool.Get();
|
||||
DebugTools.Assert(old.Count == 0);
|
||||
foreach (var chunk in oldIndices)
|
||||
{
|
||||
if (!chunks.Contains(chunk))
|
||||
old.Add(chunk);
|
||||
}
|
||||
|
||||
if (old.Count == 0)
|
||||
ChunkIndexPool.Return(old);
|
||||
else
|
||||
ev.RemovedChunks.Add(netGrid, old);
|
||||
}
|
||||
|
||||
foreach (var (netGrid, gridChunks) in chunksInRange)
|
||||
{
|
||||
// Not all grids have atmospheres.
|
||||
if (!EntManager.TryGetEntity(netGrid, out var grid) || !EntManager.TryGetComponent(grid, out GasTileOverlayComponent? overlay))
|
||||
continue;
|
||||
|
||||
List<GasOverlayChunk> dataToSend = new();
|
||||
ev.UpdatedChunks[netGrid] = dataToSend;
|
||||
|
||||
previouslySent.TryGetValue(netGrid, out var previousChunks);
|
||||
|
||||
foreach (var gIndex in gridChunks)
|
||||
{
|
||||
if (!overlay.Chunks.TryGetValue(gIndex, out var value))
|
||||
continue;
|
||||
|
||||
if (previousChunks != null &&
|
||||
previousChunks.Contains(gIndex) &&
|
||||
value.LastUpdate != CurrentTick)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
dataToSend.Add(value);
|
||||
}
|
||||
|
||||
previouslySent[netGrid] = gridChunks;
|
||||
if (previousChunks != null)
|
||||
{
|
||||
previousChunks.Clear();
|
||||
ChunkIndexPool.Return(previousChunks);
|
||||
}
|
||||
}
|
||||
|
||||
if (ev.UpdatedChunks.Count != 0 || ev.RemovedChunks.Count != 0)
|
||||
System.RaiseNetworkEvent(ev, playerSession.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,9 +118,7 @@ public sealed class RottingSystem : EntitySystem
|
||||
return;
|
||||
|
||||
var molsToDump = perishable.MolsPerSecondPerUnitMass * physics.FixturesMass * (float) component.TotalRotTime.TotalSeconds;
|
||||
var transform = Transform(uid);
|
||||
var indices = _transform.GetGridOrMapTilePosition(uid, transform);
|
||||
var tileMix = _atmosphere.GetTileMixture(transform.GridUid, transform.MapUid, indices, true);
|
||||
var tileMix = _atmosphere.GetTileMixture(uid, excite: true);
|
||||
tileMix?.AdjustMoles(Gas.Miasma, molsToDump);
|
||||
}
|
||||
|
||||
@@ -214,8 +212,7 @@ public sealed class RottingSystem : EntitySystem
|
||||
// We need a way to get the mass of the mob alone without armor etc in the future
|
||||
// or just remove the mass mechanics altogether because they aren't good.
|
||||
var molRate = perishable.MolsPerSecondPerUnitMass * (float) rotting.RotUpdateRate.TotalSeconds;
|
||||
var indices = _transform.GetGridOrMapTilePosition(uid);
|
||||
var tileMix = _atmosphere.GetTileMixture(xform.GridUid, null, indices, true);
|
||||
var tileMix = _atmosphere.GetTileMixture(uid, excite: true);
|
||||
tileMix?.AdjustMoles(Gas.Miasma, molRate * physics.FixturesMass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Shared.Atmos.Monitor;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
@@ -17,6 +18,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
{
|
||||
[Dependency] private readonly SharedAmbientSoundSystem _ambientSoundSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly NodeContainerSystem _nodeContainer = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -35,10 +37,11 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
return;
|
||||
|
||||
if (Loc.TryGetString("gas-valve-system-examined", out var str,
|
||||
("statusColor", valve.Open ? "green" : "orange"),
|
||||
("open", valve.Open)
|
||||
))
|
||||
("statusColor", valve.Open ? "green" : "orange"),
|
||||
("open", valve.Open)))
|
||||
{
|
||||
args.PushMarkup(str);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStartup(EntityUid uid, GasValveComponent component, ComponentStartup args)
|
||||
@@ -50,7 +53,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
private void OnActivate(EntityUid uid, GasValveComponent component, ActivateInWorldEvent args)
|
||||
{
|
||||
Toggle(uid, component);
|
||||
SoundSystem.Play(component.ValveSound.GetSound(), Filter.Pvs(uid), uid, AudioHelpers.WithVariation(0.25f));
|
||||
_audio.PlayPvs(component.ValveSound, uid, AudioParams.Default.WithVariation(0.25f));
|
||||
}
|
||||
|
||||
public void Set(EntityUid uid, GasValveComponent component, bool value)
|
||||
|
||||
@@ -110,9 +110,7 @@ namespace Content.Server.Atmos.Piping.Binary.EntitySystems
|
||||
// Some of the gas from the mixture leaks when overclocked.
|
||||
if (pump.Overclocked)
|
||||
{
|
||||
var transform = Transform(uid);
|
||||
var indices = _transformSystem.GetGridOrMapTilePosition(uid, transform);
|
||||
var tile = _atmosphereSystem.GetTileMixture(transform.GridUid, null, indices, true);
|
||||
var tile = _atmosphereSystem.GetTileMixture(uid, excite: true);
|
||||
|
||||
if (tile != null)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,8 @@ using Content.Shared.Hands.EntitySystems;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Lock;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
|
||||
|
||||
@@ -67,8 +67,7 @@ namespace Content.Server.Atmos.Piping.Unary.EntitySystems
|
||||
if (xform.GridUid == null)
|
||||
return;
|
||||
|
||||
var position = _transformSystem.GetGridOrMapTilePosition(uid, xform);
|
||||
|
||||
var position = _transformSystem.GetGridTilePositionOrDefault((uid,xform));
|
||||
var environment = _atmosphereSystem.GetTileMixture(xform.GridUid, xform.MapUid, position, true);
|
||||
|
||||
Scrub(timeDelta, scrubber, environment, outlet);
|
||||
|
||||
@@ -79,8 +79,7 @@ namespace Content.Server.Atmos.Portable
|
||||
if (xform.GridUid == null)
|
||||
return;
|
||||
|
||||
var position = _transformSystem.GetGridOrMapTilePosition(uid, xform);
|
||||
|
||||
var position = _transformSystem.GetGridTilePositionOrDefault((uid,xform));
|
||||
var environment = _atmosphereSystem.GetTileMixture(xform.GridUid, xform.MapUid, position, true);
|
||||
|
||||
var running = Scrub(timeDelta, component, environment);
|
||||
|
||||
@@ -1,8 +1,41 @@
|
||||
using Content.Server.GameTicking.Events;
|
||||
using Content.Shared.Audio;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Audio;
|
||||
|
||||
public sealed class ContentAudioSystem : SharedContentAudioSystem
|
||||
{
|
||||
[Dependency] private readonly AudioSystem _serverAudio = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<RoundStartingEvent>(OnRoundStart);
|
||||
_protoManager.PrototypesReloaded += OnProtoReload;
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.ByType.ContainsKey(typeof(AudioPresetPrototype)))
|
||||
return;
|
||||
|
||||
_serverAudio.ReloadPresets();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_protoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
private void OnRoundStart(RoundStartingEvent ev)
|
||||
{
|
||||
// On cleanup all entities get purged so need to ensure audio presets are still loaded
|
||||
// yeah it's whacky af.
|
||||
_serverAudio.ReloadPresets();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ using Content.Server.Beam.Components;
|
||||
using Content.Shared.Beam;
|
||||
using Content.Shared.Beam.Components;
|
||||
using Content.Shared.Physics;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
|
||||
@@ -13,6 +13,8 @@ using Content.Shared.Slippery;
|
||||
using Content.Shared.StatusEffect;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -15,6 +15,7 @@ using Content.Shared.Popups;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -29,6 +30,7 @@ namespace Content.Server.Bible
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly UseDelaySystem _delay = default!;
|
||||
|
||||
public override void Initialize()
|
||||
@@ -80,8 +82,8 @@ namespace Content.Server.Bible
|
||||
summonableComp.Summon = null;
|
||||
}
|
||||
summonableComp.AlreadySummoned = false;
|
||||
_popupSystem.PopupEntity(Loc.GetString("bible-summon-respawn-ready", ("book", uid)), uid, PopupType.Medium);
|
||||
SoundSystem.Play("/Audio/Effects/radpulse9.ogg", Filter.Pvs(uid), uid, AudioParams.Default.WithVolume(-4f));
|
||||
_popupSystem.PopupEntity(Loc.GetString("bible-summon-respawn-ready", ("book", summonableComp.Owner)), summonableComp.Owner, PopupType.Medium);
|
||||
_audio.PlayPvs("/Audio/Effects/radpulse9.ogg", summonableComp.Owner, AudioParams.Default.WithVolume(-4f));
|
||||
// Clean up the accumulator and respawn tracking component
|
||||
summonableComp.Accumulator = 0;
|
||||
_remQueue.Enqueue(uid);
|
||||
@@ -107,7 +109,7 @@ namespace Content.Server.Bible
|
||||
{
|
||||
_popupSystem.PopupEntity(Loc.GetString("bible-sizzle"), args.User, args.User);
|
||||
|
||||
SoundSystem.Play(component.SizzleSoundPath.GetSound(), Filter.Pvs(args.User), args.User);
|
||||
_audio.PlayPvs(component.SizzleSoundPath, args.User);
|
||||
_damageableSystem.TryChangeDamage(args.User, component.DamageOnUntrainedUse, true, origin: uid);
|
||||
_delay.BeginDelay(uid, delay);
|
||||
|
||||
@@ -125,7 +127,7 @@ namespace Content.Server.Bible
|
||||
var selfFailMessage = Loc.GetString(component.LocPrefix + "-heal-fail-self", ("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid));
|
||||
_popupSystem.PopupEntity(selfFailMessage, args.User, args.User, PopupType.MediumCaution);
|
||||
|
||||
SoundSystem.Play("/Audio/Effects/hit_kick.ogg", Filter.Pvs(args.Target.Value), args.User);
|
||||
_audio.PlayPvs("/Audio/Effects/hit_kick.ogg", args.User);
|
||||
_damageableSystem.TryChangeDamage(args.Target.Value, component.DamageOnFail, true, origin: uid);
|
||||
_delay.BeginDelay(uid, delay);
|
||||
return;
|
||||
@@ -149,7 +151,7 @@ namespace Content.Server.Bible
|
||||
|
||||
var selfMessage = Loc.GetString(component.LocPrefix + "-heal-success-self", ("target", Identity.Entity(args.Target.Value, EntityManager)),("bible", uid));
|
||||
_popupSystem.PopupEntity(selfMessage, args.User, args.User, PopupType.Large);
|
||||
SoundSystem.Play(component.HealSoundPath.GetSound(), Filter.Pvs(args.Target.Value), args.User);
|
||||
_audio.PlayPvs(component.HealSoundPath, args.User);
|
||||
_delay.BeginDelay(uid, delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ using Content.Shared.Speech.EntitySystems;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Speech.EntitySystems;
|
||||
using Robust.Server.Audio;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Body.Systems;
|
||||
|
||||
@@ -129,7 +130,7 @@ public sealed class BodySystem : SharedBodySystem
|
||||
var filter = Filter.Pvs(bodyId, entityManager: EntityManager);
|
||||
var audio = AudioParams.Default.WithVariation(0.025f);
|
||||
|
||||
_audio.Play(body.GibSound, filter, coordinates, true, audio);
|
||||
_audio.PlayStatic(body.GibSound, filter, coordinates, true, audio);
|
||||
|
||||
foreach (var entity in gibs)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Content.Shared.Chemistry.Reagent;
|
||||
using System.Linq;
|
||||
using Content.Shared.Atmos;
|
||||
@@ -11,11 +13,12 @@ public sealed class MutationSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
private List<ReagentPrototype> _allChemicals = default!;
|
||||
private WeightedRandomFillSolutionPrototype _randomChems = default!;
|
||||
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_allChemicals = _prototypeManager.EnumeratePrototypes<ReagentPrototype>().ToList();
|
||||
_randomChems = _prototypeManager.Index<WeightedRandomFillSolutionPrototype>("RandomPickBotanyReagent");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +40,7 @@ public sealed class MutationSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Add up everything in the bits column and put the number here.
|
||||
const int totalbits = 270;
|
||||
const int totalbits = 275;
|
||||
|
||||
// Tolerances (55)
|
||||
MutateFloat(ref seed.NutrientConsumption , 0.05f, 1.2f, 5, totalbits, severity);
|
||||
@@ -81,10 +84,10 @@ public sealed class MutationSystem : EntitySystem
|
||||
MutateGasses(ref seed.ConsumeGasses, 0.01f, 0.5f, 1, totalbits, severity);
|
||||
|
||||
// Chems (20)
|
||||
MutateChemicals(ref seed.Chemicals, 5, 20, totalbits, severity);
|
||||
MutateChemicals(ref seed.Chemicals, 20, totalbits, severity);
|
||||
|
||||
// Species (5)
|
||||
MutateSpecies(ref seed, 5, totalbits, severity);
|
||||
// Species (10)
|
||||
MutateSpecies(ref seed, 10, totalbits, severity);
|
||||
}
|
||||
|
||||
public SeedData Cross(SeedData a, SeedData b)
|
||||
@@ -246,7 +249,7 @@ public sealed class MutationSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void MutateChemicals(ref Dictionary<string, SeedChemQuantity> chemicals, int max, int bits, int totalbits, float mult)
|
||||
private void MutateChemicals(ref Dictionary<string, SeedChemQuantity> chemicals, int bits, int totalbits, float mult)
|
||||
{
|
||||
float probModify = mult * bits / totalbits;
|
||||
probModify = Math.Clamp(probModify, 0, 1);
|
||||
@@ -254,11 +257,11 @@ public sealed class MutationSystem : EntitySystem
|
||||
return;
|
||||
|
||||
// Add a random amount of a random chemical to this set of chemicals
|
||||
ReagentPrototype selectedChemical = _robustRandom.Pick(_allChemicals);
|
||||
if (selectedChemical != null)
|
||||
if (_randomChems != null)
|
||||
{
|
||||
string chemicalId = selectedChemical.ID;
|
||||
int amount = _robustRandom.Next(1, max);
|
||||
var pick = _randomChems.Pick(_robustRandom);
|
||||
string chemicalId = pick.reagent;
|
||||
int amount = _robustRandom.Next(1, ((int)pick.quantity));
|
||||
SeedChemQuantity seedChemQuantity = new SeedChemQuantity();
|
||||
if (chemicals.ContainsKey(chemicalId))
|
||||
{
|
||||
|
||||
@@ -19,6 +19,7 @@ using Content.Shared.Random;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
@@ -10,6 +10,8 @@ using Content.Shared.Stealth;
|
||||
using Content.Shared.Stealth.Components;
|
||||
using Content.Shared.Storage.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -13,6 +13,8 @@ using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -4,6 +4,7 @@ using Content.Shared.CartridgeLoader;
|
||||
using Content.Shared.CartridgeLoader.Cartridges;
|
||||
using Content.Shared.Popups;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
|
||||
84
Content.Server/Chat/Managers/ChatManager.RateLimit.cs
Normal file
84
Content.Server/Chat/Managers/ChatManager.RateLimit.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Database;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Chat.Managers;
|
||||
|
||||
internal sealed partial class ChatManager
|
||||
{
|
||||
private readonly Dictionary<ICommonSession, RateLimitDatum> _rateLimitData = new();
|
||||
|
||||
public bool HandleRateLimit(ICommonSession player)
|
||||
{
|
||||
ref var datum = ref CollectionsMarshal.GetValueRefOrAddDefault(_rateLimitData, player, out _);
|
||||
var time = _gameTiming.RealTime;
|
||||
if (datum.CountExpires < time)
|
||||
{
|
||||
// Period expired, reset it.
|
||||
var periodLength = _configurationManager.GetCVar(CCVars.ChatRateLimitPeriod);
|
||||
datum.CountExpires = time + TimeSpan.FromSeconds(periodLength);
|
||||
datum.Count = 0;
|
||||
datum.Announced = false;
|
||||
}
|
||||
|
||||
var maxCount = _configurationManager.GetCVar(CCVars.ChatRateLimitCount);
|
||||
datum.Count += 1;
|
||||
|
||||
if (datum.Count <= maxCount)
|
||||
return true;
|
||||
|
||||
// Breached rate limits, inform admins if configured.
|
||||
if (_configurationManager.GetCVar(CCVars.ChatRateLimitAnnounceAdmins))
|
||||
{
|
||||
if (datum.NextAdminAnnounce < time)
|
||||
{
|
||||
SendAdminAlert(Loc.GetString("chat-manager-rate-limit-admin-announcement", ("player", player.Name)));
|
||||
var delay = _configurationManager.GetCVar(CCVars.ChatRateLimitAnnounceAdminsDelay);
|
||||
datum.NextAdminAnnounce = time + TimeSpan.FromSeconds(delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (!datum.Announced)
|
||||
{
|
||||
DispatchServerMessage(player, Loc.GetString("chat-manager-rate-limited"), suppressLog: true);
|
||||
_adminLogger.Add(LogType.ChatRateLimited, LogImpact.Medium, $"Player {player} breached chat rate limits");
|
||||
|
||||
datum.Announced = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Disconnected)
|
||||
_rateLimitData.Remove(e.Session);
|
||||
}
|
||||
|
||||
private struct RateLimitDatum
|
||||
{
|
||||
/// <summary>
|
||||
/// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) this rate limit period will expire at.
|
||||
/// </summary>
|
||||
public TimeSpan CountExpires;
|
||||
|
||||
/// <summary>
|
||||
/// How many messages have been sent in the current rate limit period.
|
||||
/// </summary>
|
||||
public int Count;
|
||||
|
||||
/// <summary>
|
||||
/// Have we announced to the player that they've been blocked in this rate limit period?
|
||||
/// </summary>
|
||||
public bool Announced;
|
||||
|
||||
/// <summary>
|
||||
/// Time stamp (relative to <see cref="IGameTiming.RealTime"/>) of the
|
||||
/// next time we can send an announcement to admins about rate limit breach.
|
||||
/// </summary>
|
||||
public TimeSpan NextAdminAnnounce;
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,12 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Mind;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Chat.Managers
|
||||
@@ -23,7 +25,7 @@ namespace Content.Server.Chat.Managers
|
||||
/// <summary>
|
||||
/// Dispatches chat messages to clients.
|
||||
/// </summary>
|
||||
internal sealed class ChatManager : IChatManager
|
||||
internal sealed partial class ChatManager : IChatManager
|
||||
{
|
||||
private static readonly Dictionary<string, string> PatronOocColors = new()
|
||||
{
|
||||
@@ -42,6 +44,8 @@ namespace Content.Server.Chat.Managers
|
||||
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _netConfigManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
private IServerSponsorsManager? _sponsorsManager; // Corvax-Sponsors
|
||||
|
||||
/// <summary>
|
||||
@@ -62,6 +66,8 @@ namespace Content.Server.Chat.Managers
|
||||
|
||||
_configurationManager.OnValueChanged(CCVars.OocEnabled, OnOocEnabledChanged, true);
|
||||
_configurationManager.OnValueChanged(CCVars.AdminOocEnabled, OnAdminOocEnabledChanged, true);
|
||||
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnOocEnabledChanged(bool val)
|
||||
@@ -181,6 +187,9 @@ namespace Content.Server.Chat.Managers
|
||||
/// <param name="type">The type of message.</param>
|
||||
public void TrySendOOCMessage(ICommonSession player, string message, OOCChatType type)
|
||||
{
|
||||
if (!HandleRateLimit(player))
|
||||
return;
|
||||
|
||||
// Check if message exceeds the character limit
|
||||
if (message.Length > MaxMessageLength)
|
||||
{
|
||||
|
||||
@@ -41,5 +41,13 @@ namespace Content.Server.Chat.Managers
|
||||
|
||||
[return: NotNullIfNotNull(nameof(author))]
|
||||
ChatUser? EnsurePlayer(NetUserId? author);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a player sends a chat message to handle rate limits.
|
||||
/// Will update counts and do necessary actions if breached.
|
||||
/// </summary>
|
||||
/// <param name="player">The player sending a chat message.</param>
|
||||
/// <returns>False if the player has violated rate limits and should be blocked from sending further messages.</returns>
|
||||
bool HandleRateLimit(ICommonSession player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ using Content.Shared.Players;
|
||||
using Content.Shared.Radio;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Network;
|
||||
@@ -185,6 +186,9 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (player != null && !_chatManager.HandleRateLimit(player))
|
||||
return;
|
||||
|
||||
// Sus
|
||||
if (player?.AttachedEntity is { Valid: true } entity && source != entity)
|
||||
{
|
||||
@@ -194,6 +198,8 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (!CanSendInGame(message, shell, player))
|
||||
return;
|
||||
|
||||
ignoreActionBlocker = CheckIgnoreSpeechBlocker(source, ignoreActionBlocker);
|
||||
|
||||
// this method is a disaster
|
||||
// every second i have to spend working with this code is fucking agony
|
||||
// scientists have to wonder how any of this was merged
|
||||
@@ -237,7 +243,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
{
|
||||
if (TryProccessRadioMessage(source, message, out var modMessage, out var channel))
|
||||
{
|
||||
SendEntityWhisper(source, modMessage, range, channel, nameOverride, ignoreActionBlocker);
|
||||
SendEntityWhisper(source, modMessage, range, channel, nameOverride, hideLog, ignoreActionBlocker);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -269,6 +275,9 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (!CanSendInGame(message, shell, player))
|
||||
return;
|
||||
|
||||
if (player != null && !_chatManager.HandleRateLimit(player))
|
||||
return;
|
||||
|
||||
// It doesn't make any sense for a non-player to send in-game OOC messages, whereas non-players may be sending
|
||||
// in-game IC messages.
|
||||
if (player?.AttachedEntity is not { Valid: true } entity || source != entity)
|
||||
@@ -319,7 +328,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
if (playSound)
|
||||
{
|
||||
if (sender == Loc.GetString("admin-announce-announcer-default")) announcementSound = new SoundPathSpecifier(CentComAnnouncementSound); // Corvax-Announcements: Support custom alert sound from admin panel
|
||||
SoundSystem.Play(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), announcementSound?.Params ?? AudioParams.Default.WithVolume(-2f));
|
||||
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, Filter.Broadcast(), true, announcementSound?.Params ?? AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Global station announcement from {sender}: {message}");
|
||||
}
|
||||
@@ -357,7 +366,7 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
|
||||
if (playDefaultSound)
|
||||
{
|
||||
SoundSystem.Play(announcementSound?.GetSound() ?? DefaultAnnouncementSound, filter, AudioParams.Default.WithVolume(-2f));
|
||||
_audio.PlayGlobal(announcementSound?.GetSound() ?? DefaultAnnouncementSound, filter, true, AudioParams.Default.WithVolume(-2f));
|
||||
}
|
||||
|
||||
_adminLogger.Add(LogType.Chat, LogImpact.Low, $"Station Announcement on {station} from {sender}: {message}");
|
||||
@@ -738,6 +747,17 @@ public sealed partial class ChatSystem : SharedChatSystem
|
||||
return ev.Message;
|
||||
}
|
||||
|
||||
public bool CheckIgnoreSpeechBlocker(EntityUid sender, bool ignoreBlocker)
|
||||
{
|
||||
if (ignoreBlocker)
|
||||
return ignoreBlocker;
|
||||
|
||||
var ev = new CheckIgnoreSpeechBlockerEvent(sender, ignoreBlocker);
|
||||
RaiseLocalEvent(sender, ev, true);
|
||||
|
||||
return ev.IgnoreBlocker;
|
||||
}
|
||||
|
||||
private IEnumerable<INetChannel> GetDeadChatClients()
|
||||
{
|
||||
return Filter.Empty()
|
||||
@@ -875,6 +895,18 @@ public sealed class TransformSpeechEvent : EntityEventArgs
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CheckIgnoreSpeechBlockerEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid Sender;
|
||||
public bool IgnoreBlocker;
|
||||
|
||||
public CheckIgnoreSpeechBlockerEvent(EntityUid sender, bool ignoreBlocker)
|
||||
{
|
||||
Sender = sender;
|
||||
IgnoreBlocker = ignoreBlocker;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised on an entity when it speaks, either through 'say' or 'whisper'.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
@@ -7,6 +7,8 @@ using Content.Shared.Chemistry;
|
||||
using Content.Shared.Chemistry.EntitySystems;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
|
||||
namespace Content.Server.Chemistry.EntitySystems;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Content.Shared.Database;
|
||||
using Content.Shared.Emag.Components;
|
||||
using Content.Shared.Emag.Systems;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Audio;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Containers;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Content.Shared.FixedPoint;
|
||||
using Content.Shared.Maps;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
@@ -72,7 +73,8 @@ namespace Content.Server.Chemistry.ReactionEffects
|
||||
var smoke = args.EntityManager.System<SmokeSystem>();
|
||||
smoke.StartSmoke(ent, splitSolution, _duration, spreadAmount);
|
||||
|
||||
args.EntityManager.System<SharedAudioSystem>().PlayPvs(_sound, args.SolutionEntity, AudioHelpers.WithVariation(0.125f));
|
||||
var audio = args.EntityManager.System<SharedAudioSystem>();
|
||||
audio.PlayPvs(_sound, args.SolutionEntity, AudioHelpers.WithVariation(0.125f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,10 @@ public sealed class ChunkingSystem : EntitySystem
|
||||
_configurationManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnPvsRangeChanged);
|
||||
}
|
||||
|
||||
private void OnPvsRangeChanged(float value) => _baseViewBounds = Box2.UnitCentered.Scale(value);
|
||||
private void OnPvsRangeChanged(float value)
|
||||
{
|
||||
_baseViewBounds = Box2.UnitCentered.Scale(value);
|
||||
}
|
||||
|
||||
public Dictionary<NetEntity, HashSet<Vector2i>> GetChunksForSession(
|
||||
ICommonSession session,
|
||||
@@ -46,71 +49,85 @@ public sealed class ChunkingSystem : EntitySystem
|
||||
ObjectPool<HashSet<Vector2i>> indexPool,
|
||||
ObjectPool<Dictionary<NetEntity, HashSet<Vector2i>>> viewerPool,
|
||||
float? viewEnlargement = null)
|
||||
{
|
||||
var viewers = GetSessionViewers(session);
|
||||
var chunks = GetChunksForViewers(viewers, chunkSize, indexPool, viewerPool, viewEnlargement ?? chunkSize);
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private HashSet<EntityUid> GetSessionViewers(ICommonSession session)
|
||||
{
|
||||
var viewers = new HashSet<EntityUid>();
|
||||
if (session.Status != SessionStatus.InGame || session.AttachedEntity is null)
|
||||
return viewers;
|
||||
|
||||
viewers.Add(session.AttachedEntity.Value);
|
||||
|
||||
foreach (var uid in session.ViewSubscriptions)
|
||||
{
|
||||
viewers.Add(uid);
|
||||
}
|
||||
|
||||
return viewers;
|
||||
}
|
||||
|
||||
private Dictionary<NetEntity, HashSet<Vector2i>> GetChunksForViewers(
|
||||
HashSet<EntityUid> viewers,
|
||||
int chunkSize,
|
||||
ObjectPool<HashSet<Vector2i>> indexPool,
|
||||
ObjectPool<Dictionary<NetEntity, HashSet<Vector2i>>> viewerPool,
|
||||
float viewEnlargement)
|
||||
{
|
||||
var chunks = viewerPool.Get();
|
||||
DebugTools.Assert(chunks.Count == 0);
|
||||
|
||||
foreach (var viewerUid in viewers)
|
||||
if (session.Status != SessionStatus.InGame || session.AttachedEntity is not {} player)
|
||||
return chunks;
|
||||
|
||||
var enlargement = viewEnlargement ?? chunkSize;
|
||||
AddViewerChunks(player, chunks, indexPool, chunkSize, enlargement);
|
||||
foreach (var uid in session.ViewSubscriptions)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(viewerUid, out var xform))
|
||||
{
|
||||
Log.Error($"Player has deleted viewer entities? Viewers: {string.Join(", ", viewers.Select(ToPrettyString))}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var pos = _transform.GetWorldPosition(xform);
|
||||
var bounds = _baseViewBounds.Translated(pos).Enlarged(viewEnlargement);
|
||||
var grids = new List<Entity<MapGridComponent>>();
|
||||
_mapManager.FindGridsIntersecting(xform.MapID, bounds, ref grids, true);
|
||||
|
||||
foreach (var grid in grids)
|
||||
{
|
||||
var netGrid = GetNetEntity(grid);
|
||||
|
||||
if (!chunks.TryGetValue(netGrid, out var set))
|
||||
{
|
||||
chunks[netGrid] = set = indexPool.Get();
|
||||
DebugTools.Assert(set.Count == 0);
|
||||
}
|
||||
|
||||
var enumerator = new ChunkIndicesEnumerator(_transform.GetInvWorldMatrix(grid).TransformBox(bounds), chunkSize);
|
||||
|
||||
while (enumerator.MoveNext(out var indices))
|
||||
{
|
||||
set.Add(indices.Value);
|
||||
}
|
||||
}
|
||||
AddViewerChunks(uid, chunks, indexPool, chunkSize, enlargement);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private void AddViewerChunks(EntityUid viewer,
|
||||
Dictionary<NetEntity, HashSet<Vector2i>> chunks,
|
||||
ObjectPool<HashSet<Vector2i>> indexPool,
|
||||
int chunkSize,
|
||||
float viewEnlargement)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(viewer, out var xform))
|
||||
return;
|
||||
|
||||
var pos = _transform.GetWorldPosition(xform);
|
||||
var bounds = _baseViewBounds.Translated(pos).Enlarged(viewEnlargement);
|
||||
|
||||
var state = new QueryState(chunks, indexPool, chunkSize, bounds, _transform, EntityManager);
|
||||
_mapManager.FindGridsIntersecting(xform.MapID, bounds, ref state, AddGridChunks, true);
|
||||
}
|
||||
|
||||
private static bool AddGridChunks(
|
||||
EntityUid uid,
|
||||
MapGridComponent grid,
|
||||
ref QueryState state)
|
||||
{
|
||||
var netGrid = state.EntityManager.GetNetEntity(uid);
|
||||
if (!state.Chunks.TryGetValue(netGrid, out var set))
|
||||
{
|
||||
state.Chunks[netGrid] = set = state.Pool.Get();
|
||||
DebugTools.Assert(set.Count == 0);
|
||||
}
|
||||
|
||||
var aabb = state.Transform.GetInvWorldMatrix(uid).TransformBox(state.Bounds);
|
||||
var enumerator = new ChunkIndicesEnumerator(aabb, state.ChunkSize);
|
||||
while (enumerator.MoveNext(out var indices))
|
||||
{
|
||||
set.Add(indices.Value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private readonly struct QueryState
|
||||
{
|
||||
public readonly Dictionary<NetEntity, HashSet<Vector2i>> Chunks;
|
||||
public readonly ObjectPool<HashSet<Vector2i>> Pool;
|
||||
public readonly int ChunkSize;
|
||||
public readonly Box2 Bounds;
|
||||
public readonly SharedTransformSystem Transform;
|
||||
public readonly EntityManager EntityManager;
|
||||
|
||||
public QueryState(
|
||||
Dictionary<NetEntity, HashSet<Vector2i>> chunks,
|
||||
ObjectPool<HashSet<Vector2i>> pool,
|
||||
int chunkSize,
|
||||
Box2 bounds,
|
||||
SharedTransformSystem transform,
|
||||
EntityManager entityManager)
|
||||
{
|
||||
Chunks = chunks;
|
||||
Pool = pool;
|
||||
ChunkSize = chunkSize;
|
||||
Bounds = bounds;
|
||||
Transform = transform;
|
||||
EntityManager = entityManager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user