mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6cd442d209 | ||
|
|
62167dfc94 | ||
|
|
472903a0af | ||
|
|
b5fa1c84fc | ||
|
|
1901059755 | ||
|
|
8cdec92be6 | ||
|
|
0a00e7ec29 | ||
|
|
8c25a83066 | ||
|
|
0fadfc2d9b | ||
|
|
a6e7224672 | ||
|
|
37796f4806 | ||
|
|
a5494d1df2 | ||
|
|
e2525a2103 | ||
|
|
b50f68866f | ||
|
|
03a4d3e0a0 | ||
|
|
af8fb52a4f | ||
|
|
fd60dc2887 | ||
|
|
cd24fd46b6 | ||
|
|
44cc7127fa | ||
|
|
af36d24892 | ||
|
|
caa8ff0f2d | ||
|
|
cd67c67a5c | ||
|
|
57b328e8c2 | ||
|
|
4874b1db68 | ||
|
|
814ad08884 | ||
|
|
8c4deb2067 | ||
|
|
f6a5120e56 | ||
|
|
c1b8bf8e52 | ||
|
|
4b193bad26 | ||
|
|
8db3da4852 | ||
|
|
0c271fc2f8 | ||
|
|
ed406c06b7 | ||
|
|
b31940b489 | ||
|
|
84360c653d | ||
|
|
6764ed56b0 | ||
|
|
2b55d39e51 | ||
|
|
fdc1de2430 | ||
|
|
99c5b0ad08 | ||
|
|
9e2ab2a917 | ||
|
|
36eb857b55 | ||
|
|
92d7f2723a | ||
|
|
91d3f67a94 | ||
|
|
b28b5ed09b | ||
|
|
30eed7957f | ||
|
|
73c1449811 | ||
|
|
958b5dd06d | ||
|
|
4002cbddb9 | ||
|
|
c38a14e78f | ||
|
|
5164d99996 | ||
|
|
c79217ab66 | ||
|
|
9ef7f7cb37 | ||
|
|
02ac314b1a | ||
|
|
8607ba1f16 | ||
|
|
2a9de462d5 | ||
|
|
c59ef5ab2d | ||
|
|
eba1d866fb | ||
|
|
ab2bff8f40 | ||
|
|
536fca4115 | ||
|
|
7a0d02463c | ||
|
|
6df53d60ed | ||
|
|
ff38e9f12a | ||
|
|
00c58c76a8 | ||
|
|
0bf99e173c | ||
|
|
eee771c5f1 | ||
|
|
2946cd866c | ||
|
|
9a2a3d658d | ||
|
|
d933f03a54 | ||
|
|
25bbb21dc8 | ||
|
|
4460454563 | ||
|
|
a2d8fa7a9b | ||
|
|
71f0491f10 | ||
|
|
b4c1618338 | ||
|
|
df0945f3cd | ||
|
|
8d477716b0 | ||
|
|
5c1a5e9826 | ||
|
|
6daa3ad2fc | ||
|
|
033a617102 | ||
|
|
b9b565d53e | ||
|
|
b7ea4d0cca | ||
|
|
919ec01477 | ||
|
|
e484eac29c | ||
|
|
eedadb250f | ||
|
|
3097784cd7 | ||
|
|
0fb41e06c8 | ||
|
|
0a79382a62 | ||
|
|
1f2b38a6d1 | ||
|
|
f760929527 | ||
|
|
f5ade69f6d | ||
|
|
9bfe889c86 | ||
|
|
e3954494e7 | ||
|
|
6697e36e84 | ||
|
|
dae4041e61 | ||
|
|
390f399750 | ||
|
|
28cf7442ce | ||
|
|
d8b03be651 | ||
|
|
16c7c71ca6 | ||
|
|
0245c371ae | ||
|
|
c8cb13f832 | ||
|
|
86ecfaa56b | ||
|
|
43a32e7015 | ||
|
|
83371885fa | ||
|
|
e686e1b4cc |
5
.github/workflows/build-test.yml
vendored
5
.github/workflows/build-test.yml
vendored
@@ -27,7 +27,8 @@ jobs:
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
|
||||
- name: Test Engine
|
||||
- name: Robust.UnitTesting
|
||||
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
|
||||
|
||||
- name: Robust.Analyzers.Tests
|
||||
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0
|
||||
|
||||
|
||||
6
.github/workflows/publish-client.yml
vendored
6
.github/workflows/publish-client.yml
vendored
@@ -33,10 +33,10 @@ jobs:
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
|
||||
- name: Upload files to centcomm
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
We actually set ManagePackageVersionsCentrally manually in another import file.
|
||||
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
|
||||
https://github.com/NuGet/NuGet.Client/pull/5572
|
||||
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
|
||||
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
|
||||
-->
|
||||
<ManagePackageVersionsCentrally />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
@@ -45,7 +55,7 @@
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 45f89ca263...61a56c60bd
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
229
RELEASE-NOTES.md
229
RELEASE-NOTES.md
@@ -1,4 +1,4 @@
|
||||
# Release notes for RobustToolbox.
|
||||
# Release notes for RobustToolbox.
|
||||
|
||||
<!--
|
||||
NOTE: automatically updated sometimes by version.py.
|
||||
@@ -54,6 +54,233 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 218.2.2
|
||||
|
||||
|
||||
## 218.2.1
|
||||
|
||||
|
||||
## 218.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Control layout properties such as `Margin` can now be set via style sheets.
|
||||
* Expose worldposition in SpriteComponent.Render
|
||||
* Network audio entity Play/Pause/Stop states and playback position.
|
||||
* Add `Disabled` functionality to `Slider` control.
|
||||
|
||||
|
||||
## 218.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add IEquatable.Equals to the sandbox.
|
||||
* Enable roslyn extensions tests in CI.
|
||||
* Add a VerticalTabContainer control to match the horizontal one.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix divison remainder issue for Colors, fixing purples.
|
||||
|
||||
### Other
|
||||
|
||||
* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`.
|
||||
|
||||
|
||||
## 218.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `Robust.Shared.Configuration.EnvironmentVariables` is now internal and no longer usable by content.
|
||||
|
||||
### New features
|
||||
|
||||
* Add TryGetRandom to EntityManager to get a random entity with the specified component and TryGetRandom to IPrototypeManager to return a random prototype of the specified type.
|
||||
* Add CopyData to AppearanceSystem.
|
||||
* Update UI themes on prototype reloads.
|
||||
* Allow scaling the line height of a RichTextLabel.
|
||||
* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".".
|
||||
* Added non-generic variant of `GetCVar` to `IConfigurationManager`.
|
||||
* Add type tracking to FieldNotFoundErrorNode for serialization.
|
||||
* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`.
|
||||
* UI theme prototypes are now updated when reloaded.
|
||||
* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Request headers in `IStatusHandlerContext` are now case-insensitive.
|
||||
* SetWorldPosition rotation now more closely aligns with prior behavior.
|
||||
* Fix exception when inspecting elements in some cases.
|
||||
* Fix HTTP errors on watchdog ping not being reported.
|
||||
|
||||
### Other
|
||||
|
||||
* Add an analyzer for redundantly assigning to dependency fields.
|
||||
|
||||
### Internal
|
||||
|
||||
* Remove redundant Exists checks in ContainerSystem.
|
||||
* Improve logging on watchdog pings.
|
||||
|
||||
|
||||
## 217.2.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix LineEdit tests on engine.
|
||||
|
||||
### Internal
|
||||
|
||||
* Make various ValueList enumerators access the span directly for performance.
|
||||
|
||||
|
||||
## 217.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes.
|
||||
* Add double-clicking to LineEdit.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Properly ignore non-hard fixtures for IntersectRayWithPredicate.
|
||||
* Fix nullable TimeSpan addition on some platforms.
|
||||
|
||||
|
||||
## 217.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections.
|
||||
* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state.
|
||||
|
||||
### Internal
|
||||
|
||||
* `Shuffle<T>(Span<T>, System.Random)` has been removed, just use the builtin method.
|
||||
|
||||
|
||||
## 217.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this).
|
||||
|
||||
### New features
|
||||
|
||||
* Implement VV for AudioParams on SoundSpecifiers.
|
||||
* Add AddUi to the shared UI system.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix the first measure of ScrollContainer bars.
|
||||
|
||||
|
||||
## 216.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25.
|
||||
|
||||
### New features
|
||||
|
||||
* You can now specify a component to not be saved to map files with `[UnsavedComponent]`.
|
||||
* Added `ITileDefinitionManager.TryGetDefinition`.
|
||||
* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix buffered audio sources not being disposed.
|
||||
|
||||
|
||||
## 215.3.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert zstd update.
|
||||
|
||||
|
||||
## 215.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `EntityQuery<T>` now has `HasComp` and `TryComp` methods that are shorter than its existing ones.
|
||||
* Added `PlacementInformation.UseEditorContext`.
|
||||
* Added `Vector2Helpers` functions for comparing ranges between vectors.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `Texture.GetPixel()`: fixed off-by-one with Y coordinate.
|
||||
* `Texture.GetPixel()`: fix stack overflow when reading large images.
|
||||
* `Texture.GetPixel()`: use more widely compatible OpenGL calls.
|
||||
|
||||
### Other
|
||||
|
||||
* Disabled `net.mtu_expand` again by default, as it was causing issues.
|
||||
* Updated `SharpZstd` dependency.
|
||||
|
||||
|
||||
## 215.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Implement basic VV for SoundSpecifiers.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix QueueDel during EndCollideEvents from throwing while removing contacts.
|
||||
|
||||
|
||||
## 215.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add a CompletionHelper for audio filepaths that handles server packaging.
|
||||
* Add Random.NextAngle(min, max) method and Pick for `ValueList<T>`.
|
||||
* Added an `ICommonSession` parser for toolshed commands.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
||||
## 215.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Update Lidgren to 0.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped.
|
||||
* Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly.
|
||||
* IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters.
|
||||
* `net.mtu_ipv6` CVar allows specifying a different MTU value for IPv6.
|
||||
* Allows `player:entity` to take a parameter representing the player name.
|
||||
* Add collection parsing to the dev window for UI.
|
||||
* Add a debug assert to Dirty(uid, comp) to catch mismatches being passed in.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Support transform states with unknown parents.
|
||||
* Fix serialization error logging.
|
||||
* Fix naming of ResizableMemoryRegion metrics.
|
||||
* Fix uncaught overflow exception when parsing NetEntities.
|
||||
|
||||
### Other
|
||||
|
||||
* The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates).
|
||||
* `CheckBox`'s interior texture is now vertically centered.
|
||||
* Lidgren.Network has been updated to [`v0.3.0`](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.3.0/RELEASE-NOTES.md).
|
||||
* Lowered default IPv4 MTU to 900 (from 1000).
|
||||
* Automatic MTU expansion (`net.mtu_expand`) is now enabled by default.
|
||||
|
||||
### Internal
|
||||
|
||||
* Cleanup some Dirty component calls internally.
|
||||
|
||||
|
||||
## 214.2.0
|
||||
|
||||
### New features
|
||||
|
||||
@@ -11,6 +11,7 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID.
|
||||
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
|
||||
cmd-parse-failure-grid = {$arg} is not a valid grid.
|
||||
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
cmd-parse-failure-session = There is no session with username: {$username}
|
||||
|
||||
cmd-error-file-not-found = Could not find file: {$file}.
|
||||
cmd-error-dir-not-found = Could not find directory: {$dir}.
|
||||
|
||||
@@ -10,3 +10,18 @@ view-variable-instance-entity-client-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-server-components-search-bar-placeholder = Search
|
||||
view-variable-instance-entity-add-window-server-components = Add Component [S]
|
||||
view-variable-instance-entity-add-window-client-components = Add Component [C]
|
||||
|
||||
|
||||
## SoundSpecifier
|
||||
vv-sound-none = None
|
||||
vv-sound-path = Path
|
||||
vv-sound-collection = Collection
|
||||
|
||||
vv-sound-volume = volume
|
||||
vv-sound-pitch = Pitch
|
||||
vv-sound-max-distance = Max Distance
|
||||
vv-sound-rolloff-factor = Rolloff Factor
|
||||
vv-sound-reference-distance = Reference Distance
|
||||
vv-sound-loop = Loop
|
||||
vv-sound-play-offset = Play Offset (s)
|
||||
vv-sound-variation = Pitch variation
|
||||
|
||||
@@ -20,11 +20,16 @@ public sealed class AccessAnalyzer_Test
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
AdditionalReferences = { typeof(AccessAnalyzer).Assembly },
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.AccessAttribute.cs",
|
||||
"Robust.Shared.Analyzers.AccessPermissions.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
|
||||
58
Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs
Normal file
58
Robust.Analyzers.Tests/DependencyAssignAnalyzerTest.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class DependencyAssignAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.IoC.DependencyAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
public sealed class Foo
|
||||
{
|
||||
[Dependency]
|
||||
private object? Field;
|
||||
|
||||
public Foo()
|
||||
{
|
||||
Field = "A";
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,9): warning RA0025: Tried to assign to [Dependency] field 'Field'. Remove [Dependency] or inject it via field injection instead.
|
||||
VerifyCS.Diagnostic().WithSpan(10, 9, 10, 20).WithArguments("Field"));
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,13 @@
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets"/>
|
||||
<Import Project="..\MSBuild\Robust.Engine.props"/>
|
||||
|
||||
<!-- Engine source files needed to make the tests work -->
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
22
Robust.Analyzers.Tests/TestHelper.cs
Normal file
22
Robust.Analyzers.Tests/TestHelper.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
public static class TestHelper
|
||||
{
|
||||
public static void AddEmbeddedSources(SolutionState state, params string[] embeddedFiles)
|
||||
{
|
||||
AddEmbeddedSources(state, (IEnumerable<string>) embeddedFiles);
|
||||
}
|
||||
|
||||
public static void AddEmbeddedSources(SolutionState state, IEnumerable<string> embeddedFiles)
|
||||
{
|
||||
foreach (var fileName in embeddedFiles)
|
||||
{
|
||||
using var stream = typeof(AccessAnalyzer_Test).Assembly.GetManifestResourceStream(fileName)!;
|
||||
state.Sources.Add((fileName, SourceText.From(stream)));
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Robust.Analyzers/DependencyAssignAnalyzer.cs
Normal file
61
Robust.Analyzers/DependencyAssignAnalyzer.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class DependencyAssignAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new (
|
||||
Diagnostics.IdDependencyFieldAssigned,
|
||||
"Assignment to dependency field",
|
||||
"Tried to assign to [Dependency] field '{0}'. Remove [Dependency] or inject it via field injection instead.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.RegisterOperationAction(CheckAssignment, OperationKind.SimpleAssignment);
|
||||
}
|
||||
|
||||
private static void CheckAssignment(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not ISimpleAssignmentOperation assignment)
|
||||
return;
|
||||
|
||||
if (assignment.Target is not IFieldReferenceOperation fieldRef)
|
||||
return;
|
||||
|
||||
var field = fieldRef.Field;
|
||||
var attributes = field.GetAttributes();
|
||||
if (attributes.Length == 0)
|
||||
return;
|
||||
|
||||
var depAttribute = context.Compilation.GetTypeByMetadataName(DependencyAttributeType);
|
||||
if (!HasAttribute(attributes, depAttribute))
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(Rule, assignment.Syntax.GetLocation(), field.Name));
|
||||
}
|
||||
|
||||
private static bool HasAttribute(ImmutableArray<AttributeData> attributes, ISymbol symbol)
|
||||
{
|
||||
foreach (var attribute in attributes)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, symbol))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,24 +2,29 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for NotNullableFlagAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for FriendAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LinkBase="Implementations" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for PreferGenericVariantAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>disable</Nullable>
|
||||
<!--
|
||||
Rider seems to get really confused with hot reload if we directly compile in the above-linked classes.
|
||||
As such, they have an #if to change their namespace in this project.
|
||||
-->
|
||||
<DefineConstants>$(DefineConstants);ROBUST_ANALYZERS_IMPL</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Robust.Shared.Analyzers;
|
||||
using Robust.Shared.Collections;
|
||||
|
||||
namespace Robust.Benchmarks.Collections;
|
||||
|
||||
[Virtual]
|
||||
public class ValueListEnumerationBenchmarks
|
||||
{
|
||||
[Params(4, 16, 64)]
|
||||
public int N { get; set; }
|
||||
|
||||
private sealed class Data(int i)
|
||||
{
|
||||
public readonly int I = i;
|
||||
}
|
||||
|
||||
private ValueList<Data> _valueList;
|
||||
private Data[] _array = default!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
var list = new List<Data>(N);
|
||||
for (var i = 0; i < N; i++)
|
||||
{
|
||||
list.Add(new(i));
|
||||
}
|
||||
|
||||
_array = list.ToArray();
|
||||
_valueList = new(list.ToArray());
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int ValueList()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _valueList)
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int ValueListSpan()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _valueList.Span)
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int Array()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _array)
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public int Span()
|
||||
{
|
||||
var total = 0;
|
||||
foreach (var ev in _array.AsSpan())
|
||||
{
|
||||
total += ev.I;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
@@ -126,6 +126,33 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
component.Source.SetAuxiliary(null);
|
||||
}
|
||||
|
||||
switch (component.State)
|
||||
{
|
||||
case AudioState.Playing:
|
||||
component.StartPlaying();
|
||||
break;
|
||||
case AudioState.Paused:
|
||||
component.Pause();
|
||||
break;
|
||||
case AudioState.Stopped:
|
||||
component.StopPlaying();
|
||||
component.PlaybackPosition = 0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// If playback position changed then update it.
|
||||
if (!string.IsNullOrEmpty(component.FileName))
|
||||
{
|
||||
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
|
||||
var currentPosition = component.Source.PlaybackPosition;
|
||||
var diff = Math.Abs(position - currentPosition);
|
||||
|
||||
if (diff > 0.1f)
|
||||
{
|
||||
component.PlaybackPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -173,7 +200,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
|
||||
{
|
||||
var component = entity.Comp;
|
||||
|
||||
|
||||
if (TryAudioLimit(component.FileName))
|
||||
{
|
||||
var newSource = _audio.CreateAudioSource(audioResource);
|
||||
@@ -427,13 +454,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return false;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
|
||||
}
|
||||
@@ -460,8 +487,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
|
||||
@@ -493,8 +523,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// </summary>
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="entity">The entity "emitting" the audio.</param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
|
||||
@@ -534,8 +567,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
/// <param name="audioParams"></param>
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (recordReplay && _replayRecording.IsRecording)
|
||||
{
|
||||
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
|
||||
@@ -569,25 +605,25 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, entity, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
@@ -603,31 +639,31 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, uid, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, coordinates, audioParams);
|
||||
}
|
||||
|
||||
@@ -314,6 +314,8 @@ public abstract class BaseAudioSource : IAudioSource
|
||||
set
|
||||
{
|
||||
_checkDisposed();
|
||||
|
||||
value = MathF.Max(value, 0f);
|
||||
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
|
||||
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
|
||||
}
|
||||
|
||||
@@ -294,7 +294,6 @@ namespace Robust.Client.Console.Commands
|
||||
internal sealed class SnapGridGetCell : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
|
||||
public override string Command => "sggcell";
|
||||
|
||||
@@ -320,7 +319,7 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
|
||||
if (_entManager.TryGetComponent<MapGridComponent>(_entManager.GetEntity(gridNet), out var grid))
|
||||
{
|
||||
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
|
||||
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
|
||||
@@ -429,7 +428,6 @@ namespace Robust.Client.Console.Commands
|
||||
internal sealed class GridTileCount : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
|
||||
public override string Command => "gridtc";
|
||||
|
||||
@@ -448,7 +446,7 @@ namespace Robust.Client.Console.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (_map.TryGetGrid(gridUid, out var grid))
|
||||
if (_entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
|
||||
{
|
||||
shell.WriteLine(grid.GetAllTiles().Count().ToString());
|
||||
}
|
||||
@@ -555,7 +553,7 @@ namespace Robust.Client.Console.Commands
|
||||
if (type != typeof(Control))
|
||||
cname = $"Control > {cname}";
|
||||
|
||||
returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null"));
|
||||
returnVal.GetOrNew(cname).Add((member.Name, GetMemberValue(member, control, ", ")));
|
||||
}
|
||||
|
||||
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
|
||||
@@ -570,6 +568,28 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
return returnVal;
|
||||
}
|
||||
|
||||
internal static string PropertyValuesString(Control control, string key)
|
||||
{
|
||||
var member = GetAllMembers(control).Find(m => m.Name == key);
|
||||
return GetMemberValue(member, control, "\n", "\"{0}\"");
|
||||
}
|
||||
|
||||
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
|
||||
wrap = "{0}")
|
||||
{
|
||||
var value = member?.GetValue(control);
|
||||
var o = value switch
|
||||
{
|
||||
ICollection<Control> controls => string.Join(separator,
|
||||
controls.Select(ctrl => $"{ctrl.Name}({ctrl.GetType()})")),
|
||||
ICollection<string> list => string.Join(separator, list),
|
||||
null => null,
|
||||
_ => value.ToString()
|
||||
};
|
||||
// Convert to quote surrounded string or null with no quotes
|
||||
return o is not null ? string.Format(wrap, o) : "null";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SetClipboardCommand : LocalizedCommands
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var type = Type.GetType(args[0]);
|
||||
var type = GetType(args[0]);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
|
||||
shell.WriteLine(sig);
|
||||
}
|
||||
}
|
||||
|
||||
private Type? GetType(string name)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetType(name) is { } type)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -612,6 +612,8 @@ namespace Robust.Client
|
||||
{
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
|
||||
}
|
||||
|
||||
_audio.FlushALDisposeQueues();
|
||||
}
|
||||
|
||||
internal static void SetupLogging(
|
||||
|
||||
@@ -8,11 +8,9 @@ using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.ComponentTrees;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
@@ -1237,9 +1235,9 @@ namespace Robust.Client.GameObjects
|
||||
public IEnumerable<ISpriteLayer> AllLayers => Layers;
|
||||
|
||||
// Lobby SpriteView rendering path
|
||||
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
|
||||
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default)
|
||||
{
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
|
||||
RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection);
|
||||
}
|
||||
|
||||
[DataField("noRot")] private bool _screenLock = false;
|
||||
|
||||
@@ -107,7 +107,7 @@ namespace Robust.Client.GameObjects
|
||||
toDelete.Add(id);
|
||||
}
|
||||
|
||||
foreach (var dead in toDelete)
|
||||
foreach (var dead in toDelete.Span)
|
||||
{
|
||||
component.Containers.Remove(dead);
|
||||
}
|
||||
@@ -142,7 +142,7 @@ namespace Robust.Client.GameObjects
|
||||
toRemove.Add(entity);
|
||||
}
|
||||
|
||||
foreach (var entity in toRemove)
|
||||
foreach (var entity in toRemove.Span)
|
||||
{
|
||||
Remove(
|
||||
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
|
||||
@@ -162,7 +162,7 @@ namespace Robust.Client.GameObjects
|
||||
removedExpected.Add(netEntity);
|
||||
}
|
||||
|
||||
foreach (var entityUid in removedExpected)
|
||||
foreach (var entityUid in removedExpected.Span)
|
||||
{
|
||||
RemoveExpectedEntity(entityUid, out _);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IConsoleHost _conHost = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
private ISawmill _sawmillInputContext = default!;
|
||||
|
||||
@@ -151,7 +152,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var pxform = Transform(pent);
|
||||
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
|
||||
var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID));
|
||||
var coords = EntityCoordinates.FromMap(pent, new MapCoordinates(wPos, pxform.MapID), _transform, EntityManager);
|
||||
|
||||
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);
|
||||
|
||||
|
||||
@@ -700,7 +700,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
using var _ = _timing.StartStateApplicationArea();
|
||||
|
||||
// TODO repays optimize this.
|
||||
// TODO replays optimize this.
|
||||
// This currently just saves game states as they are applied.
|
||||
// However this is inefficient and may have redundant data.
|
||||
// E.g., we may record states: [10 to 15] [11 to 16] *error* [0 to 18] [18 to 19] [18 to 20] ...
|
||||
@@ -1138,7 +1138,7 @@ namespace Robust.Client.GameStates
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
|
||||
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
|
||||
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container))
|
||||
{
|
||||
containerSys.Remove((ent.Value, xform, meta), container, false, true);
|
||||
}
|
||||
@@ -1414,7 +1414,7 @@ namespace Robust.Client.GameStates
|
||||
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
|
||||
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
|
||||
{
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container, null, true);
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
|
||||
}
|
||||
|
||||
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -514,7 +515,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
|
||||
{
|
||||
ApplyFovToBuffer(viewport, eye);
|
||||
var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId);
|
||||
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
|
||||
ApplyFovToBuffer(viewport, eye);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -649,24 +649,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
return $"ClydeTexture: ({TextureId})";
|
||||
}
|
||||
|
||||
public override Color GetPixel(int x, int y)
|
||||
public override unsafe Color GetPixel(int x, int y)
|
||||
{
|
||||
if (!_clyde._loadedTextures.TryGetValue(TextureId, out var loaded))
|
||||
{
|
||||
throw new DataException("Texture not found");
|
||||
}
|
||||
|
||||
Span<byte> rgba = stackalloc byte[4*this.Size.X*this.Size.Y];
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* p = rgba)
|
||||
{
|
||||
var curTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
var bufSize = 4 * loaded.Size.X * loaded.Size.Y;
|
||||
var buffer = ArrayPool<byte>.Shared.Rent(bufSize);
|
||||
|
||||
GL.GetTextureImage(loaded.OpenGLObject.Handle, 0, PF.Rgba, PT.UnsignedByte, 4*this.Size.X*this.Size.Y, (IntPtr) p);
|
||||
}
|
||||
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p);
|
||||
}
|
||||
int pixelPos = (this.Size.X*(this.Size.Y-y) + x)*4;
|
||||
return new Color(rgba[pixelPos+0], rgba[pixelPos+1], rgba[pixelPos+2], rgba[pixelPos+3]);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);
|
||||
|
||||
var pixelPos = (loaded.Size.X * (loaded.Size.Y - y - 1) + x) * 4;
|
||||
var color = new Color(buffer[pixelPos+0], buffer[pixelPos+1], buffer[pixelPos+2], buffer[pixelPos+3]);
|
||||
ArrayPool<byte>.Shared.Return(buffer);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
|
||||
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
|
||||
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared())
|
||||
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager, pManager.EntityManager.System<SharedTransformSystem>())).LengthSquared())
|
||||
.ToList();
|
||||
|
||||
if (snapToEntities.Count == 0)
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
@@ -24,7 +25,7 @@ namespace Robust.Client.Placement.Modes
|
||||
SnapSize = 1f;
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
Grid = pManager.MapManager.GetGrid(gridId);
|
||||
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
SnapSize = Grid.TileSize; //Find snap size for the grid.
|
||||
}
|
||||
else
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Robust.Client.Placement.Modes
|
||||
SnapSize = 1f;
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
Grid = pManager.MapManager.GetGrid(gridId);
|
||||
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
SnapSize = Grid.TileSize; //Find snap size for the grid.
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
var gridId = MouseCoords.GetGridUid(pManager.EntityManager);
|
||||
|
||||
if (!pManager.MapManager.TryGetGrid(gridId, out var mapGrid))
|
||||
if (!pManager.EntityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
|
||||
return;
|
||||
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var mapGrid = pManager.MapManager.GetGrid(gridId);
|
||||
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
@@ -22,7 +23,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var mapGrid = pManager.MapManager.GetGrid(gridId);
|
||||
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
CurrentTile = mapGrid.GetTileRef(MouseCoords);
|
||||
tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
@@ -20,7 +21,7 @@ namespace Robust.Client.Placement.Modes
|
||||
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var mapGrid = pManager.MapManager.GetGrid(gridId);
|
||||
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
tileSize = mapGrid.TileSize; //convert from ushort to float
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Robust.Shared.Map.Components;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
{
|
||||
@@ -79,6 +80,10 @@ namespace Robust.Client.Placement
|
||||
private set
|
||||
{
|
||||
_isActive = value;
|
||||
|
||||
if (CurrentPermission?.UseEditorContext is false)
|
||||
return;
|
||||
|
||||
SwitchEditorContext(value);
|
||||
}
|
||||
}
|
||||
@@ -332,7 +337,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
private void HandleTileChanged(ref TileChangedEvent args)
|
||||
{
|
||||
var coords = MapManager.GetGrid(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
|
||||
var coords = EntityManager.GetComponent<MapGridComponent>(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
|
||||
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);
|
||||
}
|
||||
|
||||
@@ -753,7 +758,7 @@ namespace Robust.Client.Placement
|
||||
// If we have actually placed something on a valid grid...
|
||||
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
|
||||
{
|
||||
var grid = MapManager.GetGrid(gridId);
|
||||
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);
|
||||
|
||||
// no point changing the tile to the same thing.
|
||||
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Utility;
|
||||
@@ -115,11 +116,12 @@ namespace Robust.Client.Placement
|
||||
|
||||
var dirAng = pManager.Direction.ToAngle();
|
||||
var spriteSys = pManager.EntityManager.System<SpriteSystem>();
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
foreach (var coordinate in locationcollection)
|
||||
{
|
||||
if (!coordinate.IsValid(pManager.EntityManager))
|
||||
return; // Just some paranoia just in case
|
||||
var worldPos = coordinate.ToMapPos(pManager.EntityManager);
|
||||
var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys);
|
||||
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
|
||||
|
||||
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
|
||||
@@ -136,11 +138,12 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
|
||||
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
|
||||
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
|
||||
float iterations;
|
||||
Vector2 distance;
|
||||
if (Math.Abs(x) > Math.Abs(y))
|
||||
@@ -167,11 +170,12 @@ namespace Robust.Client.Placement
|
||||
{
|
||||
var mouseScreen = pManager.InputManager.MouseScreenPosition;
|
||||
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (mousePos.MapId == MapId.Nullspace)
|
||||
yield break;
|
||||
|
||||
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
|
||||
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
|
||||
|
||||
var xSign = Math.Sign(placementdiff.X);
|
||||
var ySign = Math.Sign(placementdiff.Y);
|
||||
@@ -193,9 +197,9 @@ namespace Robust.Client.Placement
|
||||
public TileRef GetTileRef(EntityCoordinates coordinates)
|
||||
{
|
||||
var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager);
|
||||
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.MapManager.GetGrid(gridUid).GetTileRef(MouseCoords)
|
||||
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent<MapGridComponent>(gridUid).GetTileRef(MouseCoords)
|
||||
: new TileRef(gridUidOpt ?? EntityUid.Invalid,
|
||||
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty);
|
||||
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager, pManager.EntityManager.System<SharedTransformSystem>()), Tile.Empty);
|
||||
}
|
||||
|
||||
public TextureResource GetSprite(string key)
|
||||
@@ -223,7 +227,8 @@ namespace Robust.Client.Placement
|
||||
}
|
||||
|
||||
var range = pManager.CurrentPermission!.Range;
|
||||
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, coordinates, range))
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
@@ -231,7 +236,8 @@ namespace Robust.Client.Placement
|
||||
public bool IsColliding(EntityCoordinates coordinates)
|
||||
{
|
||||
var bounds = pManager.ColliderAABB;
|
||||
var mapCoords = coordinates.ToMap(pManager.EntityManager);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys);
|
||||
var (x, y) = mapCoords.Position;
|
||||
|
||||
var collisionBox = Box2.FromDimensions(
|
||||
@@ -261,7 +267,8 @@ namespace Robust.Client.Placement
|
||||
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
|
||||
}
|
||||
|
||||
return EntityCoordinates.FromMap(pManager.EntityManager, gridUid, mapCoords);
|
||||
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
|
||||
return EntityCoordinates.FromMap(gridUid, mapCoords, transformSys, pManager.EntityManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,7 @@ public sealed partial class ReplayLoadManager
|
||||
return parsed.FirstOrDefault()?.Root as MappingDataNode;
|
||||
}
|
||||
|
||||
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan Duration, TimeSpan StartTime, bool ClientSide)
|
||||
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan? Duration, TimeSpan StartTime, bool ClientSide)
|
||||
LoadMetadata(IReplayFileReader fileReader)
|
||||
{
|
||||
_sawmill.Info($"Reading replay metadata");
|
||||
@@ -137,23 +137,16 @@ public sealed partial class ReplayLoadManager
|
||||
if (data == null)
|
||||
throw new Exception("Failed to load yaml metadata");
|
||||
|
||||
TimeSpan duration;
|
||||
var finalData = LoadYamlFinalMetadata(fileReader);
|
||||
TimeSpan? duration = finalData == null
|
||||
? null
|
||||
: TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
|
||||
if (finalData == null)
|
||||
{
|
||||
var msg = "Failed to load final yaml metadata";
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new Exception(msg);
|
||||
_sawmill.Warning("Failed to load final yaml metadata. Partial/incomplete replay?");
|
||||
|
||||
_sawmill.Error(msg);
|
||||
duration = TimeSpan.FromDays(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
|
||||
}
|
||||
|
||||
var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
|
||||
var typeHashString = ((ValueDataNode) data[MetaKeyTypeHash]).Value;
|
||||
var typeHash = Convert.FromHexString(typeHashString);
|
||||
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
|
||||
var startTick = ((ValueDataNode) data[MetaKeyStartTick]).Value;
|
||||
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
|
||||
@@ -161,7 +154,12 @@ public sealed partial class ReplayLoadManager
|
||||
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
|
||||
|
||||
if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
|
||||
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");
|
||||
{
|
||||
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
|
||||
throw new Exception($"RobustSerializer hash mismatch. do not match. Client hash: {_serializer.GetSerializableTypesHashString()}, replay hash: {typeHashString}.");
|
||||
|
||||
_sawmill.Warning($"RobustSerializer hash mismatch. Replay may fail to load!");
|
||||
}
|
||||
|
||||
using var stringFile = fileReader.Open(FileStrings);
|
||||
var stringData = new byte[stringFile.Length];
|
||||
|
||||
@@ -90,6 +90,7 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
|
||||
var maxIndex = Math.Max(1, replay.States.Count - 1);
|
||||
var state = replay.States[index];
|
||||
var replayTime = TimeSpan.FromSeconds(TickSlider.Value);
|
||||
var end = replay.Duration == null ? "N/A" : replay.Duration.Value.ToString(TimeFormat);
|
||||
|
||||
IndexLabel.Text = Loc.GetString("replay-time-box-index-label",
|
||||
("current", index), ("total", maxIndex), ("percentage", percentage));
|
||||
@@ -98,10 +99,10 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
|
||||
("current", state.ToSequence), ("total", replay.States[^1].ToSequence), ("percentage", percentage));
|
||||
|
||||
TimeLabel.Text = Loc.GetString("replay-time-box-replay-time-label",
|
||||
("current", replayTime.ToString(TimeFormat)), ("end", replay.Duration.ToString(TimeFormat)), ("percentage", percentage));
|
||||
("current", replayTime.ToString(TimeFormat)), ("end", end), ("percentage", percentage));
|
||||
|
||||
var serverTime = (replayTime + replay.StartTime).ToString(TimeFormat);
|
||||
var duration = (replay.Duration + replay.StartTime).ToString(TimeFormat);
|
||||
string duration = replay.Duration == null ? "N/A" : (replay.Duration + replay.StartTime).Value.ToString(TimeFormat);
|
||||
ServerTimeLabel.Text = Loc.GetString("replay-time-box-server-time-label",
|
||||
("current", serverTime), ("end", duration), ("percentage", percentage));
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Robust.Natives" />
|
||||
<PackageReference Include="System.Numerics.Vectors" />
|
||||
<PackageReference Include="TerraFX.Interop.Windows" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(RobustToolsBuild)' == 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" PrivateAssets="compile" />
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" />
|
||||
<PackageReference Include="TerraFX.Interop.Xlib" />
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Client.UserInterface
|
||||
toRemove.Add(key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
foreach (var key in toRemove.Span)
|
||||
{
|
||||
_playingAnimations.Remove(key);
|
||||
AnimationCompleted?.Invoke(key);
|
||||
|
||||
132
Robust.Client/UserInterface/Control.Layout.Styling.cs
Normal file
132
Robust.Client/UserInterface/Control.Layout.Styling.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
public partial class Control
|
||||
{
|
||||
private LayoutStyleProperties _layoutStyleOverride;
|
||||
private LayoutStyleProperties _layoutStyleSheet;
|
||||
|
||||
private void UpdateLayoutStyleProperties()
|
||||
{
|
||||
var propertiesSet = LayoutStyleProperties.None;
|
||||
|
||||
// Assumed most controls will have little or no style properties,
|
||||
// so iterating once is less expensive overall then checking 10+ properties.
|
||||
// C# switch statements are compiled efficiently anyways.
|
||||
foreach (var (key, value) in _styleProperties)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case nameof(SizeFlagsStretchRatio):
|
||||
UpdateField(ref _sizeFlagsStretchRatio, value, LayoutStyleProperties.StretchRatio);
|
||||
break;
|
||||
case nameof(MinWidth):
|
||||
UpdateField(ref _minWidth, value, LayoutStyleProperties.MinWidth);
|
||||
break;
|
||||
case nameof(MinHeight):
|
||||
UpdateField(ref _minHeight, value, LayoutStyleProperties.MinHeight);
|
||||
break;
|
||||
case nameof(SetWidth):
|
||||
UpdateField(ref _setWidth, value, LayoutStyleProperties.SetWidth);
|
||||
break;
|
||||
case nameof(SetHeight):
|
||||
UpdateField(ref _setHeight, value, LayoutStyleProperties.SetHeight);
|
||||
break;
|
||||
case nameof(MaxWidth):
|
||||
UpdateField(ref _maxWidth, value, LayoutStyleProperties.MaxWidth);
|
||||
break;
|
||||
case nameof(MaxHeight):
|
||||
UpdateField(ref _maxHeight, value, LayoutStyleProperties.MaxHeight);
|
||||
break;
|
||||
case nameof(HorizontalExpand):
|
||||
UpdateField(ref _horizontalExpand, value, LayoutStyleProperties.HorizontalExpand);
|
||||
break;
|
||||
case nameof(VerticalExpand):
|
||||
UpdateField(ref _verticalExpand, value, LayoutStyleProperties.VerticalExpand);
|
||||
break;
|
||||
case nameof(HorizontalAlignment):
|
||||
UpdateField(ref _horizontalAlignment, value, LayoutStyleProperties.HorizontalAlignment);
|
||||
break;
|
||||
case nameof(VerticalAlignment):
|
||||
UpdateField(ref _verticalAlignment, value, LayoutStyleProperties.VerticalAlignment);
|
||||
break;
|
||||
case nameof(Margin):
|
||||
UpdateField(ref _margin, value, LayoutStyleProperties.Margin);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset cleared properties back to defaults.
|
||||
var toClear = _layoutStyleSheet & ~propertiesSet;
|
||||
if (toClear != 0)
|
||||
{
|
||||
ClearField(ref _sizeFlagsStretchRatio, DefaultStretchRatio, LayoutStyleProperties.StretchRatio);
|
||||
ClearField(ref _minWidth, 0, LayoutStyleProperties.MinWidth);
|
||||
ClearField(ref _minHeight, 0, LayoutStyleProperties.MinHeight);
|
||||
ClearField(ref _setWidth, DefaultSetSize, LayoutStyleProperties.SetWidth);
|
||||
ClearField(ref _setHeight, DefaultSetSize, LayoutStyleProperties.SetHeight);
|
||||
ClearField(ref _maxWidth, DefaultMaxSize, LayoutStyleProperties.MaxWidth);
|
||||
ClearField(ref _maxHeight, DefaultMaxSize, LayoutStyleProperties.MaxHeight);
|
||||
ClearField(ref _horizontalExpand, false, LayoutStyleProperties.HorizontalExpand);
|
||||
ClearField(ref _verticalExpand, false, LayoutStyleProperties.VerticalExpand);
|
||||
ClearField(ref _horizontalAlignment, DefaultHAlignment, LayoutStyleProperties.HorizontalAlignment);
|
||||
ClearField(ref _verticalAlignment, DefaultVAlignment, LayoutStyleProperties.VerticalAlignment);
|
||||
ClearField(ref _margin, default, LayoutStyleProperties.Margin);
|
||||
}
|
||||
|
||||
_layoutStyleSheet = propertiesSet;
|
||||
|
||||
return;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void UpdateField<T>(ref T field, object value, LayoutStyleProperties flag)
|
||||
{
|
||||
if ((_layoutStyleOverride & flag) != 0)
|
||||
return;
|
||||
|
||||
// TODO: Probably need better error handling...
|
||||
if (value is not T valueCast)
|
||||
return;
|
||||
|
||||
field = valueCast;
|
||||
propertiesSet |= flag;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void ClearField<T>(ref T field, T defaultValue, LayoutStyleProperties flag)
|
||||
{
|
||||
if ((toClear & flag) == 0)
|
||||
return;
|
||||
|
||||
field = defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetLayoutStyleProp(LayoutStyleProperties flag)
|
||||
{
|
||||
_layoutStyleOverride |= flag;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum LayoutStyleProperties : short
|
||||
{
|
||||
// @formatter:off
|
||||
None = 0,
|
||||
Margin = 1 << 0,
|
||||
MinWidth = 1 << 1,
|
||||
MinHeight = 1 << 2,
|
||||
SetWidth = 1 << 3,
|
||||
SetHeight = 1 << 4,
|
||||
MaxWidth = 1 << 5,
|
||||
MaxHeight = 1 << 6,
|
||||
StretchRatio = 1 << 7,
|
||||
HorizontalExpand = 1 << 8,
|
||||
VerticalExpand = 1 << 9,
|
||||
HorizontalAlignment = 1 << 10,
|
||||
VerticalAlignment = 1 << 11,
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
@@ -12,24 +12,30 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
public partial class Control
|
||||
{
|
||||
private const float DefaultStretchRatio = 1;
|
||||
private const float DefaultSetSize = float.NaN;
|
||||
private const float DefaultMaxSize = float.PositiveInfinity;
|
||||
private const HAlignment DefaultHAlignment = HAlignment.Stretch;
|
||||
private const VAlignment DefaultVAlignment = VAlignment.Stretch;
|
||||
|
||||
private Vector2 _size;
|
||||
|
||||
[ViewVariables] internal Vector2? PreviousMeasure;
|
||||
[ViewVariables] internal UIBox2? PreviousArrange;
|
||||
|
||||
private float _sizeFlagsStretchRatio = 1;
|
||||
private float _sizeFlagsStretchRatio = DefaultStretchRatio;
|
||||
|
||||
private float _minWidth;
|
||||
private float _minHeight;
|
||||
private float _setWidth = float.NaN;
|
||||
private float _setHeight = float.NaN;
|
||||
private float _maxWidth = float.PositiveInfinity;
|
||||
private float _maxHeight = float.PositiveInfinity;
|
||||
private float _setWidth = DefaultSetSize;
|
||||
private float _setHeight = DefaultSetSize;
|
||||
private float _maxWidth = DefaultMaxSize;
|
||||
private float _maxHeight = DefaultMaxSize;
|
||||
|
||||
private bool _horizontalExpand;
|
||||
private bool _verticalExpand;
|
||||
private HAlignment _horizontalAlignment = HAlignment.Stretch;
|
||||
private VAlignment _verticalAlignment = VAlignment.Stretch;
|
||||
private HAlignment _horizontalAlignment = DefaultHAlignment;
|
||||
private VAlignment _verticalAlignment = DefaultVAlignment;
|
||||
private Thickness _margin;
|
||||
private bool _measuring;
|
||||
private bool _arranging;
|
||||
@@ -53,6 +59,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_margin = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.Margin);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -242,6 +249,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_horizontalAlignment = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.HorizontalAlignment);
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
@@ -258,6 +266,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_verticalAlignment = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.VerticalAlignment);
|
||||
InvalidateArrange();
|
||||
}
|
||||
}
|
||||
@@ -276,6 +285,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_horizontalExpand = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.HorizontalExpand);
|
||||
Parent?.InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -294,6 +304,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_verticalExpand = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.VerticalExpand);
|
||||
Parent?.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
@@ -318,6 +329,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
_sizeFlagsStretchRatio = value;
|
||||
|
||||
SetLayoutStyleProp(LayoutStyleProperties.StretchRatio);
|
||||
Parent?.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
@@ -394,6 +406,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_minWidth = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.MinWidth);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -408,6 +421,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_minHeight = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.MinHeight);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -422,6 +436,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_setWidth = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.SetWidth);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -436,6 +451,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_setHeight = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.SetHeight);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -450,6 +466,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_maxWidth = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.MaxWidth);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
@@ -464,6 +481,7 @@ namespace Robust.Client.UserInterface
|
||||
set
|
||||
{
|
||||
_maxHeight = value;
|
||||
SetLayoutStyleProp(LayoutStyleProperties.MaxHeight);
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
protected virtual void StylePropertiesChanged()
|
||||
{
|
||||
UpdateLayoutStyleProperties();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
|
||||
@@ -641,7 +641,11 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
foreach (var child in Children.ToArray())
|
||||
{
|
||||
RemoveChild(child);
|
||||
// This checks fails in some obscure cases like using the element inspector in the dev window.
|
||||
// Why? Well I could probably spend 15 minutes in a debugger to find out,
|
||||
// but I'd probably still end up with this fix.
|
||||
if (child.Parent == this)
|
||||
RemoveChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
TextureRect = new TextureRect
|
||||
{
|
||||
StyleClasses = { StyleClassCheckBox },
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
};
|
||||
hBox.AddChild(TextureRect);
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ using System.Numerics;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -20,6 +22,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public class LineEdit : Control
|
||||
{
|
||||
[Dependency] private readonly IClyde _clyde = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
private const float MouseScrollDelay = 0.001f;
|
||||
|
||||
@@ -46,6 +50,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private bool _mouseSelectingText;
|
||||
private float _lastMousePosition;
|
||||
|
||||
private TimeSpan? _lastClickTime;
|
||||
private Vector2? _lastClickPosition;
|
||||
|
||||
private bool IsPlaceHolderVisible => string.IsNullOrEmpty(_text) && _placeHolder != null;
|
||||
|
||||
public event Action<LineEditEventArgs>? OnTextChanged;
|
||||
@@ -685,8 +692,26 @@ namespace Robust.Client.UserInterface.Controls
|
||||
args.Handle();
|
||||
}
|
||||
}
|
||||
// Double-clicking. Clicks delay should be <= 250ms and the distance < 10 pixels.
|
||||
else if (args.Function == EngineKeyFunctions.UIClick && _lastClickPosition != null && _lastClickTime != null
|
||||
&& _timing.RealTime - _lastClickTime <= TimeSpan.FromMilliseconds(_cfgManager.GetCVar(CVars.DoubleClickDelay))
|
||||
&& (_lastClickPosition.Value - args.PointerLocation.Position).IsShorterThan(_cfgManager.GetCVar(CVars.DoubleClickRange)))
|
||||
{
|
||||
_lastClickTime = _timing.RealTime;
|
||||
_lastClickPosition = args.PointerLocation.Position;
|
||||
|
||||
_lastMousePosition = args.RelativePosition.X;
|
||||
|
||||
_selectionStart = TextEditShared.PrevWordPosition(_text, GetIndexAtPos(args.RelativePosition.X));
|
||||
_cursorPosition = TextEditShared.EndWordPosition(_text, GetIndexAtPos(args.RelativePosition.X));
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastClickTime = _timing.RealTime;
|
||||
_lastClickPosition = args.PointerLocation.Position;
|
||||
|
||||
_mouseSelectingText = true;
|
||||
_lastMousePosition = args.RelativePosition.X;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
@@ -16,6 +17,26 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private FormattedMessage? _message;
|
||||
private RichTextEntry _entry;
|
||||
private float _lineHeightScale = 1;
|
||||
private bool _lineHeightOverride;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float LineHeightScale
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_lineHeightOverride && TryGetStyleProperty(nameof(LineHeightScale), out float value))
|
||||
return value;
|
||||
|
||||
return _lineHeightScale;
|
||||
}
|
||||
set
|
||||
{
|
||||
_lineHeightScale = value;
|
||||
_lineHeightOverride = true;
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
|
||||
public RichTextLabel()
|
||||
{
|
||||
@@ -47,7 +68,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
var font = _getFont();
|
||||
_entry.Update(font, availableSize.X * UIScale, UIScale);
|
||||
_entry.Update(font, availableSize.X * UIScale, UIScale, LineHeightScale);
|
||||
|
||||
return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale);
|
||||
}
|
||||
@@ -61,7 +82,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale);
|
||||
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
|
||||
@@ -123,10 +123,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (!ReturnMeasure)
|
||||
return Vector2.Zero;
|
||||
|
||||
if (_vScrollEnabled)
|
||||
if (_vScrollEnabled && size.Y >= availableSize.Y)
|
||||
size.X += _vScrollBar.DesiredSize.X;
|
||||
|
||||
if (_hScrollEnabled)
|
||||
if (_hScrollEnabled && size.X >= availableSize.X)
|
||||
size.Y += _hScrollBar.DesiredSize.Y;
|
||||
|
||||
return size;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using static Robust.Client.UserInterface.Controls.LayoutContainer;
|
||||
|
||||
@@ -32,6 +33,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public bool Grabbed => _grabbed;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the slider can be adjusted.
|
||||
/// </summary>
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public StyleBox? ForegroundStyleBoxOverride
|
||||
{
|
||||
get => _foregroundStyleBoxOverride;
|
||||
@@ -132,7 +138,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.KeyBindDown(args);
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIClick)
|
||||
if (args.Function != EngineKeyFunctions.UIClick || Disabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -146,7 +152,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
base.KeyBindUp(args);
|
||||
|
||||
if (args.Function != EngineKeyFunctions.UIClick) return;
|
||||
if (args.Function != EngineKeyFunctions.UIClick || !_grabbed) return;
|
||||
|
||||
_grabbed = false;
|
||||
OnReleased?.Invoke(this);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!-- Organised with tabs in a vertical list to the left and the contents to the right -->
|
||||
<BoxContainer Name="Container" xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:graphics="clr-namespace:Robust.Client.Graphics"
|
||||
Orientation="Horizontal"
|
||||
MouseFilter="Pass">
|
||||
<ScrollContainer VerticalExpand="True"
|
||||
HScrollEnabled="False"
|
||||
ReturnMeasure="True"
|
||||
Margin="5 5 0 5">
|
||||
<PanelContainer Margin="5" VerticalExpand="False"
|
||||
VerticalAlignment="Top">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Name="TabContainer" Margin="5" Orientation="Vertical"/>
|
||||
</PanelContainer>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer VerticalExpand="True"
|
||||
HScrollEnabled="False"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalExpand="True"
|
||||
ReturnMeasure="True"
|
||||
Margin="0 5 5 5">
|
||||
<PanelContainer Margin="5" HorizontalExpand="True" HorizontalAlignment="Stretch">
|
||||
<PanelContainer.PanelOverride>
|
||||
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
|
||||
</PanelContainer.PanelOverride>
|
||||
<BoxContainer Margin="5"
|
||||
Orientation="Vertical"
|
||||
Name="ContentsContainer"/>
|
||||
</PanelContainer>
|
||||
</ScrollContainer>
|
||||
</BoxContainer>
|
||||
@@ -0,0 +1,97 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class VerticalTabContainer : BoxContainer
|
||||
{
|
||||
private readonly Dictionary<Control, BaseButton> _tabs = new();
|
||||
|
||||
// Just used to order controls in case one gets removed.
|
||||
private readonly List<Control> _controls = new();
|
||||
|
||||
private readonly ButtonGroup _tabGroup = new(false);
|
||||
|
||||
private Control? _currentControl;
|
||||
|
||||
public VerticalTabContainer()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public int AddTab(Control control, string title)
|
||||
{
|
||||
var button = new Button()
|
||||
{
|
||||
Text = title,
|
||||
Group = _tabGroup,
|
||||
};
|
||||
|
||||
TabContainer.AddChild(button);
|
||||
ContentsContainer.AddChild(control);
|
||||
var index = ChildCount - 1;
|
||||
button.OnPressed += args =>
|
||||
{
|
||||
SelectTab(control);
|
||||
};
|
||||
|
||||
_controls.Add(control);
|
||||
_tabs.Add(control, button);
|
||||
|
||||
// Existing tabs
|
||||
if (ContentsContainer.ChildCount > 1)
|
||||
{
|
||||
control.Visible = false;
|
||||
}
|
||||
// First tab
|
||||
else
|
||||
{
|
||||
SelectTab(control);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
protected override void ChildRemoved(Control child)
|
||||
{
|
||||
if (_tabs.Remove(child, out var button))
|
||||
{
|
||||
button.Dispose();
|
||||
}
|
||||
|
||||
// Set the current tab to a different control
|
||||
if (_currentControl == child)
|
||||
{
|
||||
var previous = _controls.IndexOf(child) - 1;
|
||||
|
||||
if (previous > -1)
|
||||
{
|
||||
var setControl = _controls[previous];
|
||||
SelectTab(setControl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
_controls.Remove(child);
|
||||
base.ChildRemoved(child);
|
||||
}
|
||||
|
||||
private void SelectTab(Control control)
|
||||
{
|
||||
if (_currentControl != null)
|
||||
{
|
||||
_currentControl.Visible = false;
|
||||
}
|
||||
|
||||
var button = _tabs[control];
|
||||
button.Pressed = true;
|
||||
control.Visible = true;
|
||||
_currentControl = control;
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
contents.TextMemory = FormatHelpers.FormatIntoMem(_textBuffer,
|
||||
$@"UP: {sentBytes / ONE_KIBIBYTE:N} KiB/s, {sentPackets} pckt/s, {LastSentBytes / ONE_KIBIBYTE:N} KiB, {LastSentPackets} pckt
|
||||
DOWN: {receivedBytes / ONE_KIBIBYTE:N} KiB/s, {receivedPackets} pckt/s, {LastReceivedBytes / ONE_KIBIBYTE:N} KiB, {LastReceivedPackets} pckt
|
||||
PING: {NetManager.ServerChannel?.Ping ?? -1} ms");
|
||||
PING: {NetManager.ServerChannel?.Ping ?? -1} ms, MTU: {NetManager.ServerChannel?.CurrentMtu} B");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,14 @@
|
||||
<BoxContainer Name="ControlTreeRoot" Orientation="Vertical" MouseFilter="Stop" />
|
||||
</PanelContainer>
|
||||
</ScrollContainer>
|
||||
<ScrollContainer HScrollEnabled="False">
|
||||
<ScrollContainer>
|
||||
<BoxContainer Name="ControlProperties" Orientation="Vertical" MouseFilter="Stop" />
|
||||
</ScrollContainer>
|
||||
</SplitContainer>
|
||||
</BoxContainer>
|
||||
</PanelContainer>
|
||||
<!-- TODO Remove and replace with a popup container on WindowRoot -->
|
||||
<PopupContainer Name="PopupContainer" Access="Public">
|
||||
<DevWindowTabUIPopup Name="UIPopup" />
|
||||
</PopupContainer>
|
||||
</Control>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Console.Commands;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -208,25 +209,40 @@ public sealed partial class DevWindowTabUI : Control
|
||||
});
|
||||
foreach (var (prop, value) in values)
|
||||
{
|
||||
ControlProperties.AddChild(new BoxContainer
|
||||
var button = new ContainerButton
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 3,
|
||||
Margin = new Thickness(3, 1),
|
||||
Children =
|
||||
{
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
SeparationOverride = 3,
|
||||
Margin = new Thickness(3, 1),
|
||||
Children =
|
||||
{
|
||||
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
|
||||
new Label { Text = ":" }, // this is for the non colored ":", intentional
|
||||
new BoxContainer
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
|
||||
new Label { Text = ":" }, // this is for the non colored ":", intentional
|
||||
}
|
||||
},
|
||||
new Label { Text = $"{value}" },
|
||||
}
|
||||
},
|
||||
new Label { Text = $"{value}" },
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
button.OnPressed += _ =>
|
||||
{
|
||||
// TODO replace with parenting to popup container on WindowRoot
|
||||
UIPopup.Text = GuiDumpCommand.PropertyValuesString(SelectedControl, prop);
|
||||
var box = UIBox2.FromDimensions(UserInterfaceManager.MousePositionScaled.Position
|
||||
- GlobalPosition, Vector2.One);
|
||||
UIPopup.Open(box);
|
||||
};
|
||||
ControlProperties.AddChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<DevWindowTabUIPopup xmlns="https://spacestation14.io"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="Robust.Client.UserInterface.DevWindowTabUIPopup"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<PanelContainer>
|
||||
<PanelContainer.PanelOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#40404C" />
|
||||
</PanelContainer.PanelOverride>
|
||||
</PanelContainer>
|
||||
<Label Name="TextLabel" />
|
||||
</DevWindowTabUIPopup>
|
||||
@@ -0,0 +1,20 @@
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
[GenerateTypedNameReferences]
|
||||
internal sealed partial class DevWindowTabUIPopup : Popup
|
||||
{
|
||||
public string? Text
|
||||
{
|
||||
get => TextLabel.Text;
|
||||
set => TextLabel.Text = value;
|
||||
}
|
||||
|
||||
public DevWindowTabUIPopup()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,8 @@ namespace Robust.Client.UserInterface
|
||||
/// <param name="defaultFont">The font being used for display.</param>
|
||||
/// <param name="maxSizeX">The maximum horizontal size of the container of this entry.</param>
|
||||
/// <param name="uiScale"></param>
|
||||
public void Update(Font defaultFont, float maxSizeX, float uiScale)
|
||||
/// <param name="lineHeightScale"></param>
|
||||
public void Update(Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
|
||||
{
|
||||
// This method is gonna suck due to complexity.
|
||||
// Bear with me here.
|
||||
@@ -159,7 +160,7 @@ namespace Robust.Client.UserInterface
|
||||
if (!context.Font.TryPeek(out var font))
|
||||
font = defaultFont;
|
||||
|
||||
src.Height += font.GetLineHeight(uiScale);
|
||||
src.Height += GetLineHeight(font, uiScale, lineHeightScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -170,7 +171,8 @@ namespace Robust.Client.UserInterface
|
||||
UIBox2 drawBox,
|
||||
float verticalOffset,
|
||||
MarkupDrawingContext context,
|
||||
float uiScale)
|
||||
float uiScale,
|
||||
float lineHeightScale = 1)
|
||||
{
|
||||
context.Clear();
|
||||
context.Color.Push(_defaultColor);
|
||||
@@ -197,7 +199,7 @@ namespace Robust.Client.UserInterface
|
||||
if (lineBreakIndex < LineBreaks.Count &&
|
||||
LineBreaks[lineBreakIndex] == globalBreakCounter)
|
||||
{
|
||||
baseLine = new Vector2(drawBox.Left, baseLine.Y + font.GetLineHeight(uiScale) + controlYAdvance);
|
||||
baseLine = new Vector2(drawBox.Left, baseLine.Y + GetLineHeight(font, uiScale, lineHeightScale) + controlYAdvance);
|
||||
controlYAdvance = 0;
|
||||
lineBreakIndex += 1;
|
||||
}
|
||||
@@ -216,7 +218,7 @@ namespace Robust.Client.UserInterface
|
||||
control.Position = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale);
|
||||
control.Measure(new Vector2(Width, Height));
|
||||
var advanceX = control.DesiredPixelSize.X;
|
||||
controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - font.GetLineHeight(uiScale)) * invertedScale);
|
||||
controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - GetLineHeight(font, uiScale, lineHeightScale)) * invertedScale);
|
||||
baseLine += new Vector2(advanceX, 0);
|
||||
}
|
||||
}
|
||||
@@ -242,5 +244,11 @@ namespace Robust.Client.UserInterface
|
||||
tag.PopDrawContext(node, context);
|
||||
return tag.TextAfter(node);
|
||||
}
|
||||
|
||||
private static int GetLineHeight(Font font, float uiScale, float lineHeightScale)
|
||||
{
|
||||
var height = font.GetLineHeight(uiScale);
|
||||
return (int)(height * lineHeightScale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Robust.Client.UserInterface.Themes;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
|
||||
@@ -18,11 +19,29 @@ internal partial class UserInterfaceManager
|
||||
{
|
||||
DefaultTheme = _protoManager.Index<UITheme>(UITheme.DefaultName);
|
||||
CurrentTheme = DefaultTheme;
|
||||
ReloadThemes();
|
||||
_configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true);
|
||||
_protoManager.PrototypesReloaded += OnPrototypesReloaded;
|
||||
}
|
||||
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.WasModified<UITheme>())
|
||||
{
|
||||
_sawmillUI.Debug("Reloading UI themes due to prototype reload");
|
||||
ReloadThemes();
|
||||
}
|
||||
}
|
||||
|
||||
private void ReloadThemes()
|
||||
{
|
||||
_themes.Clear();
|
||||
foreach (var proto in _protoManager.EnumeratePrototypes<UITheme>())
|
||||
{
|
||||
_themes.Add(proto.ID, proto);
|
||||
}
|
||||
_configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true);
|
||||
|
||||
SetThemeOrPrevious(CurrentTheme.ID);
|
||||
}
|
||||
|
||||
//Try to set the current theme, if the theme is not found do nothing
|
||||
|
||||
@@ -8,6 +8,8 @@ using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.ViewVariables.Editors;
|
||||
using Robust.Client.ViewVariables.Instances;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
@@ -29,6 +31,8 @@ namespace Robust.Client.ViewVariables
|
||||
[Dependency] private readonly IClientNetManager _netManager = default!;
|
||||
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resManager = default!;
|
||||
|
||||
private uint _nextReqId = 1;
|
||||
private readonly Vector2i _defaultWindowSize = (640, 420);
|
||||
@@ -126,8 +130,12 @@ namespace Robust.Client.ViewVariables
|
||||
return new VVPropEditorString();
|
||||
}
|
||||
|
||||
if (type == typeof(EntProtoId) ||
|
||||
type == typeof(EntProtoId?))
|
||||
if (type == typeof(EntProtoId?))
|
||||
{
|
||||
return new VVPropEditorNullableEntProtoId();
|
||||
}
|
||||
|
||||
if (type == typeof(EntProtoId))
|
||||
{
|
||||
return new VVPropEditorEntProtoId();
|
||||
}
|
||||
@@ -222,6 +230,12 @@ namespace Robust.Client.ViewVariables
|
||||
return new VVPropEditorTimeSpan();
|
||||
}
|
||||
|
||||
if (typeof(SoundSpecifier).IsAssignableFrom(type))
|
||||
{
|
||||
var control = new VVPropEditorSoundSpecifier(_protoManager, _resManager);
|
||||
return control;
|
||||
}
|
||||
|
||||
if (type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken) ||
|
||||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors;
|
||||
|
||||
internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor
|
||||
{
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var lineEdit = new LineEdit
|
||||
{
|
||||
Text = value is EntProtoId protoId ? protoId.Id : "",
|
||||
Editable = !ReadOnly,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
if (!ReadOnly)
|
||||
{
|
||||
lineEdit.OnTextEntered += e =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(e.Text))
|
||||
{
|
||||
ValueChanged(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
ValueChanged((EntProtoId) e.Text);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return lineEdit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors;
|
||||
|
||||
public sealed class VVPropEditorSoundSpecifier : VVPropEditor
|
||||
{
|
||||
private readonly IPrototypeManager _protoManager;
|
||||
private readonly IResourceManager _resManager;
|
||||
|
||||
// Need to cache to some level just to make sure each edit doesn't reset the specifier to the default.
|
||||
|
||||
private SoundSpecifier? _specifier;
|
||||
|
||||
public VVPropEditorSoundSpecifier(IPrototypeManager protoManager, IResourceManager resManager)
|
||||
{
|
||||
_protoManager = protoManager;
|
||||
_resManager = resManager;
|
||||
}
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var typeButton = new OptionButton()
|
||||
{
|
||||
Disabled = ReadOnly,
|
||||
};
|
||||
|
||||
typeButton.AddItem(Loc.GetString("vv-sound-none"));
|
||||
typeButton.AddItem(Loc.GetString("vv-sound-collection"), 1);
|
||||
typeButton.AddItem(Loc.GetString("vv-sound-path"), 2);
|
||||
|
||||
var editBox = new LineEdit()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly,
|
||||
};
|
||||
|
||||
var pathControls = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
typeButton,
|
||||
editBox
|
||||
},
|
||||
SetSize = new Vector2(384f, 32f)
|
||||
};
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case SoundCollectionSpecifier collection:
|
||||
typeButton.SelectId(1);
|
||||
editBox.Text = collection.Collection ?? string.Empty;
|
||||
_specifier = collection;
|
||||
break;
|
||||
case SoundPathSpecifier path:
|
||||
typeButton.SelectId(2);
|
||||
editBox.Text = path.Path.ToString();
|
||||
_specifier = path;
|
||||
break;
|
||||
default:
|
||||
_specifier = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
typeButton.OnItemSelected += args =>
|
||||
{
|
||||
typeButton.SelectId(args.Id);
|
||||
editBox.Text = string.Empty;
|
||||
|
||||
editBox.Editable = !ReadOnly && typeButton.SelectedId > 0;
|
||||
|
||||
if (typeButton.SelectedId == 0)
|
||||
{
|
||||
// Dummy value
|
||||
ValueChanged(new SoundPathSpecifier(""));
|
||||
}
|
||||
};
|
||||
|
||||
editBox.OnTextEntered += args =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(args.Text))
|
||||
return;
|
||||
|
||||
switch (typeButton.SelectedId)
|
||||
{
|
||||
case 1:
|
||||
if (!_protoManager.HasIndex<SoundCollectionPrototype>(args.Text))
|
||||
return;
|
||||
|
||||
_specifier = new SoundCollectionSpecifier(args.Text)
|
||||
{
|
||||
Params = _specifier?.Params ?? AudioParams.Default,
|
||||
};
|
||||
ValueChanged(_specifier);
|
||||
break;
|
||||
case 2:
|
||||
var path = new ResPath(args.Text);
|
||||
|
||||
if (!_resManager.ContentFileExists(path))
|
||||
return;
|
||||
|
||||
_specifier = new SoundPathSpecifier(args.Text)
|
||||
{
|
||||
Params = _specifier?.Params ?? AudioParams.Default,
|
||||
};
|
||||
|
||||
ValueChanged(_specifier);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Audio params
|
||||
|
||||
/* Volume */
|
||||
|
||||
var volumeEdit = new LineEdit()
|
||||
{
|
||||
Text = _specifier?.Params.Volume.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly && _specifier != null,
|
||||
};
|
||||
|
||||
volumeEdit.OnTextEntered += args =>
|
||||
{
|
||||
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithVolume(floatValue);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
var volumeContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-volume"),
|
||||
},
|
||||
volumeEdit,
|
||||
}
|
||||
};
|
||||
|
||||
/* Pitch */
|
||||
|
||||
var pitchEdit = new LineEdit()
|
||||
{
|
||||
Text = _specifier?.Params.Pitch.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly && _specifier != null,
|
||||
};
|
||||
|
||||
pitchEdit.OnTextEntered += args =>
|
||||
{
|
||||
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithPitchScale(floatValue);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
var pitchContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-pitch"),
|
||||
},
|
||||
pitchEdit,
|
||||
}
|
||||
};
|
||||
|
||||
/* MaxDistance */
|
||||
|
||||
var maxDistanceEdit = new LineEdit()
|
||||
{
|
||||
Text = _specifier?.Params.MaxDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly && _specifier != null,
|
||||
};
|
||||
|
||||
maxDistanceEdit.OnTextEntered += args =>
|
||||
{
|
||||
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithMaxDistance(floatValue);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
var maxDistanceContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-max-distance"),
|
||||
},
|
||||
maxDistanceEdit,
|
||||
}
|
||||
};
|
||||
|
||||
/* RolloffFactor */
|
||||
|
||||
var rolloffFactorEdit = new LineEdit()
|
||||
{
|
||||
Text = _specifier?.Params.RolloffFactor.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly && _specifier != null,
|
||||
};
|
||||
|
||||
rolloffFactorEdit.OnTextEntered += args =>
|
||||
{
|
||||
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithRolloffFactor(floatValue);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
var rolloffFactorContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-rolloff-factor"),
|
||||
},
|
||||
rolloffFactorEdit,
|
||||
}
|
||||
};
|
||||
|
||||
/* ReferenceDistance */
|
||||
|
||||
var referenceDistanceEdit = new LineEdit()
|
||||
{
|
||||
Text = _specifier?.Params.ReferenceDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly && _specifier != null,
|
||||
};
|
||||
|
||||
referenceDistanceEdit.OnTextEntered += args =>
|
||||
{
|
||||
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithReferenceDistance(floatValue);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
var referenceDistanceContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-reference-distance"),
|
||||
},
|
||||
referenceDistanceEdit,
|
||||
}
|
||||
};
|
||||
|
||||
/* Loop */
|
||||
|
||||
var loopButton = new Button()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-loop"),
|
||||
Pressed = _specifier?.Params.Loop ?? false,
|
||||
ToggleMode = true,
|
||||
Disabled = ReadOnly || _specifier == null,
|
||||
};
|
||||
|
||||
loopButton.OnPressed += args =>
|
||||
{
|
||||
if (_specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithLoop(args.Button.Pressed);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
/* PlayOffsetSeconds */
|
||||
|
||||
var playOffsetEdit = new LineEdit()
|
||||
{
|
||||
Text = _specifier?.Params.PlayOffsetSeconds.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly && _specifier != null,
|
||||
};
|
||||
|
||||
playOffsetEdit.OnTextEntered += args =>
|
||||
{
|
||||
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithPlayOffset(floatValue);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
var playOffsetContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-play-offset"),
|
||||
},
|
||||
playOffsetEdit,
|
||||
}
|
||||
};
|
||||
|
||||
/* Variation */
|
||||
|
||||
var variationEdit = new LineEdit()
|
||||
{
|
||||
Text = _specifier?.Params.Variation.ToString() ?? string.Empty,
|
||||
HorizontalExpand = true,
|
||||
Editable = !ReadOnly && _specifier != null,
|
||||
};
|
||||
|
||||
variationEdit.OnTextEntered += args =>
|
||||
{
|
||||
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
|
||||
return;
|
||||
|
||||
_specifier.Params = _specifier.Params.WithVariation(floatValue);
|
||||
ValueChanged(_specifier);
|
||||
};
|
||||
|
||||
var variationContainer = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("vv-sound-variation"),
|
||||
},
|
||||
variationEdit,
|
||||
}
|
||||
};
|
||||
|
||||
var audioParamsControls = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
volumeContainer,
|
||||
pitchContainer,
|
||||
maxDistanceContainer,
|
||||
rolloffFactorContainer,
|
||||
referenceDistanceContainer,
|
||||
loopButton,
|
||||
playOffsetContainer,
|
||||
variationContainer,
|
||||
}
|
||||
};
|
||||
|
||||
var controls = new BoxContainer()
|
||||
{
|
||||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||||
Children =
|
||||
{
|
||||
pathControls,
|
||||
audioParamsControls,
|
||||
}
|
||||
};
|
||||
|
||||
return controls;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ public static class Diagnostics
|
||||
public const string IdComponentPauseNoFields = "RA0022";
|
||||
public const string IdComponentPauseNoParentAttribute = "RA0023";
|
||||
public const string IdComponentPauseWrongTypeAttribute = "RA0024";
|
||||
public const string IdDependencyFieldAssigned = "RA0025";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -68,7 +68,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
var entity = Spawn("Audio", MapCoordinates.Nullspace);
|
||||
var audio = SetupAudio(entity, filename, audioParams);
|
||||
@@ -78,8 +78,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)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(uid))
|
||||
{
|
||||
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
|
||||
@@ -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)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(uid))
|
||||
{
|
||||
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
|
||||
@@ -109,8 +115,11 @@ 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)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
|
||||
@@ -128,9 +137,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
|
||||
AudioParams? audioParams = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return null;
|
||||
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
|
||||
@@ -176,12 +188,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return audio;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayGlobal(filename, actor.PlayerSession, audioParams);
|
||||
@@ -189,12 +201,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayEntity(filename, actor.PlayerSession, uid, audioParams);
|
||||
@@ -202,12 +214,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return null;
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams);
|
||||
}
|
||||
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
|
||||
{
|
||||
if (TryComp(recipient, out ActorComponent? actor))
|
||||
return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams);
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Robust.Server
|
||||
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
|
||||
[Dependency] private readonly HubManager _hubManager = default!;
|
||||
[Dependency] private readonly IScriptHost _scriptHost = default!;
|
||||
[Dependency] private readonly IMetricsManager _metricsManager = default!;
|
||||
[Dependency] private readonly IMetricsManagerInternal _metricsManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
|
||||
[Dependency] private readonly ILocalizationManagerInternal _loc = default!;
|
||||
@@ -749,6 +749,8 @@ namespace Robust.Server
|
||||
_hubManager.Heartbeat();
|
||||
|
||||
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
|
||||
|
||||
_metricsManager.FrameUpdate();
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
|
||||
@@ -66,7 +66,7 @@ public sealed class SpinCommand : LocalizedCommands
|
||||
}
|
||||
|
||||
var physicsSystem = _entities.System<SharedPhysicsSystem>();
|
||||
physicsSystem.SetAngularDamping(physics, drag);
|
||||
physicsSystem.SetAngularDamping(target.Value, physics, drag);
|
||||
physicsSystem.SetAngularVelocity(target.Value, speed, body: physics);
|
||||
}
|
||||
}
|
||||
|
||||
89
Robust.Server/DataMetrics/MetricsManager.Factory.cs
Normal file
89
Robust.Server/DataMetrics/MetricsManager.Factory.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.DataMetrics;
|
||||
|
||||
internal sealed partial class MetricsManager : IMeterFactory
|
||||
{
|
||||
private readonly Dictionary<string, List<CachedMeter>> _meterCache = new();
|
||||
private readonly object _meterCacheLock = new();
|
||||
|
||||
Meter IMeterFactory.Create(MeterOptions options)
|
||||
{
|
||||
if (options.Scope != null && options.Scope != this)
|
||||
throw new InvalidOperationException("Cannot specify a custom scope when creating a meter");
|
||||
|
||||
lock (_meterCacheLock)
|
||||
{
|
||||
if (LockedFindCachedMeter(options) is { } cached)
|
||||
return cached.Meter;
|
||||
|
||||
var meter = new Meter(options.Name, options.Version, options.Tags, this);
|
||||
var meterList = _meterCache.GetOrNew(options.Name);
|
||||
meterList.Add(new CachedMeter(options.Version, TagsToDict(options.Tags), meter));
|
||||
return meter;
|
||||
}
|
||||
}
|
||||
|
||||
private CachedMeter? LockedFindCachedMeter(MeterOptions options)
|
||||
{
|
||||
if (!_meterCache.TryGetValue(options.Name, out var metersList))
|
||||
return null;
|
||||
|
||||
var tagsDict = TagsToDict(options.Tags);
|
||||
|
||||
foreach (var cachedMeter in metersList)
|
||||
{
|
||||
if (cachedMeter.Version == options.Version && TagsMatch(tagsDict, cachedMeter.Tags))
|
||||
return cachedMeter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TagsMatch(Dictionary<string, object?> a, Dictionary<string, object?> b)
|
||||
{
|
||||
if (a.Count != b.Count)
|
||||
return false;
|
||||
|
||||
foreach (var (key, valueA) in a)
|
||||
{
|
||||
if (!b.TryGetValue(key, out var valueB))
|
||||
return false;
|
||||
|
||||
if (!Equals(valueA, valueB))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Dictionary<string, object?> TagsToDict(IEnumerable<KeyValuePair<string, object?>>? tags)
|
||||
{
|
||||
return tags?.ToDictionary() ?? [];
|
||||
}
|
||||
|
||||
private void DisposeMeters()
|
||||
{
|
||||
lock (_meterCacheLock)
|
||||
{
|
||||
foreach (var meters in _meterCache.Values)
|
||||
{
|
||||
foreach (var meter in meters)
|
||||
{
|
||||
meter.Meter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CachedMeter(string? version, Dictionary<string, object?> tags, Meter meter)
|
||||
{
|
||||
public readonly string? Version = version;
|
||||
public readonly Dictionary<string, object?> Tags = tags;
|
||||
public readonly Meter Meter = meter;
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,20 @@ internal sealed partial class MetricsManager
|
||||
private sealed class ManagedHttpListenerMetricsServer : MetricHandler
|
||||
{
|
||||
private readonly ISawmill _sawmill;
|
||||
private readonly Func<CancellationToken, Task>? _beforeCollect;
|
||||
private readonly HttpListener _listener;
|
||||
private readonly CollectorRegistry _registry;
|
||||
|
||||
public ManagedHttpListenerMetricsServer(ISawmill sawmill, string host, int port, string url = "metrics/",
|
||||
CollectorRegistry? registry = null)
|
||||
public ManagedHttpListenerMetricsServer(
|
||||
ISawmill sawmill,
|
||||
string host,
|
||||
int port,
|
||||
string url = "metrics/",
|
||||
CollectorRegistry? registry = null,
|
||||
Func<CancellationToken, Task>? beforeCollect = null)
|
||||
{
|
||||
_sawmill = sawmill;
|
||||
_beforeCollect = beforeCollect;
|
||||
_listener = new HttpListener();
|
||||
_listener.Prefixes.Add($"http://{host}:{port}/{url}");
|
||||
_registry = registry ?? Metrics.DefaultRegistry;
|
||||
@@ -57,6 +64,12 @@ internal sealed partial class MetricsManager
|
||||
{
|
||||
MetricsEvents.Log.ScrapeStart();
|
||||
|
||||
// prometheus-net does have a "before collect" callback of its own.
|
||||
// But it doesn't get ran before stuff like their System.Diagnostics.Metrics integration,
|
||||
// So I'm just gonna make my own here.
|
||||
if (_beforeCollect != null)
|
||||
await _beforeCollect(cancel);
|
||||
|
||||
var stream = resp.OutputStream;
|
||||
// prometheus-net is a terrible library and have to do all this insanity,
|
||||
// just to handle the ScrapeFailedException correctly.
|
||||
|
||||
62
Robust.Server/DataMetrics/MetricsManager.UpdateMetrics.cs
Normal file
62
Robust.Server/DataMetrics/MetricsManager.UpdateMetrics.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Server.DataMetrics;
|
||||
|
||||
internal sealed partial class MetricsManager
|
||||
{
|
||||
//
|
||||
// Handles the implementation of the "UpdateMetrics" callback.
|
||||
//
|
||||
|
||||
public event Action? UpdateMetrics;
|
||||
|
||||
private TimeSpan _fixedUpdateInterval;
|
||||
private TimeSpan _nextFixedUpdate;
|
||||
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private void InitializeUpdateMetrics()
|
||||
{
|
||||
_cfg.OnValueChanged(
|
||||
CVars.MetricsUpdateInterval,
|
||||
seconds =>
|
||||
{
|
||||
_fixedUpdateInterval = TimeSpan.FromSeconds(seconds);
|
||||
_nextFixedUpdate = _gameTiming.RealTime + _fixedUpdateInterval;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
public void FrameUpdate()
|
||||
{
|
||||
if (_fixedUpdateInterval == TimeSpan.Zero)
|
||||
return;
|
||||
|
||||
var time = _gameTiming.RealTime;
|
||||
|
||||
if (_nextFixedUpdate > time)
|
||||
return;
|
||||
|
||||
_nextFixedUpdate = time + _fixedUpdateInterval;
|
||||
|
||||
_sawmill.Verbose("Running fixed metrics update");
|
||||
UpdateMetrics?.Invoke();
|
||||
}
|
||||
|
||||
private async Task BeforeCollectCallback(CancellationToken cancel)
|
||||
{
|
||||
if (UpdateMetrics == null)
|
||||
return;
|
||||
|
||||
await _taskManager.TaskOnMainThread(() =>
|
||||
{
|
||||
UpdateMetrics?.Invoke();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,54 @@
|
||||
using System;
|
||||
using System.Diagnostics.Metrics;
|
||||
using System.Diagnostics.Tracing;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Prometheus;
|
||||
using Prometheus.DotNetRuntime;
|
||||
using Prometheus.DotNetRuntime.Metrics.Producers;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using EventSource = System.Diagnostics.Tracing.EventSource;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Robust.Server.DataMetrics;
|
||||
|
||||
internal sealed partial class MetricsManager : IMetricsManager, IDisposable
|
||||
/// <summary>
|
||||
/// Manages OpenTelemetry metrics exposure.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If enabled via <see cref="CVars.MetricsEnabled"/>, metrics about the game server are exposed via a HTTP server
|
||||
/// in an OpenTelemetry-compatible format (Prometheus).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Metrics can be added through the types in <c>System.Diagnostics.Metrics</c> or <c>Prometheus</c>.
|
||||
/// IoC contains an implementation of <see cref="IMeterFactory"/> that can be used to instantiate meters.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IMetricsManager
|
||||
{
|
||||
/// <summary>
|
||||
/// An event that gets raised on the main thread when complex metrics should be updated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This event is raised on the main thread before a Prometheus collection happens,
|
||||
/// and also with a fixed interval if <see cref="CVars.MetricsUpdateInterval"/> is set.
|
||||
/// You can use it to update complex metrics that can't "just" be stuffed into a counter.
|
||||
/// </remarks>
|
||||
event Action UpdateMetrics;
|
||||
}
|
||||
|
||||
internal sealed partial class MetricsManager : IMetricsManagerInternal, IDisposable
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly ITaskManager _taskManager = default!;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
@@ -55,6 +83,8 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
|
||||
{
|
||||
_cfg.OnValueChanged(cVar, _ => Reload());
|
||||
}
|
||||
|
||||
InitializeUpdateMetrics();
|
||||
}
|
||||
|
||||
private async Task Stop()
|
||||
@@ -73,6 +103,8 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
|
||||
|
||||
async void IDisposable.Dispose()
|
||||
{
|
||||
DisposeMeters();
|
||||
|
||||
await Stop();
|
||||
|
||||
_initialized = false;
|
||||
@@ -100,7 +132,12 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
|
||||
|
||||
_sawmill.Info("Prometheus metrics enabled, host: {1} port: {0}", port, host);
|
||||
var sawmill = Logger.GetSawmill("metrics.server");
|
||||
_metricServer = new ManagedHttpListenerMetricsServer(sawmill, host, port);
|
||||
_metricServer = new ManagedHttpListenerMetricsServer(
|
||||
sawmill,
|
||||
host,
|
||||
port,
|
||||
registry: Metrics.DefaultRegistry,
|
||||
beforeCollect: BeforeCollectCallback);
|
||||
_metricServer.Start();
|
||||
|
||||
if (_cfg.GetCVar(CVars.MetricsRuntime))
|
||||
@@ -190,7 +227,8 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IMetricsManager
|
||||
internal interface IMetricsManagerInternal : IMetricsManager
|
||||
{
|
||||
void Initialize();
|
||||
void FrameUpdate();
|
||||
}
|
||||
|
||||
@@ -280,6 +280,9 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
// Load the prototype data onto entities, e.g. transform parents, etc.
|
||||
LoadEntities(data);
|
||||
|
||||
// Assign MapSaveTileMapComponent to all read grids.
|
||||
SaveGridTileMap(data);
|
||||
|
||||
// Build the scene graph / transform hierarchy to know the order to startup entities.
|
||||
// This also allows us to swap out the root node up front if necessary.
|
||||
BuildEntityHierarchy(data);
|
||||
@@ -576,6 +579,19 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
meta.LastComponentRemoved = _timing.CurTick;
|
||||
}
|
||||
|
||||
private void SaveGridTileMap(MapData mapData)
|
||||
{
|
||||
DebugTools.Assert(_context.TileMap != null);
|
||||
|
||||
foreach (var entity in mapData.EntitiesToDeserialize.Keys)
|
||||
{
|
||||
if (HasComp<MapGridComponent>(entity))
|
||||
{
|
||||
EnsureComp<MapSaveTileMapComponent>(entity).TileMap = _context.TileMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildEntityHierarchy(MapData mapData)
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
@@ -981,28 +997,74 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
var gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
var tileDefs = new HashSet<int>();
|
||||
|
||||
Dictionary<int, string>? origTileMap = null;
|
||||
foreach (var ent in entities)
|
||||
{
|
||||
if (!gridQuery.TryGetComponent(ent, out var grid))
|
||||
continue;
|
||||
|
||||
var tileEnumerator = grid.GetAllTilesEnumerator(false);
|
||||
|
||||
var tileEnumerator = _mapSystem.GetAllTilesEnumerator(ent, grid, ignoreEmpty: false);
|
||||
while (tileEnumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
tileDefs.Add(tileRef.Value.Tile.TypeId);
|
||||
}
|
||||
|
||||
if (TryComp(ent, out MapSaveTileMapComponent? saveTileMap))
|
||||
origTileMap ??= saveTileMap.TileMap;
|
||||
}
|
||||
|
||||
Dictionary<int, int> tileIdMap;
|
||||
if (origTileMap != null)
|
||||
{
|
||||
tileIdMap = new Dictionary<int, int>();
|
||||
|
||||
// We are re-saving a map, so we have an original tile map we can preserve.
|
||||
foreach (var (origId, prototypeId) in origTileMap)
|
||||
{
|
||||
// Skip removed tile definitions.
|
||||
if (!_tileDefManager.TryGetDefinition(prototypeId, out var definition))
|
||||
continue;
|
||||
|
||||
tileIdMap.Add(definition.TileId, origId);
|
||||
}
|
||||
|
||||
// Assign new IDs for all new tile types.
|
||||
var nextId = 0;
|
||||
foreach (var tileId in tileDefs)
|
||||
{
|
||||
if (tileIdMap.ContainsKey(tileId))
|
||||
continue;
|
||||
|
||||
// New tile, assign new ID that isn't taken by original tile map.
|
||||
while (origTileMap.ContainsKey(nextId))
|
||||
{
|
||||
nextId += 1;
|
||||
}
|
||||
|
||||
tileIdMap.Add(tileId, nextId);
|
||||
nextId += 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make no-op tile ID map.
|
||||
tileIdMap = tileDefs.ToDictionary(x => x, x => x);
|
||||
}
|
||||
|
||||
DebugTools.Assert(
|
||||
tileIdMap.Count == tileIdMap.Values.Distinct().Count(),
|
||||
"Tile ID map has double mapped values??");
|
||||
|
||||
_context.TileWriteMap = tileIdMap;
|
||||
|
||||
var tileMap = new MappingDataNode();
|
||||
rootNode.Add("tilemap", tileMap);
|
||||
var ordered = new List<int>(tileDefs);
|
||||
ordered.Sort();
|
||||
|
||||
foreach (var tyleId in ordered)
|
||||
foreach (var (nativeId, mapId) in tileIdMap.OrderBy(x => x.Key))
|
||||
{
|
||||
var tileDef = _tileDefManager[tyleId];
|
||||
tileMap.Add(tyleId.ToString(CultureInfo.InvariantCulture), tileDef.ID);
|
||||
tileMap.Add(
|
||||
mapId.ToString(CultureInfo.InvariantCulture),
|
||||
_tileDefManager[nativeId].ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1176,11 +1238,12 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
|
||||
foreach (var component in EntityManager.GetComponents(entityUid))
|
||||
{
|
||||
if (component is MapSaveIdComponent)
|
||||
var compType = component.GetType();
|
||||
var registration = _factory.GetRegistration(compType);
|
||||
if (registration.Unsaved)
|
||||
continue;
|
||||
|
||||
var compType = component.GetType();
|
||||
var compName = _factory.GetComponentName(compType);
|
||||
var compName = registration.Name;
|
||||
_context.CurrentComponent = compName;
|
||||
MappingDataNode? compMapping;
|
||||
MappingDataNode? protMapping = null;
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Robust.Server.GameObjects
|
||||
/// This component stores the previous map UID of entities from map load.
|
||||
/// This can then be used to re-serialize the entity with the same UID for the merge driver to recognize.
|
||||
/// </remarks>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
public sealed partial class MapSaveIdComponent : Component
|
||||
{
|
||||
public int Uid { get; set; }
|
||||
|
||||
24
Robust.Server/GameObjects/MapSaveTileMapComponent.cs
Normal file
24
Robust.Server/GameObjects/MapSaveTileMapComponent.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Used by <see cref="MapLoaderSystem"/> to track the original tile map from when a map was loaded.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This component is used to reduce differences on map saving, by making it so that a tile map can be re-used between map saves even if internal engine IDs change.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This component is created on every grid entity read during map load.
|
||||
/// This means loading a multi-grid map will create multiple of these components.
|
||||
/// When re-saving the map, the map loader will arbitrarily choose which available <see cref="MapSaveTileMapComponent"/>
|
||||
/// to use.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[RegisterComponent, UnsavedComponent]
|
||||
internal sealed partial class MapSaveTileMapComponent : Component
|
||||
{
|
||||
public Dictionary<int, string> TileMap = [];
|
||||
}
|
||||
@@ -48,30 +48,17 @@ internal sealed partial class PvsSystem
|
||||
// We add chunk-size here so that its consistent with the normal PVS range setting.
|
||||
// I.e., distance here is the Chebyshev distance to the centre of each chunk, but the normal pvs range only
|
||||
// required that the chunk be touching the box, not the centre.
|
||||
var limit = distance < (_lowLodDistance + ChunkSize) / 2
|
||||
var count = distance <= (_viewSize + ChunkSize) / 2
|
||||
? chunk.Contents.Count
|
||||
: chunk.LodCounts[0];
|
||||
|
||||
// If the PVS budget is exceeded, it should still be safe to send all of the chunk's direct children, though
|
||||
// after that we have no guarantee that an entity's parent got sent.
|
||||
var directChildren = Math.Min(limit, chunk.LodCounts[2]);
|
||||
|
||||
// Send entities on the chunk.
|
||||
var span = CollectionsMarshal.AsSpan(chunk.Contents);
|
||||
for (var i = 0; i < limit; i++)
|
||||
var span = CollectionsMarshal.AsSpan(chunk.Contents)[..count];
|
||||
foreach (ref var ent in span)
|
||||
{
|
||||
var ent = span[i];
|
||||
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
|
||||
|
||||
if ((mask & meta.VisMask) != meta.VisMask)
|
||||
continue;
|
||||
|
||||
// TODO PVS improve this somehow
|
||||
// Having entities "leave" pvs view just because the pvs entry budget was exceeded sucks.
|
||||
// This probably requires changing client game state manager to support receiving entities with unknown parents.
|
||||
// Probably needs to do something similar to pending net entity states, but for entity spawning.
|
||||
if (!AddEntity(session, ref ent, ref meta, fromTick))
|
||||
limit = directChildren;
|
||||
if ((mask & meta.VisMask) == meta.VisMask)
|
||||
AddEntity(session, ref ent, ref meta, fromTick);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,26 +67,26 @@ internal sealed partial class PvsSystem
|
||||
/// </summary>
|
||||
/// <returns>Returns false if the entity would exceed the client's PVS budget.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref PvsMetadata meta,
|
||||
private void AddEntity(PvsSession session, ref PvsChunk.ChunkEntity ent, ref PvsMetadata meta,
|
||||
GameTick fromTick)
|
||||
{
|
||||
DebugTools.Assert(fromTick < _gameTiming.CurTick);
|
||||
ref var data = ref session.DataMemory.GetRef(ent.Ptr.Index);
|
||||
|
||||
if (data.LastSeen == _gameTiming.CurTick)
|
||||
return true;
|
||||
return;
|
||||
|
||||
if (meta.LifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}");
|
||||
EntityManager.QueueDeleteEntity(ent.Uid);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
var (entered,budgetExceeded) = IsEnteringPvsRange(ref data, fromTick, ref session.Budget);
|
||||
|
||||
if (budgetExceeded)
|
||||
return false;
|
||||
return;
|
||||
|
||||
data.LastSeen = _gameTiming.CurTick;
|
||||
session.ToSend!.Add(ent.Ptr);
|
||||
@@ -108,25 +95,23 @@ internal sealed partial class PvsSystem
|
||||
{
|
||||
var state = GetFullEntityState(session.Session, ent.Uid, ent.Meta);
|
||||
session.States.Add(state);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (entered)
|
||||
{
|
||||
var state = GetEntityState(session.Session, ent.Uid, data.EntityLastAcked, ent.Meta);
|
||||
session.States.Add(state);
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (meta.LastModifiedTick <= fromTick)
|
||||
return true;
|
||||
return;
|
||||
|
||||
var entState = GetEntityState(session.Session, ent.Uid, fromTick , ent.Meta);
|
||||
|
||||
if (!entState.Empty)
|
||||
session.States.Add(entState);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -62,12 +62,14 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
public bool CullingEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the side of the view bounds square.
|
||||
/// Size of the side of the view bounds square. Related to <see cref="CVars.NetMaxUpdateRange"/>
|
||||
/// </summary>
|
||||
private float _viewSize;
|
||||
|
||||
// see CVars.NetLowLodDistance
|
||||
private float _lowLodDistance;
|
||||
/// <summary>
|
||||
/// Size of the side of the priority view bounds square. Related to <see cref="CVars.NetPvsPriorityRange"/>
|
||||
/// </summary>
|
||||
private float _priorityViewSize;
|
||||
|
||||
/// <summary>
|
||||
/// Per-tick ack data to avoid re-allocating.
|
||||
@@ -139,7 +141,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true);
|
||||
Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetLowLodRange, OnLodChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPvsPriorityRange, OnPriorityRangeChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetForceAckThreshold, OnForceAckChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPvsAsync, OnAsyncChanged, true);
|
||||
Subs.CVar(_configManager, CVars.NetPvsCompressLevel, ResetParallelism, true);
|
||||
@@ -276,12 +278,13 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
private void OnViewsizeChanged(float value)
|
||||
{
|
||||
_viewSize = value;
|
||||
_viewSize = Math.Max(ChunkSize, value);
|
||||
OnPriorityRangeChanged(_configManager.GetCVar(CVars.NetPvsPriorityRange));
|
||||
}
|
||||
|
||||
private void OnLodChanged(float value)
|
||||
private void OnPriorityRangeChanged(float value)
|
||||
{
|
||||
_lowLodDistance = Math.Clamp(value, ChunkSize, 100f);
|
||||
_priorityViewSize = Math.Max(_viewSize, value);
|
||||
}
|
||||
|
||||
private void OnForceAckChanged(int value)
|
||||
@@ -372,13 +375,6 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
{
|
||||
ref var data = ref pvsSession.DataMemory.GetRef(intPtr.Index);
|
||||
DebugTools.AssertEqual(data.LastSeen, _gameTiming.CurTick);
|
||||
|
||||
// if an entity is visible, its parents should always be visible.
|
||||
if (_xformQuery.GetComponent(GetEntity(IndexToNetEntity(intPtr))).ParentUid is not {Valid: true} pUid)
|
||||
continue;
|
||||
|
||||
DebugTools.Assert(toSendSet.Contains(GetNetEntity(pUid)),
|
||||
$"Attempted to send an entity without sending it's parents. Entity: {ToPrettyString(pUid)}.");
|
||||
}
|
||||
|
||||
pvsSession.PreviouslySent.TryGetValue(_gameTiming.CurTick - 1, out var lastSent);
|
||||
@@ -394,7 +390,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
private (Vector2 worldPos, float range, EntityUid? map) CalcViewBounds(Entity<TransformComponent, EyeComponent?> eye)
|
||||
{
|
||||
var size = Math.Max(eye.Comp2?.PvsSize ?? _viewSize, 1);
|
||||
var size = Math.Max(eye.Comp2?.PvsSize ?? _priorityViewSize, 1);
|
||||
return (_transform.GetWorldPosition(eye.Comp1), size / 2f, eye.Comp1.MapUid);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,12 +104,16 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
|
||||
root.Add("version", new ValueDataNode("6"));
|
||||
|
||||
gridNode.Value = SerializeTiles(value);
|
||||
Dictionary<int, int>? tileWriteMap = null;
|
||||
if (context is MapSerializationContext mapContext)
|
||||
tileWriteMap = mapContext.TileWriteMap;
|
||||
|
||||
gridNode.Value = SerializeTiles(value, tileWriteMap);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static string SerializeTiles(MapChunk chunk)
|
||||
private static string SerializeTiles(MapChunk chunk, Dictionary<int, int>? tileWriteMap)
|
||||
{
|
||||
// number of bytes written per tile, because sizeof(Tile) is useless.
|
||||
const int structSize = 6;
|
||||
@@ -125,7 +129,11 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
|
||||
for (ushort x = 0; x < chunk.ChunkSize; x++)
|
||||
{
|
||||
var tile = chunk.GetTile(x, y);
|
||||
writer.Write(tile.TypeId);
|
||||
var typeId = tile.TypeId;
|
||||
if (tileWriteMap != null)
|
||||
typeId = tileWriteMap[typeId];
|
||||
|
||||
writer.Write(typeId);
|
||||
writer.Write((byte)tile.Flags);
|
||||
writer.Write(tile.Variant);
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ namespace Robust.Server.Physics
|
||||
grids.Add(foundSplits);
|
||||
}
|
||||
|
||||
var oldGrid = _mapManager.GetGrid(uid);
|
||||
var oldGrid = Comp<MapGridComponent>(uid);
|
||||
var oldGridUid = uid;
|
||||
|
||||
// Split time
|
||||
@@ -605,7 +605,7 @@ namespace Robust.Server.Physics
|
||||
|
||||
DebugTools.Assert(chunk.FilledTiles > 0);
|
||||
|
||||
var grid = _mapManager.GetGrid(gridEuid);
|
||||
var grid = Comp<MapGridComponent>(gridEuid);
|
||||
var group = CreateNodes(gridEuid, grid, chunk);
|
||||
_nodes[gridEuid][chunk.Indices] = group;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" PrivateAssets="compile" />
|
||||
<PackageReference Include="SpaceWizards.Sodium" PrivateAssets="compile" />
|
||||
<PackageReference Include="SharpZstd.Interop" PrivateAssets="compile" />
|
||||
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" />
|
||||
<PackageReference Condition="'$(RobustToolsBuild)' == 'True'" Include="JetBrains.Profiler.Api" />
|
||||
<PackageReference Include="Microsoft.NET.ILLink.Tasks" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.Metrics;
|
||||
using Robust.Server.Configuration;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.DataMetrics;
|
||||
@@ -80,6 +81,8 @@ namespace Robust.Server
|
||||
deps.Register<IWatchdogApi, WatchdogApi>();
|
||||
deps.Register<IScriptHost, ScriptHost>();
|
||||
deps.Register<IMetricsManager, MetricsManager>();
|
||||
deps.Register<IMetricsManagerInternal, MetricsManager>();
|
||||
deps.Register<IMeterFactory, MetricsManager>();
|
||||
deps.Register<IAuthManager, AuthManager>();
|
||||
deps.Register<HubManager, HubManager>();
|
||||
deps.Register<IRobustSerializer, ServerRobustSerializer>();
|
||||
|
||||
@@ -256,7 +256,7 @@ namespace Robust.Server.ServerStatus
|
||||
_context = context;
|
||||
RequestMethod = new HttpMethod(context.Request.HttpMethod!);
|
||||
|
||||
var headers = new Dictionary<string, StringValues>();
|
||||
var headers = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (string? key in context.Request.Headers.Keys)
|
||||
{
|
||||
if (key == null)
|
||||
|
||||
@@ -157,11 +157,14 @@ namespace Robust.Server.ServerStatus
|
||||
try
|
||||
{
|
||||
// Passing null as content works so...
|
||||
await _httpClient.PostAsync(new Uri(_baseUri, $"server_api/{_watchdogKey}/ping"), null!);
|
||||
_sawmill.Debug("Sending ping to watchdog...");
|
||||
using var resp = await _httpClient.PostAsync(new Uri(_baseUri, $"server_api/{_watchdogKey}/ping"), null!);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
_sawmill.Debug("Succeeded in sending ping to watchdog");
|
||||
}
|
||||
catch (HttpRequestException e)
|
||||
{
|
||||
_sawmill.Warning("Failed to send ping to watchdog:\n{0}", e);
|
||||
_sawmill.Error("Failed to send ping to watchdog:\n{0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,12 @@ public sealed class PlayerCommand : ToolshedCommand
|
||||
{
|
||||
return sessions.AttachedEntity ?? default;
|
||||
}
|
||||
|
||||
[CommandImplementation("entity")]
|
||||
public EntityUid GetPlayerEntity([CommandInvocationContext] IInvocationContext ctx, [CommandArgument] string username)
|
||||
{
|
||||
return GetPlayerEntity(Immediate(ctx, username));
|
||||
}
|
||||
}
|
||||
|
||||
public record struct NoSuchPlayerError(string Username) : IConError
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Server.Console;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -296,7 +297,6 @@ namespace Robust.Server.ViewVariables
|
||||
|
||||
output = prototype;
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Server.ViewVariables.Traits;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network.Messages;
|
||||
@@ -96,6 +97,9 @@ namespace Robust.Server.ViewVariables
|
||||
if (value is EntityUid uid)
|
||||
return IoCManager.Resolve<IEntityManager>().GetComponentOrNull<MetaDataComponent>(uid)?.NetEntity ?? NetEntity.Invalid;
|
||||
|
||||
if (value is SoundSpecifier)
|
||||
return value;
|
||||
|
||||
var valType = value.GetType();
|
||||
if (!valType.IsValueType)
|
||||
{
|
||||
|
||||
@@ -592,7 +592,11 @@ namespace Robust.Shared.Maths
|
||||
if (c != 0)
|
||||
{
|
||||
if (max == rgb.R)
|
||||
{
|
||||
h = (rgb.G - rgb.B) / c % 6.0f;
|
||||
if (h < 0f)
|
||||
h += 6.0f;
|
||||
}
|
||||
else if (max == rgb.G)
|
||||
h = (rgb.B - rgb.R) / c + 2.0f;
|
||||
else if (max == rgb.B)
|
||||
@@ -774,7 +778,11 @@ namespace Robust.Shared.Maths
|
||||
|
||||
var h = 0.0f;
|
||||
if (max == rgb.R)
|
||||
{
|
||||
h = (rgb.G - rgb.B) / c % 6.0f;
|
||||
if (h < 0f)
|
||||
h += 6.0f;
|
||||
}
|
||||
else if (max == rgb.G)
|
||||
h = (rgb.B - rgb.R) / c + 2.0f;
|
||||
else if (max == rgb.B)
|
||||
|
||||
@@ -59,6 +59,192 @@ public static class Vector2Helpers
|
||||
return length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the lengths of two vectors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Avoids square root computations by using squared lengths.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// a positive value if <paramref name="a"/> is longer than <paramref name="b"/>,
|
||||
/// a negative value if <paramref name="b"/> is longer than <paramref name="a"/>,
|
||||
/// or 0 if <paramref name="a"/> and <paramref name="b"/> have equal lengths.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float CompareLength(Vector2 a, Vector2 b)
|
||||
{
|
||||
return a.LengthSquared() - b.LengthSquared();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the length of a vector with a scalar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Avoids a square root computation by using squared length.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// a positive value if <paramref name="vec"/> is longer than <paramref name="scalar"/>,
|
||||
/// a negative value if <paramref name="vec"/> is shorter than <paramref name="scalar"/>,
|
||||
/// or 0 if <paramref name="vec"/> has a length equal to <paramref name="scalar"/>.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float CompareLength(Vector2 vec, float scalar)
|
||||
{
|
||||
return vec.LengthSquared() - (scalar * scalar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the length of this vector with a scalar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Avoids a square root computation by using squared length.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// a positive value if this vector is longer than <paramref name="scalar"/>,
|
||||
/// a negative value if this vector is shorter than <paramref name="scalar"/>,
|
||||
/// or 0 if this vector has a length equal to <paramref name="scalar"/>.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float CompareLengthTo(this Vector2 vec, float scalar)
|
||||
{
|
||||
return CompareLength(vec, scalar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the length of this vector with another.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Avoids square root computations by using squared lengths.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// a positive value if this vector is longer than <paramref name="otherVec"/>,
|
||||
/// a negative value if this vector is shorter than <paramref name="otherVec"/>,
|
||||
/// or 0 if this vector and <paramref name="otherVec"/> have equal lengths.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float CompareLengthTo(this Vector2 thisVec, Vector2 otherVec)
|
||||
{
|
||||
return CompareLength(thisVec, otherVec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector greater than <paramref name="scalar"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsLongerThan(this Vector2 vec, float scalar)
|
||||
{
|
||||
return CompareLength(vec, scalar) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector greater than the length of <paramref name="otherVec"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsLongerThan(this Vector2 thisVec, Vector2 otherVec)
|
||||
{
|
||||
return CompareLength(thisVec, otherVec) > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector greater than or equal to <paramref name="scalar"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsLongerThanOrEqualTo(this Vector2 vec, float scalar)
|
||||
{
|
||||
return CompareLength(vec, scalar) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector greater than or equal to the length of <paramref name="otherVec"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsLongerThanOrEqualTo(this Vector2 thisVec, Vector2 otherVec)
|
||||
{
|
||||
return CompareLength(thisVec, otherVec) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector less than <paramref name="scalar"/>?
|
||||
/// </summary>
|
||||
/// <param name="vec"></param>
|
||||
/// <param name="scalar"></param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsShorterThan(this Vector2 vec, float scalar)
|
||||
{
|
||||
return CompareLength(vec, scalar) < 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector less than the length of <paramref name="otherVec"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsShorterThan(this Vector2 thisVec, Vector2 otherVec)
|
||||
{
|
||||
return CompareLength(thisVec, otherVec) < 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector less than or equal to <paramref name="scalar"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsShorterThanOrEqualTo(this Vector2 vec, float scalar)
|
||||
{
|
||||
return CompareLength(vec, scalar) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector less than or equal to the length of <paramref name="otherVec"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsShorterThanOrEqualTo(this Vector2 thisVec, Vector2 otherVec)
|
||||
{
|
||||
return CompareLength(thisVec, otherVec) <= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this vector's length is equal to <paramref name="scalar"/>.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool LengthEquals(this Vector2 thisVec, float scalar)
|
||||
{
|
||||
return CompareLength(thisVec, scalar) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this vector the same length as <paramref name="otherVec"/>?
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsEqualLengthTo(this Vector2 thisVec, Vector2 otherVec)
|
||||
{
|
||||
return CompareLength(thisVec, otherVec) == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the length of a vector with a scalar.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Avoids a square root computation by using squared length.
|
||||
/// </remarks>
|
||||
/// <returns>
|
||||
/// a positive value if <paramref name="vec"/> is shorter than <paramref name="scalar"/>,
|
||||
/// a negative value if <paramref name="vec"/> is longer than <paramref name="scalar"/>,
|
||||
/// or 0 if <paramref name="vec"/> has a length equal to <paramref name="scalar"/>.
|
||||
/// </returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float CompareLength(float scalar, Vector2 vec)
|
||||
{
|
||||
return (scalar * scalar) - vec.LengthSquared();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the length of this vector zero?
|
||||
/// </summary>
|
||||
public static bool IsLengthZero(this Vector2 vec)
|
||||
{
|
||||
return vec.LengthSquared() == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform the cross product on a scalar and a vector. In 2D this produces
|
||||
/// a vector.
|
||||
|
||||
@@ -67,12 +67,12 @@ namespace Robust.Shared.Scripting
|
||||
|
||||
public MapGridComponent getgrid(int i)
|
||||
{
|
||||
return map.GetGrid(new EntityUid(i));
|
||||
return ent.GetComponent<MapGridComponent>(new EntityUid(i));
|
||||
}
|
||||
|
||||
public MapGridComponent getgrid(EntityUid mapId)
|
||||
{
|
||||
return map.GetGrid(mapId);
|
||||
return ent.GetComponent<MapGridComponent>(mapId);
|
||||
}
|
||||
|
||||
public T res<T>()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
#if ROBUST_ANALYZERS_IMPL
|
||||
namespace Robust.Shared.Analyzers.Implementation;
|
||||
#else
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
#if ROBUST_ANALYZERS_IMPL
|
||||
namespace Robust.Shared.Analyzers.Implementation;
|
||||
#else
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
#if ROBUST_ANALYZERS_IMPL
|
||||
namespace Robust.Shared.Analyzers.Implementation;
|
||||
#else
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
#if ROBUST_ANALYZERS_IMPL
|
||||
namespace Robust.Shared.Analyzers.Implementation;
|
||||
#else
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
@@ -74,4 +74,31 @@ namespace Robust.Shared.Asynchronous
|
||||
/// </remarks>
|
||||
void BlockWaitOnTask(Task task);
|
||||
}
|
||||
|
||||
internal static class TaskManagerExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Run a callback on the main thread, returning a task that represents its completion.
|
||||
/// </summary>
|
||||
/// <seealso cref="ITaskManager.RunOnMainThread"/>
|
||||
public static Task TaskOnMainThread(this ITaskManager taskManager, Action callback)
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
|
||||
taskManager.RunOnMainThread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
callback();
|
||||
tcs.SetResult();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
tcs.TrySetException(e);
|
||||
}
|
||||
});
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,6 @@ namespace Robust.Shared.Audio
|
||||
[DataDefinition]
|
||||
public partial struct AudioParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The DistanceModel to use for this specific source.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public Attenuation Attenuation { get; set; } = Attenuation.LinearDistanceClamped;
|
||||
|
||||
/// <summary>
|
||||
/// Base volume to play the audio at, in dB.
|
||||
/// </summary>
|
||||
@@ -45,13 +39,13 @@ namespace Robust.Shared.Audio
|
||||
/// Scale for the audio pitch.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float Pitch { get; set; } = Default.Pitch;
|
||||
public float Pitch
|
||||
{
|
||||
get => _pitch;
|
||||
set => _pitch = MathF.Max(0f, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Audio bus to play on.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string BusName { get; set; } = Default.BusName;
|
||||
private float _pitch = Default.Pitch;
|
||||
|
||||
/// <summary>
|
||||
/// Only applies to positional audio.
|
||||
@@ -89,7 +83,7 @@ namespace Robust.Shared.Audio
|
||||
/// <summary>
|
||||
/// The "default" audio configuration.
|
||||
/// </summary>
|
||||
public static readonly AudioParams Default = new(0, 1, "Master", SharedAudioSystem.DefaultSoundRange, 1, 1, false, 0f);
|
||||
public static readonly AudioParams Default = new(0, 1, SharedAudioSystem.DefaultSoundRange, 1, 1, false, 0f);
|
||||
|
||||
// explicit parameterless constructor required so that default values get set properly.
|
||||
public AudioParams() { }
|
||||
@@ -97,21 +91,19 @@ namespace Robust.Shared.Audio
|
||||
public AudioParams(
|
||||
float volume,
|
||||
float pitch,
|
||||
string busName,
|
||||
float maxDistance,
|
||||
float refDistance,
|
||||
bool loop,
|
||||
float playOffsetSeconds,
|
||||
float? variation = null)
|
||||
: this(volume, pitch, busName, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation)
|
||||
: this(volume, pitch, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation)
|
||||
{
|
||||
}
|
||||
|
||||
public AudioParams(float volume, float pitch, string busName, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this()
|
||||
public AudioParams(float volume, float pitch, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this()
|
||||
{
|
||||
Volume = volume;
|
||||
Pitch = pitch;
|
||||
BusName = busName;
|
||||
MaxDistance = maxDistance;
|
||||
RolloffFactor = rolloffFactor;
|
||||
ReferenceDistance = refDistance;
|
||||
@@ -167,18 +159,6 @@ namespace Robust.Shared.Audio
|
||||
return me;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of this instance with a new bus name set, for easy chaining.
|
||||
/// </summary>
|
||||
/// <param name="bus">The new bus name.</param>
|
||||
[Pure]
|
||||
public readonly AudioParams WithBusName(string bus)
|
||||
{
|
||||
var me = this;
|
||||
me.BusName = bus;
|
||||
return me;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of this instance with a new max distance set, for easy chaining.
|
||||
/// </summary>
|
||||
@@ -227,18 +207,6 @@ namespace Robust.Shared.Audio
|
||||
return me;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of this instance with attenuation set, for easy chaining.
|
||||
/// </summary>
|
||||
/// <param name="attenuation">The new attenuation.</param>
|
||||
[Pure]
|
||||
public readonly AudioParams WithAttenuation(Attenuation attenuation)
|
||||
{
|
||||
var me = this;
|
||||
me.Attenuation = attenuation;
|
||||
return me;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public readonly AudioParams WithPlayOffset(float offset)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
using Robust.Shared.ViewVariables;
|
||||
@@ -90,11 +91,24 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
public void StartPlaying() => Source.StartPlaying();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void StopPlaying() => Source.StopPlaying();
|
||||
public void StopPlaying()
|
||||
{
|
||||
PlaybackPosition = 0f;
|
||||
Source.StopPlaying();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Restart() => Source.Restart();
|
||||
|
||||
[DataField, AutoNetworkedField]
|
||||
public AudioState State = AudioState.Playing;
|
||||
|
||||
/// <summary>
|
||||
/// Time when the audio was paused so we can offset it later if relevant.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public TimeSpan? PauseTime;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IAudioSource.Playing"/>
|
||||
/// </summary>
|
||||
@@ -208,6 +222,7 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
/// <see cref="IAudioSource.PlaybackPosition"/>
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
[Access(Other = AccessPermissions.ReadWriteExecute)]
|
||||
public float PlaybackPosition
|
||||
{
|
||||
get => Source.PlaybackPosition;
|
||||
@@ -240,6 +255,14 @@ public sealed partial class AudioComponent : Component, IAudioSource
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum AudioState : byte
|
||||
{
|
||||
Stopped,
|
||||
Playing,
|
||||
Paused,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum AudioFlags : byte
|
||||
{
|
||||
|
||||
@@ -55,6 +55,141 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
SubscribeLocalEvent<AudioComponent, EntityUnpausedEvent>(OnAudioUnpaused);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the playback position of audio to the specified spot.
|
||||
/// </summary>
|
||||
public void SetPlaybackPosition(Entity<AudioComponent?>? nullEntity, float position)
|
||||
{
|
||||
if (nullEntity == null)
|
||||
return;
|
||||
|
||||
var entity = nullEntity.Value;
|
||||
|
||||
if (!Resolve(entity.Owner, ref entity.Comp, false))
|
||||
return;
|
||||
|
||||
var audioLength = GetAudioLength(entity.Comp.FileName);
|
||||
|
||||
if (audioLength.TotalSeconds < position)
|
||||
{
|
||||
// Just stop it and return
|
||||
if (!_netManager.IsClient)
|
||||
QueueDel(nullEntity.Value);
|
||||
|
||||
entity.Comp.StopPlaying();
|
||||
return;
|
||||
}
|
||||
|
||||
if (position < 0f)
|
||||
{
|
||||
Log.Error($"Tried to set playback position for {ToPrettyString(entity.Owner)} / {entity.Comp.FileName} outside of bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're paused then the current position is <pause time - start time>, else it's <cur time - start time>
|
||||
var currentPos = (entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart;
|
||||
var timeOffset = TimeSpan.FromSeconds(position - currentPos.TotalSeconds);
|
||||
|
||||
DebugTools.Assert(currentPos > TimeSpan.Zero);
|
||||
|
||||
// Rounding.
|
||||
if (Math.Abs(timeOffset.TotalSeconds) <= 0.01)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (entity.Comp.PauseTime != null)
|
||||
{
|
||||
entity.Comp.PauseTime = entity.Comp.PauseTime.Value + timeOffset;
|
||||
|
||||
// Paused audio doesn't have TimedDespawn so.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bump it back so the actual playback positions moves forward
|
||||
entity.Comp.AudioStart -= timeOffset;
|
||||
|
||||
// need to ensure it doesn't despawn too early.
|
||||
if (TryComp(entity.Owner, out TimedDespawnComponent? despawn))
|
||||
{
|
||||
despawn.Lifetime -= (float) timeOffset.TotalSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
entity.Comp.PlaybackPosition = position;
|
||||
// Network the new playback position.
|
||||
Dirty(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates playback position considering length paused.
|
||||
/// </summary>
|
||||
/// <param name="component"></param>
|
||||
/// <returns></returns>
|
||||
private float GetPlaybackPosition(AudioComponent component)
|
||||
{
|
||||
return (float) (Timing.CurTime - (component.PauseTime ?? TimeSpan.Zero) - component.AudioStart).TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the shared state for an audio entity.
|
||||
/// </summary>
|
||||
public void SetState(EntityUid? entity, AudioState state, bool force = false, AudioComponent? component = null)
|
||||
{
|
||||
if (entity == null || !Resolve(entity.Value, ref component, false))
|
||||
return;
|
||||
|
||||
if (component.State == state && !force)
|
||||
return;
|
||||
|
||||
// Unpause it
|
||||
if (component.State == AudioState.Paused && state == AudioState.Playing)
|
||||
{
|
||||
var pauseOffset = Timing.CurTime - component.PauseTime;
|
||||
component.AudioStart += pauseOffset ?? TimeSpan.Zero;
|
||||
component.PlaybackPosition = (float) (Timing.CurTime - component.AudioStart).TotalSeconds;
|
||||
}
|
||||
|
||||
// If we were stopped then played then restart audiostart to now.
|
||||
if (component.State == AudioState.Stopped && state == AudioState.Playing)
|
||||
{
|
||||
component.AudioStart = Timing.CurTime;
|
||||
component.PauseTime = null;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case AudioState.Stopped:
|
||||
component.AudioStart = Timing.CurTime;
|
||||
component.PauseTime = null;
|
||||
component.StopPlaying();
|
||||
RemComp<TimedDespawnComponent>(entity.Value);
|
||||
break;
|
||||
case AudioState.Paused:
|
||||
// Set it to current time so we can easily unpause it later.
|
||||
component.PauseTime = Timing.CurTime;
|
||||
component.Pause();
|
||||
RemComp<TimedDespawnComponent>(entity.Value);
|
||||
break;
|
||||
case AudioState.Playing:
|
||||
component.PauseTime = null;
|
||||
component.StartPlaying();
|
||||
|
||||
// Reset TimedDespawn so the audio still gets cleaned up.
|
||||
|
||||
if (!component.Looping)
|
||||
{
|
||||
var timed = EnsureComp<TimedDespawnComponent>(entity.Value);
|
||||
var audioLength = GetAudioLength(component.FileName);
|
||||
timed.Lifetime = (float) audioLength.TotalSeconds + 0.01f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
component.State = state;
|
||||
Dirty(entity.Value, component);
|
||||
}
|
||||
|
||||
protected void SetZOffset(float value)
|
||||
{
|
||||
ZOffset = value;
|
||||
@@ -234,7 +369,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
@@ -253,7 +388,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
@@ -274,7 +409,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file globally, without position.
|
||||
@@ -294,7 +429,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
@@ -303,7 +438,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
@@ -312,7 +447,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file following an entity.
|
||||
@@ -378,7 +513,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
|
||||
/// <param name="coordinates">The EntityCoordinates to attach the audio source to.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string filename,
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename,
|
||||
EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -387,7 +522,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
|
||||
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string filename, EntityUid uid,
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid,
|
||||
AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
@@ -419,7 +554,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="playerFilter">The set of players that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
@@ -428,7 +563,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
@@ -437,7 +572,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
/// <param name="recipient">The player that will hear the sound.</param>
|
||||
/// <param name="coordinates">The coordinates at which to play the audio.</param>
|
||||
[return: NotNullIfNotNull("filename")]
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
|
||||
|
||||
/// <summary>
|
||||
/// Play an audio file at a static position.
|
||||
@@ -505,4 +640,12 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
{
|
||||
public NetEntity NetEntity;
|
||||
}
|
||||
|
||||
public bool IsPlaying(EntityUid? stream, AudioComponent? component = null)
|
||||
{
|
||||
if (stream == null || !Resolve(stream.Value, ref component, false))
|
||||
return false;
|
||||
|
||||
return component.State == AudioState.Playing;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,16 +65,26 @@ namespace Robust.Shared
|
||||
CVarDef.Create("net.pool_size", 512, CVar.CLIENT | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum UDP payload size to send.
|
||||
/// Maximum UDP payload size to send by default, for IPv4.
|
||||
/// </summary>
|
||||
/// <seealso cref="NetMtuExpand"/>
|
||||
/// <seealso cref="NetMtuIpv6"/>
|
||||
public static readonly CVarDef<int> NetMtu =
|
||||
CVarDef.Create("net.mtu", 1000, CVar.ARCHIVE);
|
||||
CVarDef.Create("net.mtu", 900, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum UDP payload size to send by default, for IPv6.
|
||||
/// </summary>
|
||||
/// <seealso cref="NetMtu"/>
|
||||
/// <seealso cref="NetMtuExpand"/>
|
||||
public static readonly CVarDef<int> NetMtuIpv6 =
|
||||
CVarDef.Create("net.mtu_ipv6", NetPeerConfiguration.kDefaultMTUV6, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// If set, automatically try to detect MTU above <see cref="NetMtu"/>.
|
||||
/// </summary>
|
||||
/// <seealso cref="NetMtu"/>
|
||||
/// <seealso cref="NetMtuIpv6"/>
|
||||
/// <seealso cref="NetMtuExpandFrequency"/>
|
||||
/// <seealso cref="NetMtuExpandFailAttempts"/>
|
||||
public static readonly CVarDef<bool> NetMtuExpand =
|
||||
@@ -215,18 +225,24 @@ namespace Robust.Shared
|
||||
CVarDef.Create("net.pvs_async", true, CVar.ARCHIVE | CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// View size to take for PVS calculations,
|
||||
/// as the size of the sides of a square centered on the view points of clients.
|
||||
/// View size to take for PVS calculations, as the size of the sides of a square centered on the view points of
|
||||
/// clients. See also <see cref="NetPvsPriorityRange"/>.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> NetMaxUpdateRange =
|
||||
CVarDef.Create("net.pvs_range", 25f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Chunks whose centre is further than this distance away from a player's eye will contain fewer entities.
|
||||
/// This has no effect if it is smaller than <see cref="NetMaxUpdateRange"/>
|
||||
/// A variant of <see cref="NetMaxUpdateRange"/> that is used to limit the view-distance of entities with the
|
||||
/// <see cref="MetaDataFlags.PvsPriority"/> flag set. This can be used to extend the range at which certain
|
||||
/// entities become visible.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> NetLowLodRange =
|
||||
CVarDef.Create("net.low_lod_distance", 100f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
/// <remarks>
|
||||
/// This is useful for entities like lights and occluders to try and prevent noticeable pop-in as players
|
||||
/// move around. Note that this has no effect if it is less than <see cref="NetMaxUpdateRange"/>, and that this
|
||||
/// only works for entities that are directly parented to a grid or map.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> NetPvsPriorityRange =
|
||||
CVarDef.Create("net.pvs_priority_range", 32.5f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum allowed delay between the current tick and a client's last acknowledged tick before we send the
|
||||
@@ -416,6 +432,35 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<int> MetricsPort =
|
||||
CVarDef.Create("metrics.port", 44880, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a fixed interval (seconds) for internal collection of certain metrics,
|
||||
/// when not using the Prometheus metrics server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Most metrics are internally implemented directly via the prometheus-net library.
|
||||
/// These metrics can only be scraped by the Prometheus metrics server (<see cref="MetricsEnabled"/>).
|
||||
/// However, newer metrics are implemented with the <c>System.Diagnostics.Metrics</c> library in the .NET runtime.
|
||||
/// These metrics can be scraped through more means, such as <c>dotnet counters</c>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// While many metrics are simple counters that can "just" be reported,
|
||||
/// some metrics require more advanced internal work and need some code to be ran internally
|
||||
/// before their values are made current. When collecting metrics via a
|
||||
/// method other than the Prometheus metrics server, these metrics pose a problem,
|
||||
/// as there is no way for the game to update them before collection properly.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This CVar acts as a fallback: if set to a value other than 0 (disabled),
|
||||
/// these metrics will be internally updated at the interval provided.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This does not need to be enabled if metrics are collected exclusively via the Prometheus metrics server.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> MetricsUpdateInterval =
|
||||
CVarDef.Create("metrics.update_interval", 0f, CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Enable detailed runtime metrics. Empty to disable.
|
||||
/// </summary>
|
||||
@@ -841,6 +886,22 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<string> RenderFOVColor =
|
||||
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* CONTROLS
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Milliseconds to wait to consider double-click delays.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DoubleClickDelay =
|
||||
CVarDef.Create("controls.double_click_delay", 250, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Range in pixels for double-clicks
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> DoubleClickRange =
|
||||
CVarDef.Create("controls.double_click_range", 10, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* DISPLAY
|
||||
*/
|
||||
@@ -1384,7 +1445,7 @@ namespace Robust.Shared
|
||||
/// Comma-separated list of URLs of hub servers to advertise to.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<string> HubUrls =
|
||||
CVarDef.Create("hub.hub_urls", "https://central.spacestation14.io/hub/", CVar.SERVERONLY);
|
||||
CVarDef.Create("hub.hub_urls", "https://hub.spacestation14.com/", CVar.SERVERONLY);
|
||||
|
||||
/// <summary>
|
||||
/// URL of this server to advertise.
|
||||
@@ -1611,7 +1672,8 @@ namespace Robust.Shared
|
||||
/// original exception rather than sending people on a wild-goose chase to find a non-existent bug.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<bool> ReplayIgnoreErrors =
|
||||
CVarDef.Create("replay.ignore_errors", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
CVarDef.Create("replay.ignore_errors", false, CVar.CLIENTONLY);
|
||||
|
||||
/*
|
||||
* CFG
|
||||
*/
|
||||
|
||||
@@ -607,4 +607,12 @@ public struct ValueList<T> : IEnumerable<T>
|
||||
region.Clear();
|
||||
Count = newCount;
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> select)
|
||||
{
|
||||
foreach (var result in select)
|
||||
{
|
||||
Add(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,17 +558,21 @@ namespace Robust.Shared.Configuration
|
||||
OverrideDefault(def.Name, value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetCVar<T>(string name)
|
||||
public object GetCVar(string name)
|
||||
{
|
||||
using var _ = Lock.ReadGuard();
|
||||
if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered)
|
||||
//TODO: Make flags work, required non-derpy net system.
|
||||
return (T)(GetConfigVarValue(cVar))!;
|
||||
return GetConfigVarValue(cVar);
|
||||
|
||||
throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetCVar<T>(string name)
|
||||
{
|
||||
return (T)GetCVar(name);
|
||||
}
|
||||
|
||||
public T GetCVar<T>(CVarDef<T> def) where T : notnull
|
||||
{
|
||||
return GetCVar<T>(def.Name);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Robust.Shared.Configuration
|
||||
{
|
||||
public static class EnvironmentVariables
|
||||
internal static class EnvironmentVariables
|
||||
{
|
||||
/// <summary>
|
||||
/// The environment variable for configuring CVar overrides. The value
|
||||
@@ -12,23 +13,38 @@ namespace Robust.Shared.Configuration
|
||||
/// </summary>
|
||||
public const string ConfigVarEnvironmentVariable = "ROBUST_CVARS";
|
||||
|
||||
public const string SingleVarPrefix = "ROBUST_CVAR_";
|
||||
|
||||
/// <summary>
|
||||
/// Get the CVar overrides defined in the relevant environment variable.
|
||||
/// </summary>
|
||||
public static IEnumerable<(string, string)> GetEnvironmentCVars()
|
||||
internal static IEnumerable<(string, string)> GetEnvironmentCVars()
|
||||
{
|
||||
var eVarString = Environment.GetEnvironmentVariable(ConfigVarEnvironmentVariable);
|
||||
|
||||
if (eVarString == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
// Handle ROBUST_CVARS.
|
||||
var eVarString = Environment.GetEnvironmentVariable(ConfigVarEnvironmentVariable) ?? "";
|
||||
|
||||
foreach (var cVarPair in eVarString.Split(';', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
var pairParts = cVarPair.Split('=', 2);
|
||||
yield return (pairParts[0], pairParts[1]);
|
||||
}
|
||||
|
||||
// Handle ROBUST_CVAR_*
|
||||
|
||||
foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
|
||||
{
|
||||
var key = (string)entry.Key;
|
||||
var value = (string?)entry.Value;
|
||||
|
||||
if (value == null)
|
||||
continue;
|
||||
|
||||
if (!key.StartsWith(SingleVarPrefix))
|
||||
continue;
|
||||
|
||||
var varName = key[SingleVarPrefix.Length..].Replace("__", ".");
|
||||
yield return (varName, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,13 @@ namespace Robust.Shared.Configuration
|
||||
/// <param name="value">The new default value of the CVar.</param>
|
||||
void OverrideDefault<T>(CVarDef<T> def, T value) where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of a CVar.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the CVar.</param>
|
||||
/// <returns></returns>
|
||||
object GetCVar(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of a CVar.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -12,11 +12,16 @@ internal sealed class DumpSerializerTypeMapCommand : LocalizedCommands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
foreach (var (type, index) in _robustSerializer.GetTypeMap().OrderBy(x => x.Value))
|
||||
{
|
||||
shell.WriteLine($"{index}: {type}");
|
||||
}
|
||||
var stream = new MemoryStream();
|
||||
((RobustSerializer)_robustSerializer).GetHashManifest(stream, true);
|
||||
stream.Position = 0;
|
||||
|
||||
using var streamReader = new StreamReader(stream);
|
||||
shell.WriteLine($"Hash: {_robustSerializer.GetSerializableTypesHashString()}");
|
||||
shell.WriteLine("Manifest:");
|
||||
while (streamReader.ReadLine() is { } line)
|
||||
{
|
||||
shell.WriteLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ sealed class RemoveGridCommand : LocalizedCommands
|
||||
|
||||
var gridIdNet = NetEntity.Parse(args[0]);
|
||||
|
||||
if (!_entManager.TryGetEntity(gridIdNet, out var gridId) || !_map.GridExists(gridId))
|
||||
if (!_entManager.TryGetEntity(gridIdNet, out var gridId) || !_entManager.HasComponent<MapGridComponent>(gridId))
|
||||
{
|
||||
shell.WriteError($"Grid {gridId} does not exist.");
|
||||
return;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user