Compare commits

..

49 Commits

Author SHA1 Message Date
PJB3005
51c929c8ec Version: 265.0.0 2025-07-23 01:47:41 +02:00
PJB3005
4863b09f0a Update release notes 2025-07-23 01:47:16 +02:00
Pieter-Jan Briers
cdd3afaa4c Remove redundant custom math types (#6078)
Vector3, Vector4, Matrix4, and Quaternion are now gone. Use System.Numerics instead.

This commit is just replacing usages, cleaning up using declarations, and moving over the (couple) helpers that are actually important.
2025-07-23 01:15:27 +02:00
slarticodefast
fee67b648c Allow AutoNetworkedField to work with inherited datafields (#6090)
* allow AutoNetworkedField to work for inherited datafields

* fix

* test fix

* typo

* Update Robust.UnitTesting/Shared/GameState/AutoNetworkingTest.cs

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-07-23 01:15:12 +02:00
Perry Fraser
0bf4123b8d feat: Add VV editor for tuples (#6065)
* feat: Add VV editor for tuples

* refactor: make tuple editor work in more cases

* feat: support other arity tuples

* fix: correct release notes entry

* refactor: use a new index selector for tuples

Also yank out silly unused code.

* fix: make all non-ValueTuples readonly

* refactor: spell out ValueTuple arities

,,,,,,,,,,,,,,,,,,,,,
2025-07-22 22:31:19 +02:00
Tayrtahn
9ea51432d1 Un-hardcode EntitySpawnWindow's placement mode dropdown (#5994)
* Find PlacementModes by attribute

* Let modes specify priority

* Make some PlacementManager dependencies public so Content can use them

* Space out the priorities a bit more

* xmldoc for attribute

* Revert "xmldoc for attribute"

This reverts commit f1f0299c55.

* Revert "Space out the priorities a bit more"

This reverts commit 549eac1eb2.

* Revert "Make some PlacementManager dependencies public so Content can use them"

This reverts commit c060f6cb2d.

* Revert "Let modes specify priority"

This reverts commit f113b40c7f.

* Revert "Find PlacementModes by attribute"

This reverts commit 27efb6c5cf.

* Completely redo to use PlacementManager's mode dictionary

* Backwards compat

* Cache the value of AllModeNames
2025-07-22 22:14:53 +02:00
Perry Fraser
c8da6f30a3 fix: resolve remaining ResolvedSoundSpecifier warnings (#6057)
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-07-22 22:08:31 +02:00
Tayrtahn
974c1e827d Change a bunch of static Loc.GetString calls to be properly resolved (#6010)
* Add Loc shortcut to LocalizedCommands

* Fix static methods in commands

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-07-22 20:33:43 +02:00
Tayrtahn
3c48b24539 Cleanup prototype instantiation warnings in unit tests (#6058) 2025-07-22 20:32:49 +02:00
Fildrance
d8aefe5118 feat: now view-variable controls can be registered from content, or even dynamically added (#6077)
* feat: now view-variable controls can be registered from content, or even dynamically added

* refactor: whitespaces and xml-doc

* refactor: added changelog entry

* refactor: added methods for adding condition at start and at the end

* refactor: merged start/end methods, for IViewVariableControlFactory, improved changelog message

* refactor: replaced bool insertLast with InsertPosition enum

* refactor: reverse order of checks registration in ViewVariableControlFactory c-tor

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2025-07-22 19:48:12 +02:00
Tayrtahn
6d9a4719a9 Validate VV EntProtoId values (#6095) 2025-07-22 19:30:01 +02:00
Pieter-Jan Briers
893173ab17 Add workflow to build all configurations. (#6094)
* Add workflow to build all configurations.

Builds Debug, Tools, Release against Linux, Windows and MacOS TargetOS.

See https://github.com/space-wizards/RobustToolbox/pull/6069#issuecomment-3050607114

* Very epic, GitHub

* Whoops

* Actually add Tools configuration to sln
2025-07-22 19:21:21 +02:00
PJB3005
7c0f1b8031 Fix unused dependency warning outside DEBUG
Supersedes #6069
2025-07-22 18:10:49 +02:00
Tayrtahn
bb57f82811 More informative logging for PVS deleted/uninitialized entity errors (#6084) 2025-07-22 12:33:18 +02:00
Perry Fraser
0fc9b0acd0 Lint for prototype IDs with spaces (#6087)
* feat: lint for prototype IDs with spaces

* feat: also disallow periods

* Update RELEASE-NOTES.md

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2025-07-22 12:16:57 +02:00
Hannah Giovanna Dawson
f2b7f0d8d2 NoteOn actually being a NoteOff fix (#6092) 2025-07-22 12:10:58 +02:00
PJB3005
0ec189dece IPrototypeManager TryIndex changes
This effectively gracefully reverts 94f98073b0.

IPrototypeManager.TryIndex now no longer logs an error. This is done by adding a new overload without the logError parameter, so most existing code switches to it. The overload with the logError parameter is now obsolete.

As a replacement for defensive programming situations, the new Resolve() should be used instead.

IPrototypeManager.TryIndex() should not be used for handling IDs that should always be valid, only for handling user input and similar.

I also added a lot of docs.
2025-07-22 00:11:59 +02:00
PJB3005
74aa8fa9ed Fix ParallelManager cutting off exception info
0% tested
2025-07-12 23:06:35 +02:00
Nemanja
ceeb002692 Mark ValidatePrototypeIdAttribute as obsolete (#6062) 2025-07-12 15:20:08 +02:00
portfiend
78d807b13c Refactor ColorSelectorSliders.cs, fix color slider event stack overflow (#6072)
* add: IColorSelectorStrategy class

defines some common variables and functions that depend on the slider type
my hope is to kill all the switch statements in here

* add: IColorSelectorStrategy FromColorData method

* add: RGB and HSV color slider strategies

* add: initialize ColorSelectorStrategy

* refactor: rename IColorSelectorStrategy to IColorSliderStrategy

this makes more sense i think

* refactor: nuke switch statements, use strategy in colorselectorsliders

* remove: remove GetSliderLabels in favor of strategy

* refactor: better abstraction for slider InputBox.ValueChanged

* refactor: rename OnColorSet to OnSliderValueChanged
more intuitive

* refactor: turn alpha slider max value into a const
no magic numbers

* tweak: make color sliders update channels individually

* fix: add braces around this callback

* tweak: move some variables around
i realize there's an Order to this so

* add: throw error if UpdateSlider is called with invalid value

* add: documentation comments to ColorSelectorSliders

* refactor: simplify UpdateSlider

* refactor: simplify GetColorValueDivisor

* fix: solved the color slider stack overflow

* fix: ensure _strategy is set before other functions use it

* tweak: rename Update to UpdateAllSliders
clearer

* fix: update slider colors on update
accidentally removed it and forgot to put it back

* remove: redundant comment
false alarm

* fix: prevent inputbox infinite event loop
this was also erroneously changing the color whenever the slider type changed

* fix: reviews part 1
- changed ColorSliderStrategy into abstract class
- fixed "strategy" typo
- changed NotImplementedException into ArgumentOutOfRangeException

* fix: make selector strategy static instances
2025-07-12 14:51:02 +02:00
PJB3005
5cd4c187bf Fix VV member group headers 2025-07-12 02:22:04 +02:00
Perry Fraser
fec477bf41 fix: remove unneeded delta in SharedAudioSystem (#6055) 2025-07-12 01:56:22 +02:00
PJB3005
9d00b1f093 Make VV KeyValuePair prop editor have a minimum width
Avoid buttons for references being squashed to zero width.
2025-07-12 01:47:49 +02:00
PJB3005
de0871d17b Fix VV handling of remote KVPair types
There was a bunch of complex code to analyze the full type string the server sent, except I have no idea what use this was. It's both incorrect (the type string isn't guaranteed to work if the remote .NET version is different) and unnecessary as PropertyFor already handles all the cases.
2025-07-12 01:47:49 +02:00
Perry Fraser
053c469cac fix: loosen random timespan debug assert (#6064)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2025-07-11 21:36:13 +02:00
Leon Friedrich
efa8975bc6 Fix pool manager conflicts (#6075) 2025-07-11 20:55:49 +02:00
Errant
4851e913b0 More TimespanSerializer improvements (#5910)
* improved public TryTimeSpan

* don't want any locale shenanigans or misconceptions with the input

* missed a test line

* also support capitalized time unit indicators

* Doesn't need to be nullable.

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-07-11 20:37:39 +02:00
PJB3005
74a318c521 Allow content to skip certain paths in client/server resource copying
Intended so content can ignore the MapImages folder
2025-07-11 18:15:10 +02:00
pathetic meowmeow
e52a6bbbf2 Add OKLCH-based colour descriptions for colorblindness accessibility (#6067)
* Add OKLCH-based colour descriptions for colorblindness accessibility

* my docs so comment

* my feed so back
2025-07-11 15:40:41 +02:00
PJB3005
e169d6a5a2 Enforce integration instance idleness for more helper members
Also allow them to be accessed regardless if from the integration instance thread.
2025-07-10 16:29:16 +02:00
PJB3005
3634ee636b Pooled integration instances now get marked non-idle
Otherwise, pooled integration instances could behave differently from freshly-spawned ones, creating heisentests.
2025-07-10 16:26:56 +02:00
āda
2349728eab out of my element (#6074)
Co-authored-by: iaada <iaada@users.noreply.github.com>
2025-07-10 12:43:51 +02:00
PJB3005
777f02cadd Fix physics closure allocs and some avoidable struct copies 2025-07-10 12:33:17 +02:00
Myra
0fc6f2bce6 Config load fail is now an error instead of a warning (#6070)
* Config load fail is now an error instead of a warning

* Update RELEASE-NOTES.md

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-07-06 20:28:49 +02:00
Tayrtahn
dc3705e520 Add ForbidLiteral to IPrototypeManager methods (#6066)
* Add ForbidLiteral to IPrototypeManager methods

* Cleanup violations
2025-07-06 20:27:32 +02:00
Tayrtahn
01f71ca55a Remove prototype instantiation from AssetPassAudioMetadata (#6059) 2025-06-28 22:18:46 +02:00
PJB3005
c5e812836b Fix loading textures in root folder
Fixes #6052

Also clean up a warning while I'm at it.
2025-06-28 01:37:14 +02:00
PJB3005
56eda3ea92 Version: 264.0.0 2025-06-27 22:03:33 +02:00
Tayrtahn
9dffd36319 Use non-generic TryComp to get MetaDataComponent in DebugAnchoringSystem (#6051) 2025-06-27 20:38:10 +02:00
Tayrtahn
a45b72a1c5 IRobustCloneable and generator support (#5692)
* Add IRobustCloneable and check for it in compnet generator.

* Redo compnetgenerator support; add test

* Disconnect client at end of test

* Actually test for client entities

* Cleanup

* Cleanup 2
2025-06-27 20:37:43 +02:00
Perry Fraser
bd0579ed6d fix: apply scale when calculating sprite bounding box (#6046)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2025-06-26 23:23:57 +02:00
Pieter-Jan Briers
c73b54862e Add analyzers to detect some prototype misuse (#6048)
* Add analyzers to detect some prototype misuse

Detects people marking prototype as NetSerializable.

Detects people creating new prototype instances themselves.

* Update Robust.Analyzers/PrototypeNetSerializableAnalyzer.cs

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
2025-06-26 22:24:23 +02:00
PJB3005
6436ff8040 Fix prototype manager Index exceptions
Index<T> was documented to throw KeyNotFoundException, but actually threw UnknownPrototypeException. Index(Type type, string id) threw KeyNotFoundException.

This has now been made consistent to be UnknownPrototypeException everywhere.
2025-06-26 16:58:35 +02:00
PJB3005
98313ae369 Update NetSerializer submodule
Makes it report where broken serialization types come from.
2025-06-26 16:58:21 +02:00
PJB3005
0e63391203 Add PrototypeManagerExt.Index that takes nullable ProtoId<T> 2025-06-26 16:52:18 +02:00
wixoa
261bfaeeb8 Add AlwaysActive to WebViewControl (#6047) 2025-06-25 21:50:07 +02:00
Tayrtahn
4017e1f57e Make some PlacementManager dependency fields public (#6044)
* Make some PlacementManager dependency fields public

* Revert "Make some PlacementManager dependency fields public"

This reverts commit 99fe37b502.

* Now part of IPlacementManager
2025-06-23 22:48:25 +02:00
lzk
e170bf1ad2 genetive case (#6045)
* dative

* slipped it

* slipped it twice

* 1

* Update _engine_lib.ftl
2025-06-23 22:47:50 +02:00
PJB3005
da0abd2535 Make functions static to avoid delegate allocations in DataDefinitionAnalyzer. 2025-06-22 13:48:50 +02:00
163 changed files with 3179 additions and 5529 deletions

View File

@@ -0,0 +1,34 @@
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 }}

View File

@@ -57,7 +57,7 @@
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,10 +54,92 @@ END TEMPLATE-->
*None yet*
## 263.0.2
## 265.0.0
### Breaking changes
## 263.0.1
* 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

View File

@@ -21,7 +21,8 @@ zzzz-object-pronoun = { GENDER($ent) ->
}
# Used internally by the DAT-OBJ() function.
# Not used in en-US. Created for supporting other languages.
# Not used in en-US. Created to support other languages.
# (e.g., "to him," "for her")
zzzz-dat-object = { GENDER($ent) ->
[male] him
[female] her
@@ -29,6 +30,16 @@ 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

View File

@@ -0,0 +1,32 @@
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

View File

@@ -0,0 +1,64 @@
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));
}
}

View File

@@ -0,0 +1,61 @@
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"));
}
}

View File

@@ -0,0 +1,17 @@
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");
}
}

View File

@@ -17,6 +17,10 @@
<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>

View File

@@ -121,7 +121,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}, SymbolKind.NamedType);
}
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
private static void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
{
if (context.Node is not TypeDeclarationSyntax declaration)
return;
@@ -147,7 +147,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
private static void AnalyzeDataField(SyntaxNodeAnalysisContext context)
{
if (context.Node is not FieldDeclarationSyntax field)
return;
@@ -198,7 +198,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
{
if (context.Node is not PropertyDeclarationSyntax property)
return;

View File

@@ -0,0 +1,48 @@
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()));
}
}

View File

@@ -0,0 +1,76 @@
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()));
}
}
}

View File

@@ -6,7 +6,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
internal static class Program
public static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)

View File

@@ -162,9 +162,10 @@ namespace Robust.Client.WebView.Cef
}
}
public bool IsOpen => _data != null;
public bool IsLoading => _data?.Browser.IsLoading ?? false;
public void EnteredTree()
public void StartBrowser()
{
DebugTools.AssertNull(_data);
@@ -195,7 +196,7 @@ namespace Robust.Client.WebView.Cef
_data = new LiveData(texture, client, browser, renderer);
}
public void ExitedTree()
public void CloseBrowser()
{
DebugTools.AssertNotNull(_data);

View File

@@ -5,7 +5,6 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -25,7 +24,6 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -63,10 +61,7 @@ namespace Robust.Client.WebView.Cef
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
{
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
cachePath = Path.Combine(rootDir, "cef_cache", "0");
}
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
var settings = new CefSettings()
{

View File

@@ -81,11 +81,13 @@ namespace Robust.Client.WebView.Headless
private sealed class WebViewControlImplDummy : DummyBase, IWebViewControlImpl
{
public void EnteredTree()
public bool IsOpen => false;
public void StartBrowser()
{
}
public void ExitedTree()
public void CloseBrowser()
{
}

View File

@@ -9,8 +9,10 @@ namespace Robust.Client.WebView
/// </summary>
internal interface IWebViewControlImpl : IWebViewControl
{
void EnteredTree();
void ExitedTree();
public bool IsOpen { get; }
void StartBrowser();
void CloseBrowser();
void MouseMove(GUIMouseMoveEventArgs args);
void MouseExited();
void MouseWheel(GUIMouseWheelEventArgs args);

View File

@@ -14,6 +14,7 @@ namespace Robust.Client.WebView
[Dependency] private readonly IWebViewManagerInternal _webViewManager = default!;
private readonly IWebViewControlImpl _controlImpl;
private bool _alwaysActive;
[ViewVariables(VVAccess.ReadWrite)]
public string Url
@@ -22,6 +23,21 @@ 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()
@@ -39,14 +55,16 @@ namespace Robust.Client.WebView
{
base.EnteredTree();
_controlImpl.EnteredTree();
if (!_controlImpl.IsOpen)
_controlImpl.StartBrowser();
}
protected override void ExitedTree()
{
base.ExitedTree();
_controlImpl.ExitedTree();
if (!_alwaysActive)
_controlImpl.CloseBrowser();
}
protected internal override void MouseMove(GUIMouseMoveEventArgs args)

View File

@@ -3,8 +3,6 @@ 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
{
@@ -122,9 +120,9 @@ namespace Robust.Client.Animations
case Vector2 vector2:
return Vector2Helpers.InterpolateCubic((Vector2) preA, vector2, (Vector2) b, (Vector2) postB, t);
case Vector3 vector3:
return Vector3.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
return VectorHelpers.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
case Vector4 vector4:
return Vector4.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
return VectorHelpers.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:

View File

@@ -1,4 +1,5 @@
using System;
using System.Numerics;
using OpenTK.Audio.OpenAL.Extensions.Creative.EFX;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Effects;

View File

@@ -575,18 +575,28 @@ 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;
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;
var velocity = VelocityOverride ?? midiEvent.Velocity;
velocity = VelocityOverride ?? midiEvent.Velocity;
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
break;
break;
case RobustMidiCommand.AfterTouch:
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = midiEvent.Value;
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);

View File

@@ -144,6 +144,7 @@ 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>();

View File

@@ -85,7 +85,7 @@ namespace Robust.Client.Console
MouseFilter = MouseFilterMode.Stop;
Result = result;
var compl = new FormattedMessage();
var dim = Color.FromHsl((0f, 0f, 0.8f, 1f));
var dim = Color.FromHsl(new Vector4(0f, 0f, 0.8f, 1f));
// warning: ew ahead
string basen = "default";

View File

@@ -82,7 +82,7 @@ namespace Robust.Client.Debugging
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid, grid, spot))
{
if (TryComp<MetaDataComponent>(ent, out var meta))
if (TryComp(ent, out MetaDataComponent? meta))
{
text.AppendLine($"uid: {ent}, {meta.EntityName}");
}

View File

@@ -387,7 +387,7 @@ namespace Robust.Client
_prof.Initialize();
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

@@ -30,8 +30,6 @@ 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

View File

@@ -32,7 +32,7 @@ public sealed partial class SpriteSystem
bounds = bounds.Union(GetLocalBounds(layer));
}
sprite.Comp._bounds = bounds;
sprite.Comp._bounds = bounds.Scale(sprite.Comp.Scale);
sprite.Comp.BoundsDirty = false;
return sprite.Comp._bounds;
}

View File

@@ -7,8 +7,6 @@ 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;
@@ -157,7 +155,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 SysVec4(-1) - layerColor.RGBA);
layerColor = new(new Vector4(-1) - layerColor.RGBA);
}
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);

View File

@@ -13,6 +13,8 @@ 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!;
@@ -32,7 +34,7 @@ namespace Robust.Client.GameStates
{
IoCManager.InjectDependencies(this);
_lookup = lookup;
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
_shader = _prototypeManager.Index(UnshadedShader).Instance();
_container = _entityManager.System<SharedContainerSystem>();
_xform = _entityManager.System<SharedTransformSystem>();
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;

View File

@@ -4,7 +4,6 @@ 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
{

View File

@@ -18,7 +18,6 @@ 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
{

View File

@@ -11,8 +11,6 @@ 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
{
@@ -541,7 +539,7 @@ namespace Robust.Client.Graphics.Clyde
case Matrix3x2 matrix3:
program.SetUniform(name, matrix3);
break;
case Matrix4 matrix4:
case Matrix4x4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:

View File

@@ -10,8 +10,6 @@ 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
{
@@ -528,7 +526,7 @@ namespace Robust.Client.Graphics.Clyde
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, in Matrix4 value)
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
{
var data = Parent._shaderInstances[Handle];
data.ParametersDirty = true;

View File

@@ -17,8 +17,6 @@ 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
{
@@ -398,7 +396,7 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, in Matrix4 value)
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
{
}

View File

@@ -5,8 +5,6 @@ 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
{
@@ -277,20 +275,20 @@ namespace Robust.Client.Graphics.Clyde
_clyde.CheckGlError();
}
public void SetUniform(string uniformName, in Matrix4 matrix, bool transpose=true)
public void SetUniform(string uniformName, in Matrix4x4 matrix, bool transpose=true)
{
var uniformId = GetUniform(uniformName);
SetUniformDirect(uniformId, matrix, transpose);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private unsafe void SetUniformDirect(int uniformId, in Matrix4 value, bool transpose=true)
private unsafe void SetUniformDirect(int uniformId, in Matrix4x4 value, bool transpose=true)
{
Matrix4 tmpTranspose = value;
Matrix4x4 tmpTranspose = value;
if (transpose)
{
// transposition not supported on GLES2, & no access to _hasGLES
tmpTranspose.Transpose();
tmpTranspose = Matrix4x4.Transpose(value);
}
GL.UniformMatrix4(uniformId, 1, false, (float*) &tmpTranspose);
_clyde.CheckGlError();
@@ -551,7 +549,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
public void SetUniformMaybe(string uniformName, in Matrix4 value, bool transpose=true)
public void SetUniformMaybe(string uniformName, in Matrix4x4 value, bool transpose=true)
{
if (TryGetUniform(uniformName, out var slot))
{

View File

@@ -4,8 +4,6 @@ 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
{
@@ -169,7 +167,7 @@ namespace Robust.Client.Graphics
SetParameterImpl(name, value);
}
public void SetParameter(string name, in Matrix4 value)
public void SetParameter(string name, in Matrix4x4 value)
{
EnsureAlive();
EnsureMutable();
@@ -236,7 +234,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 Matrix4 value);
private protected abstract void SetParameterImpl(string name, in Matrix4x4 value);
private protected abstract void SetParameterImpl(string name, Texture value);
private protected abstract void SetStencilImpl(StencilParameters value);
}

View File

@@ -11,8 +11,6 @@ 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
{
@@ -222,7 +220,7 @@ namespace Robust.Client.Graphics
case Matrix3x2 i:
instance.SetParameter(key, i);
break;
case Matrix4 i:
case Matrix4x4 i:
instance.SetParameter(key, i);
break;
}

View File

@@ -1,5 +1,8 @@
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;
@@ -18,6 +21,10 @@ 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>
@@ -49,5 +56,15 @@ 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; }
}
}

View File

@@ -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
{

View File

@@ -1,6 +1,6 @@
using System.Linq;
using System.Numerics;
using Robust.Shared.Map;
using Vector2 = System.Numerics.Vector2;
namespace Robust.Client.Placement.Modes
{

View File

@@ -22,6 +22,7 @@ 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
{
@@ -32,17 +33,23 @@ namespace Robust.Client.Placement
[Dependency] internal readonly IPlayerManager PlayerManager = default!;
[Dependency] internal readonly IResourceCache ResourceCache = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
[Dependency] internal readonly IMapManager MapManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IGameTiming _time = default!;
[Dependency] internal readonly IEyeManager EyeManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] internal readonly IInputManager InputManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] internal readonly IEntityManager EntityManager = default!;
[Dependency] private 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>();
@@ -198,6 +205,15 @@ namespace Robust.Client.Placement
}
}
private string[]? _allModeNames;
public string[] AllModeNames
{
get
{
return _allModeNames ??= [IPlacementManager.DefaultModeName, .. _modeDictionary.Keys.Order()];
}
}
/// <inheritdoc />
public event EventHandler? DirectionChanged;
@@ -209,7 +225,7 @@ namespace Robust.Client.Placement
public void Initialize()
{
_drawingShader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
_drawingShader = _prototypeManager.Index(UnshadedShader).Instance();
_sawmill = _logManager.GetSawmill("placement");
_networkManager.RegisterNetMessage<MsgPlacement>(HandlePlacementMessage);

View File

@@ -22,10 +22,10 @@ namespace Robust.Client.ResourceManagement
public override void Load(IDependencyCollection dependencies, ResPath path)
{
if (path.Directory.Filename.EndsWith(".rsi"))
if (IsInRsi(path))
{
Logger.WarningS(
"res",
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("res");
sawmill.Warning(
"Loading raw texture inside RSI: {Path}. Refer to the RSI state instead of the raw PNG.",
path);
}
@@ -38,6 +38,15 @@ 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))

View File

@@ -151,7 +151,7 @@ public sealed class EntitySpawningUIController : UIController
{
var newObjInfo = new PlacementInformation
{
PlacementOption = EntitySpawnWindow.InitOpts[args.Id],
PlacementOption = _placement.AllModeNames[args.Id],
EntityType = _placement.CurrentPermission!.EntityType,
Range = 2,
IsTile = _placement.CurrentPermission.IsTile
@@ -364,10 +364,11 @@ public sealed class EntitySpawningUIController : UIController
_window.SelectedButton = null;
_window.SelectedPrototype = null;
var overrideMode = EntitySpawnWindow.InitOpts[_window.OverrideMenu.SelectedId];
var overrideMode = _placement.AllModeNames[_window.OverrideMenu.SelectedId];
var newObjInfo = new PlacementInformation
{
PlacementOption = overrideMode != "Default" ? overrideMode : item.Prototype.PlacementMode,
PlacementOption = overrideMode != IPlacementManager.DefaultModeName ? overrideMode : item.Prototype.PlacementMode,
EntityType = item.PrototypeID,
Range = 2,
IsTile = false

View File

@@ -1,5 +1,8 @@
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;
@@ -8,22 +11,17 @@ 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;
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();
_colorData = GetStrategy().ToColorData(value);
UpdateAllSliders();
}
}
@@ -32,19 +30,12 @@ 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();
Update();
UpdateAllSliders();
}
}
@@ -61,7 +52,11 @@ public sealed class ColorSelectorSliders : Control
public Action<Color>? OnColorChanged;
private bool _updating = false;
private readonly static HsvSliderStrategy _hsvStrategy = new();
private readonly static RgbSliderStrategy _rgbStrategy = new();
private const float AlphaDivisor = 100.0f;
private Color _currentColor = Color.White;
private Vector4 _colorData;
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
@@ -83,6 +78,7 @@ 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();
@@ -93,6 +89,8 @@ public sealed class ColorSelectorSliders : Control
public ColorSelectorSliders()
{
IoCManager.InjectDependencies(this);
_topColorSlider = new ColorableSlider
{
HorizontalExpand = true,
@@ -124,10 +122,10 @@ public sealed class ColorSelectorSliders : Control
MaxValue = 1.0f,
};
_topColorSlider.OnValueChanged += _ => { OnColorSet(); };
_middleColorSlider.OnValueChanged += _ => { OnColorSet(); };
_bottomColorSlider.OnValueChanged += _ => { OnColorSet(); };
_alphaSlider.OnValueChanged += _ => { OnColorSet(); };
_topColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Top); };
_middleColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Middle); };
_bottomColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Bottom); };
_alphaSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Alpha); };
_topInputBox = new SpinBox
{
@@ -153,25 +151,10 @@ public sealed class ColorSelectorSliders : Control
};
_alphaInputBox.InitDefaultButtons();
_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);
};
_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); };
_alphaSliderLabel.Text = Loc.GetString("color-selector-sliders-alpha");
@@ -188,6 +171,8 @@ 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
@@ -200,6 +185,7 @@ public sealed class ColorSelectorSliders : Control
rootBox.AddChild(headerBox);
headerBox.AddChild(_typeSelector);
headerBox.AddChild(_colorDescriptionLabel);
var bodyBox = new BoxContainer()
{
@@ -241,165 +227,114 @@ public sealed class ColorSelectorSliders : Control
Color = _currentColor;
}
private void UpdateType()
private ColorSliderStrategy GetStrategy()
{
(string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels();
_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);
return SelectorType switch
{
ColorSelectorType.Rgb => _rgbStrategy,
ColorSelectorType.Hsv => _hsvStrategy,
_ => throw new ArgumentOutOfRangeException(),
};
}
private void Update()
private (Slider slider, SpinBox inputBox) GetSliderByOrder(ColorSliderOrder 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;
_updating = true;
_topStyle.SetBaseColor(_colorData);
_middleStyle.SetBaseColor(_colorData);
_bottomStyle.SetBaseColor(_colorData);
switch (SelectorType)
return order switch
{
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)
{
if (value < 0)
{
return false;
}
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 (string, string, string) GetSliderLabels()
{
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")
);
}
return ("ERR", "ERR", "ERR");
ColorSliderOrder.Top => (_topColorSlider, _topInputBox),
ColorSliderOrder.Middle => (_middleColorSlider, _middleInputBox),
ColorSliderOrder.Bottom => (_bottomColorSlider, _bottomInputBox),
ColorSliderOrder.Alpha => (_alphaSlider, _alphaInputBox),
_ => throw new ArgumentOutOfRangeException(),
};
}
private float GetColorValueDivisor(ColorSliderOrder order)
{
if (order == ColorSliderOrder.Alpha)
{
return 100.0f;
}
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;
return order == ColorSliderOrder.Alpha
? AlphaDivisor
: GetStrategy().GetColorValueDivisor(order);
}
private void OnColorSet()
private void UpdateType()
{
// stack overflow otherwise due to value sets
if (_updating)
{
return;
}
var strategy = GetStrategy();
var labels = strategy.GetSliderLabelTexts();
_topSliderLabel.Text = labels.top;
_middleSliderLabel.Text = labels.middle;
_bottomSliderLabel.Text = labels.bottom;
_colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
_topStyle.ConfigureSlider(strategy.TopSliderStyle);
_middleStyle.ConfigureSlider(strategy.MiddleSliderStyle);
_bottomStyle.ConfigureSlider(strategy.BottomSliderStyle);
}
_currentColor = SelectorType switch
private void UpdateSlider(ColorSliderOrder order)
{
var (slider, inputBox) = GetSliderByOrder(order);
var divisor = GetColorValueDivisor(order);
var dataValue = order switch
{
ColorSelectorType.Hsv => Color.FromHsv(_colorData),
_ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W)
ColorSliderOrder.Top => _colorData.X,
ColorSliderOrder.Middle => _colorData.Y,
ColorSliderOrder.Bottom => _colorData.Z,
ColorSliderOrder.Alpha => _colorData.W,
_ => throw new ArgumentOutOfRangeException(nameof(order))
};
Update();
slider.SetValueWithoutEvent(dataValue);
inputBox.OverrideValue((int)(dataValue * divisor));
}
private void UpdateSliderVisuals()
{
_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);
}
private bool IsSpinBoxValid(int value, ColorSliderOrder ordering)
{
var divisor = GetColorValueDivisor(ordering);
var channelValue = value / divisor;
return channelValue >= 0.0f && channelValue <= 1.0f;
}
private void OnInputBoxValueChanged(ValueChangedEventArgs args, ColorSliderOrder order)
{
var (slider, _) = GetSliderByOrder(order);
var value = args.Value / GetColorValueDivisor(order);
// 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;
}
private void OnSliderValueChanged(ColorSliderOrder order)
{
_colorData = new Vector4(
_topColorSlider.Value,
_middleColorSlider.Value,
_bottomColorSlider.Value,
_alphaSlider.Value);
_currentColor = GetStrategy().FromColorData(_colorData);
OnColorChanged?.Invoke(_currentColor);
UpdateSliderVisuals();
UpdateSlider(order);
}
private enum ColorSliderOrder
@@ -415,4 +350,121 @@ 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"));
}
}
}

View File

@@ -21,19 +21,19 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
/// <summary>
/// Base background colour.
/// </summary>
public Robust.Shared.Maths.Vector4 BaseColor;
public Vector4 BaseColor;
/// <summary>
/// Colour to add to the background colour along the X-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Robust.Shared.Maths.Vector4 XAxis;
public Vector4 XAxis;
/// <summary>
/// Colour to add to the background colour along the y-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Robust.Shared.Maths.Vector4 YAxis;
public 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 Robust.Shared.Maths.Vector4(color.R, color.G, color.B, color.A);
: new Vector4(color.R, color.G, color.B, color.A);
SetBaseColor(colorData);
}
/// <summary>
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
/// </summary>
public void SetBaseColor(Robust.Shared.Maths.Vector4 colorData)
public void SetBaseColor(Vector4 colorData)
{
BaseColor = colorData - colorData * XAxis - colorData * YAxis;
}

View File

@@ -13,7 +13,6 @@ 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;

View File

@@ -1,9 +1,13 @@
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;
@@ -12,34 +16,24 @@ namespace Robust.Client.UserInterface.CustomControls
[GenerateTypedNameReferences]
public sealed partial class EntitySpawnWindow : DefaultWindow
{
public static readonly string[] InitOpts =
{
"Default",
"PlaceFree",
"PlaceNearby",
"SnapgridCenter",
"SnapgridBorder",
"AlignSimilar",
"AlignTileAny",
"AlignTileEmpty",
"AlignTileNonDense",
"AlignTileDense",
"AlignWall",
"AlignWallProper",
};
[Dependency] private readonly IPlacementManager _placement = default!;
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);
for (var i = 0; i < InitOpts.Length; i++)
var modes = _placement.AllModeNames;
for (var i = 0; i < modes.Length; i++)
{
OverrideMenu.AddItem(InitOpts[i], i);
OverrideMenu.AddItem(modes[i], i);
}
}

View File

@@ -1,27 +1,20 @@
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
{
@@ -31,8 +24,7 @@ namespace Robust.Client.ViewVariables
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
[Dependency] private readonly IViewVariableControlFactory _controlFactory = default!;
private uint _nextReqId = 1;
private readonly Vector2i _defaultWindowSize = (640, 420);
@@ -64,190 +56,7 @@ namespace Robust.Client.ViewVariables
public VVPropEditor PropertyFor(Type? 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();
return _controlFactory.CreateFor(type);
}
public void OpenVV(object obj)
@@ -263,7 +72,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);
@@ -273,7 +82,7 @@ namespace Robust.Client.ViewVariables
public void OpenVV(string path)
{
if (ReadPath(path) is {} obj)
if (ReadPath(path) is { } obj)
OpenVV(obj);
}
@@ -284,7 +93,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.
@@ -352,7 +161,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)

View File

@@ -1,16 +1,24 @@
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,
};
@@ -19,7 +27,9 @@ internal sealed class VVPropEditorEntProtoId : VVPropEditor
{
lineEdit.OnTextEntered += e =>
{
ValueChanged((EntProtoId) e.Text);
var id = (EntProtoId)e.Text;
if (_protoMan.HasIndex(id))
ValueChanged(id);
};
}

View File

@@ -23,7 +23,8 @@ namespace Robust.Client.ViewVariables.Editors
{
var hBox = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal
Orientation = LayoutOrientation.Horizontal,
MinWidth = 350
};
dynamic d = value!;

View File

@@ -1,16 +1,24 @@
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,
};
@@ -25,7 +33,9 @@ internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor
}
else
{
ValueChanged((EntProtoId) e.Text);
var id = (EntProtoId)e.Text;
if (_protoMan.HasIndex(id))
ValueChanged(id);
}
};
}

View File

@@ -0,0 +1,110 @@
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);
}
}
}

View File

@@ -0,0 +1,58 @@
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
}

View File

@@ -151,7 +151,7 @@ namespace Robust.Client.ViewVariables.Instances
ViewVariablesTraitMembers.CreateMemberGroupHeader(
ref first,
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
clientVBox);
clientVBox.Children);
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);
ViewVariablesTraitMembers.CreateMemberGroupHeader(ref first, groupName, _serverVariables.Children);
foreach (var propertyData in groupMembers)
{

View File

@@ -48,7 +48,7 @@ namespace Robust.Client.ViewVariables.Traits
CreateMemberGroupHeader(
ref first,
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
_memberList);
replacementControls);
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, _memberList);
CreateMemberGroupHeader(ref first, groupName, replacementControls);
foreach (var propertyData in groupMembers)
{
@@ -95,15 +95,15 @@ namespace Robust.Client.ViewVariables.Traits
}
}
internal static void CreateMemberGroupHeader(ref bool first, string groupName, Control container)
internal static void CreateMemberGroupHeader(ref bool first, string groupName, ICollection<Control> container)
{
if (!first)
{
container.AddChild(new Control {MinSize = new Vector2(0, 16)});
container.Add(new Control {MinSize = new Vector2(0, 16)});
}
first = false;
container.AddChild(new Label {Text = groupName, FontColorOverride = Color.DarkGray});
container.Add(new Label {Text = groupName, FontColorOverride = Color.DarkGray});
}
}
}

View File

@@ -0,0 +1,145 @@
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
);
}

View File

@@ -73,30 +73,10 @@ namespace Robust.Client.ViewVariables
public VVPropEditor SetProperty(ViewVariablesBlobMembers.MemberData member)
{
NameLabel.Text = member.Name;
var type = Type.GetType(member.Type);
var type = member.Value?.GetType();
_bottomLabel.Text = $"Type: {member.TypePretty}";
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 editor = _viewVariablesManager.PropertyFor(type);
var view = editor.Initialize(member.Value, !member.Editable);
if (!view.HorizontalExpand)

View File

@@ -14,7 +14,7 @@ namespace Robust.Packaging.AssetProcessing.Passes;
/// </summary>
public sealed class AssetPassAudioMetadata : AssetPass
{
private readonly List<AudioMetadataPrototype> _audioMetadata = new();
private readonly List<(string ID, TimeSpan Length)> _audioMetadata = new();
private readonly string _metadataPath;
public AssetPassAudioMetadata(string metadataPath = "Prototypes/_audio_metadata.yml")
@@ -32,11 +32,7 @@ public sealed class AssetPassAudioMetadata : AssetPass
lock (_audioMetadata)
{
_audioMetadata.Add(new AudioMetadataPrototype()
{
ID = "/" + file.Path,
Length = metadata.Length,
});
_audioMetadata.Add(("/" + file.Path, metadata.Length));
}
return AssetFileAcceptResult.Consumed;

View File

@@ -19,7 +19,19 @@ public sealed class RobustClientPackaging
AssetPass pass,
CancellationToken cancel = default)
{
var ignoreSet = ClientIgnoredResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
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();
await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"), pass, ignoreSet, cancel: cancel);
}

View File

@@ -15,13 +15,22 @@ 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,
ignoreSet.Union(additionalIgnoredResources).ToHashSet(),
cancel: cancel);
await RobustSharedPackaging.DoResourceCopy(

View File

@@ -43,4 +43,22 @@ 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;
}
}

View File

@@ -40,6 +40,9 @@ 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.");

View File

@@ -6,7 +6,7 @@ namespace Robust.Roslyn.Shared;
public static class TypeSymbolHelper
{
public static bool ShittyTypeMatch(INamedTypeSymbol type, string attributeMetadataName)
public static bool ShittyTypeMatch(ITypeSymbol 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(INamedTypeSymbol type, string interfaceTypeName)
public static bool ImplementsInterface(ITypeSymbol type, string interfaceTypeName)
{
foreach (var interfaceType in type.AllInterfaces)
{
@@ -25,4 +25,33 @@ 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;
}
}
}

View File

@@ -297,7 +297,7 @@ namespace Robust.Server
: null;
// Set up the VFS
_resources.Initialize(dataDir, hideUserDataDir: false);
_resources.Initialize(dataDir);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;

View File

@@ -154,7 +154,7 @@ namespace Robust.Server.Console.Commands
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return LoadMap.GetCompletionResult(shell, args, _resource);
return LoadMap.GetCompletionResult(shell, args, _resource, Loc);
}
}
@@ -235,24 +235,24 @@ namespace Robust.Server.Console.Commands
public override string Command => "loadmap";
public static CompletionResult GetCompletionResult(IConsoleShell shell, string[] args, IResourceManager resource)
public static CompletionResult GetCompletionResult(IConsoleShell shell, string[] args, IResourceManager resource, ILocalizationManager loc)
{
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);
return GetCompletionResult(shell, args, _resource, Loc);
}
public override void Execute(IConsoleShell shell, string argStr, string[] args)

View File

@@ -32,7 +32,7 @@ internal sealed partial class PvsSystem
if (component.Deleted || !component.Initialized)
{
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
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"}");
continue;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Robust.Server.ViewVariables.Traits;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
@@ -135,6 +136,10 @@ 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;
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Robust.Server.ViewVariables.Traits;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -141,6 +142,19 @@ 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
{

View File

@@ -5,6 +5,7 @@ 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
@@ -35,6 +36,7 @@ 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);
@@ -47,7 +49,7 @@ namespace Robust.Shared.CompNetworkGenerator
var componentName = classSymbol.Name;
var stateName = $"{componentName}_AutoState";
var members = classSymbol.GetMembers();
var members = TypeSymbolHelper.GetAllMembersIncludingInherited(classSymbol);
var fields = new List<(ITypeSymbol Type, string FieldName)>();
var fieldAttr = comp.GetTypeByMetadataName(MemberAttributeName);
@@ -375,7 +377,39 @@ namespace Robust.Shared.CompNetworkGenerator
stateFields.Append($@"
public {networkedType} {name} = default!;");
if (IsCloneType(type))
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))
{
getField = $"component.{name}";
cast = $"({castString})";
@@ -682,7 +716,7 @@ public partial class {componentName}{deltaInterface}
if (relevantAttribute == null)
{
foreach (var mem in typeSymbol.GetMembers())
foreach (var mem in TypeSymbolHelper.GetAllMembersIncludingInherited(typeSymbol))
{
var attribute = mem.GetAttributes().FirstOrDefault(a =>
a.AttributeClass != null &&
@@ -758,5 +792,19 @@ 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;
}
}
}

View File

@@ -29,12 +29,11 @@ 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
{
@@ -69,7 +68,7 @@ namespace Robust.Shared.Maths
/// Vector representation, for easy SIMD operations.
/// </summary>
// ReSharper disable once InconsistentNaming
public readonly SysVector4 RGBA => Unsafe.BitCast<Color, SysVector4>(this);
public readonly Vector4 RGBA => Unsafe.BitCast<Color, Vector4>(this);
public readonly byte RByte => (byte) (R * byte.MaxValue);
public readonly byte GByte => (byte) (G * byte.MaxValue);
@@ -94,9 +93,9 @@ namespace Robust.Shared.Maths
/// <summary>
/// Constructs a new Color structure from the components in a <see cref="SysVector4"/>.
/// </summary>
public Color(in SysVector4 vec)
public Color(in Vector4 vec)
{
this = Unsafe.BitCast<SysVector4, Color>(vec);
this = Unsafe.BitCast<Vector4, Color>(vec);
}
/// <summary>
@@ -591,6 +590,125 @@ 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>
@@ -786,7 +904,7 @@ namespace Robust.Shared.Maths
var m = (1 - g - k) / (1 - k);
var y = (1 - b - k) / (1 - k);
return (c, m, y, k);
return new Vector4(c, m, y, k);
}
public static Color FromCmyk(Vector4 cmyk)
@@ -813,7 +931,7 @@ namespace Robust.Shared.Maths
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Color InterpolateBetween(Color α, Color β, float λ)
{
return new(SysVector4.Lerp(α.RGBA, β.RGBA, λ));
return new(Vector4.Lerp(α.RGBA, β.RGBA, λ));
}
public static Color? TryFromHex(ReadOnlySpan<char> hexColor)
@@ -888,10 +1006,10 @@ namespace Robust.Shared.Maths
public static Color Blend(Color dstColor, Color srcColor, BlendFactor dstFactor, BlendFactor srcFactor)
{
var dst = new SysVector3(dstColor.R, dstColor.G, dstColor.B);
var src = new SysVector3(srcColor.R, srcColor.G, srcColor.B);
var dst = new Vector3(dstColor.R, dstColor.G, dstColor.B);
var src = new Vector3(srcColor.R, srcColor.G, srcColor.B);
var ret = new SysVector3();
var ret = new Vector3();
switch (dstFactor)
{
@@ -904,13 +1022,13 @@ namespace Robust.Shared.Maths
ret = dst * src;
break;
case BlendFactor.OneMinusSrcColor:
ret = dst * (SysVector3.One - src);
ret = dst * (Vector3.One - src);
break;
case BlendFactor.DstColor:
ret = dst * dst;
break;
case BlendFactor.OneMinusDstColor:
ret = dst * (SysVector3.One - dst);
ret = dst * (Vector3.One - dst);
break;
case BlendFactor.SrcAlpha:
ret = dst * srcColor.A;
@@ -939,13 +1057,13 @@ namespace Robust.Shared.Maths
ret += src * src;
break;
case BlendFactor.OneMinusSrcColor:
ret += src * (SysVector3.One - src);
ret += src * (Vector3.One - src);
break;
case BlendFactor.DstColor:
ret += src * dst;
break;
case BlendFactor.OneMinusDstColor:
ret += src * (SysVector3.One - dst);
ret += src * (Vector3.One - dst);
break;
case BlendFactor.SrcAlpha:
ret += src * srcColor.A;

View File

@@ -15,7 +15,6 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using Vec4 = System.Numerics.Vector4;
namespace Robust.Shared.Maths
{
@@ -530,13 +529,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(Vec4 a, Vec4 b, float percentage = .00001f)
public static bool CloseToPercent(Vector4 a, Vector4 b, float percentage = .00001f)
{
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);
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);
return delta.X <= epsilon.X && delta.Y <= epsilon.Y && delta.Z <= epsilon.Z && delta.W <= epsilon.W;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,978 +0,0 @@
#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
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace Robust.Shared.Maths

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
using System;
using System.Numerics;
using System.Runtime.InteropServices;
namespace Robust.Shared.Maths

View File

@@ -0,0 +1,31 @@
using System.Numerics;
namespace Robust.Shared.Maths;
public static class VectorHelpers
{
public static Vector3 InterpolateCubic(Vector3 preA, Vector3 a, Vector3 b, Vector3 postB, float t)
{
return a + (b - preA + (preA * 2.0f - a * 5.0f + b * 4.0f - postB + ((a - b) * 3.0f + postB - preA) * t) * t) * t * 0.5f;
}
public static Vector4 InterpolateCubic(Vector4 preA, Vector4 a, Vector4 b, Vector4 postB, float t)
{
return a + (b - preA + (preA * 2.0f - a * 5.0f + b * 4.0f - postB + ((a - b) * 3.0f + postB - preA) * t) * t) * t * 0.5f;
}
public static void Deconstruct(this Vector3 vector, out float x, out float y, out float z)
{
x = vector.X;
y = vector.Y;
z = vector.Z;
}
public static void Deconstruct(this Vector4 vector, out float x, out float y, out float z, out float w)
{
x = vector.X;
y = vector.Y;
z = vector.Z;
w = vector.W;
}
}

View File

@@ -20,7 +20,7 @@ internal sealed class AudioDebugCommands : LocalizedCommands
}
var audioSystem = _entitySystem.GetEntitySystem<SharedAudioSystem>();
var length = audioSystem.GetAudioLength(args[0]);
var length = audioSystem.GetAudioLength(new ResolvedPathSpecifier(args[0]));
shell.WriteLine(length.ToString());
}

View File

@@ -1,3 +1,4 @@
using System.Numerics;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,9 +1,8 @@
using System;
using System.Numerics;
using Robust.Shared.Audio.Effects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;

View File

@@ -1,3 +1,4 @@
using System.Numerics;
using Robust.Shared.Audio.Components;
using Robust.Shared.Maths;

View File

@@ -1,4 +1,5 @@
using System;
using System.Numerics;
using Robust.Shared.Maths;
namespace Robust.Shared.Audio.Effects;

View File

@@ -8,6 +8,7 @@
//
using System.Numerics;
using Robust.Shared.Maths;
namespace Robust.Shared.Audio.Effects;

View File

@@ -7,6 +7,7 @@
// of the MIT license. See the LICENSE file for details.
//
using System.Numerics;
using Robust.Shared.Maths;
namespace Robust.Shared.Audio.Effects;

View File

@@ -195,7 +195,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
component.PlaybackPosition = (float) (Timing.CurTime - component.AudioStart).TotalSeconds;
DirtyField(entity.Value, component, nameof(AudioComponent.AudioStart));
DirtyField(entity.Value, component, nameof(AudioComponent.PlaybackPosition));
}
// If we were stopped then played then restart audiostart to now.
@@ -430,11 +429,15 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// Gets the timespan of the specified audio.
/// </summary>
public TimeSpan GetAudioLength(ResolvedSoundSpecifier specifier)
{
var filename = GetAudioPath(specifier) ?? string.Empty;
if (!filename.StartsWith("/"))
throw new ArgumentException("Path must be rooted");
=> GetAudioLength(GetAudioPath(specifier));
/// <summary>
/// Gets the timespan of the specified filename.
/// </summary>
protected TimeSpan GetAudioLength(string filename)
{
if (!filename.StartsWith('/'))
throw new ArgumentException("Path must be rooted");
return GetAudioLengthImpl(filename);
}

View File

@@ -0,0 +1,158 @@
using System;
using System.Numerics;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
namespace Robust.Shared.ColorNaming;
// color naming algorithim is inspired by https://react-spectrum.adobe.com/blog/accessible-color-descriptions.html
public static class ColorNaming
{
private static readonly (float Hue, string Loc)[] HueNames =
{
(float.DegreesToRadians(0f), "color-pink"),
(float.DegreesToRadians(15f), "color-red"),
(float.DegreesToRadians(45f), "color-orange"),
(float.DegreesToRadians(90f), "color-yellow"),
(float.DegreesToRadians(135f), "color-green"),
(float.DegreesToRadians(180f), "color-cyan"),
(float.DegreesToRadians(240f), "color-blue"),
(float.DegreesToRadians(285f), "color-purple"),
(float.DegreesToRadians(330f), "color-pink"),
};
private static readonly (float Hue, string Loc) HueFallback = (float.DegreesToRadians(360f), "color-pink");
private const float BrownLightnessThreshold = 0.675f;
private static readonly LocId OrangeString = "color-orange";
private static readonly LocId BrownString = "color-brown";
private const float VeryDarkLightnessThreshold = 0.25f;
private const float DarkLightnessThreshold = 0.5f;
private const float NeutralLightnessThreshold = 0.7f;
private const float LightLightnessThreshold = 0.85f;
private static readonly LocId VeryDarkString = "color-very-dark";
private static readonly LocId DarkString = "color-dark";
private static readonly LocId LightString = "color-light";
private static readonly LocId VeryLightString = "color-very-light";
private static readonly LocId MixedHueString = "color-mixed-hue";
private static readonly LocId LightLowChromaString = "color-pale";
private static readonly LocId DarkLowChromaString = "color-gray-adjective";
private static readonly LocId HighChromaString = "color-strong";
private static readonly LocId WhiteString = "color-white";
private static readonly LocId GrayString = "color-gray";
private static readonly LocId BlackString = "color-black";
private const float LowChromaThreshold = 0.07f;
private const float HighChromaThreshold = 0.16f;
private const float LightLowChromaThreshold = 0.6f;
private const float WhiteLightnessThreshold = 0.99f;
private const float BlackLightnessThreshold = 0.01f;
private const float GrayChromaThreshold = 0.01f;
private static (string Loc, float AdjustedLightness) DescribeHue(Vector4 oklch, ILocalizationManager localization)
{
var (lightness, _, hue, _) = oklch;
for (var i = 0; i < HueNames.Length; i++)
{
var prevData = HueNames[i];
var nextData = i+1 < HueNames.Length ? HueNames[i+1] : HueFallback;
if (prevData.Hue >= hue || hue > nextData.Hue)
continue;
var loc = prevData.Loc;
var adjustedLightness = lightness;
if (prevData.Loc == OrangeString && lightness <= BrownLightnessThreshold)
loc = BrownString;
else if (prevData.Loc == OrangeString)
adjustedLightness = lightness - BrownLightnessThreshold + DarkLightnessThreshold;
if (hue >= (prevData.Hue + nextData.Hue)/2f && prevData.Loc != nextData.Loc)
{
if (localization.TryGetString($"{loc}-{nextData.Loc}", out var hueName))
return (hueName!, adjustedLightness);
else
return (localization.GetString(MixedHueString, ("a", localization.GetString(loc)), ("b", localization.GetString(nextData.Loc))), adjustedLightness);
}
return (localization.GetString(loc), adjustedLightness);
}
throw new ArgumentOutOfRangeException("oklch", $"colour ({oklch}) hue {hue} is outside of expected bounds");
}
private static string? DescribeChroma(Vector4 oklch, ILocalizationManager localization)
{
var (lightness, chroma, _, _) = oklch;
if (chroma <= LowChromaThreshold)
{
if (lightness >= LightLowChromaThreshold)
return localization.GetString(LightLowChromaString);
else
return localization.GetString(DarkLowChromaString);
}
else if (chroma >= HighChromaThreshold)
{
return localization.GetString(HighChromaString);
}
return null;
}
private static string? DescribeLightness(Vector4 oklch, ILocalizationManager localization)
{
return oklch.X switch
{
< VeryDarkLightnessThreshold => localization.GetString(VeryDarkString),
< DarkLightnessThreshold => localization.GetString(DarkString),
< NeutralLightnessThreshold => null,
< LightLightnessThreshold => localization.GetString(LightString),
_ => localization.GetString(VeryLightString)
};
}
/// <summary>
/// Textually describes a color
/// </summary>
/// <returns>
/// Returns a localized textual description of the provided color
/// </returns>
/// <param name="srgb">A Color that is assumed to be in SRGB (the default for most cases)</param>
public static string Describe(Color srgb, ILocalizationManager localization)
{
var oklch = Color.ToLch(Color.ToLab(Color.FromSrgb(srgb)));
if (oklch.X >= WhiteLightnessThreshold)
return localization.GetString(WhiteString);
if (oklch.X <= BlackLightnessThreshold)
return localization.GetString(BlackString);
var (hueDescription, adjustedLightness) = DescribeHue(oklch, localization);
oklch.X = adjustedLightness;
var chromaDescription = DescribeChroma(oklch, localization);
var lightnessDescription = DescribeLightness(oklch, localization);
if (oklch.Y <= GrayChromaThreshold)
{
hueDescription = localization.GetString(GrayString);
chromaDescription = null;
}
return (hueDescription, chromaDescription, lightnessDescription) switch
{
({ } hue, { } chroma, { } lightness) => localization.GetString("color-hue-chroma-lightness", ("hue", hue), ("chroma", chroma), ("lightness", lightness)),
({ } hue, { } chroma, null) => localization.GetString("color-hue-chroma", ("hue", hue), ("chroma", chroma)),
({ } hue, null, { } lightness) => localization.GetString("color-hue-lightness", ("hue", hue), ("lightness", lightness)),
({ } hue, null, null) => hue,
};
}
}

View File

@@ -135,7 +135,7 @@ namespace Robust.Shared.Configuration
return CompletionResult.FromHint($"<{type.Name}>");
}
private static string GetCVarValueHint(IConfigurationManager cfg, string cVar)
private string GetCVarValueHint(IConfigurationManager cfg, string cVar)
{
var flags = cfg.GetCVarFlags(cVar);
if ((flags & CVar.CONFIDENTIAL) != 0)

View File

@@ -82,7 +82,7 @@ namespace Robust.Shared.Configuration
catch (Exception e)
{
loaded.Clear();
_sawmill.Warning("Unable to load configuration from stream:\n{0}", e);
_sawmill.Error("Unable to load configuration from stream:\n{0}", e);
}
return loaded;
@@ -188,7 +188,7 @@ namespace Robust.Shared.Configuration
}
catch (Exception e)
{
_sawmill.Warning("Unable to load configuration file:\n{0}", e);
_sawmill.Error("Unable to load configuration file:\n{0}", e);
return new HashSet<string>(0);
}
}

View File

@@ -14,10 +14,10 @@ internal sealed class HelpCommand : LocalizedCommands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
// Not a toolshed command since it doesn't support optional arguments
ExecuteStatic(shell, argStr, args);
ExecuteStatic(shell, argStr, args, Loc);
}
public static void ExecuteStatic(IConsoleShell shell, string argStr, string[] args)
public static void ExecuteStatic(IConsoleShell shell, string argStr, string[] args, ILocalizationManager loc)
{
switch (args.Length)
{
@@ -40,34 +40,34 @@ For help with old console commands, run [color={Gold}]oldhelp[/color].
var commandName = args[0];
if (!shell.ConsoleHost.AvailableCommands.TryGetValue(commandName, out var cmd))
{
shell.WriteError(Loc.GetString("cmd-help-unknown", ("command", commandName)));
shell.WriteError(loc.GetString("cmd-help-unknown", ("command", commandName)));
return;
}
shell.WriteLine(Loc.GetString("cmd-help-top", ("command", cmd.Command),
shell.WriteLine(loc.GetString("cmd-help-top", ("command", cmd.Command),
("description", cmd.Description)));
shell.WriteLine(cmd.Help);
break;
default:
shell.WriteError(Loc.GetString("cmd-help-invalid-args"));
shell.WriteError(loc.GetString("cmd-help-invalid-args"));
break;
}
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return GetCompletionStatic(shell, args);
return GetCompletionStatic(shell, args, Loc);
}
public static CompletionResult GetCompletionStatic(IConsoleShell shell, string[] args)
public static CompletionResult GetCompletionStatic(IConsoleShell shell, string[] args, ILocalizationManager loc)
{
if (args.Length == 1)
{
var host = shell.ConsoleHost;
return CompletionResult.FromHintOptions(
host.AvailableCommands.Values.OrderBy(c => c.Command).Select(c => new CompletionOption(c.Command, c.Description)).ToArray(),
Loc.GetString("cmd-help-arg-cmdname"));
loc.GetString("cmd-help-arg-cmdname"));
}
return CompletionResult.Empty;

View File

@@ -7,11 +7,11 @@ public sealed class OldHelpCommand : LocalizedCommands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
// For the people that got used to oldhelp
HelpCommand.ExecuteStatic(shell, argStr, args);
HelpCommand.ExecuteStatic(shell, argStr, args, Loc);
}
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return HelpCommand.GetCompletionStatic(shell, args);
return HelpCommand.GetCompletionStatic(shell, args, Loc);
}
}

View File

@@ -10,6 +10,8 @@ public abstract class LocalizedCommands : IConsoleCommand
{
[Dependency] protected readonly ILocalizationManager LocalizationManager = default!;
public ILocalizationManager Loc => LocalizationManager;
/// <inheritdoc />
public abstract string Command { get; }

View File

@@ -60,7 +60,9 @@ namespace Robust.Shared.ContentPack
internal string GetPath(ResPath relPath)
{
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
// Sanitise platform-specific path and standardize it for engine use.
.Replace(Path.DirectorySeparatorChar, '/');
}
/// <inheritdoc />

View File

@@ -14,11 +14,7 @@ namespace Robust.Shared.ContentPack
/// The directory to use for user data.
/// If null, a virtual temporary file system is used instead.
/// </param>
/// <param name="hideUserDataDir">
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
/// <see cref="IResourceManager.UserData"/>.
/// </param>
void Initialize(string? userData, bool hideUserDataDir);
void Initialize(string? userData);
/// <summary>
/// Mounts a single stream as a content file. Useful for unit testing.

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
{
/// <summary>
/// The root path of this provider.
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
/// Can be null if it's a virtual provider.
/// </summary>
string? RootDir { get; }

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
{
@@ -64,27 +63,5 @@ namespace Robust.Shared.ContentPack
!OperatingSystem.IsWindows()
&& !OperatingSystem.IsMacOS();
internal static string SafeGetResourcePath(string baseDir, ResPath path)
{
var relSysPath = path.ToRelativeSystemPath();
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
// better safe than sorry check
if (!retPath.StartsWith(baseDir))
{
// Allow path to match if it's just missing the directory separator at the end.
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return retPath;
}
}
}

View File

@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
public IWritableDirProvider UserData { get; private set; } = default!;
/// <inheritdoc />
public virtual void Initialize(string? userData, bool hideRootDir)
public virtual void Initialize(string? userData)
{
Sawmill = _logManager.GetSawmill("res");
if (userData != null)
{
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
}
else
{
@@ -379,10 +379,6 @@ namespace Robust.Shared.ContentPack
{
var rootDir = loader.GetPath(new ResPath(@"/"));
// TODO: GET RID OF THIS.
// This code shouldn't be passing OS disk paths through ResPath.
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
yield return new ResPath(rootDir);
}
}

Some files were not shown because too many files have changed in this diff Show More