Compare commits

..

8 Commits

Author SHA1 Message Date
PJB3005
4c31e9071f Version: 247.2.4 2025-12-01 16:07:21 +01:00
PJB3005
1000e2cf2c Backport BitArray .NET 10 serializer fix
83ad6042a7 & b267cd6fb4

Does not include test code to avoid risking merge conflicts.

(cherry picked from commit 415585a30d74fcae61f581808220a7aaeca3eaf5)
(cherry picked from commit e36628a6d436ea08d6d31441c101a88a5504c515)
2025-12-01 16:07:20 +01:00
PJB3005
7212d6d6b2 Version: 247.2.3 2025-09-26 13:40:50 +02:00
PJB3005
2d5e6c2b77 Validate that content assemblies have a limited list of names.
Also, only read assemblies once from disk

(cherry picked from commit 443a8dfca65be7d60c4bd46181b4c749b4756114)
2025-09-26 13:40:50 +02:00
PJB3005
19307875cf Version: 247.2.2 2025-09-19 09:17:35 +02:00
Skye
a76c57f841 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:35 +02:00
PJB3005
0a604e4a04 Version: 247.2.1 2025-09-14 14:58:24 +02:00
PJB3005
6218ef6e3f Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
(cherry picked from commit 4413695c77fb705054c2f81fa18ec0a189b685dd)
2025-09-14 14:58:24 +02:00
86 changed files with 811 additions and 2499 deletions

View File

@@ -55,9 +55,9 @@
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,61 +54,16 @@ END TEMPLATE-->
*None yet*
## 248.0.2
### Bugfixes
* Don't throw in overlay rendering if MapUid not found.
### Internal
* Reduce EntityManager.IsDefault allocations.
## 247.2.4
## 248.0.1
### Bugfixes
* Bump ImageSharp version.
* Fix instances of NaN gain for audio where a negative-infinity value is being used for volume.
## 247.2.3
## 248.0.0
### Breaking changes
* Use `Entity<MapGridComponent>` for TileChangedEvent instead of EntityUid.
* Audio files are no longer tempo perfect when being played if the offset is small. At some point in the future an AudioParams bool is likely to be added to enforce this.
* MoveProxy method args got changed in the B2DynamicTree update.
* ResPath will now assert in debug if you pass in an invalid path containing the non-standardized directory separator.
### New features
* Added a new `MapLoaderSystem.TryLoadGrid()` override that loads a grid onto a newly created map.
* Added a CVar for the endbuffer for audio. If an audio file will play below this length (for PVS reasons) it will be ignored.
* Added Regex.Count + StringBuilder.Chars setter to the sandbox.
* Added a public API for PhysicsHull.
* Made MapLoader log more helpful.
* Add TryLoadGrid override that also creates a map at the same time.
* Updated B2Dynamictree to the latest Box2D V3 version.
* Added SetItems to ItemList control to set items without removing the existing ones.
* Shaders, textures, and audio will now hot reload automatically to varying degrees. Also added IReloadManager to handle watching for file-system changes and relaying events.
* Wrap BUI disposes in a try-catch in case of exceptions.
## 247.2.2
### Bugfixes
* Fix some instances of invalid PlaybackPositions being set.
* Play audio from the start of a file if it's only just come into PVS range / had its state handled.
* Fix TryCopyComponents.
* Use shell.WriteError if TryLoad fails for mapping commands.
* Fix UI control position saving causing exceptions where the entity is cleaned-up alongside a state change.
* Fix Map NetId completions.
* Fix some ResPath calls using the wrong paths.
### Internal
* Remove some unused local variables and the associated warnings.
## 247.2.1
## 247.2.0

View File

@@ -156,7 +156,6 @@ cmd-savemap-not-exist = Target map does not exist.
cmd-savemap-init-warning = Attempted to save a post-init map without forcing the save.
cmd-savemap-attempt = Attempting to save map {$mapId} to {$path}.
cmd-savemap-success = Map successfully saved.
cmd-savemap-error = Could not save map! See server log for details.
cmd-hint-savemap-id = <MapID>
cmd-hint-savemap-path = <Path>
cmd-hint-savemap-force = [bool]
@@ -294,7 +293,7 @@ cmd-lsgrid-desc = Lists grids.
cmd-lsgrid-help = lsgrid
cmd-addmap-desc = Adds a new empty map to the round. If the mapID already exists, this command does nothing.
cmd-addmap-help = addmap <mapID> [pre-init]
cmd-addmap-help = addmap <mapID> [initialize]
cmd-rmmap-desc = Removes a map from the world. You cannot remove nullspace.
cmd-rmmap-help = rmmap <mapId>

View File

@@ -136,7 +136,6 @@ cmd-savemap-not-exist = O mapa de destino não existe.
cmd-savemap-init-warning = Tentativa de salvar um mapa pós-inicialização sem forçar o salvamento.
cmd-savemap-attempt = Tentando salvar o mapa {$mapId} em {$path}.
cmd-savemap-success = Mapa salvo com sucesso.
cmd-savemap-error = Não foi possível salvar o mapa! Consulte o log do servidor para obter detalhes.
cmd-hint-savemap-id = <MapID>
cmd-hint-savemap-path = <Path>
cmd-hint-savemap-force = [bool]

View File

@@ -26,10 +26,10 @@ public class PhysicsTumblerBenchmark
var entManager = _sim.Resolve<IEntityManager>();
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var mapUid = entManager.System<SharedMapSystem>().CreateMap(out var mapId);
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 300; i++)
for (var i = 0; i < 800; i++)
{
entManager.TickUpdate(0.016f, false);
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
@@ -42,9 +42,6 @@ public class PhysicsTumblerBenchmark
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
if (entManager.TryGetComponent(mapUid, out BroadphaseComponent? mapBroadphase))
entManager.System<SharedBroadphaseSystem>().RebuildBottomUp(mapBroadphase);
}
[Benchmark]
@@ -52,7 +49,7 @@ public class PhysicsTumblerBenchmark
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 1000; i++)
for (var i = 0; i < 5000; i++)
{
entManager.TickUpdate(0.016f, false);
}

View File

@@ -6,7 +6,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
public static class Program
internal static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)

View File

@@ -5,6 +5,7 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
{
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
cachePath = Path.Combine(rootDir, "cef_cache", "0");
}
var settings = new CefSettings()
{

View File

@@ -84,19 +84,6 @@ internal partial class AudioManager
AL.Listener(ALListenerfv.Orientation, ref at, ref up);
}
void IAudioInternal.Remove(AudioStream stream)
{
if (stream.ClydeHandle == null)
return;
if (!_audioSampleBuffers.Remove(stream.BufferId))
{
return;
}
AL.DeleteBuffer(stream.BufferId);
}
/// <inheritdoc/>
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
{
@@ -133,9 +120,9 @@ internal partial class AudioManager
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
return new AudioStream(this, buffer, handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
}
/// <inheritdoc/>
@@ -192,9 +179,9 @@ internal partial class AudioManager
_checkAlError();
var handle = new ClydeHandle(_audioSampleBuffers.Count);
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate);
return new AudioStream(this, buffer, handle, length, wav.NumChannels, name);
return new AudioStream(handle, length, wav.NumChannels, name);
}
/// <inheritdoc/>
@@ -223,8 +210,8 @@ internal partial class AudioManager
var handle = new ClydeHandle(_audioSampleBuffers.Count);
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
return new AudioStream(this, buffer, handle, length, channels, name);
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
return new AudioStream(handle, length, channels, name);
}
public void SetMasterGain(float newGain)
@@ -306,7 +293,7 @@ internal partial class AudioManager
// ReSharper disable once PossibleInvalidOperationException
// TODO: This really shouldn't be indexing based on the ClydeHandle...
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[stream.BufferId].BufferHandle);
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value].BufferHandle);
var audioSource = new AudioSource(this, source, stream);
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
@@ -383,12 +370,5 @@ internal partial class AudioManager
}
_bufferedAudioSources.Clear();
foreach (var buffer in _audioSampleBuffers.Values)
{
DeleteAudioBufferOnMainThread(buffer.BufferHandle);
}
_audioSampleBuffers.Clear();
}
}

View File

@@ -5,7 +5,6 @@ using System.Threading;
using OpenTK.Audio.OpenAL;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Client.Audio.Sources;
using Robust.Client.ResourceManagement;
using Robust.Shared;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
@@ -18,15 +17,13 @@ internal sealed partial class AudioManager : IAudioInternal
{
[Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!;
[Shared.IoC.Dependency] private readonly ILogManager _logMan = default!;
[Shared.IoC.Dependency] private readonly IReloadManager _reload = default!;
[Shared.IoC.Dependency] private readonly IResourceCache _cache = default!;
private Thread? _gameThread;
private ALDevice _openALDevice;
private ALContext _openALContext;
private readonly Dictionary<int, LoadedAudioSample> _audioSampleBuffers = new();
private readonly List<LoadedAudioSample> _audioSampleBuffers = new();
private readonly Dictionary<int, WeakReference<BaseAudioSource>> _audioSources =
new();
@@ -119,22 +116,6 @@ internal sealed partial class AudioManager : IAudioInternal
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
_reload.Register("/Audio", "*.ogg");
_reload.Register("/Audio", "*.wav");
_reload.OnChanged += OnReload;
}
private void OnReload(ResPath args)
{
if (args.Extension != "ogg" &&
args.Extension != "wav")
{
return;
}
_cache.ReloadResource<AudioResource>(args);
}
internal bool IsMainThread()
@@ -159,11 +140,6 @@ internal sealed partial class AudioManager : IAudioInternal
}
}
internal void LogError(string message)
{
OpenALSawmill.Error(message);
}
/// <summary>
/// Like _checkAlError but allows custom data to be passed in as relevant.
/// </summary>

View File

@@ -6,15 +6,8 @@ namespace Robust.Client.Audio;
/// <summary>
/// Has the metadata for a particular audio stream as well as the relevant internal handle to it.
/// </summary>
public sealed class AudioStream : IDisposable
public sealed class AudioStream
{
private IAudioInternal _audio;
/// <summary>
/// Buffer ID for this audio in AL.
/// </summary>
internal int BufferId { get; }
public TimeSpan Length { get; }
internal IClydeHandle? ClydeHandle { get; }
public string? Name { get; }
@@ -22,10 +15,8 @@ public sealed class AudioStream : IDisposable
public string? Artist { get; }
public int ChannelCount { get; }
internal AudioStream(IAudioInternal internalAudio, int bufferId, IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
internal AudioStream(IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
{
_audio = internalAudio;
BufferId = bufferId;
ClydeHandle = handle;
Length = length;
ChannelCount = channelCount;
@@ -33,9 +24,4 @@ public sealed class AudioStream : IDisposable
Title = title;
Artist = artist;
}
public void Dispose()
{
_audio.Remove(this);
}
}

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using OpenTK.Audio.OpenAL;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
@@ -57,8 +56,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
private EntityQuery<PhysicsComponent> _physicsQuery;
private float _maxRayLength;
private float _zOffset;
private float _audioEndBuffer;
public override float ZOffset
{
@@ -82,6 +79,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
}
private float _zOffset;
/// <inheritdoc />
public override void Initialize()
{
@@ -109,31 +108,20 @@ public sealed partial class AudioSystem : SharedAudioSystem
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
Subs.CVar(CfgManager, CVars.AudioEndBuffer, OnAudioBuffer, true);
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
Subs.CVar(CfgManager, CVars.AudioTickRate, OnAudioTickRate, true);
InitializeLimit();
}
private void OnAudioBuffer(float value)
{
_audioEndBuffer = value;
}
private void OnAudioTickRate(int obj)
{
_audioFrameTime = 1f / obj;
_audioFrameTimeRemaining = MathF.Min(_audioFrameTimeRemaining, _audioFrameTime);
}
private void OnAudioState(Entity<AudioComponent> entity, ref AfterAutoHandleStateEvent args)
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
{
var component = entity.Comp;
if (component.LifeStage < ComponentLifeStage.Initialized)
return;
ApplyAudioParams(component.Params, component);
component.Source.Global = component.Global;
@@ -157,29 +145,21 @@ public sealed partial class AudioSystem : SharedAudioSystem
case AudioState.Stopped:
component.StopPlaying();
component.PlaybackPosition = 0f;
return;
break;
}
// If playback position changed then update it.
var position = (float) ((entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart).TotalSeconds;
var currentPosition = entity.Comp.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
// Don't try to set the audio too far ahead.
if (!string.IsNullOrEmpty(entity.Comp.FileName))
if (!string.IsNullOrEmpty(component.FileName))
{
if (position > GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds - _audioEndBuffer)
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
var currentPosition = component.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
if (diff > 0.1f)
{
entity.Comp.StopPlaying();
return;
component.PlaybackPosition = position;
}
}
// If the difference is minor then we'll just keep playing it.
if (diff > 0.1f)
{
entity.Comp.PlaybackPosition = position;
}
}
/// <summary>
@@ -227,10 +207,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
{
var component = entity.Comp;
length ??= GetAudioLength(component.FileName);
// If audio came into range then start playback at the correct position.
var offset = ((entity.Comp.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
if (TryAudioLimit(component.FileName))
{
@@ -254,17 +230,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
// Don't play until first frame so occlusion etc. are correct.
component.Gain = 0f;
// If the offset < buffer than just play it from the start.
if (offset < AudioDespawnBuffer)
{
offset = 0;
}
// Not enough audio to play
else if (offset > length.Value.TotalSeconds - _audioEndBuffer)
{
component.StopPlaying();
return;
}
length ??= GetAudioLength(component.FileName);
// If audio came into range then start playback at the correct position.
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % length.Value.TotalSeconds;
if (offset > 0)
{

View File

@@ -13,8 +13,6 @@ namespace Robust.Client.Audio;
/// </summary>
internal sealed class HeadlessAudioManager : IAudioInternal
{
private int _audioBuffer;
/// <inheritdoc />
public void InitializePostWindowing()
{
@@ -67,11 +65,6 @@ internal sealed class HeadlessAudioManager : IAudioInternal
{
}
/// <inheritdoc />
public void Remove(AudioStream stream)
{
}
/// <inheritdoc />
public void StopAllAudio()
{
@@ -108,11 +101,11 @@ internal sealed class HeadlessAudioManager : IAudioInternal
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
{
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
return new AudioStream(this, _audioBuffer++, null, length, channels, name);
return new AudioStream(null, length, channels, name);
}
private AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
private static AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
{
return new AudioStream(this, _audioBuffer++, null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
return new AudioStream(null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
}
}

View File

@@ -44,8 +44,6 @@ internal interface IAudioInternal : IAudioManager
void SetAttenuation(Attenuation attenuation);
void Remove(AudioStream stream);
/// <summary>
/// Stops all audio from playing.
/// </summary>

View File

@@ -1,5 +1,6 @@
using System;
using System.IO;
using Robust.Client.Audio.Sources;
using Robust.Shared.Audio.Sources;
namespace Robust.Client.Audio;
@@ -10,7 +11,7 @@ namespace Robust.Client.Audio;
public interface IAudioManager
{
IAudioSource? CreateAudioSource(AudioStream stream);
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
AudioStream LoadAudioWav(Stream stream, string? name = null);

View File

@@ -13,7 +13,7 @@ internal sealed class AudioSource : BaseAudioSource
/// <summary>
/// Underlying stream to the audio.
/// </summary>
internal readonly AudioStream SourceStream;
private readonly AudioStream _sourceStream;
#if DEBUG
private bool _didPositionWarning;
@@ -21,7 +21,7 @@ internal sealed class AudioSource : BaseAudioSource
public AudioSource(AudioManager master, int sourceHandle, AudioStream sourceStream) : base(master, sourceHandle)
{
SourceStream = sourceStream;
_sourceStream = sourceStream;
}
/// <inheritdoc />
@@ -47,13 +47,13 @@ internal sealed class AudioSource : BaseAudioSource
#if DEBUG
// OpenAL doesn't seem to want to play stereo positionally.
// Log a warning if people try to.
if (SourceStream.ChannelCount > 1 && !_didPositionWarning)
if (_sourceStream.ChannelCount > 1 && !_didPositionWarning)
{
_didPositionWarning = true;
Master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
SourceStream.Name);
_sourceStream.Name);
// warning isn't enough, people just ignore it :(
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{SourceStream.Name}'. Make sure the audio is MONO, not stereo.");
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
}
#endif

View File

@@ -208,12 +208,6 @@ public abstract class BaseAudioSource : IAudioSource
}
set
{
if (float.IsNaN(value))
{
Master.LogError($"Tried to set NaN gain, setting audio source to 0f: {Environment.StackTrace}");
value = 0f;
}
_checkDisposed();
var priorOcclusion = 1f;
if (!IsEfxSupported)

View File

@@ -46,7 +46,6 @@ using Robust.Shared.Replays;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Upload;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client
@@ -103,7 +102,6 @@ namespace Robust.Client
deps.Register<ProfViewManager>();
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
deps.Register<NetworkResourceManager>();
deps.Register<IReloadManager, ReloadManager>();
switch (mode)
{

View File

@@ -93,7 +93,6 @@ namespace Robust.Client
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] private readonly IReloadManager _reload = default!;
private IWebViewManagerHook? _webViewHook;
@@ -186,7 +185,6 @@ namespace Robust.Client
// before prototype load.
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
_reload.Initialize();
_reflectionManager.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDefaultPrototypes();
@@ -384,7 +382,7 @@ namespace Robust.Client
_prof.Initialize();
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

@@ -25,6 +25,12 @@ public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
ProtoManager.PrototypesReloaded -= OnProtoReload;
}
protected override void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
{
base.OnUserInterfaceShutdown(ent, ref args);
_savedPositions.Remove(ent.Owner);
}
/// <inheritdoc />
public override void OpenUi(Entity<UserInterfaceComponent?> entity, Enum key, bool predicted = false)
{

View File

@@ -126,7 +126,7 @@ namespace Robust.Client.Graphics.Clyde
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
var mapId = vp.Eye!.Position.MapId;
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapSystem.GetMapOrInvalid(mapId), mapId, worldBox, worldBounds);
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldBox, worldBounds);
if (!overlay.BeforeDraw(args))
return;
@@ -178,7 +178,7 @@ namespace Robust.Client.Graphics.Clyde
var worldAABB = worldBounds.CalcBoundingBox();
var mapId = vp.Eye!.Position.MapId;
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapSystem.GetMapOrInvalid(mapId), mapId, worldAABB, worldBounds);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldAABB, worldBounds);
foreach (var overlay in list)
{

View File

@@ -10,7 +10,6 @@ using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
@@ -24,7 +23,6 @@ using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
namespace Robust.Client.Graphics.Clyde
@@ -50,8 +48,6 @@ namespace Robust.Client.Graphics.Clyde
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly ClientEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IReloadManager _reloads = default!;
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
@@ -103,16 +99,6 @@ namespace Robust.Client.Graphics.Clyde
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
_sawmillWin = _logManager.GetSawmill("clyde.win");
_reloads.Register("/Shaders", "*.swsl");
_reloads.Register("/Textures/Shaders", "*.swsl");
_reloads.Register("/Textures", "*.jpg");
_reloads.Register("/Textures", "*.jpeg");
_reloads.Register("/Textures", "*.png");
_reloads.Register("/Textures", "*.webp");
_reloads.OnChanged += OnChange;
_proto.PrototypesReloaded += OnProtoReload;
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
_cfg.OnValueChanged(CVars.DisplayWindowMode, WindowModeChanged, true);
@@ -136,38 +122,6 @@ namespace Robust.Client.Graphics.Clyde
return InitWindowing();
}
private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
if (!obj.WasModified<ShaderPrototype>())
return;
foreach (var shader in obj.ByType[typeof(ShaderPrototype)].Modified.Keys)
{
_resourceCache.ReloadResource<ShaderSourceResource>(shader);
}
}
private void OnChange(ResPath obj)
{
if ((obj.TryRelativeTo(new ResPath("/Shaders"), out _) || obj.TryRelativeTo(new ResPath("/Textures/Shaders"), out _)) && obj.Extension == "swsl")
{
_resourceCache.ReloadResource<ShaderSourceResource>(obj);
}
if (obj.TryRelativeTo(new ResPath("/Textures"), out _) && !obj.TryRelativeTo(new ResPath("/Textures/Tiles"), out _))
{
if (obj.Extension == "jpg" || obj.Extension == "jpeg" || obj.Extension == "webp")
{
_resourceCache.ReloadResource<TextureResource>(obj);
}
if (obj.Extension == "png")
{
_resourceCache.ReloadResource<TextureResource>(obj);
}
}
}
public bool InitializePostWindowing()
{
_gameThread = Thread.CurrentThread;

View File

@@ -1,15 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Toolshed;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -18,8 +23,6 @@ namespace Robust.Client.Map
{
internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager, IPostInjectInit
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IReloadManager _reload = default!;
[Dependency] private readonly IResourceManager _manager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
@@ -54,30 +57,6 @@ namespace Robust.Client.Map
{
base.Initialize();
_protoManager.PrototypesReloaded += OnProtoReload;
_reload.Register("/Textures/Tiles", "*.png");
_reload.OnChanged += OnReload;
_genTextureAtlas();
}
private void OnProtoReload(PrototypesReloadedEventArgs obj)
{
if (!obj.WasModified<ITileDefinition>())
return;
_genTextureAtlas();
}
private void OnReload(ResPath obj)
{
if (obj.Extension != "png")
return;
if (!obj.TryRelativeTo(new ResPath("/Textures/Tiles"), out _))
return;
_genTextureAtlas();
}

View File

@@ -1,23 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Client.Timing;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.Prototypes
{
public sealed class ClientPrototypeManager : PrototypeManager
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly IClientGameTiming _timing = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameControllerInternal _controller = default!;
[Dependency] private readonly IReloadManager _reload = default!;
private readonly List<FileSystemWatcher> _watchers = new();
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();
private readonly HashSet<ResPath> _reloadQueue = new();
public override void Initialize()
{
@@ -25,8 +37,9 @@ namespace Robust.Client.Prototypes
_netManager.RegisterNetMessage<MsgReloadPrototypes>(accept: NetMessageAccept.Server);
_reload.Register("/Prototypes", "*.yml");
_reload.OnChanged += ReloadPrototypeQueue;
_clyde.OnWindowFocused += WindowFocusedChanged;
WatchResources();
}
public override void LoadDefaultPrototypes(Dictionary<Type, HashSet<string>>? changed = null)
@@ -36,27 +49,99 @@ namespace Robust.Client.Prototypes
ResolveResults();
}
private void ReloadPrototypeQueue(ResPath file)
private void WindowFocusedChanged(WindowFocusedEventArgs args)
{
if (file.Extension != "yml")
return;
#if TOOLS
if (args.Focused && _reloadQueue.Count > 0)
{
Timer.Spawn(_reloadDelay, ReloadPrototypeQueue, _reloadToken.Token);
}
else
{
_reloadToken.Cancel();
_reloadToken = new CancellationTokenSource();
}
#endif
}
private void ReloadPrototypeQueue()
{
#if TOOLS
var sw = Stopwatch.StartNew();
var msg = new MsgReloadPrototypes
{
Paths = [file]
};
var msg = new MsgReloadPrototypes();
msg.Paths = _reloadQueue.ToArray();
_netManager.ClientSendMessage(msg);
// Reloading prototypes modifies entities. This currently causes some state management debug asserts to
// fail. To avoid this, we set `IGameTiming.ApplyingState` to true, even though this isn't really applying a
// server state.
using var _ = _timing.StartStateApplicationArea();
ReloadPrototypes([file]);
ReloadPrototypes(_reloadQueue);
_reloadQueue.Clear();
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
#endif
}
private void WatchResources()
{
if (!_cfg.GetCVar(CVars.ResPrototypeReloadWatch))
return;
#if TOOLS
foreach (var path in Resources.GetContentRoots().Select(r => r.ToString())
.Where(r => Directory.Exists(r + "/Prototypes")).Select(p => p + "/Prototypes"))
{
var watcher = new FileSystemWatcher(path, "*.yml")
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += (_, args) =>
{
switch (args.ChangeType)
{
case WatcherChangeTypes.Renamed:
case WatcherChangeTypes.Deleted:
return;
case WatcherChangeTypes.Created:
// case WatcherChangeTypes.Deleted:
case WatcherChangeTypes.Changed:
case WatcherChangeTypes.All:
break;
default:
throw new ArgumentOutOfRangeException();
}
TaskManager.RunOnMainThread(() =>
{
var file = new ResPath(args.FullPath);
foreach (var root in Resources.GetContentRoots())
{
if (!file.TryRelativeTo(root, out var relative))
{
continue;
}
_reloadQueue.Add(relative.Value);
}
});
};
try
{
watcher.EnableRaisingEvents = true;
_watchers.Add(watcher);
}
catch (IOException ex)
{
Logger.Error($"Watching resources in path {path} threw an exception:\n{ex}");
}
}
#endif
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Threading;
using Robust.Client.Audio;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
@@ -27,26 +26,22 @@ public sealed class AudioResource : BaseResource
throw new FileNotFoundException("Content file does not exist for audio sample.");
}
using var fileStream = cache.ContentFileRead(path);
var audioManager = dependencies.Resolve<IAudioInternal>();
if (path.Extension == "ogg")
using (var fileStream = cache.ContentFileRead(path))
{
AudioStream = audioManager.LoadAudioOggVorbis(fileStream, path.ToString());
var audioManager = dependencies.Resolve<IAudioInternal>();
if (path.Extension == "ogg")
{
AudioStream = audioManager.LoadAudioOggVorbis(fileStream, path.ToString());
}
else if (path.Extension == "wav")
{
AudioStream = audioManager.LoadAudioWav(fileStream, path.ToString());
}
else
{
throw new NotSupportedException("Unable to load audio files outside of ogg Vorbis or PCM wav");
}
}
else if (path.Extension == "wav")
{
AudioStream = audioManager.LoadAudioWav(fileStream, path.ToString());
}
else
{
throw new NotSupportedException("Unable to load audio files outside of ogg Vorbis or PCM wav");
}
}
public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default)
{
dependencies.Resolve<IAudioInternal>().Remove(AudioStream);
Load(dependencies, path);
}
public AudioResource(AudioStream stream) : base()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
@@ -176,71 +176,6 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar.MoveToEnd();
}
/// <summary>
/// Replace the current list of items with the items in newItems.
/// newItems should be in the order which they should appear in the list,
/// and items are considered equal if the Item text is equal in each item.
///
/// Provided the existing items have not been re-ordered relative to each
/// other, any items which already exist in the list are not destroyed,
/// which maintains consistency of scrollbars, selected items, etc.
/// </summary>
/// <param name="newItems">The list of items to update this list to</param>
public void SetItems(List<Item> newItems)
{
SetItems(newItems, (a,b) => string.Compare(a.Text, b.Text));
}
/// <inheritdoc />
/// <summary>
/// This variant allows for a custom equality operator to compare items, when
/// comparing the Item text is not desired.
/// </summary>
/// <param name="itemCmp">Comparison function to compare existing to new items.</param>
public void SetItems(List<Item> newItems, Comparison<Item> itemCmp)
{
// Walk through the existing items in this list and in newItems
// in parallel to synchronize our items with those in newItems.
int i = this.Count - 1;
int j = newItems.Count - 1;
while(i >= 0 && j >= 0)
{
var cmpResult = itemCmp(this[i], newItems[j]);
if (cmpResult == 0)
{
// This item exists in both our list and `newItems`. Nothing to do.
i--;
j--;
}
else if (cmpResult > 0)
{
// Item exists in our list, but not in `newItems`. Remove it.
RemoveAt(i);
i--;
}
else if (cmpResult < 0)
{
// A new entry which doesn't exist in our list. Insert it.
Insert(i + 1, newItems[j]);
j--;
}
}
// Any remaining items in our list don't exist in `newItems` so remove them
while (i >= 0)
{
RemoveAt(i);
i--;
}
// And finally, any remaining items in `newItems` don't exist in our list. Create them.
while (j >= 0)
{
Insert(0, newItems[j]);
j--;
}
}
// Without this attribute, this would compile into a property called "Item", causing problems with the Item class.
[System.Runtime.CompilerServices.IndexerName("IndexItem")]
public Item this[int index]

View File

@@ -21,6 +21,7 @@ namespace Robust.Client.UserInterface.Controls
[Virtual]
public class LineEdit : Control
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;

View File

@@ -38,6 +38,7 @@ namespace Robust.Client.UserInterface.Controls;
public sealed class TextEdit : Control
{
[Dependency] private readonly IClipboardManager _clipboard = null!;
[Dependency] private readonly IClyde _clyde = null!;
// @formatter:off
public const string StylePropertyCursorColor = "cursor-color";

View File

@@ -1,141 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.Utility;
internal sealed class ReloadManager : IReloadManager
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ILogManager _logMan = default!;
[Dependency] private readonly IResourceManager _res = default!;
[Dependency] private readonly ITaskManager _tasks = default!;
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();
private readonly HashSet<ResPath> _reloadQueue = new();
private List<FileSystemWatcher> _watchers = new();
public event Action<ResPath>? OnChanged;
private ISawmill _sawmill = default!;
public void Initialize()
{
_sawmill = _logMan.GetSawmill("reload");
_clyde.OnWindowFocused += WindowFocusedChanged;
}
private void WindowFocusedChanged(WindowFocusedEventArgs args)
{
#if TOOLS
if (args.Focused && _reloadQueue.Count > 0)
{
Timer.Spawn(_reloadDelay, ReloadFiles, _reloadToken.Token);
}
else
{
_reloadToken.Cancel();
_reloadToken = new CancellationTokenSource();
}
#endif
}
private void ReloadFiles()
{
foreach (var file in _reloadQueue)
{
var rootedFile = file.ToRootedPath();
if (!_res.ContentFileExists(rootedFile))
continue;
_sawmill.Info($"Reloading {rootedFile}");
OnChanged?.Invoke(rootedFile);
}
_reloadQueue.Clear();
}
public void Register(string directory, string filter)
{
if (!_cfg.GetCVar(CVars.ResPrototypeReloadWatch))
return;
#if TOOLS
foreach (var root in _res.GetContentRoots())
{
var path = root + directory;
if (!Directory.Exists(path))
{
continue;
}
var watcher = new FileSystemWatcher(path, filter)
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite
};
_watchers.Add(watcher);
watcher.Changed += OnWatch;
try
{
watcher.EnableRaisingEvents = true;
}
catch (IOException ex)
{
Logger.Error($"Watching resources in path {path} threw an exception:\n{ex}");
}
}
void OnWatch(object sender, FileSystemEventArgs args)
{
switch (args.ChangeType)
{
case WatcherChangeTypes.Renamed:
case WatcherChangeTypes.Deleted:
return;
case WatcherChangeTypes.Created:
// case WatcherChangeTypes.Deleted:
case WatcherChangeTypes.Changed:
case WatcherChangeTypes.All:
break;
default:
throw new ArgumentOutOfRangeException();
}
_tasks.RunOnMainThread(() =>
{
var fullPath = args.FullPath.Replace(Path.DirectorySeparatorChar, '/');
var file = new ResPath(fullPath);
foreach (var rootIter in _res.GetContentRoots())
{
if (!file.TryRelativeTo(rootIter, out var relative))
{
continue;
}
_reloadQueue.Add(relative.Value);
}
});
}
#endif
}
}

View File

@@ -297,7 +297,7 @@ namespace Robust.Server
: null;
// Set up the VFS
_resources.Initialize(dataDir);
_resources.Initialize(dataDir, hideUserDataDir: false);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;

View File

@@ -43,15 +43,8 @@ namespace Robust.Server.Console.Commands
return;
}
bool saveSuccess = _ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
if(saveSuccess)
{
shell.WriteLine("Save successful. Look in the user data directory.");
}
else
{
shell.WriteError("Save unsuccessful!");
}
_ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
shell.WriteLine("Save successful. Look in the user data directory.");
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
@@ -214,15 +207,8 @@ namespace Robust.Server.Console.Commands
}
shell.WriteLine(Loc.GetString("cmd-savemap-attempt", ("mapId", mapId), ("path", args[1])));
bool saveSuccess = _system.GetEntitySystem<MapLoaderSystem>().TrySaveMap(mapId, new ResPath(args[1]));
if(saveSuccess)
{
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
}
else
{
shell.WriteError(Loc.GetString("cmd-savemap-error"));
}
_system.GetEntitySystem<MapLoaderSystem>().TrySaveMap(mapId, new ResPath(args[1]));
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
}
}

View File

@@ -29,12 +29,12 @@ namespace Robust.Server
/// <summary>
/// Directory to load all assemblies from.
/// </summary>
public ResPath AssemblyDirectory { get; init; } = new(@"/Assemblies/");
public ResPath AssemblyDirectory { get; init; } = new(@"/Assemblies");
/// <summary>
/// Directory to load all prototypes from.
/// </summary>
public ResPath PrototypeDirectory { get; init; } = new(@"/Prototypes/");
public ResPath PrototypeDirectory { get; init; } = new(@"/Prototypes");
/// <summary>
/// Whether to disable mounting the "Resources/" folder on FULL_RELEASE.

View File

@@ -212,48 +212,6 @@ namespace Robust.Shared.Maths
return surfaceIntersect / (Area(this) + Area(other) - surfaceIntersect);
}
public readonly bool IsValid()
{
var d = Vector2.Subtract(TopRight, BottomLeft);
bool valid = d.X >= 0.0f && d.Y >= 0.0f;
valid = valid && BottomLeft.IsValid() && TopRight.IsValid();
return valid;
}
/// <summary>
/// Enlarges this box to contain another box.
/// </summary>
public bool EnlargeAabb(Box2 other)
{
var changed = false;
if (other.Left < Left)
{
Left = other.Left;
changed = true;
}
if (other.Bottom < Bottom)
{
Bottom = other.Bottom;
changed = true;
}
if (Right < other.Right)
{
Right = other.Right;
changed = true;
}
if (other.Top < Top)
{
Top = other.Top;
changed = true;
}
return changed;
}
/// <summary>
/// Returns the smallest rectangle that contains both of the rectangles.
/// </summary>
@@ -443,15 +401,6 @@ namespace Robust.Shared.Maths
public static float Perimeter(in Box2 box)
=> (box.Width + box.Height) * 2;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public static Box2 Union(Box2 a, Box2 b)
{
return new Box2(
Vector2.Min(a.BottomLeft, b.BottomLeft),
Vector2.Max(a.TopRight, b.TopRight));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public static Box2 Union(in Vector2 a, in Vector2 b)

View File

@@ -6,4 +6,3 @@
[assembly: InternalsVisibleTo("Robust.Client")]
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
[assembly: InternalsVisibleTo("Content.Benchmarks")]

View File

@@ -15,7 +15,6 @@ public static class Vector2Helpers
/// </summary>
public static readonly Vector2 Half = new(0.5f, 0.5f);
[Pure]
public static bool IsValid(this Vector2 v)
{
if (float.IsNaN(v.X) || float.IsNaN(v.Y))
@@ -31,13 +30,6 @@ public static class Vector2Helpers
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public static Vector2 MulAdd(Vector2 a, float s, Vector2 b)
{
return new Vector2(a.X + s * b.X, a.Y + s * b.Y);
}
public static Vector2 GetLengthAndNormalize(this Vector2 v, ref float length)
{
length = v.Length();

View File

@@ -5,9 +5,6 @@ using Robust.Shared.Audio.Systems;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
namespace Robust.Shared.Audio
{
@@ -32,25 +29,11 @@ namespace Robust.Shared.Audio
[DataDefinition]
public partial struct AudioParams
{
private float _volume = Default.Volume;
/// <summary>
/// Base volume to play the audio at, in dB.
/// </summary>
[DataField]
public float Volume
{
get => _volume;
set
{
if (float.IsNaN(value))
{
value = float.NegativeInfinity;
}
_volume = value;
}
}
public float Volume { get; set; } = Default.Volume;
/// <summary>
/// Scale for the audio pitch.

View File

@@ -37,7 +37,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
public const float AudioDespawnBuffer = 1f;
private const float AudioDespawnBuffer = 1f;
/// <summary>
/// Default max range at which the sound can be heard.
@@ -412,13 +412,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
if (component.Params.Volume.Equals(value))
return;
// Not a log error for now because if something has a negative infinity volume (i.e. 0 gain) then subtracting from it can
// easily cause this and making callers deal with it everywhere is quite annoying.
if (float.IsNaN(value))
{
value = float.NegativeInfinity;
}
component.Params.Volume = value;
component.Volume = value;
DirtyField(entity.Value, component, nameof(AudioComponent.Params));

View File

@@ -1233,12 +1233,6 @@ namespace Robust.Shared
public static readonly CVarDef<float> AudioRaycastLength =
CVarDef.Create("audio.raycast_length", SharedAudioSystem.DefaultSoundRange, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// Maximum offset for audio to be played at from its full duration. If it's past this then the audio won't be played.
/// </summary>
public static readonly CVarDef<float> AudioEndBuffer =
CVarDef.Create("audio.end_buffer", 0.01f, CVar.REPLICATED);
/// <summary>
/// Tickrate for audio calculations.
/// OpenAL recommends 30TPS. This is to avoid running raycasts every frame especially for high-refresh rate monitors.

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
@@ -186,9 +186,7 @@ public static class CompletionHelper
public static IEnumerable<CompletionOption> MapUids(IEntityManager? entManager = null)
{
IoCManager.Resolve(ref entManager);
return Components<MapComponent>(string.Empty, entManager, limit: 128);
return Components<MapComponent>(string.Empty, entManager);
}
/// <summary>
@@ -196,7 +194,7 @@ public static class CompletionHelper
/// </summary>
public static IEnumerable<CompletionOption> NetEntities(string text, IEntityManager? entManager = null, int limit = 20)
{
if (text != string.Empty && !NetEntity.TryParse(text, out _))
if (!NetEntity.TryParse(text, out _))
yield break;
IoCManager.Resolve(ref entManager);
@@ -216,7 +214,7 @@ public static class CompletionHelper
public static IEnumerable<CompletionOption> Components<T>(string text, IEntityManager? entManager = null, int limit = 20) where T : IComponent
{
if (text != string.Empty && !NetEntity.TryParse(text, out _))
if (!NetEntity.TryParse(text, out _))
yield break;
IoCManager.Resolve(ref entManager);

View File

@@ -88,6 +88,7 @@ namespace Robust.Shared.ContentPack
public string SystemAssemblyName = default!;
public HashSet<VerifierError> AllowedVerifierErrors = default!;
public List<string> WhitelistedNamespaces = default!;
public List<string> AllowedAssemblyPrefixes = default!;
public Dictionary<string, Dictionary<string, TypeConfig>> Types = default!;
}

View File

@@ -131,6 +131,16 @@ namespace Robust.Shared.ContentPack
return false;
}
#pragma warning disable RA0004
var loadedConfig = _config.Result;
#pragma warning restore RA0004
if (!loadedConfig.AllowedAssemblyPrefixes.Any(allowedNamePrefix => asmName.StartsWith(allowedNamePrefix)))
{
_sawmill.Error($"Assembly name '{asmName}' is not allowed for a content assembly");
return false;
}
if (VerifyIL)
{
if (!DoVerifyIL(asmName, resolver, peReader, reader))
@@ -179,10 +189,6 @@ namespace Robust.Shared.ContentPack
return true;
}
#pragma warning disable RA0004
var loadedConfig = _config.Result;
#pragma warning restore RA0004
var badRefs = new ConcurrentBag<EntityHandle>();
// We still do explicit type reference scanning, even though the actual whitelists work with raw members.

View File

@@ -60,9 +60,7 @@ namespace Robust.Shared.ContentPack
internal string GetPath(ResPath relPath)
{
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
// Sanitise platform-specific path and standardize it for engine use.
.Replace(Path.DirectorySeparatorChar, '/');
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
}
/// <inheritdoc />

View File

@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
/// The directory to use for user data.
/// If null, a virtual temporary file system is used instead.
/// </param>
void Initialize(string? userData);
/// <param name="hideUserDataDir">
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
/// <see cref="IResourceManager.UserData"/>.
/// </param>
void Initialize(string? userData, bool hideUserDataDir);
/// <summary>
/// Mounts a single stream as a content file. Useful for unit testing.

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
{
/// <summary>
/// The root path of this provider.
/// Can be null if it's a virtual provider.
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
/// </summary>
string? RootDir { get; }

View File

@@ -77,7 +77,7 @@ namespace Robust.Shared.ContentPack
var paths = new List<ResPath>();
foreach (var filePath in _res.ContentFindRelativeFiles(mountPath)
.Where(p => p.Filename.StartsWith(filterPrefix) &&
.Where(p => !p.ToString().Contains('/') && p.Filename.StartsWith(filterPrefix) &&
p.Extension == "dll"))
{
var fullPath = mountPath / filePath;
@@ -93,19 +93,23 @@ namespace Robust.Shared.ContentPack
{
var sw = Stopwatch.StartNew();
Sawmill.Debug("LOADING modules");
var files = new Dictionary<string, (ResPath Path, string[] references)>();
var files = new Dictionary<string, (ResPath Path, MemoryStream data, string[] references)>();
// Find all modules we want to load.
foreach (var fullPath in paths)
{
using var asmFile = _res.ContentFileRead(fullPath);
var refData = GetAssemblyReferenceData(asmFile);
var ms = new MemoryStream();
asmFile.CopyTo(ms);
ms.Position = 0;
var refData = GetAssemblyReferenceData(ms);
if (refData == null)
continue;
var (asmRefs, asmName) = refData.Value;
if (!files.TryAdd(asmName, (fullPath, asmRefs)))
if (!files.TryAdd(asmName, (fullPath, ms, asmRefs)))
{
Sawmill.Error("Found multiple modules with the same assembly name " +
$"'{asmName}', A: {files[asmName].Path}, B: {fullPath}.");
@@ -122,10 +126,10 @@ namespace Robust.Shared.ContentPack
Parallel.ForEach(files, pair =>
{
var (name, (path, _)) = pair;
var (name, (_, data, _)) = pair;
using var stream = _res.ContentFileRead(path);
if (!typeChecker.CheckAssembly(stream, resolver))
data.Position = 0;
if (!typeChecker.CheckAssembly(data, resolver))
{
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
}
@@ -137,14 +141,15 @@ namespace Robust.Shared.ContentPack
var nodes = TopologicalSort.FromBeforeAfter(
files,
kv => kv.Key,
kv => kv.Value.Path,
kv => kv.Value,
_ => Array.Empty<string>(),
kv => kv.Value.references,
allowMissing: true); // missing refs would be non-content assemblies so allow that.
// Actually load them in the order they depend on each other.
foreach (var path in TopologicalSort.Sort(nodes))
foreach (var item in TopologicalSort.Sort(nodes))
{
var (path, memory, _) = item;
Sawmill.Debug($"Loading module: '{path}'");
try
{
@@ -156,9 +161,9 @@ namespace Robust.Shared.ContentPack
}
else
{
using var assemblyStream = _res.ContentFileRead(path);
memory.Position = 0;
using var symbolsStream = _res.ContentFileReadOrNull(path.WithExtension("pdb"));
LoadGameAssembly(assemblyStream, symbolsStream, skipVerify: true);
LoadGameAssembly(memory, symbolsStream, skipVerify: true);
}
}
catch (Exception e)
@@ -174,7 +179,7 @@ namespace Robust.Shared.ContentPack
private (string[] refs, string name)? GetAssemblyReferenceData(Stream stream)
{
using var reader = ModLoader.MakePEReader(stream);
using var reader = ModLoader.MakePEReader(stream, leaveOpen: true);
var metaReader = reader.GetMetadataReader();
var name = metaReader.GetString(metaReader.GetAssemblyDefinition().Name);

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
{
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
!OperatingSystem.IsWindows()
&& !OperatingSystem.IsMacOS();
internal static string SafeGetResourcePath(string baseDir, ResPath path)
{
var relSysPath = path.ToRelativeSystemPath();
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
// better safe than sorry check
if (!retPath.StartsWith(baseDir))
{
// Allow path to match if it's just missing the directory separator at the end.
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return retPath;
}
}
}

View File

@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
public IWritableDirProvider UserData { get; private set; } = default!;
/// <inheritdoc />
public virtual void Initialize(string? userData)
public virtual void Initialize(string? userData, bool hideRootDir)
{
Sawmill = _logManager.GetSawmill("res");
if (userData != null)
{
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
}
else
{
@@ -381,6 +381,10 @@ namespace Robust.Shared.ContentPack
{
var rootDir = loader.GetPath(new ResPath(@"/"));
// TODO: GET RID OF THIS.
// This code shouldn't be passing OS disk paths through ResPath.
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
yield return new ResPath(rootDir);
}
}

View File

@@ -17,6 +17,10 @@ WhitelistedNamespaces:
- Content
- OpenDreamShared
AllowedAssemblyPrefixes:
- OpenDream
- Content
# The type whitelist does NOT care about which assembly types come from.
# This is because types switch assembly all the time.
# Just look up stuff like StreamReader on https://apisof.net.
@@ -736,15 +740,6 @@ Types:
- "void .ctor(string)"
- "void .ctor(string, System.Text.RegularExpressions.RegexOptions)"
- "void .ctor(string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
- "int Count(string)"
- "int Count(System.ReadOnlySpan`1<char>)"
- "int Count(System.ReadOnlySpan`1<char>, int)"
- "int Count(string, string)"
- "int Count(string, string, System.Text.RegularExpressions.RegexOptions)"
- "int Count(string, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
- "int Count(System.ReadOnlySpan`1<char>, string)"
- "int Count(System.ReadOnlySpan`1<char>, string, System.Text.RegularExpressions.RegexOptions)"
- "int Count(System.ReadOnlySpan`1<char>, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
RegexMatchTimeoutException: { All: True }
RegexOptions: { } # Enum
RegexParseError: { }
@@ -798,7 +793,6 @@ Types:
- "bool Equals(System.ReadOnlySpan`1<char>)"
- "bool Equals(System.Text.StringBuilder)"
- "char get_Chars(int)"
- "void set_Chars(int, char)"
- "int EnsureCapacity(int)"
- "int get_Capacity()"
- "int get_Length()"

View File

@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
/// <inheritdoc />
internal sealed class WritableDirProvider : IWritableDirProvider
{
/// <inheritdoc />
private readonly bool _hideRootDir;
public string RootDir { get; }
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
/// <summary>
/// Constructs an instance of <see cref="WritableDirProvider"/>.
/// </summary>
/// <param name="rootDir">Root file system directory to allow writing.</param>
public WritableDirProvider(DirectoryInfo rootDir)
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
{
// FullName does not have a trailing separator, and we MUST have a separator.
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
_hideRootDir = hideRootDir;
}
#region File Access
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
throw new FileNotFoundException();
var dirInfo = new DirectoryInfo(GetFullPath(path));
return new WritableDirProvider(dirInfo);
return new WritableDirProvider(dirInfo, _hideRootDir);
}
/// <inheritdoc />
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
path = path.Clean();
return GetFullPath(RootDir, path);
}
private static string GetFullPath(string root, ResPath path)
{
var relPath = path.ToRelativeSystemPath();
if (relPath.Contains("\\..") || relPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return Path.GetFullPath(Path.Combine(root, relPath));
return PathHelpers.SafeGetResourcePath(RootDir, path);
}
}
}

View File

@@ -95,7 +95,7 @@ public sealed partial class MapLoaderSystem
}
catch (Exception e)
{
Log.Error($"Caught exception while creating entities for map {file}: {e}");
Log.Error($"Caught exception while creating entities: {e}");
Delete(deserializer.Result);
throw;
}
@@ -103,7 +103,7 @@ public sealed partial class MapLoaderSystem
if (opts.ExpectedCategory is { } exp && exp != deserializer.Result.Category)
{
// Did someone try to load a map file as a grid or vice versa?
Log.Error($"Map {file} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
Log.Error($"File does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
Delete(deserializer.Result);
return false;
}
@@ -203,35 +203,6 @@ public sealed partial class MapLoaderSystem
return false;
}
/// <summary>
/// Tries to load a grid entity from a file and parent it to a newly created map.
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
/// </summary>
public bool TryLoadGrid(
ResPath path,
[NotNullWhen(true)] out Entity<MapComponent>? map,
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
DeserializationOptions? options = null,
Vector2 offset = default,
Angle rot = default)
{
var opts = options ?? DeserializationOptions.Default;
var mapUid = _mapSystem.CreateMap(out var mapId, runMapInit: opts.InitializeMaps);
if (opts.PauseMaps)
_mapSystem.SetPaused(mapUid, true);
if (!TryLoadGrid(mapId, path, out grid, options, offset, rot))
{
Del(mapUid);
map = null;
return false;
}
map = new(mapUid, Comp<MapComponent>(mapUid));
return true;
}
private void ApplyTransform(EntityDeserializer deserializer, MapLoadOptions opts)
{
if (opts.Rotation == Angle.Zero && opts.Offset == Vector2.Zero)

View File

@@ -1101,7 +1101,7 @@ namespace Robust.Shared.GameObjects
MetaDataComponent? meta = null,
params Type[] sourceComponents)
{
if (!MetaQuery.TryGetComponent(target, out meta))
if (!MetaQuery.TryGetComponent(source, out meta))
return false;
var allCopied = true;

View File

@@ -189,11 +189,9 @@ namespace Robust.Shared.GameObjects
return false;
var compType = component.GetType();
if (compType == typeof(TransformComponent) || compType == typeof(MetaDataComponent))
continue;
var compName = _componentFactory.GetComponentName(compType);
if (compName == _xformName || compName == _metaReg.Name)
continue;
// If the component isn't on the prototype then it's custom.
if (!protoData.TryGetValue(compName, out var protoMapping))
@@ -210,7 +208,9 @@ namespace Robust.Shared.GameObjects
return false;
}
if (compMapping.AnyExcept(protoMapping))
var diff = compMapping.Except(protoMapping);
if (diff != null && diff.Children.Count != 0)
return false;
}

View File

@@ -490,7 +490,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
{
var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i);
var proxy = fixture.Proxies[i];
tree.MoveProxy(proxy.ProxyId, bounds);
tree.MoveProxy(proxy.ProxyId, bounds, Vector2.Zero);
proxy.AABB = bounds;
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
}

View File

@@ -162,7 +162,7 @@ public abstract partial class SharedMapSystem
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
{
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
gridTree.Tree.MoveProxy(component.MapProxy, in aabb, Vector2.Zero);
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
@@ -187,7 +187,7 @@ public abstract partial class SharedMapSystem
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
{
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
gridTree.Tree.MoveProxy(component.MapProxy, in aabb, Vector2.Zero);
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
@@ -309,7 +309,6 @@ public abstract partial class SharedMapSystem
ChunkDatum data)
{
var counter = 0;
var gridEnt = new Entity<MapGridComponent>(uid, component);
if (data.IsDeleted())
{
@@ -327,12 +326,10 @@ public abstract partial class SharedMapSystem
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, Tile.Empty);
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
}
}
deletedChunk.CachedBounds = Box2i.Empty;
deletedChunk.SuppressCollisionRegeneration = false;
return;
}
@@ -353,12 +350,10 @@ public abstract partial class SharedMapSystem
var gridIndices = chunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, tile);
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
}
}
DebugTools.Assert(chunk.Fixtures.SetEquals(data.Fixtures));
// These should never refer to the same object
DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures);
@@ -513,7 +508,7 @@ public abstract partial class SharedMapSystem
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
{
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), component));
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), component));
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
component.MapProxy = proxy;
}
@@ -567,7 +562,7 @@ public abstract partial class SharedMapSystem
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
{
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), grid));
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), grid));
DebugTools.Assert(grid.MapProxy == DynamicTree.Proxy.Free);
grid.MapProxy = proxy;
}
@@ -1619,7 +1614,7 @@ public abstract partial class SharedMapSystem
if (!MapManager.SuppressOnTileChanged)
{
var newTileRef = new TileRef(uid, gridTile, newTile);
_mapInternal.RaiseOnTileChanged((uid, grid), newTileRef, oldTile, mapChunk.Indices);
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, mapChunk.Indices);
}
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)

View File

@@ -22,6 +22,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
private EntityQuery<FixturesComponent> _fixturesQuery;
@@ -158,9 +159,9 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Creates a new instance of this class.
/// </summary>
public TileChangedEvent(Entity<MapGridComponent> entity, TileRef newTile, Tile oldTile, Vector2i chunkIndex)
public TileChangedEvent(EntityUid uid, TileRef newTile, Tile oldTile, Vector2i chunkIndex)
{
Entity = entity;
Entity = uid;
NewTile = newTile;
OldTile = oldTile;
ChunkIndex = chunkIndex;
@@ -172,9 +173,9 @@ namespace Robust.Shared.GameObjects
public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty;
/// <summary>
/// Entity of the grid with the tile-change. TileRef stores the GridId.
/// EntityUid of the grid with the tile-change. TileRef stores the GridId.
/// </summary>
public readonly Entity<MapGridComponent> Entity;
public readonly EntityUid Entity;
/// <summary>
/// New tile that replaced the old one.

View File

@@ -279,7 +279,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
}
}
protected void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
protected virtual void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
{
var ents = new ValueList<EntityUid>();
foreach (var (key, acts) in ent.Comp.Actors)
@@ -1064,11 +1064,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
{
if (open)
{
bui.Open();
#if EXCEPTION_TOLERANCE
try
{
#endif
bui.Open();
if (UIQuery.TryComp(bui.Owner, out var uiComp))
{
@@ -1096,24 +1096,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
uiComp.ClientOpenInterfaces.Remove(bui.UiKey);
}
#if EXCEPTION_TOLERANCE
try
{
#endif
if (!TerminatingOrDeleted(bui.Owner))
{
SavePosition(bui);
}
SavePosition(bui);
bui.Dispose();
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
Log.Error(
$"Caught exception while attempting to dispose of a BUI {bui.UiKey} with type {bui.GetType()} on entity {ToPrettyString(bui.Owner)}. Exception: {e}");
}
#endif
}
}

View File

@@ -1,5 +1,3 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Shared.Map
@@ -12,6 +10,6 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="tileRef">A reference to the new tile.</param>
/// <param name="oldTile">The old tile that got replaced.</param>
void RaiseOnTileChanged(Entity<MapGridComponent> entity, TileRef tileRef, Tile oldTile, Vector2i chunk);
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk);
}
}

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Robust.Shared.Map
@@ -8,7 +7,7 @@ namespace Robust.Shared.Map
/// <summary>
/// The definition (template) for a grid tile.
/// </summary>
public interface ITileDefinition : IPrototype
public interface ITileDefinition
{
/// <summary>
/// The numeric tile ID used to refer to this tile inside the map datastructure.

View File

@@ -96,13 +96,14 @@ internal partial class MapManager
/// </summary>
/// <param name="tileRef">A reference to the new tile.</param>
/// <param name="oldTile">The old tile that got replaced.</param>
void IMapManagerInternal.RaiseOnTileChanged(Entity<MapGridComponent> entity, TileRef tileRef, Tile oldTile, Vector2i chunk)
public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk)
{
if (SuppressOnTileChanged)
return;
var ev = new TileChangedEvent(entity, tileRef, oldTile, chunk);
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, ref ev, true);
var euid = tileRef.GridUid;
var ev = new TileChangedEvent(euid, tileRef, oldTile, chunk);
EntityManager.EventBus.RaiseLocalEvent(euid, ref ev, true);
}
protected Entity<MapGridComponent> CreateGrid(EntityUid map, ushort chunkSize, EntityUid forcedGridEuid)

View File

@@ -10,6 +10,7 @@ namespace Robust.Shared.Map
[Virtual]
internal class TileDefinitionManager : ITileDefinitionManager
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
protected readonly List<ITileDefinition> TileDefs;
private readonly Dictionary<string, ITileDefinition> _tileNames;
private readonly Dictionary<string, List<string>> _awaitingAliases;

File diff suppressed because it is too large Load Diff

View File

@@ -28,18 +28,18 @@ public sealed class DynamicTreeBroadPhase : IBroadPhase
public Box2 GetFatAabb(DynamicTree.Proxy proxy)
{
return _tree.GetUserData(proxy)!.AABB;
return _tree.GetFatAabb(proxy);
}
public DynamicTree.Proxy AddProxy(ref FixtureProxy proxy)
{
var proxyId = _tree.CreateProxy(proxy.AABB, uint.MaxValue, proxy);
var proxyId = _tree.CreateProxy(proxy.AABB, proxy);
return proxyId;
}
public void MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb)
public bool MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb, Vector2 displacement)
{
_tree.MoveProxy(proxy, in aabb);
return _tree.MoveProxy(proxy, in aabb, displacement);
}
public void RemoveProxy(DynamicTree.Proxy proxy)
@@ -132,16 +132,6 @@ public sealed class DynamicTreeBroadPhase : IBroadPhase
state = tuple.state;
}
public void Rebuild(bool fullBuild)
{
_tree.Rebuild(fullBuild);
}
public void RebuildBottomUp()
{
_tree.RebuildBottomUp();
}
private static bool AabbQueryStateCallback<TState>(ref (TState state, B2DynamicTree<FixtureProxy> tree, DynamicTree<FixtureProxy>.QueryCallbackDelegate<TState> callback, Box2 aabb, bool approx, DynamicTree<FixtureProxy>.ExtractAabbDelegate extract) tuple, DynamicTree.Proxy proxy)
{
var item = tuple.tree.GetUserData(proxy)!;

View File

@@ -1,5 +1,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
@@ -8,23 +9,21 @@ namespace Robust.Shared.Physics;
/// <summary>
/// Convex hull used for poly collision.
/// </summary>
internal ref struct InternalPhysicsHull
internal ref struct PhysicsHull()
{
public Span<Vector2> Points;
public int Count;
internal InternalPhysicsHull(Span<Vector2> vertices, int count) : this()
public PhysicsHull(Span<Vector2> vertices, int count) : this()
{
Count = count;
Points = vertices[..count];
}
private static InternalPhysicsHull RecurseHull(Vector2 p1, Vector2 p2, Span<Vector2> ps, int count)
private static PhysicsHull RecurseHull(Vector2 p1, Vector2 p2, Span<Vector2> ps, int count)
{
InternalPhysicsHull hull = new()
{
Count = 0
};
PhysicsHull hull = new();
hull.Count = 0;
if (count == 0)
{
@@ -70,10 +69,10 @@ internal ref struct InternalPhysicsHull
var bestPoint = ps[bestIndex];
// compute hull to the right of p1-bestPoint
InternalPhysicsHull hull1 = RecurseHull(p1, bestPoint, rightPoints, rightCount);
PhysicsHull hull1 = RecurseHull(p1, bestPoint, rightPoints, rightCount);
// compute hull to the right of bestPoint-p2
InternalPhysicsHull hull2 = RecurseHull(bestPoint, p2, rightPoints, rightCount);
PhysicsHull hull2 = RecurseHull(bestPoint, p2, rightPoints, rightCount);
// stich together hulls
for (var i = 0; i < hull1.Count; ++i)
@@ -97,9 +96,9 @@ internal ref struct InternalPhysicsHull
// - merges vertices based on b2_linearSlop
// - removes collinear points using b2_linearSlop
// - returns an empty hull if it fails
public static InternalPhysicsHull ComputeHull(ReadOnlySpan<Vector2> points, int count)
public static PhysicsHull ComputeHull(ReadOnlySpan<Vector2> points, int count)
{
InternalPhysicsHull hull = new();
PhysicsHull hull = new();
if (count is < 3 or > PhysicsConstants.MaxPolygonVertices)
{
@@ -288,7 +287,7 @@ internal ref struct InternalPhysicsHull
return hull;
}
public static bool ValidateHull(InternalPhysicsHull hull)
public static bool ValidateHull(PhysicsHull hull)
{
if (hull.Count < 3 || PhysicsConstants.MaxPolygonVertices < hull.Count)
{

View File

@@ -76,7 +76,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
{
DebugTools.Assert(count is >= 3 and <= PhysicsConstants.MaxPolygonVertices);
var hull = InternalPhysicsHull.ComputeHull(vertices, count);
var hull = PhysicsHull.ComputeHull(vertices, count);
if (hull.Count < 3)
{
@@ -87,7 +87,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
return true;
}
internal void Set(InternalPhysicsHull hull)
internal void Set(PhysicsHull hull)
{
DebugTools.Assert(hull.Count >= 3);
var vertexCount = hull.Count;
@@ -119,14 +119,14 @@ namespace Robust.Shared.Physics.Collision.Shapes
if (count is < 3 or > PhysicsConstants.MaxPolygonVertices)
return false;
var hull = new InternalPhysicsHull();
var hull = new PhysicsHull();
for (var i = 0; i < count; i++)
{
hull.Points[i] = Vertices[i];
}
hull.Count = count;
return InternalPhysicsHull.ValidateHull(hull);
return PhysicsHull.ValidateHull(hull);
}
private static Vector2 ComputeCentroid(Vector2[] vs, int count)
@@ -199,7 +199,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
verts[2] = bounds.TopRight;
verts[3] = bounds.TopLeft;
var hull = new InternalPhysicsHull(verts, 4);
var hull = new PhysicsHull(verts, 4);
Set(hull);
}

View File

@@ -139,7 +139,7 @@ namespace Robust.Shared.Physics
return true;
}
proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
proxy = _b2Tree.CreateProxy(aabb.Value, item);
_nodeLookup[item] = proxy;
return true;
@@ -199,12 +199,11 @@ namespace Robust.Shared.Physics
if (proxy == DynamicTree.Proxy.Free)
{
proxy = _b2Tree.CreateProxy(newBox.Value, uint.MaxValue, item);
proxy = _b2Tree.CreateProxy(newBox.Value, item);
return true;
}
_b2Tree.MoveProxy(proxy, newBox.Value);
return true;
return _b2Tree.MoveProxy(proxy, newBox.Value, Vector2.Zero);
}
public void QueryAabb(QueryCallbackDelegate callback, Box2 aabb, bool approx = false)
@@ -335,7 +334,7 @@ namespace Robust.Shared.Physics
ref var proxy = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeLookup, item, out var exists);
if (!exists)
{
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
return;
}
@@ -350,9 +349,9 @@ namespace Robust.Shared.Physics
}
if (proxy == DynamicTree.Proxy.Free)
proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
proxy = _b2Tree.CreateProxy(aabb.Value, item);
else
_b2Tree.MoveProxy(proxy, aabb.Value);
_b2Tree.MoveProxy(proxy, aabb.Value, Vector2.Zero);
}
[Conditional("DEBUG_DYNAMIC_TREE")]

View File

@@ -16,7 +16,7 @@ public interface IBroadPhase
DynamicTree.Proxy AddProxy(ref FixtureProxy proxy);
void MoveProxy(DynamicTree.Proxy proxyId, in Box2 aabb);
bool MoveProxy(DynamicTree.Proxy proxyId, in Box2 aabb, Vector2 displacement);
FixtureProxy? GetProxy(DynamicTree.Proxy proxy);
@@ -54,11 +54,6 @@ public interface IBroadPhase
DynamicTree<FixtureProxy>.RayQueryCallbackDelegate<TState> callback,
in Ray ray,
bool approx = false);
void Rebuild(bool fullBuild);
void RebuildBottomUp();
}
public interface IBroadPhase<T> : ICollection<T> where T : notnull {

View File

@@ -4,12 +4,6 @@ namespace Robust.Shared.Physics
{
public static class PhysicsConstants
{
public const int LengthUnitsPerMetre = 1;
// Used to detect bad values. Positions greater than about 16km will have precision
// problems, so 100km as a limit should be fine in all cases.
public const float Huge = (100000.0f * LengthUnitsPerMetre);
/// <summary>
/// The radius of the polygon/edge shape skin. This should not be modified. Making
/// this smaller means polygons will have an insufficient buffer for continuous collision.
@@ -29,7 +23,7 @@ namespace Robust.Shared.Physics
/// Minimum buffer distance for angles.
/// </summary>
public const float AngularSlop = 2.0f / 180.0f * MathF.PI;
public const byte MaxPolygonVertices = 8;
public const float DefaultContactFriction = 0.4f;

View File

@@ -1,13 +0,0 @@
using System;
using System.Numerics;
namespace Robust.Shared.Physics;
public struct PhysicsHull
{
public static Span<Vector2> ComputePoints(ReadOnlySpan<Vector2> points, int count)
{
var hull = InternalPhysicsHull.ComputeHull(points, count);
return hull.Points;
}
}

View File

@@ -99,7 +99,7 @@ internal record struct Polygon : IPhysShape
public Polygon(Vector2[] vertices)
{
Unsafe.SkipInit(out this);
var hull = InternalPhysicsHull.ComputeHull(vertices, vertices.Length);
var hull = PhysicsHull.ComputeHull(vertices, vertices.Length);
if (hull.Count < 3)
{
@@ -121,7 +121,7 @@ internal record struct Polygon : IPhysShape
return new Polygon(polyShape);
}
private void Set(InternalPhysicsHull hull)
private void Set(PhysicsHull hull)
{
DebugTools.Assert(hull.Count >= 3);
var vertexCount = hull.Count;

View File

@@ -77,22 +77,6 @@ namespace Robust.Shared.Physics.Systems
_broadphaseExpand = value;
}
public void Rebuild(BroadphaseComponent component, bool fullBuild)
{
component.StaticTree.Rebuild(fullBuild);
component.DynamicTree.Rebuild(fullBuild);
component.SundriesTree._b2Tree.Rebuild(fullBuild);
component.StaticSundriesTree._b2Tree.Rebuild(fullBuild);
}
public void RebuildBottomUp(BroadphaseComponent component)
{
component.StaticTree.RebuildBottomUp();
component.DynamicTree.RebuildBottomUp();
component.SundriesTree._b2Tree.RebuildBottomUp();
component.StaticSundriesTree._b2Tree.RebuildBottomUp();
}
#region Find Contacts
/// <summary>

View File

@@ -618,24 +618,6 @@ public abstract partial class SharedPhysicsSystem
ArrayPool<Vector2>.Shared.Return(worldPoints);
}
private record struct UpdateTreesJob : IRobustJob
{
public IEntityManager EntManager;
public void Execute()
{
var query = EntManager.AllEntityQueryEnumerator<BroadphaseComponent>();
while (query.MoveNext(out var broadphase))
{
broadphase.DynamicTree.Rebuild(false);
broadphase.StaticTree.Rebuild(false);
broadphase.SundriesTree._b2Tree.Rebuild(false);
broadphase.StaticSundriesTree._b2Tree.Rebuild(false);
}
}
}
private void BuildManifolds(Contact[] contacts, int count, ContactStatus[] status, Vector2[] worldPoints)
{
if (count == 0)

View File

@@ -35,6 +35,7 @@ namespace Robust.Shared.Physics.Systems
public bool TryCollideRect(Box2 collider, MapId mapId, bool approximate = true)
{
var state = (collider, mapId, found: false);
var broadphases = new ValueList<Entity<BroadphaseComponent>>();
_broadphase.GetBroadphases(mapId,
collider,

View File

@@ -9,7 +9,6 @@
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
[assembly: InternalsVisibleTo("OpenToolkit.GraphicsLibraryFramework")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Gives access to Castle(Moq)
[assembly: InternalsVisibleTo("Content.Benchmarks")]
[assembly: InternalsVisibleTo("Robust.Benchmarks")]
[assembly: InternalsVisibleTo("Robust.Client.WebView")]
[assembly: InternalsVisibleTo("Robust.Packaging")]

View File

@@ -8,7 +8,6 @@ using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using Robust.Shared.Asynchronous;
using Robust.Shared.Collections;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;

View File

@@ -2,7 +2,6 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
@@ -353,29 +352,6 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
return mappingNode._children.Count == 0 ? null : mappingNode;
}
/// <summary>
/// Returns true if there are any nodes on this node that aren't in the other node.
/// </summary>
[Pure]
public bool AnyExcept(MappingDataNode node)
{
foreach (var (key, val) in _list)
{
var other = node._list.FirstOrNull(p => p.Key.Equals(key));
if (other == null)
{
return true;
}
// We only keep the entry if the values are not equal
if (!val.Equals(other.Value.Value))
return true;
}
return false;
}
public override bool Equals(object? obj)
{
if (obj is not MappingDataNode other)

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using NetSerializer;
namespace Robust.Shared.Serialization;
/// <summary>
/// Custom serializer implementation for <see cref="BitArray"/>.
/// </summary>
/// <remarks>
/// <para>
/// This type is necessary as, since .NET 10, the internal layout of <see cref="BitArray"/> was changed.
/// The type now (internally) implements <see cref="ISerializable"/> for backwards compatibility with existing
/// <c>BinaryFormatter</c> code, but NetSerializer does not support <see cref="ISerializable"/>.
/// </para>
/// <para>
/// This code is designed to be backportable &amp; network compatible with the previous behavior on .NET 9.
/// </para>
/// </remarks>
internal sealed class NetBitArraySerializer : IStaticTypeSerializer
{
// For reference, the layout of BitArray before .NET 10 was:
// private int[] m_array;
// private int m_length;
// private int _version;
// NetSerializer serialized these in the following order (sorted by name):
// _version, m_array, m_length
public bool Handles(Type type)
{
return type == typeof(BitArray);
}
public IEnumerable<Type> GetSubtypes(Type type)
{
return [typeof(int[]), typeof(int)];
}
public MethodInfo GetStaticWriter(Type type)
{
return typeof(NetBitArraySerializer).GetMethod("Write", BindingFlags.Static | BindingFlags.NonPublic)!;
}
public MethodInfo GetStaticReader(Type type)
{
return typeof(NetBitArraySerializer).GetMethod("Read", BindingFlags.Static | BindingFlags.NonPublic)!;
}
[UsedImplicitly]
private static void Write(Serializer serializer, Stream stream, BitArray value)
{
var intCount = (31 + value.Length) >> 5;
var ints = new int[intCount];
value.CopyTo(ints, 0);
serializer.SerializeDirect(stream, 0); // _version
serializer.SerializeDirect(stream, ints); // m_array
serializer.SerializeDirect(stream, value.Length); // m_length
}
[UsedImplicitly]
private static void Read(Serializer serializer, Stream stream, out BitArray value)
{
serializer.DeserializeDirect<int>(stream, out _); // _version
serializer.DeserializeDirect<int[]>(stream, out var array); // m_array
serializer.DeserializeDirect<int>(stream, out var length); // m_length
value = new BitArray(array)
{
Length = length
};
}
}

View File

@@ -91,6 +91,7 @@ namespace Robust.Shared.Serialization
MappedStringSerializer.TypeSerializer,
new Vector2Serializer(),
new Matrix3x2Serializer(),
new NetBitArraySerializer()
}
};
_serializer = new Serializer(types, settings);

View File

@@ -26,8 +26,6 @@ namespace Robust.Shared.Utility
_count = 0;
}
internal ref T this[int index] => ref _stack[index];
public void Push(in T element)
{
if (_count == _capacity)

View File

@@ -1,22 +0,0 @@
using System;
namespace Robust.Shared.Utility;
/// <summary>
/// Handles hot-reloading modified resource files such as prototypes or shaders.
/// </summary>
internal interface IReloadManager
{
/// <summary>
/// File that has been modified.
/// </summary>
public event Action<ResPath>? OnChanged;
/// <summary>
/// Registers the specified directory and specified file extension to subscribe to.
/// </summary>
internal void Register(string directory, string filter);
void Initialize();
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Text;
using System.Text.Json.Serialization;
using JetBrains.Annotations;
@@ -65,8 +64,6 @@ public readonly struct ResPath : IEquatable<ResPath>
public ResPath(string canonPath)
{
// Paths should never have non-standardised directory separators passed in, the caller should have already sanitised it.
DebugTools.Assert(!canonPath.Contains('\\'));
CanonPath = canonPath;
}

View File

@@ -4,7 +4,6 @@ using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
@@ -45,13 +44,10 @@ public sealed partial class EntitySaveTestComponent : Component
/// <summary>
/// Dummy tile definition for serializing grids.
/// </summary>
[Prototype("testTileDef")]
public sealed class TileDef(string id) : ITileDefinition
{
public ushort TileId { get; set; }
public string Name => id;
[IdDataField]
public string ID => id;
public ResPath? Sprite => null;
public Dictionary<Direction, ResPath> EdgeSprites => new();

View File

@@ -112,46 +112,6 @@ public sealed partial class EntityManagerCopyTests
Assert.That(!ReferenceEquals(comp2, targetComp2));
}
[Test]
public void CopyComponentMultipleViaTry()
{
var instant = RobustServerSimulation.NewSimulation();
instant.RegisterComponents(fac =>
{
fac.RegisterClass<AComponent>();
fac.RegisterClass<BComponent>();
});
var sim = instant.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapSystem = entManager.System<SharedMapSystem>();
mapSystem.CreateMap(out var mapId);
var original = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
var comp = entManager.AddComponent<AComponent>(original);
var comp2 = entManager.AddComponent<BComponent>(original);
Assert.That(comp.Value, Is.EqualTo(false));
comp.Value = true;
var target = entManager.Spawn(null, new MapCoordinates(Vector2.Zero, mapId));
Assert.That(!entManager.HasComponent<AComponent>(target));
entManager.TryCopyComponents(original, target, null, comp.GetType(), comp2.GetType());
var targetComp = entManager.GetComponent<AComponent>(target);
var targetComp2 = entManager.GetComponent<BComponent>(target);
Assert.That(targetComp!.Owner == target);
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
Assert.That(targetComp2!.Owner == target);
Assert.That(targetComp2.Value, Is.EqualTo(comp2.Value));
Assert.That(!ReferenceEquals(comp, targetComp));
Assert.That(!ReferenceEquals(comp2, targetComp2));
}
[DataDefinition]
private sealed partial class AComponent : Component
{

View File

@@ -40,7 +40,7 @@ namespace Robust.UnitTesting.Shared.Physics
for (var i = 0; i < aabbs1.Length; ++i)
{
dt.CreateProxy(aabbs1[i], uint.MaxValue, i);
dt.CreateProxy(aabbs1[i], i);
}
var point = new Vector2(0, 0);

View File

@@ -35,7 +35,7 @@ internal sealed class PhysicsHull_Test
[Test, TestCaseSource(nameof(CollinearHulls))]
public void CollinearTest(Vector2[] vertices, int count)
{
var hull = InternalPhysicsHull.ComputeHull(vertices.AsSpan(), vertices.Length);
var hull = PhysicsHull.ComputeHull(vertices.AsSpan(), vertices.Length);
Assert.That(hull.Count, Is.EqualTo(count));
}
@@ -89,7 +89,7 @@ internal sealed class PhysicsHull_Test
[Test, TestCaseSource(nameof(ValidateHulls))]
public void ValidationTest(Vector2[] vertices, bool result)
{
var hull = new InternalPhysicsHull(vertices.AsSpan(), vertices.Length);
Assert.That(InternalPhysicsHull.ValidateHull(hull), Is.EqualTo(result));
var hull = new PhysicsHull(vertices.AsSpan(), vertices.Length);
Assert.That(PhysicsHull.ValidateHull(hull), Is.EqualTo(result));
}
}

View File

@@ -57,12 +57,11 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
var fixtureSystem = entitySystemManager.GetEntitySystem<FixtureSystem>();
var physSystem = entitySystemManager.GetEntitySystem<SharedPhysicsSystem>();
var gravSystem = entitySystemManager.GetEntitySystem<Gravity2DController>();
var transformSystem = entitySystemManager.GetEntitySystem<SharedTransformSystem>();
MapId mapId;
const int columnCount = 1;
const int rowCount = 15;
Entity<PhysicsComponent>[] bodies = new Entity<PhysicsComponent>[columnCount * rowCount];
PhysicsComponent[] bodies = new PhysicsComponent[columnCount * rowCount];
Vector2 firstPos = Vector2.Zero;
await server.WaitPost(() =>
@@ -111,12 +110,12 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
fixtureSystem.CreateFixture(boxUid, "fix1", new Fixture(poly, 1, 1, true), manager: manager, body: box);
physSystem.WakeBody(boxUid, manager: manager, body: box);
bodies[j * rowCount + i] = (boxUid, box);
bodies[j * rowCount + i] = box;
}
}
var bodyOne = bodies[0].Owner;
firstPos = transformSystem.GetWorldPosition(bodyOne);
firstPos = entityManager.GetComponent<TransformComponent>(bodyOne).WorldPosition;
});
await server.WaitRunTicks(1);
@@ -125,7 +124,7 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
await server.WaitAssertion(() =>
{
var tempQualifier = bodies[0].Owner;
Assert.That(firstPos, Is.Not.EqualTo(transformSystem.GetWorldPosition(tempQualifier)));
Assert.That(firstPos, Is.Not.EqualTo(entityManager.GetComponent<TransformComponent>(tempQualifier).WorldPosition));
});
// Assert
@@ -140,12 +139,12 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
for (var i = 0; i < bodies.Length; i++)
{
var body = bodies[j * columnCount + i];
var worldPos = transformSystem.GetWorldPosition(body);
var worldPos = entityManager.GetComponent<TransformComponent>(body.Owner).WorldPosition;
// TODO: Multi-column support but I cbf right now
// Can't be more exact as some level of sinking is allowed.
Assert.That(worldPos.EqualsApprox(new Vector2(0.0f, i + 0.5f), 0.2f), $"Expected y-value of {i + 0.5f} but found {worldPos.Y}");
Assert.That(!body.Comp.Awake, $"Body {i} wasn't asleep");
Assert.That(!body.Awake, $"Body {i} wasn't asleep");
}
}
});
@@ -163,12 +162,11 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
var fixtureSystem = entitySystemManager.GetEntitySystem<FixtureSystem>();
var physSystem = entitySystemManager.GetEntitySystem<SharedPhysicsSystem>();
var gravSystem = entitySystemManager.GetEntitySystem<Gravity2DController>();
var transformSystem = entitySystemManager.GetEntitySystem<SharedTransformSystem>();
MapId mapId;
var columnCount = 1;
var rowCount = 15;
Entity<PhysicsComponent>[] bodies = new Entity<PhysicsComponent>[columnCount * rowCount];
PhysicsComponent[] bodies = new PhysicsComponent[columnCount * rowCount];
Vector2 firstPos = Vector2.Zero;
await server.WaitPost(() =>
@@ -212,12 +210,12 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
fixtureSystem.CreateFixture(circleUid, "fix1", new Fixture(shape, 1, 1, true), manager: manager, body: circle);
physSystem.WakeBody(circleUid, manager: manager, body: circle);
bodies[j * rowCount + i] = (circleUid, circle);
bodies[j * rowCount + i] = circle;
}
}
EntityUid tempQualifier3 = bodies[0].Owner;
firstPos = transformSystem.GetWorldPosition(tempQualifier3);
firstPos = entityManager.GetComponent<TransformComponent>(tempQualifier3).WorldPosition;
});
await server.WaitRunTicks(1);
@@ -226,7 +224,7 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
await server.WaitAssertion(() =>
{
EntityUid tempQualifier = bodies[0].Owner;
Assert.That(firstPos, Is.Not.EqualTo(transformSystem.GetWorldPosition(tempQualifier)));
Assert.That(firstPos, Is.Not.EqualTo(entityManager.GetComponent<TransformComponent>(tempQualifier).WorldPosition));
});
// Assert
@@ -241,14 +239,14 @@ public sealed class PhysicsTestBedTest : RobustIntegrationTest
for (var i = 0; i < bodies.Length; i++)
{
var body = bodies[j * columnCount + i];
var worldPos = transformSystem.GetWorldPosition(body);
var worldPos = entityManager.GetComponent<TransformComponent>(body.Owner).WorldPosition;
var expectedY = 0.5f + i;
// TODO: Multi-column support but I cbf right now
// Can't be more exact as some level of sinking is allowed.
Assert.That(worldPos.EqualsApproxPercent(new Vector2(0.0f, expectedY), 0.1f), $"Expected y-value of {expectedY} but found {worldPos.Y}");
Assert.That(!body.Comp.Awake);
Assert.That(!body.Awake);
}
}
});

View File

@@ -24,7 +24,7 @@ namespace Robust.UnitTesting.Shared.Resources
_testDir = Directory.CreateDirectory(_testDirPath);
var subDir = Path.Combine(_testDirPath, "writable");
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir));
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir), hideRootDir: false);
}
[OneTimeTearDown]