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:
Arthur Asimov
2023-12-03 15:10:26 +03:00
629 changed files with 1000947 additions and 817412 deletions

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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});

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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()

View File

@@ -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)

View File

@@ -1,4 +1,5 @@
using Robust.Client.ResourceManagement;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
namespace Content.Client.IoC

View File

@@ -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

View File

@@ -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))

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 =

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -139,7 +139,6 @@ public sealed class ContentReplayPlaybackManager
{
case RoundEndMessageEvent:
case PopupEvent:
case AudioMessage:
case PickupAnimationEvent:
case MeleeLungeEvent:
case SharedGunSystem.HitscanEvent:

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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>();

View File

@@ -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;

View File

@@ -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",

View File

@@ -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}");

View File

@@ -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>();

View File

@@ -68,6 +68,7 @@ namespace Content.IntegrationTests.Tests
"Core",
"Marathon",
"Kettle",
"Gemini",
"MeteorArena",
"Atlas"
};

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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)

View File

@@ -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" />

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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))

View File

@@ -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)

View File

@@ -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.
});
}
}
}

View File

@@ -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
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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 { })

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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)

View File

@@ -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)));
}
}

View File

@@ -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)

View File

@@ -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!;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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)

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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))
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View 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;
}
}

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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));
}
}
}

View File

@@ -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