mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93c887eec5 | ||
|
|
45a2895e4b | ||
|
|
7d6def6adf | ||
|
|
f733a9efa5 | ||
|
|
8a68dce987 | ||
|
|
b8d840437e |
34
.github/workflows/build-all-configurations.yml
vendored
34
.github/workflows/build-all-configurations.yml
vendored
@@ -1,34 +0,0 @@
|
||||
name: Build All Configurations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
targetOS: [Windows, Linux, MacOS]
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4.1.0
|
||||
with:
|
||||
dotnet-version: 9.0.x
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore
|
||||
- name: Build Debug
|
||||
run: dotnet build --no-restore --configuration Debug /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
|
||||
- name: Build Tools
|
||||
run: dotnet build --no-restore --configuration Tools /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
|
||||
- name: Build Release
|
||||
run: dotnet build --no-restore --configuration Release /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
Submodule NetSerializer updated: ced82e76ad...4882400f2c
159
RELEASE-NOTES.md
159
RELEASE-NOTES.md
@@ -54,166 +54,13 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 265.0.3
|
||||
## 261.2.3
|
||||
|
||||
|
||||
## 265.0.2
|
||||
## 261.2.2
|
||||
|
||||
|
||||
## 265.0.1
|
||||
|
||||
|
||||
## 265.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* More members in `IntegrationInstance` now enforce that the instance is idle before accessing it.
|
||||
* `Prototype.ValidateDirectory` now requires that prototype IDs have no spaces or periods in them.
|
||||
* `IPrototypeManager.TryIndex` no longer logs errors unless using the overload with an optional parameter. Use `Resolve()` instead if error logging is desired.
|
||||
* `LocalizedCommands` now has a `Loc` property that refers to `LocalizationManager`. This can cause compile failures if you have static methods in child types that referenced static `Loc`.
|
||||
* `[AutoGenerateComponentState]` now works on parent members for inherited classes. This can cause compile failures in certain formerly silently broken cases with overriden properties.
|
||||
* `Vector3`, `Vector4`, `Quaternion`, and `Matrix4` have been removed from `Robust.Shared.Maths`. Use the `System.Numerics` types instead.
|
||||
|
||||
### New features
|
||||
|
||||
* `RobustClientPackaging.WriteClientResources()` and `RobustServerPackaging.WriteServerResources()` now have an overload taking in a set of things to ignore in the content resources directory.
|
||||
* Added `IPrototypeManager.Resolve()`, which logs an error if the resolved prototype does not exist. This is effectively the previous (but not original) default behavior of `IPrototypeManager.TryIndex`.
|
||||
* There's now a ViewVariables property editor for tuples.
|
||||
* Added `ColorNaming` helper functions for getting textual descriptions of color values.
|
||||
* Added Oklab/Oklch conversion functions for `Color`.
|
||||
* `ColorSelectorSliders` now displays textual descriptions of color values.
|
||||
* Added `TimeSpanExt.TryTimeSpan` to parse `TimeSpan`s with the `1.5h` format available in YAML.
|
||||
* Added `ITestContextLike` and related classes to allow controlling pooled integration instances better.
|
||||
* `EntProtoId` VV prop editors now don't allow setting invalid prototype IDs, inline with `ProtoId<T>`.
|
||||
* Custom VV controls can now be registered using `IViewVariableControlFactory`.
|
||||
* The entity spawn window now shows all placement modes registered with `IPlacementManager`.
|
||||
* Added `VectorHelpers.InterpolateCubic` for `System.Numerics` `Vector3` and `Vector4`.
|
||||
* Added deconstruct helpers for `System.Numerics` `Vector3` and `Vector4`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Pooled integration instances returned by `RobustIntegrationTest` are now treated as non-idle, for consistency with non-pooled startups.
|
||||
* `SharedAudioSystem.SetState` no longer calls `DirtyField` on `PlaybackPosition`, an unnetworked field.
|
||||
* Fix loading texture files from the root directory.
|
||||
* Fix integration test pooling leaking non-reusable instances.
|
||||
* Fix multiple bugs where VV displayed the wrong property editor for remote values.
|
||||
* VV displays group headings again in member list.
|
||||
* Fix a stack overflow that could occur with `ColorSelectorSliders`.
|
||||
* `MidiRenderer` now properly handles `NoteOn` events with 0 velocity (which should actually be treated as `NoteOff` events).
|
||||
|
||||
### Other
|
||||
|
||||
* The debug assert for `RobustRandom.Next(TimeSpan, TimeSpan)` now allows for the two arguments to be equal.
|
||||
* The configuration system will now report an error instead of warning if it fails to load the config file.
|
||||
* Members in `IntegrationInstance` that enforce the instance is idle now always allow access from the instance's thread (e.g. from a callback).
|
||||
* `IPrototypeManager` methods now have `[ForbidLiteral]` where appropriate.
|
||||
* Performance improvements to physics system.
|
||||
* `[ValidatePrototypeIdAttribute]` has been marked as obsolete.
|
||||
* `ParallelManager` no longer cuts out exception information for caught job exceptions.
|
||||
* Improved logging for PVS uninitialized/deleted entity errors.
|
||||
|
||||
### Internal
|
||||
|
||||
* General code & warning cleanup.
|
||||
* Fix `VisibilityTest` being unreliable.
|
||||
* `ColorSelectorSliders` has been internally refactored.
|
||||
* Added CI workflows that test all RT build configurations.
|
||||
|
||||
## 264.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `IPrototypeManager.Index(Type kind, string id)` now throws `UnknownPrototypeException` instead of `KeyNotFoundException`, for consistency with `IPrototypeManager.Index<T>`.
|
||||
|
||||
### New features
|
||||
|
||||
* Types can now implement the new interface `IRobustCloneable<T>` to be cloned by the component state source generator.
|
||||
* Added extra Roslyn Analyzers to detect some misuse of prototypes:
|
||||
* Network serializing prototypes (tagging them with `[Serializable, NetSerializable]`).
|
||||
* Constructing new instances of prototypes directly.
|
||||
* Add `PrototypeManagerExt.Index` helper function that takes a nullable `ProtoId<T>`, returning null if the ID is null.
|
||||
* Added an `AlwaysActive` field to `WebViewControl` to make a browser window active even when not in the UI tree.
|
||||
* Made some common dependencies accessible through `IPlacementManager`.
|
||||
* Added a new `GENITIVE()` localization helper function, which is useful for certain languages.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Sprite scale is now correctly applied to sprite boundaries in `SpriteSystem.GetLocalBounds`.
|
||||
* Fixed documentation for `IPrototypeManager.Index<T>` stating that `KeyNotFoundException` gets thrown, when in actuality `UnknownPrototypeException` gets thrown.
|
||||
|
||||
### Other
|
||||
|
||||
* More tiny optimizations to `DataDefinitionAnalyzer`.
|
||||
* NetSerializer has been updated. On debug, it will now report *where* a type that can't be serialized is referenced from.
|
||||
|
||||
### Internal
|
||||
|
||||
* Minor internal code cleanup.
|
||||
|
||||
|
||||
## 263.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Fully removed some non-`Entity<T>` container methods.
|
||||
|
||||
### New features
|
||||
|
||||
* `IMidiRenderer.LoadSoundfont` has been split into `LoadSoundfontResource` and `LoadSoundfontUser`, the original now being deprecated.
|
||||
* Client command execution now properly catches errors instead of letting them bubble up through the input stack.
|
||||
* Added `CompletionHelper.PrototypeIdsLimited` API to allow commands to autocomplete entity prototype IDs.
|
||||
* Added `spawn:in` Toolshed command.
|
||||
* Added `MapLoaderSystem.TryLoadGeneric` overload to load from a `Stream`.
|
||||
* Added `OutputPanel.GetMessage()` and `OutputPanel.SetMessage()` to allow replacing individual messages.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed debug asserts when using MIDI on Windows.
|
||||
* Fixed an error getting logged on startup on macOS related to window icons.
|
||||
* `CC-BY-NC-ND-4.0` is now a valid license for the RGA validator.
|
||||
* Fixed `TabContainer.CurrentTab` clamping against the wrong value.
|
||||
* Fix culture-based parsing in `TimespanSerializer`.
|
||||
* Fixed grid rendering blowing up on tile IDs that aren't registered.
|
||||
* Fixed debug assert when loading MIDI soundfonts on Windows.
|
||||
* Make `ColorSelectorSliders` properly update the dropdown when changing `SelectorType`.
|
||||
* Fixed `tpto` allowing teleports to oneself, thereby causing them to be deleted.
|
||||
* Fix OpenAL extensions being requested incorrectly, causing an error on macOS.
|
||||
* Fixed horizontal measuring of markup controls in rich text.
|
||||
|
||||
### Other
|
||||
|
||||
* Improved logging for some audio entity errors.
|
||||
* Avoided more server stutters when using `csci`.
|
||||
* Improved physics performance.
|
||||
* Made various localization functions like `GENDER()` not throw if passed a string instead of an `EntityUid`.
|
||||
* The generic clause on `EntitySystem.AddComp<T>` has been changed to `IComponent` (from `Component`) for consistency with `IEntityManager.AddComponent<T>`.
|
||||
* `DataDefinitionAnalyzer` has been optimized somewhat.
|
||||
* Improved assert logging error message when static data fields are encountered.
|
||||
|
||||
### Internal
|
||||
|
||||
* Warning cleanup.
|
||||
* Added more tests for `DataDefinitionAnalyzer`.
|
||||
* Consistently use `EntitySystem` proxy methods in engine.
|
||||
|
||||
|
||||
## 262.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Toolshed commands will now validate that each non-generic command argument is parseable (i.e., has a corresponding type parser). This check can be disabled by explicitly marking the argument as unparseable via `CommandArgumentAttribute.Unparseable`.
|
||||
|
||||
### New features
|
||||
|
||||
* `ToolshedManager.TryParse` now also supports nullable value types.
|
||||
* Add an ignoredComponents arg to IsDefault.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `SpriteComponent.Layer.Visible` setter not marking a sprite's bounding box as dirty.
|
||||
* The audio params in the passed SoundSpecifier for PlayStatic(SoundSpecifier, Filter, ...) will now be used as a default like other PlayStatic overrides.
|
||||
* Fix windows not saving their positions correctly when their x position is <= 0.
|
||||
* Fix transform state handling overriding PVS detachment.
|
||||
## 261.2.1
|
||||
|
||||
|
||||
## 261.2.0
|
||||
|
||||
@@ -21,8 +21,7 @@ zzzz-object-pronoun = { GENDER($ent) ->
|
||||
}
|
||||
|
||||
# Used internally by the DAT-OBJ() function.
|
||||
# Not used in en-US. Created to support other languages.
|
||||
# (e.g., "to him," "for her")
|
||||
# Not used in en-US. Created for supporting other languages.
|
||||
zzzz-dat-object = { GENDER($ent) ->
|
||||
[male] him
|
||||
[female] her
|
||||
@@ -30,16 +29,6 @@ zzzz-dat-object = { GENDER($ent) ->
|
||||
*[neuter] it
|
||||
}
|
||||
|
||||
# Used internally by the GENITIVE() function.
|
||||
# Not used in en-US. Created to support other languages.
|
||||
# e.g., "у него" (Russian), "seines Vaters" (German).
|
||||
zzzz-genitive = { GENDER($ent) ->
|
||||
[male] his
|
||||
[female] her
|
||||
[epicene] their
|
||||
*[neuter] its
|
||||
}
|
||||
|
||||
# Used internally by the POSS-PRONOUN() function.
|
||||
zzzz-possessive-pronoun = { GENDER($ent) ->
|
||||
[male] his
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
color-hue-chroma-lightness = {$lightness} {$chroma} {$hue}
|
||||
color-hue-chroma = {$chroma} {$hue}
|
||||
color-hue-lightness = {$lightness} {$hue}
|
||||
color-very-dark = very dark
|
||||
color-dark = dark
|
||||
color-light = light
|
||||
color-very-light = very light
|
||||
color-mixed-hue = {$a} {$b}
|
||||
color-pale = pale
|
||||
color-gray-adjective = gray
|
||||
color-strong = strong
|
||||
color-pink = pink
|
||||
color-red = red
|
||||
color-orange = orange
|
||||
color-yellow = yellow
|
||||
color-green = green
|
||||
color-cyan = cyan
|
||||
color-blue = blue
|
||||
color-purple = purple
|
||||
color-brown = brown
|
||||
color-white = white
|
||||
color-gray = gray
|
||||
color-black = black
|
||||
|
||||
color-pink-color-red = pinkish red
|
||||
color-red-color-orange = reddish orange
|
||||
color-orange-color-yellow = orangeish yellow
|
||||
color-yellow-color-green = yellowish green
|
||||
color-green-color-cyan = greenish cyan
|
||||
color-cyan-color-blue = cyanish blue
|
||||
color-blue-color-purple = blueish purple
|
||||
color-purple-color-pink = purpleish pink
|
||||
@@ -195,8 +195,6 @@ command-description-spawn-at =
|
||||
Spawns an entity at the given coordinates.
|
||||
command-description-spawn-on =
|
||||
Spawns an entity on the given entity, at it's coordinates.
|
||||
command-description-spawn-in =
|
||||
Spawns an entity in the given container on the given entity, dropping it at its coordinates if it doesn't fit
|
||||
command-description-spawn-attached =
|
||||
Spawns an entity attached to the given entity, at (0 0) relative to it.
|
||||
command-description-mappos =
|
||||
|
||||
@@ -55,7 +55,7 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute(string? tag = null) : DataFieldBaseAttribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
public sealed class NotYamlSerializableAttribute : Attribute;
|
||||
}
|
||||
@@ -117,61 +117,6 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PartialDataDefinitionTest()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
[DataDefinition]
|
||||
public sealed class Foo { }
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(4,15): error RA0017: Type Foo is a DataDefinition but is not partial
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataDefinitionPartialRule).WithSpan(4, 15, 4, 20).WithArguments("Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task NestedPartialDataDefinitionTest()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
public sealed class Foo
|
||||
{
|
||||
[DataDefinition]
|
||||
public sealed partial class Nested { }
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(3,15): error RA0018: Type Foo contains nested data definition Nested but is not partial
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.NestedDataDefinitionPartialRule).WithSpan(3, 15, 3, 20).WithArguments("Foo", "Nested")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task RedundantDataFieldTagTest()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField("someValue")]
|
||||
public int SomeValue;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(6,6): info RA0027: Data field SomeValue in data definition Foo has an explicitly set tag that matches autogenerated tag
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldRedundantTagRule).WithSpan(6, 6, 6, 28).WithArguments("SomeValue", "Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadOnlyPropertyTest()
|
||||
{
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeInstantiationAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(PrototypeInstantiationAnalyzer))]
|
||||
public sealed class PrototypeInstantiationAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new RTAnalyzerTest<PrototypeInstantiationAnalyzer>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Prototypes.Attributes.cs",
|
||||
"Robust.Shared.Prototypes.IPrototype.cs",
|
||||
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.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.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
[Prototype]
|
||||
public sealed class FooPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
}
|
||||
|
||||
public static class Bad
|
||||
{
|
||||
public static FooPrototype Real()
|
||||
{
|
||||
return new FooPrototype();
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,16): warning RA0039: Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.
|
||||
VerifyCS.Diagnostic().WithSpan(15, 16, 15, 34));
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeNetSerializableAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
[TestOf(typeof(PrototypeNetSerializableAnalyzer))]
|
||||
public sealed class PrototypeNetSerializableAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new RTAnalyzerTest<PrototypeNetSerializableAnalyzer>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Serialization.NetSerializableAttribute.cs",
|
||||
"Robust.Shared.Prototypes.Attributes.cs",
|
||||
"Robust.Shared.Prototypes.IPrototype.cs",
|
||||
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.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 System;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
[Prototype]
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class FooPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
public string ID { get; private set; } = default!;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(7,21): warning RA0037: Type FooPrototype is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.
|
||||
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleNetSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"),
|
||||
// /0/Test0.cs(7,21): warning RA0038: Type FooPrototype is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.
|
||||
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"));
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
public sealed class RTAnalyzerTest<TAnalyzer> : CSharpAnalyzerTest<TAnalyzer, DefaultVerifier>
|
||||
where TAnalyzer : DiagnosticAnalyzer, new()
|
||||
{
|
||||
protected override ParseOptions CreateParseOptions()
|
||||
{
|
||||
var baseOptions = (CSharpParseOptions) base.CreateParseOptions();
|
||||
return baseOptions.WithPreprocessorSymbols("ROBUST_ANALYZERS_TEST");
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,6 @@
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LogicalName="Robust.Shared.Serialization.NetSerializableAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Prototypes\Attributes.cs" LogicalName="Robust.Shared.Prototypes.Attributes.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Prototypes\IPrototype.cs" LogicalName="Robust.Shared.Prototypes.IPrototype.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Serialization\Manager\Attributes\DataFieldAttribute.cs" LogicalName="Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -22,7 +22,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private const string DataFieldAttributeName = "DataField";
|
||||
private const string ViewVariablesAttributeName = "ViewVariables";
|
||||
|
||||
public static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
|
||||
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
|
||||
Diagnostics.IdDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} is a DataDefinition but is not partial",
|
||||
@@ -32,7 +32,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to mark any type that is a data definition as partial."
|
||||
);
|
||||
|
||||
public static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
|
||||
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
|
||||
Diagnostics.IdNestedDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} contains nested data definition {1} but is not partial",
|
||||
@@ -62,7 +62,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to add a setter."
|
||||
);
|
||||
|
||||
public static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
|
||||
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
|
||||
Diagnostics.IdDataFieldRedundantTag,
|
||||
"Data field has redundant tag specified",
|
||||
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
|
||||
@@ -102,31 +102,23 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
|
||||
context.RegisterSymbolStartAction(symbolContext =>
|
||||
{
|
||||
if (symbolContext.Symbol is not INamedTypeSymbol typeSymbol)
|
||||
return;
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
|
||||
|
||||
if (!IsDataDefinition(typeSymbol))
|
||||
return;
|
||||
|
||||
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
|
||||
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
|
||||
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
|
||||
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
|
||||
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
|
||||
|
||||
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
|
||||
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
|
||||
}, SymbolKind.NamedType);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
|
||||
context.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
|
||||
}
|
||||
|
||||
private static void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
|
||||
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not TypeDeclarationSyntax declaration)
|
||||
return;
|
||||
|
||||
if (context.ContainingSymbol is not INamedTypeSymbol type)
|
||||
var type = context.SemanticModel.GetDeclaredSymbol(declaration)!;
|
||||
if (!IsDataDefinition(type))
|
||||
return;
|
||||
|
||||
if (!IsPartial(declaration))
|
||||
@@ -137,7 +129,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
var containingType = type.ContainingType;
|
||||
while (containingType != null)
|
||||
{
|
||||
var containingTypeDeclaration = (TypeDeclarationSyntax)containingType.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
var containingTypeDeclaration = (TypeDeclarationSyntax) containingType.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (!IsPartial(containingTypeDeclaration))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(NestedDataDefinitionPartialRule, containingTypeDeclaration.Keyword.GetLocation(), containingType.Name, type.Name));
|
||||
@@ -147,31 +139,32 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
private static void AnalyzeDataField(SyntaxNodeAnalysisContext context)
|
||||
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not FieldDeclarationSyntax field)
|
||||
return;
|
||||
|
||||
if (context.ContainingSymbol?.ContainingType is not INamedTypeSymbol type)
|
||||
var typeDeclaration = field.FirstAncestorOrSelf<TypeDeclarationSyntax>();
|
||||
if (typeDeclaration == null)
|
||||
return;
|
||||
|
||||
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
|
||||
if (!IsDataDefinition(type))
|
||||
return;
|
||||
|
||||
foreach (var variable in field.Declaration.Variables)
|
||||
{
|
||||
var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
|
||||
|
||||
if (fieldSymbol == null)
|
||||
continue;
|
||||
|
||||
if (!IsDataField(fieldSymbol, out _, out var datafieldAttribute))
|
||||
continue;
|
||||
|
||||
if (IsReadOnlyDataField(type, fieldSymbol))
|
||||
{
|
||||
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(fieldSymbol, datafieldAttribute))
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
{
|
||||
TryGetAttributeLocation(field, DataFieldAttributeName, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, fieldSymbol.Name, type.Name));
|
||||
@@ -198,33 +191,30 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
|
||||
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
|
||||
{
|
||||
if (context.Node is not PropertyDeclarationSyntax property)
|
||||
return;
|
||||
|
||||
if (context.ContainingSymbol is not IPropertySymbol propertySymbol)
|
||||
var typeDeclaration = property.FirstAncestorOrSelf<TypeDeclarationSyntax>();
|
||||
if (typeDeclaration == null)
|
||||
return;
|
||||
|
||||
if (propertySymbol.ContainingType is not INamedTypeSymbol type)
|
||||
return;
|
||||
|
||||
if (type.IsRecord || type.IsValueType)
|
||||
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
|
||||
if (!IsDataDefinition(type) || type.IsRecord || type.IsValueType)
|
||||
return;
|
||||
|
||||
var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property);
|
||||
if (propertySymbol == null)
|
||||
return;
|
||||
|
||||
if (!IsDataField(propertySymbol, out _, out var datafieldAttribute))
|
||||
return;
|
||||
|
||||
if (IsReadOnlyDataField(type, propertySymbol))
|
||||
{
|
||||
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(propertySymbol, datafieldAttribute))
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
{
|
||||
TryGetAttributeLocation(property, DataFieldAttributeName, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, propertySymbol.Name, type.Name));
|
||||
@@ -252,6 +242,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
|
||||
{
|
||||
if (!IsDataField(field, out _, out _))
|
||||
return false;
|
||||
|
||||
return IsReadOnlyMember(type, field);
|
||||
}
|
||||
|
||||
@@ -376,14 +369,17 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasRedundantTag(ISymbol symbol, AttributeData datafieldAttribute)
|
||||
private static bool HasRedundantTag(ISymbol symbol)
|
||||
{
|
||||
if (!IsDataField(symbol, out var _, out var attribute))
|
||||
return false;
|
||||
|
||||
// No args, no problem
|
||||
if (datafieldAttribute.ConstructorArguments.Length == 0)
|
||||
if (attribute.ConstructorArguments.Length == 0)
|
||||
return false;
|
||||
|
||||
// If a tag is explicitly specified, it will be the first argument...
|
||||
var tagArgument = datafieldAttribute.ConstructorArguments[0];
|
||||
var tagArgument = attribute.ConstructorArguments[0];
|
||||
// ...but the first arg could also something else, since tag is optional
|
||||
// so we make sure that it's a string
|
||||
if (tagArgument.Value is not string explicitName)
|
||||
@@ -398,6 +394,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static bool HasVVReadWrite(ISymbol symbol)
|
||||
{
|
||||
if (!IsDataField(symbol, out _, out _))
|
||||
return false;
|
||||
|
||||
// Make sure it has ViewVariablesAttribute
|
||||
AttributeData? viewVariablesAttribute = null;
|
||||
foreach (var attr in symbol.GetAttributes())
|
||||
@@ -423,6 +422,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static bool IsNotYamlSerializable(ISymbol field, ITypeSymbol type)
|
||||
{
|
||||
if (!IsDataField(field, out _, out _))
|
||||
return false;
|
||||
|
||||
return HasAttribute(type, NotYamlSerializableName);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
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 PrototypeInstantiationAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
|
||||
|
||||
public static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdPrototypeInstantiation,
|
||||
"Do not instantiate prototypes directly",
|
||||
"Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.RegisterCompilationStartAction(static ctx =>
|
||||
{
|
||||
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
|
||||
if (prototypeInterface == null)
|
||||
return;
|
||||
|
||||
ctx.RegisterOperationAction(symContext => Check(prototypeInterface, symContext), OperationKind.ObjectCreation);
|
||||
});
|
||||
}
|
||||
|
||||
private static void Check(INamedTypeSymbol prototypeInterface, OperationAnalysisContext ctx)
|
||||
{
|
||||
if (ctx.Operation is not IObjectCreationOperation { Type: { } resultType } creationOp)
|
||||
return;
|
||||
|
||||
if (!TypeSymbolHelper.ImplementsInterface(resultType, prototypeInterface))
|
||||
return;
|
||||
|
||||
ctx.ReportDiagnostic(Diagnostic.Create(Rule, creationOp.Syntax.GetLocation()));
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class PrototypeNetSerializableAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
|
||||
private const string NetSerializableAttributeType = "Robust.Shared.Serialization.NetSerializableAttribute";
|
||||
|
||||
public static readonly DiagnosticDescriptor RuleNetSerializable = new(
|
||||
Diagnostics.IdPrototypeNetSerializable,
|
||||
"Prototypes should not be [NetSerializable]",
|
||||
"Type {0} is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
|
||||
public static readonly DiagnosticDescriptor RuleSerializable = new(
|
||||
Diagnostics.IdPrototypeSerializable,
|
||||
"Prototypes should not be [Serializable]",
|
||||
"Type {0} is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [
|
||||
RuleNetSerializable,
|
||||
RuleSerializable
|
||||
];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
|
||||
context.RegisterCompilationStartAction(static ctx =>
|
||||
{
|
||||
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
|
||||
var netSerializableAttribute = ctx.Compilation.GetTypeByMetadataName(NetSerializableAttributeType);
|
||||
|
||||
if (prototypeInterface == null || netSerializableAttribute == null)
|
||||
return;
|
||||
|
||||
ctx.RegisterSymbolAction(symbolContext => CheckClass(prototypeInterface, netSerializableAttribute, symbolContext), SymbolKind.NamedType);
|
||||
});
|
||||
}
|
||||
|
||||
private static void CheckClass(
|
||||
INamedTypeSymbol prototypeInterface,
|
||||
INamedTypeSymbol netSerializableAttribute,
|
||||
SymbolAnalysisContext symbolContext)
|
||||
{
|
||||
if (symbolContext.Symbol is not INamedTypeSymbol symbol)
|
||||
return;
|
||||
|
||||
if (!TypeSymbolHelper.ImplementsInterface(symbol, prototypeInterface))
|
||||
return;
|
||||
|
||||
if (AttributeHelper.HasAttribute(symbol, netSerializableAttribute, out _))
|
||||
{
|
||||
symbolContext.ReportDiagnostic(
|
||||
Diagnostic.Create(RuleNetSerializable, symbol.Locations[0], symbol.ToDisplayString()));
|
||||
}
|
||||
|
||||
if (symbol.IsSerializable)
|
||||
{
|
||||
symbolContext.ReportDiagnostic(
|
||||
Diagnostic.Create(RuleSerializable, symbol.Locations[0], symbol.ToDisplayString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,10 +162,9 @@ namespace Robust.Client.WebView.Cef
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOpen => _data != null;
|
||||
public bool IsLoading => _data?.Browser.IsLoading ?? false;
|
||||
|
||||
public void StartBrowser()
|
||||
public void EnteredTree()
|
||||
{
|
||||
DebugTools.AssertNull(_data);
|
||||
|
||||
@@ -196,7 +195,7 @@ namespace Robust.Client.WebView.Cef
|
||||
_data = new LiveData(texture, client, browser, renderer);
|
||||
}
|
||||
|
||||
public void CloseBrowser()
|
||||
public void ExitedTree()
|
||||
{
|
||||
DebugTools.AssertNotNull(_data);
|
||||
|
||||
|
||||
@@ -81,13 +81,11 @@ namespace Robust.Client.WebView.Headless
|
||||
|
||||
private sealed class WebViewControlImplDummy : DummyBase, IWebViewControlImpl
|
||||
{
|
||||
public bool IsOpen => false;
|
||||
|
||||
public void StartBrowser()
|
||||
public void EnteredTree()
|
||||
{
|
||||
}
|
||||
|
||||
public void CloseBrowser()
|
||||
public void ExitedTree()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,8 @@ namespace Robust.Client.WebView
|
||||
/// </summary>
|
||||
internal interface IWebViewControlImpl : IWebViewControl
|
||||
{
|
||||
public bool IsOpen { get; }
|
||||
|
||||
void StartBrowser();
|
||||
void CloseBrowser();
|
||||
void EnteredTree();
|
||||
void ExitedTree();
|
||||
void MouseMove(GUIMouseMoveEventArgs args);
|
||||
void MouseExited();
|
||||
void MouseWheel(GUIMouseWheelEventArgs args);
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace Robust.Client.WebView
|
||||
[Dependency] private readonly IWebViewManagerInternal _webViewManager = default!;
|
||||
|
||||
private readonly IWebViewControlImpl _controlImpl;
|
||||
private bool _alwaysActive;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Url
|
||||
@@ -23,21 +22,6 @@ namespace Robust.Client.WebView
|
||||
set => _controlImpl.Url = value;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool AlwaysActive
|
||||
{
|
||||
get => _alwaysActive;
|
||||
set
|
||||
{
|
||||
_alwaysActive = value;
|
||||
|
||||
if (_alwaysActive && !_controlImpl.IsOpen)
|
||||
_controlImpl.StartBrowser();
|
||||
else if (!_alwaysActive && _controlImpl.IsOpen && !IsInsideTree)
|
||||
_controlImpl.CloseBrowser();
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables] public bool IsLoading => _controlImpl.IsLoading;
|
||||
|
||||
public WebViewControl()
|
||||
@@ -55,16 +39,14 @@ namespace Robust.Client.WebView
|
||||
{
|
||||
base.EnteredTree();
|
||||
|
||||
if (!_controlImpl.IsOpen)
|
||||
_controlImpl.StartBrowser();
|
||||
_controlImpl.EnteredTree();
|
||||
}
|
||||
|
||||
protected override void ExitedTree()
|
||||
{
|
||||
base.ExitedTree();
|
||||
|
||||
if (!_alwaysActive)
|
||||
_controlImpl.CloseBrowser();
|
||||
_controlImpl.ExitedTree();
|
||||
}
|
||||
|
||||
protected internal override void MouseMove(GUIMouseMoveEventArgs args)
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.Maths;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Animations
|
||||
{
|
||||
@@ -120,9 +122,9 @@ namespace Robust.Client.Animations
|
||||
case Vector2 vector2:
|
||||
return Vector2Helpers.InterpolateCubic((Vector2) preA, vector2, (Vector2) b, (Vector2) postB, t);
|
||||
case Vector3 vector3:
|
||||
return VectorHelpers.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
|
||||
return Vector3.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
|
||||
case Vector4 vector4:
|
||||
return VectorHelpers.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
|
||||
return Vector4.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
|
||||
case float f:
|
||||
return MathHelper.InterpolateCubic((float) preA, f, (float) b, (float) postB, t);
|
||||
case double d:
|
||||
|
||||
@@ -57,8 +57,8 @@ internal sealed partial class AudioManager : IAudioInternal
|
||||
_checkAlError();
|
||||
|
||||
// Load up AL context extensions.
|
||||
var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? "";
|
||||
foreach (var extension in s.Split(' ', StringSplitOptions.RemoveEmptyEntries))
|
||||
var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? "";
|
||||
foreach (var extension in s.Split(' '))
|
||||
{
|
||||
_alContextExtensions.Add(extension);
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
LogAudioPlaybackOnInvalidEntity(specifier, entity);
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -626,7 +626,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
LogAudioPlaybackOnInvalidEntity(specifier, coordinates.EntityId);
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -753,12 +753,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
return _resourceCache.GetResource<AudioResource>(filename).AudioStream.Length;
|
||||
}
|
||||
|
||||
private void LogAudioPlaybackOnInvalidEntity(ResolvedSoundSpecifier? specifier, EntityUid entityId)
|
||||
{
|
||||
var soundInfo = specifier?.ToString() ?? "unknown sound";
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entityId)}. Sound: {soundInfo}. Trace: {Environment.StackTrace}");
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
private record struct UpdateAudioJob : IParallelRobustJob
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Effects;
|
||||
|
||||
@@ -6,7 +6,6 @@ using Robust.Shared.Audio.Midi;
|
||||
using Robust.Shared.Audio.Sources;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
@@ -157,13 +156,8 @@ public interface IMidiRenderer : IDisposable
|
||||
/// <summary>
|
||||
/// Loads a new soundfont into the renderer.
|
||||
/// </summary>
|
||||
[Obsolete("Use LoadSoundfontResource or LoadSoundfontUser instead")]
|
||||
void LoadSoundfont(string filename, bool resetPresets = false);
|
||||
|
||||
void LoadSoundfontResource(ResPath path, bool resetPresets = false);
|
||||
|
||||
void LoadSoundfontUser(ResPath path, bool resetPresets = false);
|
||||
|
||||
/// <summary>
|
||||
/// Invoked whenever a new midi event is registered.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using NFluidsynth;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
internal sealed partial class MidiManager
|
||||
{
|
||||
// For loading sound fonts, we have to use a callback model where we can only parse a string.
|
||||
// This API, frankly, fucking sucks.
|
||||
//
|
||||
// These prefixes are used to separate the various places a file *can* be loaded from.
|
||||
//
|
||||
// We cannot prevent Fluidsynth from trying to load prefixed paths itself if they are invalid
|
||||
// So if content specifies "/foobar.sf2" to be loaded and it doesn't exist,
|
||||
// Fluidsynth *will* try to fopen("RES:/foobar.sf2"). For this reason I'm putting in some nonsense characters
|
||||
// that will pass through Fluidsynth fine, but make sure the filename is *never* a practically valid OS path.
|
||||
//
|
||||
// NOTE: Raw disk paths *cannot* be prefixed as Fluidsynth needs to load those itself.
|
||||
// Specifically, their .dls loader doesn't respect file callbacks.
|
||||
// If you're curious why this is: it's two-fold:
|
||||
// * The Fluidsynth C code for the .dls loader just doesn't use the file callbacks, period.
|
||||
// * Even if it did, we're not specifying those file callbacks, as they're per loader,
|
||||
// and we're only adding a *new* sound font loader with file callbacks, not modifying the existing ones.
|
||||
// The loader for .sfX format and .dls format are different loader objects in Fluidsynth.
|
||||
internal const string PrefixCommon = "!/ -?\x0001";
|
||||
internal const string PrefixLegacy = PrefixCommon + "LEGACY";
|
||||
internal const string PrefixUser = PrefixCommon + "USER";
|
||||
internal const string PrefixResources = PrefixCommon + "RES";
|
||||
|
||||
private void LoadSoundFontSetup(MidiRenderer renderer)
|
||||
{
|
||||
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfontResource(FallbackSoundfont);
|
||||
|
||||
// Load system-specific soundfonts.
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
|
||||
renderer.LoadSoundfontDisk(filepath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
|
||||
renderer.LoadSoundfontDisk(OsxSoundfont);
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
|
||||
renderer.LoadSoundfontDisk(WindowsSoundfont);
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe load soundfont specified in environment variable.
|
||||
// Load it here so it can override system soundfonts but not content or user data soundfonts.
|
||||
if (Environment.GetEnvironmentVariable(SoundfontEnvironmentVariable) is { } soundfontOverride)
|
||||
{
|
||||
// Just to avoid funny shit: avoid people smuggling a prefix in here.
|
||||
// I wish I could separate this properly...
|
||||
var (prefix, _) = SplitPrefix(soundfontOverride);
|
||||
if (IsValidPrefix(prefix))
|
||||
{
|
||||
_midiSawmill.Error($"Not respecting {SoundfontEnvironmentVariable} env variable: invalid file path");
|
||||
}
|
||||
else if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
|
||||
renderer.LoadSoundfontDisk(soundfontOverride);
|
||||
}
|
||||
}
|
||||
|
||||
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
|
||||
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
|
||||
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
|
||||
_midiSawmill.Debug($"Loading content soundfont {file}");
|
||||
renderer.LoadSoundfontResource(file);
|
||||
}
|
||||
|
||||
// Load every soundfont from the user data directory last, since those may override any other soundfont.
|
||||
_midiSawmill.Debug($"Loading soundfonts from user data directory {CustomSoundfontDirectory}");
|
||||
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
|
||||
foreach (var file in enumerator)
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
|
||||
_midiSawmill.Debug($"Loading user soundfont {file}");
|
||||
renderer.LoadSoundfontUser(file);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string PrefixPath(string prefix, string value)
|
||||
{
|
||||
return $"{prefix}:{value}";
|
||||
}
|
||||
|
||||
internal static (string prefix, string? value) SplitPrefix(string filename)
|
||||
{
|
||||
var filenameSplit = filename.Split(':', 2);
|
||||
if (filenameSplit.Length == 1)
|
||||
return (filenameSplit[0], null);
|
||||
|
||||
return (filenameSplit[0], filenameSplit[1]);
|
||||
}
|
||||
|
||||
internal static bool IsValidPrefix(string prefix)
|
||||
{
|
||||
return prefix is PrefixLegacy or PrefixUser or PrefixResources;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used to load soundfonts.
|
||||
/// </summary>
|
||||
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
|
||||
{
|
||||
private readonly MidiManager _parent;
|
||||
private readonly Dictionary<int, Stream> _openStreams = new();
|
||||
private int _nextStreamId = 1;
|
||||
|
||||
public ResourceLoaderCallbacks(MidiManager parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public override IntPtr Open(string filename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
Stream stream;
|
||||
try
|
||||
{
|
||||
stream = OpenCore(filename);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_parent._midiSawmill.Error($"Error while opening sound font: {e}");
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var id = _nextStreamId++;
|
||||
|
||||
_openStreams.Add(id, stream);
|
||||
|
||||
return (IntPtr) id;
|
||||
}
|
||||
|
||||
private Stream OpenCore(string filename)
|
||||
{
|
||||
var (prefix, value) = SplitPrefix(filename);
|
||||
|
||||
if (!IsValidPrefix(prefix) || value == null)
|
||||
return File.OpenRead(filename);
|
||||
|
||||
var resourceCache = _parent._resourceManager;
|
||||
var resourcePath = new ResPath(value);
|
||||
|
||||
switch (prefix)
|
||||
{
|
||||
case PrefixUser:
|
||||
return resourceCache.UserData.OpenRead(resourcePath);
|
||||
case PrefixResources:
|
||||
return resourceCache.ContentFileRead(resourcePath);
|
||||
case PrefixLegacy:
|
||||
// Try resources first, then try user data.
|
||||
if (resourceCache.TryContentFileRead(resourcePath, out var stream))
|
||||
return stream;
|
||||
|
||||
return resourceCache.UserData.OpenRead(resourcePath);
|
||||
default:
|
||||
throw new UnreachableException("Invalid prefix specified!");
|
||||
}
|
||||
}
|
||||
|
||||
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
|
||||
{
|
||||
var length = (int) count;
|
||||
var span = new Span<byte>(buf.ToPointer(), length);
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
|
||||
try
|
||||
{
|
||||
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
|
||||
if (count < 1024)
|
||||
{
|
||||
// ReSharper disable once SuggestVarOrType_Elsewhere
|
||||
Span<byte> buffer = stackalloc byte[(int)count];
|
||||
|
||||
stream.ReadExact(buffer);
|
||||
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
else
|
||||
{
|
||||
var buffer = stream.ReadExact(length);
|
||||
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override int Seek(IntPtr sfHandle, long offset, SeekOrigin origin)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
stream.Seek(offset, origin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override long Tell(IntPtr sfHandle)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
return (long) stream.Position;
|
||||
}
|
||||
|
||||
public override int Close(IntPtr sfHandle)
|
||||
{
|
||||
if (!_openStreams.Remove((int) sfHandle, out var stream))
|
||||
return -1;
|
||||
|
||||
stream.Dispose();
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
private const string OsxSoundfont =
|
||||
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
|
||||
|
||||
private static readonly ResPath FallbackSoundfont = new ResPath("/Midi/fallback.sf2");
|
||||
private const string FallbackSoundfont = "/Midi/fallback.sf2";
|
||||
|
||||
private const string ContentCustomSoundfontDirectory = "/Audio/MidiCustom/";
|
||||
|
||||
@@ -265,7 +265,81 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
|
||||
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
|
||||
|
||||
LoadSoundFontSetup(renderer);
|
||||
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
|
||||
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
|
||||
renderer.LoadSoundfont(FallbackSoundfont);
|
||||
|
||||
// Load system-specific soundfonts.
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
foreach (var filepath in LinuxSoundfonts)
|
||||
{
|
||||
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
|
||||
renderer.LoadSoundfont(filepath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
|
||||
renderer.LoadSoundfont(OsxSoundfont);
|
||||
}
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
|
||||
renderer.LoadSoundfont(WindowsSoundfont);
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe load soundfont specified in environment variable.
|
||||
// Load it here so it can override system soundfonts but not content or user data soundfonts.
|
||||
if (Environment.GetEnvironmentVariable(SoundfontEnvironmentVariable) is {} soundfontOverride)
|
||||
{
|
||||
if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
|
||||
{
|
||||
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
|
||||
renderer.LoadSoundfont(soundfontOverride);
|
||||
}
|
||||
}
|
||||
|
||||
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
|
||||
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
|
||||
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
|
||||
_midiSawmill.Debug($"Loading content soundfont {file}");
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
var userDataPath = _resourceManager.UserData.RootDir == null
|
||||
? CustomSoundfontDirectory
|
||||
: new ResPath(_resourceManager.UserData.RootDir) / CustomSoundfontDirectory.ToRelativePath();
|
||||
|
||||
// Load every soundfont from the user data directory last, since those may override any other soundfont.
|
||||
_midiSawmill.Debug($"Loading soundfonts from user data directory {userDataPath}");
|
||||
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
|
||||
foreach (var file in enumerator)
|
||||
{
|
||||
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
|
||||
_midiSawmill.Debug($"Loading user soundfont {file}");
|
||||
renderer.LoadSoundfont(file.ToString());
|
||||
}
|
||||
|
||||
renderer.Source.Gain = _gain;
|
||||
|
||||
@@ -498,6 +572,130 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
midiEvent.Velocity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used to load soundfonts.
|
||||
/// </summary>
|
||||
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
|
||||
{
|
||||
private readonly MidiManager _parent;
|
||||
private readonly Dictionary<int, Stream> _openStreams = new();
|
||||
private int _nextStreamId = 1;
|
||||
|
||||
public ResourceLoaderCallbacks(MidiManager parent)
|
||||
{
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public override IntPtr Open(string filename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
Stream? stream;
|
||||
var resourceCache = _parent._resourceManager;
|
||||
var resourcePath = new ResPath(filename);
|
||||
|
||||
if (resourcePath.IsRooted)
|
||||
{
|
||||
// is it in content?
|
||||
if (resourceCache.ContentFileExists(filename))
|
||||
{
|
||||
if (!resourceCache.TryContentFileRead(filename, out stream))
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
// is it in userdata?
|
||||
else if (resourceCache.UserData.Exists(resourcePath))
|
||||
{
|
||||
stream = resourceCache.UserData.OpenRead(resourcePath);
|
||||
}
|
||||
else if (File.Exists(filename))
|
||||
{
|
||||
stream = File.OpenRead(filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
else if (File.Exists(filename))
|
||||
{
|
||||
stream = File.OpenRead(filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
var id = _nextStreamId++;
|
||||
|
||||
_openStreams.Add(id, stream);
|
||||
|
||||
return (IntPtr) id;
|
||||
}
|
||||
|
||||
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
|
||||
{
|
||||
var length = (int) count;
|
||||
var span = new Span<byte>(buf.ToPointer(), length);
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
|
||||
try
|
||||
{
|
||||
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
|
||||
if (count < 1024)
|
||||
{
|
||||
// ReSharper disable once SuggestVarOrType_Elsewhere
|
||||
Span<byte> buffer = stackalloc byte[(int)count];
|
||||
|
||||
stream.ReadExact(buffer);
|
||||
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
else
|
||||
{
|
||||
var buffer = stream.ReadExact(length);
|
||||
|
||||
buffer.CopyTo(span);
|
||||
}
|
||||
}
|
||||
catch (EndOfStreamException)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override int Seek(IntPtr sfHandle, long offset, SeekOrigin origin)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
stream.Seek(offset, origin);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override long Tell(IntPtr sfHandle)
|
||||
{
|
||||
var stream = _openStreams[(int) sfHandle];
|
||||
|
||||
return (long) stream.Position;
|
||||
}
|
||||
|
||||
public override int Close(IntPtr sfHandle)
|
||||
{
|
||||
if (!_openStreams.Remove((int) sfHandle, out var stream))
|
||||
return -1;
|
||||
|
||||
stream.Dispose();
|
||||
return 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#region Jobs
|
||||
|
||||
private record struct MidiUpdateJob : IParallelRobustJob
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
internal sealed partial class MidiRenderer
|
||||
{
|
||||
[Obsolete("Use LoadSoundfontResource or LoadSoundfontUser instead")]
|
||||
public void LoadSoundfont(string filename, bool resetPresets = true)
|
||||
{
|
||||
LoadSoundfontCore(
|
||||
MidiManager.PrefixPath(MidiManager.PrefixLegacy, filename),
|
||||
resetPresets);
|
||||
}
|
||||
|
||||
public void LoadSoundfontResource(ResPath path, bool resetPresets = false)
|
||||
{
|
||||
LoadSoundfontCore(
|
||||
MidiManager.PrefixPath(MidiManager.PrefixResources, path.ToString()),
|
||||
resetPresets);
|
||||
}
|
||||
|
||||
public void LoadSoundfontUser(ResPath path, bool resetPresets = false)
|
||||
{
|
||||
LoadSoundfontCore(
|
||||
MidiManager.PrefixPath(MidiManager.PrefixUser, path.ToString()),
|
||||
resetPresets);
|
||||
}
|
||||
|
||||
internal void LoadSoundfontDisk(string path, bool resetPresets = false)
|
||||
{
|
||||
LoadSoundfontCore(
|
||||
path,
|
||||
resetPresets);
|
||||
}
|
||||
|
||||
private void LoadSoundfontCore(string filenameString, bool resetPresets)
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_synth.LoadSoundFont(filenameString, resetPresets);
|
||||
MidiSoundfont = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.Audio.Midi;
|
||||
|
||||
internal sealed partial class MidiRenderer : IMidiRenderer
|
||||
internal sealed class MidiRenderer : IMidiRenderer
|
||||
{
|
||||
private readonly IMidiManager _midiManager;
|
||||
private readonly ITaskManager _taskManager;
|
||||
@@ -435,6 +435,15 @@ internal sealed partial class MidiRenderer : IMidiRenderer
|
||||
_sequencer.RemoveEvents(SequencerClientId.Wildcard, SequencerClientId.Wildcard, -1);
|
||||
}
|
||||
|
||||
public void LoadSoundfont(string filename, bool resetPresets = true)
|
||||
{
|
||||
lock (_playerStateLock)
|
||||
{
|
||||
_synth.LoadSoundFont(filename, resetPresets);
|
||||
MidiSoundfont = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void IMidiRenderer.Render()
|
||||
{
|
||||
Render();
|
||||
@@ -575,28 +584,18 @@ internal sealed partial class MidiRenderer : IMidiRenderer
|
||||
case RobustMidiCommand.NoteOff:
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
|
||||
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
|
||||
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.NoteOn:
|
||||
// Velocity 0 *can* represent a NoteOff event.
|
||||
var velocity = midiEvent.Velocity;
|
||||
if (velocity == 0)
|
||||
{
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (FilteredChannels[midiEvent.Channel])
|
||||
break;
|
||||
|
||||
velocity = VelocityOverride ?? midiEvent.Velocity;
|
||||
var velocity = VelocityOverride ?? midiEvent.Velocity;
|
||||
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
|
||||
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
|
||||
|
||||
break;
|
||||
|
||||
case RobustMidiCommand.AfterTouch:
|
||||
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = midiEvent.Value;
|
||||
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);
|
||||
|
||||
@@ -144,7 +144,6 @@ namespace Robust.Client
|
||||
deps.Register<IViewVariablesManager, ClientViewVariablesManager>();
|
||||
deps.Register<IClientViewVariablesManager, ClientViewVariablesManager>();
|
||||
deps.Register<IClientViewVariablesManagerInternal, ClientViewVariablesManager>();
|
||||
deps.Register<IViewVariableControlFactory, ViewVariableControlFactory>();
|
||||
deps.Register<IClientConGroupController, ClientConGroupController>();
|
||||
deps.Register<IScriptClient, ScriptClient>();
|
||||
deps.Register<IRobustSerializer, ClientRobustSerializer>();
|
||||
|
||||
@@ -21,7 +21,7 @@ public sealed class SpriteTreeSystem : ComponentTreeSystem<SpriteTreeComponent,
|
||||
protected override Box2 ExtractAabb(in ComponentTreeEntry<SpriteComponent> entry, Vector2 pos, Angle rot)
|
||||
{
|
||||
// TODO SPRITE optimize this
|
||||
// Because the just take the BB of the rotated BB, I'm pretty sure we do a lot of unnecessary maths.
|
||||
// Because the just take the BB of the rotated BB, I'mt pretty sure we do a lot of unnecessary maths.
|
||||
return _sprite.CalculateBounds((entry.Uid, entry.Component), pos, rot, default).CalcBoundingBox();
|
||||
}
|
||||
|
||||
|
||||
@@ -191,16 +191,8 @@ namespace Robust.Client.Console
|
||||
var shell = new ConsoleShell(this, session ?? _player.LocalSession, session == null);
|
||||
var cmdArgs = args.ToArray();
|
||||
|
||||
try
|
||||
{
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
cmd.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_conLogger.Error($"ExecuteError - {command}:\n{e}");
|
||||
shell.WriteError($"There was an error while executing the command: {e}");
|
||||
}
|
||||
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
|
||||
cmd.Execute(shell, command, cmdArgs);
|
||||
}
|
||||
|
||||
private bool CanExecute(string cmdName)
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Robust.Client.Console
|
||||
MouseFilter = MouseFilterMode.Stop;
|
||||
Result = result;
|
||||
var compl = new FormattedMessage();
|
||||
var dim = Color.FromHsl(new Vector4(0f, 0f, 0.8f, 1f));
|
||||
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
|
||||
|
||||
// warning: ew ahead
|
||||
string basen = "default";
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid, grid, spot))
|
||||
{
|
||||
if (TryComp(ent, out MetaDataComponent? meta))
|
||||
if (EntityManager.TryGetComponent<MetaDataComponent>(ent, out var meta))
|
||||
{
|
||||
text.AppendLine($"uid: {ent}, {meta.EntityName}");
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ using Robust.Shared.ViewVariables;
|
||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
using SysVec4 = System.Numerics.Vector4;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -1222,8 +1224,6 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
_visible = value;
|
||||
|
||||
Owner.Comp.BoundsDirty = true;
|
||||
|
||||
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
|
||||
if (_parent.Owner != EntityUid.Invalid)
|
||||
Owner.Comp.Sys?.QueueUpdateIsInert(Owner);
|
||||
@@ -1791,15 +1791,76 @@ namespace Robust.Client.GameObjects
|
||||
[Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")]
|
||||
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
|
||||
{
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
return sys.GetPrototypeTextures(prototype, out noRot);
|
||||
var results = new List<IDirectionalTextureProvider>();
|
||||
noRot = false;
|
||||
|
||||
// TODO when moving to a non-static method in a system, pass in IComponentFactory
|
||||
if (prototype.TryGetComponent(out IconComponent? icon))
|
||||
{
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
results.Add(sys.GetIcon(icon));
|
||||
return results;
|
||||
}
|
||||
|
||||
if (!prototype.Components.TryGetValue("Sprite", out _))
|
||||
{
|
||||
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
|
||||
return results;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
EntitySystem.Get<AppearanceSystem>().OnChangeData(dummy, spriteComponent);
|
||||
|
||||
foreach (var layer in spriteComponent.AllLayers)
|
||||
{
|
||||
if (!layer.Visible) continue;
|
||||
|
||||
if (layer.Texture != null)
|
||||
{
|
||||
results.Add(layer.Texture);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!layer.RsiState.IsValid) continue;
|
||||
|
||||
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
|
||||
if (rsi == null ||
|
||||
!rsi.TryGetState(layer.RsiState, out var state))
|
||||
continue;
|
||||
|
||||
results.Add(state);
|
||||
}
|
||||
|
||||
noRot = spriteComponent.NoRotation;
|
||||
|
||||
entityManager.DeleteEntity(dummy);
|
||||
|
||||
if (results.Count == 0)
|
||||
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[Obsolete("Use SpriteSystem.GetPrototypeIcon() instead")]
|
||||
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
|
||||
{
|
||||
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
|
||||
return sys.GetPrototypeIcon(prototype);
|
||||
// TODO when moving to a non-static method in a system, pass in IComponentFactory
|
||||
if (prototype.TryGetComponent(out IconComponent? icon))
|
||||
return sys.GetIcon(icon);
|
||||
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
return sys.GetFallbackState();
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
|
||||
var result = spriteComponent.Icon ?? sys.GetFallbackState();
|
||||
entityManager.DeleteEntity(dummy);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace Robust.Client.GameObjects
|
||||
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
|
||||
public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
|
||||
{
|
||||
component ??= EnsureComp<AnimationPlayerComponent>(uid);
|
||||
component ??= EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
|
||||
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
public bool HasRunningAnimation(EntityUid uid, string key)
|
||||
{
|
||||
return TryComp(uid, out AnimationPlayerComponent? component) &&
|
||||
return EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? component) &&
|
||||
component.PlayingAnimations.ContainsKey(key);
|
||||
}
|
||||
|
||||
|
||||
@@ -223,12 +223,12 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
private void SetEntityContextActive(IInputManager inputMan, EntityUid entity)
|
||||
{
|
||||
if(entity == default || !Exists(entity))
|
||||
if(entity == default || !EntityManager.EntityExists(entity))
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
|
||||
if (!TryComp(entity, out InputComponent? inputComp))
|
||||
if (!EntityManager.TryGetComponent(entity, out InputComponent? inputComp))
|
||||
{
|
||||
_sawmillInputContext.Debug($"AttachedEnt has no InputComponent: entId={entity}, entProto={Comp<MetaDataComponent>(entity).EntityPrototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
_sawmillInputContext.Debug($"AttachedEnt has no InputComponent: entId={entity}, entProto={EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
|
||||
return;
|
||||
}
|
||||
@@ -239,7 +239,7 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
else
|
||||
{
|
||||
_sawmillInputContext.Error($"Unknown context: entId={entity}, entProto={Comp<MetaDataComponent>(entity).EntityPrototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
_sawmillInputContext.Error($"Unknown context: entId={entity}, entProto={EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
|
||||
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class ShowPlayerVelocityDebugSystem : EntitySystem
|
||||
|
||||
var player = _playerManager.LocalEntity;
|
||||
|
||||
if (player == null || !TryComp(player.Value, out PhysicsComponent? body))
|
||||
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
|
||||
{
|
||||
_label.Visible = false;
|
||||
return;
|
||||
|
||||
@@ -32,7 +32,7 @@ public sealed partial class SpriteSystem
|
||||
bounds = bounds.Union(GetLocalBounds(layer));
|
||||
}
|
||||
|
||||
sprite.Comp._bounds = bounds.Scale(sprite.Comp.Scale);
|
||||
sprite.Comp._bounds = bounds;
|
||||
sprite.Comp.BoundsDirty = false;
|
||||
return sprite.Comp._bounds;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ public sealed partial class SpriteSystem
|
||||
/// </summary>
|
||||
public IRsiStateLike GetPrototypeIcon(string prototype)
|
||||
{
|
||||
// Check if this prototype has been cached before, and if so return the result.
|
||||
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
|
||||
return cachedResult;
|
||||
|
||||
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
|
||||
{
|
||||
// The specified prototype doesn't exist, return the fallback "error" sprite.
|
||||
@@ -63,7 +67,11 @@ public sealed partial class SpriteSystem
|
||||
return GetFallbackState();
|
||||
}
|
||||
|
||||
return GetPrototypeIcon(entityPrototype);
|
||||
// Generate the icon and cache it in case it's ever needed again.
|
||||
var result = GetPrototypeIcon(entityPrototype);
|
||||
_cachedPrototypeIcons[prototype] = result;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,19 +79,13 @@ public sealed partial class SpriteSystem
|
||||
/// This method does NOT cache the result.
|
||||
/// </summary>
|
||||
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype)
|
||||
{
|
||||
// This method may spawn & delete an entity to get an accruate RSI state, hence we cache the results
|
||||
if (_cachedPrototypeIcons.TryGetValue(prototype.ID, out var cachedResult))
|
||||
return cachedResult;
|
||||
|
||||
return _cachedPrototypeIcons[prototype.ID] = GetPrototypeIconInternal(prototype);
|
||||
}
|
||||
|
||||
private IRsiStateLike GetPrototypeIconInternal(EntityPrototype prototype)
|
||||
{
|
||||
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
|
||||
if (prototype.TryGetComponent(out IconComponent? icon, _factory))
|
||||
if (prototype.Components.TryGetValue("Icon", out var compData)
|
||||
&& compData.Component is IconComponent icon)
|
||||
{
|
||||
return GetIcon(icon);
|
||||
}
|
||||
|
||||
// If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback.
|
||||
if (!prototype.Components.ContainsKey("Sprite"))
|
||||
@@ -100,63 +102,6 @@ public sealed partial class SpriteSystem
|
||||
return result;
|
||||
}
|
||||
|
||||
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto) =>
|
||||
GetPrototypeTextures(proto, out _);
|
||||
|
||||
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto, out bool noRot)
|
||||
{
|
||||
var results = new List<IDirectionalTextureProvider>();
|
||||
noRot = false;
|
||||
|
||||
if (proto.TryGetComponent(out IconComponent? icon, _factory))
|
||||
{
|
||||
results.Add(GetIcon(icon));
|
||||
return results;
|
||||
}
|
||||
|
||||
if (!proto.Components.ContainsKey("Sprite"))
|
||||
{
|
||||
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
|
||||
return results;
|
||||
}
|
||||
|
||||
var dummy = Spawn(proto.ID, MapCoordinates.Nullspace);
|
||||
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
|
||||
|
||||
// TODO SPRITE is this needed?
|
||||
// And if it is, shouldn't GetPrototypeIconInternal also use this?
|
||||
_appearance.OnChangeData(dummy, spriteComponent);
|
||||
|
||||
foreach (var layer in spriteComponent.AllLayers)
|
||||
{
|
||||
if (!layer.Visible)
|
||||
continue;
|
||||
|
||||
if (layer.Texture != null)
|
||||
{
|
||||
results.Add(layer.Texture);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!layer.RsiState.IsValid)
|
||||
continue;
|
||||
|
||||
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
|
||||
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
|
||||
continue;
|
||||
|
||||
results.Add(state);
|
||||
}
|
||||
|
||||
noRot = spriteComponent.NoRotation;
|
||||
Del(dummy);
|
||||
|
||||
if (results.Count == 0)
|
||||
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public RSI.State GetFallbackState()
|
||||
{
|
||||
|
||||
@@ -7,6 +7,8 @@ using Robust.Shared.Graphics.RSI;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Client.GameObjects.SpriteComponent;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
using SysVec4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
@@ -155,7 +157,7 @@ public sealed partial class SpriteSystem
|
||||
// Negative color modulation values are by the default shader to disable light shading.
|
||||
// Specifically we set colour = - 1 - colour
|
||||
// This is good enough to ensure that non-negative values become negative & is trivially invertible.
|
||||
layerColor = new(new Vector4(-1) - layerColor.RGBA);
|
||||
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
|
||||
}
|
||||
|
||||
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);
|
||||
|
||||
@@ -34,12 +34,8 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IComponentFactory _factory = default!;
|
||||
|
||||
// Note that any new system dependencies have to be added to RobustUnitTest.BaseSetup()
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
[Dependency] private readonly SpriteTreeSystem _tree = default!;
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
|
||||
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
@@ -631,7 +631,7 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
|
||||
|
||||
if ((meta.Flags & MetaDataFlags.Detached) == 0 && compState != null)
|
||||
if (compState != null)
|
||||
{
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(entity, comp, ref handleState);
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
internal sealed class NetInterpOverlay : Overlay
|
||||
{
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -34,7 +32,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_lookup = lookup;
|
||||
_shader = _prototypeManager.Index(UnshadedShader).Instance();
|
||||
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_container = _entityManager.System<SharedContainerSystem>();
|
||||
_xform = _entityManager.System<SharedTransformSystem>();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
@@ -255,9 +254,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
|
||||
var rotationMirroring = (_tileDefinitionManager.TryGetDefinition(tile.TypeId, out var tileDef) && tileDef.AllowRotationMirror) ?
|
||||
tile.RotationMirroring
|
||||
: 0;
|
||||
var rotationMirroring = _tileDefinitionManager[tile.TypeId].AllowRotationMirror
|
||||
? tile.RotationMirroring
|
||||
: 0;
|
||||
|
||||
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region, rotationMirroring);
|
||||
i += 1;
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Maths;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ using Robust.Shared.Graphics;
|
||||
using static Robust.Shared.GameObjects.OccluderComponent;
|
||||
using Robust.Shared.Utility;
|
||||
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
|
||||
@@ -11,6 +11,8 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -539,7 +541,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case Matrix3x2 matrix3:
|
||||
program.SetUniform(name, matrix3);
|
||||
break;
|
||||
case Matrix4x4 matrix4:
|
||||
case Matrix4 matrix4:
|
||||
program.SetUniform(name, matrix4);
|
||||
break;
|
||||
case ClydeTexture clydeTexture:
|
||||
|
||||
@@ -10,6 +10,8 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -526,7 +528,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
data.Parameters[name] = value;
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
|
||||
private protected override void SetParameterImpl(string name, in Matrix4 value)
|
||||
{
|
||||
var data = Parent._shaderInstances[Handle];
|
||||
data.ParametersDirty = true;
|
||||
|
||||
@@ -17,6 +17,8 @@ using Robust.Shared.Timing;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Color = Robust.Shared.Maths.Color;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -396,7 +398,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
}
|
||||
|
||||
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
|
||||
private protected override void SetParameterImpl(string name, in Matrix4 value)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ using System.Runtime.CompilerServices;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -275,20 +277,20 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_clyde.CheckGlError();
|
||||
}
|
||||
|
||||
public void SetUniform(string uniformName, in Matrix4x4 matrix, bool transpose=true)
|
||||
public void SetUniform(string uniformName, in Matrix4 matrix, bool transpose=true)
|
||||
{
|
||||
var uniformId = GetUniform(uniformName);
|
||||
SetUniformDirect(uniformId, matrix, transpose);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe void SetUniformDirect(int uniformId, in Matrix4x4 value, bool transpose=true)
|
||||
private unsafe void SetUniformDirect(int uniformId, in Matrix4 value, bool transpose=true)
|
||||
{
|
||||
Matrix4x4 tmpTranspose = value;
|
||||
Matrix4 tmpTranspose = value;
|
||||
if (transpose)
|
||||
{
|
||||
// transposition not supported on GLES2, & no access to _hasGLES
|
||||
tmpTranspose = Matrix4x4.Transpose(value);
|
||||
tmpTranspose.Transpose();
|
||||
}
|
||||
GL.UniformMatrix4(uniformId, 1, false, (float*) &tmpTranspose);
|
||||
_clyde.CheckGlError();
|
||||
@@ -549,7 +551,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
public void SetUniformMaybe(string uniformName, in Matrix4x4 value, bool transpose=true)
|
||||
public void SetUniformMaybe(string uniformName, in Matrix4 value, bool transpose=true)
|
||||
{
|
||||
if (TryGetUniform(uniformName, out var slot))
|
||||
{
|
||||
|
||||
@@ -662,10 +662,6 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var icons = _clyde.LoadWindowIcons().ToArray();
|
||||
|
||||
// Done if no icon (e.g., macOS)
|
||||
if (icons.Length == 0)
|
||||
return;
|
||||
|
||||
// Turn each image into a byte[] so we can actually pin their contents.
|
||||
// Wish I knew a clean way to do this without allocations.
|
||||
var images = icons
|
||||
|
||||
@@ -4,6 +4,8 @@ using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -167,7 +169,7 @@ namespace Robust.Client.Graphics
|
||||
SetParameterImpl(name, value);
|
||||
}
|
||||
|
||||
public void SetParameter(string name, in Matrix4x4 value)
|
||||
public void SetParameter(string name, in Matrix4 value)
|
||||
{
|
||||
EnsureAlive();
|
||||
EnsureMutable();
|
||||
@@ -234,7 +236,7 @@ namespace Robust.Client.Graphics
|
||||
private protected abstract void SetParameterImpl(string name, bool value);
|
||||
private protected abstract void SetParameterImpl(string name, bool[] value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix3x2 value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix4x4 value);
|
||||
private protected abstract void SetParameterImpl(string name, in Matrix4 value);
|
||||
private protected abstract void SetParameterImpl(string name, Texture value);
|
||||
private protected abstract void SetStencilImpl(StencilParameters value);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Vector3 = Robust.Shared.Maths.Vector3;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
@@ -220,7 +222,7 @@ namespace Robust.Client.Graphics
|
||||
case Matrix3x2 i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
case Matrix4x4 i:
|
||||
case Matrix4 i:
|
||||
instance.SetParameter(key, i);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Robust.Client.Physics
|
||||
|
||||
// Add new joint (if possible).
|
||||
// Need to wait for BOTH joint components to come in first before we can add it. Yay dependencies!
|
||||
if (!HasComp<JointComponent>(other))
|
||||
if (!EntityManager.HasComponent<JointComponent>(other))
|
||||
continue;
|
||||
|
||||
// TODO: if (other entity is outside of PVS range) continue;
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
@@ -21,10 +18,6 @@ namespace Robust.Client.Placement
|
||||
PlacementMode? CurrentMode { get; set; }
|
||||
PlacementInformation? CurrentPermission { get; set; }
|
||||
|
||||
IEntityManager EntityManager { get; }
|
||||
IEyeManager EyeManager { get; }
|
||||
IMapManager MapManager { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The direction to spawn the entity in (presently exposed for EntitySpawnWindow UI)
|
||||
/// </summary>
|
||||
@@ -56,15 +49,5 @@ namespace Robust.Client.Placement
|
||||
void ToggleEraserHijacked(PlacementHijack hijack);
|
||||
|
||||
void FrameUpdate(FrameEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// The name of the placement mode option to just use the default for the selected entity.
|
||||
/// </summary>
|
||||
const string DefaultModeName = "Default";
|
||||
|
||||
/// <summary>
|
||||
/// An array of the names of all available placement modes.
|
||||
/// </summary>
|
||||
string[] AllModeNames { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Map;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
|
||||
@@ -22,7 +22,6 @@ using Robust.Shared.Utility;
|
||||
using Robust.Shared.Log;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Robust.Shared.Map.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Robust.Client.Placement
|
||||
{
|
||||
@@ -33,23 +32,17 @@ namespace Robust.Client.Placement
|
||||
[Dependency] internal readonly IPlayerManager PlayerManager = default!;
|
||||
[Dependency] internal readonly IResourceCache ResourceCache = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] internal readonly IMapManager MapManager = default!;
|
||||
[Dependency] private readonly IGameTiming _time = default!;
|
||||
[Dependency] private readonly IEyeManager _eyeManager = default!;
|
||||
[Dependency] internal readonly IEyeManager EyeManager = default!;
|
||||
[Dependency] internal readonly IInputManager InputManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] internal readonly IEntityManager EntityManager = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IBaseClient _baseClient = default!;
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] internal readonly IClyde Clyde = default!;
|
||||
|
||||
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
|
||||
|
||||
public IEntityManager EntityManager => _entityManager;
|
||||
public IEyeManager EyeManager => _eyeManager;
|
||||
public IMapManager MapManager => _mapManager;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private SharedMapSystem Maps => EntityManager.System<SharedMapSystem>();
|
||||
@@ -205,15 +198,6 @@ namespace Robust.Client.Placement
|
||||
}
|
||||
}
|
||||
|
||||
private string[]? _allModeNames;
|
||||
public string[] AllModeNames
|
||||
{
|
||||
get
|
||||
{
|
||||
return _allModeNames ??= [IPlacementManager.DefaultModeName, .. _modeDictionary.Keys.Order()];
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public event EventHandler? DirectionChanged;
|
||||
|
||||
@@ -225,7 +209,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_drawingShader = _prototypeManager.Index(UnshadedShader).Instance();
|
||||
_drawingShader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
|
||||
_sawmill = _logManager.GetSawmill("placement");
|
||||
|
||||
_networkManager.RegisterNetMessage<MsgPlacement>(HandlePlacementMessage);
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace Robust.Client.ResourceManagement
|
||||
|
||||
public override void Load(IDependencyCollection dependencies, ResPath path)
|
||||
{
|
||||
if (IsInRsi(path))
|
||||
if (path.Directory.Filename.EndsWith(".rsi"))
|
||||
{
|
||||
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("res");
|
||||
sawmill.Warning(
|
||||
Logger.WarningS(
|
||||
"res",
|
||||
"Loading raw texture inside RSI: {Path}. Refer to the RSI state instead of the raw PNG.",
|
||||
path);
|
||||
}
|
||||
@@ -38,15 +38,6 @@ namespace Robust.Client.ResourceManagement
|
||||
LoadFinish(dependencies.Resolve<IResourceCache>(), data);
|
||||
}
|
||||
|
||||
private static bool IsInRsi(ResPath path)
|
||||
{
|
||||
var dir = path.Directory;
|
||||
if (dir == ResPath.Root)
|
||||
return false;
|
||||
|
||||
return dir.Filename.EndsWith(".rsi");
|
||||
}
|
||||
|
||||
internal static void LoadPreTextureData(IResourceManager cache, LoadStepData data)
|
||||
{
|
||||
using (var stream = cache.ContentFileRead(data.Path))
|
||||
|
||||
@@ -151,7 +151,7 @@ public sealed class EntitySpawningUIController : UIController
|
||||
{
|
||||
var newObjInfo = new PlacementInformation
|
||||
{
|
||||
PlacementOption = _placement.AllModeNames[args.Id],
|
||||
PlacementOption = EntitySpawnWindow.InitOpts[args.Id],
|
||||
EntityType = _placement.CurrentPermission!.EntityType,
|
||||
Range = 2,
|
||||
IsTile = _placement.CurrentPermission.IsTile
|
||||
@@ -364,11 +364,10 @@ public sealed class EntitySpawningUIController : UIController
|
||||
_window.SelectedButton = null;
|
||||
_window.SelectedPrototype = null;
|
||||
|
||||
|
||||
var overrideMode = _placement.AllModeNames[_window.OverrideMenu.SelectedId];
|
||||
var overrideMode = EntitySpawnWindow.InitOpts[_window.OverrideMenu.SelectedId];
|
||||
var newObjInfo = new PlacementInformation
|
||||
{
|
||||
PlacementOption = overrideMode != IPlacementManager.DefaultModeName ? overrideMode : item.Prototype.PlacementMode,
|
||||
PlacementOption = overrideMode != "Default" ? overrideMode : item.Prototype.PlacementMode,
|
||||
EntityType = item.PrototypeID,
|
||||
Range = 2,
|
||||
IsTile = false
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.ColorNaming;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
@@ -11,17 +8,22 @@ namespace Robust.Client.UserInterface.Controls;
|
||||
// condensed version of the original ColorSlider set
|
||||
public sealed class ColorSelectorSliders : Control
|
||||
{
|
||||
[Dependency] private readonly ILocalizationManager _localization = default!;
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get => _currentColor;
|
||||
set
|
||||
{
|
||||
_currentColor = value;
|
||||
_colorData = GetStrategy().ToColorData(value);
|
||||
|
||||
UpdateAllSliders();
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
_colorData = Color.ToHsv(value);
|
||||
break;
|
||||
}
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +32,18 @@ public sealed class ColorSelectorSliders : Control
|
||||
get => _currentType;
|
||||
set
|
||||
{
|
||||
switch ((_currentType, value))
|
||||
{
|
||||
case (ColorSelectorType.Rgb, ColorSelectorType.Hsv):
|
||||
_colorData = Color.ToHsv(Color);
|
||||
break;
|
||||
case (ColorSelectorType.Hsv, ColorSelectorType.Rgb):
|
||||
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
|
||||
break;
|
||||
}
|
||||
_currentType = value;
|
||||
_typeSelector.Select(_types.IndexOf(value));
|
||||
_colorData = GetStrategy().ToColorData(_currentColor);
|
||||
|
||||
UpdateType();
|
||||
UpdateAllSliders();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +60,7 @@ public sealed class ColorSelectorSliders : Control
|
||||
|
||||
public Action<Color>? OnColorChanged;
|
||||
|
||||
private readonly static HsvSliderStrategy _hsvStrategy = new();
|
||||
private readonly static RgbSliderStrategy _rgbStrategy = new();
|
||||
|
||||
private const float AlphaDivisor = 100.0f;
|
||||
|
||||
private bool _updating = false;
|
||||
private Color _currentColor = Color.White;
|
||||
private Vector4 _colorData;
|
||||
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
|
||||
@@ -78,7 +82,6 @@ public sealed class ColorSelectorSliders : Control
|
||||
private Label _middleSliderLabel = new();
|
||||
private Label _bottomSliderLabel = new();
|
||||
private Label _alphaSliderLabel = new();
|
||||
private Label _colorDescriptionLabel = new();
|
||||
|
||||
private OptionButton _typeSelector;
|
||||
private List<ColorSelectorType> _types = new();
|
||||
@@ -89,8 +92,6 @@ public sealed class ColorSelectorSliders : Control
|
||||
|
||||
public ColorSelectorSliders()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
_topColorSlider = new ColorableSlider
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
@@ -122,10 +123,10 @@ public sealed class ColorSelectorSliders : Control
|
||||
MaxValue = 1.0f,
|
||||
};
|
||||
|
||||
_topColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Top); };
|
||||
_middleColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Middle); };
|
||||
_bottomColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Bottom); };
|
||||
_alphaSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Alpha); };
|
||||
_topColorSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
_middleColorSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
_bottomColorSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
_alphaSlider.OnValueChanged += _ => { OnColorSet(); };
|
||||
|
||||
_topInputBox = new SpinBox
|
||||
{
|
||||
@@ -151,10 +152,25 @@ public sealed class ColorSelectorSliders : Control
|
||||
};
|
||||
_alphaInputBox.InitDefaultButtons();
|
||||
|
||||
_topInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Top); };
|
||||
_middleInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Middle); };
|
||||
_bottomInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Bottom); };
|
||||
_alphaInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Alpha); };
|
||||
_topInputBox.ValueChanged += value =>
|
||||
{
|
||||
_topColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Top);
|
||||
};
|
||||
|
||||
_middleInputBox.ValueChanged += value =>
|
||||
{
|
||||
_middleColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Middle);
|
||||
};
|
||||
|
||||
_bottomInputBox.ValueChanged += value =>
|
||||
{
|
||||
_bottomColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Bottom);
|
||||
};
|
||||
|
||||
_alphaInputBox.ValueChanged += value =>
|
||||
{
|
||||
_alphaSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Alpha);
|
||||
};
|
||||
|
||||
_alphaSliderLabel.Text = Loc.GetString("color-selector-sliders-alpha");
|
||||
|
||||
@@ -171,8 +187,6 @@ public sealed class ColorSelectorSliders : Control
|
||||
_typeSelector.Select(args.Id);
|
||||
};
|
||||
|
||||
_colorDescriptionLabel.Text = ColorNaming.Describe(_currentColor, _localization);
|
||||
|
||||
// TODO: Maybe some engine widgets could be laid out in XAML?
|
||||
|
||||
var rootBox = new BoxContainer
|
||||
@@ -185,7 +199,6 @@ public sealed class ColorSelectorSliders : Control
|
||||
rootBox.AddChild(headerBox);
|
||||
|
||||
headerBox.AddChild(_typeSelector);
|
||||
headerBox.AddChild(_colorDescriptionLabel);
|
||||
|
||||
var bodyBox = new BoxContainer()
|
||||
{
|
||||
@@ -227,114 +240,165 @@ public sealed class ColorSelectorSliders : Control
|
||||
Color = _currentColor;
|
||||
}
|
||||
|
||||
private ColorSliderStrategy GetStrategy()
|
||||
{
|
||||
return SelectorType switch
|
||||
{
|
||||
ColorSelectorType.Rgb => _rgbStrategy,
|
||||
ColorSelectorType.Hsv => _hsvStrategy,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
private (Slider slider, SpinBox inputBox) GetSliderByOrder(ColorSliderOrder order)
|
||||
{
|
||||
return order switch
|
||||
{
|
||||
ColorSliderOrder.Top => (_topColorSlider, _topInputBox),
|
||||
ColorSliderOrder.Middle => (_middleColorSlider, _middleInputBox),
|
||||
ColorSliderOrder.Bottom => (_bottomColorSlider, _bottomInputBox),
|
||||
ColorSliderOrder.Alpha => (_alphaSlider, _alphaInputBox),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
private float GetColorValueDivisor(ColorSliderOrder order)
|
||||
{
|
||||
return order == ColorSliderOrder.Alpha
|
||||
? AlphaDivisor
|
||||
: GetStrategy().GetColorValueDivisor(order);
|
||||
}
|
||||
|
||||
private void UpdateType()
|
||||
{
|
||||
var strategy = GetStrategy();
|
||||
var labels = strategy.GetSliderLabelTexts();
|
||||
_topSliderLabel.Text = labels.top;
|
||||
_middleSliderLabel.Text = labels.middle;
|
||||
_bottomSliderLabel.Text = labels.bottom;
|
||||
(string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels();
|
||||
|
||||
_topStyle.ConfigureSlider(strategy.TopSliderStyle);
|
||||
_middleStyle.ConfigureSlider(strategy.MiddleSliderStyle);
|
||||
_bottomStyle.ConfigureSlider(strategy.BottomSliderStyle);
|
||||
_topSliderLabel.Text = labels.topLabel;
|
||||
_middleSliderLabel.Text = labels.middleLabel;
|
||||
_bottomSliderLabel.Text = labels.bottomLabel;
|
||||
|
||||
bool hsv = SelectorType == ColorSelectorType.Hsv;
|
||||
_topStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Hue : ColorSelectorStyleBox.ColorSliderPreset.Red);
|
||||
_middleStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Saturation : ColorSelectorStyleBox.ColorSliderPreset.Green);
|
||||
_bottomStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Value : ColorSelectorStyleBox.ColorSliderPreset.Blue);
|
||||
}
|
||||
|
||||
private void UpdateSlider(ColorSliderOrder order)
|
||||
private void Update()
|
||||
{
|
||||
var (slider, inputBox) = GetSliderByOrder(order);
|
||||
var divisor = GetColorValueDivisor(order);
|
||||
// This code is a mess of UI events causing stack overflows. Also, updating one slider triggers all sliders to
|
||||
// update, which due to rounding errors causes them to actually change values, specifically for HSV sliders.
|
||||
if (_updating)
|
||||
return;
|
||||
|
||||
var dataValue = order switch
|
||||
{
|
||||
ColorSliderOrder.Top => _colorData.X,
|
||||
ColorSliderOrder.Middle => _colorData.Y,
|
||||
ColorSliderOrder.Bottom => _colorData.Z,
|
||||
ColorSliderOrder.Alpha => _colorData.W,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(order))
|
||||
};
|
||||
|
||||
slider.SetValueWithoutEvent(dataValue);
|
||||
inputBox.OverrideValue((int)(dataValue * divisor));
|
||||
}
|
||||
|
||||
private void UpdateSliderVisuals()
|
||||
{
|
||||
_updating = true;
|
||||
_topStyle.SetBaseColor(_colorData);
|
||||
_middleStyle.SetBaseColor(_colorData);
|
||||
_bottomStyle.SetBaseColor(_colorData);
|
||||
_colorDescriptionLabel.Text = ColorNaming.Describe(Color, _localization);
|
||||
}
|
||||
|
||||
private void UpdateAllSliders()
|
||||
{
|
||||
UpdateSliderVisuals();
|
||||
UpdateSlider(ColorSliderOrder.Top);
|
||||
UpdateSlider(ColorSliderOrder.Middle);
|
||||
UpdateSlider(ColorSliderOrder.Bottom);
|
||||
UpdateSlider(ColorSliderOrder.Alpha);
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
_topColorSlider.Value = _colorData.X;
|
||||
_middleColorSlider.Value = _colorData.Y;
|
||||
_bottomColorSlider.Value = _colorData.Z;
|
||||
|
||||
_topInputBox.Value = (int)(_colorData.X * 255.0f);
|
||||
_middleInputBox.Value = (int)(_colorData.Y * 255.0f);
|
||||
_bottomInputBox.Value = (int)(_colorData.Z * 255.0f);
|
||||
|
||||
break;
|
||||
case ColorSelectorType.Hsv:
|
||||
// dumb workaround because the formula for
|
||||
// HSV calculation results in a negative
|
||||
// number in any value past 300 degrees
|
||||
if (_colorData.X > 0)
|
||||
{
|
||||
_topColorSlider.Value = _colorData.X;
|
||||
_topInputBox.Value = (int)(_colorData.X * 360.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
_topInputBox.Value = (int)(_topColorSlider.Value * 360.0f);
|
||||
}
|
||||
|
||||
_middleColorSlider.Value = _colorData.Y;
|
||||
_bottomColorSlider.Value = _colorData.Z;
|
||||
|
||||
_middleInputBox.Value = (int)(_colorData.Y * 100.0f);
|
||||
_bottomInputBox.Value = (int)(_colorData.Z * 100.0f);
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_alphaSlider.Value = Color.A;
|
||||
_alphaInputBox.Value = (int)(Color.A * 100.0f);
|
||||
_updating = false;
|
||||
}
|
||||
|
||||
private bool IsSpinBoxValid(int value, ColorSliderOrder ordering)
|
||||
{
|
||||
var divisor = GetColorValueDivisor(ordering);
|
||||
var channelValue = value / divisor;
|
||||
if (value < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return channelValue >= 0.0f && channelValue <= 1.0f;
|
||||
if (ordering == ColorSliderOrder.Alpha)
|
||||
{
|
||||
return value <= 100;
|
||||
}
|
||||
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
return value <= byte.MaxValue;
|
||||
case ColorSelectorType.Hsv:
|
||||
switch (ordering)
|
||||
{
|
||||
case ColorSliderOrder.Top:
|
||||
return value <= 360;
|
||||
default:
|
||||
return value <= 100;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnInputBoxValueChanged(ValueChangedEventArgs args, ColorSliderOrder order)
|
||||
private (string, string, string) GetSliderLabels()
|
||||
{
|
||||
var (slider, _) = GetSliderByOrder(order);
|
||||
var value = args.Value / GetColorValueDivisor(order);
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
return (
|
||||
Loc.GetString("color-selector-sliders-red"),
|
||||
Loc.GetString("color-selector-sliders-green"),
|
||||
Loc.GetString("color-selector-sliders-blue")
|
||||
);
|
||||
case ColorSelectorType.Hsv:
|
||||
return (
|
||||
Loc.GetString("color-selector-sliders-hue"),
|
||||
Loc.GetString("color-selector-sliders-saturation"),
|
||||
Loc.GetString("color-selector-sliders-value")
|
||||
);
|
||||
}
|
||||
|
||||
// We are intentionally triggering the slider OnValueChanged event here.
|
||||
// This is so that the color data values of the sliders are updated accordingly.
|
||||
slider.Value = value;
|
||||
return ("ERR", "ERR", "ERR");
|
||||
}
|
||||
|
||||
private void OnSliderValueChanged(ColorSliderOrder order)
|
||||
private float GetColorValueDivisor(ColorSliderOrder order)
|
||||
{
|
||||
_colorData = new Vector4(
|
||||
_topColorSlider.Value,
|
||||
_middleColorSlider.Value,
|
||||
_bottomColorSlider.Value,
|
||||
_alphaSlider.Value);
|
||||
if (order == ColorSliderOrder.Alpha)
|
||||
{
|
||||
return 100.0f;
|
||||
}
|
||||
|
||||
_currentColor = GetStrategy().FromColorData(_colorData);
|
||||
switch (SelectorType)
|
||||
{
|
||||
case ColorSelectorType.Rgb:
|
||||
return 255.0f;
|
||||
case ColorSelectorType.Hsv:
|
||||
switch (order)
|
||||
{
|
||||
case ColorSliderOrder.Top:
|
||||
return 360.0f;
|
||||
default:
|
||||
return 100.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private void OnColorSet()
|
||||
{
|
||||
// stack overflow otherwise due to value sets
|
||||
if (_updating)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
|
||||
|
||||
_currentColor = SelectorType switch
|
||||
{
|
||||
ColorSelectorType.Hsv => Color.FromHsv(_colorData),
|
||||
_ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W)
|
||||
};
|
||||
|
||||
Update();
|
||||
OnColorChanged?.Invoke(_currentColor);
|
||||
|
||||
UpdateSliderVisuals();
|
||||
UpdateSlider(order);
|
||||
}
|
||||
|
||||
private enum ColorSliderOrder
|
||||
@@ -350,121 +414,4 @@ public sealed class ColorSelectorSliders : Control
|
||||
Rgb,
|
||||
Hsv,
|
||||
}
|
||||
|
||||
private abstract class ColorSliderStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// The style preset used by the top slider.
|
||||
/// </summary>
|
||||
public abstract ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The style preset used by the middle slider.
|
||||
/// </summary>
|
||||
public abstract ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The style preset used by the bottom slider.
|
||||
/// </summary>
|
||||
public abstract ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Color to a Vector4 representation of its components.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Each value in the Vector4 must be between 0.0f and 1.0f; this is used in the
|
||||
/// context of slider values, which are between these ranges.
|
||||
/// </remarks>
|
||||
/// <param name="color">A Color to convert into Vector4 slider values.</param>
|
||||
/// <returns>A Vector4 representation of a Color's slider values.</returns>
|
||||
public abstract Vector4 ToColorData(Color color);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Vector4 representation of color slider values into a Color.
|
||||
/// </summary>
|
||||
/// <param name="colorData">A Vector4 representation of color slider values.</param>
|
||||
/// <returns>A color generated from slider values.</returns>
|
||||
public abstract Color FromColorData(Vector4 colorData);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a color component divisor for the given slider.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is used for converting slider values to/from color component values.
|
||||
/// For example, in RGB coloration, each channel ranges from 0 to 255,
|
||||
/// so if you had a slider value of 0.2, you would multiply 0.2 * 255 = 51
|
||||
/// for the "channel" value.
|
||||
///
|
||||
/// This does not apply to the Alpha channel, as the Alpha channel
|
||||
/// always uses the same divisor; this is defined in ColorSelectorSliders.
|
||||
/// </remarks>
|
||||
/// <param name="order">The slider to retrieve a divisor for.</param>
|
||||
/// <returns>The divisor for the given slider.</returns>
|
||||
public abstract float GetColorValueDivisor(ColorSliderOrder order);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a label text string for the first three color sliders.
|
||||
/// </summary>
|
||||
/// <returns>Label text strings for the top, middle, and bottom sliders.</returns>
|
||||
public abstract (string top, string middle, string bottom) GetSliderLabelTexts();
|
||||
}
|
||||
|
||||
private sealed class RgbSliderStrategy : ColorSliderStrategy
|
||||
{
|
||||
private const float ChannelMaxValue = byte.MaxValue;
|
||||
|
||||
public override ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle
|
||||
=> ColorSelectorStyleBox.ColorSliderPreset.Red;
|
||||
public override ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle
|
||||
=> ColorSelectorStyleBox.ColorSliderPreset.Green;
|
||||
public override ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle
|
||||
=> ColorSelectorStyleBox.ColorSliderPreset.Blue;
|
||||
|
||||
public override Vector4 ToColorData(Color color) => new(color.R, color.G, color.B, color.A);
|
||||
public override Color FromColorData(Vector4 colorData)
|
||||
=> new(colorData.X, colorData.Y, colorData.Z, colorData.W);
|
||||
|
||||
public override float GetColorValueDivisor(ColorSliderOrder order) => ChannelMaxValue;
|
||||
|
||||
public override (string top, string middle, string bottom) GetSliderLabelTexts()
|
||||
{
|
||||
return (
|
||||
Loc.GetString("color-selector-sliders-red"),
|
||||
Loc.GetString("color-selector-sliders-green"),
|
||||
Loc.GetString("color-selector-sliders-blue"));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class HsvSliderStrategy : ColorSliderStrategy
|
||||
{
|
||||
private const float HueMaxValue = 360.0f;
|
||||
private const float SliderMaxValue = 100.0f;
|
||||
|
||||
public override ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle
|
||||
=> ColorSelectorStyleBox.ColorSliderPreset.Hue;
|
||||
public override ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle
|
||||
=> ColorSelectorStyleBox.ColorSliderPreset.Saturation;
|
||||
public override ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle
|
||||
=> ColorSelectorStyleBox.ColorSliderPreset.Value;
|
||||
|
||||
public override Vector4 ToColorData(Color color) => Color.ToHsv(color);
|
||||
public override Color FromColorData(Vector4 colorData) => Color.FromHsv(colorData);
|
||||
|
||||
public override float GetColorValueDivisor(ColorSliderOrder order)
|
||||
{
|
||||
return order switch
|
||||
{
|
||||
ColorSliderOrder.Top => HueMaxValue,
|
||||
_ => SliderMaxValue,
|
||||
};
|
||||
}
|
||||
|
||||
public override (string top, string middle, string bottom) GetSliderLabelTexts()
|
||||
{
|
||||
return (
|
||||
Loc.GetString("color-selector-sliders-hue"),
|
||||
Loc.GetString("color-selector-sliders-saturation"),
|
||||
Loc.GetString("color-selector-sliders-value"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,19 +21,19 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
|
||||
/// <summary>
|
||||
/// Base background colour.
|
||||
/// </summary>
|
||||
public Vector4 BaseColor;
|
||||
public Robust.Shared.Maths.Vector4 BaseColor;
|
||||
|
||||
/// <summary>
|
||||
/// Colour to add to the background colour along the X-axis.
|
||||
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
|
||||
/// </summary>
|
||||
public Vector4 XAxis;
|
||||
public Robust.Shared.Maths.Vector4 XAxis;
|
||||
|
||||
/// <summary>
|
||||
/// Colour to add to the background colour along the y-axis.
|
||||
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
|
||||
/// </summary>
|
||||
public Vector4 YAxis;
|
||||
public Robust.Shared.Maths.Vector4 YAxis;
|
||||
|
||||
/// <summary>
|
||||
/// If true, then <see cref="BaseColor"/>, <see cref="XAxis"/>, and <see cref="YAxis"/> will be interpreted as HSVa
|
||||
@@ -93,14 +93,14 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
|
||||
{
|
||||
var colorData = Hsv
|
||||
? Color.ToHsv(color)
|
||||
: new Vector4(color.R, color.G, color.B, color.A);
|
||||
: new Robust.Shared.Maths.Vector4(color.R, color.G, color.B, color.A);
|
||||
SetBaseColor(colorData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
|
||||
/// </summary>
|
||||
public void SetBaseColor(Vector4 colorData)
|
||||
public void SetBaseColor(Robust.Shared.Maths.Vector4 colorData)
|
||||
{
|
||||
BaseColor = colorData - colorData * XAxis - colorData * YAxis;
|
||||
}
|
||||
|
||||
@@ -107,11 +107,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_scrollBar.Value = 0;
|
||||
}
|
||||
|
||||
public FormattedMessage GetMessage(Index index)
|
||||
{
|
||||
return new FormattedMessage(_entries[index].Message);
|
||||
}
|
||||
|
||||
public void RemoveEntry(Index index)
|
||||
{
|
||||
var entry = _entries[index];
|
||||
@@ -143,31 +138,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
_entries.Add(entry);
|
||||
var font = _getFont();
|
||||
AddNewItemHeight(font, entry);
|
||||
|
||||
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
{
|
||||
_scrollBar.MoveToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetMessage(Index index, FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
|
||||
{
|
||||
var oldEntry = _entries[index];
|
||||
var font = _getFont();
|
||||
_totalContentHeight -= oldEntry.Height + font.GetLineSeparation(UIScale);
|
||||
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
|
||||
|
||||
var entry = new RichTextEntry(message, this, _tagManager, tagsAllowed, defaultColor);
|
||||
entry.Update(_tagManager, _getFont(), _getContentBox().Width, UIScale);
|
||||
_entries[index] = entry;
|
||||
|
||||
AddNewItemHeight(font, in entry);
|
||||
}
|
||||
|
||||
private void AddNewItemHeight(Font font, in RichTextEntry entry)
|
||||
{
|
||||
_totalContentHeight += entry.Height;
|
||||
if (_firstLine)
|
||||
{
|
||||
@@ -177,6 +147,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
_totalContentHeight += font.GetLineSeparation(UIScale);
|
||||
}
|
||||
|
||||
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
{
|
||||
_scrollBar.MoveToEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void ScrollToBottom()
|
||||
|
||||
@@ -30,12 +30,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
get => _currentTab;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
if (_currentTab < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value, "Current tab must be positive.");
|
||||
}
|
||||
|
||||
if (value >= ChildCount)
|
||||
if (_currentTab >= ChildCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(value), value,
|
||||
"Current tab must less than the amount of tabs.");
|
||||
|
||||
@@ -13,6 +13,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Vector2 = System.Numerics.Vector2;
|
||||
|
||||
namespace Robust.Client.UserInterface.Controls;
|
||||
|
||||
|
||||
@@ -145,11 +145,6 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
// This is to avoid unnecessarily setting a position where our size isn't yet fully updated.
|
||||
// This most commonly happens with saved window positions if your window position is <= 0.
|
||||
if (!IsMeasureValid)
|
||||
return;
|
||||
|
||||
var (spaceX, spaceY) = Parent!.Size;
|
||||
|
||||
var maxX = spaceX - ((AllowOffScreen & DirectionFlag.West) == 0 ? Size.X : WindowEdgeSeparation);
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Placement;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
@@ -16,24 +12,34 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
[GenerateTypedNameReferences]
|
||||
public sealed partial class EntitySpawnWindow : DefaultWindow
|
||||
{
|
||||
[Dependency] private readonly IPlacementManager _placement = default!;
|
||||
public static readonly string[] InitOpts =
|
||||
{
|
||||
"Default",
|
||||
"PlaceFree",
|
||||
"PlaceNearby",
|
||||
"SnapgridCenter",
|
||||
"SnapgridBorder",
|
||||
"AlignSimilar",
|
||||
"AlignTileAny",
|
||||
"AlignTileEmpty",
|
||||
"AlignTileNonDense",
|
||||
"AlignTileDense",
|
||||
"AlignWall",
|
||||
"AlignWallProper",
|
||||
};
|
||||
|
||||
public EntitySpawnButton? SelectedButton;
|
||||
public EntityPrototype? SelectedPrototype;
|
||||
|
||||
[Obsolete("Use IPlacementManager.AllModeNames")]
|
||||
public static string[] InitOpts =>IoCManager.Resolve<IPlacementManager>().AllModeNames;
|
||||
public EntitySpawnWindow()
|
||||
{
|
||||
RobustXamlLoader.Load(this);
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
MeasureButton.Measure(Vector2Helpers.Infinity);
|
||||
|
||||
var modes = _placement.AllModeNames;
|
||||
for (var i = 0; i < modes.Length; i++)
|
||||
for (var i = 0; i < InitOpts.Length; i++)
|
||||
{
|
||||
OverrideMenu.AddItem(modes[i], i);
|
||||
OverrideMenu.AddItem(InitOpts[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,15 +18,12 @@ namespace Robust.Client.UserInterface
|
||||
[SuppressMessage("ReSharper", "IdentifierTypo")]
|
||||
[SuppressMessage("ReSharper", "CommentTypo")]
|
||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
||||
internal sealed class FileDialogManager : IFileDialogManager, IPostInjectInit
|
||||
internal sealed class FileDialogManager : IFileDialogManager
|
||||
{
|
||||
// Uses nativefiledialog to open the file dialogs cross platform.
|
||||
// On Linux, if the kdialog command is found, it will be used instead.
|
||||
// TODO: Should we maybe try to avoid running kdialog if the DE isn't KDE?
|
||||
[Dependency] private readonly IClydeInternal _clyde = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private bool _kDialogAvailable;
|
||||
private bool _checkedKDialogAvailable;
|
||||
@@ -270,7 +267,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
if (_kDialogAvailable)
|
||||
{
|
||||
_sawmill.Debug("kdialog available.");
|
||||
Logger.DebugS("filedialog", "kdialog available.");
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -407,11 +404,6 @@ namespace Robust.Client.UserInterface
|
||||
[DllImport("swnfd.dll")]
|
||||
private static extern unsafe void sw_NFD_Free(void* ptr);
|
||||
|
||||
public void PostInject()
|
||||
{
|
||||
_sawmill = _log.GetSawmill("filedialog");
|
||||
}
|
||||
|
||||
private enum sw_nfdresult
|
||||
{
|
||||
SW_NFD_ERROR,
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace Robust.Client.UserInterface
|
||||
if (Controls == null || !Controls.TryGetValue(nodeIndex, out var control))
|
||||
continue;
|
||||
|
||||
control.Measure(new Vector2(maxSizeX, Height));
|
||||
control.Measure(new Vector2(Width, Height));
|
||||
|
||||
var desiredSize = control.DesiredPixelSize;
|
||||
var controlMetrics = new CharMetrics(
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Client.UserInterface;
|
||||
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;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.ViewVariables.Editors.VVPropEditorNumeric;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
@@ -24,7 +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 IViewVariableControlFactory _controlFactory = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resManager = default!;
|
||||
|
||||
private uint _nextReqId = 1;
|
||||
private readonly Vector2i _defaultWindowSize = (640, 420);
|
||||
@@ -56,7 +64,190 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
public VVPropEditor PropertyFor(Type? type)
|
||||
{
|
||||
return _controlFactory.CreateFor(type);
|
||||
// TODO: make this more flexible.
|
||||
if (type == null)
|
||||
{
|
||||
return new VVPropEditorDummy();
|
||||
}
|
||||
|
||||
if (type == typeof(sbyte))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.SByte);
|
||||
}
|
||||
|
||||
if (type == typeof(byte))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.Byte);
|
||||
}
|
||||
|
||||
if (type == typeof(ushort))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.UShort);
|
||||
}
|
||||
|
||||
if (type == typeof(short))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.Short);
|
||||
}
|
||||
|
||||
if (type == typeof(uint))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.UInt);
|
||||
}
|
||||
|
||||
if (type == typeof(int))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.Int);
|
||||
}
|
||||
|
||||
if (type == typeof(ulong))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.ULong);
|
||||
}
|
||||
|
||||
if (type == typeof(long))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.Long);
|
||||
}
|
||||
|
||||
if (type == typeof(float))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.Float);
|
||||
}
|
||||
|
||||
if (type == typeof(double))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.Double);
|
||||
}
|
||||
|
||||
if (type == typeof(decimal))
|
||||
{
|
||||
return new VVPropEditorNumeric(NumberType.Decimal);
|
||||
}
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return new VVPropEditorString();
|
||||
}
|
||||
|
||||
if (type == typeof(EntProtoId?))
|
||||
{
|
||||
return new VVPropEditorNullableEntProtoId();
|
||||
}
|
||||
|
||||
if (type == typeof(EntProtoId))
|
||||
{
|
||||
return new VVPropEditorEntProtoId();
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
{
|
||||
var editor =
|
||||
(VVPropEditor)Activator.CreateInstance(
|
||||
typeof(VVPropEditorProtoId<>).MakeGenericType(type.GenericTypeArguments[0]))!;
|
||||
|
||||
IoCManager.InjectDependencies(editor);
|
||||
return editor;
|
||||
}
|
||||
|
||||
if (typeof(IPrototype).IsAssignableFrom(type) || typeof(ViewVariablesBlobMembers.PrototypeReferenceToken).IsAssignableFrom(type))
|
||||
{
|
||||
return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorIPrototype<>).MakeGenericType(type))!;
|
||||
}
|
||||
|
||||
if (typeof(ISelfSerialize).IsAssignableFrom(type))
|
||||
{
|
||||
return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorISelfSerializable<>).MakeGenericType(type))!;
|
||||
}
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return new VVPropEditorEnum();
|
||||
}
|
||||
|
||||
if (type == typeof(Vector2))
|
||||
{
|
||||
return new VVPropEditorVector2(intVec: false);
|
||||
}
|
||||
|
||||
if (type == typeof(Vector2i))
|
||||
{
|
||||
return new VVPropEditorVector2(intVec: true);
|
||||
}
|
||||
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
return new VVPropEditorBoolean();
|
||||
}
|
||||
|
||||
if (type == typeof(Angle))
|
||||
{
|
||||
return new VVPropEditorAngle();
|
||||
}
|
||||
|
||||
if (type == typeof(Box2))
|
||||
{
|
||||
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2);
|
||||
}
|
||||
|
||||
if (type == typeof(Box2i))
|
||||
{
|
||||
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2i);
|
||||
}
|
||||
|
||||
if (type == typeof(UIBox2))
|
||||
{
|
||||
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2);
|
||||
}
|
||||
|
||||
if (type == typeof(UIBox2i))
|
||||
{
|
||||
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2i);
|
||||
}
|
||||
|
||||
if (type == typeof(EntityCoordinates))
|
||||
{
|
||||
return new VVPropEditorEntityCoordinates();
|
||||
}
|
||||
|
||||
if (type == typeof(EntityUid))
|
||||
{
|
||||
return new VVPropEditorEntityUid();
|
||||
}
|
||||
|
||||
if (type == typeof(NetEntity))
|
||||
{
|
||||
return new VVPropEditorNetEntity();
|
||||
}
|
||||
|
||||
if (type == typeof(Color))
|
||||
{
|
||||
return new VVPropEditorColor();
|
||||
}
|
||||
|
||||
if (type == typeof(TimeSpan))
|
||||
{
|
||||
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<,>))
|
||||
{
|
||||
return new VVPropEditorKeyValuePair();
|
||||
}
|
||||
|
||||
if (type != typeof(ViewVariablesBlobMembers.ServerValueTypeToken) && !type.IsValueType)
|
||||
{
|
||||
return new VVPropEditorReference();
|
||||
}
|
||||
|
||||
return new VVPropEditorDummy();
|
||||
}
|
||||
|
||||
public void OpenVV(object obj)
|
||||
@@ -72,7 +263,7 @@ namespace Robust.Client.ViewVariables
|
||||
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
|
||||
}
|
||||
|
||||
var window = new DefaultWindow { Title = Loc.GetString("view-variables") };
|
||||
var window = new DefaultWindow {Title = Loc.GetString("view-variables")};
|
||||
instance.Initialize(window, obj);
|
||||
window.OnClose += () => _closeInstance(instance, false);
|
||||
_windows.Add(instance, window);
|
||||
@@ -82,7 +273,7 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
public void OpenVV(string path)
|
||||
{
|
||||
if (ReadPath(path) is { } obj)
|
||||
if (ReadPath(path) is {} obj)
|
||||
OpenVV(obj);
|
||||
}
|
||||
|
||||
@@ -93,7 +284,7 @@ namespace Robust.Client.ViewVariables
|
||||
Title = Loc.GetString("view-variables"),
|
||||
SetSize = _defaultWindowSize
|
||||
};
|
||||
var loadingLabel = new Label { Text = "Retrieving remote object data from server..." };
|
||||
var loadingLabel = new Label {Text = "Retrieving remote object data from server..."};
|
||||
window.Contents.AddChild(loadingLabel);
|
||||
|
||||
// We need to request the data, THEN create an instance.
|
||||
@@ -161,7 +352,7 @@ namespace Robust.Client.ViewVariables
|
||||
|
||||
public async Task<T> RequestData<T>(ViewVariablesRemoteSession session, ViewVariablesRequest meta) where T : ViewVariablesBlob
|
||||
{
|
||||
return (T)await RequestData(session, meta);
|
||||
return (T) await RequestData(session, meta);
|
||||
}
|
||||
|
||||
public void CloseSession(ViewVariablesRemoteSession session)
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors;
|
||||
|
||||
internal sealed class VVPropEditorEntProtoId : VVPropEditor
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
|
||||
public VVPropEditorEntProtoId()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var lineEdit = new LineEdit
|
||||
{
|
||||
Text = (EntProtoId)(value ?? ""),
|
||||
Text = (EntProtoId) (value ?? ""),
|
||||
Editable = !ReadOnly,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
@@ -27,9 +19,7 @@ internal sealed class VVPropEditorEntProtoId : VVPropEditor
|
||||
{
|
||||
lineEdit.OnTextEntered += e =>
|
||||
{
|
||||
var id = (EntProtoId)e.Text;
|
||||
if (_protoMan.HasIndex(id))
|
||||
ValueChanged(id);
|
||||
ValueChanged((EntProtoId) e.Text);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ namespace Robust.Client.ViewVariables.Editors
|
||||
{
|
||||
var hBox = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Horizontal,
|
||||
MinWidth = 350
|
||||
Orientation = LayoutOrientation.Horizontal
|
||||
};
|
||||
|
||||
dynamic d = value!;
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors;
|
||||
|
||||
internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoMan = default!;
|
||||
|
||||
public VVPropEditorNullableEntProtoId()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var lineEdit = new LineEdit
|
||||
{
|
||||
Text = value is EntProtoId protoId ? protoId.Id : "",
|
||||
Text = value is EntProtoId protoId ? protoId.Id : "",
|
||||
Editable = !ReadOnly,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
@@ -33,9 +25,7 @@ internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = (EntProtoId)e.Text;
|
||||
if (_protoMan.HasIndex(id))
|
||||
ValueChanged(id);
|
||||
ValueChanged((EntProtoId) e.Text);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Robust.Client.UserInterface.Controls.BoxContainer;
|
||||
using CS = System.Runtime.CompilerServices;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors;
|
||||
|
||||
internal sealed class VVPropEditorTuple : VVPropEditor
|
||||
{
|
||||
[Dependency] private readonly IClientViewVariablesManagerInternal _viewVariables = default!;
|
||||
|
||||
private bool _readOnly;
|
||||
private readonly List<object?> _tuple = [];
|
||||
private readonly List<VVPropEditor> _editors = [];
|
||||
private Type? _actualType;
|
||||
|
||||
public VVPropEditorTuple()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var vBoxContainer = new BoxContainer
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical,
|
||||
MinSize = new Vector2(240, 0),
|
||||
};
|
||||
|
||||
if (value is not CS.ITuple tuple)
|
||||
return vBoxContainer;
|
||||
|
||||
// Zero-tuples exist?? I'm just not going to bother with that.
|
||||
if (tuple.Length == 0)
|
||||
return vBoxContainer;
|
||||
|
||||
_actualType = value.GetType();
|
||||
|
||||
// We disallow editing tuples with arity more than 7 since they would
|
||||
// a pain to construct via reflection. And no one should have tuples
|
||||
// that large. (8 is bad because last element becomes a ValueTuple<>)
|
||||
_readOnly = ReadOnly
|
||||
|| tuple.Length >= 8
|
||||
|| !IsValueTuple(_actualType); // ToTuple only supports ValueTuples
|
||||
|
||||
for (var i = 0; i < tuple.Length; i++)
|
||||
{
|
||||
var editor = CreateBox(tuple[i], vBoxContainer);
|
||||
var index = i; // thanks C#
|
||||
editor.OnValueChanged += (o, reinterpret) => ValueChanged(ToTuple(o, index), reinterpret);
|
||||
|
||||
_tuple.Add(tuple[i]);
|
||||
_editors.Add(editor);
|
||||
}
|
||||
return vBoxContainer;
|
||||
}
|
||||
|
||||
private bool IsValueTuple(Type actualType)
|
||||
{
|
||||
if (!actualType.IsGenericType)
|
||||
return false;
|
||||
|
||||
Type[] valueTupleTypes =
|
||||
[
|
||||
typeof(ValueTuple<>), typeof(ValueTuple<,>), typeof(ValueTuple<,,>), typeof(ValueTuple<,,,>),
|
||||
typeof(ValueTuple<,,,,>), typeof(ValueTuple<,,,,,>), typeof(ValueTuple<,,,,,,>), typeof(ValueTuple<,,,,,,,>)
|
||||
];
|
||||
return valueTupleTypes.Contains(actualType.GetGenericTypeDefinition());
|
||||
}
|
||||
|
||||
private CS.ITuple ToTuple(object? changed, int index)
|
||||
{
|
||||
_tuple[index] = changed;
|
||||
|
||||
// I can't seem to make this work using .GetMethod.
|
||||
// If you know of a better way of doing this... please do.
|
||||
return (CS.ITuple)typeof(ValueTuple).GetMethods()
|
||||
.First(x => x is { Name: nameof(ValueTuple.Create), IsGenericMethod: true }
|
||||
&& x.GetParameters().Length == _tuple.Count)
|
||||
.MakeGenericMethod(_actualType!.GenericTypeArguments)
|
||||
.Invoke(null, _tuple.ToArray())!;
|
||||
}
|
||||
|
||||
private VVPropEditor CreateBox<T>(T? entry, BoxContainer parent)
|
||||
{
|
||||
var editor = _viewVariables.PropertyFor(entry?.GetType());
|
||||
// We disallow editing of serverside-only tuples because, uh, I don't
|
||||
// know how to make it work. Presumably it'd have to be something
|
||||
// similarly cursed to what I did in ToTuple above.
|
||||
parent.AddChild(editor.Initialize(entry, _readOnly));
|
||||
return editor;
|
||||
}
|
||||
|
||||
// Allow selecting, for example, dictionaries within the tuple.
|
||||
// Wait, why do you have a field with a tuple that holds a dictionary??
|
||||
public override void WireNetworkSelector(uint sessionId, object[] selectorChain)
|
||||
{
|
||||
for (var i = 0; i < _editors.Count; i++)
|
||||
{
|
||||
object[] chain = [..selectorChain, new ViewVariablesTupleIndexSelector(i)];
|
||||
_editors[i].WireNetworkSelector(sessionId, chain);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using Robust.Client.ViewVariables.Editors;
|
||||
|
||||
namespace Robust.Client.ViewVariables;
|
||||
|
||||
/// <summary>
|
||||
/// Factory that creates UI controls for viewing variables based on provided property type.
|
||||
/// </summary>
|
||||
public interface IViewVariableControlFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates UI control for viewing variable of type <paramref name="type"/>.
|
||||
/// Returns <see cref="VVPropEditorDummy"/> if fails to find proper control.
|
||||
/// First will look for control factory by type match (<see cref="RegisterForType{T}"/>),
|
||||
/// then will try to check each registered conditional factory
|
||||
/// (<see cref="RegisterWithCondition"/>, <see cref="RegisterForAssignableFrom{T}"/> and similar methods).
|
||||
/// </summary>
|
||||
VVPropEditor CreateFor(Type? type);
|
||||
|
||||
/// <summary>
|
||||
/// Registers factory method for vv control. This factory method will be used if provided type will be exactly equal to <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of property, for which this control factory should be used. Will only be used in case of exact match.</typeparam>
|
||||
/// <param name="factoryMethod">Factory method that creates VV control.</param>
|
||||
void RegisterForType<T>(Func<Type, VVPropEditor> factoryMethod);
|
||||
|
||||
/// <summary>
|
||||
/// Registers factory method for vv control. This factory method will be used if provided type will be assignable to <typeparamref name="T"/>.
|
||||
/// Conditions will be checked if none of <see cref="RegisterForType{T}"/> registrations were fitting.
|
||||
/// </summary>
|
||||
/// <param name="factoryMethod">Factory method that creates VV control.</param>
|
||||
/// <param name="insertPosition">Where new condition should be inserted - at start or at the end of the list.</param>
|
||||
void RegisterForAssignableFrom<T>(Func<Type, VVPropEditor> factoryMethod, InsertPosition insertPosition = InsertPosition.First);
|
||||
|
||||
/// <summary>
|
||||
/// Registers factory method for vv control. This factory method will be used if <paramref name="condition"/> will return true for provided type.
|
||||
/// Conditions will be checked if none of <see cref="RegisterForType{T}"/> registrations were fitting.
|
||||
/// </summary>
|
||||
/// <param name="condition">Condition, that will decide, if factory should be used for provided type.</param>
|
||||
/// <param name="factory">Factory method that creates VV control.</param>
|
||||
/// <param name="insertPosition">Where new condition should be inserted - at start or at the end of the list. </param>
|
||||
void RegisterWithCondition(Func<Type, bool> condition, Func<Type, VVPropEditor> factory, InsertPosition insertPosition = InsertPosition.First);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicator, where item should be inserted in list.
|
||||
/// </summary>
|
||||
public enum InsertPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// Item will be inserted as first in list.
|
||||
/// </summary>
|
||||
First,
|
||||
/// <summary>
|
||||
/// Item will be inserted as last in list.
|
||||
/// </summary>
|
||||
Last
|
||||
}
|
||||
@@ -151,7 +151,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
ViewVariablesTraitMembers.CreateMemberGroupHeader(
|
||||
ref first,
|
||||
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
|
||||
clientVBox.Children);
|
||||
clientVBox);
|
||||
|
||||
foreach (var control in group)
|
||||
{
|
||||
@@ -525,7 +525,7 @@ namespace Robust.Client.ViewVariables.Instances
|
||||
var first = true;
|
||||
foreach (var (groupName, groupMembers) in _membersBlob!.MemberGroups)
|
||||
{
|
||||
ViewVariablesTraitMembers.CreateMemberGroupHeader(ref first, groupName, _serverVariables.Children);
|
||||
ViewVariablesTraitMembers.CreateMemberGroupHeader(ref first, groupName, _serverVariables);
|
||||
|
||||
foreach (var propertyData in groupMembers)
|
||||
{
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
CreateMemberGroupHeader(
|
||||
ref first,
|
||||
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
|
||||
replacementControls);
|
||||
_memberList);
|
||||
|
||||
foreach (var control in group)
|
||||
{
|
||||
@@ -67,7 +67,7 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
var first = true;
|
||||
foreach (var (groupName, groupMembers) in blob.MemberGroups)
|
||||
{
|
||||
CreateMemberGroupHeader(ref first, groupName, replacementControls);
|
||||
CreateMemberGroupHeader(ref first, groupName, _memberList);
|
||||
|
||||
foreach (var propertyData in groupMembers)
|
||||
{
|
||||
@@ -95,15 +95,15 @@ namespace Robust.Client.ViewVariables.Traits
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CreateMemberGroupHeader(ref bool first, string groupName, ICollection<Control> container)
|
||||
internal static void CreateMemberGroupHeader(ref bool first, string groupName, Control container)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
container.Add(new Control {MinSize = new Vector2(0, 16)});
|
||||
container.AddChild(new Control {MinSize = new Vector2(0, 16)});
|
||||
}
|
||||
|
||||
first = false;
|
||||
container.Add(new Label {Text = groupName, FontColorOverride = Color.DarkGray});
|
||||
container.AddChild(new Label {Text = groupName, FontColorOverride = Color.DarkGray});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Robust.Client.ViewVariables.Editors;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using CS = System.Runtime.CompilerServices;
|
||||
|
||||
namespace Robust.Client.ViewVariables;
|
||||
|
||||
internal sealed class ViewVariableControlFactory : IViewVariableControlFactory
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly IResourceManager _resManager = default!;
|
||||
[Dependency] private readonly IDependencyCollection _dependencyManager = default!;
|
||||
|
||||
private readonly Dictionary<Type, Func<Type, VVPropEditor>> _factoriesByType = new();
|
||||
private readonly List<ConditionalViewVariableFactoryMethodContainer> _factoriesWithCondition = new();
|
||||
|
||||
public ViewVariableControlFactory()
|
||||
{
|
||||
RegisterForType<sbyte>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.SByte));
|
||||
RegisterForType<byte>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Byte));
|
||||
RegisterForType<ushort>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.UShort));
|
||||
RegisterForType<short>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Short));
|
||||
RegisterForType<uint>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.UInt));
|
||||
RegisterForType<int>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Int));
|
||||
RegisterForType<ulong>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.ULong));
|
||||
RegisterForType<long>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Long));
|
||||
RegisterForType<float>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Float));
|
||||
RegisterForType<float>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Float));
|
||||
RegisterForType<double>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Double));
|
||||
RegisterForType<decimal>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Decimal));
|
||||
RegisterForType<string>(_ => new VVPropEditorString());
|
||||
RegisterForType<EntProtoId?>(_ => new VVPropEditorNullableEntProtoId());
|
||||
RegisterForType<EntProtoId>(_ => new VVPropEditorEntProtoId());
|
||||
RegisterForType<Vector2>(_ => new VVPropEditorVector2(intVec: false));
|
||||
RegisterForType<Vector2i>(_ => new VVPropEditorVector2(intVec: true));
|
||||
RegisterForType<bool>(_ => new VVPropEditorBoolean());
|
||||
RegisterForType<Angle>(_ => new VVPropEditorAngle());
|
||||
RegisterForType<Box2>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2));
|
||||
RegisterForType<Box2i>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2i));
|
||||
RegisterForType<UIBox2>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2));
|
||||
RegisterForType<UIBox2i>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2i));
|
||||
RegisterForType<EntityCoordinates>(_ => new VVPropEditorEntityCoordinates());
|
||||
RegisterForType<EntityUid>(_ => new VVPropEditorEntityUid());
|
||||
RegisterForType<NetEntity>(_ => new VVPropEditorNetEntity());
|
||||
RegisterForType<Color>(_ => new VVPropEditorColor());
|
||||
RegisterForType<TimeSpan>(_ => new VVPropEditorTimeSpan());
|
||||
|
||||
RegisterWithCondition(
|
||||
type => type != typeof(ViewVariablesBlobMembers.ServerValueTypeToken) && !type.IsValueType,
|
||||
_ => new VVPropEditorReference()
|
||||
);
|
||||
RegisterWithCondition(
|
||||
type => type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken)
|
||||
|| type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>),
|
||||
_ => new VVPropEditorKeyValuePair()
|
||||
);
|
||||
|
||||
RegisterForAssignableFrom<CS.ITuple>(_ => new VVPropEditorTuple());
|
||||
RegisterForAssignableFrom<SoundSpecifier>(_ => new VVPropEditorSoundSpecifier(_protoManager, _resManager));
|
||||
RegisterForAssignableFrom<ISelfSerialize>(type => CreateGenericEditor(type, typeof(VVPropEditorISelfSerializable<>)));
|
||||
RegisterForAssignableFrom<ViewVariablesBlobMembers.PrototypeReferenceToken>(type => CreateGenericEditor(type, typeof(VVPropEditorIPrototype<>)));
|
||||
RegisterForAssignableFrom<IPrototype>(type => CreateGenericEditor(type, typeof(VVPropEditorIPrototype<>)));
|
||||
RegisterWithCondition(
|
||||
type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>),
|
||||
type =>
|
||||
{
|
||||
var typeArgumentType = type.GenericTypeArguments[0];
|
||||
var editor = CreateGenericEditor(typeArgumentType, typeof(VVPropEditorProtoId<>));
|
||||
|
||||
_dependencyManager.InjectDependencies(editor);
|
||||
return editor;
|
||||
}
|
||||
);
|
||||
RegisterWithCondition(type => type.IsEnum, _ => new VVPropEditorEnum());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterForType<T>(Func<Type, VVPropEditor> factoryMethod)
|
||||
{
|
||||
_factoriesByType[typeof(T)] = factoryMethod;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterForAssignableFrom<T>(Func<Type, VVPropEditor> factoryMethod, InsertPosition insertPosition = InsertPosition.First)
|
||||
{
|
||||
int insertIndex = insertPosition == InsertPosition.Last && _factoriesWithCondition.Count > 0
|
||||
? _factoriesWithCondition.Count - 1
|
||||
: 0;
|
||||
_factoriesWithCondition.Insert(insertIndex, new(type => typeof(T).IsAssignableFrom(type), factoryMethod));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RegisterWithCondition(Func<Type, bool> condition, Func<Type, VVPropEditor> factory, InsertPosition insertPosition = InsertPosition.First)
|
||||
{
|
||||
int insertIndex = insertPosition == InsertPosition.Last && _factoriesWithCondition.Count > 0
|
||||
? _factoriesWithCondition.Count - 1
|
||||
: 0;
|
||||
_factoriesWithCondition.Insert(insertIndex, new(condition, factory));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VVPropEditor CreateFor(Type? type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return new VVPropEditorDummy();
|
||||
}
|
||||
|
||||
if (_factoriesByType.TryGetValue(type, out var factory))
|
||||
{
|
||||
return factory(type);
|
||||
}
|
||||
|
||||
foreach (var factoryWithCondition in _factoriesWithCondition)
|
||||
{
|
||||
if (factoryWithCondition.CanExecute(type))
|
||||
{
|
||||
return factoryWithCondition.Create(type);
|
||||
}
|
||||
}
|
||||
|
||||
return new VVPropEditorDummy();
|
||||
}
|
||||
|
||||
private static VVPropEditor CreateGenericEditor(Type typeArgumentType, Type genericConrolType)
|
||||
{
|
||||
var typeToCreate = genericConrolType.MakeGenericType(typeArgumentType);
|
||||
return (VVPropEditor)Activator.CreateInstance(typeToCreate)!;
|
||||
}
|
||||
|
||||
private sealed record ConditionalViewVariableFactoryMethodContainer(
|
||||
Func<Type, bool> CanExecute,
|
||||
Func<Type, VVPropEditor> Create
|
||||
);
|
||||
}
|
||||
@@ -73,10 +73,30 @@ namespace Robust.Client.ViewVariables
|
||||
public VVPropEditor SetProperty(ViewVariablesBlobMembers.MemberData member)
|
||||
{
|
||||
NameLabel.Text = member.Name;
|
||||
var type = member.Value?.GetType();
|
||||
var type = Type.GetType(member.Type);
|
||||
|
||||
_bottomLabel.Text = $"Type: {member.TypePretty}";
|
||||
var editor = _viewVariablesManager.PropertyFor(type);
|
||||
VVPropEditor editor;
|
||||
if (type == null || !_robustSerializer.CanSerialize(type))
|
||||
{
|
||||
// Type is server-side only.
|
||||
// Info whether it's reference or value type can be figured out from the sent value.
|
||||
if (type?.IsValueType == true || member.Value is ViewVariablesBlobMembers.ServerValueTypeToken)
|
||||
{
|
||||
// Value type, just display it stringified read-only.
|
||||
editor = new VVPropEditorDummy();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Has to be a reference type at this point.
|
||||
DebugTools.Assert(member.Value is ViewVariablesBlobMembers.ReferenceToken || member.Value == null || type?.IsClass == true || type?.IsInterface == true);
|
||||
editor = _viewVariablesManager.PropertyFor(type ?? typeof(object));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
editor = _viewVariablesManager.PropertyFor(type);
|
||||
}
|
||||
|
||||
var view = editor.Initialize(member.Value, !member.Editable);
|
||||
if (!view.HorizontalExpand)
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Robust.Packaging.AssetProcessing.Passes;
|
||||
/// </summary>
|
||||
public sealed class AssetPassAudioMetadata : AssetPass
|
||||
{
|
||||
private readonly List<(string ID, TimeSpan Length)> _audioMetadata = new();
|
||||
private readonly List<AudioMetadataPrototype> _audioMetadata = new();
|
||||
private readonly string _metadataPath;
|
||||
|
||||
public AssetPassAudioMetadata(string metadataPath = "Prototypes/_audio_metadata.yml")
|
||||
@@ -32,7 +32,11 @@ public sealed class AssetPassAudioMetadata : AssetPass
|
||||
|
||||
lock (_audioMetadata)
|
||||
{
|
||||
_audioMetadata.Add(("/" + file.Path, metadata.Length));
|
||||
_audioMetadata.Add(new AudioMetadataPrototype()
|
||||
{
|
||||
ID = "/" + file.Path,
|
||||
Length = metadata.Length,
|
||||
});
|
||||
}
|
||||
|
||||
return AssetFileAcceptResult.Consumed;
|
||||
|
||||
@@ -19,19 +19,7 @@ public sealed class RobustClientPackaging
|
||||
AssetPass pass,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
await WriteClientResources(contentDir, pass, new HashSet<string>(), cancel);
|
||||
}
|
||||
|
||||
public static async Task WriteClientResources(
|
||||
string contentDir,
|
||||
AssetPass pass,
|
||||
IReadOnlySet<string> additionalIgnoredResources,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var ignoreSet = ClientIgnoredResources
|
||||
.Union(RobustSharedPackaging.SharedIgnoredResources)
|
||||
.Union(additionalIgnoredResources)
|
||||
.ToHashSet();
|
||||
var ignoreSet = ClientIgnoredResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
|
||||
|
||||
await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"), pass, ignoreSet, cancel: cancel);
|
||||
}
|
||||
|
||||
@@ -15,22 +15,13 @@ public sealed class RobustServerPackaging
|
||||
string contentDir,
|
||||
AssetPass pass,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
await WriteServerResources(contentDir, pass, new HashSet<string>(), cancel);
|
||||
}
|
||||
|
||||
public static async Task WriteServerResources(
|
||||
string contentDir,
|
||||
AssetPass pass,
|
||||
IReadOnlySet<string> additionalIgnoredResources,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var ignoreSet = ServerIgnoresResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
|
||||
|
||||
await RobustSharedPackaging.DoResourceCopy(
|
||||
Path.Combine(contentDir, "Resources"),
|
||||
pass,
|
||||
ignoreSet.Union(additionalIgnoredResources).ToHashSet(),
|
||||
ignoreSet,
|
||||
cancel: cancel);
|
||||
|
||||
await RobustSharedPackaging.DoResourceCopy(
|
||||
|
||||
@@ -43,22 +43,4 @@ public static class AttributeHelper
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static bool HasAttribute(
|
||||
INamedTypeSymbol symbol,
|
||||
INamedTypeSymbol attribute,
|
||||
[NotNullWhen(true)] out AttributeData? matchedAttribute)
|
||||
{
|
||||
matchedAttribute = null;
|
||||
foreach (var typeAttribute in symbol.GetAttributes())
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(typeAttribute.AttributeClass, attribute))
|
||||
{
|
||||
matchedAttribute = typeAttribute;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ public static class Diagnostics
|
||||
public const string IdObsoleteInheritance = "RA0034";
|
||||
public const string IdObsoleteInheritanceWithMessage = "RA0035";
|
||||
public const string IdDataFieldYamlSerializable = "RA0036";
|
||||
public const string IdPrototypeNetSerializable = "RA0037";
|
||||
public const string IdPrototypeSerializable = "RA0038";
|
||||
public const string IdPrototypeInstantiation = "RA0039";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Robust.Roslyn.Shared;
|
||||
|
||||
public static class TypeSymbolHelper
|
||||
{
|
||||
public static bool ShittyTypeMatch(ITypeSymbol type, string attributeMetadataName)
|
||||
public static bool ShittyTypeMatch(INamedTypeSymbol type, string attributeMetadataName)
|
||||
{
|
||||
// Doing it like this only allocates when the type actually matches, which is good enough for me right now.
|
||||
if (!attributeMetadataName.EndsWith(type.Name))
|
||||
@@ -15,7 +15,7 @@ public static class TypeSymbolHelper
|
||||
return type.ToDisplayString() == attributeMetadataName;
|
||||
}
|
||||
|
||||
public static bool ImplementsInterface(ITypeSymbol type, string interfaceTypeName)
|
||||
public static bool ImplementsInterface(INamedTypeSymbol type, string interfaceTypeName)
|
||||
{
|
||||
foreach (var interfaceType in type.AllInterfaces)
|
||||
{
|
||||
@@ -25,33 +25,4 @@ public static class TypeSymbolHelper
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ImplementsInterface(ITypeSymbol type, INamedTypeSymbol interfaceType)
|
||||
{
|
||||
foreach (var @interface in type.AllInterfaces)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(@interface, interfaceType))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all Members of a symbol, including those that are inherited.
|
||||
/// We need this because sometimes Components have abstract parents with autonetworked datafields.
|
||||
/// </summary>
|
||||
public static IEnumerable<ISymbol> GetAllMembersIncludingInherited(INamedTypeSymbol type)
|
||||
{
|
||||
var current = type;
|
||||
while (current != null)
|
||||
{
|
||||
foreach (var member in current.GetMembers())
|
||||
{
|
||||
yield return member;
|
||||
}
|
||||
|
||||
current = current.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
LogAudioPlaybackOnInvalidEntity(specifier, coordinates.EntityId);
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
if (TerminatingOrDeleted(coordinates.EntityId))
|
||||
{
|
||||
LogAudioPlaybackOnInvalidEntity(specifier, coordinates.EntityId);
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -281,10 +281,4 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
// TODO: Yeah remove this...
|
||||
}
|
||||
|
||||
private void LogAudioPlaybackOnInvalidEntity(ResolvedSoundSpecifier? specifier, EntityUid entityId)
|
||||
{
|
||||
var soundInfo = specifier?.ToString() ?? "unknown sound";
|
||||
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entityId)}. Sound: {soundInfo}. Trace: {Environment.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return LoadMap.GetCompletionResult(shell, args, _resource, Loc);
|
||||
return LoadMap.GetCompletionResult(shell, args, _resource);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,24 +235,24 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
public override string Command => "loadmap";
|
||||
|
||||
public static CompletionResult GetCompletionResult(IConsoleShell shell, string[] args, IResourceManager resource, ILocalizationManager loc)
|
||||
public static CompletionResult GetCompletionResult(IConsoleShell shell, string[] args, IResourceManager resource)
|
||||
{
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
return CompletionResult.FromHint(loc.GetString("cmd-hint-savemap-id"));
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-savemap-id"));
|
||||
case 2:
|
||||
var opts = CompletionHelper.UserFilePath(args[1], resource.UserData)
|
||||
.Concat(CompletionHelper.ContentFilePath(args[1], resource));
|
||||
return CompletionResult.FromHintOptions(opts, loc.GetString("cmd-hint-savemap-path"));
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-savemap-path"));
|
||||
case 3:
|
||||
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-x-position"));
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-x-position"));
|
||||
case 4:
|
||||
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-y-position"));
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-y-position"));
|
||||
case 5:
|
||||
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-rotation"));
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-rotation"));
|
||||
case 6:
|
||||
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-uids"));
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-uids"));
|
||||
}
|
||||
|
||||
return CompletionResult.Empty;
|
||||
@@ -260,7 +260,7 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return GetCompletionResult(shell, args, _resource, Loc);
|
||||
return GetCompletionResult(shell, args, _resource);
|
||||
}
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
foreach (var uid in toDelete)
|
||||
{
|
||||
Del(uid);
|
||||
EntityManager.DeleteEntity(uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace Robust.Server.GameObjects
|
||||
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
|
||||
return;
|
||||
|
||||
Del(args.GridId);
|
||||
EntityManager.DeleteEntity(args.GridId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem
|
||||
public override void AddViewSubscriber(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
// If the entity doesn't have the component, it will be added.
|
||||
var viewSubscriber = EnsureComp<Shared.GameObjects.ViewSubscriberComponent>(uid);
|
||||
var viewSubscriber = EntityManager.EnsureComponent<Shared.GameObjects.ViewSubscriberComponent>(uid);
|
||||
|
||||
if (viewSubscriber.SubscribedSessions.Contains(session))
|
||||
return; // Already subscribed, do nothing else.
|
||||
@@ -36,7 +36,7 @@ public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem
|
||||
/// </summary>
|
||||
public override void RemoveViewSubscriber(EntityUid uid, ICommonSession session)
|
||||
{
|
||||
if(!TryComp(uid, out Shared.GameObjects.ViewSubscriberComponent? viewSubscriber))
|
||||
if(!EntityManager.TryGetComponent(uid, out Shared.GameObjects.ViewSubscriberComponent? viewSubscriber))
|
||||
return; // Entity didn't have any subscriptions, do nothing.
|
||||
|
||||
if (!viewSubscriber.SubscribedSessions.Remove(session))
|
||||
|
||||
@@ -32,7 +32,7 @@ internal sealed partial class PvsSystem
|
||||
|
||||
if (component.Deleted || !component.Initialized)
|
||||
{
|
||||
Log.Error($"Entity manager returned deleted or uninitialized component of type {component.GetType()} on entity {ToPrettyString(entityUid)} while generating entity state data for {player?.Name ?? "replay"}");
|
||||
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ internal sealed partial class PvsSystem
|
||||
|
||||
if (enumerateAll)
|
||||
{
|
||||
var query = AllEntityQuery<MetaDataComponent>();
|
||||
var query = EntityManager.AllEntityQueryEnumerator<MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var md))
|
||||
{
|
||||
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized, $"Entity {ToPrettyString(uid)} has not been initialized");
|
||||
|
||||
@@ -199,7 +199,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
foreach (var uid in _toDelete)
|
||||
{
|
||||
QueueDel(uid);
|
||||
EntityManager.QueueDeleteEntity(uid);
|
||||
}
|
||||
_toDelete.Clear();
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Robust.Server.Player
|
||||
{
|
||||
foreach (var uid in entities)
|
||||
{
|
||||
if (TryComp(uid, out ActorComponent? actor))
|
||||
if (EntityManager.TryGetComponent(uid, out ActorComponent? actor))
|
||||
filter.AddPlayer(actor.PlayerSession);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
@@ -189,20 +188,13 @@ namespace Robust.Server.Scripting
|
||||
}
|
||||
|
||||
// Compile ahead of time so that we can do syntax highlighting correctly for the echo.
|
||||
await Task.Run(() =>
|
||||
{
|
||||
newScript.Compile();
|
||||
newScript.Compile();
|
||||
|
||||
// Echo entered script.
|
||||
var echoMessage = new FormattedMessage();
|
||||
ScriptInstanceShared.AddWithSyntaxHighlighting(
|
||||
newScript,
|
||||
echoMessage,
|
||||
code,
|
||||
instance.HighlightWorkspace.Value);
|
||||
// Echo entered script.
|
||||
var echoMessage = new FormattedMessage();
|
||||
ScriptInstanceShared.AddWithSyntaxHighlighting(newScript, echoMessage, code, instance.HighlightWorkspace);
|
||||
|
||||
replyMessage.Echo = echoMessage;
|
||||
});
|
||||
replyMessage.Echo = echoMessage;
|
||||
|
||||
var msg = new FormattedMessage();
|
||||
|
||||
@@ -340,7 +332,7 @@ namespace Robust.Server.Scripting
|
||||
|
||||
private sealed class ScriptInstance
|
||||
{
|
||||
public Lazy<Workspace> HighlightWorkspace { get; } = new(() => new AdhocWorkspace());
|
||||
public Workspace HighlightWorkspace { get; } = new AdhocWorkspace();
|
||||
public StringBuilder InputBuffer { get; } = new();
|
||||
public FormattedMessage OutputBuffer { get; } = new();
|
||||
public bool RunningScript { get; set; }
|
||||
@@ -381,7 +373,7 @@ namespace Robust.Server.Scripting
|
||||
script.Compile();
|
||||
|
||||
var syntax = new FormattedMessage();
|
||||
ScriptInstanceShared.AddWithSyntaxHighlighting(script, syntax, code, _scriptInstance.HighlightWorkspace.Value);
|
||||
ScriptInstanceShared.AddWithSyntaxHighlighting(script, syntax, code, _scriptInstance.HighlightWorkspace);
|
||||
|
||||
_scriptInstance.OutputBuffer.AddMessage(syntax);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Server.ViewVariables.Traits;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Log;
|
||||
@@ -136,10 +135,6 @@ namespace Robust.Server.ViewVariables
|
||||
dynamic kv = value;
|
||||
value = kvPair.Key ? kv.Key : kv.Value;
|
||||
break;
|
||||
case ViewVariablesTupleIndexSelector indexSelector
|
||||
when value is ITuple tuple:
|
||||
value = indexSelector.Index <= tuple.Length - 1 ? tuple[indexSelector.Index] : null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Server.ViewVariables.Traits;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -142,19 +141,6 @@ namespace Robust.Server.ViewVariables
|
||||
};
|
||||
}
|
||||
|
||||
// Handle ValueTuples
|
||||
if (typeof(ITuple).IsAssignableFrom(valType))
|
||||
{
|
||||
var tuple = (ITuple)value;
|
||||
var items = new object?[tuple.Length];
|
||||
for (var i = 0; i < tuple.Length; i++)
|
||||
{
|
||||
items[i] = MakeValueNetSafe(tuple[i]);
|
||||
}
|
||||
|
||||
return new ViewVariablesBlobMembers.ServerTupleToken { Items = items };
|
||||
}
|
||||
|
||||
// Can't send this value type over the wire.
|
||||
return new ViewVariablesBlobMembers.ServerValueTypeToken
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using static Microsoft.CodeAnalysis.SymbolDisplayFormat;
|
||||
using static Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
// Yes dude I know this source generator isn't incremental, I'll fix it eventually.
|
||||
#pragma warning disable RS1035
|
||||
@@ -36,7 +35,6 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
private const string GlobalDictionaryName = "global::System.Collections.Generic.Dictionary<TKey, TValue>";
|
||||
private const string GlobalHashSetName = "global::System.Collections.Generic.HashSet<T>";
|
||||
private const string GlobalListName = "global::System.Collections.Generic.List<T>";
|
||||
private const string GlobalIRobustCloneableName = "global::Robust.Shared.Serialization.IRobustCloneable";
|
||||
|
||||
private static readonly SymbolDisplayFormat FullNullableFormat =
|
||||
FullyQualifiedFormat.WithMiscellaneousOptions(IncludeNullableReferenceTypeModifier);
|
||||
@@ -49,7 +47,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
var componentName = classSymbol.Name;
|
||||
var stateName = $"{componentName}_AutoState";
|
||||
|
||||
var members = TypeSymbolHelper.GetAllMembersIncludingInherited(classSymbol);
|
||||
var members = classSymbol.GetMembers();
|
||||
var fields = new List<(ITypeSymbol Type, string FieldName)>();
|
||||
var fieldAttr = comp.GetTypeByMetadataName(MemberAttributeName);
|
||||
|
||||
@@ -377,39 +375,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
stateFields.Append($@"
|
||||
public {networkedType} {name} = default!;");
|
||||
|
||||
if (ImplementsInterface(type, GlobalIRobustCloneableName))
|
||||
{
|
||||
getField = $"component.{name}";
|
||||
cast = $"({castString})";
|
||||
|
||||
var nullCast = nullable ? castString.Substring(0, castString.Length - 1) : castString;
|
||||
|
||||
if (nullable)
|
||||
{
|
||||
handleStateSetters.Append($@"
|
||||
component.{name} = state.{name} == null ? null! : state.{name}.Clone();");
|
||||
deltaHandleFields.Append($@"
|
||||
var {name}Value = {cast} {fieldHandleValue};
|
||||
if ({name}Value == null)
|
||||
component.{name} = null!;
|
||||
else
|
||||
component.{name} = {nullCast}({name}Value.Clone());");
|
||||
shallowClone.Append($@"
|
||||
{name} = this.{name},");
|
||||
deltaApply.Add($"fullState.{name} = {name} == null ? null! : {name}.Clone();");
|
||||
}
|
||||
else
|
||||
{
|
||||
handleStateSetters.Append($@"
|
||||
component.{name} = state.{name}.Clone();");
|
||||
deltaHandleFields.Append($@"
|
||||
component.{name} = {cast}({fieldHandleValue}.Clone());");
|
||||
shallowClone.Append($@"
|
||||
{name} = this.{name},");
|
||||
deltaApply.Add($"fullState.{name} = {name}.Clone();");
|
||||
}
|
||||
}
|
||||
else if (IsCloneType(type))
|
||||
if (IsCloneType(type))
|
||||
{
|
||||
getField = $"component.{name}";
|
||||
cast = $"({castString})";
|
||||
@@ -716,7 +682,7 @@ public partial class {componentName}{deltaInterface}
|
||||
|
||||
if (relevantAttribute == null)
|
||||
{
|
||||
foreach (var mem in TypeSymbolHelper.GetAllMembersIncludingInherited(typeSymbol))
|
||||
foreach (var mem in typeSymbol.GetMembers())
|
||||
{
|
||||
var attribute = mem.GetAttributes().FirstOrDefault(a =>
|
||||
a.AttributeClass != null &&
|
||||
@@ -792,19 +758,5 @@ public partial class {componentName}{deltaInterface}
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static bool ImplementsInterface(ITypeSymbol type, string interfaceName)
|
||||
{
|
||||
foreach (var interfaceType in type.AllInterfaces)
|
||||
{
|
||||
if (interfaceType.ToDisplayString(FullyQualifiedFormat).Contains(interfaceName)
|
||||
|| interfaceType.ConstructedFrom.ToDisplayString(FullyQualifiedFormat).Contains(interfaceName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,12 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
using SysVector3 = System.Numerics.Vector3;
|
||||
using SysVector4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
@@ -68,7 +69,7 @@ namespace Robust.Shared.Maths
|
||||
/// Vector representation, for easy SIMD operations.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public readonly Vector4 RGBA => Unsafe.BitCast<Color, Vector4>(this);
|
||||
public readonly SysVector4 RGBA => Unsafe.BitCast<Color, SysVector4>(this);
|
||||
|
||||
public readonly byte RByte => (byte) (R * byte.MaxValue);
|
||||
public readonly byte GByte => (byte) (G * byte.MaxValue);
|
||||
@@ -93,9 +94,9 @@ namespace Robust.Shared.Maths
|
||||
/// <summary>
|
||||
/// Constructs a new Color structure from the components in a <see cref="SysVector4"/>.
|
||||
/// </summary>
|
||||
public Color(in Vector4 vec)
|
||||
public Color(in SysVector4 vec)
|
||||
{
|
||||
this = Unsafe.BitCast<Vector4, Color>(vec);
|
||||
this = Unsafe.BitCast<SysVector4, Color>(vec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -590,125 +591,6 @@ namespace Robust.Shared.Maths
|
||||
return new Vector4(hue, saturation, max, rgb.A);
|
||||
}
|
||||
|
||||
#region Oklab/Oklch
|
||||
/*
|
||||
|
||||
The code in this region is based off of https://bottosson.github.io/posts/oklab/, available under public domain or the MIT license.
|
||||
|
||||
Copyright (c) 2020 Björn Ottosson
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Converts linear sRGB color values to Oklab color values.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the converted color value.
|
||||
/// The X element is L (lightness), the Y element is a (green-red), the Z element is b (blue-yellow), and the W element is Alpha
|
||||
/// (a copy of the input's Alpha value).
|
||||
/// L and Alpha have a range of 0 to 1, while a and b are unbounded, but roughly -0.5 to 0.5
|
||||
/// </returns>
|
||||
/// <param name="srgb">Linear sRGB color value to convert. <see cref="FromSrgb"/> to convert an sRGB color into linear sRGB.</param>
|
||||
public static Vector4 ToLab(Color srgb)
|
||||
{
|
||||
// convert from srgb to linear lms
|
||||
|
||||
var l = 0.4122214708f * srgb.R + 0.5363325363f * srgb.G + 0.0514459929f * srgb.B;
|
||||
var m = 0.2119034982f * srgb.R + 0.6806995451f * srgb.G + 0.1073969566f * srgb.B;
|
||||
var s = 0.0883024619f * srgb.R + 0.2817188376f * srgb.G + 0.6299787005f * srgb.B;
|
||||
|
||||
// convert from linear lms to non-linear lms
|
||||
|
||||
var l_ = MathF.Cbrt(l);
|
||||
var m_ = MathF.Cbrt(m);
|
||||
var s_ = MathF.Cbrt(s);
|
||||
|
||||
// convert from non-linear lms to lab
|
||||
|
||||
return new Vector4(
|
||||
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
|
||||
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
|
||||
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
|
||||
srgb.A
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Oklab color values to linear sRGB color values.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the converted color value. <see cref="ToSrgb"/> to convert an sRGB color into linear sRGB.
|
||||
/// </returns>
|
||||
/// <param name="oklab">Oklab color value to convert.</param>
|
||||
public static Color FromLab(Vector4 oklab)
|
||||
{
|
||||
var l_ = oklab.X + 0.3963377774f * oklab.Y + 0.2158037573f * oklab.Z;
|
||||
var m_ = oklab.X - 0.1055613458f * oklab.Y - 0.0638541728f * oklab.Z;
|
||||
var s_ = oklab.X - 0.0894841775f * oklab.Y - 1.2914855480f * oklab.Z;
|
||||
|
||||
// convert from non-linear lms to linear lms
|
||||
|
||||
var l = l_ * l_ * l_;
|
||||
var m = m_ * m_ * m_;
|
||||
var s = s_ * s_ * s_;
|
||||
|
||||
// convert from linear lms to linear srgb
|
||||
|
||||
var r = +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
|
||||
var g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
|
||||
var b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
|
||||
|
||||
return new Color(r, g, b, oklab.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts cartesian Oklab color values to polar Oklch color values.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the converted color value.
|
||||
/// </returns>
|
||||
/// <param name="oklab">Oklab color value to convert.</param>
|
||||
public static Vector4 ToLch(Vector4 oklab)
|
||||
{
|
||||
var c = MathF.Sqrt(oklab.Y * oklab.Y + oklab.Z * oklab.Z);
|
||||
var h = MathF.Atan2(oklab.Z, oklab.Y);
|
||||
if (h < 0)
|
||||
h += 2 * MathF.PI;
|
||||
|
||||
return new Vector4(oklab.X, c, h, oklab.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts polar Oklch color values to cartesian Oklab color values.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the converted color value.
|
||||
/// </returns>
|
||||
/// <param name="oklch">Oklch color value to convert.</param>
|
||||
public static Vector4 FromLch(Vector4 oklch)
|
||||
{
|
||||
var a = oklch.Y * MathF.Cos(oklch.Z);
|
||||
var b = oklch.Y * MathF.Sin(oklch.Z);
|
||||
|
||||
return new Vector4(oklch.X, a, b, oklch.W);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Converts XYZ color values to RGB color values.
|
||||
/// </summary>
|
||||
@@ -904,7 +786,7 @@ namespace Robust.Shared.Maths
|
||||
var m = (1 - g - k) / (1 - k);
|
||||
var y = (1 - b - k) / (1 - k);
|
||||
|
||||
return new Vector4(c, m, y, k);
|
||||
return (c, m, y, k);
|
||||
}
|
||||
|
||||
public static Color FromCmyk(Vector4 cmyk)
|
||||
@@ -931,7 +813,7 @@ namespace Robust.Shared.Maths
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Color InterpolateBetween(Color α, Color β, float λ)
|
||||
{
|
||||
return new(Vector4.Lerp(α.RGBA, β.RGBA, λ));
|
||||
return new(SysVector4.Lerp(α.RGBA, β.RGBA, λ));
|
||||
}
|
||||
|
||||
public static Color? TryFromHex(ReadOnlySpan<char> hexColor)
|
||||
@@ -1006,10 +888,10 @@ namespace Robust.Shared.Maths
|
||||
|
||||
public static Color Blend(Color dstColor, Color srcColor, BlendFactor dstFactor, BlendFactor srcFactor)
|
||||
{
|
||||
var dst = new Vector3(dstColor.R, dstColor.G, dstColor.B);
|
||||
var src = new Vector3(srcColor.R, srcColor.G, srcColor.B);
|
||||
var dst = new SysVector3(dstColor.R, dstColor.G, dstColor.B);
|
||||
var src = new SysVector3(srcColor.R, srcColor.G, srcColor.B);
|
||||
|
||||
var ret = new Vector3();
|
||||
var ret = new SysVector3();
|
||||
|
||||
switch (dstFactor)
|
||||
{
|
||||
@@ -1022,13 +904,13 @@ namespace Robust.Shared.Maths
|
||||
ret = dst * src;
|
||||
break;
|
||||
case BlendFactor.OneMinusSrcColor:
|
||||
ret = dst * (Vector3.One - src);
|
||||
ret = dst * (SysVector3.One - src);
|
||||
break;
|
||||
case BlendFactor.DstColor:
|
||||
ret = dst * dst;
|
||||
break;
|
||||
case BlendFactor.OneMinusDstColor:
|
||||
ret = dst * (Vector3.One - dst);
|
||||
ret = dst * (SysVector3.One - dst);
|
||||
break;
|
||||
case BlendFactor.SrcAlpha:
|
||||
ret = dst * srcColor.A;
|
||||
@@ -1057,13 +939,13 @@ namespace Robust.Shared.Maths
|
||||
ret += src * src;
|
||||
break;
|
||||
case BlendFactor.OneMinusSrcColor:
|
||||
ret += src * (Vector3.One - src);
|
||||
ret += src * (SysVector3.One - src);
|
||||
break;
|
||||
case BlendFactor.DstColor:
|
||||
ret += src * dst;
|
||||
break;
|
||||
case BlendFactor.OneMinusDstColor:
|
||||
ret += src * (Vector3.One - dst);
|
||||
ret += src * (SysVector3.One - dst);
|
||||
break;
|
||||
case BlendFactor.SrcAlpha:
|
||||
ret += src * srcColor.A;
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Vec4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
@@ -529,13 +530,13 @@ namespace Robust.Shared.Maths
|
||||
/// Returns whether two vectors are within <paramref name="percentage"/> of each other
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CloseToPercent(Vector4 a, Vector4 b, float percentage = .00001f)
|
||||
public static bool CloseToPercent(Vec4 a, Vec4 b, float percentage = .00001f)
|
||||
{
|
||||
a = Vector4.Abs(a);
|
||||
b = Vector4.Abs(b);
|
||||
var p = new Vector4(percentage);
|
||||
var epsilon = Vector4.Max(Vector4.Max(a, b) * p, p);
|
||||
var delta = Vector4.Abs(a - b);
|
||||
a = Vec4.Abs(a);
|
||||
b = Vec4.Abs(b);
|
||||
var p = new Vec4(percentage);
|
||||
var epsilon = Vec4.Max(Vec4.Max(a, b) * p, p);
|
||||
var delta = Vec4.Abs(a - b);
|
||||
return delta.X <= epsilon.X && delta.Y <= epsilon.Y && delta.Z <= epsilon.Z && delta.W <= epsilon.W;
|
||||
}
|
||||
|
||||
|
||||
1239
Robust.Shared.Maths/Matrix4.cs
Normal file
1239
Robust.Shared.Maths/Matrix4.cs
Normal file
File diff suppressed because it is too large
Load Diff
978
Robust.Shared.Maths/Quaternion.cs
Normal file
978
Robust.Shared.Maths/Quaternion.cs
Normal file
@@ -0,0 +1,978 @@
|
||||
#region --- License ---
|
||||
|
||||
/*
|
||||
Copyright (c) 2006 - 2008 The Open Toolkit library.
|
||||
Copyright 2013 Xamarin Inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#endregion --- License ---
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Xml.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Quaternion.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Quaternion : IEquatable<Quaternion>, ISpanFormattable
|
||||
{
|
||||
#region Fields
|
||||
|
||||
private Vector3 xyz;
|
||||
private float w;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new Quaternion from vector and w components
|
||||
/// </summary>
|
||||
/// <param name="v">The vector part</param>
|
||||
/// <param name="w">The w part</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Quaternion(Vector3 v, float w)
|
||||
{
|
||||
xyz = v;
|
||||
this.w = w;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construct a new Quaternion
|
||||
/// </summary>
|
||||
/// <param name="x">The x component</param>
|
||||
/// <param name="y">The y component</param>
|
||||
/// <param name="z">The z component</param>
|
||||
/// <param name="w">The w component</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Quaternion(float x, float y, float z, float w)
|
||||
: this(new Vector3(x, y, z), w) { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Quaternion(ref Matrix3x2 matrix)
|
||||
{
|
||||
var scale = Math.Pow(matrix.GetDeterminant(), 1.0d / 3.0d);
|
||||
float x, y, z;
|
||||
|
||||
w = (float) (Math.Sqrt(Math.Max(0, scale + matrix[0, 0] + matrix[1, 1] + matrix[2, 2])) / 2);
|
||||
x = (float) (Math.Sqrt(Math.Max(0, scale + matrix[0, 0] - matrix[1, 1] - matrix[2, 2])) / 2);
|
||||
y = (float) (Math.Sqrt(Math.Max(0, scale - matrix[0, 0] + matrix[1, 1] - matrix[2, 2])) / 2);
|
||||
z = (float) (Math.Sqrt(Math.Max(0, scale - matrix[0, 0] - matrix[1, 1] + matrix[2, 2])) / 2);
|
||||
|
||||
xyz = new Vector3(x, y, z);
|
||||
|
||||
if (matrix[2, 1] - matrix[1, 2] < 0) X = -X;
|
||||
if (matrix[0, 2] - matrix[2, 0] < 0) Y = -Y;
|
||||
if (matrix[1, 0] - matrix[0, 1] < 0) Z = -Z;
|
||||
}
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Public Members
|
||||
|
||||
#region Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an OpenTK.Vector3 with the X, Y and Z components of this instance.
|
||||
/// </summary>
|
||||
public Vector3 Xyz
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => xyz;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => xyz = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the X component of this instance.
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
public float X
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => xyz.X;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => xyz.X = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Y component of this instance.
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
public float Y
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => xyz.Y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => xyz.Y = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Z component of this instance.
|
||||
/// </summary>
|
||||
[XmlIgnore]
|
||||
public float Z
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => xyz.Z;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => xyz.Z = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the W component of this instance.
|
||||
/// </summary>
|
||||
public float W
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => w;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => w = value;
|
||||
}
|
||||
|
||||
public float x
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => xyz.X;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => xyz.X = value;
|
||||
}
|
||||
|
||||
public float y
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => xyz.Y;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => xyz.Y = value;
|
||||
}
|
||||
|
||||
public float z
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => xyz.Z;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => xyz.Z = value;
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Instance
|
||||
|
||||
#region ToAxisAngle
|
||||
|
||||
/// <summary>
|
||||
/// Convert the current quaternion to axis angle representation
|
||||
/// </summary>
|
||||
/// <param name="axis">The resultant axis</param>
|
||||
/// <param name="angle">The resultant angle</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ToAxisAngle(out Vector3 axis, out float angle)
|
||||
{
|
||||
var result = ToAxisAngle();
|
||||
axis = result.Xyz;
|
||||
angle = result.W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert this instance to an axis-angle representation.
|
||||
/// </summary>
|
||||
/// <returns>A Vector4 that is the axis-angle representation of this quaternion.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector4 ToAxisAngle()
|
||||
{
|
||||
var q = this;
|
||||
if (Math.Abs(q.W) > 1.0f)
|
||||
q.Normalize();
|
||||
|
||||
var result = new Vector4();
|
||||
|
||||
result.W = 2.0f * (float) Math.Acos(q.W); // angle
|
||||
var den = (float) Math.Sqrt(1.0 - q.W * q.W);
|
||||
if (den > 0.0001f)
|
||||
{
|
||||
result.Xyz = q.Xyz / den;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This occurs when the angle is zero.
|
||||
// Not a problem: just set an arbitrary normalized axis.
|
||||
result.Xyz = Vector3.UnitX;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion ToAxisAngle
|
||||
|
||||
#region public float Length
|
||||
|
||||
/// <summary>
|
||||
/// Gets the length (magnitude) of the quaternion.
|
||||
/// </summary>
|
||||
/// <seealso cref="LengthSquared"/>
|
||||
public float Length
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => (float) Math.Sqrt(W * W + Xyz.LengthSquared);
|
||||
}
|
||||
|
||||
#endregion public float Length
|
||||
|
||||
#region public float LengthSquared
|
||||
|
||||
/// <summary>
|
||||
/// Gets the square of the quaternion length (magnitude).
|
||||
/// </summary>
|
||||
public float LengthSquared
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => W * W + Xyz.LengthSquared;
|
||||
}
|
||||
|
||||
#endregion public float LengthSquared
|
||||
|
||||
#region public void Normalize()
|
||||
|
||||
/// <summary>
|
||||
/// Scales the Quaternion to unit length.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Normalize()
|
||||
{
|
||||
var scale = 1.0f / Length;
|
||||
Xyz *= scale;
|
||||
W *= scale;
|
||||
}
|
||||
|
||||
#endregion public void Normalize()
|
||||
|
||||
#region public void Conjugate()
|
||||
|
||||
/// <summary>
|
||||
/// Convert this quaternion to its conjugate
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Conjugate()
|
||||
{
|
||||
Xyz = -Xyz;
|
||||
}
|
||||
|
||||
#endregion public void Conjugate()
|
||||
|
||||
#endregion Instance
|
||||
|
||||
#region Static
|
||||
|
||||
#region Fields
|
||||
|
||||
private const float RadToDeg = (float) (180.0 / Math.PI);
|
||||
private const float DegToRad = (float) (Math.PI / 180.0);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the identity quaternion.
|
||||
/// </summary>
|
||||
public static readonly Quaternion Identity = new(0, 0, 0, 1);
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Add
|
||||
|
||||
/// <summary>
|
||||
/// Add two quaternions
|
||||
/// </summary>
|
||||
/// <param name="left">The first operand</param>
|
||||
/// <param name="right">The second operand</param>
|
||||
/// <returns>The result of the addition</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Add(Quaternion left, Quaternion right)
|
||||
{
|
||||
return new(
|
||||
left.Xyz + right.Xyz,
|
||||
left.W + right.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add two quaternions
|
||||
/// </summary>
|
||||
/// <param name="left">The first operand</param>
|
||||
/// <param name="right">The second operand</param>
|
||||
/// <param name="result">The result of the addition</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Add(ref Quaternion left, ref Quaternion right, out Quaternion result)
|
||||
{
|
||||
result = new Quaternion(
|
||||
left.Xyz + right.Xyz,
|
||||
left.W + right.W);
|
||||
}
|
||||
|
||||
#endregion Add
|
||||
|
||||
#region Sub
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts two instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The left instance.</param>
|
||||
/// <param name="right">The right instance.</param>
|
||||
/// <returns>The result of the operation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Sub(Quaternion left, Quaternion right)
|
||||
{
|
||||
return new(
|
||||
left.Xyz - right.Xyz,
|
||||
left.W - right.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts two instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The left instance.</param>
|
||||
/// <param name="right">The right instance.</param>
|
||||
/// <param name="result">The result of the operation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Sub(ref Quaternion left, ref Quaternion right, out Quaternion result)
|
||||
{
|
||||
result = new Quaternion(
|
||||
left.Xyz - right.Xyz,
|
||||
left.W - right.W);
|
||||
}
|
||||
|
||||
#endregion Sub
|
||||
|
||||
#region Mult
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>A new instance containing the result of the calculation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Multiply(Quaternion left, Quaternion right)
|
||||
{
|
||||
Multiply(ref left, ref right, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <param name="result">A new instance containing the result of the calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Multiply(ref Quaternion left, ref Quaternion right, out Quaternion result)
|
||||
{
|
||||
result = new Quaternion(
|
||||
right.W * left.Xyz + left.W * right.Xyz + Vector3.Cross(left.Xyz, right.Xyz),
|
||||
left.W * right.W - Vector3.Dot(left.Xyz, right.Xyz));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies an instance by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">The instance.</param>
|
||||
/// <param name="scale">The scalar.</param>
|
||||
/// <param name="result">A new instance containing the result of the calculation.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Multiply(ref Quaternion quaternion, float scale, out Quaternion result)
|
||||
{
|
||||
result = new Quaternion(quaternion.X * scale, quaternion.Y * scale, quaternion.Z * scale, quaternion.W * scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies an instance by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">The instance.</param>
|
||||
/// <param name="scale">The scalar.</param>
|
||||
/// <returns>A new instance containing the result of the calculation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Multiply(Quaternion quaternion, float scale)
|
||||
{
|
||||
return new(quaternion.X * scale, quaternion.Y * scale, quaternion.Z * scale, quaternion.W * scale);
|
||||
}
|
||||
|
||||
#endregion Mult
|
||||
|
||||
#region Dot
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the dot product between two Quaternions.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Dot(Quaternion a, Quaternion b)
|
||||
{
|
||||
return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W;
|
||||
}
|
||||
|
||||
#endregion Dot
|
||||
|
||||
#region Conjugate
|
||||
|
||||
/// <summary>
|
||||
/// Get the conjugate of the given quaternion
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion</param>
|
||||
/// <returns>The conjugate of the given quaternion</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Conjugate(Quaternion q)
|
||||
{
|
||||
return new(-q.Xyz, q.W);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the conjugate of the given quaternion
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion</param>
|
||||
/// <param name="result">The conjugate of the given quaternion</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Conjugate(ref Quaternion q, out Quaternion result)
|
||||
{
|
||||
result = new Quaternion(-q.Xyz, q.W);
|
||||
}
|
||||
|
||||
#endregion Conjugate
|
||||
|
||||
#region Invert
|
||||
|
||||
/// <summary>
|
||||
/// Get the inverse of the given quaternion
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion to invert</param>
|
||||
/// <returns>The inverse of the given quaternion</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Invert(Quaternion q)
|
||||
{
|
||||
Invert(ref q, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the inverse of the given quaternion
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion to invert</param>
|
||||
/// <param name="result">The inverse of the given quaternion</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Invert(ref Quaternion q, out Quaternion result)
|
||||
{
|
||||
var lengthSq = q.LengthSquared;
|
||||
if (lengthSq != 0.0)
|
||||
{
|
||||
var i = 1.0f / lengthSq;
|
||||
result = new Quaternion(q.Xyz * -i, q.W * i);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = q;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Invert
|
||||
|
||||
#region Normalize
|
||||
|
||||
/// <summary>
|
||||
/// Scale the given quaternion to unit length
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion to normalize</param>
|
||||
/// <returns>The normalized quaternion</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Normalize(Quaternion q)
|
||||
{
|
||||
Normalize(ref q, out var result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale the given quaternion to unit length
|
||||
/// </summary>
|
||||
/// <param name="q">The quaternion to normalize</param>
|
||||
/// <param name="result">The normalized quaternion</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Normalize(ref Quaternion q, out Quaternion result)
|
||||
{
|
||||
var scale = 1.0f / q.Length;
|
||||
result = new Quaternion(q.Xyz * scale, q.W * scale);
|
||||
}
|
||||
|
||||
#endregion Normalize
|
||||
|
||||
#region FromAxisAngle
|
||||
|
||||
/// <summary>
|
||||
/// Build a quaternion from the given axis and angle
|
||||
/// </summary>
|
||||
/// <param name="axis">The axis to rotate about</param>
|
||||
/// <param name="angle">The rotation angle in radians</param>
|
||||
/// <returns></returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion FromAxisAngle(Vector3 axis, float angle)
|
||||
{
|
||||
if (axis.LengthSquared == 0.0f)
|
||||
return Identity;
|
||||
|
||||
var result = Identity;
|
||||
|
||||
angle *= 0.5f;
|
||||
axis.Normalize();
|
||||
result.Xyz = axis * (float) Math.Sin(angle);
|
||||
result.W = (float) Math.Cos(angle);
|
||||
|
||||
return Normalize(result);
|
||||
}
|
||||
|
||||
#endregion FromAxisAngle
|
||||
|
||||
#region Slerp
|
||||
|
||||
/// <summary>
|
||||
/// Do Spherical linear interpolation between two quaternions
|
||||
/// </summary>
|
||||
/// <param name="q1">The first quaternion</param>
|
||||
/// <param name="q2">The second quaternion</param>
|
||||
/// <param name="blend">The blend factor</param>
|
||||
/// <returns>A smooth blend between the given quaternions</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion Slerp(Quaternion q1, Quaternion q2, float blend)
|
||||
{
|
||||
// if either input is zero, return the other.
|
||||
if (q1.LengthSquared == 0.0f)
|
||||
{
|
||||
if (q2.LengthSquared == 0.0f)
|
||||
{
|
||||
return Identity;
|
||||
}
|
||||
|
||||
return q2;
|
||||
}
|
||||
|
||||
if (q2.LengthSquared == 0.0f)
|
||||
{
|
||||
return q1;
|
||||
}
|
||||
|
||||
var cosHalfAngle = q1.W * q2.W + Vector3.Dot(q1.Xyz, q2.Xyz);
|
||||
|
||||
if (cosHalfAngle >= 1.0f || cosHalfAngle <= -1.0f)
|
||||
{
|
||||
// angle = 0.0f, so just return one input.
|
||||
return q1;
|
||||
}
|
||||
|
||||
if (cosHalfAngle < 0.0f)
|
||||
{
|
||||
q2.Xyz = -q2.Xyz;
|
||||
q2.W = -q2.W;
|
||||
cosHalfAngle = -cosHalfAngle;
|
||||
}
|
||||
|
||||
float blendA;
|
||||
float blendB;
|
||||
if (cosHalfAngle < 0.99f)
|
||||
{
|
||||
// do proper slerp for big angles
|
||||
var halfAngle = (float) Math.Acos(cosHalfAngle);
|
||||
var sinHalfAngle = (float) Math.Sin(halfAngle);
|
||||
var oneOverSinHalfAngle = 1.0f / sinHalfAngle;
|
||||
blendA = (float) Math.Sin(halfAngle * (1.0f - blend)) * oneOverSinHalfAngle;
|
||||
blendB = (float) Math.Sin(halfAngle * blend) * oneOverSinHalfAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
// do lerp if angle is really small.
|
||||
blendA = 1.0f - blend;
|
||||
blendB = blend;
|
||||
}
|
||||
|
||||
var result = new Quaternion(blendA * q1.Xyz + blendB * q2.Xyz, blendA * q1.W + blendB * q2.W);
|
||||
if (result.LengthSquared > 0.0f)
|
||||
return Normalize(result);
|
||||
return Identity;
|
||||
}
|
||||
|
||||
#endregion Slerp
|
||||
|
||||
#region RotateTowards
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion RotateTowards(Quaternion from, Quaternion to, float maxDegreesDelta)
|
||||
{
|
||||
var num = Angle(from, to);
|
||||
if (num == 0f)
|
||||
{
|
||||
return to;
|
||||
}
|
||||
|
||||
var t = MathF.Min(1f, maxDegreesDelta / num);
|
||||
return Slerp(from, to, t);
|
||||
}
|
||||
|
||||
#endregion RotateTowards
|
||||
|
||||
#region Angle
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float Angle(Quaternion a, Quaternion b)
|
||||
{
|
||||
var f = Dot(a, b);
|
||||
return (float) (Math.Acos(Math.Min(Math.Abs(f), 1f)) * 2f * RadToDeg);
|
||||
}
|
||||
|
||||
#endregion Angle
|
||||
|
||||
#region LookRotation
|
||||
|
||||
// from http://answers.unity3d.com/questions/467614/what-is-the-source-code-of-quaternionlookrotation.html
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion LookRotation(ref Vector3 forward, ref Vector3 up)
|
||||
{
|
||||
forward = Vector3.Normalize(forward);
|
||||
var right = Vector3.Normalize(Vector3.Cross(up, forward));
|
||||
up = Vector3.Cross(forward, right);
|
||||
var m00 = right.X;
|
||||
var m01 = right.Y;
|
||||
var m02 = right.Z;
|
||||
var m10 = up.X;
|
||||
var m11 = up.Y;
|
||||
var m12 = up.Z;
|
||||
var m20 = forward.X;
|
||||
var m21 = forward.Y;
|
||||
var m22 = forward.Z;
|
||||
|
||||
var num8 = m00 + m11 + m22;
|
||||
var quaternion = new Quaternion();
|
||||
if (num8 > 0f)
|
||||
{
|
||||
var num = MathF.Sqrt(num8 + 1f);
|
||||
quaternion.w = num * 0.5f;
|
||||
num = 0.5f / num;
|
||||
quaternion.X = (m12 - m21) * num;
|
||||
quaternion.Y = (m20 - m02) * num;
|
||||
quaternion.Z = (m01 - m10) * num;
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
if (m00 >= m11 && m00 >= m22)
|
||||
{
|
||||
var num7 = MathF.Sqrt(1f + m00 - m11 - m22);
|
||||
var num4 = 0.5f / num7;
|
||||
quaternion.X = 0.5f * num7;
|
||||
quaternion.Y = (m01 + m10) * num4;
|
||||
quaternion.Z = (m02 + m20) * num4;
|
||||
quaternion.W = (m12 - m21) * num4;
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
if (m11 > m22)
|
||||
{
|
||||
var num6 = MathF.Sqrt(1f + m11 - m00 - m22);
|
||||
var num3 = 0.5f / num6;
|
||||
quaternion.X = (m10 + m01) * num3;
|
||||
quaternion.Y = 0.5f * num6;
|
||||
quaternion.Z = (m21 + m12) * num3;
|
||||
quaternion.W = (m20 - m02) * num3;
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
var num5 = MathF.Sqrt(1f + m22 - m00 - m11);
|
||||
var num2 = 0.5f / num5;
|
||||
quaternion.X = (m20 + m02) * num2;
|
||||
quaternion.Y = (m21 + m12) * num2;
|
||||
quaternion.Z = 0.5f * num5;
|
||||
quaternion.W = (m01 - m10) * num2;
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
#endregion LookRotation
|
||||
|
||||
#region Euler Angles
|
||||
|
||||
// from http://stackoverflow.com/questions/12088610/conversion-between-euler-quaternion-like-in-unity3d-engine
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector3 ToEulerRad(Quaternion rotation)
|
||||
{
|
||||
var sqw = rotation.w * rotation.w;
|
||||
var sqx = rotation.x * rotation.x;
|
||||
var sqy = rotation.y * rotation.y;
|
||||
var sqz = rotation.z * rotation.z;
|
||||
var unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor
|
||||
var test = rotation.x * rotation.w - rotation.y * rotation.z;
|
||||
Vector3 v;
|
||||
|
||||
if (test > 0.4995f * unit)
|
||||
{
|
||||
// singularity at north pole
|
||||
v.Y = 2f * MathF.Atan2(rotation.y, rotation.x);
|
||||
v.X = (float) (Math.PI / 2);
|
||||
v.Z = 0;
|
||||
return NormalizeAngles(v * RadToDeg);
|
||||
}
|
||||
|
||||
if (test < -0.4995f * unit)
|
||||
{
|
||||
// singularity at south pole
|
||||
v.Y = -2f * MathF.Atan2(rotation.y, rotation.x);
|
||||
v.X = (float) (-Math.PI / 2);
|
||||
v.Z = 0;
|
||||
return NormalizeAngles(v * RadToDeg);
|
||||
}
|
||||
|
||||
var q = new Quaternion(rotation.w, rotation.z, rotation.x, rotation.y);
|
||||
v.Y = MathF.Atan2(2f * q.x * q.w + 2f * q.y * q.z, 1 - 2f * (q.z * q.z + q.w * q.w)); // Yaw
|
||||
v.X = MathF.Asin(2f * (q.x * q.z - q.w * q.y)); // Pitch
|
||||
v.Z = MathF.Atan2(2f * q.x * q.y + 2f * q.z * q.w, 1 - 2f * (q.y * q.y + q.z * q.z)); // Roll
|
||||
return NormalizeAngles(v * RadToDeg);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static Vector3 NormalizeAngles(Vector3 angles)
|
||||
{
|
||||
angles.X = NormalizeAngle(angles.X);
|
||||
angles.Y = NormalizeAngle(angles.Y);
|
||||
angles.Z = NormalizeAngle(angles.Z);
|
||||
return angles;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static float NormalizeAngle(float angle)
|
||||
{
|
||||
/*
|
||||
while (angle > 360)
|
||||
angle -= 360;
|
||||
while (angle < 0)
|
||||
angle += 360;
|
||||
return angle;
|
||||
|
||||
asm:
|
||||
|
||||
L0000: vzeroupper
|
||||
L0003: vucomiss xmm0, [fld 360f]
|
||||
L000b: jbe short L001f
|
||||
L000d: vsubss xmm0, xmm0, [fld 360f]
|
||||
L0015: vucomiss xmm0, [fld 0]
|
||||
L001d: ja short L000d
|
||||
L001f: vxorps xmm1, xmm1, xmm1
|
||||
L0023: vucomiss xmm1, xmm0
|
||||
L0027: jbe short L003b
|
||||
L0029: vaddss xmm0, xmm0, [fld 360f]
|
||||
L0031: vxorps xmm1, xmm1, xmm1
|
||||
L0035: vucomiss xmm1, xmm0
|
||||
L0039: ja short L0029
|
||||
L003b: ret
|
||||
|
||||
*/
|
||||
|
||||
return angle - MathF.Floor(angle * (1/360f)) * 360f;
|
||||
/* asm:
|
||||
L0000: vzeroupper
|
||||
L0003: vmovaps xmm1, xmm0
|
||||
L0007: vmulss xmm1, xmm1, [fld 1/360f]
|
||||
L000f: vroundss xmm1, xmm1, xmm1, 9
|
||||
L0015: vmulss xmm1, xmm1, [fld 360f]
|
||||
L001d: vsubss xmm0, xmm0, xmm1
|
||||
L0021: ret
|
||||
*/
|
||||
}
|
||||
|
||||
#endregion Euler Angles
|
||||
|
||||
#endregion Static
|
||||
|
||||
#region Operators
|
||||
|
||||
/// <summary>
|
||||
/// Adds two instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>The result of the calculation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion operator +(Quaternion left, Quaternion right)
|
||||
{
|
||||
left.Xyz += right.Xyz;
|
||||
left.W += right.W;
|
||||
return left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts two instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>The result of the calculation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion operator -(Quaternion left, Quaternion right)
|
||||
{
|
||||
left.Xyz -= right.Xyz;
|
||||
left.W -= right.W;
|
||||
return left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies two instances.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>The result of the calculation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion operator *(Quaternion left, Quaternion right)
|
||||
{
|
||||
Multiply(ref left, ref right, out left);
|
||||
return left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies an instance by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">The instance.</param>
|
||||
/// <param name="scale">The scalar.</param>
|
||||
/// <returns>A new instance containing the result of the calculation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion operator *(Quaternion quaternion, float scale)
|
||||
{
|
||||
Multiply(ref quaternion, scale, out quaternion);
|
||||
return quaternion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies an instance by a scalar.
|
||||
/// </summary>
|
||||
/// <param name="quaternion">The instance.</param>
|
||||
/// <param name="scale">The scalar.</param>
|
||||
/// <returns>A new instance containing the result of the calculation.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Quaternion operator *(float scale, Quaternion quaternion)
|
||||
{
|
||||
return new(quaternion.X * scale, quaternion.Y * scale, quaternion.Z * scale, quaternion.W * scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two instances for equality.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>True, if left equals right; false otherwise.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(Quaternion left, Quaternion right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two instances for inequality.
|
||||
/// </summary>
|
||||
/// <param name="left">The first instance.</param>
|
||||
/// <param name="right">The second instance.</param>
|
||||
/// <returns>True, if left does not equal right; false otherwise.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(Quaternion left, Quaternion right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
#endregion Operators
|
||||
|
||||
#region Overrides
|
||||
|
||||
#region public override string ToString()
|
||||
|
||||
/// <summary>
|
||||
/// Returns a System.String that represents the current Quaternion.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return $"V: {xyz}, W: {w}";
|
||||
}
|
||||
|
||||
public readonly string ToString(string? format, IFormatProvider? formatProvider)
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
|
||||
public readonly bool TryFormat(
|
||||
Span<char> destination,
|
||||
out int charsWritten,
|
||||
ReadOnlySpan<char> format,
|
||||
IFormatProvider? provider)
|
||||
{
|
||||
return FormatHelpers.TryFormatInto(
|
||||
destination,
|
||||
out charsWritten,
|
||||
$"V: {xyz}, W: {w}");
|
||||
}
|
||||
|
||||
#endregion public override string ToString()
|
||||
|
||||
#region public override bool Equals (object o)
|
||||
|
||||
/// <summary>
|
||||
/// Compares this object instance to another object for equality.
|
||||
/// </summary>
|
||||
/// <param name="obj">The other object to be used in the comparison.</param>
|
||||
/// <returns>True if both objects are Quaternions of equal value. Otherwise it returns false.</returns>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is Quaternion quaternion) return this == quaternion;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion public override bool Equals (object o)
|
||||
|
||||
#region public override int GetHashCode ()
|
||||
|
||||
/// <summary>
|
||||
/// Provides the hash code for this object.
|
||||
/// </summary>
|
||||
/// <returns>A hash code formed from the bitwise XOR of this objects members.</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Xyz.GetHashCode() ^ W.GetHashCode();
|
||||
}
|
||||
|
||||
#endregion public override int GetHashCode ()
|
||||
|
||||
#endregion Overrides
|
||||
|
||||
#endregion Public Members
|
||||
|
||||
#region IEquatable<Quaternion> Members
|
||||
|
||||
/// <summary>
|
||||
/// Compares this Quaternion instance to another Quaternion for equality.
|
||||
/// </summary>
|
||||
/// <param name="other">The other Quaternion to be used in the comparison.</param>
|
||||
/// <returns>True if both instances are equal; false otherwise.</returns>
|
||||
public bool Equals(Quaternion other)
|
||||
{
|
||||
return Xyz == other.Xyz && W == other.W;
|
||||
}
|
||||
|
||||
#endregion IEquatable<Quaternion> Members
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user