mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c7f0a8d6b | ||
|
|
9eaf52886a | ||
|
|
fce6f6c714 | ||
|
|
c04d51d489 | ||
|
|
d310871aa6 | ||
|
|
5fa422865f | ||
|
|
c137823355 | ||
|
|
0a0026b9ae | ||
|
|
97a2a5cfae | ||
|
|
a266e21d9e | ||
|
|
ad8bbe6401 | ||
|
|
71ce4749fe | ||
|
|
1e33c3c843 | ||
|
|
b3f3ca9725 | ||
|
|
33c37393ba | ||
|
|
ae36529744 | ||
|
|
ed2be48864 | ||
|
|
7ad5ce302e | ||
|
|
647f1a6808 |
@@ -6,6 +6,8 @@ using System.Threading;
|
||||
using NFluidsynth;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -61,6 +63,7 @@ namespace Robust.Client.Audio.Midi
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgMan = default!;
|
||||
|
||||
private SharedBroadPhaseSystem _broadPhaseSystem = default!;
|
||||
|
||||
@@ -94,7 +97,7 @@ namespace Robust.Client.Audio.Midi
|
||||
if (MathHelper.CloseTo(_volume, value))
|
||||
return;
|
||||
|
||||
_volume = value;
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -131,6 +134,12 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (FluidsynthInitialized || _failedInitialize) return;
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
_midiSawmill = Logger.GetSawmill("midi");
|
||||
_sawmill = Logger.GetSawmill("midi.fluidsynth");
|
||||
_loggerDelegate = LoggerDelegate;
|
||||
|
||||
@@ -7,6 +7,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using MidiEvent = NFluidsynth.MidiEvent;
|
||||
@@ -203,7 +204,9 @@ namespace Robust.Client.Audio.Midi
|
||||
private const int SampleRate = 44100;
|
||||
private const int Buffers = SampleRate / 2205;
|
||||
private readonly object _playerStateLock = new();
|
||||
private bool _debugEvents = false;
|
||||
private SequencerClientId _synthRegister;
|
||||
private SequencerClientId _debugRegister;
|
||||
public IClydeBufferedAudioSource Source { get; set; }
|
||||
IClydeBufferedAudioSource IMidiRenderer.Source => Source;
|
||||
|
||||
@@ -313,6 +316,7 @@ namespace Robust.Client.Audio.Midi
|
||||
_soundFontLoader = soundFontLoader;
|
||||
_synth = new Synth(_settings);
|
||||
_sequencer = new Sequencer(false);
|
||||
_debugRegister = _sequencer.RegisterClient("honk", DumpSequencerEvent);
|
||||
_synthRegister = _sequencer.RegisterFluidsynth(_synth);
|
||||
|
||||
_synth.AddSoundFontLoader(soundFontLoader);
|
||||
@@ -322,6 +326,27 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.StartPlaying();
|
||||
}
|
||||
|
||||
private void DumpSequencerEvent(uint time, SequencerEvent @event)
|
||||
{
|
||||
// ReSharper disable once UseStringInterpolation
|
||||
_midiSawmill.Debug(string.Format(
|
||||
"{0:D8}: {1} chan:{2:D2} key:{3:D5} bank:{4:D2} ctrl:{5:D5} dur:{6:D5} pitch:{7:D5} prog:{8:D3} val:{9:D5} vel:{10:D5}",
|
||||
time,
|
||||
@event.Type.ToString().PadLeft(22),
|
||||
@event.Channel,
|
||||
@event.Key,
|
||||
@event.Bank,
|
||||
@event.Control,
|
||||
@event.Duration,
|
||||
@event.Pitch,
|
||||
@event.Program,
|
||||
@event.Value,
|
||||
@event.Velocity));
|
||||
|
||||
@event.Dest = _synthRegister;
|
||||
_sequencer.SendNow(@event);
|
||||
}
|
||||
|
||||
public bool OpenInput()
|
||||
{
|
||||
if (Disposed)
|
||||
@@ -428,9 +453,6 @@ namespace Robust.Client.Audio.Midi
|
||||
{
|
||||
if (Disposed) return;
|
||||
|
||||
// SSE needs this.
|
||||
DebugTools.Assert(length % 4 == 0, "Sample length must be multiple of 4");
|
||||
|
||||
var buffersProcessed = Source.GetNumberOfBuffersProcessed();
|
||||
if(buffersProcessed == Buffers) _midiSawmill.Warning("MIDI buffer overflow!");
|
||||
if (buffersProcessed == 0) return;
|
||||
@@ -445,36 +467,16 @@ namespace Robust.Client.Audio.Midi
|
||||
Source.GetBuffersProcessed(buffers);
|
||||
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
// _sequencer.Process(10);
|
||||
_synth?.WriteSampleFloat(length * buffers.Length, audio, 0, Mono ? 1 : 2,
|
||||
audio, Mono ? length * buffers.Length : 1, Mono ? 1 : 2);
|
||||
|
||||
}
|
||||
if (Mono) // Turn audio to mono
|
||||
{
|
||||
var l = length * buffers.Length;
|
||||
|
||||
if (Sse.IsSupported)
|
||||
{
|
||||
fixed (float* ptr = audio)
|
||||
{
|
||||
for (var j = 0; j < l; j += 4)
|
||||
{
|
||||
var k = j + l;
|
||||
|
||||
var jV = Sse.LoadVector128(ptr + j);
|
||||
var kV = Sse.LoadVector128(ptr + k);
|
||||
|
||||
Sse.Store(j + ptr, Sse.Add(jV, kV));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var j = 0; j < l; j++)
|
||||
{
|
||||
var k = j + l;
|
||||
audio[j] = ((audio[k] + audio[j]));
|
||||
}
|
||||
}
|
||||
NumericsHelpers.Add(audio[..l], audio[l..]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < buffers.Length; i++)
|
||||
@@ -532,16 +534,16 @@ namespace Robust.Client.Audio.Midi
|
||||
lock(_playerStateLock)
|
||||
switch (midiEvent.Type)
|
||||
{
|
||||
// Note On 0x80
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
// Note Off - 0x90
|
||||
// Note Off - 0x80
|
||||
case 128:
|
||||
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
break;
|
||||
|
||||
// Note On 0x90
|
||||
case 144:
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, midiEvent.Velocity);
|
||||
break;
|
||||
|
||||
// After Touch - 0xA
|
||||
case 160:
|
||||
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
|
||||
@@ -576,6 +578,12 @@ namespace Robust.Client.Audio.Midi
|
||||
case 81:
|
||||
// System Messages - 0xF0
|
||||
case 240:
|
||||
switch (midiEvent.Control)
|
||||
{
|
||||
case 11:
|
||||
_synth.AllNotesOff(midiEvent.Channel);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
default:
|
||||
@@ -597,7 +605,7 @@ namespace Robust.Client.Audio.Midi
|
||||
if (Disposed) return;
|
||||
|
||||
var seqEv = (SequencerEvent) midiEvent;
|
||||
seqEv.Dest = _synthRegister;
|
||||
seqEv.Dest = _debugEvents ? _debugRegister : _synthRegister;
|
||||
_sequencer.SendAt(seqEv, time, absolute);
|
||||
}
|
||||
|
||||
|
||||
@@ -1359,18 +1359,6 @@ namespace Robust.Client.GameObjects
|
||||
return texture;
|
||||
}
|
||||
|
||||
public override void OnRemove()
|
||||
{
|
||||
base.OnRemove();
|
||||
|
||||
var map = Owner.Transform.MapID;
|
||||
if (map != MapId.Nullspace)
|
||||
{
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local,
|
||||
new RenderTreeRemoveSpriteEvent(this, map));
|
||||
}
|
||||
}
|
||||
|
||||
public void FrameUpdate(float delta)
|
||||
{
|
||||
foreach (var t in Layers)
|
||||
|
||||
@@ -254,7 +254,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(entity.Transform.WorldPosition))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning("Can't play positional audio, can't set position.");
|
||||
Logger.Warning($"Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!source.SetPosition(coordinates.ToMapPos(EntityManager)))
|
||||
{
|
||||
source.Dispose();
|
||||
Logger.Warning("Can't play positional audio, can't set position.");
|
||||
Logger.Warning("Can't play positional audio \"{stream.Name}\", can't set position.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,10 +50,13 @@ namespace Robust.Client.GameObjects
|
||||
_mapManager.OnGridCreated += MapManagerOnGridCreated;
|
||||
_mapManager.OnGridRemoved += MapManagerOnGridRemoved;
|
||||
|
||||
// Due to how recursion works, this must be done.
|
||||
SubscribeLocalEvent<MoveEvent>(AnythingMoved);
|
||||
|
||||
SubscribeLocalEvent<SpriteComponent, EntMapIdChangedMessage>(SpriteMapChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, MoveEvent>(SpriteMoved);
|
||||
SubscribeLocalEvent<SpriteComponent, EntParentChangedMessage>(SpriteParentChanged);
|
||||
SubscribeLocalEvent<SpriteComponent, RenderTreeRemoveSpriteEvent>(RemoveSprite);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentRemove>(RemoveSprite);
|
||||
|
||||
SubscribeLocalEvent<PointLightComponent, EntMapIdChangedMessage>(LightMapChanged);
|
||||
SubscribeLocalEvent<PointLightComponent, MoveEvent>(LightMoved);
|
||||
@@ -62,6 +65,26 @@ namespace Robust.Client.GameObjects
|
||||
SubscribeLocalEvent<PointLightComponent, RenderTreeRemoveLightEvent>(RemoveLight);
|
||||
}
|
||||
|
||||
private void AnythingMoved(MoveEvent args)
|
||||
{
|
||||
AnythingMovedSubHandler(args.Sender.Transform);
|
||||
}
|
||||
|
||||
private void AnythingMovedSubHandler(ITransformComponent sender)
|
||||
{
|
||||
// This recursive search is needed, as MoveEvent is defined to not care about indirect events like children.
|
||||
// WHATEVER YOU DO, DON'T REPLACE THIS WITH SPAMMING EVENTS UNLESS YOU HAVE A GUARANTEE IT WON'T LAG THE GC.
|
||||
// (Struct-based events ok though)
|
||||
if (sender.Owner.TryGetComponent(out SpriteComponent? sprite))
|
||||
QueueSpriteUpdate(sprite);
|
||||
if (sender.Owner.TryGetComponent(out PointLightComponent? light))
|
||||
QueueLightUpdate(light);
|
||||
foreach (ITransformComponent child in sender.Children)
|
||||
{
|
||||
AnythingMovedSubHandler(child);
|
||||
}
|
||||
}
|
||||
|
||||
// For the RemoveX methods
|
||||
// If the Transform is removed BEFORE the Sprite/Light,
|
||||
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
|
||||
@@ -83,7 +106,7 @@ namespace Robust.Client.GameObjects
|
||||
QueueSpriteUpdate(component);
|
||||
}
|
||||
|
||||
private void RemoveSprite(EntityUid uid, SpriteComponent component, RenderTreeRemoveSpriteEvent args)
|
||||
private void RemoveSprite(EntityUid uid, SpriteComponent component, ComponentRemove args)
|
||||
{
|
||||
ClearSprite(component);
|
||||
}
|
||||
@@ -380,18 +403,6 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
internal class RenderTreeRemoveSpriteEvent : EntityEventArgs
|
||||
{
|
||||
public RenderTreeRemoveSpriteEvent(SpriteComponent sprite, MapId map)
|
||||
{
|
||||
Sprite = sprite;
|
||||
Map = map;
|
||||
}
|
||||
|
||||
public SpriteComponent Sprite { get; }
|
||||
public MapId Map { get; }
|
||||
}
|
||||
|
||||
internal class RenderTreeRemoveLightEvent : EntityEventArgs
|
||||
{
|
||||
public RenderTreeRemoveLightEvent(PointLightComponent light, MapId map)
|
||||
|
||||
@@ -44,8 +44,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
internal bool IsEfxSupported;
|
||||
|
||||
private ISawmill _openALSawmill = default!;
|
||||
|
||||
private void _initializeAudio()
|
||||
{
|
||||
_openALSawmill = Logger.GetSawmill("clyde.oal");
|
||||
|
||||
_audioOpenDevice();
|
||||
|
||||
// Create OpenAL context.
|
||||
@@ -74,9 +78,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_alContextExtensions.Add(extension);
|
||||
}
|
||||
|
||||
Logger.DebugS("clyde.oal", "OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
Logger.DebugS("clyde.oal", "OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
Logger.DebugS("clyde.oal", "OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
_openALSawmill.Debug("OpenAL Vendor: {0}", AL.Get(ALGetString.Vendor));
|
||||
_openALSawmill.Debug("OpenAL Renderer: {0}", AL.Get(ALGetString.Renderer));
|
||||
_openALSawmill.Debug("OpenAL Version: {0}", AL.Get(ALGetString.Version));
|
||||
}
|
||||
|
||||
private void _audioOpenDevice()
|
||||
@@ -89,7 +93,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_openALDevice = ALC.OpenDevice(preferredDevice);
|
||||
if (_openALDevice == IntPtr.Zero)
|
||||
{
|
||||
Logger.WarningS("clyde.oal", "Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
_openALSawmill.Warning("Unable to open preferred audio device '{0}': {1}. Falling back default.",
|
||||
preferredDevice, ALC.GetError(ALDevice.Null));
|
||||
|
||||
_openALDevice = ALC.OpenDevice(null);
|
||||
@@ -153,7 +157,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Clear out finalized audio sources.
|
||||
while (_sourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
Logger.DebugS("clyde.oal", "Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
_openALSawmill.Debug("Cleaning out source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
@@ -163,7 +167,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Clear out finalized buffered audio sources.
|
||||
while (_bufferedSourceDisposeQueue.TryDequeue(out var handles))
|
||||
{
|
||||
Logger.DebugS("clyde.oal", "Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
_openALSawmill.Debug("Cleaning out buffered source {0} which finalized in another thread.", handles.sourceHandle);
|
||||
if (IsEfxSupported) RemoveEfx(handles);
|
||||
AL.DeleteSource(handles.sourceHandle);
|
||||
_checkAlError();
|
||||
@@ -211,24 +215,24 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
private static void _checkAlcError(ALDevice device,
|
||||
private void _checkAlcError(ALDevice device,
|
||||
[CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = ALC.GetError(device);
|
||||
if (error != AlcError.NoError)
|
||||
{
|
||||
Logger.ErrorS("clyde.oal", "[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
_openALSawmill.Error("[{0}:{1}] ALC error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
private static void _checkAlError([CallerMemberName] string callerMember = "",
|
||||
private void _checkAlError([CallerMemberName] string callerMember = "",
|
||||
[CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
Logger.ErrorS("clyde.oal", "[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
_openALSawmill.Error("[{0}:{1}] AL error: {2}", callerMember, callerLineNumber, error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,6 +334,35 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var fmt = channels switch
|
||||
{
|
||||
1 => ALFormat.Mono16,
|
||||
2 => ALFormat.Stereo16,
|
||||
_ => throw new ArgumentOutOfRangeException(
|
||||
nameof(channels), "Only stereo and mono is currently supported")
|
||||
};
|
||||
|
||||
var buffer = AL.GenBuffer();
|
||||
_checkAlError();
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (short* ptr = samples)
|
||||
{
|
||||
AL.BufferData(buffer, fmt, (IntPtr) ptr, samples.Length * sizeof(short), sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
private sealed class LoadedAudioSample
|
||||
{
|
||||
public readonly int BufferHandle;
|
||||
@@ -381,14 +414,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.SourcePlay(SourceHandle);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.SourceStop(SourceHandle);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
@@ -407,14 +440,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourceb.Looping, out var ret);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
return ret;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.Looping, value);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +455,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourceb.SourceRelative, true);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolume(float decibels)
|
||||
@@ -436,7 +469,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float scale)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = scale;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
@@ -453,7 +500,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
gain *= gain * gain;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * gain);
|
||||
}
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
@@ -473,7 +520,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, seconds);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
@@ -499,7 +546,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
#endif
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Position, x, y, 0);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -526,14 +573,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
AL.Source(SourceHandle, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.Pitch, pitch);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
~AudioSource()
|
||||
@@ -559,7 +606,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (FilterHandle != 0) EFX.DeleteFilter(FilterHandle);
|
||||
AL.DeleteSource(SourceHandle);
|
||||
_master._audioSources.Remove(SourceHandle);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = -1;
|
||||
@@ -609,7 +656,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourcePlay(stackalloc int[] {SourceHandle!.Value});
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void StopPlaying()
|
||||
@@ -617,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.SourceStop(SourceHandle!.Value);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool IsPlaying
|
||||
@@ -643,7 +690,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mono = false;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourceb.SourceRelative, true);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetLooping()
|
||||
@@ -662,7 +709,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
_gain = MathF.Pow(10, decibels / 10);
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float scale)
|
||||
{
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle!.Value, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
}
|
||||
_gain = scale;
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
@@ -680,7 +741,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Gain, gain * _gain);
|
||||
}
|
||||
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
private void SetOcclusionEfx(float gain, float cutoff)
|
||||
@@ -700,7 +761,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.SecOffset, seconds);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public bool SetPosition(Vector2 position)
|
||||
@@ -717,7 +778,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mono = true;
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Position, x, y, 0);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -744,7 +805,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
AL.Source(SourceHandle!.Value, ALSource3f.Velocity, x, y, 0);
|
||||
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
public void SetPitch(float pitch)
|
||||
@@ -752,7 +813,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_checkDisposed();
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
AL.Source(SourceHandle!.Value, ALSourcef.Pitch, pitch);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
~BufferedAudioSource()
|
||||
@@ -783,7 +844,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
AL.DeleteSource(SourceHandle.Value);
|
||||
AL.DeleteBuffers(BufferHandles);
|
||||
_master._bufferedAudioSources.Remove(SourceHandle.Value);
|
||||
_checkAlError();
|
||||
_master._checkAlError();
|
||||
}
|
||||
|
||||
SourceHandle = null;
|
||||
|
||||
@@ -248,6 +248,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return new(default, default, 1, name);
|
||||
}
|
||||
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
// TODO: Might wanna actually load this so the length gets reported correctly.
|
||||
return new(default, default, channels, name);
|
||||
}
|
||||
|
||||
public IClydeAudioSource CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
return DummyAudioSource.Instance;
|
||||
@@ -323,6 +329,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetVolumeDirect(float scale)
|
||||
{
|
||||
// Nada.
|
||||
}
|
||||
|
||||
public void SetOcclusion(float blocks)
|
||||
{
|
||||
// Nada.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Audio;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -8,6 +9,7 @@ namespace Robust.Client.Graphics
|
||||
// AUDIO SYSTEM DOWN BELOW.
|
||||
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
|
||||
AudioStream LoadAudioWav(Stream stream, string? name = null);
|
||||
AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null);
|
||||
|
||||
void SetMasterVolume(float newVolume);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Robust.Client.Graphics
|
||||
void SetPitch(float pitch);
|
||||
void SetGlobal();
|
||||
void SetVolume(float decibels);
|
||||
void SetVolumeDirect(float decibels);
|
||||
void SetOcclusion(float blocks);
|
||||
void SetPlaybackPosition(float seconds);
|
||||
void SetVelocity(Vector2 velocity);
|
||||
|
||||
@@ -36,7 +36,8 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
return;
|
||||
}
|
||||
|
||||
_label.Text = string.Join("\n", _inputManager.DownKeyFunctions);
|
||||
var functionsText = string.Join("\n", _inputManager.DownKeyFunctions);
|
||||
_label.Text = $"Context: {_inputManager.Contexts.ActiveContext.Name}\n{functionsText}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,8 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
public override string Name => "Actor";
|
||||
|
||||
[ViewVariables] public IPlayerSession PlayerSession { get; internal set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
// Warning: careful here, Detach removes this component, make sure this is after the base shutdown
|
||||
// to prevent infinite recursion
|
||||
// ReSharper disable once ConstantConditionalAccessQualifier
|
||||
PlayerSession?.DetachFromEntity();
|
||||
}
|
||||
[ViewVariables]
|
||||
public IPlayerSession PlayerSession { get; internal set; } = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -52,26 +42,4 @@ namespace Robust.Server.GameObjects
|
||||
OldPlayer = oldPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
public class PlayerAttachSystemMessage : EntityEventArgs
|
||||
{
|
||||
public PlayerAttachSystemMessage(IEntity entity, IPlayerSession newPlayer)
|
||||
{
|
||||
Entity = entity;
|
||||
NewPlayer = newPlayer;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
public IPlayerSession NewPlayer { get; }
|
||||
}
|
||||
|
||||
public class PlayerDetachedSystemMessage : EntityEventArgs
|
||||
{
|
||||
public PlayerDetachedSystemMessage(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public IEntity Entity { get; }
|
||||
}
|
||||
}
|
||||
|
||||
173
Robust.Server/GameObjects/EntitySystems/ActorSystem.cs
Normal file
173
Robust.Server/GameObjects/EntitySystems/ActorSystem.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// System that handles players being attached/detached from entities.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class ActorSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<AttachPlayerEvent>(OnActorPlayerAttach);
|
||||
SubscribeLocalEvent<ActorComponent, DetachPlayerEvent>(OnActorPlayerDetach);
|
||||
SubscribeLocalEvent<ActorComponent, ComponentShutdown>(OnActorShutdown);
|
||||
}
|
||||
|
||||
private void OnActorPlayerAttach(AttachPlayerEvent args)
|
||||
{
|
||||
// Cannot attach to a deleted entity.
|
||||
if (args.Entity.Deleted)
|
||||
{
|
||||
args.Result = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var uid = args.Entity.Uid;
|
||||
|
||||
// Check if there was a player attached to the entity already...
|
||||
if (ComponentManager.TryGetComponent(uid, out ActorComponent actor))
|
||||
{
|
||||
// If we're not forcing the attach, this fails.
|
||||
if (!args.Force)
|
||||
{
|
||||
args.Result = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the event's force-kicked session before detaching it.
|
||||
args.ForceKicked = actor.PlayerSession;
|
||||
|
||||
// This detach cannot fail, as a player is attached to this entity.
|
||||
// It's important to note that detaching the player removes the component.
|
||||
RaiseLocalEvent(uid, new DetachPlayerEvent());
|
||||
}
|
||||
|
||||
// We add the actor component.
|
||||
actor = ComponentManager.AddComponent<ActorComponent>(args.Entity);
|
||||
actor.PlayerSession = args.Player;
|
||||
args.Player.SetAttachedEntity(args.Entity);
|
||||
args.Result = true;
|
||||
|
||||
// TODO: Remove component message.
|
||||
args.Entity.SendMessage(actor, new PlayerAttachedMsg(args.Player));
|
||||
|
||||
// The player is fully attached now, raise an event!
|
||||
RaiseLocalEvent(uid, new PlayerAttachedEvent(args.Entity, args.Player));
|
||||
}
|
||||
|
||||
private void OnActorPlayerDetach(EntityUid uid, ActorComponent component, DetachPlayerEvent args)
|
||||
{
|
||||
// Removing the component will call shutdown, and our subscription will handle the rest of the detach logic.
|
||||
ComponentManager.RemoveComponent<ActorComponent>(uid);
|
||||
args.Result = true;
|
||||
}
|
||||
|
||||
private void OnActorShutdown(EntityUid uid, ActorComponent component, ComponentShutdown args)
|
||||
{
|
||||
component.PlayerSession.SetAttachedEntity(null);
|
||||
|
||||
var entity = EntityManager.GetEntity(uid);
|
||||
|
||||
// TODO: Remove component message.
|
||||
entity.SendMessage(component, new PlayerDetachedMsg(component.PlayerSession));
|
||||
|
||||
// The player is fully detached now that the component has shut down.
|
||||
RaiseLocalEvent(uid, new PlayerDetachedEvent(entity, component.PlayerSession));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise this broadcast event to attach a player to an entity, optionally detaching the player attached to it.
|
||||
/// </summary>
|
||||
public class AttachPlayerEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Player to attach to the entity.
|
||||
/// Input parameter.
|
||||
/// </summary>
|
||||
public IPlayerSession Player { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Entity to attach the player to.
|
||||
/// Input parameter.
|
||||
/// </summary>
|
||||
public IEntity Entity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to force-attach the player,
|
||||
/// detaching any players attached to it if any.
|
||||
/// Input parameter.
|
||||
/// </summary>
|
||||
public bool Force { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the attach was forced and there was a player attached to the entity before, this will be it.
|
||||
/// Output parameter.
|
||||
/// </summary>
|
||||
public IPlayerSession? ForceKicked { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the player was attached correctly.
|
||||
/// False if not forcing and the entity already had a player attached to it.
|
||||
/// Output parameter.
|
||||
/// </summary>
|
||||
public bool Result { get; set; } = false;
|
||||
|
||||
public AttachPlayerEvent(IEntity entity, IPlayerSession player, bool force = false)
|
||||
{
|
||||
Entity = entity;
|
||||
Player = player;
|
||||
Force = force;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raise this directed event to detach a player from an entity.
|
||||
/// </summary>
|
||||
public class DetachPlayerEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the player was detached correctly.
|
||||
/// Fails if no player was attached to the entity.
|
||||
/// Output parameter.
|
||||
/// </summary>
|
||||
public bool Result { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event for when a player has been attached to an entity.
|
||||
/// </summary>
|
||||
public class PlayerAttachedEvent : EntityEventArgs
|
||||
{
|
||||
public IEntity Entity { get; }
|
||||
public IPlayerSession Player { get; }
|
||||
|
||||
public PlayerAttachedEvent(IEntity entity, IPlayerSession player)
|
||||
{
|
||||
Entity = entity;
|
||||
Player = player;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event for when a player has been detached from an entity.
|
||||
/// </summary>
|
||||
public class PlayerDetachedEvent : EntityEventArgs
|
||||
{
|
||||
public IEntity Entity { get; }
|
||||
public IPlayerSession Player { get; }
|
||||
|
||||
public PlayerDetachedEvent(IEntity entity, IPlayerSession player)
|
||||
{
|
||||
Entity = entity;
|
||||
Player = player;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ namespace Robust.Server.Player
|
||||
/// NOTE: The content pack almost certainly has an alternative for this.
|
||||
/// Do not call this directly for most content code.
|
||||
/// </summary>
|
||||
/// <param name="a">The entity to attach to.</param>
|
||||
void AttachToEntity(IEntity? a);
|
||||
/// <param name="entity">The entity to attach to.</param>
|
||||
void AttachToEntity(IEntity? entity);
|
||||
|
||||
/// <summary>
|
||||
/// Detaches this player from an entity.
|
||||
@@ -36,5 +36,12 @@ namespace Robust.Server.Player
|
||||
/// Persistent data for this player.
|
||||
/// </summary>
|
||||
IPlayerData Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Internal method to set <see cref="ICommonSession.AttachedEntity"/> and update the player's status.
|
||||
/// Do NOT use this unless you know what you're doing, you probably want <see cref="AttachToEntity"/>
|
||||
/// and <see cref="DetachFromEntity"/> instead.
|
||||
/// </summary>
|
||||
internal void SetAttachedEntity(IEntity? entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Logger = Robust.Shared.Log.Logger;
|
||||
|
||||
namespace Robust.Server.Player
|
||||
{
|
||||
@@ -36,7 +37,7 @@ namespace Robust.Server.Player
|
||||
|
||||
[ViewVariables] public INetChannel ConnectedClient { get; }
|
||||
|
||||
[ViewVariables] public IEntity? AttachedEntity { get; private set; }
|
||||
[ViewVariables] public IEntity? AttachedEntity { get; set; }
|
||||
|
||||
[ViewVariables] public EntityUid? AttachedEntityUid => AttachedEntity?.Uid;
|
||||
|
||||
@@ -109,21 +110,21 @@ namespace Robust.Server.Player
|
||||
public event EventHandler<SessionStatusEventArgs>? PlayerStatusChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AttachToEntity(IEntity? a)
|
||||
public void AttachToEntity(IEntity? entity)
|
||||
{
|
||||
DetachFromEntity();
|
||||
|
||||
if (a == null)
|
||||
{
|
||||
if (entity == null)
|
||||
return;
|
||||
}
|
||||
|
||||
var actorComponent = a.AddComponent<ActorComponent>();
|
||||
actorComponent.PlayerSession = this;
|
||||
AttachedEntity = a;
|
||||
a.SendMessage(actorComponent, new PlayerAttachedMsg(this));
|
||||
a.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerAttachSystemMessage(a, this));
|
||||
UpdatePlayerState();
|
||||
// This event needs to be broadcast.
|
||||
var attachPlayer = new AttachPlayerEvent(entity, this);
|
||||
entity.EntityManager.EventBus.RaiseLocalEvent(entity.Uid, attachPlayer);
|
||||
|
||||
if (!attachPlayer.Result)
|
||||
{
|
||||
Logger.Warning($"Couldn't attach player \"{this}\" to entity \"{entity}\"! Did it have a player already attached to it?");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -132,22 +133,12 @@ namespace Robust.Server.Player
|
||||
if (AttachedEntity == null)
|
||||
return;
|
||||
|
||||
if (AttachedEntity.Deleted)
|
||||
{
|
||||
throw new InvalidOperationException("Tried to detach player, but my entity does not exist!");
|
||||
}
|
||||
var detachPlayer = new DetachPlayerEvent();
|
||||
AttachedEntity.EntityManager.EventBus.RaiseLocalEvent(AttachedEntity.Uid, detachPlayer, false);
|
||||
|
||||
if (AttachedEntity.TryGetComponent<ActorComponent>(out var actor))
|
||||
if (!detachPlayer.Result)
|
||||
{
|
||||
AttachedEntity.SendMessage(actor, new PlayerDetachedMsg(this));
|
||||
AttachedEntity.EntityManager.EventBus.RaiseEvent(EventSource.Local, new PlayerDetachedSystemMessage(AttachedEntity));
|
||||
AttachedEntity.RemoveComponent<ActorComponent>();
|
||||
AttachedEntity = null;
|
||||
UpdatePlayerState();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Tried to detach player, but entity does not have ActorComponent!");
|
||||
Logger.Warning($"Couldn't detach player \"{this}\" to entity \"{AttachedEntity}\"! Is it missing an ActorComponent?");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +181,13 @@ namespace Robust.Server.Player
|
||||
|
||||
public LoginType AuthType => ConnectedClient.AuthType;
|
||||
|
||||
/// <inheritdoc />
|
||||
void IPlayerSession.SetAttachedEntity(IEntity? entity)
|
||||
{
|
||||
AttachedEntity = entity;
|
||||
UpdatePlayerState();
|
||||
}
|
||||
|
||||
private void UpdatePlayerState()
|
||||
{
|
||||
PlayerState.Status = Status;
|
||||
|
||||
@@ -463,5 +463,12 @@ namespace Robust.Shared
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DebugTargetFps =
|
||||
CVarDef.Create("debug.target_fps", 60, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* MIDI
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<float> MidiVolume =
|
||||
CVarDef.Create("midi.volume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,6 +197,27 @@ namespace Robust.Shared.Containers
|
||||
return userContainer == otherContainer;
|
||||
}
|
||||
|
||||
public static bool IsInSameOrParentContainer(this IEntity user, IEntity other)
|
||||
{
|
||||
DebugTools.AssertNotNull(user);
|
||||
DebugTools.AssertNotNull(other);
|
||||
|
||||
var isUserContained = TryGetContainer(user, out var userContainer);
|
||||
var isOtherContained = TryGetContainer(other, out var otherContainer);
|
||||
|
||||
// Both entities are not in a container
|
||||
if (!isUserContained && !isOtherContained) return true;
|
||||
|
||||
// One contains the other
|
||||
if (userContainer?.Owner == other || otherContainer?.Owner == user) return true;
|
||||
|
||||
// Both entities are in different contained states
|
||||
if (isUserContained != isOtherContained) return false;
|
||||
|
||||
// Both entities are in the same container
|
||||
return userContainer == otherContainer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shortcut method to make creation of containers easier.
|
||||
/// Creates a new container on the entity and gives it back to you.
|
||||
|
||||
@@ -106,6 +106,12 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
|
||||
|
||||
if (peReader.PEHeaders.CorHeader?.ManagedNativeHeaderDirectory is {Size: not 0})
|
||||
{
|
||||
_sawmill.Error($"Assembly {asmName} contains native code.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VerifyIL)
|
||||
{
|
||||
if (!DoVerifyIL(asmName, resolver, peReader, reader))
|
||||
@@ -404,7 +410,8 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsTypeAccessAllowed(SandboxConfig sandboxConfig, MTypeReferenced type, [NotNullWhen(true)] out TypeConfig? cfg)
|
||||
private bool IsTypeAccessAllowed(SandboxConfig sandboxConfig, MTypeReferenced type,
|
||||
[NotNullWhen(true)] out TypeConfig? cfg)
|
||||
{
|
||||
if (type.Namespace == null)
|
||||
{
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
|
||||
#endif
|
||||
|
||||
public IComponentFactory ComponentFactory => _componentFactory;
|
||||
|
||||
private const int TypeCapacity = 32;
|
||||
private const int ComponentCollectionCapacity = 1024;
|
||||
private const int EntityCapacity = 1024;
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// Set _nextRotation to null to break any active lerps if this is a client side prediction.
|
||||
_nextRotation = null;
|
||||
SetRotation(value);
|
||||
_localRotation = value;
|
||||
Dirty();
|
||||
|
||||
if (!DeferUpdates)
|
||||
@@ -258,17 +258,51 @@ namespace Robust.Shared.GameObjects
|
||||
var valid = _parent.IsValid();
|
||||
return new EntityCoordinates(valid ? _parent : Owner.Uid, valid ? LocalPosition : Vector2.Zero);
|
||||
}
|
||||
// NOTE: This setter must be callable from before initialize (inheriting from AttachParent's note)
|
||||
set
|
||||
{
|
||||
var oldPosition = Coordinates;
|
||||
_localPosition = value.Position;
|
||||
|
||||
var changedParent = false;
|
||||
|
||||
if (value.EntityId != _parent)
|
||||
{
|
||||
var newEntity = Owner.EntityManager.GetEntity(value.EntityId);
|
||||
AttachParent(newEntity);
|
||||
changedParent = true;
|
||||
var newParentEnt = Owner.EntityManager.GetEntity(value.EntityId);
|
||||
var newParent = newParentEnt.Transform;
|
||||
|
||||
DebugTools.Assert(newParent != this,
|
||||
$"Can't parent a {nameof(ITransformComponent)} to itself.");
|
||||
|
||||
// That's already our parent, don't bother attaching again.
|
||||
|
||||
var oldParent = Parent;
|
||||
var oldConcrete = (TransformComponent?) oldParent;
|
||||
var uid = Owner.Uid;
|
||||
oldConcrete?._children.Remove(uid);
|
||||
var newConcrete = (TransformComponent) newParent;
|
||||
newConcrete._children.Add(uid);
|
||||
|
||||
var oldParentOwner = oldParent?.Owner;
|
||||
var entMessage = new EntParentChangedMessage(Owner, oldParentOwner);
|
||||
var compMessage = new ParentChangedMessage(newParentEnt, oldParentOwner);
|
||||
|
||||
// offset position from world to parent
|
||||
_parent = newParentEnt.Uid;
|
||||
ChangeMapId(newConcrete.MapID);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, entMessage);
|
||||
Owner.SendMessage(this, compMessage);
|
||||
|
||||
GridID = GetGridIndex();
|
||||
}
|
||||
|
||||
// These conditions roughly emulate the effects of the code before I changed things,
|
||||
// in regards to when to rebuild matrices.
|
||||
// This may not in fact be the right thing.
|
||||
if (changedParent || !DeferUpdates)
|
||||
RebuildMatrices();
|
||||
Dirty();
|
||||
|
||||
if (!DeferUpdates)
|
||||
@@ -276,7 +310,6 @@ namespace Robust.Shared.GameObjects
|
||||
//TODO: This is a hack, look into WHY we can't call GridPosition before the comp is Running
|
||||
if (Running)
|
||||
{
|
||||
RebuildMatrices();
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, new MoveEvent(Owner, oldPosition, Coordinates));
|
||||
}
|
||||
}
|
||||
@@ -304,7 +337,7 @@ namespace Robust.Shared.GameObjects
|
||||
_nextPosition = null;
|
||||
|
||||
var oldGridPos = Coordinates;
|
||||
SetPosition(value);
|
||||
_localPosition = value;
|
||||
Dirty();
|
||||
|
||||
if (!DeferUpdates)
|
||||
@@ -556,49 +589,22 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets another entity as the parent entity.
|
||||
/// Sets another entity as the parent entity, maintaining world position.
|
||||
/// </summary>
|
||||
/// <param name="newParent"></param>
|
||||
public virtual void AttachParent(ITransformComponent newParent)
|
||||
{
|
||||
//NOTE: This function must be callable from before initialize
|
||||
|
||||
// nothing to attach to.
|
||||
// don't attach to something we're already attached to
|
||||
if (ParentUid == newParent.Owner.Uid)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(newParent != this,
|
||||
$"Can't parent a {nameof(ITransformComponent)} to itself.");
|
||||
|
||||
// That's already our parent, don't bother attaching again.
|
||||
var newParentEnt = newParent.Owner;
|
||||
if (newParentEnt.Uid == _parent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var oldParent = Parent;
|
||||
var oldConcrete = (TransformComponent?) oldParent;
|
||||
var uid = Owner.Uid;
|
||||
oldConcrete?._children.Remove(uid);
|
||||
var newConcrete = (TransformComponent) newParent;
|
||||
newConcrete._children.Add(uid);
|
||||
|
||||
var oldParentOwner = oldParent?.Owner;
|
||||
var entMessage = new EntParentChangedMessage(Owner, oldParentOwner);
|
||||
var compMessage = new ParentChangedMessage(newParentEnt, oldParentOwner);
|
||||
|
||||
// offset position from world to parent
|
||||
SetPosition(newParent.InvWorldMatrix.Transform(WorldPosition));
|
||||
_parent = newParentEnt.Uid;
|
||||
ChangeMapId(newConcrete.MapID);
|
||||
|
||||
Owner.EntityManager.EventBus.RaiseLocalEvent(Owner.Uid, entMessage);
|
||||
Owner.SendMessage(this, compMessage);
|
||||
|
||||
RebuildMatrices();
|
||||
Dirty();
|
||||
GridID = GetGridIndex();
|
||||
// offset position from world to parent, and set
|
||||
Coordinates = new EntityCoordinates(newParent.Owner.Uid, newParent.InvWorldMatrix.Transform(WorldPosition));
|
||||
}
|
||||
|
||||
internal void ChangeMapId(MapId newMapId)
|
||||
@@ -713,14 +719,14 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (LocalRotation != newState.Rotation)
|
||||
{
|
||||
SetRotation(newState.Rotation);
|
||||
_localRotation = newState.Rotation;
|
||||
rebuildMatrices = true;
|
||||
}
|
||||
|
||||
if (!_localPosition.EqualsApprox(newState.LocalPosition, 0.0001))
|
||||
{
|
||||
var oldPos = Coordinates;
|
||||
SetPosition(newState.LocalPosition);
|
||||
_localPosition = newState.LocalPosition;
|
||||
|
||||
var ev = new MoveEvent(Owner, oldPos, Coordinates);
|
||||
EntitySystem.Get<SharedTransformSystem>().DeferMoveEvent(ev);
|
||||
@@ -755,17 +761,6 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SetPosition(Vector2 position)
|
||||
{
|
||||
// DebugTools.Assert(!float.IsNaN(position.X) && !float.IsNaN(position.Y));
|
||||
_localPosition = position;
|
||||
}
|
||||
|
||||
protected virtual void SetRotation(Angle rotation)
|
||||
{
|
||||
_localRotation = rotation;
|
||||
}
|
||||
|
||||
public Matrix3 GetLocalMatrix()
|
||||
{
|
||||
return _localMatrix;
|
||||
|
||||
@@ -99,6 +99,7 @@ namespace Robust.Shared.GameObjects
|
||||
private class EventTables : IDisposable
|
||||
{
|
||||
private IEntityManager _entMan;
|
||||
private IComponentFactory _comFac;
|
||||
|
||||
// eUid -> EventType -> { CompType1, ... CompTypeN }
|
||||
private Dictionary<EntityUid, Dictionary<Type, HashSet<Type>>> _eventTables;
|
||||
@@ -112,6 +113,7 @@ namespace Robust.Shared.GameObjects
|
||||
public EventTables(IEntityManager entMan)
|
||||
{
|
||||
_entMan = entMan;
|
||||
_comFac = entMan.ComponentManager.ComponentFactory;
|
||||
|
||||
_entMan.EntityAdded += OnEntityAdded;
|
||||
_entMan.EntityDeleted += OnEntityDeleted;
|
||||
@@ -194,18 +196,21 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var eventTable = _eventTables[euid];
|
||||
|
||||
if (!_subscriptions.TryGetValue(compType, out var compSubs))
|
||||
return;
|
||||
|
||||
foreach (var kvSub in compSubs)
|
||||
foreach (var type in GetReferences(compType))
|
||||
{
|
||||
if(!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
|
||||
{
|
||||
subscribedComps = new HashSet<Type>();
|
||||
eventTable.Add(kvSub.Key, subscribedComps);
|
||||
}
|
||||
if (!_subscriptions.TryGetValue(type, out var compSubs))
|
||||
continue;
|
||||
|
||||
subscribedComps.Add(compType);
|
||||
foreach (var kvSub in compSubs)
|
||||
{
|
||||
if(!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
|
||||
{
|
||||
subscribedComps = new HashSet<Type>();
|
||||
eventTable.Add(kvSub.Key, subscribedComps);
|
||||
}
|
||||
|
||||
subscribedComps.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,15 +218,18 @@ namespace Robust.Shared.GameObjects
|
||||
{
|
||||
var eventTable = _eventTables[euid];
|
||||
|
||||
if (!_subscriptions.TryGetValue(compType, out var compSubs))
|
||||
return;
|
||||
|
||||
foreach (var kvSub in compSubs)
|
||||
foreach (var type in GetReferences(compType))
|
||||
{
|
||||
if (!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
|
||||
return;
|
||||
if (!_subscriptions.TryGetValue(type, out var compSubs))
|
||||
continue;
|
||||
|
||||
subscribedComps.Remove(compType);
|
||||
foreach (var kvSub in compSubs)
|
||||
{
|
||||
if (!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
|
||||
return;
|
||||
|
||||
subscribedComps.Remove(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,19 +249,23 @@ namespace Robust.Shared.GameObjects
|
||||
return;
|
||||
|
||||
var component = _entMan.ComponentManager.GetComponent(euid, compType);
|
||||
|
||||
handler(euid, component, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void DispatchComponent(EntityUid euid, IComponent component, Type eventType, EntityEventArgs args)
|
||||
{
|
||||
if (!_subscriptions.TryGetValue(component.GetType(), out var compSubs))
|
||||
return;
|
||||
foreach (var type in GetReferences(component.GetType()))
|
||||
{
|
||||
if (!_subscriptions.TryGetValue(type, out var compSubs))
|
||||
continue;
|
||||
|
||||
if (!compSubs.TryGetValue(eventType, out var handler))
|
||||
return;
|
||||
if (!compSubs.TryGetValue(eventType, out var handler))
|
||||
continue;
|
||||
|
||||
handler(euid, component, args);
|
||||
handler(euid, component, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearEntities()
|
||||
@@ -281,6 +293,11 @@ namespace Robust.Shared.GameObjects
|
||||
_eventTables = null!;
|
||||
_subscriptions = null!;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GetReferences(Type type)
|
||||
{
|
||||
return _comFac.GetRegistration(type).References;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -252,5 +252,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Culls all components from the collection that are marked as deleted. This needs to be called often.
|
||||
/// </summary>
|
||||
void CullRemovedComponents();
|
||||
|
||||
IComponentFactory ComponentFactory { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace Robust.Shared.Input
|
||||
/// </summary>
|
||||
/// <param name="function">Function to remove.</param>
|
||||
void RemoveFunction(BoundKeyFunction function);
|
||||
|
||||
string Name { get; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -40,20 +42,25 @@ namespace Robust.Shared.Input
|
||||
{
|
||||
private readonly List<BoundKeyFunction> _commands = new();
|
||||
private readonly IInputCmdContext? _parent;
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="InputCmdContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="parent">Parent context.</param>
|
||||
internal InputCmdContext(IInputCmdContext? parent)
|
||||
internal InputCmdContext(IInputCmdContext? parent, string name)
|
||||
{
|
||||
_parent = parent;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a instance of <see cref="InputCmdContext"/> with no parent.
|
||||
/// </summary>
|
||||
internal InputCmdContext() { }
|
||||
internal InputCmdContext(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddFunction(BoundKeyFunction function)
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace Robust.Shared.Input
|
||||
{
|
||||
var icc = _deferredContextSwitch;
|
||||
_deferredContextSwitch = null;
|
||||
_setActiveContextImmediately(icc);
|
||||
_setActiveContextImmediately( icc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ namespace Robust.Shared.Input
|
||||
/// </summary>
|
||||
public InputContextContainer()
|
||||
{
|
||||
_contexts.Add(DefaultContextName, new InputCmdContext());
|
||||
_contexts.Add(DefaultContextName, new InputCmdContext(DefaultContextName));
|
||||
SetActiveContext(DefaultContextName);
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace Robust.Shared.Input
|
||||
if (_contexts.ContainsKey(uniqueName))
|
||||
throw new ArgumentException($"Context with name {uniqueName} already exists.", nameof(uniqueName));
|
||||
|
||||
var newContext = new InputCmdContext(parentContext);
|
||||
var newContext = new InputCmdContext(parentContext, uniqueName);
|
||||
_contexts.Add(uniqueName, newContext);
|
||||
return newContext;
|
||||
}
|
||||
@@ -168,7 +168,7 @@ namespace Robust.Shared.Input
|
||||
if (parent == null)
|
||||
throw new ArgumentNullException(nameof(parent));
|
||||
|
||||
var newContext = new InputCmdContext(parent);
|
||||
var newContext = new InputCmdContext(parent, uniqueName);
|
||||
_contexts.Add(uniqueName, newContext);
|
||||
return newContext;
|
||||
}
|
||||
|
||||
@@ -104,6 +104,23 @@ namespace Robust.Shared.IoC
|
||||
_container.Value!.Register<TInterface, TImplementation>(overwrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an implementation, to make it accessible to <see cref="Resolve{T}"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type that will be resolvable and implementation.</typeparam>
|
||||
/// <param name="overwrite">
|
||||
/// If true, do not throw an <see cref="InvalidOperationException"/> if an interface is already registered,
|
||||
/// replace the current implementation instead.
|
||||
/// </param>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown if <paramref name="overwrite"/> is false and <typeparamref name="T"/> has been registered before,
|
||||
/// or if an already instantiated interface (by <see cref="BuildGraph"/>) is attempting to be overwritten.
|
||||
/// </exception>
|
||||
public static void Register<T>(bool overwrite = false) where T : class
|
||||
{
|
||||
Register<T, T>(overwrite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an interface to an implementation, to make it accessible to <see cref="Resolve{T}"/>
|
||||
/// <see cref="BuildGraph"/> MUST be called after this method to make the new interface available.
|
||||
|
||||
@@ -131,13 +131,13 @@ namespace Robust.Shared.Prototypes
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
/// </summary>
|
||||
List<IPrototype> LoadDirectory(ResourcePath path);
|
||||
List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false);
|
||||
|
||||
Dictionary<string, HashSet<ErrorNode>> ValidateDirectory(ResourcePath path);
|
||||
|
||||
List<IPrototype> LoadFromStream(TextReader stream);
|
||||
List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false);
|
||||
|
||||
List<IPrototype> LoadString(string str);
|
||||
List<IPrototype> LoadString(string str, bool overwrite = false);
|
||||
|
||||
/// <summary>
|
||||
/// Clear out all prototypes and reset to a blank slate.
|
||||
@@ -294,12 +294,18 @@ namespace Robust.Shared.Prototypes
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
var changed = filePaths.SelectMany(f => LoadFile(f.ToRootedPath(), true)).ToList();
|
||||
ReloadPrototypes(changed);
|
||||
#endif
|
||||
}
|
||||
|
||||
changed.Sort((a, b) => SortPrototypesByPriority(a.GetType(), b.GetType()));
|
||||
internal void ReloadPrototypes(List<IPrototype> prototypes)
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
prototypes.Sort((a, b) => SortPrototypesByPriority(a.GetType(), b.GetType()));
|
||||
|
||||
var pushed = new Dictionary<Type, HashSet<string>>();
|
||||
|
||||
foreach (var prototype in changed)
|
||||
foreach (var prototype in prototypes)
|
||||
{
|
||||
if (prototype is not IInheritingPrototype inheritingPrototype) continue;
|
||||
var type = prototype.GetType();
|
||||
@@ -338,7 +344,7 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
PrototypesReloaded?.Invoke(
|
||||
new PrototypesReloadedEventArgs(
|
||||
changed
|
||||
prototypes
|
||||
.GroupBy(p => p.GetType())
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
@@ -358,7 +364,6 @@ namespace Robust.Shared.Prototypes
|
||||
((EntityPrototype) entityPrototypes[prototype]).UpdateEntity((Entity) entity);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -431,7 +436,7 @@ namespace Robust.Shared.Prototypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<IPrototype> LoadDirectory(ResourcePath path)
|
||||
public List<IPrototype> LoadDirectory(ResourcePath path, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
|
||||
@@ -441,7 +446,7 @@ namespace Robust.Shared.Prototypes
|
||||
|
||||
foreach (var resourcePath in streams)
|
||||
{
|
||||
var filePrototypes = LoadFile(resourcePath);
|
||||
var filePrototypes = LoadFile(resourcePath, overwrite);
|
||||
changedPrototypes.AddRange(filePrototypes);
|
||||
}
|
||||
|
||||
@@ -568,7 +573,7 @@ namespace Robust.Shared.Prototypes
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public List<IPrototype> LoadFromStream(TextReader stream)
|
||||
public List<IPrototype> LoadFromStream(TextReader stream, bool overwrite = false)
|
||||
{
|
||||
var changedPrototypes = new List<IPrototype>();
|
||||
_hasEverBeenReloaded = true;
|
||||
@@ -579,7 +584,7 @@ namespace Robust.Shared.Prototypes
|
||||
{
|
||||
try
|
||||
{
|
||||
var documentPrototypes = LoadFromDocument(yaml.Documents[i]);
|
||||
var documentPrototypes = LoadFromDocument(yaml.Documents[i], overwrite);
|
||||
changedPrototypes.AddRange(documentPrototypes);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -593,9 +598,9 @@ namespace Robust.Shared.Prototypes
|
||||
return changedPrototypes;
|
||||
}
|
||||
|
||||
public List<IPrototype> LoadString(string str)
|
||||
public List<IPrototype> LoadString(string str, bool overwrite = false)
|
||||
{
|
||||
return LoadFromStream(new StringReader(str));
|
||||
return LoadFromStream(new StringReader(str), overwrite);
|
||||
}
|
||||
|
||||
#endregion IPrototypeManager members
|
||||
|
||||
@@ -359,7 +359,7 @@ namespace Robust.Shared.Serialization.Manager
|
||||
if (node is not MappingDataNode mappingDataNode)
|
||||
{
|
||||
if (node is not ValueDataNode emptyValueDataNode || emptyValueDataNode.Value != string.Empty)
|
||||
throw new ArgumentException($"No mapping node provided for type {type}");
|
||||
throw new ArgumentException($"No mapping node provided for type {type} at line: {node.Start.Line}");
|
||||
|
||||
// If we get an empty ValueDataNode we just use an empty mapping
|
||||
mappingDataNode = new MappingDataNode();
|
||||
|
||||
@@ -78,6 +78,11 @@ namespace Robust.Shared.Utility
|
||||
_tags.Add(new TagColor(color));
|
||||
}
|
||||
|
||||
public void PushNewline()
|
||||
{
|
||||
AddText("\n");
|
||||
}
|
||||
|
||||
public void Pop()
|
||||
{
|
||||
_tags.Add(new TagPop());
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
@@ -14,10 +17,17 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
var entUid = new EntityUid(7);
|
||||
var compInstance = new MetaDataComponent();
|
||||
|
||||
var compRegistration = new Mock<IComponentRegistration>();
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
|
||||
|
||||
|
||||
var compManMock = new Mock<IComponentManager>();
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
|
||||
compRegistration.Setup(m => m.References).Returns(new List<Type> {typeof(MetaDataComponent)});
|
||||
compFacMock.Setup(m => m.GetRegistration(typeof(MetaDataComponent))).Returns(compRegistration.Object);
|
||||
compManMock.Setup(m => m.ComponentFactory).Returns(compFacMock.Object);
|
||||
|
||||
IComponent? outIComponent = compInstance;
|
||||
compManMock.Setup(m => m.TryGetComponent(entUid, typeof(MetaDataComponent), out outIComponent))
|
||||
@@ -26,6 +36,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
compManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
|
||||
.Returns(compInstance);
|
||||
|
||||
|
||||
entManMock.Setup(m => m.ComponentManager).Returns(compManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
|
||||
@@ -62,8 +73,14 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
|
||||
var compRegistration = new Mock<IComponentRegistration>();
|
||||
|
||||
var compManMock = new Mock<IComponentManager>();
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
|
||||
compRegistration.Setup(m => m.References).Returns(new List<Type> {typeof(MetaDataComponent)});
|
||||
compFacMock.Setup(m => m.GetRegistration(typeof(MetaDataComponent))).Returns(compRegistration.Object);
|
||||
compManMock.Setup(m => m.ComponentFactory).Returns(compFacMock.Object);
|
||||
|
||||
IComponent? outIComponent = compInstance;
|
||||
compManMock.Setup(m => m.TryGetComponent(entUid, typeof(MetaDataComponent), out outIComponent))
|
||||
@@ -96,7 +113,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void SubscribeCompLifeEvent()
|
||||
{
|
||||
@@ -108,9 +125,15 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
compInstance.Owner = mockEnt.Object;
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
|
||||
|
||||
var compRegistration = new Mock<IComponentRegistration>();
|
||||
|
||||
var compManMock = new Mock<IComponentManager>();
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
|
||||
compRegistration.Setup(m => m.References).Returns(new List<Type> {typeof(MetaDataComponent)});
|
||||
compFacMock.Setup(m => m.GetRegistration(typeof(MetaDataComponent))).Returns(compRegistration.Object);
|
||||
compManMock.Setup(m => m.ComponentFactory).Returns(compFacMock.Object);
|
||||
|
||||
IComponent? outIComponent = compInstance;
|
||||
compManMock.Setup(m => m.TryGetComponent(entUid, typeof(MetaDataComponent), out outIComponent))
|
||||
|
||||
114
Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs
Normal file
114
Robust.UnitTesting/Shared/Prototypes/HotReloadTest.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Prototypes
|
||||
{
|
||||
[TestFixture]
|
||||
public class HotReloadTest : RobustUnitTest
|
||||
{
|
||||
private const string DummyId = "Dummy";
|
||||
public const string HotReloadTestComponentOneId = "HotReloadTestOne";
|
||||
public const string HotReloadTestComponentTwoId = "HotReloadTestTwo";
|
||||
|
||||
private static readonly string InitialPrototypes = $@"
|
||||
- type: entity
|
||||
id: {DummyId}
|
||||
components:
|
||||
- type: {HotReloadTestComponentOneId}
|
||||
value: 5";
|
||||
|
||||
private static readonly string ReloadedPrototypes = $@"
|
||||
- type: entity
|
||||
id: {DummyId}
|
||||
components:
|
||||
- type: {HotReloadTestComponentOneId}
|
||||
value: 10
|
||||
- type: {HotReloadTestComponentTwoId}";
|
||||
|
||||
private IComponentFactory _components = default!;
|
||||
private PrototypeManager _prototypes = default!;
|
||||
private IMapManager _maps = default!;
|
||||
private IEntityManager _entities = default!;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_components = IoCManager.Resolve<IComponentFactory>();
|
||||
_components.RegisterClass<HotReloadTestComponentOne>();
|
||||
_components.RegisterClass<HotReloadTestComponentTwo>();
|
||||
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
_prototypes = (PrototypeManager) IoCManager.Resolve<IPrototypeManager>();
|
||||
_prototypes.LoadString(InitialPrototypes);
|
||||
_prototypes.Resync();
|
||||
|
||||
_maps = IoCManager.Resolve<IMapManager>();
|
||||
_entities = IoCManager.Resolve<IEntityManager>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHotReload()
|
||||
{
|
||||
_maps.CreateNewMapEntity(new MapId(0));
|
||||
var entity = _entities.SpawnEntity(DummyId, MapCoordinates.Nullspace);
|
||||
var entityComponent = entity.GetComponent<HotReloadTestComponentOne>();
|
||||
|
||||
Assert.That(entityComponent.Value, Is.EqualTo(5));
|
||||
Assert.False(entity.HasComponent<HotReloadTestComponentTwo>());
|
||||
|
||||
var reloaded = false;
|
||||
_prototypes.PrototypesReloaded += _ => reloaded = true;
|
||||
|
||||
_prototypes.ReloadPrototypes(new List<IPrototype>());
|
||||
|
||||
Assert.True(reloaded);
|
||||
reloaded = false;
|
||||
|
||||
Assert.That(entityComponent.Value, Is.EqualTo(5));
|
||||
Assert.False(entity.HasComponent<HotReloadTestComponentTwo>());
|
||||
|
||||
var changedPrototypes = _prototypes.LoadString(ReloadedPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes);
|
||||
|
||||
Assert.True(reloaded);
|
||||
reloaded = false;
|
||||
|
||||
// Existing component values are not modified in the current implementation
|
||||
Assert.That(entityComponent.Value, Is.EqualTo(5));
|
||||
|
||||
// New components are added
|
||||
Assert.True(entity.HasComponent<HotReloadTestComponentTwo>());
|
||||
|
||||
changedPrototypes = _prototypes.LoadString(InitialPrototypes, true);
|
||||
_prototypes.ReloadPrototypes(changedPrototypes);
|
||||
|
||||
Assert.True(reloaded);
|
||||
reloaded = false;
|
||||
|
||||
// Existing component values are not modified in the current implementation
|
||||
Assert.That(entityComponent.Value, Is.EqualTo(5));
|
||||
|
||||
// Old components are removed
|
||||
Assert.False(entity.HasComponent<HotReloadTestComponentTwo>());
|
||||
}
|
||||
}
|
||||
|
||||
public class HotReloadTestComponentOne : Component
|
||||
{
|
||||
public override string Name => HotReloadTest.HotReloadTestComponentOneId;
|
||||
|
||||
[DataField("value")]
|
||||
public int Value { get; }
|
||||
}
|
||||
|
||||
public class HotReloadTestComponentTwo : Component
|
||||
{
|
||||
public override string Name => HotReloadTest.HotReloadTestComponentTwoId;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using JetBrains.Annotations;
|
||||
using NUnit.Framework;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -27,7 +26,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
|
||||
|
||||
IoCManager.Resolve<ISerializationManager>().Initialize();
|
||||
manager = IoCManager.Resolve<IPrototypeManager>();
|
||||
manager.LoadFromStream(new StringReader(DOCUMENT));
|
||||
manager.LoadString(DOCUMENT);
|
||||
manager.Resync();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user