mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7c6151310 | ||
|
|
005c2e784a | ||
|
|
1009dd3ea0 | ||
|
|
87b82160b0 | ||
|
|
fc318c9ecd | ||
|
|
ac957ca7fc | ||
|
|
1bdd82b0bf | ||
|
|
0150c5e6ff | ||
|
|
b2cc90d00f | ||
|
|
1f8b89e92f | ||
|
|
33d394295e | ||
|
|
4934a9c5a5 | ||
|
|
91ebc3eb02 | ||
|
|
bc84590a33 | ||
|
|
e3944dc6fb | ||
|
|
6a77f4c27b | ||
|
|
6246ae412e | ||
|
|
ac86accc20 | ||
|
|
19ff7f25ca | ||
|
|
3f83733a03 | ||
|
|
438fed2f0e | ||
|
|
816a535a92 | ||
|
|
01df42aa8f | ||
|
|
a200d73ef9 | ||
|
|
8d30735ffb | ||
|
|
2686150f9d | ||
|
|
d720e9393b | ||
|
|
f5b1c26bec | ||
|
|
3204002c72 | ||
|
|
4c79d0c6d0 | ||
|
|
e0d38fb8bd | ||
|
|
250f6ca7db | ||
|
|
773365c185 | ||
|
|
9a1e6af586 | ||
|
|
dc96318379 | ||
|
|
31a3f145de | ||
|
|
331e1fcc81 | ||
|
|
dc7a51e582 | ||
|
|
bfe8e687da | ||
|
|
04b6d60d76 | ||
|
|
5bc5bfd58a | ||
|
|
56899b4e64 | ||
|
|
a23915e0dd | ||
|
|
726d91c5e8 | ||
|
|
d8a8783680 | ||
|
|
8839dd9a3b | ||
|
|
0296d9635c | ||
|
|
f24d9751d4 | ||
|
|
58ac82ae55 | ||
|
|
b9130bf236 | ||
|
|
a2cd33afe5 | ||
|
|
1772651049 | ||
|
|
826fa4d131 | ||
|
|
0b712ae86c | ||
|
|
22528fc484 | ||
|
|
20a411e6ce | ||
|
|
6f9ed8a242 | ||
|
|
790f4c1309 | ||
|
|
0b62cb6445 | ||
|
|
9d0f4d8a08 | ||
|
|
5069b0ccf9 | ||
|
|
12cfdb2175 | ||
|
|
b5e079815d | ||
|
|
ca3a3279c5 | ||
|
|
003752a161 | ||
|
|
55e51cba9c | ||
|
|
2cd829f4f6 | ||
|
|
43138669ec | ||
|
|
3fe30bc00f | ||
|
|
3ccbdeac6a | ||
|
|
9eb9c91da6 | ||
|
|
28d2b47a2c | ||
|
|
049ffa05e4 | ||
|
|
2f36a0a5fc | ||
|
|
41d03db59d | ||
|
|
68df887a65 | ||
|
|
e0bbcd7b08 |
2
.github/workflows/build-docfx.yml
vendored
2
.github/workflows/build-docfx.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
2
.github/workflows/publish-client.yml
vendored
2
.github/workflows/publish-client.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Package client
|
||||
run: Tools/package_client_build.py -p windows mac linux
|
||||
|
||||
2
.github/workflows/test-content.yml
vendored
2
.github/workflows/test-content.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v3.2.0
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
dotnet-version: 8.0.x
|
||||
- name: Disable submodule autoupdate
|
||||
run: touch BuildChecker/DISABLE_SUBMODULE_AUTOUPDATE
|
||||
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: f19cea8010...45f89ca263
@@ -10,6 +10,9 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project>
|
||||
<!-- Engine-specific properties. Content should not use this file. -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<!-- Import this at the end of any project files in Robust and Content. -->
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="Robust.Custom.targets" Condition="Exists('Robust.Custom.targets')"/>
|
||||
|
||||
@@ -61,18 +61,5 @@ namespace OpenToolkit.GraphicsLibraryFramework
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GLFWException"/> class with the specified context
|
||||
/// and the serialization information.
|
||||
/// </summary>
|
||||
/// <param name="info">The <see cref="SerializationInfo"/> associated with this exception.</param>
|
||||
/// <param name="context">
|
||||
/// A <see cref="StreamingContext"/> that represents the context of this exception.
|
||||
/// </param>
|
||||
protected GLFWException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
165
RELEASE-NOTES.md
165
RELEASE-NOTES.md
@@ -54,6 +54,169 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 193.2.0
|
||||
|
||||
### Other
|
||||
|
||||
* Added more PVS error logs
|
||||
|
||||
|
||||
## 193.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception when building in FULL_RELEASE
|
||||
|
||||
|
||||
## 193.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added FrozenDictionary and FrozenHashSet to sandbox whitelist
|
||||
* Added yaml type serializers for FrozenDictionary and FrozenHashSet
|
||||
* Added `IPrototypeManager.GetInstances<T>()`
|
||||
* `IPrototypeManager` now also raises `PrototypesReloadedEventArgs` as a system event.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Might fix some PVS bugs added in the last version.
|
||||
|
||||
### Internal
|
||||
|
||||
* Various static dictionaries have been converted into FrozenDictionary.
|
||||
|
||||
|
||||
## 193.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `TransformChildrenEnumerator`'s out values are now non-nullable
|
||||
|
||||
### New features
|
||||
|
||||
* Added `IPrototypeManager.TryGetInstances()`, which returns a dictionary of prototype instances for a given prototype kind/type.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed `BaseAudioSource.SetAuxiliary()` throwing errors on non-EFX systems
|
||||
|
||||
### Internal
|
||||
|
||||
|
||||
* The internals of PVS system have been reworked to reduce the number of dictionary lookups.
|
||||
* `RobustMappedStringSerializer` now uses frozen dictionaries
|
||||
* `IPrototypeManager` now uses frozen dictionaries
|
||||
|
||||
|
||||
## 192.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `EntitySystem.TryGetEntity` is now `protected`.
|
||||
|
||||
### Internal
|
||||
|
||||
* PVS message ack processing now happens asynchronously
|
||||
* Dependency collections now use a `FrozenDictionary`
|
||||
|
||||
|
||||
## 191.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
.* Fix sandbox being broken thanks to .NET 8.
|
||||
|
||||
|
||||
## 191.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Robust now uses **.NET 8**. Nyoom.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `IResourceCache.TryGetResource<T>` won't silently eat all exceptions anymore.
|
||||
|
||||
|
||||
## 190.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert broadphase job to prevent OOM from logs.
|
||||
|
||||
|
||||
## 190.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add OnGrabbed / OnReleased to slider controls.
|
||||
* Add Rotation method for matrices and also make the precision slightly better when angles are passed in by taking double-precision not single-precision floats.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix some grid setting asserts when adding gridcomponent to existing maps.
|
||||
|
||||
|
||||
## 190.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add color gradients to sliders.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix HSV / HSL producing black colors on 360 hue.
|
||||
* Stop terminating entities from prematurely detaching to nullspace.
|
||||
* Ensure shader parameters update when swapping instances.
|
||||
|
||||
### Other
|
||||
|
||||
* Add more verbose logging to OpenAL errors.
|
||||
|
||||
### Internal
|
||||
|
||||
* Change NetSyncEnabled to an assert and fix instances where it slips through to PVS.
|
||||
|
||||
|
||||
## 189.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Use the base AudioParams for networking not the z-offset adjusted ones.
|
||||
* Modulate SpriteView sprites by the control's color modulation.
|
||||
|
||||
### New features
|
||||
|
||||
* Improve YAML linter error messages for parent nodes.
|
||||
* ExpandPvsEvent will also be raised directed to the session's attached entity.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Client clientside entity error spam.
|
||||
|
||||
### Internal
|
||||
|
||||
* Set priorGain to 0 where no EFX is supported for audio rather than 0.5.
|
||||
* Try to hotfix MIDI lock contention more via a semaphore.
|
||||
|
||||
|
||||
## 188.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Return null buffered audio if there's an exception and use the dummy instance internally.
|
||||
* Use entity name then suffix for entity spawn window ordering.
|
||||
* Change MidiManager volume to gain.
|
||||
* Remove EntityQuery from the MapVelocity API.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Potentially fix some audio issues by setting gain to half where EFX not found and the prior gain was 0.
|
||||
* Log errors upon trying to spawn audio attached to deleted entities instead of trying to spawn them and erroring later.
|
||||
* Fixed predicted audio spawns not applying the adjusted audio params.
|
||||
* Fix GetDimensions for the screenhandle where the text is only a single line.
|
||||
|
||||
|
||||
## 187.2.0
|
||||
|
||||
### New features
|
||||
@@ -322,7 +485,7 @@ END TEMPLATE-->
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
|
||||
* Most methods in ActorSystem have been moved to ISharedPlayerManager.
|
||||
* Several actor/player related components and events have been moved to shared.
|
||||
|
||||
### New features
|
||||
|
||||
@@ -12,3 +12,8 @@
|
||||
id: bgra
|
||||
kind: source
|
||||
path: "/Shaders/Internal/bgra.swsl"
|
||||
|
||||
- type: shader
|
||||
id: ColorPicker
|
||||
kind: source
|
||||
path: "/Shaders/color_picker.swsl"
|
||||
|
||||
46
Resources/Shaders/color_picker.swsl
Normal file
46
Resources/Shaders/color_picker.swsl
Normal file
@@ -0,0 +1,46 @@
|
||||
// Simple shader for creating a box with colours varying along the x and y axes.
|
||||
|
||||
uniform highp vec2 size;
|
||||
uniform highp vec2 offset;
|
||||
|
||||
uniform highp vec4 xAxis;
|
||||
uniform highp vec4 yAxis;
|
||||
uniform highp vec4 baseColor;
|
||||
|
||||
uniform bool hsv;
|
||||
|
||||
void fragment()
|
||||
{
|
||||
// Calculate local uv coordinates.
|
||||
// I.e., if using this shader to draw a box to the screen, (0,0) is the bottom left of the box.
|
||||
|
||||
highp float yCoords = 1.0/SCREEN_PIXEL_SIZE.y - FRAGCOORD.y;
|
||||
highp vec2 uv = vec2(FRAGCOORD.x - offset.x, yCoords - offset.y);
|
||||
uv /= size;
|
||||
uv.y = 1.0 - uv.y;
|
||||
|
||||
highp vec4 modulate = baseColor + uv.x * xAxis + uv.y * yAxis;
|
||||
|
||||
if (hsv)
|
||||
{
|
||||
modulate.xyz = hsv2rgb(modulate.xyz);
|
||||
}
|
||||
|
||||
// The UV used for the texture lookup is the TEXTURE UV coordinate, which is different from the coordinates computed above.
|
||||
COLOR = zTexture(UV) * modulate;
|
||||
}
|
||||
|
||||
|
||||
// hsv to RGB conversion taken from www.shadertoy.com/view/MsS3Wc
|
||||
|
||||
// The MIT License
|
||||
// Copyright © 2014 Inigo Quilez
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// https://www.youtube.com/c/InigoQuilez
|
||||
// https://iquilezles.org
|
||||
|
||||
highp vec3 hsv2rgb( in highp vec3 c )
|
||||
{
|
||||
highp vec3 rgb = clamp( abs(mod(c.x*6.0+vec3(0.0,4.0,2.0),6.0)-3.0)-1.0, 0.0, 1.0 );
|
||||
return c.z * mix( vec3(1.0), rgb, c.y);
|
||||
}
|
||||
@@ -97,11 +97,11 @@ internal partial class AudioManager
|
||||
// check the git history, I originally used libvorbisfile which worked and loaded 16 bit LPCM.
|
||||
if (vorbis.Channels == 1)
|
||||
{
|
||||
format = ALFormat.MonoFloat32Ext;
|
||||
format = ALFormat.Mono16;
|
||||
}
|
||||
else if (vorbis.Channels == 2)
|
||||
{
|
||||
format = ALFormat.StereoFloat32Ext;
|
||||
format = ALFormat.Stereo16;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -110,9 +110,9 @@ internal partial class AudioManager
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (float* ptr = vorbis.Data.Span)
|
||||
fixed (short* ptr = vorbis.Data.Span)
|
||||
{
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(float),
|
||||
AL.BufferData(buffer, format, (IntPtr) ptr, vorbis.Data.Length * sizeof(short),
|
||||
(int) vorbis.SampleRate);
|
||||
}
|
||||
}
|
||||
@@ -214,9 +214,28 @@ internal partial class AudioManager
|
||||
return new AudioStream(handle, length, channels, name);
|
||||
}
|
||||
|
||||
public void SetMasterVolume(float newVolume)
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
AL.Listener(ALListenerf.Gain, newVolume);
|
||||
if (newGain < 0f)
|
||||
{
|
||||
OpenALSawmill.Error("Tried to set master gain below 0, clamping to 0");
|
||||
AL.Listener(ALListenerf.Gain, 0f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
#region Platform hack for MacOS
|
||||
// HACK/BUG: Apple's OpenAL implementation has a bug where values of 0f for listener gain don't actually
|
||||
// HACK/BUG: prevent sound playback. Workaround is to cap the minimum gain at a value just above 0.
|
||||
if (OperatingSystem.IsMacOS() && newGain == 0f)
|
||||
{
|
||||
OpenALSawmill.Verbose("Not setting gain to 0 because Apple can't write an OpenAL implementation");
|
||||
AL.Listener(ALListenerf.Gain, float.Epsilon);
|
||||
return;
|
||||
}
|
||||
#endregion Platform hack for MacOS
|
||||
|
||||
AL.Listener(ALListenerf.Gain, newGain);
|
||||
}
|
||||
|
||||
public void SetAttenuation(Attenuation attenuation)
|
||||
@@ -262,7 +281,7 @@ internal partial class AudioManager
|
||||
_bufferedAudioSources.Remove(handle);
|
||||
}
|
||||
|
||||
public IAudioSource? CreateAudioSource(AudioStream stream)
|
||||
IAudioSource? IAudioInternal.CreateAudioSource(AudioStream stream)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
@@ -282,13 +301,15 @@ internal partial class AudioManager
|
||||
return audioSource;
|
||||
}
|
||||
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
/// <inheritdoc/>
|
||||
IBufferedAudioSource? IAudioInternal.CreateBufferedAudioSource(int buffers, bool floatAudio=false)
|
||||
{
|
||||
var source = AL.GenSource();
|
||||
|
||||
if (!AL.IsSource(source))
|
||||
{
|
||||
OpenALSawmill.Error("Failed to generate source. Too many simultaneous audio streams? {0}", Environment.StackTrace);
|
||||
return null;
|
||||
}
|
||||
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
|
||||
@@ -115,7 +115,7 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
|
||||
IsEfxSupported = HasAlDeviceExtension("ALC_EXT_EFX");
|
||||
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterVolume, true);
|
||||
_cfg.OnValueChanged(CVars.AudioMasterVolume, SetMasterGain, true);
|
||||
}
|
||||
|
||||
internal bool IsMainThread()
|
||||
@@ -140,6 +140,18 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like _checkAlError but allows custom data to be passed in as relevant.
|
||||
/// </summary>
|
||||
internal void LogALError(string message, [CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
if (error != ALError.NoError)
|
||||
{
|
||||
OpenALSawmill.Error("[{0}:{1}] AL error: {2}, {3}", callerMember, callerLineNumber, error, message);
|
||||
}
|
||||
}
|
||||
|
||||
public void _checkAlError([CallerMemberName] string callerMember = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
{
|
||||
var error = AL.GetError();
|
||||
|
||||
@@ -62,6 +62,18 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
_zOffset = value;
|
||||
_audio.SetZOffset(value);
|
||||
|
||||
var query = AllEntityQuery<AudioComponent>();
|
||||
|
||||
while (query.MoveNext(out var audio))
|
||||
{
|
||||
// Pythagoras back to normal then adjust.
|
||||
var maxDistance = GetAudioDistance(audio.Params.MaxDistance);
|
||||
var refDistance = GetAudioDistance(audio.Params.ReferenceDistance);
|
||||
|
||||
audio.MaxDistance = maxDistance;
|
||||
audio.ReferenceDistance = refDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +130,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
public void SetMasterVolume(float value)
|
||||
{
|
||||
_audio.SetMasterVolume(value);
|
||||
_audio.SetMasterGain(value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
@@ -325,13 +337,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
var delta = worldPos - listener.Position;
|
||||
var distance = delta.Length();
|
||||
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
// Out of range so just clip it for us.
|
||||
if (distance > component.MaxDistance)
|
||||
{
|
||||
@@ -340,6 +345,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return;
|
||||
}
|
||||
|
||||
if (distance > 0f && distance < 0.01f)
|
||||
{
|
||||
worldPos = listener.Position;
|
||||
delta = Vector2.Zero;
|
||||
distance = 0f;
|
||||
}
|
||||
|
||||
// Update audio occlusion
|
||||
var occlusion = GetOcclusion(listener, delta, distance, entity);
|
||||
component.Occlusion = occlusion;
|
||||
@@ -411,7 +423,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
return PlayEntity(sound, Filter.Local(), source, false, audioParams);
|
||||
|
||||
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
|
||||
@@ -419,7 +431,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
|
||||
{
|
||||
if (Timing.IsFirstTimePredicted || sound == null)
|
||||
if (Timing.IsFirstTimePredicted && sound != null)
|
||||
return PlayStatic(sound, Filter.Local(), coordinates, false, audioParams);
|
||||
|
||||
return null;
|
||||
@@ -486,6 +498,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(AudioStream stream, EntityUid entity, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, new EntityCoordinates(entity, Vector2.Zero));
|
||||
|
||||
@@ -521,6 +539,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(AudioStream stream, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var playing = CreateAndStartPlayingStream(audioParams, stream);
|
||||
_xformSys.SetCoordinates(playing.Entity, coordinates);
|
||||
return playing;
|
||||
@@ -593,8 +617,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
|
||||
source.PlaybackPosition = offset;
|
||||
|
||||
ApplyAudioParams(audioP, comp);
|
||||
comp.Params = audioP;
|
||||
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.
|
||||
ApplyAudioParams(comp.Params, comp);
|
||||
source.StartPlaying();
|
||||
return (entity, comp);
|
||||
}
|
||||
@@ -607,8 +631,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
source.Pitch = audioParams.Pitch;
|
||||
source.Volume = audioParams.Volume;
|
||||
source.RolloffFactor = audioParams.RolloffFactor;
|
||||
source.MaxDistance = audioParams.MaxDistance;
|
||||
source.ReferenceDistance = audioParams.ReferenceDistance;
|
||||
source.MaxDistance = GetAudioDistance(audioParams.MaxDistance);
|
||||
source.ReferenceDistance = GetAudioDistance(audioParams.ReferenceDistance);
|
||||
source.Looping = audioParams.Loop;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
public IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio = false)
|
||||
{
|
||||
return DummyBufferedAudioSource.Instance;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ internal sealed class HeadlessAudioManager : IAudioInternal
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetMasterVolume(float value)
|
||||
public void SetMasterGain(float newGain)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Audio;
|
||||
/// <summary>
|
||||
/// Handles clientside audio.
|
||||
/// </summary>
|
||||
internal interface IAudioInternal
|
||||
internal interface IAudioInternal : IAudioManager
|
||||
{
|
||||
void InitializePostWindowing();
|
||||
void Shutdown();
|
||||
@@ -23,7 +23,11 @@ internal interface IAudioInternal
|
||||
|
||||
IAudioSource? CreateAudioSource(AudioStream stream);
|
||||
|
||||
IBufferedAudioSource CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
/// <summary>
|
||||
/// Returns a buffered audio source.
|
||||
/// </summary>
|
||||
/// <returns>null if unable to create the source.</returns>
|
||||
IBufferedAudioSource? CreateBufferedAudioSource(int buffers, bool floatAudio=false);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the velocity for the audio listener.
|
||||
@@ -40,7 +44,6 @@ internal interface IAudioInternal
|
||||
/// </summary>
|
||||
void SetRotation(Angle angle);
|
||||
|
||||
void SetMasterVolume(float value);
|
||||
void SetAttenuation(Attenuation attenuation);
|
||||
|
||||
/// <summary>
|
||||
|
||||
9
Robust.Client/Audio/IAudioManager.cs
Normal file
9
Robust.Client/Audio/IAudioManager.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
/// <summary>
|
||||
/// Public audio API for stuff that can't go through <see cref="AudioSystem"/>
|
||||
/// </summary>
|
||||
public interface IAudioManager
|
||||
{
|
||||
void SetMasterGain(float gain);
|
||||
}
|
||||
@@ -17,9 +17,9 @@ public interface IMidiManager
|
||||
bool IsAvailable { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Volume, in db.
|
||||
/// Gain of audio.
|
||||
/// </summary>
|
||||
float Volume { get; set; }
|
||||
float Gain { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This method tries to return a midi renderer ready to be used.
|
||||
|
||||
@@ -9,6 +9,7 @@ using NFluidsynth;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
@@ -69,28 +70,32 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
[ViewVariables] private readonly List<IMidiRenderer> _renderers = new();
|
||||
|
||||
// To avoid lock contention for now just don't update that much fam.
|
||||
// To avoid lock contention until some kind of MIDI refactor.
|
||||
private TimeSpan _nextUpdate;
|
||||
private TimeSpan _updateFrequency = TimeSpan.FromSeconds(0.1f);
|
||||
|
||||
private SemaphoreSlim _updateSemaphore = new(1);
|
||||
|
||||
private bool _alive = true;
|
||||
[ViewVariables] private Settings? _settings;
|
||||
private Thread? _midiThread;
|
||||
private ISawmill _midiSawmill = default!;
|
||||
private float _volume = 0f;
|
||||
private float _gain = 0f;
|
||||
private bool _volumeDirty = true;
|
||||
|
||||
// Not reliable until Fluidsynth is initialized!
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Volume
|
||||
public float Gain
|
||||
{
|
||||
get => _volume;
|
||||
get => _gain;
|
||||
set
|
||||
{
|
||||
if (MathHelper.CloseToPercent(_volume, value))
|
||||
var clamped = Math.Clamp(value, 0f, 1f);
|
||||
|
||||
if (MathHelper.CloseToPercent(_gain, clamped))
|
||||
return;
|
||||
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, value);
|
||||
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
|
||||
_volumeDirty = true;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +147,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
|
||||
{
|
||||
_volume = value;
|
||||
_gain = value;
|
||||
_volumeDirty = true;
|
||||
}, true);
|
||||
|
||||
@@ -340,7 +345,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
renderer.Source.Volume = _volume;
|
||||
renderer.Source.Gain = _gain;
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
@@ -364,21 +369,23 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
if (_nextUpdate > _timing.RealTime)
|
||||
return;
|
||||
|
||||
// I don't care for accuracy we only have this for performance for now.
|
||||
_nextUpdate = _timing.RealTime + _updateFrequency;
|
||||
|
||||
// Update positions of streams occasionally.
|
||||
// This has a lot of code duplication with AudioSystem.FrameUpdate(), and they should probably be combined somehow.
|
||||
// so TRUE
|
||||
|
||||
lock (_renderers)
|
||||
{
|
||||
if (_renderers.Count == 0)
|
||||
return;
|
||||
_updateJob.OurPosition = _audioSys.GetListenerCoordinates();
|
||||
|
||||
_updateJob.OurPosition = _audioSys.GetListenerCoordinates();
|
||||
_parallel.ProcessNow(_updateJob, _renderers.Count);
|
||||
}
|
||||
// This semaphore is here to avoid lock contention as much as possible.
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
// The ONLY time this should be contested is with ThreadUpdate.
|
||||
// If that becomes NOT the case then just lock this, remove the semaphore, and drop the update frequency even harder.
|
||||
// ReSharper disable once InconsistentlySynchronizedField
|
||||
_parallel.ProcessNow(_updateJob, _renderers.Count);
|
||||
|
||||
_updateSemaphore.Release();
|
||||
|
||||
_volumeDirty = false;
|
||||
}
|
||||
@@ -393,7 +400,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
if (_volumeDirty)
|
||||
{
|
||||
renderer.Source.Volume = Volume;
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
if (!renderer.Mono)
|
||||
@@ -433,7 +440,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
// Was previously muted maybe so try unmuting it?
|
||||
if (renderer.Source.Gain == 0f)
|
||||
{
|
||||
renderer.Source.Volume = Volume;
|
||||
renderer.Source.Gain = Gain;
|
||||
}
|
||||
|
||||
var worldPos = mapPos.Position;
|
||||
@@ -489,21 +496,39 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
lock (_renderers)
|
||||
{
|
||||
var toRemove = new ValueList<IMidiRenderer>();
|
||||
|
||||
for (var i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
lock (renderer)
|
||||
{
|
||||
if (!renderer.Disposed)
|
||||
{
|
||||
if (renderer.Master is { Disposed: true })
|
||||
renderer.Master = null;
|
||||
|
||||
renderer.Render();
|
||||
}
|
||||
else
|
||||
{
|
||||
toRemove.Add(renderer);
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
_updateSemaphore.Wait();
|
||||
|
||||
foreach (var renderer in toRemove)
|
||||
{
|
||||
renderer.InternalDispose();
|
||||
_renderers.Remove(renderer);
|
||||
}
|
||||
|
||||
_updateSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,7 +706,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
{
|
||||
public int MinimumBatchParallel => 2;
|
||||
|
||||
public int BatchSize => 2;
|
||||
public int BatchSize => 1;
|
||||
|
||||
public MidiManager Manager;
|
||||
|
||||
@@ -690,7 +715,13 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
Manager.UpdateRenderer(Renderers[index], OurPosition);
|
||||
// The indices shouldn't be able to be touched while this job is running, just the renderer itself getting locked.
|
||||
var renderer = Renderers[index];
|
||||
|
||||
lock (renderer)
|
||||
{
|
||||
Manager.UpdateRenderer(renderer, OurPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ internal sealed class MidiRenderer : IMidiRenderer
|
||||
_taskManager = taskManager;
|
||||
_midiSawmill = midiSawmill;
|
||||
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true);
|
||||
Source = clydeAudio.CreateBufferedAudioSource(Buffers, true) ?? DummyBufferedAudioSource.Instance;
|
||||
Source.SampleRate = SampleRate;
|
||||
_settings = settings;
|
||||
_soundFontLoader = soundFontLoader;
|
||||
|
||||
@@ -157,7 +157,22 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Pitch { get; set; }
|
||||
public float Pitch
|
||||
{
|
||||
get
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.GetSource(SourceHandle, ALSourcef.Pitch, out var value);
|
||||
Master._checkAlError();
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.Pitch, value);
|
||||
Master._checkAlError();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public float Volume
|
||||
@@ -188,12 +203,13 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
if (!IsEfxSupported)
|
||||
{
|
||||
AL.GetSource(SourceHandle, ALSourcef.Gain, out var priorGain);
|
||||
priorOcclusion = priorGain / _gain;
|
||||
// Default to 0 to avoid spiking audio, just means it will be muted for a frame in this case.
|
||||
priorOcclusion = _gain == 0 ? 1f : priorGain / _gain;
|
||||
}
|
||||
|
||||
_gain = value;
|
||||
AL.Source(SourceHandle, ALSourcef.Gain, _gain * priorOcclusion);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"Gain is {_gain:0.00} and priorOcclusion is {priorOcclusion:0.00}. EFX supported: {IsEfxSupported}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +227,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.MaxDistance, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"MaxDistance is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +245,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.RolloffFactor, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"RolloffFactor is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,7 +263,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.ReferenceDistance, value);
|
||||
Master._checkAlError();
|
||||
Master.LogALError($"ReferenceDistance is {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +309,7 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
{
|
||||
_checkDisposed();
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
|
||||
Master._checkAlError();
|
||||
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,6 +343,8 @@ internal abstract class BaseAudioSource : IAudioSource
|
||||
public void SetAuxiliary(IAuxiliaryAudio? audio)
|
||||
{
|
||||
_checkDisposed();
|
||||
if (!IsEfxSupported)
|
||||
return;
|
||||
|
||||
if (audio is AuxiliaryAudio impAudio)
|
||||
{
|
||||
|
||||
@@ -20,8 +20,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
private bool _float = false;
|
||||
private int FilterHandle;
|
||||
|
||||
private float _gain;
|
||||
|
||||
public int SampleRate { get; set; } = 44100;
|
||||
|
||||
private bool IsEfxSupported => _master.IsEfxSupported;
|
||||
@@ -37,7 +35,6 @@ internal sealed class BufferedAudioSource : BaseAudioSource, IBufferedAudioSourc
|
||||
BufferMap[bufferHandle] = i;
|
||||
}
|
||||
_float = floatAudio;
|
||||
AL.GetSource(sourceHandle, ALSourcef.Gain, out _gain);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, ClydeHeadless>();
|
||||
deps.Register<IClipboardManager, ClydeHeadless>();
|
||||
deps.Register<IClydeInternal, ClydeHeadless>();
|
||||
deps.Register<IAudioManager, HeadlessAudioManager>();
|
||||
deps.Register<IAudioInternal, HeadlessAudioManager>();
|
||||
deps.Register<IInputManager, InputManager>();
|
||||
deps.Register<IFileDialogManager, DummyFileDialogManager>();
|
||||
@@ -116,6 +117,7 @@ namespace Robust.Client
|
||||
deps.Register<IClyde, Clyde>();
|
||||
deps.Register<IClipboardManager, Clyde>();
|
||||
deps.Register<IClydeInternal, Clyde>();
|
||||
deps.Register<IAudioManager, AudioManager>();
|
||||
deps.Register<IAudioInternal, AudioManager>();
|
||||
deps.Register<IInputManager, ClydeInputManager>();
|
||||
deps.Register<IFileDialogManager, FileDialogManager>();
|
||||
|
||||
@@ -198,6 +198,7 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
internal sealed class ShowRayCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
@@ -224,6 +225,7 @@ namespace Robust.Client.Console.Commands
|
||||
mgr.DebugRayLifetime = TimeSpan.FromSeconds(duration);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
internal sealed class DisconnectCommand : LocalizedCommands
|
||||
{
|
||||
|
||||
@@ -4,18 +4,17 @@ using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Debugging;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.Debugging
|
||||
{
|
||||
internal sealed class DebugRayDrawingSystem : SharedDebugRayDrawingSystem
|
||||
{
|
||||
#if DEBUG
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTimer = default!;
|
||||
|
||||
@@ -28,6 +27,8 @@ namespace Robust.Client.Debugging
|
||||
public Vector2 RayHit;
|
||||
public TimeSpan LifeTime;
|
||||
public bool DidActuallyHit;
|
||||
public bool Server;
|
||||
public MapId Map;
|
||||
}
|
||||
|
||||
public bool DebugDrawRays
|
||||
@@ -73,7 +74,8 @@ namespace Robust.Client.Debugging
|
||||
DidActuallyHit = ev.Results != null,
|
||||
RayOrigin = ev.Ray.Position,
|
||||
RayHit = ev.Results?.HitPos ?? ev.Ray.Direction * ev.MaxLength + ev.Ray.Position,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
|
||||
Map = ev.Map
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
@@ -93,7 +95,9 @@ namespace Robust.Client.Debugging
|
||||
DidActuallyHit = msg.DidHit,
|
||||
RayOrigin = msg.RayOrigin,
|
||||
RayHit = msg.RayHit,
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime
|
||||
LifeTime = _gameTimer.RealTime + DebugRayLifetime,
|
||||
Server = true,
|
||||
Map = msg.Map
|
||||
};
|
||||
|
||||
_raysWithLifeTime.Add(newRayWithLifetime);
|
||||
@@ -114,10 +118,20 @@ namespace Robust.Client.Debugging
|
||||
var handle = args.WorldHandle;
|
||||
foreach (var ray in _owner._raysWithLifeTime)
|
||||
{
|
||||
if (args.MapId != ray.Map)
|
||||
continue;
|
||||
|
||||
Color color;
|
||||
if (ray.Server)
|
||||
color = ray.DidActuallyHit ? Color.Cyan : Color.Orange;
|
||||
else
|
||||
color = ray.DidActuallyHit ? Color.Blue : Color.Red;
|
||||
|
||||
handle.DrawLine(
|
||||
ray.RayOrigin,
|
||||
ray.RayHit,
|
||||
ray.DidActuallyHit ? Color.Yellow : Color.Magenta);
|
||||
color
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,5 +142,6 @@ namespace Robust.Client.Debugging
|
||||
_owner._raysWithLifeTime.RemoveAll(r => r.LifeTime < _owner._gameTimer.RealTime);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,5 @@ namespace Robust.Client.GameObjects
|
||||
public ComponentStateApplyException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected ComponentStateApplyException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!RemoveExpectedEntity(GetNetEntity(uid), out var container))
|
||||
return;
|
||||
|
||||
container.Insert(uid, EntityManager, transform: TransformQuery.GetComponent(uid), meta: MetaQuery.GetComponent(uid));
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container);
|
||||
}
|
||||
|
||||
private void HandleComponentState(EntityUid uid, ContainerManagerComponent component, ref ComponentHandleState args)
|
||||
@@ -81,17 +81,17 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var entity in container.ContainedEntities.ToArray())
|
||||
{
|
||||
container.Remove(entity,
|
||||
EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
MetaQuery.GetComponent(entity),
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false);
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
|
||||
container.Shutdown(EntityManager, _netMan);
|
||||
ShutdownContainer(container);
|
||||
toDelete.Add(id);
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
var type = _serializer.FindSerializedType(typeof(BaseContainer), data.ContainerType);
|
||||
container = _dynFactory.CreateInstanceUnchecked<BaseContainer>(type!, inject:false);
|
||||
container.Init(id, uid, component);
|
||||
InitContainer(container, (uid, component), id);
|
||||
component.Containers.Add(id, container);
|
||||
}
|
||||
|
||||
@@ -132,13 +132,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
foreach (var entity in toRemove)
|
||||
{
|
||||
container.Remove(
|
||||
entity,
|
||||
EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
MetaQuery.GetComponent(entity),
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
container,
|
||||
force: true,
|
||||
reparent: false);
|
||||
reparent: false
|
||||
);
|
||||
|
||||
DebugTools.Assert(!container.Contains(entity));
|
||||
}
|
||||
@@ -188,11 +187,12 @@ namespace Robust.Client.GameObjects
|
||||
continue;
|
||||
|
||||
RemoveExpectedEntity(netEnt, out _);
|
||||
container.Insert(entity, EntityManager,
|
||||
TransformQuery.GetComponent(entity),
|
||||
Insert(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity), null),
|
||||
container,
|
||||
xform,
|
||||
MetaQuery.GetComponent(entity),
|
||||
force: true);
|
||||
force: true
|
||||
);
|
||||
|
||||
DebugTools.Assert(container.Contains(entity));
|
||||
}
|
||||
@@ -222,7 +222,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
container.Insert(message.Entity, EntityManager);
|
||||
Insert(message.Entity, container);
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
|
||||
@@ -324,32 +324,30 @@ namespace Robust.Client.GameObjects
|
||||
if (_pointLightQuery.TryGetComponent(entity, out var light))
|
||||
_lightSys.SetContainerOccluded(entity, lightOccluded, light);
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
|
||||
// Try to avoid TryComp if we already know stuff is occluded.
|
||||
if ((!spriteOccluded || !lightOccluded) && TryComp<ContainerManagerComponent>(entity, out var manager))
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
// Thank god it's by value and not by ref.
|
||||
var childSpriteOccluded = spriteOccluded;
|
||||
var childLightOccluded = lightOccluded;
|
||||
|
||||
// We already know either sprite or light is not occluding so need to check container.
|
||||
if (manager.TryGetContainer(child.Value, out var container))
|
||||
if (manager.TryGetContainer(child, out var container))
|
||||
{
|
||||
childSpriteOccluded = childSpriteOccluded || !container.ShowContents;
|
||||
childLightOccluded = childLightOccluded || container.OccludesLight;
|
||||
}
|
||||
|
||||
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), childSpriteOccluded, childLightOccluded);
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), childSpriteOccluded, childLightOccluded);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
UpdateEntity(child.Value, TransformQuery.GetComponent(child.Value), spriteOccluded, lightOccluded);
|
||||
UpdateEntity(child, TransformQuery.GetComponent(child), spriteOccluded, lightOccluded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,30 +8,28 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _mapManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
|
||||
{
|
||||
EnsureComp<PhysicsMapComponent>(uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,14 +122,13 @@ public sealed partial class SpriteSystem
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs protoReloaded)
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
// Check if any EntityPrototype has been changed.
|
||||
if (!protoReloaded.ByType.TryGetValue(typeof(EntityPrototype), out var changedSet))
|
||||
if (!args.TryGetModified<EntityPrototype>(out var modified))
|
||||
return;
|
||||
|
||||
// Remove all changed prototypes from the cache, if they're there.
|
||||
foreach (var (prototype, _) in changedSet.Modified)
|
||||
foreach (var prototype in modified)
|
||||
{
|
||||
// Let's be lazy and not regenerate them until something needs them again.
|
||||
_cachedPrototypeIcons.Remove(prototype);
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
UpdatesAfter.Add(typeof(SpriteTreeSystem));
|
||||
|
||||
_proto.PrototypesReloaded += OnPrototypesReloaded;
|
||||
SubscribeLocalEvent<PrototypesReloadedEventArgs>(OnPrototypesReloaded);
|
||||
SubscribeLocalEvent<SpriteComponent, SpriteUpdateInertEvent>(QueueUpdateInert);
|
||||
SubscribeLocalEvent<SpriteComponent, ComponentInit>(OnInit);
|
||||
|
||||
@@ -75,7 +75,6 @@ namespace Robust.Client.GameObjects
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_proto.PrototypesReloaded -= OnPrototypesReloaded;
|
||||
_cfg.UnsubValueChanged(CVars.RenderSpriteDirectionBias, OnBiasChanged);
|
||||
}
|
||||
|
||||
|
||||
@@ -174,19 +174,21 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void OnComponentAdded(AddedComponentEventArgs args)
|
||||
{
|
||||
if (_resettingPredictedEntities)
|
||||
{
|
||||
var comp = args.ComponentType;
|
||||
if (!_resettingPredictedEntities)
|
||||
return;
|
||||
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
var comp = args.ComponentType;
|
||||
if (comp.NetID == null)
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} with net id {comp.NetID} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
}
|
||||
if (_entityManager.IsClientSide(args.BaseArgs.Owner))
|
||||
return;
|
||||
|
||||
_sawmill.Error($"""
|
||||
Added component {comp.Name} to entity {_entityManager.ToPrettyString(args.BaseArgs.Owner)} while resetting predicted entities.
|
||||
Stack trace:
|
||||
{Environment.StackTrace}
|
||||
""");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -663,14 +665,13 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
{
|
||||
var (createdEntity, meta) = _entityManager.GetEntityData(netEntity);
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
if (!component.NetSyncEnabled)
|
||||
continue;
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
@@ -981,16 +982,15 @@ namespace Robust.Client.GameStates
|
||||
xformSys.DetachParentToNull(ent, xform);
|
||||
|
||||
// Then detach all children.
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
|
||||
if (deleteClientChildren
|
||||
&& !deleteClientEntities // don't add duplicates
|
||||
&& _entities.IsClientSide(child.Value))
|
||||
&& _entities.IsClientSide(child))
|
||||
{
|
||||
_toDelete.Add(child.Value);
|
||||
_toDelete.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1038,7 +1038,7 @@ namespace Robust.Client.GameStates
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
xformSys.DetachParentToNull(child.Value, xforms.GetComponent(child.Value), xform);
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
}
|
||||
|
||||
// Finally, delete the entity.
|
||||
@@ -1129,7 +1129,7 @@ namespace Robust.Client.GameStates
|
||||
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
|
||||
{
|
||||
container.Remove(ent.Value, _entities, xform, meta, false, true);
|
||||
containerSys.Remove((ent.Value, xform, meta), container, false, true);
|
||||
}
|
||||
|
||||
meta._flags |= MetaDataFlags.Detached;
|
||||
@@ -1217,7 +1217,9 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (id, comp) in meta.NetComponents)
|
||||
{
|
||||
if (comp.NetSyncEnabled && !curState.NetComponents.Contains(id))
|
||||
DebugTools.Assert(comp.NetSyncEnabled);
|
||||
|
||||
if (!curState.NetComponents.Contains(id))
|
||||
_toRemove.Add(comp);
|
||||
}
|
||||
|
||||
@@ -1427,7 +1429,7 @@ namespace Robust.Client.GameStates
|
||||
void _recursiveRemoveState(NetEntity netEntity, TransformComponent xform, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
_processor._lastStateFullRep.Remove(netEntity);
|
||||
foreach (var child in xform.ChildEntities)
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (xformQuery.TryGetComponent(child, out var childXform) &&
|
||||
metaQuery.TryGetComponent(child, out var childMeta))
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -18,10 +20,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private readonly Dictionary<EntityUid, Dictionary<Vector2i, MapChunkData>> _mapChunkData =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// To avoid spamming errors we'll just log it once and move on.
|
||||
/// </summary>
|
||||
private HashSet<Type> _erroredGridOverlays = new();
|
||||
|
||||
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
private void _drawGrids(Viewport viewport, Box2Rotated worldBounds, IEye eye)
|
||||
private void _drawGrids(Viewport viewport, Box2 worldAABB, Box2Rotated worldBounds, IEye eye)
|
||||
{
|
||||
var mapId = eye.Position.MapId;
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
@@ -30,27 +37,35 @@ namespace Robust.Client.Graphics.Clyde
|
||||
mapId = MapId.Nullspace;
|
||||
}
|
||||
|
||||
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
|
||||
var gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1;
|
||||
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
|
||||
|
||||
gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
|
||||
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
|
||||
|
||||
var grids = new List<Entity<MapGridComponent>>();
|
||||
_mapManager.FindGridsIntersecting(mapId, worldBounds, ref grids);
|
||||
|
||||
var requiresFlush = true;
|
||||
GLShaderProgram gridProgram = default!;
|
||||
var gridOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceGrids);
|
||||
|
||||
foreach (var mapGrid in grids)
|
||||
{
|
||||
if (!_mapChunkData.ContainsKey(mapGrid))
|
||||
if (!_mapChunkData.TryGetValue(mapGrid, out var data))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (requiresFlush)
|
||||
{
|
||||
SetTexture(TextureUnit.Texture0, _tileDefinitionManager.TileTextureAtlas);
|
||||
SetTexture(TextureUnit.Texture1, _lightingReady ? viewport.LightRenderTarget.Texture : _stockTextureWhite);
|
||||
gridProgram = ActivateShaderInstance(_defaultShader.Handle).Item1;
|
||||
SetupGlobalUniformsImmediate(gridProgram, (ClydeTexture) _tileDefinitionManager.TileTextureAtlas);
|
||||
|
||||
gridProgram.SetUniformTextureMaybe(UniIMainTexture, TextureUnit.Texture0);
|
||||
gridProgram.SetUniformTextureMaybe(UniILightTexture, TextureUnit.Texture1);
|
||||
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
|
||||
}
|
||||
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
|
||||
gridProgram.SetUniform(UniIModelMatrix, transform.WorldMatrix);
|
||||
var enumerator = mapGrid.Comp.GetMapChunks(worldBounds);
|
||||
var data = _mapChunkData[mapGrid];
|
||||
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
@@ -72,6 +87,31 @@ namespace Robust.Client.Graphics.Clyde
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
requiresFlush = false;
|
||||
|
||||
foreach (var overlay in gridOverlays)
|
||||
{
|
||||
if (overlay is not IGridOverlay iGrid)
|
||||
{
|
||||
if (!_erroredGridOverlays.Add(overlay.GetType()))
|
||||
{
|
||||
_clydeSawmill.Error($"Tried to render grid overlay {overlay.GetType()} that doesn't implement {nameof(IGridOverlay)}");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
iGrid.Grid = mapGrid;
|
||||
iGrid.RequiresFlush = false;
|
||||
RenderSingleWorldOverlay(overlay, viewport, OverlaySpace.WorldSpaceGrids, worldAABB, worldBounds);
|
||||
requiresFlush |= iGrid.RequiresFlush;
|
||||
}
|
||||
|
||||
if (requiresFlush)
|
||||
{
|
||||
FlushRenderQueue();
|
||||
}
|
||||
}
|
||||
|
||||
CullEmptyChunks();
|
||||
|
||||
@@ -497,7 +497,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
using (DebugGroup("Grids"))
|
||||
using (_prof.Group("Grids"))
|
||||
{
|
||||
_drawGrids(viewport, worldBounds, eye);
|
||||
_drawGrids(viewport, worldAABB, worldBounds, eye);
|
||||
}
|
||||
|
||||
// We will also render worldspace overlays here so we can do them under / above entities as necessary
|
||||
|
||||
@@ -38,6 +38,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.DrawSetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public Matrix3 GetModelTransform()
|
||||
{
|
||||
return _clyde.DrawGetModelTransform();
|
||||
}
|
||||
|
||||
public void SetProjView(in Matrix3 proj, in Matrix3 view)
|
||||
{
|
||||
_clyde.DrawSetProjViewTransform(proj, view);
|
||||
@@ -222,7 +227,14 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var clydeShader = (ClydeShaderInstance?) shader;
|
||||
|
||||
_clyde.DrawUseShader(clydeShader?.Handle ?? _clyde._defaultShader.Handle);
|
||||
_clyde.DrawUseShader(clydeShader ?? _clyde._defaultShader);
|
||||
}
|
||||
|
||||
public ShaderInstance? GetShader()
|
||||
{
|
||||
return _clyde._queuedShaderInstance == _clyde._defaultShader
|
||||
? null
|
||||
: _clyde._queuedShaderInstance;
|
||||
}
|
||||
|
||||
public void Viewport(Box2i viewport)
|
||||
@@ -285,11 +297,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
|
||||
public override void UseShader(ShaderInstance? shader)
|
||||
{
|
||||
_renderHandle.UseShader(shader);
|
||||
}
|
||||
|
||||
public override ShaderInstance? GetShader()
|
||||
{
|
||||
return _renderHandle.GetShader();
|
||||
}
|
||||
|
||||
public override void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, Texture texture,
|
||||
ReadOnlySpan<DrawVertexUV2DColor> vertices)
|
||||
{
|
||||
@@ -380,11 +402,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_renderHandle.SetModelTransform(matrix);
|
||||
}
|
||||
|
||||
public override Matrix3 GetTransform()
|
||||
{
|
||||
return _renderHandle.GetModelTransform();
|
||||
}
|
||||
|
||||
public override void UseShader(ShaderInstance? shader)
|
||||
{
|
||||
_renderHandle.UseShader(shader);
|
||||
}
|
||||
|
||||
public override ShaderInstance? GetShader()
|
||||
{
|
||||
return _renderHandle.GetShader();
|
||||
}
|
||||
|
||||
public override void DrawCircle(Vector2 position, float radius, Color color, bool filled = true)
|
||||
{
|
||||
int divisions = Math.Max(16,(int)(radius * 16));
|
||||
|
||||
@@ -78,7 +78,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// private LoadedTexture? _batchLoadedTexture;
|
||||
// Contains the shader instance that's currently being used by the (queue) stage for new commands.
|
||||
private ClydeHandle _queuedShader;
|
||||
private ClydeHandle _queuedShader => _queuedShaderInstance.Handle;
|
||||
|
||||
private ClydeShaderInstance _queuedShaderInstance = default!;
|
||||
|
||||
// Current projection & view matrices that are being used ot render.
|
||||
// This gets updated to keep track during (queue) and (misc), but not during (submit).
|
||||
@@ -314,7 +316,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// Reset renderer state.
|
||||
_currentMatrixModel = Matrix3.Identity;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
SetScissorFull(null);
|
||||
}
|
||||
|
||||
@@ -443,9 +445,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_isStencilling = false;
|
||||
}
|
||||
|
||||
if (!instance.ParametersDirty)
|
||||
if (instance.Parameters.Count == 0)
|
||||
return (program, instance);
|
||||
|
||||
if (shader.LastInstance == instance && !instance.ParametersDirty)
|
||||
return (program, instance);
|
||||
|
||||
shader.LastInstance = instance;
|
||||
instance.ParametersDirty = false;
|
||||
|
||||
int textureUnitVal = 0;
|
||||
@@ -531,6 +537,11 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_currentMatrixModel = matrix;
|
||||
}
|
||||
|
||||
private Matrix3 DrawGetModelTransform()
|
||||
{
|
||||
return _currentMatrixModel;
|
||||
}
|
||||
|
||||
private void DrawSetProjViewTransform(in Matrix3 proj, in Matrix3 view)
|
||||
{
|
||||
BreakBatch();
|
||||
@@ -700,9 +711,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_currentScissorState = scissorBox;
|
||||
}
|
||||
|
||||
private void DrawUseShader(ClydeHandle handle)
|
||||
private void DrawUseShader(ClydeShaderInstance instance)
|
||||
{
|
||||
_queuedShader = handle;
|
||||
_queuedShaderInstance = instance;
|
||||
}
|
||||
|
||||
private void DrawClear(Color color, int stencil, ClearBufferMask mask)
|
||||
@@ -875,7 +886,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
SetScissorFull(null);
|
||||
BindRenderTargetFull(_mainWindow!.RenderTarget);
|
||||
_batchMetaData = null;
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
|
||||
GL.Viewport(0, 0, _mainWindow!.FramebufferSize.X, _mainWindow!.FramebufferSize.Y);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
[ViewVariables]
|
||||
public string? Name;
|
||||
|
||||
// Last instance that used this shader.
|
||||
// Used to ensure that shader uniforms get updated.
|
||||
public LoadedShaderInstance? LastInstance;
|
||||
}
|
||||
|
||||
private sealed class LoadedShaderInstance
|
||||
@@ -158,7 +162,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
_defaultShader = (ClydeShaderInstance) InstanceShader(defaultLoadedShader);
|
||||
|
||||
_queuedShader = _defaultShader.Handle;
|
||||
_queuedShaderInstance = _defaultShader;
|
||||
}
|
||||
|
||||
private string ReadEmbeddedShader(string fileName)
|
||||
|
||||
@@ -76,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private Thread? _gameThread;
|
||||
|
||||
private ISawmill _clydeSawmill = default!;
|
||||
private ISawmill _sawmillOgl = default!;
|
||||
|
||||
private IBindingsContext _glBindingsContext = default!;
|
||||
@@ -91,6 +92,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
public bool InitializePreWindowing()
|
||||
{
|
||||
_clydeSawmill = _logManager.GetSawmill("clyde");
|
||||
_sawmillOgl = _logManager.GetSawmill("clyde.ogl");
|
||||
|
||||
_cfg.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
|
||||
|
||||
@@ -20,11 +20,5 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public ShaderCompilationException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected ShaderCompilationException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared;
|
||||
@@ -65,7 +66,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return MouseButtonMap[button];
|
||||
}
|
||||
|
||||
private static readonly Dictionary<GlfwButton, Button> MouseButtonMap = new()
|
||||
private static readonly FrozenDictionary<GlfwButton, Button> MouseButtonMap = new Dictionary<GlfwButton, Button>()
|
||||
{
|
||||
{GlfwButton.Left, Button.Left},
|
||||
{GlfwButton.Middle, Button.Middle},
|
||||
@@ -75,10 +76,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{GlfwButton.Button6, Button.Button6},
|
||||
{GlfwButton.Button7, Button.Button7},
|
||||
{GlfwButton.Button8, Button.Button8},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
private static readonly Dictionary<GlfwKey, Key> KeyMap;
|
||||
private static readonly Dictionary<Key, GlfwKey> KeyMapReverse;
|
||||
private static readonly FrozenDictionary<GlfwKey, Key> KeyMap;
|
||||
private static readonly FrozenDictionary<Key, GlfwKey> KeyMapReverse;
|
||||
|
||||
|
||||
internal static Key ConvertGlfwKey(GlfwKey key)
|
||||
@@ -218,14 +219,16 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{GlfwKey.F24, Key.F24},
|
||||
{GlfwKey.Pause, Key.Pause},
|
||||
{GlfwKey.World1, Key.World1},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
KeyMapReverse = new Dictionary<Key, GlfwKey>();
|
||||
var keyMapReverse = new Dictionary<Key, GlfwKey>();
|
||||
|
||||
foreach (var (key, value) in KeyMap)
|
||||
{
|
||||
KeyMapReverse[value] = key;
|
||||
keyMapReverse[value] = key;
|
||||
}
|
||||
|
||||
KeyMapReverse = keyMapReverse.ToFrozenDictionary();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,12 +125,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public GlfwException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected GlfwException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using Robust.Client.Input;
|
||||
@@ -16,7 +17,7 @@ internal partial class Clyde
|
||||
{
|
||||
// Indices are values of SDL_Scancode
|
||||
private static readonly Key[] KeyMap;
|
||||
private static readonly Dictionary<Key, SDL_Scancode> KeyMapReverse;
|
||||
private static readonly FrozenDictionary<Key, SDL_Scancode> KeyMapReverse;
|
||||
private static readonly Button[] MouseButtonMap;
|
||||
|
||||
// TODO: to avoid having to ask the windowing thread, key names are cached.
|
||||
@@ -202,15 +203,17 @@ internal partial class Clyde
|
||||
MapKey(SDL_SCANCODE_F24, Key.F24);
|
||||
MapKey(SDL_SCANCODE_PAUSE, Key.Pause);
|
||||
|
||||
KeyMapReverse = new Dictionary<Key, SDL_Scancode>();
|
||||
var keyMapReverse = new Dictionary<Key, SDL_Scancode>();
|
||||
|
||||
for (var code = 0; code < KeyMap.Length; code++)
|
||||
{
|
||||
var key = KeyMap[code];
|
||||
if (key != Key.Unknown)
|
||||
KeyMapReverse[key] = (SDL_Scancode) code;
|
||||
keyMapReverse[key] = (SDL_Scancode) code;
|
||||
}
|
||||
|
||||
KeyMapReverse = keyMapReverse.ToFrozenDictionary();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
static void MapKey(SDL_Scancode code, Key key)
|
||||
{
|
||||
|
||||
@@ -55,8 +55,12 @@ namespace Robust.Client.Graphics
|
||||
|
||||
public abstract void SetTransform(in Matrix3 matrix);
|
||||
|
||||
public abstract Matrix3 GetTransform();
|
||||
|
||||
public abstract void UseShader(ShaderInstance? shader);
|
||||
|
||||
public abstract ShaderInstance? GetShader();
|
||||
|
||||
// ---- DrawPrimitives: Vector2 API ----
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -116,7 +116,6 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
baseLine.Y += lineHeight;
|
||||
advanceTotal.Y += lineHeight;
|
||||
advanceTotal.X = Math.Max(advanceTotal.X, baseLine.X);
|
||||
baseLine.X = 0f;
|
||||
continue;
|
||||
}
|
||||
@@ -128,6 +127,7 @@ namespace Robust.Client.Graphics
|
||||
|
||||
var advance = metrics.Value.Advance;
|
||||
baseLine += new Vector2(advance, 0);
|
||||
advanceTotal.X = MathF.Max(baseLine.X, advanceTotal.X);
|
||||
}
|
||||
|
||||
return advanceTotal;
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace Robust.Client.Graphics
|
||||
/// which can be either stretched or tiled to fill up
|
||||
/// the space the box is being drawn in.
|
||||
/// </summary>
|
||||
public sealed class StyleBoxTexture : StyleBox
|
||||
[Virtual]
|
||||
public class StyleBoxTexture : StyleBox
|
||||
{
|
||||
public StyleBoxTexture()
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Robust.Client.Graphics
|
||||
Vector2 scale,
|
||||
Angle? worldRot,
|
||||
Angle eyeRotation = default,
|
||||
Shared.Maths.Direction? overrideDirection = null,
|
||||
Direction? overrideDirection = null,
|
||||
SpriteComponent? sprite = null,
|
||||
TransformComponent? xform = null,
|
||||
SharedTransformSystem? xformSystem = null);
|
||||
|
||||
14
Robust.Client/Graphics/Overlays/GridOverlay.cs
Normal file
14
Robust.Client/Graphics/Overlays/GridOverlay.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
public abstract class GridOverlay : Overlay, IGridOverlay
|
||||
{
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceGrids;
|
||||
|
||||
public Entity<MapGridComponent> Grid { get; set; }
|
||||
|
||||
public bool RequiresFlush { get; set; }
|
||||
}
|
||||
18
Robust.Client/Graphics/Overlays/IGridOverlay.cs
Normal file
18
Robust.Client/Graphics/Overlays/IGridOverlay.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Marks this overlay as implementing per-grid rendering.
|
||||
/// </summary>
|
||||
public interface IGridOverlay
|
||||
{
|
||||
Entity<MapGridComponent> Grid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should we flush the render or can we keep going.
|
||||
/// </summary>
|
||||
public bool RequiresFlush { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -227,7 +228,7 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
private static readonly Dictionary<ShaderDataType, string> _nativeTypes = new()
|
||||
private static readonly FrozenDictionary<ShaderDataType, string> _nativeTypes = new Dictionary<ShaderDataType, string>()
|
||||
{
|
||||
{ShaderDataType.Void, "void"},
|
||||
{ShaderDataType.Bool, "bool"},
|
||||
@@ -252,7 +253,7 @@ namespace Robust.Client.Graphics
|
||||
{ShaderDataType.Sampler2D, "sampler2D"},
|
||||
{ShaderDataType.ISampler2D, "isampler2D"},
|
||||
{ShaderDataType.USampler2D, "usampler2D"},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
internal enum ShaderLightMode : byte
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
@@ -637,7 +638,7 @@ namespace Robust.Client.Graphics
|
||||
Colon,
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Symbols, string> _symbolStringMap = new()
|
||||
private static readonly FrozenDictionary<Symbols, string> _symbolStringMap = new Dictionary<Symbols, string>()
|
||||
{
|
||||
{Symbols.Semicolon, ";\n"},
|
||||
{Symbols.Comma, ","},
|
||||
@@ -679,6 +680,6 @@ namespace Robust.Client.Graphics
|
||||
{Symbols.GreaterOrEq, ">="},
|
||||
{Symbols.QuestionMark, "?"},
|
||||
{Symbols.Colon, ":"},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
@@ -536,17 +537,17 @@ namespace Robust.Client.Graphics
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
private static readonly Dictionary<string, ShaderPrecisionQualifier> _shaderTypePrecisionMap =
|
||||
new()
|
||||
private static readonly FrozenDictionary<string, ShaderPrecisionQualifier> _shaderTypePrecisionMap =
|
||||
new Dictionary<string, ShaderPrecisionQualifier>()
|
||||
{
|
||||
{"lowp", ShaderPrecisionQualifier.Low},
|
||||
{"mediump", ShaderPrecisionQualifier.Medium},
|
||||
{"highp", ShaderPrecisionQualifier.High}
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
private static readonly Dictionary<string, ShaderDataType> _shaderTypeMap =
|
||||
new()
|
||||
private static readonly FrozenDictionary<string, ShaderDataType> _shaderTypeMap =
|
||||
new Dictionary<string, ShaderDataType>()
|
||||
{
|
||||
{"void", ShaderDataType.Void},
|
||||
{"bool", ShaderDataType.Bool},
|
||||
@@ -571,7 +572,7 @@ namespace Robust.Client.Graphics
|
||||
{"sampler2D", ShaderDataType.Sampler2D},
|
||||
{"isampler2D", ShaderDataType.ISampler2D},
|
||||
{"usampler2D", ShaderDataType.USampler2D},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
[PublicAPI]
|
||||
internal readonly struct TextFileRange
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
@@ -28,7 +29,7 @@ namespace Robust.Client.Input
|
||||
return _mouseKeyMap[button];
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Button, Keyboard.Key> _mouseKeyMap = new()
|
||||
private static readonly FrozenDictionary<Button, Keyboard.Key> _mouseKeyMap = new Dictionary<Button, Keyboard.Key>()
|
||||
{
|
||||
{Button.Left, Keyboard.Key.MouseLeft},
|
||||
{Button.Middle, Keyboard.Key.MouseMiddle},
|
||||
@@ -40,7 +41,7 @@ namespace Robust.Client.Input
|
||||
{Button.Button8, Keyboard.Key.MouseButton8},
|
||||
{Button.Button9, Keyboard.Key.MouseButton9},
|
||||
{Button.LastButton, Keyboard.Key.Unknown},
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Map;
|
||||
@@ -14,21 +11,15 @@ namespace Robust.Client.Map;
|
||||
/// <summary>
|
||||
/// Draws border sprites for tiles that support them.
|
||||
/// </summary>
|
||||
public sealed class TileEdgeOverlay : Overlay
|
||||
public sealed class TileEdgeOverlay : GridOverlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IMapManager _mapManager;
|
||||
private readonly IResourceCache _resource;
|
||||
private readonly ITileDefinitionManager _tileDefManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowEntities;
|
||||
|
||||
private List<Entity<MapGridComponent>> _grids = new();
|
||||
|
||||
public TileEdgeOverlay(IEntityManager entManager, IMapManager mapManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
|
||||
public TileEdgeOverlay(IEntityManager entManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_mapManager = mapManager;
|
||||
_resource = resource;
|
||||
_tileDefManager = tileDefManager;
|
||||
ZIndex = -1;
|
||||
@@ -39,94 +30,90 @@ public sealed class TileEdgeOverlay : Overlay
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
_grids.Clear();
|
||||
_mapManager.FindGridsIntersecting(args.MapId, args.WorldBounds, ref _grids);
|
||||
|
||||
var mapSystem = _entManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
foreach (var grid in _grids)
|
||||
var tileSize = Grid.Comp.TileSize;
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
|
||||
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var tileSize = grid.Comp.TileSize;
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var localAABB = invMatrix.TransformBox(args.WorldBounds);
|
||||
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(grid.Owner, grid.Comp, localAABB, false);
|
||||
if (tileDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
// Get what tiles border us to determine what sprites we need to draw.
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
if (tileDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// Get what tiles border us to determine what sprites we need to draw.
|
||||
for (var x = -1; x <= 1; x++)
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
|
||||
var neighborTile = mapSystem.GetTileRef(Grid.Owner, Grid, neighborIndices);
|
||||
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
|
||||
continue;
|
||||
|
||||
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
|
||||
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(x, y).AsDirection();
|
||||
|
||||
// No edge tile
|
||||
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
|
||||
continue;
|
||||
|
||||
var texture = _resource.GetResource<TextureResource>(edgePath);
|
||||
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
|
||||
// If we ever need one for both cardinals and corners then update this.
|
||||
switch (direction)
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
|
||||
var neighborTile = mapSystem.GetTileRef(grid.Owner, grid.Comp, neighborIndices);
|
||||
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
|
||||
continue;
|
||||
|
||||
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
|
||||
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(x, y).AsDirection();
|
||||
|
||||
// No edge tile
|
||||
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
|
||||
continue;
|
||||
|
||||
var texture = _resource.GetResource<TextureResource>(edgePath);
|
||||
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
|
||||
// If we ever need one for both cardinals and corners then update this.
|
||||
switch (direction)
|
||||
{
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle == Angle.Zero)
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, box);
|
||||
else
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle == Angle.Zero)
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, box);
|
||||
else
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
|
||||
|
||||
RequiresFlush = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.ContentPack;
|
||||
@@ -45,13 +46,13 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
{
|
||||
if (useFallback && resource.Fallback != null)
|
||||
{
|
||||
Logger.Error(
|
||||
Sawmill.Error(
|
||||
$"Exception while loading resource {typeof(T)} at '{path}', resorting to fallback.\n{Environment.StackTrace}\n{e}");
|
||||
return GetResource<T>(resource.Fallback.Value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error(
|
||||
Sawmill.Error(
|
||||
$"Exception while loading resource {typeof(T)} at '{path}', no fallback available\n{Environment.StackTrace}\n{e}");
|
||||
throw;
|
||||
}
|
||||
@@ -81,11 +82,17 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
cache[path] = resource;
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Sawmill.Error($"Exception while loading resource {typeof(T)} at '{path}'\n{e}");
|
||||
resource = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void ReloadResource<T>(string path) where T : BaseResource, new()
|
||||
@@ -109,7 +116,7 @@ internal sealed partial class ResourceCache : ResourceManager, IResourceCacheInt
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}");
|
||||
Sawmill.Error($"Exception while reloading resource {typeof(T)} at '{path}'\n{e}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.20348-rc2" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.0.1" PrivateAssets="compile" />
|
||||
|
||||
@@ -213,7 +213,12 @@ public sealed class EntitySpawningUIController : UIController
|
||||
_shownEntities.Add(prototype);
|
||||
}
|
||||
|
||||
_shownEntities.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
|
||||
_shownEntities.Sort((a, b) => {
|
||||
var namesComparation = string.Compare(a.Name, b.Name, StringComparison.Ordinal);
|
||||
if (namesComparation == 0)
|
||||
return string.Compare(a.EditorSuffix, b.EditorSuffix, StringComparison.Ordinal);
|
||||
return namesComparation;
|
||||
});
|
||||
|
||||
_window.PrototypeList.TotalItemCount = _shownEntities.Count;
|
||||
_window.PrototypeScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// <summary>
|
||||
/// Whether key functions other than <see cref="EngineKeyFunctions.UIClick"/> trigger the button.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool EnableAllKeybinds
|
||||
{
|
||||
get => _enableAllKeybinds;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Log;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
@@ -15,6 +17,15 @@ public sealed class ColorSelectorSliders : Control
|
||||
set
|
||||
{
|
||||
_currentColor = value;
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
_colorData = Color.ToHsv(value);
|
||||
break;
|
||||
}
|
||||
Update();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +35,15 @@ public sealed class ColorSelectorSliders : Control
|
||||
get => _currentType;
|
||||
set
|
||||
{
|
||||
switch ((_currentType, value))
|
||||
{
|
||||
case (ColorSelectorType.Rgb, ColorSelectorType.Hsv):
|
||||
_colorData = Color.ToHsv(Color);
|
||||
break;
|
||||
case (ColorSelectorType.Hsv, ColorSelectorType.Rgb):
|
||||
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
|
||||
break;
|
||||
}
|
||||
_currentType = value;
|
||||
UpdateType();
|
||||
Update();
|
||||
@@ -45,6 +65,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
|
||||
private bool _updating = false;
|
||||
private Color _currentColor = Color.White;
|
||||
private Vector4 _colorData;
|
||||
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
|
||||
private bool _isAlphaVisible = false;
|
||||
|
||||
@@ -68,12 +89,19 @@ public sealed class ColorSelectorSliders : Control
|
||||
private OptionButton _typeSelector;
|
||||
private List<ColorSelectorType> _types = new();
|
||||
|
||||
private static ShaderInstance _shader = default!;
|
||||
|
||||
private ColorSelectorStyleBox _topStyle;
|
||||
private ColorSelectorStyleBox _middleStyle;
|
||||
private ColorSelectorStyleBox _bottomStyle;
|
||||
|
||||
public ColorSelectorSliders()
|
||||
{
|
||||
_topColorSlider = new ColorableSlider
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
BackgroundStyleBoxOverride = _topStyle = new(),
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
@@ -81,6 +109,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
BackgroundStyleBoxOverride = _middleStyle = new(),
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
@@ -88,6 +117,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
BackgroundStyleBoxOverride = _bottomStyle = new(),
|
||||
MaxValue = 1.0f
|
||||
};
|
||||
|
||||
@@ -212,7 +242,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
rootBox.AddChild(bodyBox);
|
||||
|
||||
UpdateType();
|
||||
Update();
|
||||
Color = _currentColor;
|
||||
}
|
||||
|
||||
private void UpdateType()
|
||||
@@ -222,6 +252,11 @@ public sealed class ColorSelectorSliders : Control
|
||||
_topSliderLabel.Text = labels.topLabel;
|
||||
_middleSliderLabel.Text = labels.middleLabel;
|
||||
_bottomSliderLabel.Text = labels.bottomLabel;
|
||||
|
||||
bool hsv = SelectorType == ColorSelectorType.Hsv;
|
||||
_topStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Hue : ColorSelectorStyleBox.ColorSliderPreset.Red);
|
||||
_middleStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Saturation : ColorSelectorStyleBox.ColorSliderPreset.Green);
|
||||
_bottomStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Value : ColorSelectorStyleBox.ColorSliderPreset.Blue);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
@@ -232,43 +267,41 @@ public sealed class ColorSelectorSliders : Control
|
||||
return;
|
||||
|
||||
_updating = true;
|
||||
_topColorSlider.SetColor(_currentColor);
|
||||
_middleColorSlider.SetColor(_currentColor);
|
||||
_bottomColorSlider.SetColor(_currentColor);
|
||||
_topStyle.SetBaseColor(_colorData);
|
||||
_middleStyle.SetBaseColor(_colorData);
|
||||
_bottomStyle.SetBaseColor(_colorData);
|
||||
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
_topColorSlider.Value = Color.R;
|
||||
_middleColorSlider.Value = Color.G;
|
||||
_bottomColorSlider.Value = Color.B;
|
||||
_topColorSlider.Value = _colorData.X;
|
||||
_middleColorSlider.Value = _colorData.Y;
|
||||
_bottomColorSlider.Value = _colorData.Z;
|
||||
|
||||
_topInputBox.Value = (int)(Color.R * 255.0f);
|
||||
_middleInputBox.Value = (int)(Color.G * 255.0f);
|
||||
_bottomInputBox.Value = (int)(Color.B * 255.0f);
|
||||
_topInputBox.Value = (int)(_colorData.X * 255.0f);
|
||||
_middleInputBox.Value = (int)(_colorData.Y * 255.0f);
|
||||
_bottomInputBox.Value = (int)(_colorData.Z * 255.0f);
|
||||
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
Vector4 color = Color.ToHsv(Color);
|
||||
|
||||
// dumb workaround because the formula for
|
||||
// HSV calculation results in a negative
|
||||
// number in any value past 300 degrees
|
||||
if (color.X > 0)
|
||||
if (_colorData.X > 0)
|
||||
{
|
||||
_topColorSlider.Value = color.X;
|
||||
_topInputBox.Value = (int)(color.X * 360.0f);
|
||||
_topColorSlider.Value = _colorData.X;
|
||||
_topInputBox.Value = (int)(_colorData.X * 360.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_topInputBox.Value = (int)(_topColorSlider.Value * 360.0f);
|
||||
}
|
||||
|
||||
_middleColorSlider.Value = color.Y;
|
||||
_bottomColorSlider.Value = color.Z;
|
||||
_middleColorSlider.Value = _colorData.Y;
|
||||
_bottomColorSlider.Value = _colorData.Z;
|
||||
|
||||
_middleInputBox.Value = (int)(color.Y * 100.0f);
|
||||
_bottomInputBox.Value = (int)(color.Z * 100.0f);
|
||||
_middleInputBox.Value = (int)(_colorData.Y * 100.0f);
|
||||
_bottomInputBox.Value = (int)(_colorData.Z * 100.0f);
|
||||
|
||||
|
||||
break;
|
||||
@@ -361,25 +394,16 @@ public sealed class ColorSelectorSliders : Control
|
||||
return;
|
||||
}
|
||||
|
||||
switch (SelectorType)
|
||||
_colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
|
||||
|
||||
_currentColor = SelectorType switch
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
Color rgbColor = new Color(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
|
||||
ColorSelectorType.Hsv => Color.FromHsv(_colorData),
|
||||
_ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W)
|
||||
};
|
||||
|
||||
_currentColor = rgbColor;
|
||||
Update();
|
||||
|
||||
OnColorChanged!(rgbColor);
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
Color hsvColor = Color.FromHsv(new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value));
|
||||
|
||||
_currentColor = hsvColor;
|
||||
Update();
|
||||
|
||||
OnColorChanged!(hsvColor);
|
||||
break;
|
||||
}
|
||||
Update();
|
||||
OnColorChanged?.Invoke(_currentColor);
|
||||
}
|
||||
|
||||
private enum ColorSliderOrder
|
||||
|
||||
130
Robust.Client/UserInterface/Controls/ColorSelectorStyleBox.cs
Normal file
130
Robust.Client/UserInterface/Controls/ColorSelectorStyleBox.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// Style box for colouring sliders and 2-d colour selectors. E.g., this could be used to draw the typical HSV colour
|
||||
/// selection rainbow.
|
||||
/// </summary>
|
||||
public sealed class ColorSelectorStyleBox : StyleBoxTexture
|
||||
{
|
||||
public const string TexturePath = "/Textures/Interface/Nano/slider_fill.svg.96dpi.png";
|
||||
public static ProtoId<ShaderPrototype> Prototype = "ColorPicker";
|
||||
|
||||
private ShaderInstance _shader;
|
||||
|
||||
/// <summary>
|
||||
/// Base background colour.
|
||||
/// </summary>
|
||||
public Vector4 BaseColor;
|
||||
|
||||
/// <summary>
|
||||
/// Colour to add to the background colour along the X-axis.
|
||||
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
|
||||
/// </summary>
|
||||
public Vector4 XAxis;
|
||||
|
||||
/// <summary>
|
||||
/// Colour to add to the background colour along the y-axis.
|
||||
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
|
||||
/// </summary>
|
||||
public Vector4 YAxis;
|
||||
|
||||
/// <summary>
|
||||
/// If true, then <see cref="BaseColor"/>, <see cref="XAxis"/>, and <see cref="YAxis"/> will be interpreted as HSVa
|
||||
/// colours.
|
||||
/// </summary>
|
||||
public bool Hsv;
|
||||
|
||||
public ColorSelectorStyleBox(ColorSliderPreset preset = ColorSliderPreset.Red)
|
||||
{
|
||||
Texture = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>(TexturePath);
|
||||
_shader = IoCManager.Resolve<IPrototypeManager>().Index(Prototype).InstanceUnique();
|
||||
SetPatchMargin(Margin.All, 12);
|
||||
ConfigureSlider(preset);
|
||||
}
|
||||
|
||||
protected override void DoDraw(DrawingHandleScreen handle, UIBox2 box, float uiScale)
|
||||
{
|
||||
var old = handle.GetShader();
|
||||
handle.UseShader(_shader);
|
||||
|
||||
var globalPixelPos = handle.GetTransform().Transform(default);
|
||||
_shader.SetParameter("size", box.Size);
|
||||
_shader.SetParameter("offset", globalPixelPos);
|
||||
_shader.SetParameter("xAxis", XAxis);
|
||||
_shader.SetParameter("yAxis", YAxis);
|
||||
_shader.SetParameter("baseColor", BaseColor);
|
||||
_shader.SetParameter("hsv", Hsv);
|
||||
|
||||
base.DoDraw(handle, box, uiScale);
|
||||
handle.UseShader(old);
|
||||
}
|
||||
|
||||
public void ConfigureSlider(ColorSliderPreset preset)
|
||||
{
|
||||
Hsv = preset > ColorSliderPreset.Blue;
|
||||
|
||||
if (preset == ColorSliderPreset.HueValue)
|
||||
{
|
||||
XAxis = new(1, 0, 0, 0); // Hue;
|
||||
YAxis = new(0, 0, 1, 0); // value;
|
||||
return;
|
||||
}
|
||||
|
||||
YAxis = default;
|
||||
XAxis = preset switch
|
||||
{
|
||||
ColorSliderPreset.Red or ColorSliderPreset.Hue => new(1, 0, 0, 0),
|
||||
ColorSliderPreset.Green or ColorSliderPreset.Saturation => new(0, 1, 0, 0),
|
||||
_ => new(0, 0, 1, 0),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
|
||||
/// </summary>
|
||||
public void SetBaseColor(Color color)
|
||||
{
|
||||
var colorData = Hsv
|
||||
? Color.ToHsv(color)
|
||||
: new Vector4(color.R, color.G, color.B, color.A);
|
||||
SetBaseColor(colorData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
|
||||
/// </summary>
|
||||
public void SetBaseColor(Vector4 colorData)
|
||||
{
|
||||
BaseColor = colorData - colorData * XAxis - colorData * YAxis;
|
||||
}
|
||||
|
||||
public enum ColorSliderPreset : byte
|
||||
{
|
||||
// Horizontal red slider
|
||||
Red = 1,
|
||||
|
||||
// Horizontal green slider
|
||||
Green = 2,
|
||||
|
||||
// Horizontal blue slider
|
||||
Blue = 3,
|
||||
|
||||
// Horizontal hue slider
|
||||
Hue = 4,
|
||||
|
||||
// Horizontal situation slider
|
||||
Saturation = 5,
|
||||
|
||||
// Horizontal saturation slider
|
||||
Value = 6,
|
||||
|
||||
// 2-D hue-value box
|
||||
HueValue = 7
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -14,6 +15,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string StylePropertyFill = "fill";
|
||||
public const string StylePropertyGrabber = "grabber";
|
||||
|
||||
public event Action<Slider>? OnGrabbed;
|
||||
public event Action<Slider>? OnReleased;
|
||||
|
||||
protected readonly PanelContainer _foregroundPanel;
|
||||
protected readonly PanelContainer _backgroundPanel;
|
||||
protected readonly PanelContainer _fillPanel;
|
||||
@@ -135,6 +139,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
HandlePositionChange(args.RelativePosition);
|
||||
_grabbed = true;
|
||||
OnGrabbed?.Invoke(this);
|
||||
}
|
||||
|
||||
protected internal override void KeyBindUp(GUIBoundKeyEventArgs args)
|
||||
@@ -144,6 +149,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (args.Function != EngineKeyFunctions.UIClick) return;
|
||||
|
||||
_grabbed = false;
|
||||
OnReleased?.Invoke(this);
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
|
||||
@@ -13,17 +13,15 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[Virtual]
|
||||
public class SpriteView : Control
|
||||
{
|
||||
private SpriteSystem? _spriteSystem;
|
||||
private SpriteSystem? _sprite;
|
||||
private SharedTransformSystem? _transform;
|
||||
IEntityManager _entMan;
|
||||
|
||||
[ViewVariables]
|
||||
public SpriteComponent? Sprite { get; private set; }
|
||||
|
||||
public SpriteComponent? Sprite => Entity?.Comp1;
|
||||
|
||||
[ViewVariables]
|
||||
public EntityUid? Entity { get; private set; }
|
||||
|
||||
public Entity<SpriteComponent>? Ent => Entity == null || Sprite == null ? null : (Entity.Value, Sprite);
|
||||
public Entity<SpriteComponent, TransformComponent>? Entity { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// This field configures automatic scaling of the sprite. This automatic scaling is done before
|
||||
@@ -118,23 +116,30 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public SpriteView()
|
||||
{
|
||||
_entMan = IoCManager.Resolve<IEntityManager>();
|
||||
if (_entMan.TryGetComponent(Entity, out SpriteComponent? sprite))
|
||||
{
|
||||
Sprite = sprite;
|
||||
}
|
||||
|
||||
IoCManager.Resolve(ref _entMan);
|
||||
RectClipContent = true;
|
||||
}
|
||||
|
||||
public SpriteView(EntityUid uid, IEntityManager entMan)
|
||||
{
|
||||
_entMan = entMan;
|
||||
RectClipContent = true;
|
||||
SetEntity(uid);
|
||||
}
|
||||
|
||||
public void SetEntity(EntityUid? uid)
|
||||
{
|
||||
Entity = uid;
|
||||
if (Entity?.Owner == uid)
|
||||
return;
|
||||
|
||||
if (_entMan.TryGetComponent(Entity, out SpriteComponent? sprite))
|
||||
if (!_entMan.TryGetComponent(uid, out SpriteComponent? sprite)
|
||||
|| !_entMan.TryGetComponent(uid, out TransformComponent? xform))
|
||||
{
|
||||
Sprite = sprite;
|
||||
Entity = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Entity = new(uid.Value, sprite, xform);
|
||||
}
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
@@ -146,13 +151,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private void UpdateSize()
|
||||
{
|
||||
if (Entity == null || Sprite == null)
|
||||
if (Entity is not { } ent)
|
||||
{
|
||||
_spriteSize = default;
|
||||
return;
|
||||
}
|
||||
|
||||
var spriteBox = Sprite.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
|
||||
var spriteBox = ent.Comp1.CalculateRotatedBoundingBox(default, _worldRotation ?? Angle.Zero, _eyeRotation)
|
||||
.CalcBoundingBox();
|
||||
|
||||
if (!SpriteOffset)
|
||||
@@ -194,18 +199,22 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
internal override void DrawInternal(IRenderHandle renderHandle)
|
||||
{
|
||||
if (Entity is not {} uid || Sprite == null)
|
||||
if (Entity == null)
|
||||
return;
|
||||
|
||||
if (Sprite.Deleted)
|
||||
var (uid, sprite, xform) = Entity.Value;
|
||||
|
||||
if (sprite.Deleted)
|
||||
{
|
||||
SetEntity(null);
|
||||
return;
|
||||
}
|
||||
|
||||
_sprite ??= _entMan.System<SpriteSystem>();
|
||||
_transform ??= _entMan.System<TransformSystem>();
|
||||
|
||||
// Ensure the sprite is animated despite possible not being visible in any viewport.
|
||||
_spriteSystem ??= _entMan.System<SpriteSystem>();
|
||||
_spriteSystem.ForceUpdate(uid);
|
||||
_sprite.ForceUpdate(uid);
|
||||
|
||||
var stretchVec = Stretch switch
|
||||
{
|
||||
@@ -217,11 +226,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
var offset = SpriteOffset
|
||||
? Vector2.Zero
|
||||
: - (-_eyeRotation).RotateVec(Sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
: - (-_eyeRotation).RotateVec(sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
|
||||
|
||||
var position = PixelSize / 2 + offset * stretch * UIScale;
|
||||
var scale = Scale * UIScale * stretch;
|
||||
renderHandle.DrawEntity(uid, position, scale, _worldRotation, _eyeRotation, OverrideDirection, Sprite);
|
||||
|
||||
// control modulation is applied automatically to the screen handle, but here we need to use the world handle
|
||||
var world = renderHandle.DrawingHandleWorld;
|
||||
var oldModulate = world.Modulate;
|
||||
world.Modulate *= Modulate * ActualModulateSelf;
|
||||
|
||||
renderHandle.DrawEntity(uid, position, scale, _worldRotation, _eyeRotation, OverrideDirection, sprite, xform, _transform);
|
||||
world.Modulate = oldModulate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
@@ -228,7 +229,7 @@ public sealed class TextEdit : Control
|
||||
private bool IsPlaceholderVisible => Rope.IsNullOrEmpty(_textRope) && _placeholder != null;
|
||||
|
||||
// Table used by cursor movement system, see below.
|
||||
private static readonly Dictionary<BoundKeyFunction, MoveType> MoveTypeMap = new()
|
||||
private static readonly FrozenDictionary<BoundKeyFunction, MoveType> MoveTypeMap = new Dictionary<BoundKeyFunction, MoveType>()
|
||||
{
|
||||
// @formatter:off
|
||||
{ EngineKeyFunctions.TextCursorLeft, MoveType.Left },
|
||||
@@ -249,7 +250,7 @@ public sealed class TextEdit : Control
|
||||
{ EngineKeyFunctions.TextCursorSelectBegin, MoveType.BeginOfLine | MoveType.SelectFlag },
|
||||
{ EngineKeyFunctions.TextCursorSelectEnd, MoveType.EndOfLine | MoveType.SelectFlag },
|
||||
// @formatter:on
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
protected internal override void KeyBindDown(GUIBoundKeyEventArgs args)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -35,7 +36,7 @@ public sealed class UITheme : IPrototype
|
||||
private ResPath _path;
|
||||
|
||||
[DataField("colors", readOnly: true)] // This is a prototype, why is this readonly??
|
||||
public Dictionary<string, Color>? Colors { get; }
|
||||
public FrozenDictionary<string, Color>? Colors { get; }
|
||||
public ResPath Path => _path == default ? new ResPath(DefaultPath+"/"+ID) : _path;
|
||||
|
||||
private void ValidateFilePath(IResourceManager manager)
|
||||
|
||||
@@ -81,8 +81,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
if (TerminatingOrDeleted(uid))
|
||||
{
|
||||
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
@@ -94,8 +97,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (!Exists(uid))
|
||||
if (TerminatingOrDeleted(uid))
|
||||
{
|
||||
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var entity = Spawn("Audio", new EntityCoordinates(uid, Vector2.Zero));
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
@@ -106,6 +112,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return null;
|
||||
|
||||
@@ -120,6 +132,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!coordinates.IsValid(EntityManager))
|
||||
return null;
|
||||
|
||||
|
||||
@@ -28,10 +28,10 @@ namespace Robust.Server.Console.Commands
|
||||
foreach (var p in players)
|
||||
{
|
||||
sb.AppendLine(string.Format("{4,20} {1,12} {2,14:hh\\:mm\\:ss} {3,9} {0,20}",
|
||||
p.ConnectedClient.RemoteEndPoint,
|
||||
p.Channel.RemoteEndPoint,
|
||||
p.Status.ToString(),
|
||||
DateTime.UtcNow - p.ConnectedTime,
|
||||
p.ConnectedClient.Ping + "ms",
|
||||
p.Channel.Ping + "ms",
|
||||
p.Name));
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Robust.Server.Console.Commands
|
||||
else
|
||||
reason = "Kicked by console";
|
||||
|
||||
_netManager.DisconnectChannel(target.ConnectedClient, reason);
|
||||
_netManager.DisconnectChannel(target.Channel, reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ namespace Robust.Server.Console
|
||||
var replyMsg = new MsgConCmdAck();
|
||||
replyMsg.Error = error;
|
||||
replyMsg.Text = text;
|
||||
NetManager.ServerSendMessage(replyMsg, session.ConnectedClient);
|
||||
NetManager.ServerSendMessage(replyMsg, session.Channel);
|
||||
}
|
||||
else
|
||||
_systemConsole.Print(text + "\n");
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Debugging;
|
||||
|
||||
namespace Robust.Server.Debugging;
|
||||
@@ -10,11 +7,11 @@ namespace Robust.Server.Debugging;
|
||||
[UsedImplicitly]
|
||||
internal sealed class DebugRayDrawingSystem : SharedDebugRayDrawingSystem
|
||||
{
|
||||
#if DEBUG
|
||||
protected override void ReceiveLocalRayAtMainThread(DebugRayData data)
|
||||
{
|
||||
// This code won't be called on release - eliminate it anyway for good measure.
|
||||
#if DEBUG
|
||||
var msg = new MsgRay {RayOrigin = data.Ray.Position};
|
||||
var msg = new MsgRay {RayOrigin = data.Ray.Position, Map = data.Map};
|
||||
if (data.Results != null)
|
||||
{
|
||||
msg.DidHit = true;
|
||||
@@ -25,8 +22,7 @@ internal sealed class DebugRayDrawingSystem : SharedDebugRayDrawingSystem
|
||||
msg.RayHit = data.Ray.Position + data.Ray.Direction * data.MaxLength;
|
||||
}
|
||||
|
||||
EntityManager.EventBus.RaiseEvent(EventSource.Network, msg);
|
||||
#endif
|
||||
RaiseNetworkEvent(msg);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -924,7 +924,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
|
||||
#region Saving
|
||||
|
||||
private MappingDataNode GetSaveData(EntityUid uid)
|
||||
public MappingDataNode GetSaveData(EntityUid uid)
|
||||
{
|
||||
var ev = new BeforeSaveEvent(uid, Transform(uid).MapUid);
|
||||
RaiseLocalEvent(ev);
|
||||
@@ -1075,11 +1075,10 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
withoutUid.Add(uid);
|
||||
}
|
||||
|
||||
var enumerator = transformQuery.GetComponent(uid).ChildEnumerator;
|
||||
|
||||
while (enumerator.MoveNext(out var child))
|
||||
var xform = transformQuery.GetComponent(uid);
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
RecursivePopulate(child.Value, entities, uidEntityMap, withoutUid, metaQuery, transformQuery, saveCompQuery);
|
||||
RecursivePopulate(child, entities, uidEntityMap, withoutUid, metaQuery, transformQuery, saveCompQuery);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
foreach (var (player, state) in ui.PlayerStateOverrides)
|
||||
{
|
||||
RaiseNetworkEvent(state, player.ConnectedClient);
|
||||
RaiseNetworkEvent(state, player.Channel);
|
||||
}
|
||||
|
||||
if (ui.LastStateMsg == null)
|
||||
@@ -80,7 +80,7 @@ namespace Robust.Server.GameObjects
|
||||
foreach (var session in ui.SubscribedSessions)
|
||||
{
|
||||
if (!ui.PlayerStateOverrides.ContainsKey(session))
|
||||
RaiseNetworkEvent(ui.LastStateMsg, session.ConnectedClient);
|
||||
RaiseNetworkEvent(ui.LastStateMsg, session.Channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ namespace Robust.Server.GameObjects
|
||||
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey);
|
||||
foreach (var session in bui.SubscribedSessions)
|
||||
{
|
||||
RaiseNetworkEvent(msg, session.ConnectedClient);
|
||||
RaiseNetworkEvent(msg, session.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +347,7 @@ namespace Robust.Server.GameObjects
|
||||
if (!bui.SubscribedSessions.Contains(session))
|
||||
return false;
|
||||
|
||||
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.ConnectedClient);
|
||||
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.Channel);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -105,15 +105,15 @@ namespace Robust.Server.GameObjects
|
||||
meta.VisibilityMask = mask;
|
||||
_pvs.MarkDirty(uid, xform);
|
||||
|
||||
foreach (var child in xform.ChildEntities)
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (!_metaQuery.TryGetComponent(child, out var childMeta))
|
||||
continue;
|
||||
|
||||
var childMask = mask;
|
||||
|
||||
if (_visiblityQuery.TryGetComponent(child, out VisibilityComponent? hildVis))
|
||||
childMask |= hildVis.Layer;
|
||||
if (_visiblityQuery.TryGetComponent(child, out var childVis))
|
||||
childMask |= childVis.Layer;
|
||||
|
||||
RecursivelyApplyVisibility(child, childMask, childMeta);
|
||||
}
|
||||
|
||||
82
Robust.Server/GameStates/PVSData.cs
Normal file
82
Robust.Server/GameStates/PVSData.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
/// <summary>
|
||||
/// Class for storing session specific PVS data.
|
||||
/// </summary>
|
||||
internal sealed class SessionPvsData
|
||||
{
|
||||
/// <summary>
|
||||
/// All <see cref="EntityUid"/>s that this session saw during the last <see cref="PvsSystem.DirtyBufferSize"/> ticks.
|
||||
/// </summary>
|
||||
public readonly OverflowDictionary<GameTick, List<NetEntity>> SentEntities = new(PvsSystem.DirtyBufferSize);
|
||||
|
||||
public readonly Dictionary<NetEntity, EntityData> EntityData = new();
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="SentEntities"/> overflow in case a player's last ack is more than
|
||||
/// <see cref="PvsSystem.DirtyBufferSize"/> ticks behind the current tick.
|
||||
/// </summary>
|
||||
public (GameTick Tick, List<NetEntity> SentEnts)? Overflow;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the client has explicitly requested a full state. Unlike the first state, we will send them all data,
|
||||
/// not just data that cannot be implicitly inferred from entity prototypes.
|
||||
/// </summary>
|
||||
public bool RequestedFull = false;
|
||||
|
||||
public GameTick LastReceivedAck;
|
||||
|
||||
public readonly ICommonSession Session;
|
||||
|
||||
public SessionPvsData(ICommonSession session)
|
||||
{
|
||||
Session = session;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Struct for storing session-specific information about when an entity was last sent to a player.
|
||||
/// </summary>
|
||||
internal struct EntityData
|
||||
{
|
||||
public readonly Entity<MetaDataComponent> Entity;
|
||||
|
||||
/// <summary>
|
||||
/// Tick at which this entity was last sent to a player.
|
||||
/// </summary>
|
||||
public GameTick LastSent;
|
||||
|
||||
/// <summary>
|
||||
/// Tick at which an entity last left a player's PVS view.
|
||||
/// </summary>
|
||||
public GameTick LastLeftView;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the last tick at which a given entity was acked by a player. Used to avoid re-sending the whole entity
|
||||
/// state when an item re-enters PVS. This is only the same as the player's last acked tick if the entity was
|
||||
/// present in that state.
|
||||
/// </summary>
|
||||
public GameTick EntityLastAcked;
|
||||
|
||||
/// <summary>
|
||||
/// Entity visibility state when it was last sent to this player.
|
||||
/// </summary>
|
||||
public PvsEntityVisibility Visibility;
|
||||
|
||||
public EntityData(Entity<MetaDataComponent> entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var rep = new EntityStringRepresentation(Entity);
|
||||
return $"PVS Entity: {rep} - {LastSent}/{LastLeftView}/{EntityLastAcked}";
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ namespace Robust.Server.GameStates;
|
||||
|
||||
public enum PvsEntityVisibility : byte
|
||||
{
|
||||
Invalid = 0,
|
||||
Entered,
|
||||
StayedUnchanged,
|
||||
StayedChanged
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Threading;
|
||||
@@ -16,6 +19,7 @@ internal sealed partial class PvsSystem
|
||||
/// </summary>
|
||||
private void OnClientAck(ICommonSession session, GameTick ackedTick)
|
||||
{
|
||||
DebugTools.Assert(ackedTick < _gameTiming.CurTick);
|
||||
if (!PlayerData.TryGetValue(session, out var sessionData))
|
||||
return;
|
||||
|
||||
@@ -29,10 +33,12 @@ internal sealed partial class PvsSystem
|
||||
/// <summary>
|
||||
/// Processes queued client acks in parallel
|
||||
/// </summary>
|
||||
internal void ProcessQueuedAcks()
|
||||
internal WaitHandle ProcessQueuedAcks()
|
||||
{
|
||||
if (PendingAcks.Count == 0)
|
||||
return;
|
||||
{
|
||||
return ParallelManager.DummyResetEvent.WaitHandle;
|
||||
}
|
||||
|
||||
_toAck.Clear();
|
||||
|
||||
@@ -41,8 +47,8 @@ internal sealed partial class PvsSystem
|
||||
_toAck.Add(session);
|
||||
}
|
||||
|
||||
_parallelManager.ProcessNow(_ackJob, _toAck.Count);
|
||||
PendingAcks.Clear();
|
||||
return _parallelManager.Process(_ackJob, _toAck.Count);
|
||||
}
|
||||
|
||||
private record struct PvsAckJob : IParallelRobustJob
|
||||
@@ -67,37 +73,43 @@ internal sealed partial class PvsSystem
|
||||
return;
|
||||
|
||||
var ackedTick = sessionData.LastReceivedAck;
|
||||
Dictionary<NetEntity, PvsEntityVisibility>? ackedData;
|
||||
List<NetEntity>? ackedEnts;
|
||||
|
||||
if (sessionData.Overflow != null && sessionData.Overflow.Value.Tick <= ackedTick)
|
||||
{
|
||||
var (overflowTick, overflowEnts) = sessionData.Overflow.Value;
|
||||
sessionData.Overflow = null;
|
||||
ackedData = overflowEnts;
|
||||
ackedEnts = overflowEnts;
|
||||
|
||||
// Even though the acked tick might be newer, we have no guarantee that the client received the cached tick,
|
||||
// so discard it unless they happen to be equal.
|
||||
if (overflowTick != ackedTick)
|
||||
{
|
||||
_visSetPool.Return(overflowEnts);
|
||||
_netUidListPool.Return(overflowEnts);
|
||||
DebugTools.Assert(!sessionData.SentEntities.Values.Contains(overflowEnts));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!sessionData.SentEntities.TryGetValue(ackedTick, out ackedData))
|
||||
else if (!sessionData.SentEntities.TryGetValue(ackedTick, out ackedEnts))
|
||||
return;
|
||||
|
||||
// return last acked to pool, but only if it is not still in the OverflowDictionary.
|
||||
if (sessionData.LastAcked != null && !sessionData.SentEntities.ContainsKey(sessionData.LastAcked.Value.Tick))
|
||||
var entityData = sessionData.EntityData;
|
||||
foreach (var ent in CollectionsMarshal.AsSpan(ackedEnts))
|
||||
{
|
||||
DebugTools.Assert(!sessionData.SentEntities.Values.Contains(sessionData.LastAcked.Value.Data));
|
||||
_visSetPool.Return(sessionData.LastAcked.Value.Data);
|
||||
}
|
||||
ref var data = ref CollectionsMarshal.GetValueRefOrNullRef(entityData, ent);
|
||||
if (Unsafe.IsNullRef(ref data))
|
||||
{
|
||||
// This should only happen if the entity has been deleted.
|
||||
|
||||
sessionData.LastAcked = (ackedTick, ackedData);
|
||||
foreach (var ent in ackedData.Keys)
|
||||
{
|
||||
sessionData.LastSeenAt[ent] = ackedTick;
|
||||
// TODO PVS turn into debug assert
|
||||
if (TryGetEntity(ent, out _))
|
||||
Log.Error($"Acked entity {ToPrettyString(ent)} is missing entityData entry");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
data.EntityLastAcked = ackedTick;
|
||||
DebugTools.Assert(data.LastSent >= ackedTick); // LastSent may equal ackedTick if the packet was sent reliably.
|
||||
}
|
||||
|
||||
// The client acked a tick. If they requested a full state, this ack happened some time after that, so we can safely set this to false
|
||||
|
||||
264
Robust.Server/GameStates/PvsSystem.GetStates.cs
Normal file
264
Robust.Server/GameStates/PvsSystem.GetStates.cs
Normal file
@@ -0,0 +1,264 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
// This partial class contains code for turning a list of visible entities into actual entity states.
|
||||
internal sealed partial class PvsSystem
|
||||
{
|
||||
public void GetStateList(
|
||||
List<EntityState> states,
|
||||
List<NetEntity> toSend,
|
||||
SessionPvsData sessionData,
|
||||
GameTick fromTick)
|
||||
{
|
||||
DebugTools.Assert(states.Count == 0);
|
||||
var entData = sessionData.EntityData;
|
||||
var session = sessionData.Session;
|
||||
|
||||
if (sessionData.RequestedFull)
|
||||
{
|
||||
foreach (var netEntity in CollectionsMarshal.AsSpan(toSend))
|
||||
{
|
||||
ref var data = ref GetEntityData(entData, netEntity);
|
||||
DebugTools.Assert(data.LastSent == _gameTiming.CurTick);
|
||||
states.Add(GetFullEntityState(session, data.Entity.Owner, data.Entity.Comp));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var netEntity in CollectionsMarshal.AsSpan(toSend))
|
||||
{
|
||||
ref var data = ref GetEntityData(entData, netEntity);
|
||||
DebugTools.Assert(data.LastSent == _gameTiming.CurTick);
|
||||
|
||||
if (data.Visibility == PvsEntityVisibility.StayedUnchanged)
|
||||
continue;
|
||||
|
||||
var (uid, meta) = data.Entity;
|
||||
var entered = data.Visibility == PvsEntityVisibility.Entered;
|
||||
var entFromTick = entered ? data.EntityLastAcked : fromTick;
|
||||
|
||||
// TODO PVS turn into debug assert
|
||||
// This is should really be a debug assert, but I want to check for errors on live servers
|
||||
// If an entity is not marked as "entering" this tick, then it HAS to have been in the last acked state
|
||||
if (!entered && data.EntityLastAcked < fromTick)
|
||||
{
|
||||
Log.Error($"un-acked entity is not marked as entering. Entity{ToPrettyString(uid)}. FromTick: {fromTick}. CurTick: {_gameTiming.CurTick}. Data: {data}");
|
||||
}
|
||||
|
||||
var state = GetEntityState(session, uid, entFromTick, meta);
|
||||
|
||||
if (entered || !state.Empty)
|
||||
states.Add(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a network entity state for the given entity.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to generate this state for. This may be null if the state is for replay recordings.</param>
|
||||
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
|
||||
/// <param name="fromTick">Only provide delta changes from this tick.</param>
|
||||
/// <param name="meta">The entity's metadata component</param>
|
||||
/// <returns>New entity State for the given entity.</returns>
|
||||
private EntityState GetEntityState(ICommonSession? player, EntityUid entityUid, GameTick fromTick, MetaDataComponent meta)
|
||||
{
|
||||
var bus = EntityManager.EventBus;
|
||||
var changed = new List<ComponentChange>();
|
||||
|
||||
bool sendCompList = meta.LastComponentRemoved > fromTick;
|
||||
HashSet<ushort>? netComps = sendCompList ? new() : null;
|
||||
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
if (component.Deleted || !component.Initialized)
|
||||
{
|
||||
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (component.SendOnlyToOwner && player != null && player.AttachedEntity != entityUid)
|
||||
continue;
|
||||
|
||||
if (component.LastModifiedTick <= fromTick)
|
||||
{
|
||||
if (sendCompList && (!component.SessionSpecific || player == null || EntityManager.CanGetComponentState(bus, component, player)))
|
||||
netComps!.Add(netId);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (component.SessionSpecific && player != null && !EntityManager.CanGetComponentState(bus, component, player))
|
||||
continue;
|
||||
|
||||
var state = EntityManager.GetComponentState(bus, component, player, fromTick);
|
||||
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState delta || delta.FullState);
|
||||
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
|
||||
|
||||
if (sendCompList)
|
||||
netComps!.Add(netId);
|
||||
}
|
||||
|
||||
DebugTools.Assert(meta.EntityLastModifiedTick >= meta.LastComponentRemoved);
|
||||
DebugTools.Assert(GetEntity(meta.NetEntity) == entityUid);
|
||||
var entState = new EntityState(meta.NetEntity, changed, meta.EntityLastModifiedTick, netComps);
|
||||
|
||||
return entState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Variant of <see cref="GetEntityState"/> that includes all entity data, including data that can be inferred implicitly from the entity prototype.
|
||||
/// </summary>
|
||||
private EntityState GetFullEntityState(ICommonSession player, EntityUid entityUid, MetaDataComponent meta)
|
||||
{
|
||||
var bus = EntityManager.EventBus;
|
||||
var changed = new List<ComponentChange>();
|
||||
|
||||
HashSet<ushort> netComps = new();
|
||||
|
||||
foreach (var (netId, component) in meta.NetComponents)
|
||||
{
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
if (component.SendOnlyToOwner && player.AttachedEntity != entityUid)
|
||||
continue;
|
||||
|
||||
if (component.SessionSpecific && !EntityManager.CanGetComponentState(bus, component, player))
|
||||
continue;
|
||||
|
||||
var state = EntityManager.GetComponentState(bus, component, player, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
|
||||
netComps.Add(netId);
|
||||
}
|
||||
|
||||
var entState = new EntityState(meta.NetEntity, changed, meta.EntityLastModifiedTick, netComps);
|
||||
|
||||
return entState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
public (List<EntityState>?, List<NetEntity>?, GameTick fromTick) GetAllEntityStates(ICommonSession? player, GameTick fromTick, GameTick toTick)
|
||||
{
|
||||
List<EntityState>? stateEntities;
|
||||
var toSend = _uidSetPool.Get();
|
||||
DebugTools.Assert(toSend.Count == 0);
|
||||
bool enumerateAll = false;
|
||||
DebugTools.AssertEqual(toTick, _gameTiming.CurTick);
|
||||
DebugTools.Assert(toTick > fromTick);
|
||||
|
||||
if (player == null)
|
||||
{
|
||||
enumerateAll = fromTick == GameTick.Zero;
|
||||
}
|
||||
else if (!_seenAllEnts.Contains(player))
|
||||
{
|
||||
enumerateAll = true;
|
||||
fromTick = GameTick.Zero;
|
||||
}
|
||||
|
||||
if (toTick.Value - fromTick.Value > DirtyBufferSize)
|
||||
{
|
||||
// Fall back to enumerating over all entities.
|
||||
enumerateAll = true;
|
||||
}
|
||||
|
||||
if (enumerateAll)
|
||||
{
|
||||
stateEntities = new List<EntityState>(EntityManager.EntityCount);
|
||||
var query = EntityManager.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var md))
|
||||
{
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized, $"Entity {ToPrettyString(uid)} has not been initialized");
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating, $"Entity {ToPrettyString(uid)} is/has been terminated");
|
||||
if (md.EntityLastModifiedTick <= fromTick)
|
||||
continue;
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
if (state.Empty)
|
||||
{
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state while enumerating entities.
|
||||
Tick: {fromTick}--{toTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stateEntities = new();
|
||||
for (var i = fromTick.Value + 1; i <= toTick.Value; i++)
|
||||
{
|
||||
if (!TryGetDirtyEntities(new GameTick(i), out var add, out var dirty))
|
||||
{
|
||||
// This should be unreachable if `enumerateAll` is false.
|
||||
throw new Exception($"Failed to get tick dirty data. tick: {i}, from: {fromTick}, to {toTick}, buffer: {DirtyBufferSize}");
|
||||
}
|
||||
|
||||
foreach (var uid in add)
|
||||
{
|
||||
if (!toSend.Add(uid) || !_metaQuery.TryGetComponent(uid, out var md))
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized, $"Entity {ToPrettyString(uid)} has not been initialized");
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating, $"Entity {ToPrettyString(uid)} is/has been terminated");
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick, $"Entity {ToPrettyString(uid)} last modified tick is less than creation tick");
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick, $"Entity {ToPrettyString(uid)} last modified tick is less than from tick");
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
|
||||
if (state.Empty)
|
||||
{
|
||||
Log.Error($@"{nameof(GetEntityState)} returned an empty state for a new entity.
|
||||
Tick: {fromTick}--{toTick}
|
||||
Entity: {ToPrettyString(uid)}
|
||||
Last modified: {md.EntityLastModifiedTick}
|
||||
Metadata last modified: {md.LastModifiedTick}
|
||||
Transform last modified: {Transform(uid).LastModifiedTick}");
|
||||
continue;
|
||||
}
|
||||
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
|
||||
foreach (var uid in dirty)
|
||||
{
|
||||
DebugTools.Assert(!add.Contains(uid));
|
||||
if (!toSend.Add(uid) || !_metaQuery.TryGetComponent(uid, out var md))
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized, $"Entity {ToPrettyString(uid)} has not been initialized");
|
||||
DebugTools.Assert(md.EntityLifeStage < EntityLifeStage.Terminating, $"Entity {ToPrettyString(uid)} is/has been terminated");
|
||||
DebugTools.Assert(md.EntityLastModifiedTick >= md.CreationTick, $"Entity {ToPrettyString(uid)} last modified tick is less than creation tick");
|
||||
DebugTools.Assert(md.EntityLastModifiedTick > fromTick, $"Entity {ToPrettyString(uid)} last modified tick is less than from tick");
|
||||
|
||||
var state = GetEntityState(player, uid, fromTick, md);
|
||||
if (!state.Empty)
|
||||
stateEntities.Add(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_uidSetPool.Return(toSend);
|
||||
var deletions = _entityPvsCollection.GetDeletedIndices(fromTick);
|
||||
|
||||
if (stateEntities.Count == 0)
|
||||
stateEntities = null;
|
||||
|
||||
return (stateEntities, deletions, fromTick);
|
||||
}
|
||||
}
|
||||
40
Robust.Server/GameStates/PvsSystem.Helpers.cs
Normal file
40
Robust.Server/GameStates/PvsSystem.Helpers.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
// This partial class contains miscellaneous convenience functions
|
||||
internal sealed partial class PvsSystem
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ref EntityData GetOrNewEntityData(Dictionary<NetEntity, EntityData> entityData, NetEntity entity)
|
||||
{
|
||||
ref var data = ref CollectionsMarshal.GetValueRefOrAddDefault(entityData, entity, out var exists);
|
||||
if (!exists)
|
||||
{
|
||||
if (TryGetEntityData(entity, out var uid, out var meta))
|
||||
{
|
||||
data = new((uid.Value, meta));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Attempted to add deleted entity. NetUid: {entity}");
|
||||
}
|
||||
}
|
||||
DebugTools.AssertEqual(data.Entity.Comp.NetEntity, entity);
|
||||
return ref data;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal ref EntityData GetEntityData(Dictionary<NetEntity, EntityData> entityData, NetEntity entity)
|
||||
{
|
||||
DebugTools.Assert(entityData.ContainsKey(entity));
|
||||
ref var data = ref CollectionsMarshal.GetValueRefOrAddDefault(entityData, entity, out _);
|
||||
DebugTools.AssertNotNull(data.Entity.Comp);
|
||||
DebugTools.AssertEqual(data.Entity.Comp.NetEntity, entity);
|
||||
return ref data;
|
||||
}
|
||||
}
|
||||
254
Robust.Server/GameStates/PvsSystem.ToSendSet.cs
Normal file
254
Robust.Server/GameStates/PvsSystem.ToSendSet.cs
Normal file
@@ -0,0 +1,254 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
// This partial class contains contains methods for adding entities to the set of entities that are about to get sent
|
||||
// to a player.
|
||||
internal sealed partial class PvsSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// This method adds an entity to the to-send list, updates the last-sent tick, and updates the entity's visibility.
|
||||
/// </summary>
|
||||
private void AddToSendList(
|
||||
NetEntity ent,
|
||||
ref EntityData data,
|
||||
List<NetEntity> list,
|
||||
GameTick fromTick,
|
||||
GameTick toTick,
|
||||
bool entered,
|
||||
ref int dirtyEntityCount)
|
||||
{
|
||||
var meta = data.Entity.Comp;
|
||||
DebugTools.AssertEqual(meta.NetEntity, ent);
|
||||
DebugTools.Assert(fromTick < toTick);
|
||||
DebugTools.AssertNotEqual(data.LastSent, toTick);
|
||||
DebugTools.AssertEqual(toTick, _gameTiming.CurTick);
|
||||
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
|
||||
if (meta == null || meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
var rep = new EntityStringRepresentation(data.Entity);
|
||||
Log.Error($"Attempted to add a deleted entity to PVS send set: '{rep}'. Deletion queued: {EntityManager.IsQueuedForDeletion(data.Entity)}. Trace:\n{Environment.StackTrace}");
|
||||
|
||||
// This can happen if some entity was some removed from it's parent while that parent was being deleted.
|
||||
// As a result the entity was marked for deletion but was never actually properly deleted.
|
||||
EntityManager.QueueDeleteEntity(data.Entity);
|
||||
return;
|
||||
}
|
||||
|
||||
data.LastSent = toTick;
|
||||
list.Add(ent);
|
||||
|
||||
if (entered)
|
||||
{
|
||||
data.Visibility = PvsEntityVisibility.Entered;
|
||||
dirtyEntityCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
if (meta.EntityLastModifiedTick <= fromTick)
|
||||
{
|
||||
//entity has been sent before and hasn't been updated since
|
||||
data.Visibility = PvsEntityVisibility.StayedUnchanged;
|
||||
return;
|
||||
}
|
||||
|
||||
//add us
|
||||
data.Visibility = PvsEntityVisibility.StayedChanged;
|
||||
dirtyEntityCount++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method figures out whether a given entity is currently entering a player's PVS range.
|
||||
/// This method will also check that the player's PVS entry budget is not being exceeded.
|
||||
/// </summary>
|
||||
private (bool Entered, bool BudgetExceeded) GetPvsEntryData(ref EntityData entity,
|
||||
GameTick fromTick,
|
||||
GameTick toTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
int newEntityBudget,
|
||||
int enteredEntityBudget)
|
||||
{
|
||||
DebugTools.AssertEqual(toTick, _gameTiming.CurTick);
|
||||
|
||||
var enteredSinceLastSent = fromTick == GameTick.Zero
|
||||
|| entity.LastSent == GameTick.Zero
|
||||
|| entity.LastSent.Value != toTick.Value - 1;
|
||||
|
||||
var entered = enteredSinceLastSent
|
||||
|| entity.EntityLastAcked == GameTick.Zero
|
||||
|| entity.EntityLastAcked < fromTick // this entity was not in the last acked state.
|
||||
|| entity.LastLeftView >= fromTick; // entity left and re-entered sometime after the last acked tick
|
||||
|
||||
// If the entity is entering, but we already sent this entering entity in the last message, we won't add it to
|
||||
// the budget. Chances are the packet will arrive in a nice and orderly fashion, and the client will stick to
|
||||
// their requested budget. However this can cause issues if a packet gets dropped, because a player may create
|
||||
// 2x or more times the normal entity creation budget.
|
||||
if (enteredSinceLastSent)
|
||||
{
|
||||
if (newEntityCount >= newEntityBudget || enteredEntityCount >= enteredEntityBudget)
|
||||
return (entered, true);
|
||||
|
||||
enteredEntityCount++;
|
||||
|
||||
if (entity.EntityLastAcked == GameTick.Zero)
|
||||
newEntityCount++;
|
||||
}
|
||||
|
||||
return (entered, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively add an entity and all of its children to the to-send set.
|
||||
/// </summary>
|
||||
private void RecursivelyAddTreeNode(in NetEntity nodeIndex,
|
||||
RobustTree<NetEntity> tree,
|
||||
List<NetEntity> toSend,
|
||||
Dictionary<NetEntity, EntityData> entityData,
|
||||
Stack<NetEntity> stack,
|
||||
GameTick fromTick,
|
||||
GameTick toTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
ref int dirtyEntityCount,
|
||||
int newEntityBudget,
|
||||
int enteredEntityBudget)
|
||||
{
|
||||
stack.Push(nodeIndex);
|
||||
|
||||
while (stack.TryPop(out var currentNodeIndex))
|
||||
{
|
||||
DebugTools.Assert(currentNodeIndex.IsValid());
|
||||
|
||||
// As every map is parented to uid 0 in the tree we still need to get their children, plus because we go top-down
|
||||
// we may find duplicate parents with children we haven't encountered before
|
||||
// on different chunks (this is especially common with direct grid children)
|
||||
|
||||
ref var data = ref GetOrNewEntityData(entityData, currentNodeIndex);
|
||||
if (data.LastSent != toTick)
|
||||
{
|
||||
var (entered, budgetExceeded) = GetPvsEntryData(ref data, fromTick, toTick,
|
||||
ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
|
||||
if (budgetExceeded)
|
||||
{
|
||||
// should be false for the majority of entities
|
||||
if (data.LastSent == GameTick.Zero)
|
||||
entityData.Remove(currentNodeIndex);
|
||||
|
||||
// We continue, but do not stop iterating this or other chunks.
|
||||
// This is to avoid sending bad pvs-leave messages. I.e., other entities may have just stayed in view, and we can send them without exceeding our budget.
|
||||
continue;
|
||||
}
|
||||
|
||||
AddToSendList(currentNodeIndex, ref data, toSend, fromTick, toTick, entered, ref dirtyEntityCount);
|
||||
}
|
||||
|
||||
if (!tree.TryGet(currentNodeIndex, out var node))
|
||||
{
|
||||
Log.Error($"tree is missing the current node! Node: {currentNodeIndex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.Children == null)
|
||||
continue;
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
stack.Push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively add an entity and all of its parents to the to-send set. This optionally also adds all children.
|
||||
/// </summary>
|
||||
public bool RecursivelyAddOverride(in EntityUid uid,
|
||||
List<NetEntity> toSend,
|
||||
Dictionary<NetEntity, EntityData> entityData,
|
||||
GameTick fromTick,
|
||||
GameTick toTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
ref int dirtyEntityCount,
|
||||
int newEntityBudget,
|
||||
int enteredEntityBudget,
|
||||
bool addChildren = false)
|
||||
{
|
||||
//are we valid?
|
||||
//sometimes uids gets added without being valid YET (looking at you mapmanager) (mapcreate & gridcreated fire before the uids becomes valid)
|
||||
if (!uid.IsValid())
|
||||
return false;
|
||||
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
var parent = xform.ParentUid;
|
||||
if (parent.IsValid() && !RecursivelyAddOverride(in parent, toSend, entityData, fromTick, toTick,
|
||||
ref newEntityCount, ref enteredEntityCount, ref dirtyEntityCount, newEntityBudget, enteredEntityBudget))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var netEntity = _metaQuery.GetComponent(uid).NetEntity;
|
||||
|
||||
// Note that we check this AFTER adding parents. This is because while this entity may already have been added
|
||||
// to the toSend set, it doesn't guarantee that its parents have been. E.g., if a player ghost just teleported
|
||||
// to follow a far away entity, the player's own entity is still being sent, but we need to ensure that we also
|
||||
// send the new parents, which may otherwise be delayed because of the PVS budget.
|
||||
|
||||
var curTick = _gameTiming.CurTick;
|
||||
ref var data = ref GetOrNewEntityData(entityData, netEntity);
|
||||
if (data.LastSent != curTick)
|
||||
{
|
||||
var (entered, _) = GetPvsEntryData(ref data, fromTick, toTick, ref newEntityCount, ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
AddToSendList(netEntity, ref data, toSend, fromTick, toTick, entered, ref dirtyEntityCount);
|
||||
}
|
||||
|
||||
if (addChildren)
|
||||
{
|
||||
RecursivelyAddChildren(xform, toSend, entityData, fromTick, toTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref dirtyEntityCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively add an entity and all of its children to the to-send set.
|
||||
/// </summary>
|
||||
private void RecursivelyAddChildren(TransformComponent xform,
|
||||
List<NetEntity> toSend,
|
||||
Dictionary<NetEntity, EntityData> entityData,
|
||||
in GameTick fromTick,
|
||||
in GameTick toTick,
|
||||
ref int newEntityCount,
|
||||
ref int enteredEntityCount,
|
||||
ref int dirtyEntityCount,
|
||||
in int newEntityBudget,
|
||||
in int enteredEntityBudget)
|
||||
{
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(child, out var childXform))
|
||||
continue;
|
||||
|
||||
var metadata = _metaQuery.GetComponent(child);
|
||||
var netChild = metadata.NetEntity;
|
||||
ref var data = ref GetOrNewEntityData(entityData, netChild);
|
||||
if (data.LastSent != toTick)
|
||||
{
|
||||
var (entered, _) = GetPvsEntryData(ref data, fromTick, toTick, ref newEntityCount,
|
||||
ref enteredEntityCount, newEntityBudget, enteredEntityBudget);
|
||||
AddToSendList(netChild, ref data, toSend, fromTick, toTick, entered, ref dirtyEntityCount);
|
||||
}
|
||||
|
||||
RecursivelyAddChildren(childXform, toSend, entityData, fromTick, toTick, ref newEntityCount,
|
||||
ref enteredEntityCount, ref dirtyEntityCount, in newEntityBudget, in enteredEntityBudget);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,8 @@ public sealed class RobustTree<T> where T : notnull
|
||||
|
||||
public TreeNode this[T index] => _nodeIndex[index];
|
||||
|
||||
public bool TryGet(T index, out TreeNode node) => _nodeIndex.TryGetValue(index, out node);
|
||||
|
||||
public void Remove(T value, bool mend = false)
|
||||
{
|
||||
if (!_nodeIndex.TryGetValue(value, out var node))
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -173,6 +174,10 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
{
|
||||
var players = _playerManager.Sessions.Where(o => o.Status == SessionStatus.InGame).ToArray();
|
||||
|
||||
// Update client acks, which is used to figure out what data needs to be sent to clients
|
||||
// This only needs SessionData which isn't touched during GetPVSData or ProcessCollections.
|
||||
var ackJob = _pvs.ProcessQueuedAcks();
|
||||
|
||||
// Update entity positions in PVS chunks/collections
|
||||
// TODO disable processing if culling is disabled? Need to check if toggling PVS breaks anything.
|
||||
// TODO parallelize?
|
||||
@@ -189,11 +194,7 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
pvsData = GetPVSData(players);
|
||||
}
|
||||
|
||||
// Update client acks, which is used to figure out what data needs to be sent to clients
|
||||
using (_usageHistogram.WithLabels("Process Acks").NewTimer())
|
||||
{
|
||||
_pvs.ProcessQueuedAcks();
|
||||
}
|
||||
ackJob.WaitOne();
|
||||
|
||||
// Construct & send the game state to each player.
|
||||
GameTick oldestAck;
|
||||
@@ -253,7 +254,8 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
}
|
||||
catch (Exception e) // Catch EVERY exception
|
||||
{
|
||||
_logger.Log(LogLevel.Error, e, "Caught exception while generating mail.");
|
||||
var source = i >= 0 ? players[i].ToString() : "replays";
|
||||
_logger.Log(LogLevel.Error, e, $"Caught exception while generating mail for {source}.");
|
||||
}
|
||||
return resource;
|
||||
}
|
||||
@@ -265,15 +267,14 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
{
|
||||
public HashSet<int>[] PlayerChunks;
|
||||
public EntityUid[][] ViewerEntities;
|
||||
public (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] ChunkCache;
|
||||
public RobustTree<NetEntity>?[] ChunkCache;
|
||||
}
|
||||
|
||||
private PvsData? GetPVSData(ICommonSession[] players)
|
||||
{
|
||||
var chunks = _pvs.GetChunks(players, ref _playerChunks, ref _viewerEntities);
|
||||
var chunksCount = chunks.Count;
|
||||
var chunkCache =
|
||||
new (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[chunksCount];
|
||||
var chunkCache = new RobustTree<NetEntity>?[chunksCount];
|
||||
// Update the reused trees sequentially to avoid having to lock the dictionary per chunk.
|
||||
var reuse = ArrayPool<bool>.Shared.Rent(chunksCount);
|
||||
|
||||
@@ -310,7 +311,7 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
{
|
||||
var channel = session.Channel;
|
||||
var sessionData = _pvs.PlayerData[session];
|
||||
var lastAck = sessionData.LastReceivedAck;
|
||||
var from = sessionData.RequestedFull ? GameTick.Zero : sessionData.LastReceivedAck;
|
||||
List<NetEntity>? leftPvs = null;
|
||||
List<EntityState>? entStates;
|
||||
List<NetEntity>? deletions;
|
||||
@@ -321,7 +322,7 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
{
|
||||
(entStates, deletions, leftPvs, fromTick) = _pvs.CalculateEntityStates(
|
||||
session,
|
||||
lastAck,
|
||||
from,
|
||||
_gameTiming.CurTick,
|
||||
pvsData.Value.ChunkCache,
|
||||
pvsData.Value.PlayerChunks[i],
|
||||
@@ -329,7 +330,7 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
}
|
||||
else
|
||||
{
|
||||
(entStates, deletions, fromTick) = _pvs.GetAllEntityStates(session, lastAck, _gameTiming.CurTick);
|
||||
(entStates, deletions, fromTick) = _pvs.GetAllEntityStates(session, from, _gameTiming.CurTick);
|
||||
}
|
||||
|
||||
var playerStates = _playerManager.GetPlayerStates(fromTick);
|
||||
@@ -346,7 +347,7 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
playerStates,
|
||||
deletions);
|
||||
|
||||
InterlockedHelper.Min(ref oldestAckValue, lastAck.Value);
|
||||
InterlockedHelper.Min(ref oldestAckValue, from.Value);
|
||||
|
||||
// actually send the state
|
||||
var stateUpdateMessage = new MsgState();
|
||||
@@ -360,13 +361,13 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
// We also do this if the client's last ack is too old. This helps prevent things like the entity deletion
|
||||
// history from becoming too bloated if a bad client fails to send acks for whatever reason.
|
||||
|
||||
if (_gameTiming.CurTick.Value > lastAck.Value + _pvs.ForceAckThreshold)
|
||||
if (_gameTiming.CurTick.Value > from.Value + _pvs.ForceAckThreshold)
|
||||
{
|
||||
stateUpdateMessage.ForceSendReliably = true;
|
||||
#if FULL_RELEASE
|
||||
var connectedTime = (DateTime.UtcNow - session.ConnectedTime).TotalMinutes;
|
||||
if (lastAck > GameTick.Zero && connectedTime > 1)
|
||||
_logger.Warning($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
|
||||
if (sessionData.LastReceivedAck > GameTick.Zero && connectedTime > 1)
|
||||
_logger.Warning($"Client {session} exceeded ack-tick threshold. Last ack: {sessionData.LastReceivedAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -436,22 +437,22 @@ Oldest acked clients: {string.Join(", ", players)}
|
||||
|
||||
public List<(int, IChunkIndexLocation)> Chunks;
|
||||
public bool[] Reuse;
|
||||
public (Dictionary<NetEntity, MetaDataComponent> metadata, RobustTree<NetEntity> tree)?[] ChunkCache;
|
||||
public RobustTree<NetEntity>?[] ChunkCache;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var (visMask, chunkIndexLocation) = Chunks[index];
|
||||
Reuse[index] = Pvs.TryCalculateChunk(chunkIndexLocation, visMask, out var chunk);
|
||||
ChunkCache[index] = chunk;
|
||||
Reuse[index] = Pvs.TryCalculateChunk(chunkIndexLocation, visMask, out var tree);
|
||||
ChunkCache[index] = tree;
|
||||
|
||||
#if DEBUG
|
||||
if (chunk == null)
|
||||
if (tree == null)
|
||||
return;
|
||||
|
||||
// Each root nodes should simply be a map or a grid entity.
|
||||
DebugTools.Assert(chunk.Value.tree.RootNodes.Count == 1,
|
||||
$"Root node count is {chunk.Value.tree.RootNodes.Count} instead of 1.");
|
||||
var nent = chunk.Value.tree.RootNodes.FirstOrDefault();
|
||||
DebugTools.Assert(tree.RootNodes.Count == 1,
|
||||
$"Root node count is {tree.RootNodes.Count} instead of 1.");
|
||||
var nent = tree.RootNodes.FirstOrDefault();
|
||||
var ent = EntManager.GetEntity(nent);
|
||||
DebugTools.Assert(EntManager.EntityExists(ent), $"Root node does not exist. Node {ent}.");
|
||||
DebugTools.Assert(EntManager.HasComponent<MapComponent>(ent)
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace Robust.Server.Physics
|
||||
|
||||
foreach (var session in _subscribedSessions)
|
||||
{
|
||||
RaiseNetworkEvent(msg, session.ConnectedClient);
|
||||
RaiseNetworkEvent(msg, session.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
<PackageReference Include="SpaceWizards.Sodium" Version="0.2.1" PrivateAssets="compile" />
|
||||
<PackageReference Include="SharpZstd.Interop" Version="1.5.2-beta2" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Web;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -77,6 +79,8 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
if (string.IsNullOrEmpty(downloadUrl))
|
||||
{
|
||||
var query = HttpUtility.ParseQueryString(context.Url.Query);
|
||||
var optional = query.Keys;
|
||||
buildInfo = await PrepareACZBuildInfo();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -278,7 +278,7 @@ namespace Robust.Server.ViewVariables
|
||||
|
||||
var closeMsg = new MsgViewVariablesCloseSession();
|
||||
closeMsg.SessionId = session.SessionId;
|
||||
_netManager.ServerSendMessage(closeMsg, player.ConnectedClient);
|
||||
_netManager.ServerSendMessage(closeMsg, player.Channel);
|
||||
}
|
||||
|
||||
private bool TryReinterpretValue(object? input, [NotNullWhen(true)] out object? output)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -403,7 +404,7 @@ namespace Robust.Shared.Maths
|
||||
/// </param>
|
||||
public static Color FromHsl(Vector4 hsl)
|
||||
{
|
||||
var hue = hsl.X * 360.0f;
|
||||
var hue = (hsl.X - MathF.Truncate(hsl.X)) * 360.0f;
|
||||
var saturation = hsl.Y;
|
||||
var lightness = hsl.Z;
|
||||
|
||||
@@ -514,7 +515,7 @@ namespace Robust.Shared.Maths
|
||||
/// </param>
|
||||
public static Color FromHsv(Vector4 hsv)
|
||||
{
|
||||
var hue = hsv.X * 360.0f;
|
||||
var hue = (hsv.X - MathF.Truncate(hsv.X)) * 360.0f;
|
||||
var saturation = hsv.Y;
|
||||
var value = hsv.Z;
|
||||
|
||||
@@ -1777,7 +1778,7 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
public static Color YellowGreen => new(154, 205, 50, 255);
|
||||
|
||||
private static readonly Dictionary<string, Color> DefaultColors = new()
|
||||
private static readonly FrozenDictionary<string, Color> DefaultColors = new Dictionary<string, Color>()
|
||||
{
|
||||
["transparent"] = Transparent,
|
||||
["aliceblue"] = AliceBlue,
|
||||
@@ -1924,12 +1925,12 @@ namespace Robust.Shared.Maths
|
||||
["whitesmoke"] = WhiteSmoke,
|
||||
["yellow"] = Yellow,
|
||||
["yellowgreen"] = YellowGreen,
|
||||
};
|
||||
}.ToFrozenDictionary();
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly Dictionary<Color, string> DefaultColorsInverted =
|
||||
DefaultColors.ToLookup(pair => pair.Value).ToDictionary(i => i.Key, i => i.First().Key);
|
||||
private static readonly FrozenDictionary<Color, string> DefaultColorsInverted =
|
||||
DefaultColors.ToLookup(pair => pair.Value).ToFrozenDictionary(i => i.Key, i => i.First().Key);
|
||||
|
||||
public readonly string? Name()
|
||||
{
|
||||
|
||||
@@ -30,6 +30,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
@@ -385,7 +386,7 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Matrix3 CreateRotation(Angle angle)
|
||||
public static Matrix3 CreateRotation(double angle)
|
||||
{
|
||||
var cos = (float) Math.Cos(angle);
|
||||
var sin = (float) Math.Sin(angle);
|
||||
@@ -426,12 +427,12 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Matrix3 CreateTransform(float posX, float posY, float angle, float scaleX = 1, float scaleY = 1)
|
||||
public static Matrix3 CreateTransform(float posX, float posY, double angle, float scaleX = 1, float scaleY = 1)
|
||||
{
|
||||
// returns a matrix that is equivalent to returning CreateScale(scale) * CreateRotation(angle) * CreateTranslation(posX, posY)
|
||||
|
||||
var sin = MathF.Sin(angle);
|
||||
var cos = MathF.Cos(angle);
|
||||
var sin = (float) Math.Sin(angle);
|
||||
var cos = (float) Math.Cos(angle);
|
||||
|
||||
return new Matrix3
|
||||
{
|
||||
@@ -446,12 +447,12 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Matrix3 CreateInverseTransform(float posX, float posY, float angle, float scaleX = 1, float scaleY = 1)
|
||||
public static Matrix3 CreateInverseTransform(float posX, float posY, double angle, float scaleX = 1, float scaleY = 1)
|
||||
{
|
||||
// returns a matrix that is equivalent to returning CreateTranslation(-posX, -posY) * CreateRotation(-angle) * CreateScale(1/scaleX, 1/scaleY)
|
||||
|
||||
var sin = MathF.Sin(angle);
|
||||
var cos = MathF.Cos(angle);
|
||||
var sin = (float) Math.Sin(angle);
|
||||
var cos = (float) Math.Cos(angle);
|
||||
|
||||
return new Matrix3
|
||||
{
|
||||
@@ -493,7 +494,17 @@ namespace Robust.Shared.Maths
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Matrix3 CreateInverseTransform(in Vector2 position, in Angle angle, in Vector2 scale)
|
||||
{
|
||||
return CreateInverseTransform(position.X, position.Y, (float)angle.Theta, scale.X, scale.Y);
|
||||
return CreateInverseTransform(position.X, position.Y, angle.Theta, scale.X, scale.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of the Matrix. Will have some precision loss.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly Angle Rotation()
|
||||
{
|
||||
return new Vector2(R0C0, R1C0).ToAngle();
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using NVorbis;
|
||||
|
||||
namespace Robust.Shared.Audio.AudioLoading;
|
||||
@@ -16,7 +17,8 @@ internal static class AudioLoaderOgg
|
||||
/// <param name="stream">Audio file stream to load.</param>
|
||||
public static AudioMetadata LoadAudioMetadata(Stream stream)
|
||||
{
|
||||
using var reader = new VorbisReader(stream);
|
||||
using var reader = new VorbisReader(stream, false);
|
||||
reader.Initialize();
|
||||
return new AudioMetadata(reader.TotalTime, reader.Channels, reader.Tags.Title, reader.Tags.Artist);
|
||||
}
|
||||
|
||||
@@ -26,39 +28,88 @@ internal static class AudioLoaderOgg
|
||||
/// <param name="stream">Audio file stream to load.</param>
|
||||
public static OggVorbisData LoadAudioData(Stream stream)
|
||||
{
|
||||
using var vorbis = new NVorbis.VorbisReader(stream, false);
|
||||
using var vorbis = new VorbisReader(stream, false);
|
||||
vorbis.Initialize();
|
||||
|
||||
var sampleRate = vorbis.SampleRate;
|
||||
var channels = vorbis.Channels;
|
||||
var totalSamples = vorbis.TotalSamples;
|
||||
|
||||
var readSamples = 0;
|
||||
var buffer = new float[totalSamples * channels];
|
||||
var totalValues = totalSamples * channels;
|
||||
var readValues = 0;
|
||||
var buffer = new short[totalSamples * channels];
|
||||
Span<float> readBuffer = stackalloc float[32768];
|
||||
|
||||
while (readSamples < totalSamples)
|
||||
while (readValues < totalValues)
|
||||
{
|
||||
var read = vorbis.ReadSamples(buffer, readSamples * channels, buffer.Length - readSamples);
|
||||
var read = ReadSamples(buffer.AsSpan(readValues), readBuffer, channels, vorbis);
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
readSamples += read;
|
||||
readValues += read;
|
||||
}
|
||||
|
||||
return new OggVorbisData(totalSamples, sampleRate, channels, buffer, vorbis.Tags.Title, vorbis.Tags.Artist);
|
||||
}
|
||||
|
||||
private static int ReadSamples(Span<short> dest, Span<float> readBuffer, int channels, VorbisReader reader)
|
||||
{
|
||||
var read = reader.ReadSamples(readBuffer);
|
||||
read *= channels;
|
||||
|
||||
ConvertToShort(readBuffer[..read], dest[..read]);
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private static void ConvertToShort(ReadOnlySpan<float> src, Span<short> dst)
|
||||
{
|
||||
if (src.Length != dst.Length)
|
||||
throw new InvalidOperationException("Invalid lengths!");
|
||||
|
||||
var simdSamples = (src.Length / Vector<short>.Count) * Vector<short>.Count;
|
||||
|
||||
// Note: I think according to spec we'd actually need to multiply negative values with 2^15 instead of 2^15-1.
|
||||
// because it's -32768 -> 32767
|
||||
// Can't be arsed though
|
||||
var factor = new Vector<float>(short.MaxValue);
|
||||
|
||||
ref readonly var srcBase = ref src[0];
|
||||
ref var dstBase = ref dst[0];
|
||||
|
||||
for (var i = 0; i < simdSamples; i += Vector<short>.Count)
|
||||
{
|
||||
var lower = Vector.LoadUnsafe(in srcBase, (nuint) i);
|
||||
var upper = Vector.LoadUnsafe(in srcBase, (nuint) (i + Vector<float>.Count));
|
||||
|
||||
lower *= factor;
|
||||
upper *= factor;
|
||||
|
||||
var lowerInt = Vector.ConvertToInt32(lower);
|
||||
var upperInt = Vector.ConvertToInt32(upper);
|
||||
|
||||
var merged = Vector.Narrow(lowerInt, upperInt);
|
||||
|
||||
merged.StoreUnsafe(ref dstBase, (nuint) i);
|
||||
}
|
||||
|
||||
for (var i = simdSamples; i < src.Length; i++)
|
||||
{
|
||||
dst[i] = (short)(src[i] * short.MaxValue);
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly struct OggVorbisData
|
||||
{
|
||||
public readonly long TotalSamples;
|
||||
public readonly long SampleRate;
|
||||
public readonly long Channels;
|
||||
public readonly ReadOnlyMemory<float> Data;
|
||||
public readonly ReadOnlyMemory<short> Data;
|
||||
public readonly string Title;
|
||||
public readonly string Artist;
|
||||
|
||||
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<float> data, string title, string artist)
|
||||
public OggVorbisData(long totalSamples, long sampleRate, long channels, ReadOnlyMemory<short> data,
|
||||
string title, string artist)
|
||||
{
|
||||
TotalSamples = totalSamples;
|
||||
SampleRate = sampleRate;
|
||||
|
||||
@@ -63,21 +63,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
|
||||
protected void SetZOffset(float value)
|
||||
{
|
||||
var query = AllEntityQuery<AudioComponent>();
|
||||
var oldZOffset = ZOffset;
|
||||
ZOffset = value;
|
||||
|
||||
while (query.MoveNext(out var uid, out var audio))
|
||||
{
|
||||
// Pythagoras back to normal then adjust.
|
||||
var maxDistance = MathF.Pow(audio.Params.MaxDistance, 2) - oldZOffset;
|
||||
var refDistance = MathF.Pow(audio.Params.ReferenceDistance, 2) - oldZOffset;
|
||||
|
||||
audio.Params.MaxDistance = maxDistance;
|
||||
audio.Params.ReferenceDistance = refDistance;
|
||||
audio.Params = GetAdjustedParams(audio.Params);
|
||||
Dirty(uid, audio);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnAudioUnpaused(EntityUid uid, AudioComponent component, ref EntityUnpausedEvent args)
|
||||
@@ -141,9 +127,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
DebugTools.Assert(!string.IsNullOrEmpty(fileName));
|
||||
audioParams ??= AudioParams.Default;
|
||||
var comp = AddComp<Components.AudioComponent>(uid);
|
||||
var comp = AddComp<AudioComponent>(uid);
|
||||
comp.FileName = fileName;
|
||||
comp.Params = GetAdjustedParams(audioParams.Value);
|
||||
comp.Params = audioParams.Value;
|
||||
comp.AudioStart = Timing.CurTime;
|
||||
|
||||
if (!audioParams.Value.Loop)
|
||||
@@ -155,22 +141,14 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
despawn.Lifetime = (float) length.TotalSeconds + 0.01f;
|
||||
}
|
||||
|
||||
if (comp.Params.Variation != null && comp.Params.Variation.Value != 0f)
|
||||
{
|
||||
comp.Params.Pitch *= (float) RandMan.NextGaussian(1, comp.Params.Variation.Value);
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Accounts for ZOffset on audio distance.
|
||||
/// </summary>
|
||||
private AudioParams GetAdjustedParams(AudioParams audioParams)
|
||||
{
|
||||
var maxDistance = GetAudioDistance(audioParams.MaxDistance);
|
||||
var refDistance = GetAudioDistance(audioParams.ReferenceDistance);
|
||||
|
||||
return audioParams
|
||||
.WithMaxDistance(maxDistance)
|
||||
.WithReferenceDistance(refDistance);
|
||||
}
|
||||
|
||||
public static float GainToVolume(float value)
|
||||
{
|
||||
return 10f * MathF.Log10(value);
|
||||
@@ -205,6 +183,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
return;
|
||||
|
||||
component.Params.Volume = value;
|
||||
component.Volume = value;
|
||||
Dirty(entity.Value, component);
|
||||
}
|
||||
|
||||
|
||||
@@ -1030,7 +1030,7 @@ namespace Robust.Shared
|
||||
/// Master volume for audio output.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AudioMasterVolume =
|
||||
CVarDef.Create("audio.mastervolume", 1.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
CVarDef.Create("audio.mastervolume", 0.50f, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum raycast distance for audio occlusion.
|
||||
@@ -1302,7 +1302,7 @@ namespace Robust.Shared
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<float> MidiVolume =
|
||||
CVarDef.Create("midi.volume", 0f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
CVarDef.Create("midi.volume", 0.50f, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* HUB
|
||||
|
||||
@@ -58,11 +58,10 @@ internal sealed class RecursiveMoveSystem : EntitySystem
|
||||
// annoyingly, containers aren't guaranteed to occlude sprites & lights
|
||||
// but AFAIK thats currently unused???
|
||||
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (_xformQuery.TryGetComponent(child.Value, out var childXform))
|
||||
AnythingMovedSubHandler(child.Value, childXform);
|
||||
if (_xformQuery.TryGetComponent(child, out var childXform))
|
||||
AnythingMovedSubHandler(child, childXform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,11 +799,5 @@ namespace Robust.Shared.Configuration
|
||||
public InvalidConfigurationException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
protected InvalidConfigurationException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public sealed class TeleportToCommand : LocalizedCommands
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_players.Sessions.TryFirstOrDefault(x => x.ConnectedClient.UserName == str, out var session)
|
||||
if (_players.Sessions.TryFirstOrDefault(x => x.Channel.UserName == str, out var session)
|
||||
&& _entities.TryGetComponent(session.AttachedEntity, out transform))
|
||||
{
|
||||
victimUid = session.AttachedEntity;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Shared.Containers
|
||||
public string ID = default!;
|
||||
|
||||
[NonSerialized]
|
||||
internal ContainerManagerComponent Manager = default!;
|
||||
protected internal ContainerManagerComponent Manager = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents light from escaping the container, from ex. a flashlight.
|
||||
@@ -71,14 +71,6 @@ namespace Robust.Shared.Containers
|
||||
[DataField("showEnts")]
|
||||
public bool ShowContents { get; set; }
|
||||
|
||||
internal void Init(string id, EntityUid owner, ContainerManagerComponent component)
|
||||
{
|
||||
DebugTools.AssertNull(ID);
|
||||
ID = id;
|
||||
Manager = component;
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
[Obsolete("Use container system method")]
|
||||
public bool Insert(
|
||||
EntityUid toinsert,
|
||||
@@ -90,172 +82,7 @@ namespace Robust.Shared.Containers
|
||||
bool force = false)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
DebugTools.AssertOwner(toinsert, transform);
|
||||
DebugTools.AssertOwner(Owner, ownerTransform);
|
||||
DebugTools.AssertOwner(toinsert, physics);
|
||||
DebugTools.Assert(!ExpectedEntities.Contains(entMan.GetNetEntity(toinsert)));
|
||||
DebugTools.Assert(Manager.Containers.ContainsKey(ID));
|
||||
|
||||
var physicsQuery = entMan.GetEntityQuery<PhysicsComponent>();
|
||||
var transformQuery = entMan.GetEntityQuery<TransformComponent>();
|
||||
var jointQuery = entMan.GetEntityQuery<JointComponent>();
|
||||
|
||||
// ECS containers when
|
||||
var physicsSys = entMan.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
|
||||
var jointSys = entMan.EntitySysManager.GetEntitySystem<SharedJointSystem>();
|
||||
var containerSys = entMan.EntitySysManager.GetEntitySystem<SharedContainerSystem>();
|
||||
|
||||
// If someone is attempting to insert an entity into a container that is getting deleted, then we will
|
||||
// automatically delete that entity. I.e., the insertion automatically "succeeds" and both entities get deleted.
|
||||
// This is consistent with what happens if you attempt to attach an entity to a terminating parent.
|
||||
|
||||
if (!entMan.TryGetComponent(Owner, out MetaDataComponent? ownerMeta))
|
||||
{
|
||||
Logger.ErrorS("container",
|
||||
$"Attempted to insert an entity {entMan.ToPrettyString(toinsert)} into a non-existent entity.");
|
||||
entMan.QueueDeleteEntity(toinsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ownerMeta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Logger.ErrorS("container",
|
||||
$"Attempted to insert an entity {entMan.ToPrettyString(toinsert)} into an entity that is terminating. Entity: {entMan.ToPrettyString(Owner)}.");
|
||||
entMan.QueueDeleteEntity(toinsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
transform ??= transformQuery.GetComponent(toinsert);
|
||||
|
||||
//Verify we can insert into this container
|
||||
if (!force && !containerSys.CanInsert(toinsert, this, containerXform: ownerTransform))
|
||||
return false;
|
||||
|
||||
// Please somebody ecs containers
|
||||
var lookupSys = entMan.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
|
||||
var xformSys = entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
|
||||
|
||||
meta ??= entMan.GetComponent<MetaDataComponent>(toinsert);
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Logger.ErrorS("container",
|
||||
$"Attempted to insert a terminating entity {entMan.ToPrettyString(toinsert)} into a container {ID} in entity: {entMan.ToPrettyString(Owner)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove from any old containers.
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
|
||||
entMan.TryGetComponent(transform.ParentUid, out ContainerManagerComponent? oldManager) &&
|
||||
oldManager.TryGetContainer(toinsert, out var oldContainer) &&
|
||||
!oldContainer.Remove(toinsert, entMan, transform, meta, false, false))
|
||||
{
|
||||
// failed to remove from container --> cannot insert.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update metadata first, so that parent change events can check IsInContainer.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
|
||||
meta.Flags |= MetaDataFlags.InContainer;
|
||||
|
||||
// Remove the entity and any children from broadphases.
|
||||
// This is done before changing can collide to avoid unecceary updates.
|
||||
// TODO maybe combine with RecursivelyUpdatePhysics to avoid fetching components and iterating parents twice?
|
||||
lookupSys.RemoveFromEntityTree(toinsert, transform);
|
||||
DebugTools.Assert(transform.Broadphase == null || !transform.Broadphase.Value.IsValid());
|
||||
|
||||
// Avoid unnecessary broadphase updates while unanchoring, changing physics collision, and re-parenting.
|
||||
var old = transform.Broadphase;
|
||||
transform.Broadphase = BroadphaseData.Invalid;
|
||||
|
||||
// Unanchor the entity (without changing physics body types).
|
||||
xformSys.Unanchor(toinsert, transform, false);
|
||||
|
||||
// Next, update physics. Note that this cannot just be done in the physics system via parent change events,
|
||||
// because the insertion may not result in a parent change. This could alternatively be done via a
|
||||
// got-inserted event, but really that event should run after the entity was actually inserted (so that
|
||||
// parent/map have updated). But we are better of disabling collision before doing map/parent changes.
|
||||
physicsQuery.Resolve(toinsert, ref physics, false);
|
||||
RecursivelyUpdatePhysics(toinsert, transform, physics, physicsSys, physicsQuery, transformQuery);
|
||||
|
||||
// Attach to new parent
|
||||
var oldParent = transform.ParentUid;
|
||||
xformSys.SetCoordinates(toinsert, transform, new EntityCoordinates(Owner, Vector2.Zero), Angle.Zero);
|
||||
transform.Broadphase = old;
|
||||
|
||||
// the transform.AttachParent() could previously result in the flag being unset, so check that this hasn't happened.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
||||
|
||||
// Implementation specific insert logic
|
||||
InternalInsert(toinsert, entMan);
|
||||
|
||||
// Update any relevant joint relays
|
||||
// Can't be done above as the container flag isn't set yet.
|
||||
RecursivelyUpdateJoints(toinsert, transform, jointSys, jointQuery, transformQuery);
|
||||
|
||||
// Raise container events (after re-parenting and internal remove).
|
||||
entMan.EventBus.RaiseLocalEvent(Owner, new EntInsertedIntoContainerMessage(toinsert, oldParent, this), true);
|
||||
entMan.EventBus.RaiseLocalEvent(toinsert, new EntGotInsertedIntoContainerMessage(toinsert, this), true);
|
||||
|
||||
// The sheer number of asserts tells you about how little I trust container and parenting code.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
||||
DebugTools.Assert(!transform.Anchored);
|
||||
DebugTools.Assert(transform.LocalPosition == Vector2.Zero);
|
||||
DebugTools.Assert(transform.LocalRotation == Angle.Zero);
|
||||
DebugTools.Assert(!physicsQuery.TryGetComponent(toinsert, out var phys) || (!phys.Awake && !phys.CanCollide));
|
||||
|
||||
entMan.Dirty(Owner, Manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void RecursivelyUpdatePhysics(
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
PhysicsComponent? physics,
|
||||
SharedPhysicsSystem physicsSys,
|
||||
EntityQuery<PhysicsComponent> physicsQuery,
|
||||
EntityQuery<TransformComponent> transformQuery)
|
||||
{
|
||||
if (physics != null)
|
||||
{
|
||||
// Here we intentionally don't dirty the physics comp. Client-side state handling will apply these same
|
||||
// changes. This also ensures that the server doesn't have to send the physics comp state to every
|
||||
// player for any entity inside of a container during init.
|
||||
physicsSys.SetLinearVelocity(uid, Vector2.Zero, false, body: physics);
|
||||
physicsSys.SetAngularVelocity(uid,0, false, body: physics);
|
||||
physicsSys.SetCanCollide(uid, false, false, body: physics);
|
||||
}
|
||||
|
||||
var enumerator = xform.ChildEnumerator;
|
||||
|
||||
while (enumerator.MoveNext(out var child))
|
||||
{
|
||||
var childXform = transformQuery.GetComponent(child.Value);
|
||||
physicsQuery.TryGetComponent(child.Value, out var childPhysics);
|
||||
RecursivelyUpdatePhysics(child.Value, childXform, childPhysics, physicsSys, physicsQuery, transformQuery);
|
||||
}
|
||||
}
|
||||
|
||||
internal void RecursivelyUpdateJoints(
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
SharedJointSystem jointSys,
|
||||
EntityQuery<JointComponent> jointQuery,
|
||||
EntityQuery<TransformComponent> transformQuery)
|
||||
{
|
||||
if (jointQuery.TryGetComponent(uid, out var jointComp))
|
||||
{
|
||||
// TODO: This is going to be going up while joints going down, although these aren't too common
|
||||
// in SS14 atm.
|
||||
jointSys.RefreshRelay(uid, jointComp);
|
||||
}
|
||||
|
||||
var enumerator = xform.ChildEnumerator;
|
||||
|
||||
while (enumerator.MoveNext(out var child))
|
||||
{
|
||||
var childXform = transformQuery.GetComponent(child.Value);
|
||||
RecursivelyUpdateJoints(child.Value, childXform, jointSys, jointQuery, transformQuery);
|
||||
}
|
||||
return entMan.System<SharedContainerSystem>().Insert((toinsert, transform, meta, physics), this, ownerTransform, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -273,75 +100,11 @@ namespace Robust.Shared.Containers
|
||||
bool reparent = true,
|
||||
bool force = false,
|
||||
EntityCoordinates? destination = null,
|
||||
Angle? localRotation = null)
|
||||
Angle? localRotation = null
|
||||
)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
DebugTools.AssertNotNull(Manager);
|
||||
DebugTools.Assert(entMan.EntityExists(toRemove));
|
||||
DebugTools.AssertOwner(toRemove, xform);
|
||||
DebugTools.AssertOwner(toRemove, meta);
|
||||
|
||||
xform ??= entMan.GetComponent<TransformComponent>(toRemove);
|
||||
meta ??= entMan.GetComponent<MetaDataComponent>(toRemove);
|
||||
|
||||
var sys = entMan.EntitySysManager.GetEntitySystem<SharedContainerSystem>();
|
||||
if (!force && !sys.CanRemove(toRemove, this))
|
||||
return false;
|
||||
|
||||
if (force && !Contains(toRemove))
|
||||
{
|
||||
DebugTools.Assert("Attempted to force remove an entity that was never inside of the container.");
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating || (force && !reparent));
|
||||
DebugTools.Assert(xform.Broadphase == null || !xform.Broadphase.Value.IsValid());
|
||||
DebugTools.Assert(!xform.Anchored);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0x0);
|
||||
DebugTools.Assert(!entMan.TryGetComponent(toRemove, out PhysicsComponent? phys) || (!phys.Awake && !phys.CanCollide));
|
||||
|
||||
// Unset flag (before parent change events are raised).
|
||||
meta.Flags &= ~MetaDataFlags.InContainer;
|
||||
|
||||
// Implementation specific remove logic
|
||||
InternalRemove(toRemove, entMan);
|
||||
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0x0);
|
||||
var oldParent = xform.ParentUid;
|
||||
|
||||
if (destination != null)
|
||||
{
|
||||
// Container ECS when.
|
||||
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetCoordinates(toRemove, xform, destination.Value, localRotation);
|
||||
}
|
||||
else if (reparent)
|
||||
{
|
||||
// Container ECS when.
|
||||
sys.AttachParentToContainerOrGrid((toRemove, xform));
|
||||
if (localRotation != null)
|
||||
entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetLocalRotation(xform, localRotation.Value);
|
||||
}
|
||||
|
||||
// Add to new broadphase
|
||||
if (xform.ParentUid == oldParent // move event should already have handled it
|
||||
&& xform.Broadphase == null) // broadphase explicitly invalid?
|
||||
{
|
||||
entMan.EntitySysManager.GetEntitySystem<EntityLookupSystem>().FindAndAddToEntityTree(toRemove, xform: xform);
|
||||
}
|
||||
|
||||
if (entMan.TryGetComponent<JointComponent>(toRemove, out var jointComp))
|
||||
{
|
||||
entMan.System<SharedJointSystem>().RefreshRelay(toRemove, jointComp);
|
||||
}
|
||||
|
||||
// Raise container events (after re-parenting and internal remove).
|
||||
entMan.EventBus.RaiseLocalEvent(Owner, new EntRemovedFromContainerMessage(toRemove, this), true);
|
||||
entMan.EventBus.RaiseLocalEvent(toRemove, new EntGotRemovedFromContainerMessage(toRemove, this), false);
|
||||
|
||||
DebugTools.Assert(destination == null || xform.Coordinates.Equals(destination.Value));
|
||||
|
||||
entMan.Dirty(Owner, Manager);
|
||||
return true;
|
||||
return entMan.System<SharedContainerSystem>().Remove((toRemove, xform, meta), this, reparent, force, destination, localRotation);
|
||||
}
|
||||
|
||||
[Obsolete("Use container system method")]
|
||||
@@ -359,28 +122,31 @@ namespace Robust.Shared.Containers
|
||||
/// <summary>
|
||||
/// Clears the container and marks it as deleted.
|
||||
/// </summary>
|
||||
public void Shutdown(IEntityManager? entMan = null, INetManager? netMan = null)
|
||||
[Obsolete("use system method")]
|
||||
public void Shutdown(IEntityManager? entMan = null, INetManager? _ = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan, ref netMan);
|
||||
InternalShutdown(entMan, netMan.IsClient);
|
||||
Manager.Containers.Remove(ID);
|
||||
IoCManager.Resolve(ref entMan);
|
||||
entMan.System<SharedContainerSystem>().ShutdownContainer(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected abstract void InternalShutdown(IEntityManager entMan, bool isClient);
|
||||
[Access(typeof(SharedContainerSystem))]
|
||||
protected internal abstract void InternalShutdown(IEntityManager entMan, SharedContainerSystem system, bool isClient);
|
||||
|
||||
/// <summary>
|
||||
/// Implement to store the reference in whatever form you want
|
||||
/// </summary>
|
||||
/// <param name="toInsert"></param>
|
||||
/// <param name="entMan"></param>
|
||||
protected abstract void InternalInsert(EntityUid toInsert, IEntityManager entMan);
|
||||
[Access(typeof(SharedContainerSystem))]
|
||||
protected internal abstract void InternalInsert(EntityUid toInsert, IEntityManager entMan);
|
||||
|
||||
/// <summary>
|
||||
/// Implement to remove the reference you used to store the entity
|
||||
/// </summary>
|
||||
/// <param name="toRemove"></param>
|
||||
/// <param name="entMan"></param>
|
||||
protected abstract void InternalRemove(EntityUid toRemove, IEntityManager entMan);
|
||||
[Access(typeof(SharedContainerSystem))]
|
||||
protected internal abstract void InternalRemove(EntityUid toRemove, IEntityManager entMan);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ namespace Robust.Shared.Containers
|
||||
public override IReadOnlyList<EntityUid> ContainedEntities => _containerList;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan)
|
||||
protected internal override void InternalInsert(EntityUid toInsert, IEntityManager entMan)
|
||||
{
|
||||
DebugTools.Assert(!_containerList.Contains(toInsert));
|
||||
_containerList.Add(toInsert);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InternalRemove(EntityUid toRemove, IEntityManager entMan)
|
||||
protected internal override void InternalRemove(EntityUid toRemove, IEntityManager entMan)
|
||||
{
|
||||
_containerList.Remove(toRemove);
|
||||
}
|
||||
@@ -63,14 +63,14 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InternalShutdown(IEntityManager entMan, bool isClient)
|
||||
protected internal override void InternalShutdown(IEntityManager entMan, SharedContainerSystem system, bool isClient)
|
||||
{
|
||||
foreach (var entity in _containerList.ToArray())
|
||||
{
|
||||
if (!isClient)
|
||||
entMan.DeleteEntity(entity);
|
||||
else if (entMan.EntityExists(entity))
|
||||
Remove(entity, entMan, reparent: false, force: true);
|
||||
system.Remove(entity, this, reparent: false, force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,72 +25,43 @@ namespace Robust.Shared.Containers
|
||||
[DataField("containers")]
|
||||
public Dictionary<string, BaseContainer> Containers = new();
|
||||
|
||||
// Requires a custom serializer + copier to get rid of. Good luck
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
{
|
||||
// TODO custom type serializer
|
||||
foreach (var (id, container) in Containers)
|
||||
{
|
||||
container.Manager = this;
|
||||
container.Owner = Owner;
|
||||
container.ID = id;
|
||||
container.Owner = Owner;
|
||||
container.Manager = this;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public T MakeContainer<T>(EntityUid uid, string id)
|
||||
where T : BaseContainer
|
||||
{
|
||||
if (HasContainer(id))
|
||||
throw new ArgumentException($"Container with specified ID already exists: '{id}'");
|
||||
|
||||
var container = _dynFactory.CreateInstanceUnchecked<T>(typeof(T), inject: false);
|
||||
container.Init(id, uid, this);
|
||||
Containers[id] = container;
|
||||
_entMan.Dirty(uid, this);
|
||||
return container;
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().MakeContainer<T>(uid, id, this);
|
||||
|
||||
[Obsolete]
|
||||
public BaseContainer GetContainer(string id)
|
||||
{
|
||||
return Containers[id];
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().GetContainer(Owner, id, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool HasContainer(string id)
|
||||
{
|
||||
return Containers.ContainsKey(id);
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().HasContainer(Owner, id, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool TryGetContainer(string id, [NotNullWhen(true)] out BaseContainer? container)
|
||||
{
|
||||
var ret = Containers.TryGetValue(id, out var cont);
|
||||
container = cont!;
|
||||
return ret;
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().TryGetContainer(Owner, id, out container, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out BaseContainer? container)
|
||||
{
|
||||
foreach (var contain in Containers.Values)
|
||||
{
|
||||
if (contain.Contains(entity))
|
||||
{
|
||||
container = contain;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
container = default;
|
||||
return false;
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().TryGetContainingContainer(Owner, entity, out container, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool ContainsEntity(EntityUid entity)
|
||||
{
|
||||
foreach (var container in Containers.Values)
|
||||
{
|
||||
if (container.Contains(entity)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().ContainsEntity(Owner, entity, this);
|
||||
|
||||
[Obsolete]
|
||||
public bool Remove(EntityUid toremove,
|
||||
TransformComponent? xform = null,
|
||||
MetaDataComponent? meta = null,
|
||||
@@ -98,20 +69,11 @@ namespace Robust.Shared.Containers
|
||||
bool force = false,
|
||||
EntityCoordinates? destination = null,
|
||||
Angle? localRotation = null)
|
||||
{
|
||||
foreach (var containers in Containers.Values)
|
||||
{
|
||||
if (containers.Contains(toremove))
|
||||
return containers.Remove(toremove, _entMan, xform, meta, reparent, force, destination, localRotation);
|
||||
}
|
||||
|
||||
return true; // If we don't contain the entity, it will always be removed
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().RemoveEntity(Owner, toremove, this, xform, meta, reparent, force, destination, localRotation);
|
||||
|
||||
[Obsolete]
|
||||
public AllContainersEnumerable GetAllContainers()
|
||||
{
|
||||
return new(this);
|
||||
}
|
||||
=> _entMan.System<SharedContainerSystem>().GetAllContainers(Owner, this);
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class ContainerManagerComponentState : ComponentState
|
||||
|
||||
@@ -74,7 +74,7 @@ namespace Robust.Shared.Containers
|
||||
=> ContainedEntity == null || assumeEmpty;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InternalInsert(EntityUid toInsert, IEntityManager entMan)
|
||||
protected internal override void InternalInsert(EntityUid toInsert, IEntityManager entMan)
|
||||
{
|
||||
DebugTools.Assert(ContainedEntity == null);
|
||||
|
||||
@@ -87,14 +87,14 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InternalRemove(EntityUid toRemove, IEntityManager entMan)
|
||||
protected internal override void InternalRemove(EntityUid toRemove, IEntityManager entMan)
|
||||
{
|
||||
DebugTools.Assert(ContainedEntity == toRemove);
|
||||
ContainedEntity = null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void InternalShutdown(IEntityManager entMan, bool isClient)
|
||||
protected internal override void InternalShutdown(IEntityManager entMan, SharedContainerSystem system, bool isClient)
|
||||
{
|
||||
if (ContainedEntity is not { } entity)
|
||||
return;
|
||||
@@ -102,7 +102,7 @@ namespace Robust.Shared.Containers
|
||||
if (!isClient)
|
||||
entMan.DeleteEntity(entity);
|
||||
else if (entMan.EntityExists(entity))
|
||||
Remove(entity, entMan, reparent: false, force: true);
|
||||
system.Remove(entity, this, reparent: false, force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Shared.Containers;
|
||||
|
||||
@@ -28,12 +32,107 @@ public abstract partial class SharedContainerSystem
|
||||
TransformComponent? containerXform = null,
|
||||
bool force = false)
|
||||
{
|
||||
var (uid, transform, meta, physics) = toInsert;
|
||||
|
||||
// Cannot Use Resolve(ref toInsert) as the physics component is optional
|
||||
if (!Resolve(toInsert.Owner, ref toInsert.Comp1, ref toInsert.Comp2))
|
||||
if (!Resolve(uid, ref transform, ref meta))
|
||||
return false;
|
||||
|
||||
// TODO move logic over to the system.
|
||||
return container.Insert(toInsert, EntityManager, toInsert, containerXform, toInsert, toInsert, force);
|
||||
DebugTools.AssertOwner(container.Owner, containerXform);
|
||||
DebugTools.AssertOwner(toInsert, physics);
|
||||
DebugTools.Assert(!container.ExpectedEntities.Contains(GetNetEntity(toInsert)));
|
||||
DebugTools.Assert(container.Manager.Containers.ContainsKey(container.ID));
|
||||
|
||||
// If someone is attempting to insert an entity into a container that is getting deleted, then we will
|
||||
// automatically delete that entity. I.e., the insertion automatically "succeeds" and both entities get deleted.
|
||||
// This is consistent with what happens if you attempt to attach an entity to a terminating parent.
|
||||
|
||||
if (!TryComp(container.Owner, out MetaDataComponent? ownerMeta))
|
||||
{
|
||||
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into a non-existent entity.");
|
||||
QueueDel(toInsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ownerMeta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Log.Error($"Attempted to insert an entity {ToPrettyString(toInsert)} into an entity that is terminating. Entity: {ToPrettyString(container.Owner)}.");
|
||||
QueueDel(toInsert);
|
||||
return false;
|
||||
}
|
||||
|
||||
//Verify we can insert into this container
|
||||
if (!force && !CanInsert(uid, container, containerXform: containerXform))
|
||||
return false;
|
||||
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Log.Error($"Attempted to insert a terminating entity {ToPrettyString(uid)} into a container {container.ID} in entity: {ToPrettyString(container.Owner)}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// remove from any old containers.
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
|
||||
TryComp(transform.ParentUid, out ContainerManagerComponent? oldManager) &&
|
||||
TryGetContainingContainer(transform.ParentUid, toInsert, out var oldContainer, oldManager) &&
|
||||
!Remove((uid, transform, meta), oldContainer, reparent: false, force: false))
|
||||
{
|
||||
// failed to remove from container --> cannot insert.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update metadata first, so that parent change events can check IsInContainer.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
|
||||
meta.Flags |= MetaDataFlags.InContainer;
|
||||
|
||||
// Remove the entity and any children from broadphases.
|
||||
// This is done before changing can collide to avoid unecceary updates.
|
||||
// TODO maybe combine with RecursivelyUpdatePhysics to avoid fetching components and iterating parents twice?
|
||||
_lookup.RemoveFromEntityTree(toInsert, transform);
|
||||
DebugTools.Assert(transform.Broadphase == null || !transform.Broadphase.Value.IsValid());
|
||||
|
||||
// Avoid unnecessary broadphase updates while unanchoring, changing physics collision, and re-parenting.
|
||||
var old = transform.Broadphase;
|
||||
transform.Broadphase = BroadphaseData.Invalid;
|
||||
|
||||
// Unanchor the entity (without changing physics body types).
|
||||
_transform.Unanchor(toInsert, transform, false);
|
||||
|
||||
// Next, update physics. Note that this cannot just be done in the physics system via parent change events,
|
||||
// because the insertion may not result in a parent change. This could alternatively be done via a
|
||||
// got-inserted event, but really that event should run after the entity was actually inserted (so that
|
||||
// parent/map have updated). But we are better of disabling collision before doing map/parent changes.
|
||||
PhysicsQuery.Resolve(toInsert, ref physics, logMissing: false);
|
||||
RecursivelyUpdatePhysics((toInsert, transform, physics));
|
||||
|
||||
// Attach to new parent
|
||||
var oldParent = transform.ParentUid;
|
||||
_transform.SetCoordinates(toInsert, transform, new EntityCoordinates(container.Owner, Vector2.Zero), Angle.Zero);
|
||||
transform.Broadphase = old;
|
||||
|
||||
// the transform.AttachParent() could previously result in the flag being unset, so check that this hasn't happened.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
||||
|
||||
// Implementation specific insert logic
|
||||
container.InternalInsert(toInsert, EntityManager);
|
||||
|
||||
// Update any relevant joint relays
|
||||
// Can't be done above as the container flag isn't set yet.
|
||||
RecursivelyUpdateJoints((toInsert, transform));
|
||||
|
||||
// Raise container events (after re-parenting and internal remove).
|
||||
RaiseLocalEvent(container.Owner, new EntInsertedIntoContainerMessage(toInsert, oldParent, container), true);
|
||||
RaiseLocalEvent(toInsert, new EntGotInsertedIntoContainerMessage(toInsert, container), true);
|
||||
|
||||
// The sheer number of asserts tells you about how little I trust container and parenting code.
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0);
|
||||
DebugTools.Assert(!transform.Anchored);
|
||||
DebugTools.Assert(transform.LocalPosition == Vector2.Zero);
|
||||
DebugTools.Assert(transform.LocalRotation == Angle.Zero);
|
||||
DebugTools.Assert(!PhysicsQuery.TryGetComponent(toInsert, out var phys) || (!phys.Awake && !phys.CanCollide));
|
||||
|
||||
Dirty(container.Owner, container.Manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,4 +173,40 @@ public abstract partial class SharedContainerSystem
|
||||
|
||||
return !gettingInsertedAttemptEvent.Cancelled;
|
||||
}
|
||||
|
||||
private void RecursivelyUpdatePhysics(Entity<TransformComponent, PhysicsComponent?> entity)
|
||||
{
|
||||
if (entity.Comp2 is { } physics)
|
||||
{
|
||||
// Here we intentionally don't dirty the physics comp. Client-side state handling will apply these same
|
||||
// changes. This also ensures that the server doesn't have to send the physics comp state to every
|
||||
// player for any entity inside of a container during init.
|
||||
_physics.SetLinearVelocity(entity, Vector2.Zero, false, body: physics);
|
||||
_physics.SetAngularVelocity(entity, 0, false, body: physics);
|
||||
_physics.SetCanCollide(entity, false, false, body: physics);
|
||||
}
|
||||
|
||||
foreach (var child in entity.Comp1._children)
|
||||
{
|
||||
var childXform = TransformQuery.GetComponent(child);
|
||||
PhysicsQuery.TryGetComponent(child, out var childPhysics);
|
||||
RecursivelyUpdatePhysics((child, childXform, childPhysics));
|
||||
}
|
||||
}
|
||||
|
||||
internal void RecursivelyUpdateJoints(Entity<TransformComponent> entity)
|
||||
{
|
||||
if (JointQuery.TryGetComponent(entity, out var jointComp))
|
||||
{
|
||||
// TODO: This is going to be going up while joints going down, although these aren't too common
|
||||
// in SS14 atm.
|
||||
_joint.RefreshRelay(entity, jointComp);
|
||||
}
|
||||
|
||||
foreach (var child in entity.Comp._children)
|
||||
{
|
||||
var childXform = TransformQuery.GetComponent(child);
|
||||
RecursivelyUpdateJoints((child, childXform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Robust.Shared.Containers;
|
||||
|
||||
@@ -30,12 +36,80 @@ public abstract partial class SharedContainerSystem
|
||||
EntityCoordinates? destination = null,
|
||||
Angle? localRotation = null)
|
||||
{
|
||||
var (uid, xform, meta) = toRemove;
|
||||
|
||||
// Cannot Use Resolve(ref toInsert) as the physics component is optional
|
||||
if (!Resolve(toRemove.Owner, ref toRemove.Comp1, ref toRemove.Comp2))
|
||||
if (!Resolve(uid, ref xform, ref meta))
|
||||
return false;
|
||||
|
||||
// TODO move logic over to the system.
|
||||
return container.Remove(toRemove, EntityManager, toRemove, toRemove, reparent, force, destination, localRotation);
|
||||
DebugTools.AssertNotNull(container.Manager);
|
||||
DebugTools.Assert(Exists(toRemove));
|
||||
|
||||
if (!force && !CanRemove(toRemove, container))
|
||||
return false;
|
||||
|
||||
if (force && !container.Contains(toRemove))
|
||||
{
|
||||
DebugTools.Assert("Attempted to force remove an entity that was never inside of the container.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Terminating entities should not get re-parented. However, this removal will still be forced when
|
||||
// detaching to null-space just before deletion happens.
|
||||
if (meta.EntityLifeStage >= EntityLifeStage.Terminating && (!force || reparent))
|
||||
{
|
||||
Log.Error($"Attempting to remove an entity from a container while it is terminating. Entity: {ToPrettyString(toRemove, meta)}. Container: {ToPrettyString(container.Owner)}. Trace: {Environment.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
|
||||
DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating || (force && !reparent));
|
||||
DebugTools.Assert(xform.Broadphase == null || !xform.Broadphase.Value.IsValid());
|
||||
DebugTools.Assert(!xform.Anchored);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) != 0x0);
|
||||
DebugTools.Assert(!TryComp(toRemove, out PhysicsComponent? phys) || (!phys.Awake && !phys.CanCollide));
|
||||
|
||||
// Unset flag (before parent change events are raised).
|
||||
meta.Flags &= ~MetaDataFlags.InContainer;
|
||||
|
||||
// Implementation specific remove logic
|
||||
container.InternalRemove(toRemove, EntityManager);
|
||||
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0x0);
|
||||
var oldParent = xform.ParentUid;
|
||||
|
||||
if (destination != null)
|
||||
{
|
||||
// Container ECS when.
|
||||
_transform.SetCoordinates(toRemove, xform, destination.Value, localRotation);
|
||||
}
|
||||
else if (reparent)
|
||||
{
|
||||
// Container ECS when.
|
||||
AttachParentToContainerOrGrid((toRemove, xform));
|
||||
if (localRotation != null)
|
||||
_transform.SetLocalRotation(uid, localRotation.Value, xform);
|
||||
}
|
||||
|
||||
// Add to new broadphase
|
||||
if (xform.ParentUid == oldParent // move event should already have handled it
|
||||
&& xform.Broadphase == null) // broadphase explicitly invalid?
|
||||
{
|
||||
_lookup.FindAndAddToEntityTree(toRemove, xform: xform);
|
||||
}
|
||||
|
||||
if (TryComp<JointComponent>(toRemove, out var jointComp))
|
||||
{
|
||||
_joint.RefreshRelay(toRemove, jointComp);
|
||||
}
|
||||
|
||||
// Raise container events (after re-parenting and internal remove).
|
||||
RaiseLocalEvent(container.Owner, new EntRemovedFromContainerMessage(toRemove, container), true);
|
||||
RaiseLocalEvent(toRemove, new EntGotRemovedFromContainerMessage(toRemove, container), false);
|
||||
|
||||
DebugTools.Assert(destination == null || xform.Coordinates.Equals(destination.Value));
|
||||
|
||||
Dirty(container.Owner, container.Manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -42,7 +42,7 @@ public abstract partial class SharedContainerSystem : EntitySystem
|
||||
// entities in containers without having to "re-insert" them.
|
||||
meta.Flags |= MetaDataFlags.InContainer;
|
||||
_lookup.RemoveFromEntityTree(ent, xform);
|
||||
cont.RecursivelyUpdatePhysics(ent, xform, physics, _physics, PhysicsQuery, TransformQuery);
|
||||
RecursivelyUpdatePhysics((ent, xform, physics));
|
||||
|
||||
// assert children have correct properties
|
||||
ValidateChildren(xform, TransformQuery, PhysicsQuery);
|
||||
@@ -54,18 +54,17 @@ public abstract partial class SharedContainerSystem : EntitySystem
|
||||
|
||||
private void ValidateChildren(TransformComponent xform, EntityQuery<TransformComponent> xformQuery, EntityQuery<PhysicsComponent> physicsQuery)
|
||||
{
|
||||
var enumerator = xform.ChildEnumerator;
|
||||
while (enumerator.MoveNext(out var child))
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (!xformQuery.TryGetComponent(child, out var childXform))
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(!xform.Anchored,
|
||||
$"Child of contained entity is anchored, Entity: {ToPrettyString(child.Value)}");
|
||||
$"Child of contained entity is anchored, Entity: {ToPrettyString(child)}");
|
||||
DebugTools.Assert(!physicsQuery.TryGetComponent(child, out var physics) || (!physics.Awake && !physics.CanCollide),
|
||||
$"Child of contained entity is can collide, Entity: {ToPrettyString(child.Value)}");
|
||||
$"Child of contained entity is can collide, Entity: {ToPrettyString(child)}");
|
||||
DebugTools.Assert(xform.Broadphase == null,
|
||||
$"Child of contained entity is has non-null broadphase, Entity: {ToPrettyString(child.Value)}");
|
||||
$"Child of contained entity is has non-null broadphase, Entity: {ToPrettyString(child)}");
|
||||
ValidateChildren(childXform, xformQuery, physicsQuery);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -17,15 +18,18 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
public abstract partial class SharedContainerSystem
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
|
||||
[Dependency] private readonly INetManager _net = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedJointSystem _joint = default!;
|
||||
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
protected EntityQuery<MetaDataComponent> MetaQuery;
|
||||
protected EntityQuery<PhysicsComponent> PhysicsQuery;
|
||||
protected EntityQuery<JointComponent> JointQuery;
|
||||
protected EntityQuery<TransformComponent> TransformQuery;
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -42,6 +46,7 @@ namespace Robust.Shared.Containers
|
||||
_mapQuery = GetEntityQuery<MapComponent>();
|
||||
MetaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
PhysicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
JointQuery = GetEntityQuery<JointComponent>();
|
||||
TransformQuery = GetEntityQuery<TransformComponent>();
|
||||
}
|
||||
|
||||
@@ -69,7 +74,7 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
foreach (var container in component.Containers.Values)
|
||||
{
|
||||
container.Shutdown(EntityManager, _net);
|
||||
ShutdownContainer(container);
|
||||
}
|
||||
|
||||
component.Containers.Clear();
|
||||
@@ -85,7 +90,26 @@ namespace Robust.Shared.Containers
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
containerManager = AddComp<ContainerManagerComponent>(uid); // Happy Vera.
|
||||
|
||||
return containerManager.MakeContainer<T>(uid, id);
|
||||
if (HasContainer(uid, id, containerManager))
|
||||
throw new ArgumentException($"Container with specified ID already exists: '{id}'");
|
||||
|
||||
var container = _dynFactory.CreateInstanceUnchecked<T>(typeof(T), inject: false);
|
||||
InitContainer(container, (uid, containerManager), id);
|
||||
containerManager.Containers[id] = container;
|
||||
Dirty(uid, containerManager);
|
||||
return container;
|
||||
}
|
||||
|
||||
protected void InitContainer(BaseContainer container, Entity<ContainerManagerComponent> containerEnt, string id)
|
||||
{
|
||||
DebugTools.AssertNull(container.ID);
|
||||
((container.Owner, container.Manager), container.ID) = (containerEnt, id);
|
||||
}
|
||||
|
||||
public void ShutdownContainer(BaseContainer container)
|
||||
{
|
||||
container.InternalShutdown(EntityManager, this, _net.IsClient);
|
||||
container.Manager.Containers.Remove(container.ID);
|
||||
}
|
||||
|
||||
public T EnsureContainer<T>(EntityUid uid, string id, out bool alreadyExisted, ContainerManagerComponent? containerManager = null)
|
||||
@@ -119,7 +143,7 @@ namespace Robust.Shared.Containers
|
||||
if (!Resolve(uid, ref containerManager))
|
||||
throw new ArgumentException("Entity does not have a ContainerManagerComponent!", nameof(uid));
|
||||
|
||||
return containerManager.GetContainer(id);
|
||||
return containerManager.Containers[id];
|
||||
}
|
||||
|
||||
public bool HasContainer(EntityUid uid, string id, ContainerManagerComponent? containerManager)
|
||||
@@ -127,24 +151,38 @@ namespace Robust.Shared.Containers
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
return false;
|
||||
|
||||
return containerManager.HasContainer(id);
|
||||
return containerManager.Containers.ContainsKey(id);
|
||||
}
|
||||
|
||||
public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
if (Resolve(uid, ref containerManager, false))
|
||||
return containerManager.TryGetContainer(id, out container);
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
{
|
||||
container = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
container = null;
|
||||
return false;
|
||||
return containerManager.Containers.TryGetValue(id, out container);
|
||||
}
|
||||
|
||||
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false)
|
||||
{
|
||||
if (Resolve(uid, ref containerManager, false) && (skipExistCheck || Exists(containedUid)))
|
||||
return containerManager.TryGetContainer(containedUid, out container);
|
||||
if (!Resolve(uid, ref containerManager, false) || !(skipExistCheck || Exists(containedUid)))
|
||||
{
|
||||
container = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
container = null;
|
||||
foreach (var contain in containerManager.Containers.Values)
|
||||
{
|
||||
if (contain.Contains(containedUid))
|
||||
{
|
||||
container = contain;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
container = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -153,10 +191,16 @@ namespace Robust.Shared.Containers
|
||||
if (!Resolve(uid, ref containerManager, false) || !Exists(containedUid))
|
||||
return false;
|
||||
|
||||
return containerManager.ContainsEntity(containedUid);
|
||||
foreach (var container in containerManager.Containers.Values)
|
||||
{
|
||||
if (container.Contains(containedUid))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void RemoveEntity(
|
||||
public bool RemoveEntity(
|
||||
EntityUid uid,
|
||||
EntityUid toremove,
|
||||
ContainerManagerComponent? containerManager = null,
|
||||
@@ -168,9 +212,15 @@ namespace Robust.Shared.Containers
|
||||
Angle? localRotation = null)
|
||||
{
|
||||
if (!Resolve(uid, ref containerManager) || !Resolve(toremove, ref containedMeta, ref containedXform))
|
||||
return;
|
||||
return false;
|
||||
|
||||
containerManager.Remove(toremove, containedXform, containedMeta, reparent, force, destination, localRotation);
|
||||
foreach (var containers in containerManager.Containers.Values)
|
||||
{
|
||||
if (containers.Contains(toremove))
|
||||
return Remove((toremove, containedXform, containedMeta), containers, reparent, force, destination, localRotation);
|
||||
}
|
||||
|
||||
return true; // If we don't contain the entity, it will always be removed
|
||||
}
|
||||
|
||||
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(EntityUid uid, ContainerManagerComponent? containerManager = null)
|
||||
@@ -178,7 +228,7 @@ namespace Robust.Shared.Containers
|
||||
if (!Resolve(uid, ref containerManager))
|
||||
return new ContainerManagerComponent.AllContainersEnumerable();
|
||||
|
||||
return containerManager.GetAllContainers();
|
||||
return new(containerManager);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -407,7 +457,7 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
if (((metaQuery.GetComponent(child).Flags & MetaDataFlags.InContainer) == MetaDataFlags.InContainer) &&
|
||||
conQuery.TryGetComponent(parent, out var conManager) &&
|
||||
conManager.TryGetContainer(child, out var parentContainer))
|
||||
TryGetContainingContainer(parent, child, out var parentContainer, conManager))
|
||||
{
|
||||
container = parentContainer;
|
||||
}
|
||||
@@ -436,9 +486,9 @@ namespace Robust.Shared.Containers
|
||||
wasInContainer = true;
|
||||
|
||||
if (!force)
|
||||
return container.Remove(entity);
|
||||
return Remove(entity, container);
|
||||
|
||||
container.Remove(entity, EntityManager, force: true);
|
||||
Remove(entity, container, force: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -469,7 +519,7 @@ namespace Robust.Shared.Containers
|
||||
var removed = new List<EntityUid>(container.ContainedEntities);
|
||||
for (var i = removed.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (container.Remove(removed[i], EntityManager, reparent: reparent, force: force, destination: destination))
|
||||
if (Remove(removed[i], container, reparent: reparent, force: force, destination: destination))
|
||||
continue;
|
||||
|
||||
// failed to remove entity.
|
||||
@@ -487,8 +537,10 @@ namespace Robust.Shared.Containers
|
||||
{
|
||||
foreach (var ent in container.ContainedEntities.ToArray())
|
||||
{
|
||||
if (Deleted(ent)) continue;
|
||||
container.Remove(ent, EntityManager, force: true);
|
||||
if (Deleted(ent))
|
||||
continue;
|
||||
|
||||
Remove(ent, container, force: true);
|
||||
Del(ent);
|
||||
}
|
||||
}
|
||||
@@ -506,7 +558,8 @@ namespace Robust.Shared.Containers
|
||||
|
||||
private bool TryInsertIntoContainer(Entity<TransformComponent> transform, BaseContainer container)
|
||||
{
|
||||
if (container.Insert(transform)) return true;
|
||||
if (Insert((transform.Owner, transform.Comp, null, null), container))
|
||||
return true;
|
||||
|
||||
if (Transform(container.Owner).ParentUid.IsValid()
|
||||
&& TryGetContainingContainer(container.Owner, out var newContainer))
|
||||
@@ -540,7 +593,7 @@ namespace Robust.Shared.Containers
|
||||
|
||||
// Eject entities from their parent container if the parent change is done via setting the transform.
|
||||
if (TryComp(message.OldParent, out ContainerManagerComponent? containerManager))
|
||||
containerManager.Remove(message.Entity, message.Transform, meta, reparent: false, force: true);
|
||||
RemoveEntity(message.OldParent.Value, message.Entity, containerManager, message.Transform, meta, reparent: false, force: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user