mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
47 Commits
v247.0.2
...
v248.0.2-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ae65f9e3d | ||
|
|
af6fc51def | ||
|
|
139b6f796c | ||
|
|
2ee7c35fd3 | ||
|
|
56eae5ad08 | ||
|
|
0662cae224 | ||
|
|
0e2b00edd0 | ||
|
|
d48f7ecb5b | ||
|
|
e064b7a4f9 | ||
|
|
654480862e | ||
|
|
353c044b52 | ||
|
|
3dda8d9e93 | ||
|
|
41ea10083d | ||
|
|
6290bb7af1 | ||
|
|
348ab70a8d | ||
|
|
47e11e988c | ||
|
|
c459b55052 | ||
|
|
f10e96a6d1 | ||
|
|
e8bac558c6 | ||
|
|
ffa3bb7202 | ||
|
|
9bcdc95651 | ||
|
|
543088ea1f | ||
|
|
56daa63783 | ||
|
|
9fe9730d4a | ||
|
|
a1a7ea92d9 | ||
|
|
0dec6a425f | ||
|
|
56ced913b7 | ||
|
|
76b46479b6 | ||
|
|
de9a8d286a | ||
|
|
a1df0fb4af | ||
|
|
e6bc5a1057 | ||
|
|
11b24579a2 | ||
|
|
685d002bb7 | ||
|
|
2e0d18aeaf | ||
|
|
06dbff0429 | ||
|
|
15958a9447 | ||
|
|
fd5a4d9b8a | ||
|
|
6d958847cb | ||
|
|
8a04a4f3a5 | ||
|
|
7104a4f459 | ||
|
|
f29949a32c | ||
|
|
3bbbabf238 | ||
|
|
d95aca3d9e | ||
|
|
e14537074e | ||
|
|
af2d01981f | ||
|
|
7df23e047c | ||
|
|
5c7ab43049 |
@@ -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.6" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<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" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,10 +54,103 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 247.0.2
|
||||
## 248.0.2-source-gen-debug
|
||||
|
||||
|
||||
## 247.0.1
|
||||
## 248.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Don't throw in overlay rendering if MapUid not found.
|
||||
|
||||
### Internal
|
||||
|
||||
* Reduce EntityManager.IsDefault allocations.
|
||||
|
||||
|
||||
## 248.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Bump ImageSharp version.
|
||||
* Fix instances of NaN gain for audio where a negative-infinity value is being used for volume.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
### 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.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added functions for copying components to `IEntityManager` and `EntitySystem`.
|
||||
* Sound played from sound collections is now sent as "collection ID + index" over the network instead of the final filename.
|
||||
* This enables integration of future accessibility systems.
|
||||
* Added a new `ResolvedSoundSpecifier` to represent played sounds. Methods that previously took a filename now take a `ResolvedSoundSpecifier`, with an implicit cast from string being interpreted as a raw filename.
|
||||
* `VisibilitySystem` has been made accessible to shared as `SharedVisibilitySystem`.
|
||||
* `ScrollContainer` now has properties exposing `Value` and `ValueTarget` on its internal scroll bars.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix prototype hot reload crashing when adding a new component already exists on an entity.
|
||||
* Fix maps failing to save in some cases related to tilemap IDs.
|
||||
* Fix `Regex.Escape(string)` not being available in sandbox.
|
||||
* Prototypes that parent themselves directly won't cause the game to hang on an infinite loop anymore.
|
||||
* Fixed disconnecting during a connection attempt leaving the client stuck in a phantom state.
|
||||
|
||||
### Internal
|
||||
|
||||
* More warning cleanup.
|
||||
|
||||
## 247.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added support for `Color[]` shader uniforms
|
||||
* Added optional minimumDistance parameter to `SharedJointSystem.CreateDistanceJoint()`
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `EntitySystem.DirtyFields()` not actually marking fields as dirty.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated the Yamale map file format validator to support v7 map/grid files.
|
||||
|
||||
|
||||
## 247.0.0
|
||||
@@ -70,7 +163,7 @@ END TEMPLATE-->
|
||||
when warnings are logged.
|
||||
* The minimum supported map format / version has been increased from 2 to 3.
|
||||
* The server-side `MapLoaderSystem` and associated classes & structs has been moved to `Robust.Shared`, and has been significantly modified.
|
||||
* The`TryLoad` and `Save` methods have been replaced with grid, map, generic entity variants. I.e, `SaveGrid`, `SaveMap`, and `SaveEntities`.
|
||||
* The `TryLoad` and `Save` methods have been replaced with grid, map, generic entity variants. I.e, `SaveGrid`, `SaveMap`, and `SaveEntities`.
|
||||
* Most of the serialization logic and methods have been moved out of `MapLoaderSystem` and into new `EntitySerializer`
|
||||
and `EntityDeserializer` classes, which also replace the old `MapSerializationContext`.
|
||||
* The `MapLoadOptions` class has been split into `MapLoadOptions`, `SerializationOptions`, and `DeserializationOptions`
|
||||
|
||||
@@ -156,6 +156,7 @@ 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]
|
||||
@@ -293,7 +294,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> [initialize]
|
||||
cmd-addmap-help = addmap <mapID> [pre-init]
|
||||
|
||||
cmd-rmmap-desc = Removes a map from the world. You cannot remove nullspace.
|
||||
cmd-rmmap-help = rmmap <mapId>
|
||||
|
||||
@@ -136,6 +136,7 @@ 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]
|
||||
|
||||
@@ -26,10 +26,10 @@ public class PhysicsTumblerBenchmark
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var mapUid = entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 800; i++)
|
||||
for (var i = 0; i < 300; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
@@ -42,6 +42,9 @@ 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]
|
||||
@@ -49,7 +52,7 @@ public class PhysicsTumblerBenchmark
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 5000; i++)
|
||||
for (var i = 0; i < 1000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
internal static class Program
|
||||
public 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)
|
||||
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
@@ -25,7 +24,6 @@ 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!;
|
||||
@@ -63,10 +61,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -40,11 +40,7 @@ namespace Robust.Client.Animations
|
||||
var keyFrame = KeyFrames[keyFrameIndex];
|
||||
|
||||
var audioParams = keyFrame.AudioParamsFunc.Invoke();
|
||||
var audio = new SoundPathSpecifier(keyFrame.Resource)
|
||||
{
|
||||
Params = audioParams
|
||||
};
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().PlayEntity(audio, Filter.Local(), entity, true);
|
||||
IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<AudioSystem>().PlayEntity(keyFrame.Specifier, Filter.Local(), entity, true, audioParams);
|
||||
}
|
||||
|
||||
return (keyFrameIndex, playingTime);
|
||||
@@ -55,7 +51,7 @@ namespace Robust.Client.Animations
|
||||
/// <summary>
|
||||
/// The RSI state to play when this keyframe gets triggered.
|
||||
/// </summary>
|
||||
public readonly string Resource;
|
||||
public readonly ResolvedSoundSpecifier Specifier;
|
||||
|
||||
/// <summary>
|
||||
/// A function that returns the audio parameter to be used.
|
||||
@@ -69,9 +65,9 @@ namespace Robust.Client.Animations
|
||||
/// </summary>
|
||||
public readonly float KeyTime;
|
||||
|
||||
public KeyFrame(string resource, float keyTime, Func<AudioParams>? audioParams = null)
|
||||
public KeyFrame(ResolvedSoundSpecifier specifier, float keyTime, Func<AudioParams>? audioParams = null)
|
||||
{
|
||||
Resource = resource;
|
||||
Specifier = specifier;
|
||||
KeyTime = keyTime;
|
||||
AudioParamsFunc = audioParams ?? (() => AudioParams.Default);
|
||||
}
|
||||
|
||||
@@ -84,6 +84,19 @@ 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)
|
||||
{
|
||||
@@ -120,9 +133,9 @@ internal partial class AudioManager
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
return new AudioStream(this, buffer, handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -179,9 +192,9 @@ internal partial class AudioManager
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate);
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
return new AudioStream(this, buffer, handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -210,8 +223,8 @@ internal partial class AudioManager
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
return new AudioStream(this, buffer, handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterGain(float newGain)
|
||||
@@ -293,7 +306,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[(int) stream.ClydeHandle!.Value].BufferHandle);
|
||||
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[stream.BufferId].BufferHandle);
|
||||
|
||||
var audioSource = new AudioSource(this, source, stream);
|
||||
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
|
||||
@@ -370,5 +383,12 @@ internal partial class AudioManager
|
||||
}
|
||||
|
||||
_bufferedAudioSources.Clear();
|
||||
|
||||
foreach (var buffer in _audioSampleBuffers.Values)
|
||||
{
|
||||
DeleteAudioBufferOnMainThread(buffer.BufferHandle);
|
||||
}
|
||||
|
||||
_audioSampleBuffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -17,13 +18,15 @@ 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 List<LoadedAudioSample> _audioSampleBuffers = new();
|
||||
private readonly Dictionary<int, LoadedAudioSample> _audioSampleBuffers = new();
|
||||
|
||||
private readonly Dictionary<int, WeakReference<BaseAudioSource>> _audioSources =
|
||||
new();
|
||||
@@ -116,6 +119,22 @@ 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()
|
||||
@@ -140,6 +159,11 @@ 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>
|
||||
|
||||
@@ -6,8 +6,15 @@ 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
|
||||
public sealed class AudioStream : IDisposable
|
||||
{
|
||||
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; }
|
||||
@@ -15,8 +22,10 @@ public sealed class AudioStream
|
||||
public string? Artist { get; }
|
||||
public int ChannelCount { get; }
|
||||
|
||||
internal AudioStream(IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
internal AudioStream(IAudioInternal internalAudio, int bufferId, 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;
|
||||
@@ -24,4 +33,9 @@ public sealed class AudioStream
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_audio.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -56,6 +57,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
private float _zOffset;
|
||||
private float _audioEndBuffer;
|
||||
|
||||
public override float ZOffset
|
||||
{
|
||||
@@ -79,8 +82,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
}
|
||||
|
||||
private float _zOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -108,20 +109,31 @@ 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(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
private void OnAudioState(Entity<AudioComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
var component = entity.Comp;
|
||||
|
||||
if (component.LifeStage < ComponentLifeStage.Initialized)
|
||||
return;
|
||||
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Source.Global = component.Global;
|
||||
|
||||
@@ -145,21 +157,29 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
case AudioState.Stopped:
|
||||
component.StopPlaying();
|
||||
component.PlaybackPosition = 0f;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
// If playback position changed then update it.
|
||||
if (!string.IsNullOrEmpty(component.FileName))
|
||||
{
|
||||
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
|
||||
var currentPosition = component.Source.PlaybackPosition;
|
||||
var diff = Math.Abs(position - currentPosition);
|
||||
var position = (float) ((entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart).TotalSeconds;
|
||||
var currentPosition = entity.Comp.Source.PlaybackPosition;
|
||||
var diff = Math.Abs(position - currentPosition);
|
||||
|
||||
if (diff > 0.1f)
|
||||
// Don't try to set the audio too far ahead.
|
||||
if (!string.IsNullOrEmpty(entity.Comp.FileName))
|
||||
{
|
||||
if (position > GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds - _audioEndBuffer)
|
||||
{
|
||||
component.PlaybackPosition = position;
|
||||
entity.Comp.StopPlaying();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If the difference is minor then we'll just keep playing it.
|
||||
if (diff > 0.1f)
|
||||
{
|
||||
entity.Comp.PlaybackPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,6 +227,10 @@ 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))
|
||||
{
|
||||
@@ -230,10 +254,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
@@ -415,6 +446,16 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return occlusion;
|
||||
}
|
||||
|
||||
private bool TryGetAudio(ResolvedSoundSpecifier specifier, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
var filename = GetAudioPath(specifier);
|
||||
if (_resourceCache.TryGetResource(new ResPath(filename), out audio))
|
||||
return true;
|
||||
|
||||
Log.Error($"Server tried to play audio file {filename} which does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetAudio(string filename, [NotNullWhen(true)] out AudioResource? audio)
|
||||
{
|
||||
if (_resourceCache.TryGetResource(new ResPath(filename), out audio))
|
||||
@@ -433,15 +474,15 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams);
|
||||
return PlayStatic(specifier, Filter.Local(), coordinates, true, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
|
||||
return PlayEntity(specifier, Filter.Local(), uid, true, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -477,21 +518,21 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Specifier = specifier,
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayGlobal(audio, audioParams) : default;
|
||||
return TryGetAudio(specifier, out var audio) ? PlayGlobal(audio, specifier, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -499,9 +540,9 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayGlobal(AudioStream stream, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null)
|
||||
{
|
||||
var (entity, component) = CreateAndStartPlayingStream(audioParams, stream);
|
||||
var (entity, component) = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
component.Global = true;
|
||||
component.Source.Global = true;
|
||||
DirtyField(entity, component, nameof(AudioComponent.Global));
|
||||
@@ -513,22 +554,22 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Specifier = specifier,
|
||||
NetEntity = GetNetEntity(entity),
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayEntity(audio, entity, audioParams) : default;
|
||||
return TryGetAudio(specifier, out var audio) ? PlayEntity(audio, entity, specifier, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -537,7 +578,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
@@ -545,7 +586,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
return playing;
|
||||
@@ -557,22 +598,22 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
|
||||
{
|
||||
FileName = filename,
|
||||
Specifier = specifier,
|
||||
Coordinates = GetNetCoordinates(coordinates),
|
||||
AudioParams = audioParams ?? AudioParams.Default
|
||||
});
|
||||
}
|
||||
|
||||
return TryGetAudio(filename, out var audio) ? PlayStatic(audio, coordinates, audioParams) : default;
|
||||
return TryGetAudio(specifier, out var audio) ? PlayStatic(audio, coordinates, specifier, audioParams) : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -581,7 +622,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="stream">The audio stream to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, ResolvedSoundSpecifier? specifier, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
@@ -589,33 +630,33 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
var playing = CreateAndStartPlayingStream(audioParams, specifier, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
return playing;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
return PlayGlobal(specifier, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, entity, audioParams);
|
||||
return PlayEntity(specifier, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
return PlayStatic(specifier, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
return PlayGlobal(specifier, audioParams);
|
||||
}
|
||||
|
||||
public override void LoadStream<T>(Entity<AudioComponent> entity, T stream)
|
||||
@@ -629,39 +670,39 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
return PlayGlobal(specifier, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
return PlayEntity(specifier, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
return PlayEntity(specifier, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
return PlayStatic(specifier, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
return PlayStatic(specifier, coordinates, audioParams);
|
||||
}
|
||||
|
||||
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, AudioStream stream)
|
||||
private (EntityUid Entity, AudioComponent Component) CreateAndStartPlayingStream(AudioParams? audioParams, ResolvedSoundSpecifier? specifier, AudioStream stream)
|
||||
{
|
||||
var audioP = audioParams ?? AudioParams.Default;
|
||||
var entity = SetupAudio(null, audioP, initialize: false, length: stream.Length);
|
||||
var entity = SetupAudio(specifier, audioP, initialize: false, length: stream.Length);
|
||||
LoadStream(entity, stream);
|
||||
EntityManager.InitializeAndStartEntity(entity);
|
||||
var comp = entity.Comp;
|
||||
@@ -694,17 +735,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
private void OnEntityCoordinates(PlayAudioPositionalMessage ev)
|
||||
{
|
||||
PlayStatic(ev.FileName, GetCoordinates(ev.Coordinates), ev.AudioParams, false);
|
||||
PlayStatic(ev.Specifier, GetCoordinates(ev.Coordinates), ev.AudioParams, false);
|
||||
}
|
||||
|
||||
private void OnEntityAudio(PlayAudioEntityMessage ev)
|
||||
{
|
||||
PlayEntity(ev.FileName, GetEntity(ev.NetEntity), ev.AudioParams, false);
|
||||
PlayEntity(ev.Specifier, GetEntity(ev.NetEntity), ev.AudioParams, false);
|
||||
}
|
||||
|
||||
private void OnGlobalAudio(PlayAudioGlobalMessage ev)
|
||||
{
|
||||
PlayGlobal(ev.FileName, ev.AudioParams, false);
|
||||
PlayGlobal(ev.Specifier, ev.AudioParams, false);
|
||||
}
|
||||
|
||||
protected override TimeSpan GetAudioLengthImpl(string filename)
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Robust.Client.Audio;
|
||||
/// </summary>
|
||||
internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
{
|
||||
private int _audioBuffer;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void InitializePostWindowing()
|
||||
{
|
||||
@@ -65,6 +67,11 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(AudioStream stream)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopAllAudio()
|
||||
{
|
||||
@@ -101,11 +108,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(null, length, channels, name);
|
||||
return new AudioStream(this, _audioBuffer++, null, length, channels, name);
|
||||
}
|
||||
|
||||
private static AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
|
||||
private AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
|
||||
{
|
||||
return new AudioStream(null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
|
||||
return new AudioStream(this, _audioBuffer++, null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ internal interface IAudioInternal : IAudioManager
|
||||
|
||||
void SetAttenuation(Attenuation attenuation);
|
||||
|
||||
void Remove(AudioStream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Stops all audio from playing.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
@@ -11,7 +10,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);
|
||||
|
||||
@@ -13,7 +13,7 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
/// <summary>
|
||||
/// Underlying stream to the audio.
|
||||
/// </summary>
|
||||
private readonly AudioStream _sourceStream;
|
||||
internal 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
|
||||
|
||||
|
||||
@@ -208,6 +208,12 @@ 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)
|
||||
|
||||
@@ -115,10 +115,6 @@ namespace Robust.Client
|
||||
/// <inheritdoc />
|
||||
public void DisconnectFromServer(string reason)
|
||||
{
|
||||
DebugTools.Assert(RunLevel > ClientRunLevel.Initialize);
|
||||
DebugTools.Assert(_net.IsConnected);
|
||||
// run level changed in OnNetDisconnect()
|
||||
// are both of these *really* needed?
|
||||
_net.ClientDisconnect(reason);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ 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
|
||||
@@ -102,6 +103,7 @@ namespace Robust.Client
|
||||
deps.Register<ProfViewManager>();
|
||||
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
|
||||
deps.Register<NetworkResourceManager>();
|
||||
deps.Register<IReloadManager, ReloadManager>();
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
|
||||
@@ -93,6 +93,7 @@ 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;
|
||||
|
||||
@@ -185,6 +186,7 @@ namespace Robust.Client
|
||||
// before prototype load.
|
||||
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
|
||||
|
||||
_reload.Initialize();
|
||||
_reflectionManager.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
@@ -382,7 +384,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -25,12 +25,6 @@ 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)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class VisibilitySystem : SharedVisibilitySystem
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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), _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldBox, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapSystem.GetMapOrInvalid(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, _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldAABB, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapSystem.GetMapOrInvalid(mapId), mapId, worldAABB, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
|
||||
@@ -523,6 +523,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case Color color:
|
||||
program.SetUniform(name, color);
|
||||
break;
|
||||
case Color[] colorArr:
|
||||
program.SetUniform(name, colorArr);
|
||||
break;
|
||||
case int i:
|
||||
program.SetUniform(name, i);
|
||||
break;
|
||||
|
||||
@@ -485,6 +485,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Color[] value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.ParametersDirty = true;
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, int value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
|
||||
@@ -10,6 +10,7 @@ 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;
|
||||
@@ -23,6 +24,7 @@ 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
|
||||
@@ -48,6 +50,8 @@ 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!;
|
||||
@@ -99,6 +103,16 @@ 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);
|
||||
@@ -122,6 +136,38 @@ 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;
|
||||
|
||||
@@ -369,6 +369,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, Color[] value)
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, int value)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -334,7 +334,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, in Color color, bool convertToLinear=true)
|
||||
private void SetUniformDirect(int slot, in Color color, bool convertToLinear = true)
|
||||
{
|
||||
var converted = color;
|
||||
if (convertToLinear)
|
||||
@@ -349,6 +349,39 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, Color[] colors)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, colors);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetUniformDirect(int slot, Color[] colors, bool convertToLinear = true)
|
||||
{
|
||||
scoped Span<Color> colorsToPass;
|
||||
if (convertToLinear)
|
||||
{
|
||||
colorsToPass = stackalloc Color[colors.Length];
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
colorsToPass[i] = Color.FromSrgb(colors[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
colorsToPass = colors;
|
||||
}
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (Color* ptr = &colorsToPass[0])
|
||||
{
|
||||
GL.Uniform4(slot, colorsToPass.Length, (float*)ptr);
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Vector3 vector)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
|
||||
@@ -221,11 +221,12 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public static bool TypeSupportsArrays(this ShaderDataType type)
|
||||
{
|
||||
// TODO: add support for int, and vec3/4 arrays
|
||||
// TODO: add support for int, and vec3 arrays
|
||||
return
|
||||
(type == ShaderDataType.Float) ||
|
||||
(type == ShaderDataType.Vec2) ||
|
||||
(type == ShaderDataType.Bool);
|
||||
(type == ShaderDataType.Bool) ||
|
||||
(type == ShaderDataType.Vec4);
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
|
||||
@@ -113,6 +113,13 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, Color[] value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, Vector4 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
@@ -223,6 +230,7 @@ namespace Robust.Client.Graphics
|
||||
private protected abstract void SetParameterImpl(string name, Vector3 value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector4 value);
|
||||
private protected abstract void SetParameterImpl(string name, Color value);
|
||||
private protected abstract void SetParameterImpl(string name, Color[] value);
|
||||
private protected abstract void SetParameterImpl(string name, int value);
|
||||
private protected abstract void SetParameterImpl(string name, Vector2i value);
|
||||
private protected abstract void SetParameterImpl(string name, bool value);
|
||||
|
||||
@@ -1,20 +1,15 @@
|
||||
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.Toolshed;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
@@ -23,6 +18,8 @@ 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!;
|
||||
|
||||
@@ -57,6 +54,30 @@ 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace Robust.Client.Placement
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
|
||||
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
|
||||
var (_, (x, y)) = transformSys.ToCoordinates(pManager.StartPoint.EntityId, mousePos) - pManager.StartPoint;
|
||||
float iterations;
|
||||
Vector2 distance;
|
||||
if (Math.Abs(x) > Math.Abs(y))
|
||||
@@ -176,7 +176,7 @@ namespace Robust.Client.Placement
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
|
||||
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
|
||||
var placementdiff = transformSys.ToCoordinates(pManager.StartPoint.EntityId, mousePos) - pManager.StartPoint;
|
||||
|
||||
var xSign = Math.Sign(placementdiff.X);
|
||||
var ySign = Math.Sign(placementdiff.Y);
|
||||
@@ -264,13 +264,15 @@ namespace Robust.Client.Placement
|
||||
protected EntityCoordinates ScreenToCursorGrid(ScreenCoordinates coords)
|
||||
{
|
||||
var mapCoords = pManager.EyeManager.PixelToMap(coords.Position);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (!pManager.MapManager.TryFindGridAt(mapCoords, out var gridUid, out var grid))
|
||||
{
|
||||
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
|
||||
|
||||
return transformSys.ToCoordinates(mapCoords);
|
||||
}
|
||||
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
return EntityCoordinates.FromMap(gridUid, mapCoords, transformSys, pManager.EntityManager);
|
||||
return transformSys.ToCoordinates(gridUid, mapCoords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,23 @@
|
||||
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.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Client.Utility;
|
||||
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!;
|
||||
|
||||
private readonly List<FileSystemWatcher> _watchers = new();
|
||||
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
|
||||
private CancellationTokenSource _reloadToken = new();
|
||||
private readonly HashSet<ResPath> _reloadQueue = new();
|
||||
[Dependency] private readonly IReloadManager _reload = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -37,9 +25,8 @@ namespace Robust.Client.Prototypes
|
||||
|
||||
_netManager.RegisterNetMessage<MsgReloadPrototypes>(accept: NetMessageAccept.Server);
|
||||
|
||||
_clyde.OnWindowFocused += WindowFocusedChanged;
|
||||
|
||||
WatchResources();
|
||||
_reload.Register("/Prototypes", "*.yml");
|
||||
_reload.OnChanged += ReloadPrototypeQueue;
|
||||
}
|
||||
|
||||
public override void LoadDefaultPrototypes(Dictionary<Type, HashSet<string>>? changed = null)
|
||||
@@ -49,99 +36,27 @@ namespace Robust.Client.Prototypes
|
||||
ResolveResults();
|
||||
}
|
||||
|
||||
private void WindowFocusedChanged(WindowFocusedEventArgs args)
|
||||
private void ReloadPrototypeQueue(ResPath file)
|
||||
{
|
||||
#if TOOLS
|
||||
if (args.Focused && _reloadQueue.Count > 0)
|
||||
{
|
||||
Timer.Spawn(_reloadDelay, ReloadPrototypeQueue, _reloadToken.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
_reloadToken.Cancel();
|
||||
_reloadToken = new CancellationTokenSource();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (file.Extension != "yml")
|
||||
return;
|
||||
|
||||
private void ReloadPrototypeQueue()
|
||||
{
|
||||
#if TOOLS
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
var msg = new MsgReloadPrototypes();
|
||||
msg.Paths = _reloadQueue.ToArray();
|
||||
var msg = new MsgReloadPrototypes
|
||||
{
|
||||
Paths = [file]
|
||||
};
|
||||
_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(_reloadQueue);
|
||||
|
||||
_reloadQueue.Clear();
|
||||
ReloadPrototypes([file]);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -26,22 +27,26 @@ public sealed class AudioResource : BaseResource
|
||||
throw new FileNotFoundException("Content file does not exist for audio sample.");
|
||||
}
|
||||
|
||||
using (var fileStream = cache.ContentFileRead(path))
|
||||
using var fileStream = cache.ContentFileRead(path);
|
||||
var audioManager = dependencies.Resolve<IAudioInternal>();
|
||||
if (path.Extension == "ogg")
|
||||
{
|
||||
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");
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default)
|
||||
{
|
||||
dependencies.Resolve<IAudioInternal>().Remove(AudioStream);
|
||||
Load(dependencies, path);
|
||||
}
|
||||
|
||||
public AudioResource(AudioStream stream) : base()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
@@ -176,6 +176,71 @@ 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]
|
||||
|
||||
@@ -21,7 +21,6 @@ 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!;
|
||||
|
||||
|
||||
@@ -28,6 +28,30 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public int ScrollSpeedX { get; set; } = 50;
|
||||
public int ScrollSpeedY { get; set; } = 50;
|
||||
|
||||
public float VScroll
|
||||
{
|
||||
get => _vScrollBar.Value;
|
||||
set => _vScrollBar.Value = value;
|
||||
}
|
||||
|
||||
public float VScrollTarget
|
||||
{
|
||||
get => _vScrollBar.ValueTarget;
|
||||
set => _vScrollBar.ValueTarget = value;
|
||||
}
|
||||
|
||||
public float HScroll
|
||||
{
|
||||
get => _hScrollBar.Value;
|
||||
set => _hScrollBar.Value = value;
|
||||
}
|
||||
|
||||
public float HScrollTarget
|
||||
{
|
||||
get => _hScrollBar.ValueTarget;
|
||||
set => _hScrollBar.ValueTarget = value;
|
||||
}
|
||||
|
||||
private bool _reserveScrollbarSpace;
|
||||
public bool ReserveScrollbarSpace
|
||||
{
|
||||
|
||||
@@ -38,7 +38,6 @@ 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";
|
||||
|
||||
141
Robust.Client/Utility/ReloadManager.cs
Normal file
141
Robust.Client/Utility/ReloadManager.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -19,112 +19,117 @@ public class Generator : IIncrementalGenerator
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext initContext)
|
||||
{
|
||||
IncrementalValuesProvider<(string name, string code)?> dataDefinitions = initContext.SyntaxProvider
|
||||
.CreateSyntaxProvider(
|
||||
static (node, _) => node is TypeDeclarationSyntax,
|
||||
static (context, _) =>
|
||||
{
|
||||
var type = (TypeDeclarationSyntax)context.Node;
|
||||
var symbol = (ITypeSymbol)context.SemanticModel.GetDeclaredSymbol(type)!;
|
||||
if (!IsDataDefinition(symbol))
|
||||
return null;
|
||||
|
||||
return GenerateForDataDefinition(type, symbol);
|
||||
}
|
||||
)
|
||||
.Where(static type => type != null);
|
||||
IncrementalValuesProvider<TypeDeclarationSyntax> dataDefinitions = initContext.SyntaxProvider.CreateSyntaxProvider(
|
||||
static (node, _) => node is TypeDeclarationSyntax,
|
||||
static (context, _) =>
|
||||
{
|
||||
var type = (TypeDeclarationSyntax) context.Node;
|
||||
var symbol = (ITypeSymbol) context.SemanticModel.GetDeclaredSymbol(type)!;
|
||||
return IsDataDefinition(symbol) ? type : null;
|
||||
}
|
||||
).Where(static type => type != null)!;
|
||||
|
||||
var comparer = new DataDefinitionComparer();
|
||||
initContext.RegisterSourceOutput(
|
||||
dataDefinitions,
|
||||
initContext.CompilationProvider.Combine(dataDefinitions.WithComparer(comparer).Collect()),
|
||||
static (sourceContext, source) =>
|
||||
{
|
||||
// TODO: deduplicate based on name?
|
||||
var (name, code) = source!.Value;
|
||||
var (compilation, declarations) = source;
|
||||
var builder = new StringBuilder();
|
||||
var containingTypes = new Stack<INamedTypeSymbol>();
|
||||
var declarationsGenerated = new HashSet<string>();
|
||||
var deltaType = compilation.GetTypeByMetadataName(ComponentDeltaInterfaceName)!;
|
||||
|
||||
sourceContext.AddSource(name, code);
|
||||
foreach (var declaration in declarations)
|
||||
{
|
||||
builder.Clear();
|
||||
containingTypes.Clear();
|
||||
|
||||
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
|
||||
|
||||
var symbolName = type
|
||||
.ToDisplayString()
|
||||
.Replace('<', '{')
|
||||
.Replace('>', '}');
|
||||
|
||||
if (!declarationsGenerated.Add(symbolName))
|
||||
continue;
|
||||
|
||||
var nonPartial = !IsPartial(declaration);
|
||||
|
||||
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty
|
||||
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
|
||||
|
||||
var containingType = type.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
containingTypes.Push(containingType);
|
||||
containingType = containingType.ContainingType;
|
||||
}
|
||||
|
||||
var containingTypesStart = new StringBuilder();
|
||||
var containingTypesEnd = new StringBuilder();
|
||||
foreach (var parent in containingTypes)
|
||||
{
|
||||
var syntax = (ClassDeclarationSyntax) parent.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (!IsPartial(syntax))
|
||||
{
|
||||
nonPartial = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
|
||||
containingTypesEnd.AppendLine("}");
|
||||
}
|
||||
|
||||
var definition = GetDataDefinition(type);
|
||||
if (nonPartial || definition.InvalidFields)
|
||||
continue;
|
||||
|
||||
builder.AppendLine($$"""
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Exceptions;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
#pragma warning disable RA0002 // Robust access analyzer
|
||||
|
||||
{{namespaceString}}
|
||||
|
||||
{{containingTypesStart}}
|
||||
|
||||
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
|
||||
{
|
||||
{{GetConstructor(definition)}}
|
||||
|
||||
{{GetCopyMethods(definition, deltaType)}}
|
||||
|
||||
{{GetInstantiators(definition, deltaType)}}
|
||||
}
|
||||
|
||||
{{containingTypesEnd}}
|
||||
""");
|
||||
|
||||
var sourceText = CSharpSyntaxTree
|
||||
.ParseText(builder.ToString())
|
||||
.GetRoot()
|
||||
.NormalizeWhitespace()
|
||||
.ToFullString();
|
||||
|
||||
sourceContext.AddSource($"{symbolName}.g.cs", sourceText);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static (string, string)? GenerateForDataDefinition(
|
||||
TypeDeclarationSyntax declaration,
|
||||
ITypeSymbol type)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var containingTypes = new Stack<INamedTypeSymbol>();
|
||||
containingTypes.Clear();
|
||||
|
||||
var symbolName = type
|
||||
.ToDisplayString()
|
||||
.Replace('<', '{')
|
||||
.Replace('>', '}');
|
||||
|
||||
var nonPartial = !IsPartial(declaration);
|
||||
|
||||
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
|
||||
? string.Empty
|
||||
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
|
||||
|
||||
var containingType = type.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
containingTypes.Push(containingType);
|
||||
containingType = containingType.ContainingType;
|
||||
}
|
||||
|
||||
var containingTypesStart = new StringBuilder();
|
||||
var containingTypesEnd = new StringBuilder();
|
||||
foreach (var parent in containingTypes)
|
||||
{
|
||||
var syntax = (ClassDeclarationSyntax)parent.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (!IsPartial(syntax))
|
||||
{
|
||||
nonPartial = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
|
||||
containingTypesEnd.AppendLine("}");
|
||||
}
|
||||
|
||||
var definition = GetDataDefinition(type);
|
||||
if (nonPartial || definition.InvalidFields)
|
||||
return null;
|
||||
|
||||
builder.AppendLine($$"""
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Exceptions;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
|
||||
#pragma warning disable RA0002 // Robust access analyzer
|
||||
|
||||
{{namespaceString}}
|
||||
|
||||
{{containingTypesStart}}
|
||||
|
||||
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
|
||||
{
|
||||
{{GetConstructor(definition)}}
|
||||
|
||||
{{GetCopyMethods(definition)}}
|
||||
|
||||
{{GetInstantiators(definition)}}
|
||||
}
|
||||
|
||||
{{containingTypesEnd}}
|
||||
""");
|
||||
|
||||
return ($"{symbolName}.g.cs", builder.ToString());
|
||||
}
|
||||
|
||||
private static DataDefinition GetDataDefinition(ITypeSymbol definition)
|
||||
{
|
||||
var fields = new List<DataField>();
|
||||
@@ -191,7 +196,7 @@ public class Generator : IIncrementalGenerator
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetCopyMethods(DataDefinition definition)
|
||||
private static string GetCopyMethods(DataDefinition definition, ITypeSymbol deltaType)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
@@ -262,36 +267,36 @@ public class Generator : IIncrementalGenerator
|
||||
{{baseCopy}}
|
||||
""");
|
||||
|
||||
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true))
|
||||
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true, deltaType))
|
||||
{
|
||||
var interfaceModifiers = baseType != null &&
|
||||
baseType.AllInterfaces.Any(i => i.ToDisplayString() == interfaceName)
|
||||
var interfaceModifiers = baseType != null && baseType.AllInterfaces.Contains(@interface, SymbolEqualityComparer.Default)
|
||||
? "override "
|
||||
: modifiers;
|
||||
var interfaceName = @interface.ToDisplayString();
|
||||
|
||||
builder.AppendLine($$"""
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var def = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref def, serialization, hookCtx, context);
|
||||
target = def;
|
||||
}
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
var def = ({{definition.GenericTypeName}}) target;
|
||||
Copy(ref def, serialization, hookCtx, context);
|
||||
target = def;
|
||||
}
|
||||
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
InternalCopy(ref target, serialization, hookCtx, context);
|
||||
}
|
||||
""");
|
||||
/// <seealso cref="ISerializationManager.CopyTo"/>
|
||||
[Obsolete("Use ISerializationManager.CopyTo instead")]
|
||||
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
InternalCopy(ref target, serialization, hookCtx, context);
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private static string GetInstantiators(DataDefinition definition)
|
||||
private static string GetInstantiators(DataDefinition definition, ITypeSymbol deltaType)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var modifiers = string.Empty;
|
||||
@@ -325,28 +330,27 @@ public class Generator : IIncrementalGenerator
|
||||
""");
|
||||
}
|
||||
|
||||
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false))
|
||||
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false, deltaType))
|
||||
{
|
||||
var interfaceName = @interface.ToDisplayString();
|
||||
builder.AppendLine($$"""
|
||||
{{interfaceName}} {{interfaceName}}.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
{{interfaceName}} {{interfaceName}}.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
|
||||
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
""");
|
||||
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
|
||||
{
|
||||
return Instantiate();
|
||||
}
|
||||
""");
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
|
||||
private static IEnumerable<string> InternalGetImplicitDataDefinitionInterfaces(
|
||||
ITypeSymbol type,
|
||||
bool all)
|
||||
private static IEnumerable<ITypeSymbol> InternalGetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all, ITypeSymbol deltaType)
|
||||
{
|
||||
var symbols = GetImplicitDataDefinitionInterfaces(type, all);
|
||||
|
||||
@@ -364,10 +368,10 @@ public class Generator : IIncrementalGenerator
|
||||
return symbols;
|
||||
}
|
||||
|
||||
if (symbols.Any(x => x == ComponentDeltaInterfaceName))
|
||||
if (symbols.Any(x => x.ToDisplayString() == deltaType.ToDisplayString()))
|
||||
return symbols;
|
||||
|
||||
return symbols.Append(ComponentDeltaInterfaceName);
|
||||
return symbols.Append(deltaType);
|
||||
}
|
||||
|
||||
// TODO serveronly? do we care? who knows!!
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
@@ -94,13 +93,13 @@ internal static class Types
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
|
||||
internal static IEnumerable<ITypeSymbol> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
|
||||
{
|
||||
var interfaces = all ? type.AllInterfaces : type.Interfaces;
|
||||
foreach (var @interface in interfaces)
|
||||
{
|
||||
if (IsImplicitDataDefinitionInterface(@interface))
|
||||
yield return @interface.ToDisplayString();
|
||||
yield return @interface;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -81,27 +81,27 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? specifier, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
var entity = SetupAudio(filename, audioParams);
|
||||
var entity = SetupAudio(specifier, audioParams);
|
||||
AddAudioFilter(entity, entity.Comp, playerFilter);
|
||||
entity.Comp.Global = true;
|
||||
return (entity, entity.Comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(uid))
|
||||
return null;
|
||||
|
||||
var entity = SetupAudio(filename, audioParams);
|
||||
var entity = SetupAudio(specifier, audioParams);
|
||||
// Move it after setting it up
|
||||
XformSystem.SetCoordinates(entity, new EntityCoordinates(uid, Vector2.Zero));
|
||||
|
||||
@@ -115,24 +115,24 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(uid))
|
||||
return null;
|
||||
|
||||
var entity = SetupAudio(filename, audioParams);
|
||||
var entity = SetupAudio(specifier, audioParams);
|
||||
XformSystem.SetCoordinates(entity, new EntityCoordinates(uid, Vector2.Zero));
|
||||
|
||||
return (entity, entity.Comp);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? specifier, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
@@ -144,7 +144,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return null;
|
||||
|
||||
var entity = SetupAudio(filename, audioParams);
|
||||
var entity = SetupAudio(specifier, audioParams);
|
||||
XformSystem.SetCoordinates(entity, coordinates);
|
||||
AddAudioFilter(entity, entity.Comp, playerFilter);
|
||||
|
||||
@@ -152,10 +152,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? specifier, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
if (specifier is null)
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
@@ -168,7 +168,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
|
||||
// TODO: Transform TryFindGridAt mess + optimisation required.
|
||||
var entity = SetupAudio(filename, audioParams);
|
||||
var entity = SetupAudio(specifier, audioParams);
|
||||
XformSystem.SetCoordinates(entity, coordinates);
|
||||
|
||||
return (entity, entity.Comp);
|
||||
@@ -191,7 +191,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
if (sound == null)
|
||||
return null;
|
||||
|
||||
var audio = PlayPvs(GetSound(sound), source, audioParams ?? sound.Params);
|
||||
var audio = PlayPvs(ResolveSound(sound), source, audioParams ?? sound.Params);
|
||||
|
||||
if (audio == null)
|
||||
return null;
|
||||
@@ -206,7 +206,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
if (sound == null)
|
||||
return null;
|
||||
|
||||
var audio = PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params);
|
||||
var audio = PlayPvs(ResolveSound(sound), coordinates, audioParams ?? sound.Params);
|
||||
|
||||
if (audio == null)
|
||||
return null;
|
||||
@@ -215,12 +215,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return audio;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayGlobal(filename, actor.PlayerSession, audioParams);
|
||||
@@ -228,12 +228,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayEntity(filename, actor.PlayerSession, uid, audioParams);
|
||||
@@ -241,12 +241,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams);
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
_resources.Initialize(dataDir);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -43,8 +43,15 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
_ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
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!");
|
||||
}
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
@@ -207,8 +214,15 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-attempt", ("mapId", mapId), ("path", args[1])));
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TrySaveMap(mapId, new ResPath(args[1]));
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
{
|
||||
public sealed class VisibilitySystem : EntitySystem
|
||||
public sealed class VisibilitySystem : SharedVisibilitySystem
|
||||
{
|
||||
[Dependency] private readonly PvsSystem _pvs = default!;
|
||||
[Dependency] private readonly IViewVariablesManager _vvManager = default!;
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Server.GameObjects
|
||||
EntityManager.EntityInitialized -= OnEntityInit;
|
||||
}
|
||||
|
||||
public void AddLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
public override void AddLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
ent.Comp ??= _visibilityQuery.CompOrNull(ent.Owner) ?? AddComp<VisibilityComponent>(ent.Owner);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Server.GameObjects
|
||||
RefreshVisibility(ent);
|
||||
}
|
||||
|
||||
public void RemoveLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
public override void RemoveLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
if (!_visibilityQuery.Resolve(ent.Owner, ref ent.Comp, false))
|
||||
return;
|
||||
@@ -67,7 +67,7 @@ namespace Robust.Server.GameObjects
|
||||
RefreshVisibility(ent);
|
||||
}
|
||||
|
||||
public void SetLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
public override void SetLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
ent.Comp ??= _visibilityQuery.CompOrNull(ent.Owner) ?? AddComp<VisibilityComponent>(ent.Owner);
|
||||
|
||||
@@ -90,14 +90,14 @@ namespace Robust.Server.GameObjects
|
||||
RefreshVisibility(ent.Owner, null, ent.Comp);
|
||||
}
|
||||
|
||||
public void RefreshVisibility(EntityUid uid,
|
||||
public override void RefreshVisibility(EntityUid uid,
|
||||
VisibilityComponent? visibilityComponent = null,
|
||||
MetaDataComponent? meta = null)
|
||||
{
|
||||
RefreshVisibility((uid, visibilityComponent, meta));
|
||||
}
|
||||
|
||||
public void RefreshVisibility(Entity<VisibilityComponent?, MetaDataComponent?> ent)
|
||||
public override void RefreshVisibility(Entity<VisibilityComponent?, MetaDataComponent?> ent)
|
||||
{
|
||||
if (!_metaQuery.Resolve(ent, ref ent.Comp2, false))
|
||||
return;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -212,6 +212,48 @@ 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>
|
||||
@@ -401,6 +443,15 @@ 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)
|
||||
|
||||
@@ -15,6 +15,7 @@ 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))
|
||||
@@ -30,6 +31,13 @@ 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();
|
||||
|
||||
@@ -5,6 +5,9 @@ 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
|
||||
{
|
||||
@@ -29,11 +32,25 @@ 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; set; } = Default.Volume;
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
value = float.NegativeInfinity;
|
||||
}
|
||||
|
||||
_volume = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale for the audio pitch.
|
||||
|
||||
90
Robust.Shared/Audio/ResolvedSoundSpecifier.cs
Normal file
90
Robust.Shared/Audio/ResolvedSoundSpecifier.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path to a sound resource, either as a literal path or as a collection ID and index.
|
||||
/// </summary>
|
||||
/// <seealso cref="ResolvedPathSpecifier"/>
|
||||
/// <seealso cref="ResolvedCollectionSpecifier"/>
|
||||
[Serializable, NetSerializable]
|
||||
public abstract partial class ResolvedSoundSpecifier {
|
||||
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
|
||||
public static implicit operator ResolvedSoundSpecifier(string s) => new ResolvedPathSpecifier(s);
|
||||
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
|
||||
public static implicit operator ResolvedSoundSpecifier(ResPath s) => new ResolvedPathSpecifier(s);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether <c>s</c> is null, or if it contains an empty path/collection ID.
|
||||
/// </summary>
|
||||
public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s) {
|
||||
return s switch {
|
||||
null => true,
|
||||
ResolvedPathSpecifier path => path.Path.ToString() == "",
|
||||
ResolvedCollectionSpecifier collection => string.IsNullOrEmpty(collection.Collection),
|
||||
_ => throw new ArgumentOutOfRangeException("s", s, "argument is not a ResolvedPathSpecifier or a ResolvedCollectionSpecifier"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path to a sound resource as a literal path.
|
||||
/// </summary>
|
||||
/// <seealso cref="ResolvedCollectionSpecifier"/>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
|
||||
/// <summary>
|
||||
/// The resource path of the sound.
|
||||
/// </summary>
|
||||
public ResPath Path { get; private set; }
|
||||
|
||||
override public string ToString() =>
|
||||
$"ResolvedPathSpecifier({Path})";
|
||||
|
||||
[UsedImplicitly]
|
||||
private ResolvedPathSpecifier()
|
||||
{
|
||||
}
|
||||
public ResolvedPathSpecifier(ResPath path)
|
||||
{
|
||||
Path = path;
|
||||
}
|
||||
public ResolvedPathSpecifier(string path) : this(new ResPath(path))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a path to a sound resource as a collection ID and index.
|
||||
/// </summary>
|
||||
/// <seealso cref="ResolvedPathSpecifier"/>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier {
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="SoundCollectionPrototype">sound collection</see> to look up.
|
||||
/// </summary>
|
||||
public ProtoId<SoundCollectionPrototype>? Collection { get; private set; }
|
||||
/// <summary>
|
||||
/// The index of the file in the associated sound collection to play.
|
||||
/// </summary>
|
||||
public int Index { get; private set; }
|
||||
|
||||
override public string ToString() =>
|
||||
$"ResolvedCollectionSpecifier({Collection}, {Index})";
|
||||
|
||||
[UsedImplicitly]
|
||||
private ResolvedCollectionSpecifier()
|
||||
{
|
||||
}
|
||||
|
||||
public ResolvedCollectionSpecifier(string collection, int index)
|
||||
{
|
||||
Collection = collection;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,9 @@ public sealed partial class SoundPathSpecifier : SoundSpecifier
|
||||
[DataField(Node, customTypeSerializer: typeof(ResPathSerializer), required: true)]
|
||||
public ResPath Path { get; private set; }
|
||||
|
||||
override public string ToString() =>
|
||||
$"SoundPathSpecifier({Path})";
|
||||
|
||||
[UsedImplicitly]
|
||||
private SoundPathSpecifier()
|
||||
{
|
||||
@@ -52,6 +55,9 @@ public sealed partial class SoundCollectionSpecifier : SoundSpecifier
|
||||
[DataField(Node, customTypeSerializer: typeof(PrototypeIdSerializer<SoundCollectionPrototype>), required: true)]
|
||||
public string? Collection { get; private set; }
|
||||
|
||||
override public string ToString() =>
|
||||
$"SoundCollectionSpecifier({Collection})";
|
||||
|
||||
[UsedImplicitly]
|
||||
public SoundCollectionSpecifier() { }
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
|
||||
|
||||
private const float AudioDespawnBuffer = 1f;
|
||||
public const float AudioDespawnBuffer = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Default max range at which the sound can be heard.
|
||||
@@ -283,33 +283,60 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the filepath to a sound file.
|
||||
/// Resolve a sound specifier so it can be consistently played back on all clients.
|
||||
/// </summary>
|
||||
public string GetSound(SoundSpecifier specifier)
|
||||
public ResolvedSoundSpecifier ResolveSound(SoundSpecifier specifier)
|
||||
{
|
||||
switch (specifier)
|
||||
{
|
||||
case SoundPathSpecifier path:
|
||||
return path.Path == default ? string.Empty : path.Path.ToString();
|
||||
return new ResolvedPathSpecifier(path.Path == default ? string.Empty : path.Path.ToString());
|
||||
|
||||
case SoundCollectionSpecifier collection:
|
||||
{
|
||||
if (collection.Collection == null)
|
||||
return string.Empty;
|
||||
return new ResolvedPathSpecifier(string.Empty);
|
||||
|
||||
var soundCollection = ProtoMan.Index<SoundCollectionPrototype>(collection.Collection);
|
||||
return RandMan.Pick(soundCollection.PickFiles).ToString();
|
||||
var index = RandMan.Next(soundCollection.PickFiles.Count);
|
||||
return new ResolvedCollectionSpecifier(collection.Collection, index);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
return new ResolvedPathSpecifier(string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the filepath to a sound file.
|
||||
/// </summary>
|
||||
[Obsolete("Use ResolveSound() and pass around resolved sound specifiers instead.")]
|
||||
public string GetSound(SoundSpecifier specifier)
|
||||
{
|
||||
var resolved = ResolveSound(specifier);
|
||||
return GetAudioPath(resolved);
|
||||
}
|
||||
|
||||
#region AudioParams
|
||||
|
||||
protected Entity<AudioComponent> SetupAudio(string? fileName, AudioParams? audioParams, bool initialize = true, TimeSpan? length = null)
|
||||
[return: NotNullIfNotNull(nameof(specifier))]
|
||||
public string? GetAudioPath(ResolvedSoundSpecifier? specifier)
|
||||
{
|
||||
return specifier switch {
|
||||
ResolvedPathSpecifier path =>
|
||||
path.Path.ToString(),
|
||||
ResolvedCollectionSpecifier collection =>
|
||||
collection.Collection is null ?
|
||||
string.Empty :
|
||||
ProtoMan.Index<SoundCollectionPrototype>(collection.Collection).PickFiles[collection.Index].ToString(),
|
||||
null => null,
|
||||
_ => throw new ArgumentOutOfRangeException("specifier", specifier, "argument is not a ResolvedPathSpecifier or a ResolvedCollectionSpecifier"),
|
||||
};
|
||||
}
|
||||
|
||||
protected Entity<AudioComponent> SetupAudio(ResolvedSoundSpecifier? specifier, AudioParams? audioParams, bool initialize = true, TimeSpan? length = null)
|
||||
{
|
||||
var uid = EntityManager.CreateEntityUninitialized("Audio", MapCoordinates.Nullspace);
|
||||
var fileName = GetAudioPath(specifier);
|
||||
DebugTools.Assert(!string.IsNullOrEmpty(fileName) || length is not null);
|
||||
MetadataSys.SetEntityName(uid, $"Audio ({fileName})", raiseEvents: false);
|
||||
audioParams ??= AudioParams.Default;
|
||||
@@ -385,6 +412,13 @@ 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));
|
||||
@@ -395,8 +429,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Gets the timespan of the specified audio.
|
||||
/// </summary>
|
||||
public TimeSpan GetAudioLength(string filename)
|
||||
public TimeSpan GetAudioLength(ResolvedSoundSpecifier specifier)
|
||||
{
|
||||
var filename = GetAudioPath(specifier) ?? string.Empty;
|
||||
if (!filename.StartsWith("/"))
|
||||
throw new ArgumentException("Path must be rooted");
|
||||
|
||||
@@ -429,7 +464,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
@@ -438,7 +473,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayGlobal(ResolveSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -446,7 +481,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, ICommonSession recipient, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
@@ -455,7 +490,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayGlobal(ResolveSound(sound), recipient, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
public abstract void LoadStream<T>(Entity<AudioComponent> entity, T stream);
|
||||
@@ -465,7 +500,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(ResolvedSoundSpecifier? filename, EntityUid recipient, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
@@ -474,7 +509,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayGlobal(ResolveSound(sound), recipient, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -483,7 +518,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
@@ -491,7 +526,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
@@ -499,7 +534,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
@@ -509,7 +544,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayEntity(GetSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayEntity(ResolveSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -520,7 +555,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayEntity(ResolveSound(sound), recipient, uid, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -531,7 +566,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayEntity(ResolveSound(sound), recipient, uid, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -541,7 +576,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayPvs(GetSound(sound), uid, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayPvs(ResolveSound(sound), uid, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -551,7 +586,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="coordinates">The EntityCoordinates to attach the audio source to.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayPvs(ResolveSound(sound), coordinates, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -559,7 +594,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="coordinates">The EntityCoordinates to attach the audio source to.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename,
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? filename,
|
||||
EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -567,7 +602,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid,
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(ResolvedSoundSpecifier? filename, EntityUid uid,
|
||||
AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -604,7 +639,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
@@ -612,7 +647,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
@@ -620,7 +655,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(ResolvedSoundSpecifier? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
@@ -630,7 +665,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay, audioParams);
|
||||
return sound == null ? null : PlayStatic(ResolveSound(sound), playerFilter, coordinates, recordReplay, audioParams);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -641,7 +676,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayStatic(ResolveSound(sound), recipient, coordinates, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -652,7 +687,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params);
|
||||
return sound == null ? null : PlayStatic(ResolveSound(sound), recipient, coordinates, audioParams ?? sound.Params);
|
||||
}
|
||||
|
||||
// These are just here for replays now.
|
||||
@@ -665,7 +700,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
[NetSerializable, Serializable]
|
||||
protected abstract class AudioMessage : EntityEventArgs
|
||||
{
|
||||
public string FileName = string.Empty;
|
||||
public ResolvedSoundSpecifier Specifier = new ResolvedPathSpecifier(string.Empty);
|
||||
public AudioParams AudioParams;
|
||||
}
|
||||
|
||||
|
||||
@@ -1233,6 +1233,12 @@ 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
@@ -186,7 +186,9 @@ public static class CompletionHelper
|
||||
|
||||
public static IEnumerable<CompletionOption> MapUids(IEntityManager? entManager = null)
|
||||
{
|
||||
return Components<MapComponent>(string.Empty, entManager);
|
||||
IoCManager.Resolve(ref entManager);
|
||||
|
||||
return Components<MapComponent>(string.Empty, entManager, limit: 128);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -194,7 +196,7 @@ public static class CompletionHelper
|
||||
/// </summary>
|
||||
public static IEnumerable<CompletionOption> NetEntities(string text, IEntityManager? entManager = null, int limit = 20)
|
||||
{
|
||||
if (!NetEntity.TryParse(text, out _))
|
||||
if (text != string.Empty && !NetEntity.TryParse(text, out _))
|
||||
yield break;
|
||||
|
||||
IoCManager.Resolve(ref entManager);
|
||||
@@ -214,7 +216,7 @@ public static class CompletionHelper
|
||||
|
||||
public static IEnumerable<CompletionOption> Components<T>(string text, IEntityManager? entManager = null, int limit = 20) where T : IComponent
|
||||
{
|
||||
if (!NetEntity.TryParse(text, out _))
|
||||
if (text != string.Empty && !NetEntity.TryParse(text, out _))
|
||||
yield break;
|
||||
|
||||
IoCManager.Resolve(ref entManager);
|
||||
|
||||
@@ -60,7 +60,9 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
|
||||
// Sanitise platform-specific path and standardize it for engine use.
|
||||
.Replace(Path.DirectorySeparatorChar, '/');
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,11 +14,7 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
void Initialize(string? userData);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Robust.Shared.ContentPack
|
||||
var paths = new List<ResPath>();
|
||||
|
||||
foreach (var filePath in _res.ContentFindRelativeFiles(mountPath)
|
||||
.Where(p => !p.ToString().Contains('/') && p.Filename.StartsWith(filterPrefix) &&
|
||||
.Where(p => p.Filename.StartsWith(filterPrefix) &&
|
||||
p.Extension == "dll"))
|
||||
{
|
||||
var fullPath = mountPath / filePath;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -64,27 +63,5 @@ 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
public virtual void Initialize(string? userData)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -381,10 +381,6 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -696,7 +696,7 @@ Types:
|
||||
- "bool IsMatch(string, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
|
||||
- "int GroupNumberFromName(string)"
|
||||
- "int[] GetGroupNumbers()"
|
||||
- "string Escape()"
|
||||
- "string Escape(string)"
|
||||
- "string GroupNameFromNumber(int)"
|
||||
- "string Replace(string, string)"
|
||||
- "string Replace(string, string, int)"
|
||||
@@ -736,6 +736,15 @@ 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: { }
|
||||
@@ -789,6 +798,7 @@ 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()"
|
||||
|
||||
@@ -10,22 +10,17 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
/// <inheritdoc />
|
||||
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>
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -124,7 +119,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
return new WritableDirProvider(dirInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -185,7 +180,20 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,10 @@ public sealed class EntitySerializer : ISerializationContext,
|
||||
foreach (var (origId, prototypeId) in savedMap)
|
||||
{
|
||||
if (_tileDef.TryGetDefinition(prototypeId, out var definition))
|
||||
{
|
||||
_tileMap.TryAdd(definition.TileId, origId);
|
||||
_yamlTileIds.Add(origId); // Make sure we record the IDs we're using so when we need to reserve new ones we can
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -593,7 +596,7 @@ public sealed class EntitySerializer : ISerializationContext,
|
||||
public MappingDataNode Write()
|
||||
{
|
||||
DebugTools.AssertEqual(Maps.ToHashSet().Count, Maps.Count, "Duplicate maps?");
|
||||
DebugTools.AssertEqual(Grids.ToHashSet().Count, Grids.Count, "Duplicate frids?");
|
||||
DebugTools.AssertEqual(Grids.ToHashSet().Count, Grids.Count, "Duplicate grids?");
|
||||
DebugTools.AssertEqual(Orphans.ToHashSet().Count, Orphans.Count, "Duplicate orphans?");
|
||||
DebugTools.AssertEqual(Nullspace.ToHashSet().Count, Nullspace.Count, "Duplicate nullspace?");
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while creating entities: {e}");
|
||||
Log.Error($"Caught exception while creating entities for map {file}: {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($"File does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Log.Error($"Map {file} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
@@ -203,6 +203,35 @@ 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)
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls PVS visibility of entities. THIS COMPONENT CONTROLS WHETHER ENTITIES ARE NETWORKED TO PLAYERS
|
||||
/// AND SHOULD NOT BE USED AS THE SOLE WAY TO HIDE AN ENTITY FROM A PLAYER.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(VisibilitySystem))]
|
||||
[Access(typeof(SharedVisibilitySystem))]
|
||||
public sealed partial class VisibilityComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
@@ -1075,6 +1075,97 @@ namespace Robust.Shared.GameObjects
|
||||
return TryGetComponent(uid.Value, netId, out component, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryCopyComponent<T>(EntityUid source, EntityUid target, ref T? sourceComponent, [NotNullWhen(true)] out T? targetComp, MetaDataComponent? meta = null) where T : IComponent
|
||||
{
|
||||
if (!MetaQuery.Resolve(target, ref meta))
|
||||
{
|
||||
targetComp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceComponent == null && !TryGetComponent(source, out sourceComponent))
|
||||
{
|
||||
targetComp = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
targetComp = CopyComponentInternal(source, target, sourceComponent, meta);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool TryCopyComponents(
|
||||
EntityUid source,
|
||||
EntityUid target,
|
||||
MetaDataComponent? meta = null,
|
||||
params Type[] sourceComponents)
|
||||
{
|
||||
if (!MetaQuery.TryGetComponent(target, out meta))
|
||||
return false;
|
||||
|
||||
var allCopied = true;
|
||||
|
||||
foreach (var type in sourceComponents)
|
||||
{
|
||||
if (!TryGetComponent(source, type, out var srcComp))
|
||||
{
|
||||
allCopied = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
CopyComponent(source, target, srcComp, meta: meta);
|
||||
}
|
||||
|
||||
return allCopied;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IComponent CopyComponent(EntityUid source, EntityUid target, IComponent sourceComponent, MetaDataComponent? meta = null)
|
||||
{
|
||||
if (!MetaQuery.Resolve(target, ref meta))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return CopyComponentInternal(source, target, sourceComponent, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public T CopyComponent<T>(EntityUid source, EntityUid target, T sourceComponent,MetaDataComponent? meta = null) where T : IComponent
|
||||
{
|
||||
if (!MetaQuery.Resolve(target, ref meta))
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return CopyComponentInternal(source, target, sourceComponent, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents)
|
||||
{
|
||||
if (!MetaQuery.Resolve(target, ref meta))
|
||||
return;
|
||||
|
||||
foreach (var comp in sourceComponents)
|
||||
{
|
||||
CopyComponentInternal(source, target, comp, meta);
|
||||
}
|
||||
}
|
||||
|
||||
private T CopyComponentInternal<T>(EntityUid source, EntityUid target, T sourceComponent, MetaDataComponent meta) where T : IComponent
|
||||
{
|
||||
var compReg = ComponentFactory.GetRegistration(sourceComponent.GetType());
|
||||
var component = (T)ComponentFactory.GetComponent(compReg);
|
||||
|
||||
_serManager.CopyTo(sourceComponent, ref component, notNullableOverride: true);
|
||||
component.Owner = target;
|
||||
|
||||
AddComponentInternal(target, component, compReg, true, false, meta);
|
||||
return component;
|
||||
}
|
||||
|
||||
public EntityQuery<TComp1> GetEntityQuery<TComp1>() where TComp1 : IComponent
|
||||
{
|
||||
var comps = _entTraitArray[CompIdx.ArrayIndex<TComp1>()];
|
||||
|
||||
@@ -189,10 +189,12 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
|
||||
var compType = component.GetType();
|
||||
var compName = _componentFactory.GetComponentName(compType);
|
||||
if (compName == _xformName || compName == _metaReg.Name)
|
||||
|
||||
if (compType == typeof(TransformComponent) || compType == typeof(MetaDataComponent))
|
||||
continue;
|
||||
|
||||
var compName = _componentFactory.GetComponentName(compType);
|
||||
|
||||
// If the component isn't on the prototype then it's custom.
|
||||
if (!protoData.TryGetValue(compName, out var protoMapping))
|
||||
return false;
|
||||
@@ -208,9 +210,7 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
var diff = compMapping.Except(protoMapping);
|
||||
|
||||
if (diff != null && diff.Children.Count != 0)
|
||||
if (compMapping.AnyExcept(protoMapping))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ public partial class EntitySystem
|
||||
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
EntityManager.DirtyFields(uid, comp, meta);
|
||||
EntityManager.DirtyFields(uid, comp, meta, fields);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -565,6 +565,52 @@ public partial class EntitySystem
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Copy
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryCopyComponent"/>
|
||||
protected bool TryCopyComponent<T>(
|
||||
EntityUid source,
|
||||
EntityUid target,
|
||||
ref T? sourceComponent,
|
||||
[NotNullWhen(true)] out T? targetComp,
|
||||
MetaDataComponent? meta = null) where T : IComponent
|
||||
{
|
||||
return EntityManager.TryCopyComponent(source, target, ref sourceComponent, out targetComp, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.TryCopyComponents"/>
|
||||
protected bool TryCopyComponents(
|
||||
EntityUid source,
|
||||
EntityUid target,
|
||||
MetaDataComponent? meta = null,
|
||||
params Type[] sourceComponents)
|
||||
{
|
||||
return EntityManager.TryCopyComponents(source, target, meta, sourceComponents);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.CopyComponent"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected IComponent CopyComp(EntityUid source, EntityUid target, IComponent sourceComponent, MetaDataComponent? meta = null)
|
||||
{
|
||||
return EntityManager.CopyComponent(source, target, sourceComponent, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.CopyComponent{T}"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected T CopyComp<T>(EntityUid source, EntityUid target, T sourceComponent, MetaDataComponent? meta = null) where T : IComponent
|
||||
{
|
||||
return EntityManager.CopyComponent(source, target, sourceComponent, meta);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IEntityManager.CopyComponents"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void CopyComps(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents)
|
||||
{
|
||||
EntityManager.CopyComponents(source, target, meta, sourceComponents);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Has
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -358,6 +358,51 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>If the component existed in the entity.</returns>
|
||||
bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, ushort netId, [NotNullWhen(true)] out IComponent? component, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Tries to run <see cref="CopyComponents"/> without throwing if the component doesn't exist.
|
||||
/// </summary>
|
||||
bool TryCopyComponent<T>(
|
||||
EntityUid source,
|
||||
EntityUid target,
|
||||
ref T? sourceComponent,
|
||||
[NotNullWhen(true)] out T? targetComp,
|
||||
MetaDataComponent? meta = null) where T : IComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to run <see cref="CopyComponents"/> without throwing if the components don't exist.
|
||||
/// </summary>
|
||||
bool TryCopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params Type[] sourceComponents);
|
||||
|
||||
/// <summary>
|
||||
/// Copy a single component from source to target entity.
|
||||
/// </summary>
|
||||
/// <param name="source">The source entity to copy from.</param>
|
||||
/// <param name="target">The target entity to copy to.</param>
|
||||
/// <param name="sourceComponent">The source component instance to copy.</param>
|
||||
/// <param name="component">The copied component if successful.</param>
|
||||
/// <param name="meta">Optional metadata of the target entity.</param>
|
||||
IComponent CopyComponent(EntityUid source, EntityUid target, IComponent sourceComponent, MetaDataComponent? meta = null);
|
||||
|
||||
/// <summary>
|
||||
/// Copy a single component from source to target entity.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of component to copy.</typeparam>
|
||||
/// <param name="source">The source entity to copy from.</param>
|
||||
/// <param name="target">The target entity to copy to.</param>
|
||||
/// <param name="sourceComponent">The source component instance to copy.</param>
|
||||
/// <param name="component">The copied component if successful.</param>
|
||||
/// <param name="meta">Optional metadata of the target entity.</param>
|
||||
T CopyComponent<T>(EntityUid source, EntityUid target, T sourceComponent, MetaDataComponent? meta = null) where T : IComponent;
|
||||
|
||||
/// <summary>
|
||||
/// Copy multiple components from source to target entity using existing component instances.
|
||||
/// </summary>
|
||||
/// <param name="source">The source entity to copy from.</param>
|
||||
/// <param name="target">The target entity to copy to.</param>
|
||||
/// <param name="meta">Optional metadata of the target entity.</param>
|
||||
/// <param name="sourceComponents">Array of component instances to copy.</param>
|
||||
void CopyComponents(EntityUid source, EntityUid target, MetaDataComponent? meta = null, params IComponent[] sourceComponents);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a cached struct enumerator with the specified component.
|
||||
/// </summary>
|
||||
|
||||
@@ -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, Vector2.Zero);
|
||||
tree.MoveProxy(proxy.ProxyId, bounds);
|
||||
proxy.AABB = bounds;
|
||||
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,9 @@ internal sealed class PrototypeReloadSystem : EntitySystem
|
||||
{
|
||||
var data = newPrototype.Components[name];
|
||||
var component = _componentFactory.GetComponent(name);
|
||||
EntityManager.AddComponent(entity, component);
|
||||
|
||||
if (!EntityManager.HasComponent(entity, component.GetType()))
|
||||
EntityManager.AddComponent(entity, component);
|
||||
}
|
||||
|
||||
// Update entity metadata
|
||||
|
||||
@@ -162,7 +162,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
{
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb, Vector2.Zero);
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
||||
}
|
||||
|
||||
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, Vector2.Zero);
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
||||
}
|
||||
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
|
||||
@@ -309,6 +309,7 @@ public abstract partial class SharedMapSystem
|
||||
ChunkDatum data)
|
||||
{
|
||||
var counter = 0;
|
||||
var gridEnt = new Entity<MapGridComponent>(uid, component);
|
||||
|
||||
if (data.IsDeleted())
|
||||
{
|
||||
@@ -326,10 +327,12 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
|
||||
var newTileRef = new TileRef(uid, gridIndices, Tile.Empty);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
|
||||
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
|
||||
}
|
||||
}
|
||||
|
||||
deletedChunk.CachedBounds = Box2i.Empty;
|
||||
deletedChunk.SuppressCollisionRegeneration = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,10 +353,12 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
var gridIndices = chunk.ChunkTileToGridTile((x, y));
|
||||
var newTileRef = new TileRef(uid, gridIndices, tile);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
|
||||
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
|
||||
}
|
||||
}
|
||||
|
||||
DebugTools.Assert(chunk.Fixtures.SetEquals(data.Fixtures));
|
||||
|
||||
// These should never refer to the same object
|
||||
DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures);
|
||||
|
||||
@@ -508,7 +513,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
{
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), component));
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), component));
|
||||
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
|
||||
component.MapProxy = proxy;
|
||||
}
|
||||
@@ -562,7 +567,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
{
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), grid));
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), grid));
|
||||
DebugTools.Assert(grid.MapProxy == DynamicTree.Proxy.Free);
|
||||
grid.MapProxy = proxy;
|
||||
}
|
||||
@@ -1614,7 +1619,7 @@ public abstract partial class SharedMapSystem
|
||||
if (!MapManager.SuppressOnTileChanged)
|
||||
{
|
||||
var newTileRef = new TileRef(uid, gridTile, newTile);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, mapChunk.Indices);
|
||||
_mapInternal.RaiseOnTileChanged((uid, grid), newTileRef, oldTile, mapChunk.Indices);
|
||||
}
|
||||
|
||||
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
|
||||
|
||||
@@ -22,7 +22,6 @@ 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;
|
||||
@@ -159,9 +158,9 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Creates a new instance of this class.
|
||||
/// </summary>
|
||||
public TileChangedEvent(EntityUid uid, TileRef newTile, Tile oldTile, Vector2i chunkIndex)
|
||||
public TileChangedEvent(Entity<MapGridComponent> entity, TileRef newTile, Tile oldTile, Vector2i chunkIndex)
|
||||
{
|
||||
Entity = uid;
|
||||
Entity = entity;
|
||||
NewTile = newTile;
|
||||
OldTile = oldTile;
|
||||
ChunkIndex = chunkIndex;
|
||||
@@ -173,9 +172,9 @@ namespace Robust.Shared.GameObjects
|
||||
public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty;
|
||||
|
||||
/// <summary>
|
||||
/// EntityUid of the grid with the tile-change. TileRef stores the GridId.
|
||||
/// Entity of the grid with the tile-change. TileRef stores the GridId.
|
||||
/// </summary>
|
||||
public readonly EntityUid Entity;
|
||||
public readonly Entity<MapGridComponent> Entity;
|
||||
|
||||
/// <summary>
|
||||
/// New tile that replaced the old one.
|
||||
|
||||
@@ -279,7 +279,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
protected 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,8 +1096,24 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
uiComp.ClientOpenInterfaces.Remove(bui.UiKey);
|
||||
}
|
||||
|
||||
SavePosition(bui);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (!TerminatingOrDeleted(bui.Owner))
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
26
Robust.Shared/GameObjects/Systems/SharedVisibilitySystem.cs
Normal file
26
Robust.Shared/GameObjects/Systems/SharedVisibilitySystem.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public abstract class SharedVisibilitySystem : EntitySystem
|
||||
{
|
||||
public virtual void AddLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void RemoveLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void SetLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void RefreshVisibility(EntityUid uid,
|
||||
VisibilityComponent? visibilityComponent = null,
|
||||
MetaDataComponent? meta = null)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void RefreshVisibility(Entity<VisibilityComponent?, MetaDataComponent?> ent)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -10,6 +12,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(TileRef tileRef, Tile oldTile, Vector2i chunk);
|
||||
void RaiseOnTileChanged(Entity<MapGridComponent> entity, TileRef tileRef, Tile oldTile, Vector2i chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -7,7 +8,7 @@ namespace Robust.Shared.Map
|
||||
/// <summary>
|
||||
/// The definition (template) for a grid tile.
|
||||
/// </summary>
|
||||
public interface ITileDefinition
|
||||
public interface ITileDefinition : IPrototype
|
||||
{
|
||||
/// <summary>
|
||||
/// The numeric tile ID used to refer to this tile inside the map datastructure.
|
||||
|
||||
@@ -96,14 +96,13 @@ 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>
|
||||
public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk)
|
||||
void IMapManagerInternal.RaiseOnTileChanged(Entity<MapGridComponent> entity, TileRef tileRef, Tile oldTile, Vector2i chunk)
|
||||
{
|
||||
if (SuppressOnTileChanged)
|
||||
return;
|
||||
|
||||
var euid = tileRef.GridUid;
|
||||
var ev = new TileChangedEvent(euid, tileRef, oldTile, chunk);
|
||||
EntityManager.EventBus.RaiseLocalEvent(euid, ref ev, true);
|
||||
var ev = new TileChangedEvent(entity, tileRef, oldTile, chunk);
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, ref ev, true);
|
||||
}
|
||||
|
||||
protected Entity<MapGridComponent> CreateGrid(EntityUid map, ushort chunkSize, EntityUid forcedGridEuid)
|
||||
|
||||
@@ -10,7 +10,6 @@ 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;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -11,7 +10,6 @@ using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Lidgren.Network;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network.Messages.Handshake;
|
||||
using Robust.Shared.Utility;
|
||||
using SpaceWizards.Sodium;
|
||||
@@ -98,7 +96,7 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
await CCDoHandshake(winningPeer, winningConnection, userNameRequest, mainCancelToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
winningPeer.Peer.Shutdown("Cancelled");
|
||||
_toCleanNetPeers.Add(winningPeer.Peer);
|
||||
@@ -120,7 +118,10 @@ namespace Robust.Shared.Network
|
||||
_logger.Debug("Handshake completed, connection established.");
|
||||
}
|
||||
|
||||
private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, string userNameRequest,
|
||||
private async Task CCDoHandshake(
|
||||
NetPeerData peer,
|
||||
NetConnection connection,
|
||||
string userNameRequest,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var encrypt = _config.GetCVar(CVars.NetEncrypt);
|
||||
@@ -289,37 +290,51 @@ namespace Robust.Shared.Network
|
||||
private async Task<(NetPeerData winningPeer, NetConnection winningConnection)?>
|
||||
CCHappyEyeballs(int port, IPAddress first, IPAddress? second, CancellationToken mainCancelToken)
|
||||
{
|
||||
NetPeerData CreatePeerForIp(IPAddress address)
|
||||
// Try to establish a connection with an IP address and wait for it to either connect or fail
|
||||
// Returns a disposable wrapper around the peer/connection because ParallelTask
|
||||
async Task<ConnectionAttempt> AttemptConnection(IPAddress address, CancellationToken cancel)
|
||||
{
|
||||
var config = _getBaseNetPeerConfig();
|
||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||
{
|
||||
config.LocalAddress = IPAddress.IPv6Any;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.LocalAddress = IPAddress.Any;
|
||||
}
|
||||
config.LocalAddress = address.AddressFamily == AddressFamily.InterNetworkV6
|
||||
? IPAddress.IPv6Any
|
||||
: IPAddress.Any;
|
||||
|
||||
var peer = new NetPeer(config);
|
||||
peer.Start();
|
||||
var data = new NetPeerData(peer);
|
||||
_netPeers.Add(data);
|
||||
return data;
|
||||
var peerData = new NetPeerData(peer);
|
||||
_netPeers.Add(peerData);
|
||||
|
||||
var connection = peer.Connect(new IPEndPoint(address, port));
|
||||
|
||||
try
|
||||
{
|
||||
// We need AwaitNonInitStatusChange to properly handle connection state transitions
|
||||
var reason = await AwaitNonInitStatusChange(connection, cancel);
|
||||
|
||||
if (connection.Status != NetConnectionStatus.Connected)
|
||||
{
|
||||
// Connection failed, clean up and yeet an exception
|
||||
peer.Shutdown(reason);
|
||||
_toCleanNetPeers.Add(peer);
|
||||
throw new Exception($"Connection failed: {reason}");
|
||||
}
|
||||
|
||||
return new ConnectionAttempt(peerData, connection, this);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Something went wrong!
|
||||
peer.Shutdown("Connection attempt failed");
|
||||
_toCleanNetPeers.Add(peer);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Create first peer.
|
||||
var firstPeer = CreatePeerForIp(first);
|
||||
var firstConnection = firstPeer.Peer.Connect(new IPEndPoint(first, port));
|
||||
NetPeerData? secondPeer = null;
|
||||
NetConnection? secondConnection = null;
|
||||
string? secondReason = null;
|
||||
|
||||
// Waits for a connection's status to change from InitiatedConnect to anything else
|
||||
async Task<string> AwaitNonInitStatusChange(NetConnection connection, CancellationToken cancellationToken)
|
||||
{
|
||||
NetConnectionStatus status;
|
||||
string reason;
|
||||
|
||||
NetConnectionStatus status;
|
||||
do
|
||||
{
|
||||
reason = await AwaitStatusChange(connection, cancellationToken);
|
||||
@@ -329,124 +344,37 @@ namespace Robust.Shared.Network
|
||||
return reason;
|
||||
}
|
||||
|
||||
async Task ConnectSecondDelayed(CancellationToken cancellationToken)
|
||||
{
|
||||
DebugTools.AssertNotNull(second);
|
||||
// Connecting via second peer is delayed by 25ms to give an advantage to IPv6, if it works.
|
||||
var delay = TimeSpan.FromSeconds(_config.GetCVar(CVars.NetHappyEyeballsDelay));
|
||||
await Task.Delay(delay, cancellationToken);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
secondPeer = CreatePeerForIp(second);
|
||||
secondConnection = secondPeer.Peer.Connect(new IPEndPoint(second, port));
|
||||
|
||||
secondReason = await AwaitNonInitStatusChange(secondConnection, cancellationToken);
|
||||
}
|
||||
|
||||
NetPeerData? winningPeer;
|
||||
NetConnection? winningConnection;
|
||||
string? firstReason = null;
|
||||
try
|
||||
{
|
||||
if (second != null)
|
||||
{
|
||||
// We have two addresses to try.
|
||||
var cancellation = CancellationTokenSource.CreateLinkedTokenSource(mainCancelToken);
|
||||
var firstPeerChanged = AwaitNonInitStatusChange(firstConnection, cancellation.Token);
|
||||
var secondPeerChanged = ConnectSecondDelayed(cancellation.Token);
|
||||
// Create list of IPs to try
|
||||
var addresses = second != null
|
||||
? new[] { first, second }
|
||||
: new[] { first };
|
||||
|
||||
var firstChange = await Task.WhenAny(firstPeerChanged, secondPeerChanged);
|
||||
// Use ParallelTask to handle the connection attempts
|
||||
var delay = TimeSpan.FromSeconds(_config.GetCVar(CVars.NetHappyEyeballsDelay));
|
||||
var (result, _) = await HappyEyeballsHttp.ParallelTask(
|
||||
addresses.Length,
|
||||
(i, token) => AttemptConnection(addresses[i], token),
|
||||
delay,
|
||||
mainCancelToken);
|
||||
|
||||
if (firstChange == firstPeerChanged)
|
||||
{
|
||||
_logger.Debug("First peer status changed.");
|
||||
// First peer responded first.
|
||||
if (firstConnection.Status == NetConnectionStatus.Connected)
|
||||
{
|
||||
// First peer won!
|
||||
_logger.Debug("First peer succeeded.");
|
||||
cancellation.Cancel();
|
||||
if (secondPeer != null)
|
||||
{
|
||||
secondPeer.Peer.Shutdown("First connection attempt won.");
|
||||
_toCleanNetPeers.Add(secondPeer.Peer);
|
||||
}
|
||||
|
||||
winningPeer = firstPeer;
|
||||
winningConnection = firstConnection;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First peer failed, try the second one I guess.
|
||||
_logger.Debug("First peer failed.");
|
||||
firstPeer.Peer.Shutdown("You failed.");
|
||||
_toCleanNetPeers.Add(firstPeer.Peer);
|
||||
firstReason = await firstPeerChanged;
|
||||
await secondPeerChanged;
|
||||
winningPeer = secondPeer;
|
||||
winningConnection = secondConnection;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (secondConnection!.Status == NetConnectionStatus.Connected)
|
||||
{
|
||||
// Second peer won!
|
||||
_logger.Debug("Second peer succeeded.");
|
||||
cancellation.Cancel();
|
||||
firstPeer.Peer.Shutdown("Second connection attempt won.");
|
||||
_toCleanNetPeers.Add(firstPeer.Peer);
|
||||
winningPeer = secondPeer;
|
||||
winningConnection = secondConnection;
|
||||
}
|
||||
else
|
||||
{
|
||||
// First peer failed, try the second one I guess.
|
||||
_logger.Debug("Second peer failed.");
|
||||
secondPeer!.Peer.Shutdown("You failed.");
|
||||
_toCleanNetPeers.Add(secondPeer.Peer);
|
||||
firstReason = await firstPeerChanged;
|
||||
winningPeer = firstPeer;
|
||||
winningConnection = firstConnection;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only one address to try. Pretty straight forward.
|
||||
firstReason = await AwaitNonInitStatusChange(firstConnection, mainCancelToken);
|
||||
winningPeer = firstPeer;
|
||||
winningConnection = firstConnection;
|
||||
}
|
||||
return (result.Peer, result.Connection);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
firstPeer.Peer.Shutdown("Cancelled");
|
||||
_toCleanNetPeers.Add(firstPeer.Peer);
|
||||
if (secondPeer != null)
|
||||
{
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
secondPeer.Peer.Shutdown("Cancelled");
|
||||
_toCleanNetPeers.Add(secondPeer.Peer);
|
||||
}
|
||||
|
||||
// Connection attempt was cancelled, nothing to see here
|
||||
OnConnectFailed("Connection attempt cancelled.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// winningPeer can still be failed at this point.
|
||||
// If it is, neither succeeded. RIP.
|
||||
if (winningConnection!.Status != NetConnectionStatus.Connected)
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
winningPeer!.Peer.Shutdown("You failed");
|
||||
_toCleanNetPeers.Add(winningPeer.Peer);
|
||||
OnConnectFailed((secondReason ?? firstReason)!);
|
||||
// ParallelTask throws AggregateException with all connection failures
|
||||
// We just take the first one
|
||||
var message = ae.InnerExceptions.First().Message;
|
||||
OnConnectFailed(message);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (winningPeer!, winningConnection);
|
||||
}
|
||||
|
||||
private Task<string> AwaitStatusChange(NetConnection connection, CancellationToken cancellationToken = default)
|
||||
@@ -471,7 +399,8 @@ namespace Robust.Shared.Network
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private Task<NetIncomingMessage> AwaitData(NetConnection connection,
|
||||
private Task<NetIncomingMessage> AwaitData(
|
||||
NetConnection connection,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (_awaitingData.ContainsKey(connection))
|
||||
@@ -529,5 +458,17 @@ namespace Robust.Shared.Network
|
||||
}
|
||||
|
||||
private sealed record JoinRequest(string Hash, string? Hwid);
|
||||
|
||||
private sealed class ConnectionAttempt(NetPeerData peer, NetConnection connection, NetManager netManager) : IDisposable
|
||||
{
|
||||
public NetPeerData Peer { get; } = peer;
|
||||
public NetConnection Connection { get; } = connection;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Peer.Peer.Shutdown("Disposing unused connection attempt");
|
||||
netManager._toCleanNetPeers.Add(Peer.Peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,6 +587,14 @@ namespace Robust.Shared.Network
|
||||
public void ClientDisconnect(string reason)
|
||||
{
|
||||
DebugTools.Assert(IsClient, "Should never be called on the server.");
|
||||
|
||||
// First handle any in-progress connection attempt
|
||||
if (ClientConnectState != ClientConnectionState.NotConnecting)
|
||||
{
|
||||
_cancelConnectTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
// Then handle existing connection if any
|
||||
if (ServerChannel != null)
|
||||
{
|
||||
Disconnect?.Invoke(this, new NetDisconnectedArgs(ServerChannel, reason));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,18 +28,18 @@ public sealed class DynamicTreeBroadPhase : IBroadPhase
|
||||
|
||||
public Box2 GetFatAabb(DynamicTree.Proxy proxy)
|
||||
{
|
||||
return _tree.GetFatAabb(proxy);
|
||||
return _tree.GetUserData(proxy)!.AABB;
|
||||
}
|
||||
|
||||
public DynamicTree.Proxy AddProxy(ref FixtureProxy proxy)
|
||||
{
|
||||
var proxyId = _tree.CreateProxy(proxy.AABB, proxy);
|
||||
var proxyId = _tree.CreateProxy(proxy.AABB, uint.MaxValue, proxy);
|
||||
return proxyId;
|
||||
}
|
||||
|
||||
public bool MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb, Vector2 displacement)
|
||||
public void MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb)
|
||||
{
|
||||
return _tree.MoveProxy(proxy, in aabb, displacement);
|
||||
_tree.MoveProxy(proxy, in aabb);
|
||||
}
|
||||
|
||||
public void RemoveProxy(DynamicTree.Proxy proxy)
|
||||
@@ -132,6 +132,16 @@ 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)!;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -9,21 +8,23 @@ namespace Robust.Shared.Physics;
|
||||
/// <summary>
|
||||
/// Convex hull used for poly collision.
|
||||
/// </summary>
|
||||
internal ref struct PhysicsHull()
|
||||
internal ref struct InternalPhysicsHull
|
||||
{
|
||||
public Span<Vector2> Points;
|
||||
public int Count;
|
||||
|
||||
public PhysicsHull(Span<Vector2> vertices, int count) : this()
|
||||
internal InternalPhysicsHull(Span<Vector2> vertices, int count) : this()
|
||||
{
|
||||
Count = count;
|
||||
Points = vertices[..count];
|
||||
}
|
||||
|
||||
private static PhysicsHull RecurseHull(Vector2 p1, Vector2 p2, Span<Vector2> ps, int count)
|
||||
private static InternalPhysicsHull RecurseHull(Vector2 p1, Vector2 p2, Span<Vector2> ps, int count)
|
||||
{
|
||||
PhysicsHull hull = new();
|
||||
hull.Count = 0;
|
||||
InternalPhysicsHull hull = new()
|
||||
{
|
||||
Count = 0
|
||||
};
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
@@ -69,10 +70,10 @@ internal ref struct PhysicsHull()
|
||||
var bestPoint = ps[bestIndex];
|
||||
|
||||
// compute hull to the right of p1-bestPoint
|
||||
PhysicsHull hull1 = RecurseHull(p1, bestPoint, rightPoints, rightCount);
|
||||
InternalPhysicsHull hull1 = RecurseHull(p1, bestPoint, rightPoints, rightCount);
|
||||
|
||||
// compute hull to the right of bestPoint-p2
|
||||
PhysicsHull hull2 = RecurseHull(bestPoint, p2, rightPoints, rightCount);
|
||||
InternalPhysicsHull hull2 = RecurseHull(bestPoint, p2, rightPoints, rightCount);
|
||||
|
||||
// stich together hulls
|
||||
for (var i = 0; i < hull1.Count; ++i)
|
||||
@@ -96,9 +97,9 @@ internal ref struct PhysicsHull()
|
||||
// - merges vertices based on b2_linearSlop
|
||||
// - removes collinear points using b2_linearSlop
|
||||
// - returns an empty hull if it fails
|
||||
public static PhysicsHull ComputeHull(ReadOnlySpan<Vector2> points, int count)
|
||||
public static InternalPhysicsHull ComputeHull(ReadOnlySpan<Vector2> points, int count)
|
||||
{
|
||||
PhysicsHull hull = new();
|
||||
InternalPhysicsHull hull = new();
|
||||
|
||||
if (count is < 3 or > PhysicsConstants.MaxPolygonVertices)
|
||||
{
|
||||
@@ -287,7 +288,7 @@ internal ref struct PhysicsHull()
|
||||
return hull;
|
||||
}
|
||||
|
||||
public static bool ValidateHull(PhysicsHull hull)
|
||||
public static bool ValidateHull(InternalPhysicsHull hull)
|
||||
{
|
||||
if (hull.Count < 3 || PhysicsConstants.MaxPolygonVertices < hull.Count)
|
||||
{
|
||||
@@ -76,7 +76,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
{
|
||||
DebugTools.Assert(count is >= 3 and <= PhysicsConstants.MaxPolygonVertices);
|
||||
|
||||
var hull = PhysicsHull.ComputeHull(vertices, count);
|
||||
var hull = InternalPhysicsHull.ComputeHull(vertices, count);
|
||||
|
||||
if (hull.Count < 3)
|
||||
{
|
||||
@@ -87,7 +87,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void Set(PhysicsHull hull)
|
||||
internal void Set(InternalPhysicsHull 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 PhysicsHull();
|
||||
var hull = new InternalPhysicsHull();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
hull.Points[i] = Vertices[i];
|
||||
}
|
||||
|
||||
hull.Count = count;
|
||||
return PhysicsHull.ValidateHull(hull);
|
||||
return InternalPhysicsHull.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 PhysicsHull(verts, 4);
|
||||
var hull = new InternalPhysicsHull(verts, 4);
|
||||
Set(hull);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Robust.Shared.Physics
|
||||
return true;
|
||||
}
|
||||
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
|
||||
_nodeLookup[item] = proxy;
|
||||
|
||||
return true;
|
||||
@@ -199,11 +199,12 @@ namespace Robust.Shared.Physics
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
proxy = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(newBox.Value, uint.MaxValue, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _b2Tree.MoveProxy(proxy, newBox.Value, Vector2.Zero);
|
||||
_b2Tree.MoveProxy(proxy, newBox.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void QueryAabb(QueryCallbackDelegate callback, Box2 aabb, bool approx = false)
|
||||
@@ -334,7 +335,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, item);
|
||||
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,9 +350,9 @@ namespace Robust.Shared.Physics
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
|
||||
else
|
||||
_b2Tree.MoveProxy(proxy, aabb.Value, Vector2.Zero);
|
||||
_b2Tree.MoveProxy(proxy, aabb.Value);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG_DYNAMIC_TREE")]
|
||||
|
||||
@@ -16,7 +16,7 @@ public interface IBroadPhase
|
||||
|
||||
DynamicTree.Proxy AddProxy(ref FixtureProxy proxy);
|
||||
|
||||
bool MoveProxy(DynamicTree.Proxy proxyId, in Box2 aabb, Vector2 displacement);
|
||||
void MoveProxy(DynamicTree.Proxy proxyId, in Box2 aabb);
|
||||
|
||||
FixtureProxy? GetProxy(DynamicTree.Proxy proxy);
|
||||
|
||||
@@ -54,6 +54,11 @@ 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 {
|
||||
|
||||
@@ -4,6 +4,12 @@ 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.
|
||||
@@ -23,7 +29,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;
|
||||
|
||||
13
Robust.Shared/Physics/PhysicsHull.cs
Normal file
13
Robust.Shared/Physics/PhysicsHull.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ internal record struct Polygon : IPhysShape
|
||||
public Polygon(Vector2[] vertices)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
var hull = PhysicsHull.ComputeHull(vertices, vertices.Length);
|
||||
var hull = InternalPhysicsHull.ComputeHull(vertices, vertices.Length);
|
||||
|
||||
if (hull.Count < 3)
|
||||
{
|
||||
@@ -121,7 +121,7 @@ internal record struct Polygon : IPhysShape
|
||||
return new Polygon(polyShape);
|
||||
}
|
||||
|
||||
private void Set(PhysicsHull hull)
|
||||
private void Set(InternalPhysicsHull hull)
|
||||
{
|
||||
DebugTools.Assert(hull.Count >= 3);
|
||||
var vertexCount = hull.Count;
|
||||
|
||||
@@ -77,6 +77,22 @@ 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>
|
||||
|
||||
@@ -236,7 +236,8 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
Vector2? anchorB = null,
|
||||
string? id = null,
|
||||
TransformComponent? xformA = null,
|
||||
TransformComponent? xformB = null)
|
||||
TransformComponent? xformB = null,
|
||||
int? minimumDistance = null)
|
||||
{
|
||||
if (!Resolve(bodyA, ref xformA) || !Resolve(bodyB, ref xformB))
|
||||
{
|
||||
@@ -246,9 +247,13 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
anchorA ??= Vector2.Zero;
|
||||
anchorB ??= Vector2.Zero;
|
||||
|
||||
var length = Vector2.Transform(anchorA.Value, xformA.WorldMatrix) - Vector2.Transform(anchorB.Value, xformB.WorldMatrix);
|
||||
var vecA = Vector2.Transform(anchorA.Value, xformA.WorldMatrix);
|
||||
var vecB = Vector2.Transform(anchorB.Value, xformB.WorldMatrix);
|
||||
var length = (vecA - vecB).Length();
|
||||
if (minimumDistance != null)
|
||||
length = Math.Max(minimumDistance.Value, length);
|
||||
|
||||
var joint = new DistanceJoint(bodyA, bodyB, anchorA.Value, anchorB.Value, length.Length());
|
||||
var joint = new DistanceJoint(bodyA, bodyB, anchorA.Value, anchorB.Value, length);
|
||||
id ??= GetJointId(joint);
|
||||
joint.ID = id;
|
||||
AddJoint(joint);
|
||||
|
||||
@@ -618,6 +618,24 @@ 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)
|
||||
|
||||
@@ -35,7 +35,6 @@ 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,
|
||||
|
||||
@@ -38,6 +38,9 @@ public sealed class MultiRootInheritanceGraph<T> where T : notnull
|
||||
//check for circular inheritance
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(parent, id))
|
||||
throw new InvalidOperationException($"Self Inheritance detected for id \"{id}\"!");
|
||||
|
||||
var parentsL1 = GetParents(parent);
|
||||
if(parentsL1 == null) continue;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ 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;
|
||||
|
||||
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -352,6 +353,29 @@ 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)
|
||||
|
||||
@@ -26,6 +26,8 @@ namespace Robust.Shared.Utility
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
internal ref T this[int index] => ref _stack[index];
|
||||
|
||||
public void Push(in T element)
|
||||
{
|
||||
if (_count == _capacity)
|
||||
|
||||
22
Robust.Shared/Utility/IReloadManager.cs
Normal file
22
Robust.Shared/Utility/IReloadManager.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
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();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user