mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
19307875cf | ||
|
|
a76c57f841 | ||
|
|
0a604e4a04 | ||
|
|
6218ef6e3f |
@@ -55,7 +55,7 @@
|
||||
<PackageVersion Include="Serilog" Version="4.2.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.6" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
119
RELEASE-NOTES.md
119
RELEASE-NOTES.md
@@ -54,125 +54,10 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 250.0.3
|
||||
## 247.2.2
|
||||
|
||||
|
||||
## 250.0.2
|
||||
|
||||
|
||||
## 250.0.1
|
||||
|
||||
|
||||
## 250.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The default shader now interprets negative color modulation as a flag that indicates that the light map should be ignored.
|
||||
* This can be used to avoid having to change the light map texture, thus reducing draw batches.
|
||||
* Sprite layers that are set to use the "unshaded" shader prototype now use this.
|
||||
* Any fragment shaders that previously the `VtxModulate` colour modulation variable should instead use the new `MODULATE` variable, as the former may now contain negative values.
|
||||
|
||||
### New features
|
||||
|
||||
* Add OtherBody API to contacts.
|
||||
* Make FormattedMessages Equatable.
|
||||
* AnimationCompletionEvent now has the AnimationPlayerComponent.
|
||||
* Add entity description as a tooltip on the entity spawn panel.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix serialization source generator breaking if a class has two partial locations.
|
||||
* Fix map saving throwing a `DirectoryNotFoundException` when given a path with a non-existent directory. Now it once again creates any missing directories.
|
||||
* Fix map loading taking a significant time due to MappingDataNode.Equals calls being slow.
|
||||
|
||||
### Other
|
||||
|
||||
* Add Pure to some Angle methods.
|
||||
|
||||
### Internal
|
||||
|
||||
* Cleanup some warnings in classes.
|
||||
|
||||
|
||||
## 249.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Layer is now read-only on VisibilityComponent and isn't serialized.
|
||||
|
||||
### New features
|
||||
|
||||
* Added a debug overlay for the linear and angular velocity of all entities on the screen. Use the `showvel` and `showangvel` commands to toggle it.
|
||||
* Add a GetWorldManifold overload that doesn't require a span of points.
|
||||
* Added a GetVisMaskEvent. Calling `RefreshVisibilityMask` will raise it and subscribers can update the vismask via the event rather than subscribers having to each manually try and handle the vismask directly.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `BoxContainer` no longer causes stretching children to go below their minimum size.
|
||||
* Fix lights on other grids getting clipped due to ignoring the light range cvar.
|
||||
* Fix the `showvelocities` command.
|
||||
* Fix the DirtyFields overload not being sandbox safe for content.
|
||||
|
||||
### Internal
|
||||
|
||||
* Polygon vertices are now inlined with FixedArray8 and a separate SlimPolygon using FixedArray4 for hot paths rather than using pooled arrays.
|
||||
|
||||
|
||||
## 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.1
|
||||
|
||||
|
||||
## 247.2.0
|
||||
|
||||
@@ -156,7 +156,6 @@ cmd-savemap-not-exist = Target map does not exist.
|
||||
cmd-savemap-init-warning = Attempted to save a post-init map without forcing the save.
|
||||
cmd-savemap-attempt = Attempting to save map {$mapId} to {$path}.
|
||||
cmd-savemap-success = Map successfully saved.
|
||||
cmd-savemap-error = Could not save map! See server log for details.
|
||||
cmd-hint-savemap-id = <MapID>
|
||||
cmd-hint-savemap-path = <Path>
|
||||
cmd-hint-savemap-force = [bool]
|
||||
@@ -294,7 +293,7 @@ cmd-lsgrid-desc = Lists grids.
|
||||
cmd-lsgrid-help = lsgrid
|
||||
|
||||
cmd-addmap-desc = Adds a new empty map to the round. If the mapID already exists, this command does nothing.
|
||||
cmd-addmap-help = addmap <mapID> [pre-init]
|
||||
cmd-addmap-help = addmap <mapID> [initialize]
|
||||
|
||||
cmd-rmmap-desc = Removes a map from the world. You cannot remove nullspace.
|
||||
cmd-rmmap-help = rmmap <mapId>
|
||||
@@ -428,20 +427,11 @@ cmd-entfo-help = Usage: entfo <entityuid>
|
||||
The entity UID can be prefixed with 'c' to convert it to a client entity UID.
|
||||
|
||||
cmd-fuck-desc = Throws an exception
|
||||
cmd-fuck-help = Usage: fuck
|
||||
cmd-fuck-help = Throws an exception
|
||||
|
||||
cmd-showpos-desc = Show the position of all entities on the screen.
|
||||
cmd-showpos-desc = Enables debug drawing over all entity positions in the game.
|
||||
cmd-showpos-help = Usage: showpos
|
||||
|
||||
cmd-showrot-desc = Show the rotation of all entities on the screen.
|
||||
cmd-showrot-help = Usage: showrot
|
||||
|
||||
cmd-showvel-desc = Show the local velocity of all entites on the screen.
|
||||
cmd-showvel-help = Usage: showvel
|
||||
|
||||
cmd-showangvel-desc = Show the angular velocity of all entities on the screen.
|
||||
cmd-showangvel-help = Usage: showangvel
|
||||
|
||||
cmd-sggcell-desc = Lists entities on a snap grid cell.
|
||||
cmd-sggcell-help = Usage: sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
|
||||
|
||||
|
||||
@@ -136,7 +136,6 @@ cmd-savemap-not-exist = O mapa de destino não existe.
|
||||
cmd-savemap-init-warning = Tentativa de salvar um mapa pós-inicialização sem forçar o salvamento.
|
||||
cmd-savemap-attempt = Tentando salvar o mapa {$mapId} em {$path}.
|
||||
cmd-savemap-success = Mapa salvo com sucesso.
|
||||
cmd-savemap-error = Não foi possível salvar o mapa! Consulte o log do servidor para obter detalhes.
|
||||
cmd-hint-savemap-id = <MapID>
|
||||
cmd-hint-savemap-path = <Path>
|
||||
cmd-hint-savemap-force = [bool]
|
||||
|
||||
@@ -26,10 +26,10 @@ public class PhysicsTumblerBenchmark
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
var physics = entManager.System<SharedPhysicsSystem>();
|
||||
var fixtures = entManager.System<FixtureSystem>();
|
||||
var mapUid = entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
SetupTumbler(entManager, mapId);
|
||||
|
||||
for (var i = 0; i < 300; i++)
|
||||
for (var i = 0; i < 800; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
|
||||
@@ -42,9 +42,6 @@ public class PhysicsTumblerBenchmark
|
||||
physics.WakeBody(boxUid, body: box);
|
||||
physics.SetSleepingAllowed(boxUid, box, false);
|
||||
}
|
||||
|
||||
if (entManager.TryGetComponent(mapUid, out BroadphaseComponent? mapBroadphase))
|
||||
entManager.System<SharedBroadphaseSystem>().RebuildBottomUp(mapBroadphase);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
@@ -52,7 +49,7 @@ public class PhysicsTumblerBenchmark
|
||||
{
|
||||
var entManager = _sim.Resolve<IEntityManager>();
|
||||
|
||||
for (var i = 0; i < 1000; i++)
|
||||
for (var i = 0; i < 5000; i++)
|
||||
{
|
||||
entManager.TickUpdate(0.016f, false);
|
||||
}
|
||||
|
||||
@@ -84,19 +84,6 @@ internal partial class AudioManager
|
||||
AL.Listener(ALListenerfv.Orientation, ref at, ref up);
|
||||
}
|
||||
|
||||
void IAudioInternal.Remove(AudioStream stream)
|
||||
{
|
||||
if (stream.ClydeHandle == null)
|
||||
return;
|
||||
|
||||
if (!_audioSampleBuffers.Remove(stream.BufferId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AL.DeleteBuffer(stream.BufferId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public AudioStream LoadAudioOggVorbis(Stream stream, string? name = null)
|
||||
{
|
||||
@@ -133,9 +120,9 @@ internal partial class AudioManager
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(vorbis.TotalSamples / (double) vorbis.SampleRate);
|
||||
return new AudioStream(this, buffer, handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
return new AudioStream(handle, length, (int) vorbis.Channels, name, vorbis.Title, vorbis.Artist);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -192,9 +179,9 @@ internal partial class AudioManager
|
||||
_checkAlError();
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
var length = TimeSpan.FromSeconds(wav.Data.Length / (double) wav.BlockAlign / wav.SampleRate);
|
||||
return new AudioStream(this, buffer, handle, length, wav.NumChannels, name);
|
||||
return new AudioStream(handle, length, wav.NumChannels, name);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -223,8 +210,8 @@ internal partial class AudioManager
|
||||
|
||||
var handle = new ClydeHandle(_audioSampleBuffers.Count);
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
_audioSampleBuffers.Add(buffer, new LoadedAudioSample(buffer));
|
||||
return new AudioStream(this, buffer, handle, length, channels, name);
|
||||
_audioSampleBuffers.Add(new LoadedAudioSample(buffer));
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterGain(float newGain)
|
||||
@@ -306,7 +293,7 @@ internal partial class AudioManager
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
// TODO: This really shouldn't be indexing based on the ClydeHandle...
|
||||
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[stream.BufferId].BufferHandle);
|
||||
AL.Source(source, ALSourcei.Buffer, _audioSampleBuffers[(int) stream.ClydeHandle!.Value].BufferHandle);
|
||||
|
||||
var audioSource = new AudioSource(this, source, stream);
|
||||
_audioSources.Add(source, new WeakReference<BaseAudioSource>(audioSource));
|
||||
@@ -383,12 +370,5 @@ internal partial class AudioManager
|
||||
}
|
||||
|
||||
_bufferedAudioSources.Clear();
|
||||
|
||||
foreach (var buffer in _audioSampleBuffers.Values)
|
||||
{
|
||||
DeleteAudioBufferOnMainThread(buffer.BufferHandle);
|
||||
}
|
||||
|
||||
_audioSampleBuffers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Threading;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
@@ -18,15 +17,13 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
{
|
||||
[Shared.IoC.Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Shared.IoC.Dependency] private readonly ILogManager _logMan = default!;
|
||||
[Shared.IoC.Dependency] private readonly IReloadManager _reload = default!;
|
||||
[Shared.IoC.Dependency] private readonly IResourceCache _cache = default!;
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
private ALDevice _openALDevice;
|
||||
private ALContext _openALContext;
|
||||
|
||||
private readonly Dictionary<int, LoadedAudioSample> _audioSampleBuffers = new();
|
||||
private readonly List<LoadedAudioSample> _audioSampleBuffers = new();
|
||||
|
||||
private readonly Dictionary<int, WeakReference<BaseAudioSource>> _audioSources =
|
||||
new();
|
||||
@@ -119,22 +116,6 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
|
||||
|
||||
_reload.Register("/Audio", "*.ogg");
|
||||
_reload.Register("/Audio", "*.wav");
|
||||
|
||||
_reload.OnChanged += OnReload;
|
||||
}
|
||||
|
||||
private void OnReload(ResPath args)
|
||||
{
|
||||
if (args.Extension != "ogg" &&
|
||||
args.Extension != "wav")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_cache.ReloadResource<AudioResource>(args);
|
||||
}
|
||||
|
||||
internal bool IsMainThread()
|
||||
@@ -159,11 +140,6 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
}
|
||||
}
|
||||
|
||||
internal void LogError(string message)
|
||||
{
|
||||
OpenALSawmill.Error(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like _checkAlError but allows custom data to be passed in as relevant.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,15 +6,8 @@ namespace Robust.Client.Audio;
|
||||
/// <summary>
|
||||
/// Has the metadata for a particular audio stream as well as the relevant internal handle to it.
|
||||
/// </summary>
|
||||
public sealed class AudioStream : IDisposable
|
||||
public sealed class AudioStream
|
||||
{
|
||||
private IAudioInternal _audio;
|
||||
|
||||
/// <summary>
|
||||
/// Buffer ID for this audio in AL.
|
||||
/// </summary>
|
||||
internal int BufferId { get; }
|
||||
|
||||
public TimeSpan Length { get; }
|
||||
internal IClydeHandle? ClydeHandle { get; }
|
||||
public string? Name { get; }
|
||||
@@ -22,10 +15,8 @@ public sealed class AudioStream : IDisposable
|
||||
public string? Artist { get; }
|
||||
public int ChannelCount { get; }
|
||||
|
||||
internal AudioStream(IAudioInternal internalAudio, int bufferId, IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
internal AudioStream(IClydeHandle? handle, TimeSpan length, int channelCount, string? name = null, string? title = null, string? artist = null)
|
||||
{
|
||||
_audio = internalAudio;
|
||||
BufferId = bufferId;
|
||||
ClydeHandle = handle;
|
||||
Length = length;
|
||||
ChannelCount = channelCount;
|
||||
@@ -33,9 +24,4 @@ public sealed class AudioStream : IDisposable
|
||||
Title = title;
|
||||
Artist = artist;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_audio.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
@@ -57,8 +56,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
private float _zOffset;
|
||||
private float _audioEndBuffer;
|
||||
|
||||
public override float ZOffset
|
||||
{
|
||||
@@ -82,6 +79,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
}
|
||||
|
||||
private float _zOffset;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -109,31 +108,20 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
SubscribeNetworkEvent<PlayAudioEntityMessage>(OnEntityAudio);
|
||||
SubscribeNetworkEvent<PlayAudioPositionalMessage>(OnEntityCoordinates);
|
||||
|
||||
Subs.CVar(CfgManager, CVars.AudioEndBuffer, OnAudioBuffer, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioTickRate, OnAudioTickRate, true);
|
||||
InitializeLimit();
|
||||
}
|
||||
|
||||
private void OnAudioBuffer(float value)
|
||||
{
|
||||
_audioEndBuffer = value;
|
||||
}
|
||||
|
||||
private void OnAudioTickRate(int obj)
|
||||
{
|
||||
_audioFrameTime = 1f / obj;
|
||||
_audioFrameTimeRemaining = MathF.Min(_audioFrameTimeRemaining, _audioFrameTime);
|
||||
}
|
||||
|
||||
private void OnAudioState(Entity<AudioComponent> entity, ref AfterAutoHandleStateEvent args)
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
{
|
||||
var component = entity.Comp;
|
||||
|
||||
if (component.LifeStage < ComponentLifeStage.Initialized)
|
||||
return;
|
||||
|
||||
ApplyAudioParams(component.Params, component);
|
||||
component.Source.Global = component.Global;
|
||||
|
||||
@@ -157,29 +145,21 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
case AudioState.Stopped:
|
||||
component.StopPlaying();
|
||||
component.PlaybackPosition = 0f;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
// If playback position changed then update it.
|
||||
var position = (float) ((entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart).TotalSeconds;
|
||||
var currentPosition = entity.Comp.Source.PlaybackPosition;
|
||||
var diff = Math.Abs(position - currentPosition);
|
||||
|
||||
// Don't try to set the audio too far ahead.
|
||||
if (!string.IsNullOrEmpty(entity.Comp.FileName))
|
||||
if (!string.IsNullOrEmpty(component.FileName))
|
||||
{
|
||||
if (position > GetAudioLengthImpl(entity.Comp.FileName).TotalSeconds - _audioEndBuffer)
|
||||
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
|
||||
var currentPosition = component.Source.PlaybackPosition;
|
||||
var diff = Math.Abs(position - currentPosition);
|
||||
|
||||
if (diff > 0.1f)
|
||||
{
|
||||
entity.Comp.StopPlaying();
|
||||
return;
|
||||
component.PlaybackPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
// If the difference is minor then we'll just keep playing it.
|
||||
if (diff > 0.1f)
|
||||
{
|
||||
entity.Comp.PlaybackPosition = position;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -227,10 +207,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
|
||||
{
|
||||
var component = entity.Comp;
|
||||
length ??= GetAudioLength(component.FileName);
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = ((entity.Comp.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
|
||||
|
||||
if (TryAudioLimit(component.FileName))
|
||||
{
|
||||
@@ -254,17 +230,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
// Don't play until first frame so occlusion etc. are correct.
|
||||
component.Gain = 0f;
|
||||
|
||||
// If the offset < buffer than just play it from the start.
|
||||
if (offset < AudioDespawnBuffer)
|
||||
{
|
||||
offset = 0;
|
||||
}
|
||||
// Not enough audio to play
|
||||
else if (offset > length.Value.TotalSeconds - _audioEndBuffer)
|
||||
{
|
||||
component.StopPlaying();
|
||||
return;
|
||||
}
|
||||
length ??= GetAudioLength(component.FileName);
|
||||
|
||||
// If audio came into range then start playback at the correct position.
|
||||
var offset = (Timing.CurTime - component.AudioStart).TotalSeconds % length.Value.TotalSeconds;
|
||||
|
||||
if (offset > 0)
|
||||
{
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace Robust.Client.Audio;
|
||||
/// </summary>
|
||||
internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
{
|
||||
private int _audioBuffer;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void InitializePostWindowing()
|
||||
{
|
||||
@@ -67,11 +65,6 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Remove(AudioStream stream)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopAllAudio()
|
||||
{
|
||||
@@ -108,11 +101,11 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
public AudioStream LoadAudioRaw(ReadOnlySpan<short> samples, int channels, int sampleRate, string? name = null)
|
||||
{
|
||||
var length = TimeSpan.FromSeconds((double) samples.Length / channels / sampleRate);
|
||||
return new AudioStream(this, _audioBuffer++, null, length, channels, name);
|
||||
return new AudioStream(null, length, channels, name);
|
||||
}
|
||||
|
||||
private AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
|
||||
private static AudioStream AudioStreamFromMetadata(AudioMetadata metadata, string? name)
|
||||
{
|
||||
return new AudioStream(this, _audioBuffer++, null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
|
||||
return new AudioStream(null, metadata.Length, metadata.ChannelCount, name, metadata.Title, metadata.Artist);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,6 @@ internal interface IAudioInternal : IAudioManager
|
||||
|
||||
void SetAttenuation(Attenuation attenuation);
|
||||
|
||||
void Remove(AudioStream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Stops all audio from playing.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Robust.Client.Audio.Sources;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
@@ -10,7 +11,7 @@ namespace Robust.Client.Audio;
|
||||
public interface IAudioManager
|
||||
{
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
|
||||
|
||||
AudioStream LoadAudioOggVorbis(Stream stream, string? name = null);
|
||||
|
||||
AudioStream LoadAudioWav(Stream stream, string? name = null);
|
||||
|
||||
@@ -13,7 +13,7 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
/// <summary>
|
||||
/// Underlying stream to the audio.
|
||||
/// </summary>
|
||||
internal readonly AudioStream SourceStream;
|
||||
private readonly AudioStream _sourceStream;
|
||||
|
||||
#if DEBUG
|
||||
private bool _didPositionWarning;
|
||||
@@ -21,7 +21,7 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
|
||||
public AudioSource(AudioManager master, int sourceHandle, AudioStream sourceStream) : base(master, sourceHandle)
|
||||
{
|
||||
SourceStream = sourceStream;
|
||||
_sourceStream = sourceStream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -47,13 +47,13 @@ internal sealed class AudioSource : BaseAudioSource
|
||||
#if DEBUG
|
||||
// OpenAL doesn't seem to want to play stereo positionally.
|
||||
// Log a warning if people try to.
|
||||
if (SourceStream.ChannelCount > 1 && !_didPositionWarning)
|
||||
if (_sourceStream.ChannelCount > 1 && !_didPositionWarning)
|
||||
{
|
||||
_didPositionWarning = true;
|
||||
Master.OpenALSawmill.Warning("Attempting to set position on audio source with multiple audio channels! Stream: '{0}'. Make sure the audio is MONO, not stereo.",
|
||||
SourceStream.Name);
|
||||
_sourceStream.Name);
|
||||
// warning isn't enough, people just ignore it :(
|
||||
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{SourceStream.Name}'. Make sure the audio is MONO, not stereo.");
|
||||
DebugTools.Assert(false, $"Attempting to set position on audio source with multiple audio channels! Stream: '{_sourceStream.Name}'. Make sure the audio is MONO, not stereo.");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -208,12 +208,6 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
}
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
Master.LogError($"Tried to set NaN gain, setting audio source to 0f: {Environment.StackTrace}");
|
||||
value = 0f;
|
||||
}
|
||||
|
||||
_checkDisposed();
|
||||
var priorOcclusion = 1f;
|
||||
if (!IsEfxSupported)
|
||||
|
||||
@@ -46,7 +46,6 @@ using Robust.Shared.Replays;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Upload;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client
|
||||
@@ -103,7 +102,6 @@ namespace Robust.Client
|
||||
deps.Register<ProfViewManager>();
|
||||
deps.Register<IGamePrototypeLoadManager, GamePrototypeLoadManager>();
|
||||
deps.Register<NetworkResourceManager>();
|
||||
deps.Register<IReloadManager, ReloadManager>();
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
|
||||
@@ -173,51 +173,29 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowPositionsCommand : LocalizedEntityCommands
|
||||
internal sealed class ShowPositionsCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
|
||||
public override string Command => "showpos";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_debugDrawing.DebugPositions = !_debugDrawing.DebugPositions;
|
||||
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
|
||||
mgr.DebugPositions = !mgr.DebugPositions;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowRotationsCommand : LocalizedEntityCommands
|
||||
internal sealed class ShowRotationsCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
|
||||
public override string Command => "showrot";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_debugDrawing.DebugRotations = !_debugDrawing.DebugRotations;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowVelocitiesCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showvel";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_debugDrawing.DebugVelocities = !_debugDrawing.DebugVelocities;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowAngularVelocitiesCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showangvel";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_debugDrawing.DebugAngularVelocities = !_debugDrawing.DebugAngularVelocities;
|
||||
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
|
||||
mgr.DebugRotations = !mgr.DebugRotations;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,15 +5,15 @@ using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class ShowPlayerVelocityCommand : LocalizedCommands
|
||||
public sealed class VelocitiesCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
|
||||
public override string Command => "showplayervelocity";
|
||||
public override string Command => "showvelocities";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_entitySystems.GetEntitySystem<ShowPlayerVelocityDebugSystem>().Enabled ^= true;
|
||||
_entitySystems.GetEntitySystem<VelocityDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,221 +1,140 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Client.Debugging;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private bool _debugPositions;
|
||||
private bool _debugRotations;
|
||||
private bool _debugVelocities;
|
||||
private bool _debugAngularVelocities;
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public bool DebugPositions
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
{
|
||||
get => _debugPositions;
|
||||
set
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
|
||||
private bool _debugPositions;
|
||||
private bool _debugRotations;
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugPositions
|
||||
{
|
||||
if (value == DebugPositions)
|
||||
get => _debugPositions;
|
||||
set
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value == DebugPositions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_debugPositions = value;
|
||||
_debugPositions = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the rotation for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugRotations
|
||||
{
|
||||
get => _debugRotations;
|
||||
set
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local rotation.
|
||||
/// </summary>
|
||||
public bool DebugRotations
|
||||
{
|
||||
if (value == DebugRotations)
|
||||
get => _debugRotations;
|
||||
set
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value == DebugRotations)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_debugRotations = value;
|
||||
_debugRotations = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
|
||||
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, EntityManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local velocity for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugVelocities
|
||||
{
|
||||
get => _debugVelocities;
|
||||
set
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
{
|
||||
if (value == DebugVelocities)
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly SharedTransformSystem _transform;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager, SharedTransformSystem transform)
|
||||
{
|
||||
return;
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
_transform = transform;
|
||||
}
|
||||
|
||||
_debugVelocities = value;
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityVelocityOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityVelocityOverlay(EntityManager, _lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityVelocityOverlay>();
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = _transform.GetWorldPositionRotation(entity);
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
|
||||
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the angular velocity for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugAngularVelocities
|
||||
{
|
||||
get => _debugAngularVelocities;
|
||||
set
|
||||
private sealed class EntityRotationOverlay : Overlay
|
||||
{
|
||||
if (value == DebugAngularVelocities)
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityRotationOverlay(EntityLookupSystem lookup, IEntityManager entityManager)
|
||||
{
|
||||
return;
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
}
|
||||
|
||||
_debugAngularVelocities = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityAngularVelocityOverlay>())
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityAngularVelocityOverlay(EntityManager, _lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityAngularVelocityOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
private sealed class EntityPositionOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
const float stubLength = 0.25f;
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
|
||||
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
|
||||
|
||||
var xLine = worldRotation.RotateVec(Vector2.UnitX);
|
||||
var yLine = worldRotation.RotateVec(Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
|
||||
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityRotationOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
|
||||
|
||||
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
|
||||
|
||||
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float multiplier = 0.2f;
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
|
||||
continue;
|
||||
|
||||
var center = _transform.GetWorldPosition(uid);
|
||||
var localVelocity = physicsComp.LinearVelocity;
|
||||
|
||||
if (localVelocity != Vector2.Zero)
|
||||
worldHandle.DrawLine(center, center + localVelocity * multiplier, Color.Yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityAngularVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float multiplier = (float)(0.2 / (2 * System.Math.PI));
|
||||
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
|
||||
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
|
||||
continue;
|
||||
|
||||
var center = _transform.GetWorldPosition(uid);
|
||||
var angularVelocity = physicsComp.AngularVelocity;
|
||||
|
||||
if (angularVelocity != 0.0f)
|
||||
worldHandle.DrawCircle(center, angularVelocity * multiplier, angularVelocity > 0 ? Color.Magenta : Color.Blue, false);
|
||||
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ namespace Robust.Client
|
||||
[Dependency] private readonly IReplayPlaybackManager _replayPlayback = default!;
|
||||
[Dependency] private readonly IReplayRecordingManagerInternal _replayRecording = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IReloadManager _reload = default!;
|
||||
|
||||
private IWebViewManagerHook? _webViewHook;
|
||||
|
||||
@@ -186,7 +185,6 @@ namespace Robust.Client
|
||||
// before prototype load.
|
||||
ProgramShared.FinishCheckBadFileExtensions(checkBadExtensions);
|
||||
|
||||
_reload.Initialize();
|
||||
_reflectionManager.Initialize();
|
||||
_prototypeManager.Initialize();
|
||||
_prototypeManager.LoadDefaultPrototypes();
|
||||
|
||||
@@ -117,7 +117,7 @@ namespace Robust.Client.GameObjects
|
||||
base.DirtyField(uid, comp, fieldName, metadata);
|
||||
}
|
||||
|
||||
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
|
||||
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
|
||||
@@ -30,7 +30,6 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
using SysVec4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -754,20 +753,12 @@ namespace Robust.Client.GameObjects
|
||||
if (layerDatum.Shader == string.Empty)
|
||||
{
|
||||
layer.ShaderPrototype = null;
|
||||
layer.UnShaded = false;
|
||||
layer.Shader = null;
|
||||
}
|
||||
else if (layerDatum.Shader == SpriteSystem.UnshadedId.Id)
|
||||
{
|
||||
layer.ShaderPrototype = SpriteSystem.UnshadedId;
|
||||
layer.UnShaded = true;
|
||||
layer.Shader = null;
|
||||
}
|
||||
else if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.ShaderPrototype = layerDatum.Shader;
|
||||
layer.Shader = prototype.Instance();
|
||||
layer.UnShaded = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -844,28 +835,11 @@ namespace Robust.Client.GameObjects
|
||||
if (!TryGetLayer(layer, out var theLayer, true))
|
||||
return;
|
||||
|
||||
if (shader == null)
|
||||
{
|
||||
theLayer.UnShaded = false;
|
||||
theLayer.Shader = null;
|
||||
theLayer.ShaderPrototype = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (prototype == SpriteSystem.UnshadedId.Id)
|
||||
{
|
||||
theLayer.UnShaded = true;
|
||||
theLayer.ShaderPrototype = SpriteSystem.UnshadedId;
|
||||
theLayer.Shader = null;
|
||||
return;
|
||||
}
|
||||
|
||||
theLayer.UnShaded = false;
|
||||
theLayer.Shader = shader;
|
||||
theLayer.ShaderPrototype = prototype;
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, ShaderInstance? shader, string? prototype = null)
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer, true))
|
||||
return;
|
||||
@@ -1519,18 +1493,10 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
[ViewVariables] public ProtoId<ShaderPrototype>? ShaderPrototype;
|
||||
[ViewVariables] public string? ShaderPrototype;
|
||||
[ViewVariables] public ShaderInstance? Shader;
|
||||
[ViewVariables] public Texture? Texture;
|
||||
|
||||
/// <summary>
|
||||
/// If true, then this layer is drawn without lighting applied.
|
||||
/// Unshaded layers are given special treatment and don't just use the unshaded-shader to avoid having to
|
||||
/// unnecessarily swap out the light texture. This helps the number of batches that need to be sent to the
|
||||
/// GPU while drawing sprites.
|
||||
/// </summary>
|
||||
[ViewVariables] internal bool UnShaded;
|
||||
|
||||
private RSI? _rsi;
|
||||
[ViewVariables] public RSI? RSI
|
||||
{
|
||||
@@ -1697,7 +1663,6 @@ namespace Robust.Client.GameObjects
|
||||
if (toClone.Shader != null)
|
||||
{
|
||||
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
|
||||
UnShaded = toClone.UnShaded;
|
||||
ShaderPrototype = toClone.ShaderPrototype;
|
||||
}
|
||||
Texture = toClone.Texture;
|
||||
@@ -2113,20 +2078,6 @@ namespace Robust.Client.GameObjects
|
||||
drawingHandle.UseShader(Shader);
|
||||
|
||||
var layerColor = _parent.color * Color;
|
||||
|
||||
DebugTools.Assert(layerColor is {R: >= 0, G: >= 0, B: >= 0, A: >= 0}, "Negative colour modulation");
|
||||
|
||||
if (UnShaded)
|
||||
{
|
||||
DebugTools.AssertNull(Shader);
|
||||
|
||||
// Negative modulation values are used to disable light shading in the default shader.
|
||||
// Specifically we set colour = -1 - colour
|
||||
// This ensures that non-negative values become negative & is trivially invertible.
|
||||
// Alternatively we could just clamp the colour to [0,1] and subtract a constant.
|
||||
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
|
||||
}
|
||||
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(textureSize/-2, textureSize);
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var key in remie)
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
var completedEvent = new AnimationCompletedEvent(uid, component, key, true);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var completedEvent = new AnimationCompletedEvent(entity.Owner, entity.Comp, key, false);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
|
||||
}
|
||||
|
||||
@@ -202,33 +202,13 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class AnimationCompletedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity associated with the event.
|
||||
/// </summary>
|
||||
public EntityUid Uid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The animation player component associated with the entity this event was raised on.
|
||||
/// </summary>
|
||||
public AnimationPlayerComponent AnimationPlayer { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The key associated with the animation that was completed.
|
||||
/// </summary>
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the animation finished by getting to its natural end.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(EntityUid,AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// </summary>
|
||||
public bool Finished { get; init; }
|
||||
|
||||
public AnimationCompletedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key, bool finished = true)
|
||||
{
|
||||
Uid = uid;
|
||||
AnimationPlayer = animationPlayer;
|
||||
Key = key;
|
||||
Finished = finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class ShowPlayerVelocityDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
|
||||
|
||||
internal bool Enabled
|
||||
{
|
||||
get => _label.Parent != null;
|
||||
set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_uiManager.WindowRoot.AddChild(_label);
|
||||
}
|
||||
else
|
||||
{
|
||||
_label.Orphan();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Label _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_label = new Label();
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled)
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
|
||||
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -25,6 +25,12 @@ public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
ProtoManager.PrototypesReloaded -= OnProtoReload;
|
||||
}
|
||||
|
||||
protected override void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
base.OnUserInterfaceShutdown(ent, ref args);
|
||||
_savedPositions.Remove(ent.Owner);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OpenUi(Entity<UserInterfaceComponent?> entity, Enum key, bool predicted = false)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
public sealed class VelocityDebugSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly TransformSystem _transform = default!;
|
||||
|
||||
internal bool Enabled { get; set; }
|
||||
|
||||
private Label _label = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_label = new Label();
|
||||
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
|
||||
}
|
||||
|
||||
public override void FrameUpdate(float frameTime)
|
||||
{
|
||||
base.FrameUpdate(frameTime);
|
||||
if (!Enabled)
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
|
||||
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
|
||||
_label.Visible = true;
|
||||
|
||||
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,6 +384,7 @@ namespace Robust.Client.GameStates
|
||||
_processor.UpdateFullRep(curState);
|
||||
}
|
||||
|
||||
IEnumerable<NetEntity> createdEntities;
|
||||
using (_prof.Group("ApplyGameState"))
|
||||
{
|
||||
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
|
||||
@@ -698,9 +699,8 @@ namespace Robust.Client.GameStates
|
||||
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw new KeyNotFoundException();
|
||||
#else
|
||||
continue;
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
@@ -961,9 +961,8 @@ namespace Robust.Client.GameStates
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.Created)
|
||||
@@ -981,9 +980,8 @@ namespace Robust.Client.GameStates
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private const string UniModelMatrix = "modelMatrix";
|
||||
private const string UniTexturePixelSize = "TEXTURE_PIXEL_SIZE";
|
||||
private const string UniMainTexture = "TEXTURE";
|
||||
private const string UniLightTexture = "lightMap"; // TODO CLYDE consistent shader variable naming
|
||||
private const string UniLightTexture = "lightMap";
|
||||
private const string UniProjViewMatrices = "projectionViewMatrices";
|
||||
private const string UniUniformConstants = "uniformConstants";
|
||||
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
|
||||
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapSystem.GetMapOrInvalid(mapId), mapId, worldBox, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldBox, worldBounds);
|
||||
|
||||
if (!overlay.BeforeDraw(args))
|
||||
return;
|
||||
@@ -178,7 +178,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapSystem.GetMapOrInvalid(mapId), mapId, worldAABB, worldBounds);
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapManager.GetMapEntityIdOrThrow(mapId), mapId, worldAABB, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
|
||||
@@ -98,11 +98,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private LightCapacityComparer _lightCap = new();
|
||||
private ShadowCapacityComparer _shadowCap = new ShadowCapacityComparer();
|
||||
|
||||
private float _maxLightRadius;
|
||||
|
||||
private unsafe void InitLighting()
|
||||
{
|
||||
_cfg.OnValueChanged(CVars.MaxLightRadius, val => { _maxLightRadius = val;}, true);
|
||||
|
||||
|
||||
// Other...
|
||||
LoadLightingShaders();
|
||||
@@ -619,9 +617,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Use worldbounds for this one as we only care if the light intersects our actual bounds
|
||||
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
var state = (this, count: 0, shadowCastingCount: 0, xforms, worldAABB);
|
||||
var lightAabb = worldAABB.Enlarged(_maxLightRadius);
|
||||
|
||||
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, lightAabb))
|
||||
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, worldAABB))
|
||||
{
|
||||
var bounds = _transformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
|
||||
comp.Tree.QueryAabb(ref state, LightQuery, bounds);
|
||||
|
||||
@@ -10,7 +10,6 @@ using Robust.Client.Input;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -24,7 +23,6 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
@@ -50,8 +48,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
[Dependency] private readonly ClientEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IReloadManager _reloads = default!;
|
||||
|
||||
private GLUniformBuffer<ProjViewMatrices> ProjViewUBO = default!;
|
||||
private GLUniformBuffer<UniformConstants> UniformConstantsUBO = default!;
|
||||
@@ -103,16 +99,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
|
||||
_sawmillWin = _logManager.GetSawmill("clyde.win");
|
||||
|
||||
_reloads.Register("/Shaders", "*.swsl");
|
||||
_reloads.Register("/Textures/Shaders", "*.swsl");
|
||||
_reloads.Register("/Textures", "*.jpg");
|
||||
_reloads.Register("/Textures", "*.jpeg");
|
||||
_reloads.Register("/Textures", "*.png");
|
||||
_reloads.Register("/Textures", "*.webp");
|
||||
|
||||
_reloads.OnChanged += OnChange;
|
||||
_proto.PrototypesReloaded += OnProtoReload;
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayVSync, VSyncChanged, true);
|
||||
_cfg.OnValueChanged(CVars.DisplayWindowMode, WindowModeChanged, true);
|
||||
@@ -136,38 +122,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return InitWindowing();
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.WasModified<ShaderPrototype>())
|
||||
return;
|
||||
|
||||
foreach (var shader in obj.ByType[typeof(ShaderPrototype)].Modified.Keys)
|
||||
{
|
||||
_resourceCache.ReloadResource<ShaderSourceResource>(shader);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnChange(ResPath obj)
|
||||
{
|
||||
if ((obj.TryRelativeTo(new ResPath("/Shaders"), out _) || obj.TryRelativeTo(new ResPath("/Textures/Shaders"), out _)) && obj.Extension == "swsl")
|
||||
{
|
||||
_resourceCache.ReloadResource<ShaderSourceResource>(obj);
|
||||
}
|
||||
|
||||
if (obj.TryRelativeTo(new ResPath("/Textures"), out _) && !obj.TryRelativeTo(new ResPath("/Textures/Tiles"), out _))
|
||||
{
|
||||
if (obj.Extension == "jpg" || obj.Extension == "jpeg" || obj.Extension == "webp")
|
||||
{
|
||||
_resourceCache.ReloadResource<TextureResource>(obj);
|
||||
}
|
||||
|
||||
if (obj.Extension == "png")
|
||||
{
|
||||
_resourceCache.ReloadResource<TextureResource>(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool InitializePostWindowing()
|
||||
{
|
||||
_gameThread = Thread.CurrentThread;
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
// UV coordinates in texture-space. I.e., (0,0) is the corner of the texture currently being used to draw.
|
||||
// When drawing a sprite from a texture atlas, (0,0) is the corner of the atlas, not the specific sprite being drawn.
|
||||
varying highp vec2 UV;
|
||||
|
||||
// UV coordinates in quad-space. I.e., when drawing a sprite from a texture atlas (0,0) is the corner of the sprite
|
||||
// currently being drawn.
|
||||
varying highp vec2 UV2;
|
||||
|
||||
// TBH I'm not sure what this is for. I think it is scree UV coordiantes, i.e., FRAGCOORD.xy * SCREEN_PIXEL_SIZE ?
|
||||
// TODO CLYDE Is this still needed?
|
||||
varying highp vec2 Pos;
|
||||
|
||||
// Vertex colour modulation. Note that negative values imply that the LIGHTMAP should be ignored. This is used to avoid
|
||||
// having to set the texture to a white/blank texture for sprites that have no light shading applied.
|
||||
varying highp vec4 VtxModulate;
|
||||
|
||||
// The current light map. Unless disabled, this is automatically sampled to create the LIGHT vector, which is then used
|
||||
// to modulate the output colour.
|
||||
// TODO CLYDE consistent shader variable naming
|
||||
uniform sampler2D lightMap;
|
||||
|
||||
// [SHADER_HEADER_CODE]
|
||||
@@ -25,37 +11,11 @@ void main()
|
||||
{
|
||||
highp vec4 FRAGCOORD = gl_FragCoord;
|
||||
|
||||
// The output colour. This should get set by the shader code block.
|
||||
// This will get modified by the LIGHT and MODULATE vectors.
|
||||
lowp vec4 COLOR;
|
||||
|
||||
// The light colour, usually sampled from the LIGHTMAP
|
||||
lowp vec4 LIGHT;
|
||||
|
||||
// Colour modulation vector.
|
||||
highp vec4 MODULATE;
|
||||
|
||||
// Sample the texture outside of the branch / with uniform control flow.
|
||||
LIGHT = texture2D(lightMap, Pos);
|
||||
|
||||
if (VtxModulate.x < 0.0)
|
||||
{
|
||||
// Negative VtxModulate implies unshaded/no lighting.
|
||||
MODULATE = -1.0 - VtxModulate;
|
||||
LIGHT = vec4(1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
MODULATE = VtxModulate;
|
||||
}
|
||||
|
||||
// TODO CLYDE consistent shader variable naming
|
||||
// Requires breaking changes.
|
||||
lowp vec3 lightSample = LIGHT.xyz;
|
||||
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
LIGHT.xyz = lightSample;
|
||||
|
||||
gl_FragColor = zAdjustResult(COLOR * MODULATE * LIGHT);
|
||||
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ uniform mat3 modelMatrix;
|
||||
// Allows us to do texture atlassing with texture coordinates 0->1
|
||||
// Input texture coordinates get mapped to this range.
|
||||
uniform vec4 modifyUV;
|
||||
// TODO CLYDE Is this still needed?
|
||||
|
||||
// [SHADER_HEADER_CODE]
|
||||
|
||||
@@ -40,15 +39,5 @@ void main()
|
||||
Pos = (VERTEX + 1.0) / 2.0;
|
||||
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
|
||||
UV2 = tCoord2;
|
||||
|
||||
// Negative modulation is being used as a hacky way to squeeze in lighting data.
|
||||
// I.e., negative modulation implies we ignore the lighting.
|
||||
if (modulate.x < 0.0)
|
||||
{
|
||||
VtxModulate = -1.0 - zFromSrgb(-1.0 - modulate);
|
||||
}
|
||||
else
|
||||
{
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
}
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
|
||||
// TODO CLYDE consistent shader variable naming
|
||||
uniform sampler2D lightMap;
|
||||
|
||||
// [SHADER_HEADER_CODE]
|
||||
|
||||
@@ -16,7 +16,7 @@ using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
[Prototype]
|
||||
[Prototype("shader")]
|
||||
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Map;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Toolshed;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
@@ -18,8 +23,6 @@ namespace Robust.Client.Map
|
||||
{
|
||||
internal sealed class ClydeTileDefinitionManager : TileDefinitionManager, IClydeTileDefinitionManager, IPostInjectInit
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IReloadManager _reload = default!;
|
||||
[Dependency] private readonly IResourceManager _manager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
@@ -54,30 +57,6 @@ namespace Robust.Client.Map
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_protoManager.PrototypesReloaded += OnProtoReload;
|
||||
|
||||
_reload.Register("/Textures/Tiles", "*.png");
|
||||
_reload.OnChanged += OnReload;
|
||||
|
||||
_genTextureAtlas();
|
||||
}
|
||||
|
||||
private void OnProtoReload(PrototypesReloadedEventArgs obj)
|
||||
{
|
||||
if (!obj.WasModified<ITileDefinition>())
|
||||
return;
|
||||
|
||||
_genTextureAtlas();
|
||||
}
|
||||
|
||||
private void OnReload(ResPath obj)
|
||||
{
|
||||
if (obj.Extension != "png")
|
||||
return;
|
||||
|
||||
if (!obj.TryRelativeTo(new ResPath("/Textures/Tiles"), out _))
|
||||
return;
|
||||
|
||||
_genTextureAtlas();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Timing;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Robust.Client.Prototypes
|
||||
{
|
||||
public sealed class ClientPrototypeManager : PrototypeManager
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IClientGameTiming _timing = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _controller = default!;
|
||||
[Dependency] private readonly IReloadManager _reload = default!;
|
||||
|
||||
private readonly List<FileSystemWatcher> _watchers = new();
|
||||
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
|
||||
private CancellationTokenSource _reloadToken = new();
|
||||
private readonly HashSet<ResPath> _reloadQueue = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -25,8 +37,9 @@ namespace Robust.Client.Prototypes
|
||||
|
||||
_netManager.RegisterNetMessage<MsgReloadPrototypes>(accept: NetMessageAccept.Server);
|
||||
|
||||
_reload.Register("/Prototypes", "*.yml");
|
||||
_reload.OnChanged += ReloadPrototypeQueue;
|
||||
_clyde.OnWindowFocused += WindowFocusedChanged;
|
||||
|
||||
WatchResources();
|
||||
}
|
||||
|
||||
public override void LoadDefaultPrototypes(Dictionary<Type, HashSet<string>>? changed = null)
|
||||
@@ -36,27 +49,99 @@ namespace Robust.Client.Prototypes
|
||||
ResolveResults();
|
||||
}
|
||||
|
||||
private void ReloadPrototypeQueue(ResPath file)
|
||||
private void WindowFocusedChanged(WindowFocusedEventArgs args)
|
||||
{
|
||||
if (file.Extension != "yml")
|
||||
return;
|
||||
#if TOOLS
|
||||
if (args.Focused && _reloadQueue.Count > 0)
|
||||
{
|
||||
Timer.Spawn(_reloadDelay, ReloadPrototypeQueue, _reloadToken.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
_reloadToken.Cancel();
|
||||
_reloadToken = new CancellationTokenSource();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ReloadPrototypeQueue()
|
||||
{
|
||||
#if TOOLS
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
var msg = new MsgReloadPrototypes
|
||||
{
|
||||
Paths = [file]
|
||||
};
|
||||
var msg = new MsgReloadPrototypes();
|
||||
msg.Paths = _reloadQueue.ToArray();
|
||||
_netManager.ClientSendMessage(msg);
|
||||
|
||||
// Reloading prototypes modifies entities. This currently causes some state management debug asserts to
|
||||
// fail. To avoid this, we set `IGameTiming.ApplyingState` to true, even though this isn't really applying a
|
||||
// server state.
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
ReloadPrototypes([file]);
|
||||
ReloadPrototypes(_reloadQueue);
|
||||
|
||||
_reloadQueue.Clear();
|
||||
|
||||
Logger.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void WatchResources()
|
||||
{
|
||||
if (!_cfg.GetCVar(CVars.ResPrototypeReloadWatch))
|
||||
return;
|
||||
|
||||
#if TOOLS
|
||||
foreach (var path in Resources.GetContentRoots().Select(r => r.ToString())
|
||||
.Where(r => Directory.Exists(r + "/Prototypes")).Select(p => p + "/Prototypes"))
|
||||
{
|
||||
var watcher = new FileSystemWatcher(path, "*.yml")
|
||||
{
|
||||
IncludeSubdirectories = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite
|
||||
};
|
||||
|
||||
watcher.Changed += (_, args) =>
|
||||
{
|
||||
switch (args.ChangeType)
|
||||
{
|
||||
case WatcherChangeTypes.Renamed:
|
||||
case WatcherChangeTypes.Deleted:
|
||||
return;
|
||||
case WatcherChangeTypes.Created:
|
||||
// case WatcherChangeTypes.Deleted:
|
||||
case WatcherChangeTypes.Changed:
|
||||
case WatcherChangeTypes.All:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
TaskManager.RunOnMainThread(() =>
|
||||
{
|
||||
var file = new ResPath(args.FullPath);
|
||||
|
||||
foreach (var root in Resources.GetContentRoots())
|
||||
{
|
||||
if (!file.TryRelativeTo(root, out var relative))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_reloadQueue.Add(relative.Value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
watcher.EnableRaisingEvents = true;
|
||||
_watchers.Add(watcher);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.Error($"Watching resources in path {path} threw an exception:\n{ex}");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Client.Audio;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -27,26 +26,22 @@ public sealed class AudioResource : BaseResource
|
||||
throw new FileNotFoundException("Content file does not exist for audio sample.");
|
||||
}
|
||||
|
||||
using var fileStream = cache.ContentFileRead(path);
|
||||
var audioManager = dependencies.Resolve<IAudioInternal>();
|
||||
if (path.Extension == "ogg")
|
||||
using (var fileStream = cache.ContentFileRead(path))
|
||||
{
|
||||
AudioStream = audioManager.LoadAudioOggVorbis(fileStream, path.ToString());
|
||||
var audioManager = dependencies.Resolve<IAudioInternal>();
|
||||
if (path.Extension == "ogg")
|
||||
{
|
||||
AudioStream = audioManager.LoadAudioOggVorbis(fileStream, path.ToString());
|
||||
}
|
||||
else if (path.Extension == "wav")
|
||||
{
|
||||
AudioStream = audioManager.LoadAudioWav(fileStream, path.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unable to load audio files outside of ogg Vorbis or PCM wav");
|
||||
}
|
||||
}
|
||||
else if (path.Extension == "wav")
|
||||
{
|
||||
AudioStream = audioManager.LoadAudioWav(fileStream, path.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Unable to load audio files outside of ogg Vorbis or PCM wav");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reload(IDependencyCollection dependencies, ResPath path, CancellationToken ct = default)
|
||||
{
|
||||
dependencies.Resolve<IAudioInternal>().Remove(AudioStream);
|
||||
Load(dependencies, path);
|
||||
}
|
||||
|
||||
public AudioResource(AudioStream stream) : base()
|
||||
|
||||
@@ -71,11 +71,21 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
// First, we measure non-stretching children.
|
||||
var stretching = new List<Control>();
|
||||
float totalStretchRatio = 0;
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (!child.Visible)
|
||||
continue;
|
||||
|
||||
var stretch = Vertical ? child.VerticalExpand : child.HorizontalExpand;
|
||||
if (stretch)
|
||||
{
|
||||
totalStretchRatio += child.SizeFlagsStretchRatio;
|
||||
stretching.Add(child);
|
||||
continue;
|
||||
}
|
||||
|
||||
child.Measure(availableSize);
|
||||
|
||||
if (Vertical)
|
||||
@@ -92,6 +102,35 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
if (stretching.Count == 0)
|
||||
return desiredSize;
|
||||
|
||||
// Then we measure stretching children
|
||||
foreach (var child in stretching)
|
||||
{
|
||||
var size = availableSize;
|
||||
if (Vertical)
|
||||
{
|
||||
size.Y *= child.SizeFlagsStretchRatio / totalStretchRatio;
|
||||
child.Measure(size);
|
||||
desiredSize.Y += child.DesiredSize.Y;
|
||||
desiredSize.X = Math.Max(desiredSize.X, child.DesiredSize.X);
|
||||
}
|
||||
else
|
||||
{
|
||||
size.X *= child.SizeFlagsStretchRatio / totalStretchRatio;
|
||||
child.Measure(size);
|
||||
desiredSize.X += child.DesiredSize.X;
|
||||
desiredSize.Y = Math.Max(desiredSize.Y, child.DesiredSize.Y);
|
||||
}
|
||||
|
||||
// TODO Maybe make BoxContainer.MeasureOverride more rigorous.
|
||||
// This should check if size < desired size. If it is, treat child as non-stretching (see the code in
|
||||
// ArrangeOverride). This requires remeasuring all stretching controls + the control that just became
|
||||
// non-stretching. But the re-measured controls might then become smaller (e.g. rich text wrapping),
|
||||
// leading to a recursion problem.
|
||||
}
|
||||
|
||||
return desiredSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
@@ -176,71 +176,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_scrollBar.MoveToEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace the current list of items with the items in newItems.
|
||||
/// newItems should be in the order which they should appear in the list,
|
||||
/// and items are considered equal if the Item text is equal in each item.
|
||||
///
|
||||
/// Provided the existing items have not been re-ordered relative to each
|
||||
/// other, any items which already exist in the list are not destroyed,
|
||||
/// which maintains consistency of scrollbars, selected items, etc.
|
||||
/// </summary>
|
||||
/// <param name="newItems">The list of items to update this list to</param>
|
||||
public void SetItems(List<Item> newItems)
|
||||
{
|
||||
SetItems(newItems, (a,b) => string.Compare(a.Text, b.Text));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// This variant allows for a custom equality operator to compare items, when
|
||||
/// comparing the Item text is not desired.
|
||||
/// </summary>
|
||||
/// <param name="itemCmp">Comparison function to compare existing to new items.</param>
|
||||
public void SetItems(List<Item> newItems, Comparison<Item> itemCmp)
|
||||
{
|
||||
// Walk through the existing items in this list and in newItems
|
||||
// in parallel to synchronize our items with those in newItems.
|
||||
int i = this.Count - 1;
|
||||
int j = newItems.Count - 1;
|
||||
while(i >= 0 && j >= 0)
|
||||
{
|
||||
var cmpResult = itemCmp(this[i], newItems[j]);
|
||||
if (cmpResult == 0)
|
||||
{
|
||||
// This item exists in both our list and `newItems`. Nothing to do.
|
||||
i--;
|
||||
j--;
|
||||
}
|
||||
else if (cmpResult > 0)
|
||||
{
|
||||
// Item exists in our list, but not in `newItems`. Remove it.
|
||||
RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
else if (cmpResult < 0)
|
||||
{
|
||||
// A new entry which doesn't exist in our list. Insert it.
|
||||
Insert(i + 1, newItems[j]);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
// Any remaining items in our list don't exist in `newItems` so remove them
|
||||
while (i >= 0)
|
||||
{
|
||||
RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
// And finally, any remaining items in `newItems` don't exist in our list. Create them.
|
||||
while (j >= 0)
|
||||
{
|
||||
Insert(0, newItems[j]);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
// Without this attribute, this would compile into a property called "Item", causing problems with the Item class.
|
||||
[System.Runtime.CompilerServices.IndexerName("IndexItem")]
|
||||
public Item this[int index]
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[Virtual]
|
||||
public class LineEdit : Control
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Robust.Client.UserInterface.Controls;
|
||||
public sealed class TextEdit : Control
|
||||
{
|
||||
[Dependency] private readonly IClipboardManager _clipboard = null!;
|
||||
[Dependency] private readonly IClyde _clyde = null!;
|
||||
|
||||
// @formatter:off
|
||||
public const string StylePropertyCursorColor = "cursor-color";
|
||||
|
||||
@@ -59,7 +59,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
}
|
||||
|
||||
button.EntityLabel.Text = entityLabelText;
|
||||
button.ActualButton.ToolTip = prototype.Description;
|
||||
|
||||
if (prototype == SelectedPrototype)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
[Prototype]
|
||||
[Prototype("font")]
|
||||
public sealed partial class FontPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Robust.Client.Utility;
|
||||
|
||||
internal sealed class ReloadManager : IReloadManager
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILogManager _logMan = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly ITaskManager _tasks = default!;
|
||||
|
||||
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
|
||||
private CancellationTokenSource _reloadToken = new();
|
||||
private readonly HashSet<ResPath> _reloadQueue = new();
|
||||
private List<FileSystemWatcher> _watchers = new();
|
||||
|
||||
public event Action<ResPath>? OnChanged;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_sawmill = _logMan.GetSawmill("reload");
|
||||
_clyde.OnWindowFocused += WindowFocusedChanged;
|
||||
}
|
||||
|
||||
private void WindowFocusedChanged(WindowFocusedEventArgs args)
|
||||
{
|
||||
#if TOOLS
|
||||
if (args.Focused && _reloadQueue.Count > 0)
|
||||
{
|
||||
Timer.Spawn(_reloadDelay, ReloadFiles, _reloadToken.Token);
|
||||
}
|
||||
else
|
||||
{
|
||||
_reloadToken.Cancel();
|
||||
_reloadToken = new CancellationTokenSource();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void ReloadFiles()
|
||||
{
|
||||
foreach (var file in _reloadQueue)
|
||||
{
|
||||
var rootedFile = file.ToRootedPath();
|
||||
|
||||
if (!_res.ContentFileExists(rootedFile))
|
||||
continue;
|
||||
|
||||
_sawmill.Info($"Reloading {rootedFile}");
|
||||
OnChanged?.Invoke(rootedFile);
|
||||
}
|
||||
|
||||
_reloadQueue.Clear();
|
||||
}
|
||||
|
||||
public void Register(string directory, string filter)
|
||||
{
|
||||
if (!_cfg.GetCVar(CVars.ResPrototypeReloadWatch))
|
||||
return;
|
||||
|
||||
#if TOOLS
|
||||
foreach (var root in _res.GetContentRoots())
|
||||
{
|
||||
var path = root + directory;
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var watcher = new FileSystemWatcher(path, filter)
|
||||
{
|
||||
IncludeSubdirectories = true,
|
||||
NotifyFilter = NotifyFilters.LastWrite
|
||||
};
|
||||
|
||||
_watchers.Add(watcher);
|
||||
|
||||
watcher.Changed += OnWatch;
|
||||
|
||||
try
|
||||
{
|
||||
watcher.EnableRaisingEvents = true;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.Error($"Watching resources in path {path} threw an exception:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
void OnWatch(object sender, FileSystemEventArgs args)
|
||||
{
|
||||
switch (args.ChangeType)
|
||||
{
|
||||
case WatcherChangeTypes.Renamed:
|
||||
case WatcherChangeTypes.Deleted:
|
||||
return;
|
||||
case WatcherChangeTypes.Created:
|
||||
// case WatcherChangeTypes.Deleted:
|
||||
case WatcherChangeTypes.Changed:
|
||||
case WatcherChangeTypes.All:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_tasks.RunOnMainThread(() =>
|
||||
{
|
||||
var fullPath = args.FullPath.Replace(Path.DirectorySeparatorChar, '/');
|
||||
var file = new ResPath(fullPath);
|
||||
|
||||
foreach (var rootIter in _res.GetContentRoots())
|
||||
{
|
||||
if (!file.TryRelativeTo(rootIter, out var relative))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_reloadQueue.Add(relative.Value);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PolySharpIncludeGeneratedTypes>System.Index;System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;System.Runtime.CompilerServices.IsExternalInit;System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute</PolySharpIncludeGeneratedTypes>
|
||||
<NoWarn>RS2008;RS1038</NoWarn>
|
||||
<NoWarn>RS2008</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -35,20 +35,13 @@ public class Generator : IIncrementalGenerator
|
||||
.Where(static type => type != null);
|
||||
|
||||
initContext.RegisterSourceOutput(
|
||||
dataDefinitions.Collect(),
|
||||
static (sourceContext, sources) =>
|
||||
dataDefinitions,
|
||||
static (sourceContext, source) =>
|
||||
{
|
||||
var done = new HashSet<string>();
|
||||
// TODO: deduplicate based on name?
|
||||
var (name, code) = source!.Value;
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var (name, code) = source!.Value;
|
||||
|
||||
if (!done.Add(name))
|
||||
continue;
|
||||
|
||||
sourceContext.AddSource(name, code);
|
||||
}
|
||||
sourceContext.AddSource(name, code);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,15 +43,8 @@ namespace Robust.Server.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
bool saveSuccess = _ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
|
||||
if(saveSuccess)
|
||||
{
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError("Save unsuccessful!");
|
||||
}
|
||||
_ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
@@ -214,15 +207,8 @@ namespace Robust.Server.Console.Commands
|
||||
}
|
||||
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-attempt", ("mapId", mapId), ("path", args[1])));
|
||||
bool saveSuccess = _system.GetEntitySystem<MapLoaderSystem>().TrySaveMap(mapId, new ResPath(args[1]));
|
||||
if(saveSuccess)
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-savemap-error"));
|
||||
}
|
||||
_system.GetEntitySystem<MapLoaderSystem>().TrySaveMap(mapId, new ResPath(args[1]));
|
||||
shell.WriteLine(Loc.GetString("cmd-savemap-success"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Robust.Server.ServerStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="IStatusHandlerContext"/>.
|
||||
/// </summary>
|
||||
public static class StatusExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Add <c>Access-Control-Allow-Origin: *</c> to the response headers for this request.
|
||||
/// </summary>
|
||||
public static void AddAllowOriginAny(this IStatusHandlerContext context)
|
||||
{
|
||||
context.ResponseHeaders.Add("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,6 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
OnStatusRequest?.Invoke(jObject);
|
||||
|
||||
context.AddAllowOriginAny();
|
||||
await context.RespondJsonAsync(jObject);
|
||||
|
||||
return true;
|
||||
@@ -122,7 +121,6 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
OnInfoRequest?.Invoke(jObject);
|
||||
|
||||
context.AddAllowOriginAny();
|
||||
await context.RespondJsonAsync(jObject);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -93,7 +93,6 @@ namespace Robust.Shared.Maths
|
||||
private const double CardinalSegment = 2 * Math.PI / 4.0; // Cut the circle into 4 pieces
|
||||
private const double CardinalOffset = CardinalSegment / 2.0; // offset the pieces by 1/2 their size
|
||||
|
||||
[Pure]
|
||||
public readonly Direction GetCardinalDir()
|
||||
{
|
||||
var ang = Theta % (2 * Math.PI);
|
||||
@@ -168,7 +167,6 @@ namespace Robust.Shared.Maths
|
||||
/// <summary>
|
||||
/// Removes revolutions from a positive or negative angle to make it as small as possible.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public readonly Angle Reduced()
|
||||
{
|
||||
return new(Reduce(Theta));
|
||||
@@ -215,13 +213,11 @@ namespace Robust.Shared.Maths
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public readonly Angle Opposite()
|
||||
{
|
||||
return new Angle(FlipPositive(Theta-Math.PI));
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public readonly Angle FlipPositive()
|
||||
{
|
||||
return new(FlipPositive(Theta));
|
||||
|
||||
@@ -212,48 +212,6 @@ namespace Robust.Shared.Maths
|
||||
return surfaceIntersect / (Area(this) + Area(other) - surfaceIntersect);
|
||||
}
|
||||
|
||||
public readonly bool IsValid()
|
||||
{
|
||||
var d = Vector2.Subtract(TopRight, BottomLeft);
|
||||
bool valid = d.X >= 0.0f && d.Y >= 0.0f;
|
||||
valid = valid && BottomLeft.IsValid() && TopRight.IsValid();
|
||||
return valid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enlarges this box to contain another box.
|
||||
/// </summary>
|
||||
public bool EnlargeAabb(Box2 other)
|
||||
{
|
||||
var changed = false;
|
||||
|
||||
if (other.Left < Left)
|
||||
{
|
||||
Left = other.Left;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (other.Bottom < Bottom)
|
||||
{
|
||||
Bottom = other.Bottom;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (Right < other.Right)
|
||||
{
|
||||
Right = other.Right;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (other.Top < Top)
|
||||
{
|
||||
Top = other.Top;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the smallest rectangle that contains both of the rectangles.
|
||||
/// </summary>
|
||||
@@ -443,15 +401,6 @@ namespace Robust.Shared.Maths
|
||||
public static float Perimeter(in Box2 box)
|
||||
=> (box.Width + box.Height) * 2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public static Box2 Union(Box2 a, Box2 b)
|
||||
{
|
||||
return new Box2(
|
||||
Vector2.Min(a.BottomLeft, b.BottomLeft),
|
||||
Vector2.Max(a.TopRight, b.TopRight));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public static Box2 Union(in Vector2 a, in Vector2 b)
|
||||
|
||||
@@ -682,7 +682,6 @@ namespace Robust.Shared.Maths
|
||||
/// (which is copied to the output's Alpha value).
|
||||
/// Each has a range of 0.0 to 1.0.
|
||||
/// </param>
|
||||
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
|
||||
public static Color FromHcy(Vector4 hcy)
|
||||
{
|
||||
var hue = hcy.X * 360.0f;
|
||||
@@ -751,7 +750,6 @@ namespace Robust.Shared.Maths
|
||||
/// </returns>
|
||||
/// <param name="rgb">Color value to convert.</param>
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
||||
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
|
||||
public static Vector4 ToHcy(Color rgb)
|
||||
{
|
||||
var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
@@ -239,7 +238,6 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
/// <param name="dir"></param>
|
||||
/// <returns></returns>
|
||||
[Pure]
|
||||
public static Angle ToAngle(this Direction dir)
|
||||
{
|
||||
var ang = Segment * (int) dir;
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
|
||||
[assembly: InternalsVisibleTo("Robust.Client")]
|
||||
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
|
||||
[assembly: InternalsVisibleTo("Content.Benchmarks")]
|
||||
|
||||
@@ -15,7 +15,6 @@ public static class Vector2Helpers
|
||||
/// </summary>
|
||||
public static readonly Vector2 Half = new(0.5f, 0.5f);
|
||||
|
||||
[Pure]
|
||||
public static bool IsValid(this Vector2 v)
|
||||
{
|
||||
if (float.IsNaN(v.X) || float.IsNaN(v.Y))
|
||||
@@ -31,13 +30,6 @@ public static class Vector2Helpers
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public static Vector2 MulAdd(Vector2 a, float s, Vector2 b)
|
||||
{
|
||||
return new Vector2(a.X + s * b.X, a.Y + s * b.Y);
|
||||
}
|
||||
|
||||
public static Vector2 GetLengthAndNormalize(this Vector2 v, ref float length)
|
||||
{
|
||||
length = v.Length();
|
||||
|
||||
@@ -5,9 +5,6 @@ using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Audio
|
||||
{
|
||||
@@ -32,25 +29,11 @@ namespace Robust.Shared.Audio
|
||||
[DataDefinition]
|
||||
public partial struct AudioParams
|
||||
{
|
||||
private float _volume = Default.Volume;
|
||||
|
||||
/// <summary>
|
||||
/// Base volume to play the audio at, in dB.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Volume
|
||||
{
|
||||
get => _volume;
|
||||
set
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
value = float.NegativeInfinity;
|
||||
}
|
||||
|
||||
_volume = value;
|
||||
}
|
||||
}
|
||||
public float Volume { get; set; } = Default.Volume;
|
||||
|
||||
/// <summary>
|
||||
/// Scale for the audio pitch.
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Shared.Audio;
|
||||
/// Contains audio defaults to set for sounds.
|
||||
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
|
||||
/// </summary>
|
||||
[Prototype]
|
||||
[Prototype("audioPreset")]
|
||||
public sealed partial class AudioPresetPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
|
||||
[Prototype]
|
||||
[Prototype("soundCollection")]
|
||||
public sealed partial class SoundCollectionPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
@@ -37,7 +37,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
|
||||
[Dependency] protected readonly SharedTransformSystem XformSystem = default!;
|
||||
|
||||
public const float AudioDespawnBuffer = 1f;
|
||||
private const float AudioDespawnBuffer = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Default max range at which the sound can be heard.
|
||||
@@ -412,13 +412,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
if (component.Params.Volume.Equals(value))
|
||||
return;
|
||||
|
||||
// Not a log error for now because if something has a negative infinity volume (i.e. 0 gain) then subtracting from it can
|
||||
// easily cause this and making callers deal with it everywhere is quite annoying.
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
value = float.NegativeInfinity;
|
||||
}
|
||||
|
||||
component.Params.Volume = value;
|
||||
component.Volume = value;
|
||||
DirtyField(entity.Value, component, nameof(AudioComponent.Params));
|
||||
|
||||
@@ -1233,12 +1233,6 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> AudioRaycastLength =
|
||||
CVarDef.Create("audio.raycast_length", SharedAudioSystem.DefaultSoundRange, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum offset for audio to be played at from its full duration. If it's past this then the audio won't be played.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AudioEndBuffer =
|
||||
CVarDef.Create("audio.end_buffer", 0.01f, CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// Tickrate for audio calculations.
|
||||
/// OpenAL recommends 30TPS. This is to avoid running raycasts every frame especially for high-refresh rate monitors.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
@@ -186,9 +186,7 @@ public static class CompletionHelper
|
||||
|
||||
public static IEnumerable<CompletionOption> MapUids(IEntityManager? entManager = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entManager);
|
||||
|
||||
return Components<MapComponent>(string.Empty, entManager, limit: 128);
|
||||
return Components<MapComponent>(string.Empty, entManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -196,7 +194,7 @@ public static class CompletionHelper
|
||||
/// </summary>
|
||||
public static IEnumerable<CompletionOption> NetEntities(string text, IEntityManager? entManager = null, int limit = 20)
|
||||
{
|
||||
if (text != string.Empty && !NetEntity.TryParse(text, out _))
|
||||
if (!NetEntity.TryParse(text, out _))
|
||||
yield break;
|
||||
|
||||
IoCManager.Resolve(ref entManager);
|
||||
@@ -216,7 +214,7 @@ public static class CompletionHelper
|
||||
|
||||
public static IEnumerable<CompletionOption> Components<T>(string text, IEntityManager? entManager = null, int limit = 20) where T : IComponent
|
||||
{
|
||||
if (text != string.Empty && !NetEntity.TryParse(text, out _))
|
||||
if (!NetEntity.TryParse(text, out _))
|
||||
yield break;
|
||||
|
||||
IoCManager.Resolve(ref entManager);
|
||||
|
||||
@@ -88,7 +88,6 @@ namespace Robust.Shared.ContentPack
|
||||
public string SystemAssemblyName = default!;
|
||||
public HashSet<VerifierError> AllowedVerifierErrors = default!;
|
||||
public List<string> WhitelistedNamespaces = default!;
|
||||
public List<string> AllowedAssemblyPrefixes = default!;
|
||||
public Dictionary<string, Dictionary<string, TypeConfig>> Types = default!;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,16 +131,6 @@ namespace Robust.Shared.ContentPack
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma warning disable RA0004
|
||||
var loadedConfig = _config.Result;
|
||||
#pragma warning restore RA0004
|
||||
|
||||
if (!loadedConfig.AllowedAssemblyPrefixes.Any(allowedNamePrefix => asmName.StartsWith(allowedNamePrefix)))
|
||||
{
|
||||
_sawmill.Error($"Assembly name '{asmName}' is not allowed for a content assembly");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VerifyIL)
|
||||
{
|
||||
if (!DoVerifyIL(asmName, resolver, peReader, reader))
|
||||
@@ -189,6 +179,10 @@ namespace Robust.Shared.ContentPack
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma warning disable RA0004
|
||||
var loadedConfig = _config.Result;
|
||||
#pragma warning restore RA0004
|
||||
|
||||
var badRefs = new ConcurrentBag<EntityHandle>();
|
||||
|
||||
// We still do explicit type reference scanning, even though the actual whitelists work with raw members.
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Robust.Shared.ContentPack
|
||||
var paths = new List<ResPath>();
|
||||
|
||||
foreach (var filePath in _res.ContentFindRelativeFiles(mountPath)
|
||||
.Where(p => p.Filename.StartsWith(filterPrefix) &&
|
||||
.Where(p => !p.ToString().Contains('/') && p.Filename.StartsWith(filterPrefix) &&
|
||||
p.Extension == "dll"))
|
||||
{
|
||||
var fullPath = mountPath / filePath;
|
||||
@@ -93,23 +93,19 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
Sawmill.Debug("LOADING modules");
|
||||
var files = new Dictionary<string, (ResPath Path, MemoryStream data, string[] references)>();
|
||||
var files = new Dictionary<string, (ResPath Path, string[] references)>();
|
||||
|
||||
// Find all modules we want to load.
|
||||
foreach (var fullPath in paths)
|
||||
{
|
||||
using var asmFile = _res.ContentFileRead(fullPath);
|
||||
var ms = new MemoryStream();
|
||||
asmFile.CopyTo(ms);
|
||||
|
||||
ms.Position = 0;
|
||||
var refData = GetAssemblyReferenceData(ms);
|
||||
var refData = GetAssemblyReferenceData(asmFile);
|
||||
if (refData == null)
|
||||
continue;
|
||||
|
||||
var (asmRefs, asmName) = refData.Value;
|
||||
|
||||
if (!files.TryAdd(asmName, (fullPath, ms, asmRefs)))
|
||||
if (!files.TryAdd(asmName, (fullPath, asmRefs)))
|
||||
{
|
||||
Sawmill.Error("Found multiple modules with the same assembly name " +
|
||||
$"'{asmName}', A: {files[asmName].Path}, B: {fullPath}.");
|
||||
@@ -126,10 +122,10 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
Parallel.ForEach(files, pair =>
|
||||
{
|
||||
var (name, (_, data, _)) = pair;
|
||||
var (name, (path, _)) = pair;
|
||||
|
||||
data.Position = 0;
|
||||
if (!typeChecker.CheckAssembly(data, resolver))
|
||||
using var stream = _res.ContentFileRead(path);
|
||||
if (!typeChecker.CheckAssembly(stream, resolver))
|
||||
{
|
||||
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
|
||||
}
|
||||
@@ -141,15 +137,14 @@ namespace Robust.Shared.ContentPack
|
||||
var nodes = TopologicalSort.FromBeforeAfter(
|
||||
files,
|
||||
kv => kv.Key,
|
||||
kv => kv.Value,
|
||||
kv => kv.Value.Path,
|
||||
_ => Array.Empty<string>(),
|
||||
kv => kv.Value.references,
|
||||
allowMissing: true); // missing refs would be non-content assemblies so allow that.
|
||||
|
||||
// Actually load them in the order they depend on each other.
|
||||
foreach (var item in TopologicalSort.Sort(nodes))
|
||||
foreach (var path in TopologicalSort.Sort(nodes))
|
||||
{
|
||||
var (path, memory, _) = item;
|
||||
Sawmill.Debug($"Loading module: '{path}'");
|
||||
try
|
||||
{
|
||||
@@ -161,9 +156,9 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
else
|
||||
{
|
||||
memory.Position = 0;
|
||||
using var assemblyStream = _res.ContentFileRead(path);
|
||||
using var symbolsStream = _res.ContentFileReadOrNull(path.WithExtension("pdb"));
|
||||
LoadGameAssembly(memory, symbolsStream, skipVerify: true);
|
||||
LoadGameAssembly(assemblyStream, symbolsStream, skipVerify: true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -179,7 +174,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
private (string[] refs, string name)? GetAssemblyReferenceData(Stream stream)
|
||||
{
|
||||
using var reader = ModLoader.MakePEReader(stream, leaveOpen: true);
|
||||
using var reader = ModLoader.MakePEReader(stream);
|
||||
var metaReader = reader.GetMetadataReader();
|
||||
|
||||
var name = metaReader.GetString(metaReader.GetAssemblyDefinition().Name);
|
||||
|
||||
@@ -17,10 +17,6 @@ WhitelistedNamespaces:
|
||||
- Content
|
||||
- OpenDreamShared
|
||||
|
||||
AllowedAssemblyPrefixes:
|
||||
- OpenDream
|
||||
- Content
|
||||
|
||||
# The type whitelist does NOT care about which assembly types come from.
|
||||
# This is because types switch assembly all the time.
|
||||
# Just look up stuff like StreamReader on https://apisof.net.
|
||||
@@ -740,15 +736,6 @@ Types:
|
||||
- "void .ctor(string)"
|
||||
- "void .ctor(string, System.Text.RegularExpressions.RegexOptions)"
|
||||
- "void .ctor(string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
|
||||
- "int Count(string)"
|
||||
- "int Count(System.ReadOnlySpan`1<char>)"
|
||||
- "int Count(System.ReadOnlySpan`1<char>, int)"
|
||||
- "int Count(string, string)"
|
||||
- "int Count(string, string, System.Text.RegularExpressions.RegexOptions)"
|
||||
- "int Count(string, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
|
||||
- "int Count(System.ReadOnlySpan`1<char>, string)"
|
||||
- "int Count(System.ReadOnlySpan`1<char>, string, System.Text.RegularExpressions.RegexOptions)"
|
||||
- "int Count(System.ReadOnlySpan`1<char>, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
|
||||
RegexMatchTimeoutException: { All: True }
|
||||
RegexOptions: { } # Enum
|
||||
RegexParseError: { }
|
||||
@@ -802,7 +789,6 @@ Types:
|
||||
- "bool Equals(System.ReadOnlySpan`1<char>)"
|
||||
- "bool Equals(System.Text.StringBuilder)"
|
||||
- "char get_Chars(int)"
|
||||
- "void set_Chars(int, char)"
|
||||
- "int EnsureCapacity(int)"
|
||||
- "int get_Capacity()"
|
||||
- "int get_Length()"
|
||||
|
||||
@@ -95,7 +95,7 @@ public sealed partial class MapLoaderSystem
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while creating entities for map {file}: {e}");
|
||||
Log.Error($"Caught exception while creating entities: {e}");
|
||||
Delete(deserializer.Result);
|
||||
throw;
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public sealed partial class MapLoaderSystem
|
||||
if (opts.ExpectedCategory is { } exp && exp != deserializer.Result.Category)
|
||||
{
|
||||
// Did someone try to load a map file as a grid or vice versa?
|
||||
Log.Error($"Map {file} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Log.Error($"File does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
|
||||
Delete(deserializer.Result);
|
||||
return false;
|
||||
}
|
||||
@@ -203,35 +203,6 @@ public sealed partial class MapLoaderSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to load a grid entity from a file and parent it to a newly created map.
|
||||
/// If the file does not contain exactly one grid, this will return false and delete loaded entities.
|
||||
/// </summary>
|
||||
public bool TryLoadGrid(
|
||||
ResPath path,
|
||||
[NotNullWhen(true)] out Entity<MapComponent>? map,
|
||||
[NotNullWhen(true)] out Entity<MapGridComponent>? grid,
|
||||
DeserializationOptions? options = null,
|
||||
Vector2 offset = default,
|
||||
Angle rot = default)
|
||||
{
|
||||
var opts = options ?? DeserializationOptions.Default;
|
||||
|
||||
var mapUid = _mapSystem.CreateMap(out var mapId, runMapInit: opts.InitializeMaps);
|
||||
if (opts.PauseMaps)
|
||||
_mapSystem.SetPaused(mapUid, true);
|
||||
|
||||
if (!TryLoadGrid(mapId, path, out grid, options, offset, rot))
|
||||
{
|
||||
Del(mapUid);
|
||||
map = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
map = new(mapUid, Comp<MapComponent>(mapUid));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ApplyTransform(EntityDeserializer deserializer, MapLoadOptions opts)
|
||||
{
|
||||
if (opts.Rotation == Angle.Zero && opts.Offset == Vector2.Zero)
|
||||
|
||||
@@ -47,7 +47,6 @@ public sealed partial class MapLoaderSystem : EntitySystem
|
||||
Log.Info($"Saving serialized results to {path}");
|
||||
path = path.ToRootedPath();
|
||||
var document = new YamlDocument(data.ToYaml());
|
||||
_resourceManager.UserData.CreateDir(path.Directory);
|
||||
using var writer = _resourceManager.UserData.OpenWriteText(path);
|
||||
{
|
||||
var stream = new YamlStream {document};
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
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(SharedVisibilitySystem))]
|
||||
public sealed partial class VisibilityComponent : Component
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
/// <summary>
|
||||
/// The visibility layer for the entity.
|
||||
/// Players whose visibility masks don't match this won't get state updates for it.
|
||||
/// 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>
|
||||
/// <remarks>
|
||||
/// Not serialized as visibility is normally immediate (i.e. prior to MapInit) and content should be handling it as such.
|
||||
/// </remarks>
|
||||
[DataField(readOnly: true)]
|
||||
public ushort Layer = 1;
|
||||
[RegisterComponent]
|
||||
[Access(typeof(SharedVisibilitySystem))]
|
||||
public sealed partial class VisibilityComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The visibility layer for the entity.
|
||||
/// Players whose visibility masks don't match this won't get state updates for it.
|
||||
/// </summary>
|
||||
[DataField("layer")]
|
||||
public ushort Layer = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ public abstract partial class EntityManager
|
||||
Dirty(uid, comp, metadata);
|
||||
}
|
||||
|
||||
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
|
||||
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
|
||||
|
||||
@@ -1101,7 +1101,7 @@ namespace Robust.Shared.GameObjects
|
||||
MetaDataComponent? meta = null,
|
||||
params Type[] sourceComponents)
|
||||
{
|
||||
if (!MetaQuery.TryGetComponent(target, out meta))
|
||||
if (!MetaQuery.TryGetComponent(source, out meta))
|
||||
return false;
|
||||
|
||||
var allCopied = true;
|
||||
|
||||
@@ -189,11 +189,9 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
|
||||
var compType = component.GetType();
|
||||
|
||||
if (compType == typeof(TransformComponent) || compType == typeof(MetaDataComponent))
|
||||
continue;
|
||||
|
||||
var compName = _componentFactory.GetComponentName(compType);
|
||||
if (compName == _xformName || compName == _metaReg.Name)
|
||||
continue;
|
||||
|
||||
// If the component isn't on the prototype then it's custom.
|
||||
if (!protoData.TryGetValue(compName, out var protoMapping))
|
||||
@@ -210,7 +208,9 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
if (compMapping.AnyExcept(protoMapping))
|
||||
var diff = compMapping.Except(protoMapping);
|
||||
|
||||
if (diff != null && diff.Children.Count != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -172,22 +172,12 @@ public partial class EntitySystem
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
|
||||
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
EntityManager.DirtyFields(uid, comp, meta, fields);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void DirtyFields<T>(Entity<T?> ent, MetaDataComponent? meta, params string[] fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return;
|
||||
|
||||
EntityManager.DirtyFields(ent, ent.Comp, meta, fields);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,7 +9,6 @@ using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Shapes;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -407,7 +406,7 @@ public sealed partial class EntityLookupSystem
|
||||
var broadphaseInv = _transform.GetInvWorldMatrix(lookupUid);
|
||||
|
||||
var localBounds = broadphaseInv.TransformBounds(worldBounds);
|
||||
var polygon = new SlimPolygon(localBounds);
|
||||
var polygon = _physics.GetPooled(localBounds);
|
||||
var result = AnyEntitiesIntersecting(lookupUid,
|
||||
polygon,
|
||||
localBounds.CalcBoundingBox(),
|
||||
@@ -415,6 +414,7 @@ public sealed partial class EntityLookupSystem
|
||||
flags,
|
||||
ignored);
|
||||
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -458,8 +458,9 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return false;
|
||||
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -474,8 +475,9 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -485,16 +487,18 @@ public sealed partial class EntityLookupSystem
|
||||
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
// Don't need to check contained entities as they have the same bounds as the parent.
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var intersecting = new HashSet<EntityUid>();
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return intersecting;
|
||||
}
|
||||
|
||||
@@ -757,10 +761,11 @@ public sealed partial class EntityLookupSystem
|
||||
return;
|
||||
|
||||
var localAABB = _transform.GetInvWorldMatrix(gridId).TransformBox(worldAABB);
|
||||
var polygon = new SlimPolygon(localAABB);
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, lookup);
|
||||
AddContained(intersecting, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting(EntityUid gridId, Box2Rotated worldBounds, HashSet<EntityUid> intersecting, LookupFlags flags = DefaultFlags)
|
||||
@@ -769,10 +774,11 @@ public sealed partial class EntityLookupSystem
|
||||
return;
|
||||
|
||||
var localBounds = _transform.GetInvWorldMatrix(gridId).TransformBounds(worldBounds);
|
||||
var polygon = new SlimPolygon(localBounds);
|
||||
var polygon = _physics.GetPooled(localBounds);
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, polygon, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, lookup);
|
||||
AddContained(intersecting, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -121,8 +121,9 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var polygon = new SlimPolygon(localAABB);
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
AddEntitiesIntersecting(lookupUid, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, query, lookup);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
private void AddEntitiesIntersecting<T, TShape>(
|
||||
@@ -251,10 +252,11 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return false;
|
||||
|
||||
var polygon = new SlimPolygon(localAABB);
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid);
|
||||
var transform = new Transform(lookupPos, lookupRot);
|
||||
var result = AnyComponentsIntersecting(lookupUid, polygon, localAABB, transform, flags, query, ignored, lookup);
|
||||
_physics.ReturnPooled(polygon);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -425,9 +427,10 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, EntityUid? ignored = null, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var transform = Physics.Transform.Empty;
|
||||
var result = AnyComponentsIntersecting(type, mapId, polygon, transform, ignored, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -493,30 +496,33 @@ public sealed partial class EntityLookupSystem
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var transform = Physics.Transform.Empty;
|
||||
|
||||
GetEntitiesIntersecting(type, mapId, polygon, transform, intersecting, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting<T>(MapId mapId, Box2Rotated worldBounds, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
var shapeTransform = Physics.Transform.Empty;
|
||||
|
||||
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void GetEntitiesIntersecting<T>(MapId mapId, Box2 worldAABB, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var shapeTransform = Physics.Transform.Empty;
|
||||
|
||||
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var lookupPoly = new SlimPolygon(localAABB);
|
||||
var lookupPoly = new Polygon(localAABB);
|
||||
AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, localAABB, Physics.Transform.Empty, flags, lookup);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var shape = new SlimPolygon(localBounds);
|
||||
var shape = new Polygon(localBounds);
|
||||
var localAABB = localBounds.CalcBoundingBox();
|
||||
|
||||
AddEntitiesIntersecting(lookupUid, intersecting, shape, localAABB, Physics.Transform.Empty, flags);
|
||||
@@ -55,7 +55,7 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return false;
|
||||
|
||||
var shape = new SlimPolygon(localAABB);
|
||||
var shape = new Polygon(localAABB);
|
||||
return AnyEntitiesIntersecting(lookupUid, shape, localAABB, Physics.Transform.Empty, flags, ignored, lookup);
|
||||
}
|
||||
|
||||
|
||||
@@ -490,7 +490,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
{
|
||||
var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i);
|
||||
var proxy = fixture.Proxies[i];
|
||||
tree.MoveProxy(proxy.ProxyId, bounds);
|
||||
tree.MoveProxy(proxy.ProxyId, bounds, Vector2.Zero);
|
||||
proxy.AABB = bounds;
|
||||
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
|
||||
}
|
||||
|
||||
@@ -159,10 +159,6 @@ public abstract class SharedEyeSystem : EntitySystem
|
||||
eye.Comp.PvsScale = Math.Clamp(scale, 0.1f, 100f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overwrites visibility mask of an entity's eye.
|
||||
/// If you wish for other systems to potentially change it consider raising <see cref="RefreshVisibilityMask"/>.
|
||||
/// </summary>
|
||||
public void SetVisibilityMask(EntityUid uid, int value, EyeComponent? eyeComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref eyeComponent))
|
||||
@@ -174,32 +170,4 @@ public abstract class SharedEyeSystem : EntitySystem
|
||||
eyeComponent.VisibilityMask = value;
|
||||
DirtyField(uid, eyeComponent, nameof(EyeComponent.VisibilityMask));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the visibility mask for an entity by raising a <see cref="GetVisMaskEvent"/>
|
||||
/// </summary>
|
||||
public void RefreshVisibilityMask(Entity<EyeComponent?> entity)
|
||||
{
|
||||
if (!Resolve(entity.Owner, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
var ev = new GetVisMaskEvent()
|
||||
{
|
||||
Entity = entity.Owner,
|
||||
};
|
||||
RaiseLocalEvent(entity.Owner, ref ev, true);
|
||||
|
||||
SetVisibilityMask(entity.Owner, ev.VisibilityMask, entity.Comp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event raised to update the vismask of an entity's eye.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct GetVisMaskEvent()
|
||||
{
|
||||
public EntityUid Entity;
|
||||
|
||||
public int VisibilityMask = EyeComponent.DefaultVisibilityMask;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
{
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb, Vector2.Zero);
|
||||
}
|
||||
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
|
||||
@@ -187,7 +187,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
{
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
|
||||
gridTree.Tree.MoveProxy(component.MapProxy, in aabb, Vector2.Zero);
|
||||
}
|
||||
|
||||
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
|
||||
@@ -309,7 +309,6 @@ public abstract partial class SharedMapSystem
|
||||
ChunkDatum data)
|
||||
{
|
||||
var counter = 0;
|
||||
var gridEnt = new Entity<MapGridComponent>(uid, component);
|
||||
|
||||
if (data.IsDeleted())
|
||||
{
|
||||
@@ -327,12 +326,10 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
var gridIndices = deletedChunk.ChunkTileToGridTile((x, y));
|
||||
var newTileRef = new TileRef(uid, gridIndices, Tile.Empty);
|
||||
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
|
||||
}
|
||||
}
|
||||
|
||||
deletedChunk.CachedBounds = Box2i.Empty;
|
||||
deletedChunk.SuppressCollisionRegeneration = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -353,12 +350,10 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
var gridIndices = chunk.ChunkTileToGridTile((x, y));
|
||||
var newTileRef = new TileRef(uid, gridIndices, tile);
|
||||
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, index);
|
||||
}
|
||||
}
|
||||
|
||||
DebugTools.Assert(chunk.Fixtures.SetEquals(data.Fixtures));
|
||||
|
||||
// These should never refer to the same object
|
||||
DebugTools.AssertNotEqual(chunk.Fixtures, data.Fixtures);
|
||||
|
||||
@@ -513,7 +508,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
{
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), component));
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), component));
|
||||
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
|
||||
component.MapProxy = proxy;
|
||||
}
|
||||
@@ -567,7 +562,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
|
||||
{
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, uint.MaxValue, (uid, _fixturesQuery.Comp(uid), grid));
|
||||
var proxy = gridTree.Tree.CreateProxy(in aabb, (uid, _fixturesQuery.Comp(uid), grid));
|
||||
DebugTools.Assert(grid.MapProxy == DynamicTree.Proxy.Free);
|
||||
grid.MapProxy = proxy;
|
||||
}
|
||||
@@ -1619,7 +1614,7 @@ public abstract partial class SharedMapSystem
|
||||
if (!MapManager.SuppressOnTileChanged)
|
||||
{
|
||||
var newTileRef = new TileRef(uid, gridTile, newTile);
|
||||
_mapInternal.RaiseOnTileChanged((uid, grid), newTileRef, oldTile, mapChunk.Indices);
|
||||
_mapInternal.RaiseOnTileChanged(newTileRef, oldTile, mapChunk.Indices);
|
||||
}
|
||||
|
||||
if (shapeChanged && !mapChunk.SuppressCollisionRegeneration)
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly FixtureSystem _fixtures = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
[Dependency] private readonly MetaDataSystem _meta = default!;
|
||||
|
||||
private EntityQuery<FixturesComponent> _fixturesQuery;
|
||||
@@ -158,9 +159,9 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Creates a new instance of this class.
|
||||
/// </summary>
|
||||
public TileChangedEvent(Entity<MapGridComponent> entity, TileRef newTile, Tile oldTile, Vector2i chunkIndex)
|
||||
public TileChangedEvent(EntityUid uid, TileRef newTile, Tile oldTile, Vector2i chunkIndex)
|
||||
{
|
||||
Entity = entity;
|
||||
Entity = uid;
|
||||
NewTile = newTile;
|
||||
OldTile = oldTile;
|
||||
ChunkIndex = chunkIndex;
|
||||
@@ -172,9 +173,9 @@ namespace Robust.Shared.GameObjects
|
||||
public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty;
|
||||
|
||||
/// <summary>
|
||||
/// Entity of the grid with the tile-change. TileRef stores the GridId.
|
||||
/// EntityUid of the grid with the tile-change. TileRef stores the GridId.
|
||||
/// </summary>
|
||||
public readonly Entity<MapGridComponent> Entity;
|
||||
public readonly EntityUid Entity;
|
||||
|
||||
/// <summary>
|
||||
/// New tile that replaced the old one.
|
||||
|
||||
@@ -279,7 +279,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
protected virtual void OnUserInterfaceShutdown(Entity<UserInterfaceComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
var ents = new ValueList<EntityUid>();
|
||||
foreach (var (key, acts) in ent.Comp.Actors)
|
||||
@@ -1064,11 +1064,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
{
|
||||
if (open)
|
||||
{
|
||||
bui.Open();
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
bui.Open();
|
||||
|
||||
if (UIQuery.TryComp(bui.Owner, out var uiComp))
|
||||
{
|
||||
@@ -1096,24 +1096,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
uiComp.ClientOpenInterfaces.Remove(bui.UiKey);
|
||||
}
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
if (!TerminatingOrDeleted(bui.Owner))
|
||||
{
|
||||
SavePosition(bui);
|
||||
}
|
||||
|
||||
SavePosition(bui);
|
||||
bui.Dispose();
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(
|
||||
$"Caught exception while attempting to dispose of a BUI {bui.UiKey} with type {bui.GetType()} on entity {ToPrettyString(bui.Owner)}. Exception: {e}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -12,6 +10,6 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="tileRef">A reference to the new tile.</param>
|
||||
/// <param name="oldTile">The old tile that got replaced.</param>
|
||||
void RaiseOnTileChanged(Entity<MapGridComponent> entity, TileRef tileRef, Tile oldTile, Vector2i chunk);
|
||||
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -8,7 +7,7 @@ namespace Robust.Shared.Map
|
||||
/// <summary>
|
||||
/// The definition (template) for a grid tile.
|
||||
/// </summary>
|
||||
public interface ITileDefinition : IPrototype
|
||||
public interface ITileDefinition
|
||||
{
|
||||
/// <summary>
|
||||
/// The numeric tile ID used to refer to this tile inside the map datastructure.
|
||||
|
||||
@@ -96,13 +96,14 @@ internal partial class MapManager
|
||||
/// </summary>
|
||||
/// <param name="tileRef">A reference to the new tile.</param>
|
||||
/// <param name="oldTile">The old tile that got replaced.</param>
|
||||
void IMapManagerInternal.RaiseOnTileChanged(Entity<MapGridComponent> entity, TileRef tileRef, Tile oldTile, Vector2i chunk)
|
||||
public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk)
|
||||
{
|
||||
if (SuppressOnTileChanged)
|
||||
return;
|
||||
|
||||
var ev = new TileChangedEvent(entity, tileRef, oldTile, chunk);
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, ref ev, true);
|
||||
var euid = tileRef.GridUid;
|
||||
var ev = new TileChangedEvent(euid, tileRef, oldTile, chunk);
|
||||
EntityManager.EventBus.RaiseLocalEvent(euid, ref ev, true);
|
||||
}
|
||||
|
||||
protected Entity<MapGridComponent> CreateGrid(EntityUid map, ushort chunkSize, EntityUid forcedGridEuid)
|
||||
|
||||
@@ -8,7 +8,6 @@ using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Shapes;
|
||||
|
||||
namespace Robust.Shared.Map;
|
||||
|
||||
@@ -225,42 +224,48 @@ internal partial class MapManager
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2 worldAABB, ref TState state, GridCallback<TState> callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref state, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, ref List<Entity<MapGridComponent>> grids,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref grids, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, GridCallback callback, bool approx = IMapManager.Approximate,
|
||||
bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref state, callback, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, ref List<Entity<MapGridComponent>> grids,
|
||||
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
var polygon = _physics.GetPooled(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref grids, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Robust.Shared.Map
|
||||
[Virtual]
|
||||
internal class TileDefinitionManager : ITileDefinitionManager
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
protected readonly List<ITileDefinition> TileDefs;
|
||||
private readonly Dictionary<string, ITileDefinition> _tileNames;
|
||||
private readonly Dictionary<string, List<string>> _awaitingAliases;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -28,18 +28,18 @@ public sealed class DynamicTreeBroadPhase : IBroadPhase
|
||||
|
||||
public Box2 GetFatAabb(DynamicTree.Proxy proxy)
|
||||
{
|
||||
return _tree.GetUserData(proxy)!.AABB;
|
||||
return _tree.GetFatAabb(proxy);
|
||||
}
|
||||
|
||||
public DynamicTree.Proxy AddProxy(ref FixtureProxy proxy)
|
||||
{
|
||||
var proxyId = _tree.CreateProxy(proxy.AABB, uint.MaxValue, proxy);
|
||||
var proxyId = _tree.CreateProxy(proxy.AABB, proxy);
|
||||
return proxyId;
|
||||
}
|
||||
|
||||
public void MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb)
|
||||
public bool MoveProxy(DynamicTree.Proxy proxy, in Box2 aabb, Vector2 displacement)
|
||||
{
|
||||
_tree.MoveProxy(proxy, in aabb);
|
||||
return _tree.MoveProxy(proxy, in aabb, displacement);
|
||||
}
|
||||
|
||||
public void RemoveProxy(DynamicTree.Proxy proxy)
|
||||
@@ -132,16 +132,6 @@ public sealed class DynamicTreeBroadPhase : IBroadPhase
|
||||
state = tuple.state;
|
||||
}
|
||||
|
||||
public void Rebuild(bool fullBuild)
|
||||
{
|
||||
_tree.Rebuild(fullBuild);
|
||||
}
|
||||
|
||||
public void RebuildBottomUp()
|
||||
{
|
||||
_tree.RebuildBottomUp();
|
||||
}
|
||||
|
||||
private static bool AabbQueryStateCallback<TState>(ref (TState state, B2DynamicTree<FixtureProxy> tree, DynamicTree<FixtureProxy>.QueryCallbackDelegate<TState> callback, Box2 aabb, bool approx, DynamicTree<FixtureProxy>.ExtractAabbDelegate extract) tuple, DynamicTree.Proxy proxy)
|
||||
{
|
||||
var item = tuple.tree.GetUserData(proxy)!;
|
||||
|
||||
@@ -12,9 +12,7 @@ internal sealed partial class CollisionManager
|
||||
/// <param name="xfA">The transform for the first shape.</param>
|
||||
/// <param name="xfB">The transform for the seconds shape.</param>
|
||||
/// <returns></returns>
|
||||
public bool TestOverlap<T, U>(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB)
|
||||
where T : IPhysShape
|
||||
where U : IPhysShape
|
||||
public bool TestOverlap<T, U>(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB) where T : IPhysShape where U : IPhysShape
|
||||
{
|
||||
var input = new DistanceInput();
|
||||
|
||||
|
||||
@@ -21,9 +21,12 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Shapes;
|
||||
@@ -43,7 +46,7 @@ internal ref struct DistanceProxy
|
||||
|
||||
// GJK using Voronoi regions (Christer Ericson) and Barycentric coordinates.
|
||||
|
||||
internal DistanceProxy(ReadOnlySpan<Vector2> vertices, float radius)
|
||||
internal DistanceProxy(Vector2[] vertices, float radius)
|
||||
{
|
||||
Vertices = vertices;
|
||||
Radius = radius;
|
||||
@@ -68,18 +71,9 @@ internal ref struct DistanceProxy
|
||||
case ShapeType.Polygon:
|
||||
if (shape is Polygon poly)
|
||||
{
|
||||
Span<Vector2> verts = new Vector2[poly.VertexCount];
|
||||
poly._vertices.AsSpan[..poly.VertexCount].CopyTo(verts);
|
||||
Vertices = verts;
|
||||
Vertices = poly.Vertices.AsSpan()[..poly.VertexCount];
|
||||
Radius = poly.Radius;
|
||||
}
|
||||
else if (shape is SlimPolygon fast)
|
||||
{
|
||||
Span<Vector2> verts = new Vector2[fast.VertexCount];
|
||||
fast._vertices.AsSpan[..fast.VertexCount].CopyTo(verts);
|
||||
Vertices = verts;
|
||||
Radius = fast.Radius;
|
||||
}
|
||||
else
|
||||
{
|
||||
var polyShape = Unsafe.As<PolygonShape>(shape);
|
||||
@@ -157,7 +151,7 @@ internal ref struct DistanceProxy
|
||||
return Vertices[bestIndex];
|
||||
}
|
||||
|
||||
internal static DistanceProxy MakeProxy(ReadOnlySpan<Vector2> vertices, int count, float radius )
|
||||
internal static DistanceProxy MakeProxy(Vector2[] vertices, int count, float radius )
|
||||
{
|
||||
count = Math.Min(count, PhysicsConstants.MaxPolygonVertices);
|
||||
var proxy = new DistanceProxy(vertices[..count], radius);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -8,23 +9,21 @@ namespace Robust.Shared.Physics;
|
||||
/// <summary>
|
||||
/// Convex hull used for poly collision.
|
||||
/// </summary>
|
||||
internal ref struct InternalPhysicsHull
|
||||
internal ref struct PhysicsHull()
|
||||
{
|
||||
public Span<Vector2> Points;
|
||||
public int Count;
|
||||
|
||||
internal InternalPhysicsHull(Span<Vector2> vertices, int count) : this()
|
||||
public PhysicsHull(Span<Vector2> vertices, int count) : this()
|
||||
{
|
||||
Count = count;
|
||||
Points = vertices[..count];
|
||||
}
|
||||
|
||||
private static InternalPhysicsHull RecurseHull(Vector2 p1, Vector2 p2, Span<Vector2> ps, int count)
|
||||
private static PhysicsHull RecurseHull(Vector2 p1, Vector2 p2, Span<Vector2> ps, int count)
|
||||
{
|
||||
InternalPhysicsHull hull = new()
|
||||
{
|
||||
Count = 0
|
||||
};
|
||||
PhysicsHull hull = new();
|
||||
hull.Count = 0;
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
@@ -70,10 +69,10 @@ internal ref struct InternalPhysicsHull
|
||||
var bestPoint = ps[bestIndex];
|
||||
|
||||
// compute hull to the right of p1-bestPoint
|
||||
InternalPhysicsHull hull1 = RecurseHull(p1, bestPoint, rightPoints, rightCount);
|
||||
PhysicsHull hull1 = RecurseHull(p1, bestPoint, rightPoints, rightCount);
|
||||
|
||||
// compute hull to the right of bestPoint-p2
|
||||
InternalPhysicsHull hull2 = RecurseHull(bestPoint, p2, rightPoints, rightCount);
|
||||
PhysicsHull hull2 = RecurseHull(bestPoint, p2, rightPoints, rightCount);
|
||||
|
||||
// stich together hulls
|
||||
for (var i = 0; i < hull1.Count; ++i)
|
||||
@@ -97,9 +96,9 @@ internal ref struct InternalPhysicsHull
|
||||
// - merges vertices based on b2_linearSlop
|
||||
// - removes collinear points using b2_linearSlop
|
||||
// - returns an empty hull if it fails
|
||||
public static InternalPhysicsHull ComputeHull(ReadOnlySpan<Vector2> points, int count)
|
||||
public static PhysicsHull ComputeHull(ReadOnlySpan<Vector2> points, int count)
|
||||
{
|
||||
InternalPhysicsHull hull = new();
|
||||
PhysicsHull hull = new();
|
||||
|
||||
if (count is < 3 or > PhysicsConstants.MaxPolygonVertices)
|
||||
{
|
||||
@@ -288,7 +287,7 @@ internal ref struct InternalPhysicsHull
|
||||
return hull;
|
||||
}
|
||||
|
||||
public static bool ValidateHull(InternalPhysicsHull hull)
|
||||
public static bool ValidateHull(PhysicsHull hull)
|
||||
{
|
||||
if (hull.Count < 3 || PhysicsConstants.MaxPolygonVertices < hull.Count)
|
||||
{
|
||||
@@ -76,7 +76,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
{
|
||||
DebugTools.Assert(count is >= 3 and <= PhysicsConstants.MaxPolygonVertices);
|
||||
|
||||
var hull = InternalPhysicsHull.ComputeHull(vertices, count);
|
||||
var hull = PhysicsHull.ComputeHull(vertices, count);
|
||||
|
||||
if (hull.Count < 3)
|
||||
{
|
||||
@@ -87,7 +87,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void Set(InternalPhysicsHull hull)
|
||||
internal void Set(PhysicsHull hull)
|
||||
{
|
||||
DebugTools.Assert(hull.Count >= 3);
|
||||
var vertexCount = hull.Count;
|
||||
@@ -119,14 +119,14 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
if (count is < 3 or > PhysicsConstants.MaxPolygonVertices)
|
||||
return false;
|
||||
|
||||
var hull = new InternalPhysicsHull();
|
||||
var hull = new PhysicsHull();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
hull.Points[i] = Vertices[i];
|
||||
}
|
||||
|
||||
hull.Count = count;
|
||||
return InternalPhysicsHull.ValidateHull(hull);
|
||||
return PhysicsHull.ValidateHull(hull);
|
||||
}
|
||||
|
||||
private static Vector2 ComputeCentroid(Vector2[] vs, int count)
|
||||
@@ -173,25 +173,10 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
{
|
||||
}
|
||||
|
||||
internal PolygonShape(SlimPolygon poly)
|
||||
{
|
||||
Vertices = new Vector2[poly.VertexCount];
|
||||
Normals = new Vector2[poly.VertexCount];
|
||||
|
||||
poly._vertices.AsSpan[..VertexCount].CopyTo(Vertices);
|
||||
poly._normals.AsSpan[..VertexCount].CopyTo(Normals);
|
||||
|
||||
Centroid = poly.Centroid;
|
||||
}
|
||||
|
||||
internal PolygonShape(Polygon poly)
|
||||
{
|
||||
Vertices = new Vector2[poly.VertexCount];
|
||||
Normals = new Vector2[poly.VertexCount];
|
||||
|
||||
poly._vertices.AsSpan[..VertexCount].CopyTo(Vertices);
|
||||
poly._normals.AsSpan[..VertexCount].CopyTo(Normals);
|
||||
|
||||
Vertices = poly.Vertices;
|
||||
Normals = poly.Normals;
|
||||
Centroid = poly.Centroid;
|
||||
}
|
||||
|
||||
@@ -214,7 +199,7 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
verts[2] = bounds.TopRight;
|
||||
verts[3] = bounds.TopLeft;
|
||||
|
||||
var hull = new InternalPhysicsHull(verts, 4);
|
||||
var hull = new PhysicsHull(verts, 4);
|
||||
Set(hull);
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Robust.Shared.Physics
|
||||
return true;
|
||||
}
|
||||
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
_nodeLookup[item] = proxy;
|
||||
|
||||
return true;
|
||||
@@ -199,12 +199,11 @@ namespace Robust.Shared.Physics
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
{
|
||||
proxy = _b2Tree.CreateProxy(newBox.Value, uint.MaxValue, item);
|
||||
proxy = _b2Tree.CreateProxy(newBox.Value, item);
|
||||
return true;
|
||||
}
|
||||
|
||||
_b2Tree.MoveProxy(proxy, newBox.Value);
|
||||
return true;
|
||||
return _b2Tree.MoveProxy(proxy, newBox.Value, Vector2.Zero);
|
||||
}
|
||||
|
||||
public void QueryAabb(QueryCallbackDelegate callback, Box2 aabb, bool approx = false)
|
||||
@@ -335,7 +334,7 @@ namespace Robust.Shared.Physics
|
||||
ref var proxy = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeLookup, item, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
|
||||
proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, item);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -350,9 +349,9 @@ namespace Robust.Shared.Physics
|
||||
}
|
||||
|
||||
if (proxy == DynamicTree.Proxy.Free)
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item);
|
||||
proxy = _b2Tree.CreateProxy(aabb.Value, item);
|
||||
else
|
||||
_b2Tree.MoveProxy(proxy, aabb.Value);
|
||||
_b2Tree.MoveProxy(proxy, aabb.Value, Vector2.Zero);
|
||||
}
|
||||
|
||||
[Conditional("DEBUG_DYNAMIC_TREE")]
|
||||
|
||||
@@ -150,15 +150,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
Friction = MathF.Sqrt((FixtureA?.Friction ?? 0.0f) * (FixtureB?.Friction ?? 0.0f));
|
||||
}
|
||||
|
||||
public void GetWorldManifold(Transform transformA, Transform transformB, out Vector2 normal)
|
||||
{
|
||||
var shapeA = FixtureA?.Shape!;
|
||||
var shapeB = FixtureB?.Shape!;
|
||||
Span<Vector2> points = stackalloc Vector2[PhysicsConstants.MaxPolygonVertices];
|
||||
|
||||
SharedPhysicsSystem.InitializeManifold(ref Manifold, transformA, transformB, shapeA.Radius, shapeB.Radius, out normal, points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the world manifold.
|
||||
/// </summary>
|
||||
@@ -417,28 +408,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
[Pure, PublicAPI]
|
||||
public PhysicsComponent OurBody(EntityUid uid)
|
||||
{
|
||||
if (uid == EntityA)
|
||||
return BodyA!;
|
||||
else if (uid == EntityB)
|
||||
return BodyB!;
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
[Pure, PublicAPI]
|
||||
public PhysicsComponent OtherBody(EntityUid uid)
|
||||
{
|
||||
if (uid == EntityA)
|
||||
return BodyB!;
|
||||
else if (uid == EntityB)
|
||||
return BodyA!;
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
@@ -16,7 +16,7 @@ public interface IBroadPhase
|
||||
|
||||
DynamicTree.Proxy AddProxy(ref FixtureProxy proxy);
|
||||
|
||||
void MoveProxy(DynamicTree.Proxy proxyId, in Box2 aabb);
|
||||
bool MoveProxy(DynamicTree.Proxy proxyId, in Box2 aabb, Vector2 displacement);
|
||||
|
||||
FixtureProxy? GetProxy(DynamicTree.Proxy proxy);
|
||||
|
||||
@@ -54,11 +54,6 @@ public interface IBroadPhase
|
||||
DynamicTree<FixtureProxy>.RayQueryCallbackDelegate<TState> callback,
|
||||
in Ray ray,
|
||||
bool approx = false);
|
||||
|
||||
void Rebuild(bool fullBuild);
|
||||
|
||||
|
||||
void RebuildBottomUp();
|
||||
}
|
||||
|
||||
public interface IBroadPhase<T> : ICollection<T> where T : notnull {
|
||||
|
||||
@@ -4,12 +4,6 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
public static class PhysicsConstants
|
||||
{
|
||||
public const int LengthUnitsPerMetre = 1;
|
||||
|
||||
// Used to detect bad values. Positions greater than about 16km will have precision
|
||||
// problems, so 100km as a limit should be fine in all cases.
|
||||
public const float Huge = (100000.0f * LengthUnitsPerMetre);
|
||||
|
||||
/// <summary>
|
||||
/// The radius of the polygon/edge shape skin. This should not be modified. Making
|
||||
/// this smaller means polygons will have an insufficient buffer for continuous collision.
|
||||
@@ -29,7 +23,7 @@ namespace Robust.Shared.Physics
|
||||
/// Minimum buffer distance for angles.
|
||||
/// </summary>
|
||||
public const float AngularSlop = 2.0f / 180.0f * MathF.PI;
|
||||
|
||||
|
||||
public const byte MaxPolygonVertices = 8;
|
||||
|
||||
public const float DefaultContactFriction = 0.4f;
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Shared.Physics;
|
||||
|
||||
public struct PhysicsHull
|
||||
{
|
||||
public static Span<Vector2> ComputePoints(ReadOnlySpan<Vector2> points, int count)
|
||||
{
|
||||
var hull = InternalPhysicsHull.ComputeHull(points, count);
|
||||
return hull.Points;
|
||||
}
|
||||
}
|
||||
@@ -11,28 +11,19 @@ namespace Robust.Shared.Physics.Shapes;
|
||||
// Internal so people don't use it when it will have breaking changes very soon.
|
||||
internal record struct Polygon : IPhysShape
|
||||
{
|
||||
[DataField]
|
||||
public byte VertexCount { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Vertices associated with this polygon. Will be sliced to <see cref="VertexCount"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Consider using _vertices if doing engine work.
|
||||
/// </remarks>
|
||||
public Vector2[] Vertices => _vertices.AsSpan[..VertexCount].ToArray();
|
||||
|
||||
public Vector2[] Normals => _normals.AsSpan[..VertexCount].ToArray();
|
||||
public static Polygon Empty = new(Box2.Empty);
|
||||
|
||||
[DataField]
|
||||
internal FixedArray8<Vector2> _vertices;
|
||||
public Vector2[] Vertices;
|
||||
|
||||
internal FixedArray8<Vector2> _normals;
|
||||
public byte VertexCount;
|
||||
|
||||
public Vector2[] Normals;
|
||||
|
||||
public Vector2 Centroid;
|
||||
|
||||
public int ChildCount => 1;
|
||||
public float Radius { get; set; } = PhysicsConstants.PolygonRadius;
|
||||
public float Radius { get; set; }
|
||||
public ShapeType ShapeType => ShapeType.Polygon;
|
||||
|
||||
// Hopefully this one is short-lived for a few months
|
||||
@@ -44,77 +35,85 @@ internal record struct Polygon : IPhysShape
|
||||
public Polygon(PolygonShape polyShape)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
Vertices = new Vector2[polyShape.VertexCount];
|
||||
Normals = new Vector2[polyShape.Normals.Length];
|
||||
Radius = polyShape.Radius;
|
||||
Centroid = polyShape.Centroid;
|
||||
VertexCount = (byte) polyShape.VertexCount;
|
||||
|
||||
polyShape.Vertices.AsSpan()[..VertexCount].CopyTo(_vertices.AsSpan);
|
||||
polyShape.Normals.AsSpan()[..VertexCount].CopyTo(_normals.AsSpan);
|
||||
}
|
||||
|
||||
public Polygon(Box2 box)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
Radius = 0f;
|
||||
VertexCount = 4;
|
||||
|
||||
_vertices._00 = box.BottomLeft;
|
||||
_vertices._01 = box.BottomRight;
|
||||
_vertices._02 = box.TopRight;
|
||||
_vertices._03 = box.TopLeft;
|
||||
|
||||
_normals._00 = new Vector2(0.0f, -1.0f);
|
||||
_normals._01 = new Vector2(1.0f, 0.0f);
|
||||
_normals._02 = new Vector2(0.0f, 1.0f);
|
||||
_normals._03 = new Vector2(-1.0f, 0.0f);
|
||||
}
|
||||
|
||||
public Polygon(Box2Rotated bounds)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
Radius = 0f;
|
||||
VertexCount = 4;
|
||||
|
||||
_vertices._00 = bounds.BottomLeft;
|
||||
_vertices._01 = bounds.BottomRight;
|
||||
_vertices._02 = bounds.TopRight;
|
||||
_vertices._03 = bounds.TopLeft;
|
||||
|
||||
CalculateNormals(_vertices.AsSpan, _normals.AsSpan, 4);
|
||||
|
||||
Centroid = bounds.Center;
|
||||
Array.Copy(polyShape.Vertices, Vertices, Vertices.Length);
|
||||
Array.Copy(polyShape.Normals, Normals, Vertices.Length);
|
||||
VertexCount = (byte) Vertices.Length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually constructed polygon for internal use to take advantage of pooling.
|
||||
/// </summary>
|
||||
internal Polygon(ReadOnlySpan<Vector2> vertices, ReadOnlySpan<Vector2> normals, Vector2 centroid, byte count)
|
||||
internal Polygon(Vector2[] vertices, Vector2[] normals, Vector2 centroid, byte count)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
vertices[..VertexCount].CopyTo(_vertices.AsSpan);
|
||||
normals[..VertexCount].CopyTo(_normals.AsSpan);
|
||||
Vertices = vertices;
|
||||
Normals = normals;
|
||||
Centroid = centroid;
|
||||
VertexCount = count;
|
||||
}
|
||||
|
||||
public Polygon(Box2 aabb)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
Vertices = new Vector2[4];
|
||||
Normals = new Vector2[4];
|
||||
Radius = 0f;
|
||||
|
||||
Vertices[0] = aabb.BottomLeft;
|
||||
Vertices[1] = aabb.BottomRight;
|
||||
Vertices[2] = aabb.TopRight;
|
||||
Vertices[3] = aabb.TopLeft;
|
||||
|
||||
Normals[0] = new Vector2(0.0f, -1.0f);
|
||||
Normals[1] = new Vector2(1.0f, 0.0f);
|
||||
Normals[2] = new Vector2(0.0f, 1.0f);
|
||||
Normals[3] = new Vector2(-1.0f, 0.0f);
|
||||
|
||||
VertexCount = 4;
|
||||
Centroid = aabb.Center;
|
||||
}
|
||||
|
||||
public Polygon(Box2Rotated bounds)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
Vertices = new Vector2[4];
|
||||
Normals = new Vector2[4];
|
||||
Radius = 0f;
|
||||
|
||||
Vertices[0] = bounds.BottomLeft;
|
||||
Vertices[1] = bounds.BottomRight;
|
||||
Vertices[2] = bounds.TopRight;
|
||||
Vertices[3] = bounds.TopLeft;
|
||||
|
||||
CalculateNormals(Normals, 4);
|
||||
|
||||
VertexCount = 4;
|
||||
Centroid = bounds.Center;
|
||||
}
|
||||
|
||||
public Polygon(Vector2[] vertices)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
var hull = InternalPhysicsHull.ComputeHull(vertices, vertices.Length);
|
||||
var hull = PhysicsHull.ComputeHull(vertices, vertices.Length);
|
||||
|
||||
if (hull.Count < 3)
|
||||
{
|
||||
VertexCount = 0;
|
||||
Vertices = [];
|
||||
Normals = [];
|
||||
return;
|
||||
}
|
||||
|
||||
VertexCount = (byte) vertices.Length;
|
||||
var vertSpan = _vertices.AsSpan;
|
||||
|
||||
vertices.AsSpan().CopyTo(vertSpan);
|
||||
Vertices = vertices;
|
||||
Normals = new Vector2[vertices.Length];
|
||||
Set(hull);
|
||||
Centroid = ComputeCentroid(vertSpan);
|
||||
Centroid = ComputeCentroid(Vertices);
|
||||
}
|
||||
|
||||
public static explicit operator Polygon(PolygonShape polyShape)
|
||||
@@ -122,36 +121,36 @@ internal record struct Polygon : IPhysShape
|
||||
return new Polygon(polyShape);
|
||||
}
|
||||
|
||||
private void Set(InternalPhysicsHull hull)
|
||||
private void Set(PhysicsHull hull)
|
||||
{
|
||||
DebugTools.Assert(hull.Count >= 3);
|
||||
var vertexCount = hull.Count;
|
||||
var verts = _vertices.AsSpan;
|
||||
var norms = _normals.AsSpan;
|
||||
Array.Resize(ref Vertices, vertexCount);
|
||||
Array.Resize(ref Normals, vertexCount);
|
||||
|
||||
for (var i = 0; i < vertexCount; i++)
|
||||
{
|
||||
verts[i] = hull.Points[i];
|
||||
Vertices[i] = hull.Points[i];
|
||||
}
|
||||
|
||||
// Compute normals. Ensure the edges have non-zero length.
|
||||
CalculateNormals(verts, norms, vertexCount);
|
||||
CalculateNormals(Normals, vertexCount);
|
||||
}
|
||||
|
||||
public static void CalculateNormals(ReadOnlySpan<Vector2> vertices, Span<Vector2> normals, int count)
|
||||
internal void CalculateNormals(Span<Vector2> normals, int count)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var next = i + 1 < count ? i + 1 : 0;
|
||||
var edge = vertices[next] - vertices[i];
|
||||
var edge = Vertices[next] - Vertices[i];
|
||||
DebugTools.Assert(edge.LengthSquared() > float.Epsilon * float.Epsilon);
|
||||
|
||||
var temp = Vector2Helpers.Cross(edge, 1f);
|
||||
normals[i] = temp.Normalized();
|
||||
Normals[i] = temp.Normalized();
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector2 ComputeCentroid(ReadOnlySpan<Vector2> vs)
|
||||
private static Vector2 ComputeCentroid(Vector2[] vs)
|
||||
{
|
||||
var count = vs.Length;
|
||||
DebugTools.Assert(count >= 3);
|
||||
@@ -192,15 +191,13 @@ internal record struct Polygon : IPhysShape
|
||||
|
||||
public Box2 ComputeAABB(Transform transform, int childIndex)
|
||||
{
|
||||
DebugTools.Assert(VertexCount > 0);
|
||||
DebugTools.Assert(childIndex == 0);
|
||||
var verts = _vertices.AsSpan;
|
||||
var lower = Transform.Mul(transform, verts[0]);
|
||||
var lower = Transform.Mul(transform, Vertices[0]);
|
||||
var upper = lower;
|
||||
|
||||
for (var i = 1; i < VertexCount; ++i)
|
||||
{
|
||||
var v = Transform.Mul(transform, verts[i]);
|
||||
var v = Transform.Mul(transform, Vertices[i]);
|
||||
lower = Vector2.Min(lower, v);
|
||||
upper = Vector2.Max(upper, v);
|
||||
}
|
||||
@@ -211,41 +208,12 @@ internal record struct Polygon : IPhysShape
|
||||
|
||||
public bool Equals(IPhysShape? other)
|
||||
{
|
||||
if (other is SlimPolygon slim)
|
||||
{
|
||||
return Equals(slim);
|
||||
}
|
||||
|
||||
return other is Polygon poly && Equals(poly);
|
||||
}
|
||||
|
||||
public bool Equals(Polygon other)
|
||||
{
|
||||
if (VertexCount != other.VertexCount) return false;
|
||||
|
||||
var ourVerts = _vertices.AsSpan;
|
||||
var otherVerts = other._vertices.AsSpan;
|
||||
|
||||
if (other is not PolygonShape poly) return false;
|
||||
if (VertexCount != poly.VertexCount) return false;
|
||||
for (var i = 0; i < VertexCount; i++)
|
||||
{
|
||||
var vert = ourVerts[i];
|
||||
if (!vert.Equals(otherVerts[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Equals(SlimPolygon other)
|
||||
{
|
||||
if (VertexCount != other.VertexCount) return false;
|
||||
|
||||
var ourVerts = _vertices.AsSpan;
|
||||
var otherVerts = other._vertices.AsSpan;
|
||||
|
||||
for (var i = 0; i < VertexCount; i++)
|
||||
{
|
||||
var vert = ourVerts[i];
|
||||
if (!vert.Equals(otherVerts[i])) return false;
|
||||
var vert = Vertices[i];
|
||||
if (!vert.Equals(poly.Vertices[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -253,6 +221,6 @@ internal record struct Polygon : IPhysShape
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(VertexCount, _vertices.AsSpan.ToArray(), Radius);
|
||||
return HashCode.Combine(VertexCount, Vertices.AsSpan(0, VertexCount).ToArray(), Radius);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user