Compare commits

..

4 Commits

Author SHA1 Message Date
PJB3005
3caffa04da Version: 260.2.2 2025-09-19 09:17:28 +02:00
Skye
06b377d1d5 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:28 +02:00
PJB3005
41fb191dda Version: 260.2.1 2025-09-14 14:55:52 +02:00
PJB3005
d4bcc1dc05 Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
2025-09-14 14:55:51 +02:00
286 changed files with 7148 additions and 5477 deletions

View File

@@ -1,34 +0,0 @@
name: Build All Configurations
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
build:
strategy:
matrix:
targetOS: [Windows, Linux, MacOS]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
with:
submodules: true
- name: Setup .NET
uses: actions/setup-dotnet@v4.1.0
with:
dotnet-version: 9.0.x
- name: Install dependencies
run: dotnet restore
- name: Build Debug
run: dotnet build --no-restore --configuration Debug /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
- name: Build Tools
run: dotnet build --no-restore --configuration Tools /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}
- name: Build Release
run: dotnet build --no-restore --configuration Release /p:WarningsAsErrors=nullable /p:TargetOS=${{ matrix.targetOS }}

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,244 +54,10 @@ END TEMPLATE-->
*None yet*
## 265.0.4
## 260.2.2
## 265.0.3
## 265.0.2
## 265.0.1
## 265.0.0
### Breaking changes
* More members in `IntegrationInstance` now enforce that the instance is idle before accessing it.
* `Prototype.ValidateDirectory` now requires that prototype IDs have no spaces or periods in them.
* `IPrototypeManager.TryIndex` no longer logs errors unless using the overload with an optional parameter. Use `Resolve()` instead if error logging is desired.
* `LocalizedCommands` now has a `Loc` property that refers to `LocalizationManager`. This can cause compile failures if you have static methods in child types that referenced static `Loc`.
* `[AutoGenerateComponentState]` now works on parent members for inherited classes. This can cause compile failures in certain formerly silently broken cases with overriden properties.
* `Vector3`, `Vector4`, `Quaternion`, and `Matrix4` have been removed from `Robust.Shared.Maths`. Use the `System.Numerics` types instead.
### New features
* `RobustClientPackaging.WriteClientResources()` and `RobustServerPackaging.WriteServerResources()` now have an overload taking in a set of things to ignore in the content resources directory.
* Added `IPrototypeManager.Resolve()`, which logs an error if the resolved prototype does not exist. This is effectively the previous (but not original) default behavior of `IPrototypeManager.TryIndex`.
* There's now a ViewVariables property editor for tuples.
* Added `ColorNaming` helper functions for getting textual descriptions of color values.
* Added Oklab/Oklch conversion functions for `Color`.
* `ColorSelectorSliders` now displays textual descriptions of color values.
* Added `TimeSpanExt.TryTimeSpan` to parse `TimeSpan`s with the `1.5h` format available in YAML.
* Added `ITestContextLike` and related classes to allow controlling pooled integration instances better.
* `EntProtoId` VV prop editors now don't allow setting invalid prototype IDs, inline with `ProtoId<T>`.
* Custom VV controls can now be registered using `IViewVariableControlFactory`.
* The entity spawn window now shows all placement modes registered with `IPlacementManager`.
* Added `VectorHelpers.InterpolateCubic` for `System.Numerics` `Vector3` and `Vector4`.
* Added deconstruct helpers for `System.Numerics` `Vector3` and `Vector4`.
### Bugfixes
* Pooled integration instances returned by `RobustIntegrationTest` are now treated as non-idle, for consistency with non-pooled startups.
* `SharedAudioSystem.SetState` no longer calls `DirtyField` on `PlaybackPosition`, an unnetworked field.
* Fix loading texture files from the root directory.
* Fix integration test pooling leaking non-reusable instances.
* Fix multiple bugs where VV displayed the wrong property editor for remote values.
* VV displays group headings again in member list.
* Fix a stack overflow that could occur with `ColorSelectorSliders`.
* `MidiRenderer` now properly handles `NoteOn` events with 0 velocity (which should actually be treated as `NoteOff` events).
### Other
* The debug assert for `RobustRandom.Next(TimeSpan, TimeSpan)` now allows for the two arguments to be equal.
* The configuration system will now report an error instead of warning if it fails to load the config file.
* Members in `IntegrationInstance` that enforce the instance is idle now always allow access from the instance's thread (e.g. from a callback).
* `IPrototypeManager` methods now have `[ForbidLiteral]` where appropriate.
* Performance improvements to physics system.
* `[ValidatePrototypeIdAttribute]` has been marked as obsolete.
* `ParallelManager` no longer cuts out exception information for caught job exceptions.
* Improved logging for PVS uninitialized/deleted entity errors.
### Internal
* General code & warning cleanup.
* Fix `VisibilityTest` being unreliable.
* `ColorSelectorSliders` has been internally refactored.
* Added CI workflows that test all RT build configurations.
## 264.0.0
### Breaking changes
* `IPrototypeManager.Index(Type kind, string id)` now throws `UnknownPrototypeException` instead of `KeyNotFoundException`, for consistency with `IPrototypeManager.Index<T>`.
### New features
* Types can now implement the new interface `IRobustCloneable<T>` to be cloned by the component state source generator.
* Added extra Roslyn Analyzers to detect some misuse of prototypes:
* Network serializing prototypes (tagging them with `[Serializable, NetSerializable]`).
* Constructing new instances of prototypes directly.
* Add `PrototypeManagerExt.Index` helper function that takes a nullable `ProtoId<T>`, returning null if the ID is null.
* Added an `AlwaysActive` field to `WebViewControl` to make a browser window active even when not in the UI tree.
* Made some common dependencies accessible through `IPlacementManager`.
* Added a new `GENITIVE()` localization helper function, which is useful for certain languages.
### Bugfixes
* Sprite scale is now correctly applied to sprite boundaries in `SpriteSystem.GetLocalBounds`.
* Fixed documentation for `IPrototypeManager.Index<T>` stating that `KeyNotFoundException` gets thrown, when in actuality `UnknownPrototypeException` gets thrown.
### Other
* More tiny optimizations to `DataDefinitionAnalyzer`.
* NetSerializer has been updated. On debug, it will now report *where* a type that can't be serialized is referenced from.
### Internal
* Minor internal code cleanup.
## 263.0.0
### Breaking changes
* Fully removed some non-`Entity<T>` container methods.
### New features
* `IMidiRenderer.LoadSoundfont` has been split into `LoadSoundfontResource` and `LoadSoundfontUser`, the original now being deprecated.
* Client command execution now properly catches errors instead of letting them bubble up through the input stack.
* Added `CompletionHelper.PrototypeIdsLimited` API to allow commands to autocomplete entity prototype IDs.
* Added `spawn:in` Toolshed command.
* Added `MapLoaderSystem.TryLoadGeneric` overload to load from a `Stream`.
* Added `OutputPanel.GetMessage()` and `OutputPanel.SetMessage()` to allow replacing individual messages.
### Bugfixes
* Fixed debug asserts when using MIDI on Windows.
* Fixed an error getting logged on startup on macOS related to window icons.
* `CC-BY-NC-ND-4.0` is now a valid license for the RGA validator.
* Fixed `TabContainer.CurrentTab` clamping against the wrong value.
* Fix culture-based parsing in `TimespanSerializer`.
* Fixed grid rendering blowing up on tile IDs that aren't registered.
* Fixed debug assert when loading MIDI soundfonts on Windows.
* Make `ColorSelectorSliders` properly update the dropdown when changing `SelectorType`.
* Fixed `tpto` allowing teleports to oneself, thereby causing them to be deleted.
* Fix OpenAL extensions being requested incorrectly, causing an error on macOS.
* Fixed horizontal measuring of markup controls in rich text.
### Other
* Improved logging for some audio entity errors.
* Avoided more server stutters when using `csci`.
* Improved physics performance.
* Made various localization functions like `GENDER()` not throw if passed a string instead of an `EntityUid`.
* The generic clause on `EntitySystem.AddComp<T>` has been changed to `IComponent` (from `Component`) for consistency with `IEntityManager.AddComponent<T>`.
* `DataDefinitionAnalyzer` has been optimized somewhat.
* Improved assert logging error message when static data fields are encountered.
### Internal
* Warning cleanup.
* Added more tests for `DataDefinitionAnalyzer`.
* Consistently use `EntitySystem` proxy methods in engine.
## 262.0.0
### Breaking changes
* Toolshed commands will now validate that each non-generic command argument is parseable (i.e., has a corresponding type parser). This check can be disabled by explicitly marking the argument as unparseable via `CommandArgumentAttribute.Unparseable`.
### New features
* `ToolshedManager.TryParse` now also supports nullable value types.
* Add an ignoredComponents arg to IsDefault.
### Bugfixes
* Fix `SpriteComponent.Layer.Visible` setter not marking a sprite's bounding box as dirty.
* The audio params in the passed SoundSpecifier for PlayStatic(SoundSpecifier, Filter, ...) will now be used as a default like other PlayStatic overrides.
* Fix windows not saving their positions correctly when their x position is <= 0.
* Fix transform state handling overriding PVS detachment.
## 261.2.0
### New features
* Implement IEquatable for ResolvedPathSpecifier & ResolvedCollectionSpecifier.
* Add NearestChunkEnumerator.
### Bugfixes
* Fix static entities not having the center of mass updated.
* Fix TryQueueDelete.
* Fix tpto potentially parenting grids to non-map entities.
### Other
* TileChangedEvent is now raised once in clientside grid state handling rather than per tile.
* Removed ITileDefinition.ID as it was redundant.
* Change the lifestage checks on predicted entity deletion to check for terminating.
### Internal
* Update some `GetComponentName<T>` uses to generic.
## 261.1.0
### New features
* Automatically create logger sawmills for `UIController`s similar to `EntitySystem`s.
### Bugfixes
* Fix physics forces not auto-clearing / respecting the cvar.
### Internal
* Cleanup more compiler warnings in unit tests.
## 261.0.0
### Breaking changes
* Remove unused TryGetContainingContainer override.
* Stop recursive FrameUpdates for controls that are not visible.
* Initialize LocMgr earlier in the callstack for GameController.
* Fix FastNoiseLise fractal bounding and remove its DataField property as it should be derived on other properties updating.
* Make RaiseMoveEvent internal.
* MovedGridsComponent and PhysicsMapComponent are now purged and properties on `SharedPhysicsSystem`. Additionally the TransformComponent for Awake entities is stored alongside the PhysicsComponent for them.
* TransformComponent is now stored on physics contacts.
* Gravity2DComponent and Gravity2DController were moved to SharedPhysicsSystem.
### New features
* `IFileDialogManager` now allows specifying `FileAccess` and `FileShare` modes.
* Add Intersects and Enlarged to Box2i in line with Box2.
* Make `KeyFrame`s on `AnimationTrackProperty` public settable.
* Add the spawned entities to a returned array from `SpawnEntitiesAttachedTo`.
### Bugfixes
* Fixed SDL3 file dialog implementation having a memory leak and not opening files read-write.
* Fix GetMapLinearVelocity.
### Other
* `uploadfile` and `loadprototype` commands now only open files with read access.
* Optimize `ToMapCoordinates`.
### Internal
* Cleanup on internals of `IFileDialogManager`, removing duplicate code.
* Fix Contacts not correctly being marked as `Touching` while contact is ongoing.
## 260.2.1
## 260.2.0

View File

@@ -21,8 +21,7 @@ zzzz-object-pronoun = { GENDER($ent) ->
}
# Used internally by the DAT-OBJ() function.
# Not used in en-US. Created to support other languages.
# (e.g., "to him," "for her")
# Not used in en-US. Created for supporting other languages.
zzzz-dat-object = { GENDER($ent) ->
[male] him
[female] her
@@ -30,16 +29,6 @@ zzzz-dat-object = { GENDER($ent) ->
*[neuter] it
}
# Used internally by the GENITIVE() function.
# Not used in en-US. Created to support other languages.
# e.g., "у него" (Russian), "seines Vaters" (German).
zzzz-genitive = { GENDER($ent) ->
[male] his
[female] her
[epicene] their
*[neuter] its
}
# Used internally by the POSS-PRONOUN() function.
zzzz-possessive-pronoun = { GENDER($ent) ->
[male] his

View File

@@ -1,32 +0,0 @@
color-hue-chroma-lightness = {$lightness} {$chroma} {$hue}
color-hue-chroma = {$chroma} {$hue}
color-hue-lightness = {$lightness} {$hue}
color-very-dark = very dark
color-dark = dark
color-light = light
color-very-light = very light
color-mixed-hue = {$a} {$b}
color-pale = pale
color-gray-adjective = gray
color-strong = strong
color-pink = pink
color-red = red
color-orange = orange
color-yellow = yellow
color-green = green
color-cyan = cyan
color-blue = blue
color-purple = purple
color-brown = brown
color-white = white
color-gray = gray
color-black = black
color-pink-color-red = pinkish red
color-red-color-orange = reddish orange
color-orange-color-yellow = orangeish yellow
color-yellow-color-green = yellowish green
color-green-color-cyan = greenish cyan
color-cyan-color-blue = cyanish blue
color-blue-color-purple = blueish purple
color-purple-color-pink = purpleish pink

View File

@@ -195,8 +195,6 @@ command-description-spawn-at =
Spawns an entity at the given coordinates.
command-description-spawn-on =
Spawns an entity on the given entity, at it's coordinates.
command-description-spawn-in =
Spawns an entity in the given container on the given entity, dropping it at its coordinates if it doesn't fit
command-description-spawn-attached =
Spawns an entity attached to the given entity, at (0 0) relative to it.
command-description-mappos =

View File

@@ -55,7 +55,7 @@ public sealed class DataDefinitionAnalyzerTest
namespace Robust.Shared.Serialization.Manager.Attributes
{
public class DataFieldBaseAttribute : Attribute;
public class DataFieldAttribute(string? tag = null) : DataFieldBaseAttribute;
public class DataFieldAttribute : DataFieldBaseAttribute;
public sealed class DataDefinitionAttribute : Attribute;
public sealed class NotYamlSerializableAttribute : Attribute;
}
@@ -117,61 +117,6 @@ public sealed class DataDefinitionAnalyzerTest
);
}
[Test]
public async Task PartialDataDefinitionTest()
{
const string code = """
using Robust.Shared.Serialization.Manager.Attributes;
[DataDefinition]
public sealed class Foo { }
""";
await Verifier(code,
// /0/Test0.cs(4,15): error RA0017: Type Foo is a DataDefinition but is not partial
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataDefinitionPartialRule).WithSpan(4, 15, 4, 20).WithArguments("Foo")
);
}
[Test]
public async Task NestedPartialDataDefinitionTest()
{
const string code = """
using Robust.Shared.Serialization.Manager.Attributes;
public sealed class Foo
{
[DataDefinition]
public sealed partial class Nested { }
}
""";
await Verifier(code,
// /0/Test0.cs(3,15): error RA0018: Type Foo contains nested data definition Nested but is not partial
VerifyCS.Diagnostic(DataDefinitionAnalyzer.NestedDataDefinitionPartialRule).WithSpan(3, 15, 3, 20).WithArguments("Foo", "Nested")
);
}
[Test]
public async Task RedundantDataFieldTagTest()
{
const string code = """
using Robust.Shared.Serialization.Manager.Attributes;
[DataDefinition]
public sealed partial class Foo
{
[DataField("someValue")]
public int SomeValue;
}
""";
await Verifier(code,
// /0/Test0.cs(6,6): info RA0027: Data field SomeValue in data definition Foo has an explicitly set tag that matches autogenerated tag
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldRedundantTagRule).WithSpan(6, 6, 6, 28).WithArguments("SomeValue", "Foo")
);
}
[Test]
public async Task ReadOnlyPropertyTest()
{

View File

@@ -1,64 +0,0 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeInstantiationAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(PrototypeInstantiationAnalyzer))]
public sealed class PrototypeInstantiationAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new RTAnalyzerTest<PrototypeInstantiationAnalyzer>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Prototypes.Attributes.cs",
"Robust.Shared.Prototypes.IPrototype.cs",
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.Serialization;
using Robust.Shared.Prototypes;
[Prototype]
public sealed class FooPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
public static class Bad
{
public static FooPrototype Real()
{
return new FooPrototype();
}
}
""";
await Verifier(code,
// /0/Test0.cs(15,16): warning RA0039: Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.
VerifyCS.Diagnostic().WithSpan(15, 16, 15, 34));
}
}

View File

@@ -1,61 +0,0 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.PrototypeNetSerializableAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(PrototypeNetSerializableAnalyzer))]
public sealed class PrototypeNetSerializableAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new RTAnalyzerTest<PrototypeNetSerializableAnalyzer>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Serialization.NetSerializableAttribute.cs",
"Robust.Shared.Prototypes.Attributes.cs",
"Robust.Shared.Prototypes.IPrototype.cs",
"Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using System;
using Robust.Shared.Serialization;
using Robust.Shared.Prototypes;
[Prototype]
[Serializable, NetSerializable]
public sealed class FooPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
}
""";
await Verifier(code,
// /0/Test0.cs(7,21): warning RA0037: Type FooPrototype is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleNetSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"),
// /0/Test0.cs(7,21): warning RA0038: Type FooPrototype is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.
VerifyCS.Diagnostic(PrototypeNetSerializableAnalyzer.RuleSerializable).WithSpan(7, 21, 7, 33).WithArguments("FooPrototype"));
}
}

View File

@@ -1,17 +0,0 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
namespace Robust.Analyzers.Tests;
public sealed class RTAnalyzerTest<TAnalyzer> : CSharpAnalyzerTest<TAnalyzer, DefaultVerifier>
where TAnalyzer : DiagnosticAnalyzer, new()
{
protected override ParseOptions CreateParseOptions()
{
var baseOptions = (CSharpParseOptions) base.CreateParseOptions();
return baseOptions.WithPreprocessorSymbols("ROBUST_ANALYZERS_TEST");
}
}

View File

@@ -17,10 +17,6 @@
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ObsoleteInheritanceAttribute.cs" LogicalName="Robust.Shared.Analyzers.ObsoleteInheritanceAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Serialization\NetSerializableAttribute.cs" LogicalName="Robust.Shared.Serialization.NetSerializableAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Prototypes\Attributes.cs" LogicalName="Robust.Shared.Prototypes.Attributes.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Prototypes\IPrototype.cs" LogicalName="Robust.Shared.Prototypes.IPrototype.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Serialization\Manager\Attributes\DataFieldAttribute.cs" LogicalName="Robust.Shared.Serialization.Manager.Attributes.DataFieldAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<PropertyGroup>

View File

@@ -22,7 +22,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private const string DataFieldAttributeName = "DataField";
private const string ViewVariablesAttributeName = "ViewVariables";
public static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
Diagnostics.IdDataDefinitionPartial,
"Type must be partial",
"Type {0} is a DataDefinition but is not partial",
@@ -32,7 +32,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to mark any type that is a data definition as partial."
);
public static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
Diagnostics.IdNestedDataDefinitionPartial,
"Type must be partial",
"Type {0} contains nested data definition {1} but is not partial",
@@ -62,7 +62,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to add a setter."
);
public static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
Diagnostics.IdDataFieldRedundantTag,
"Data field has redundant tag specified",
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
@@ -102,31 +102,23 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSymbolStartAction(symbolContext =>
{
if (symbolContext.Symbol is not INamedTypeSymbol typeSymbol)
return;
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
if (!IsDataDefinition(typeSymbol))
return;
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
}, SymbolKind.NamedType);
context.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
}
private static void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
{
if (context.Node is not TypeDeclarationSyntax declaration)
return;
if (context.ContainingSymbol is not INamedTypeSymbol type)
var type = context.SemanticModel.GetDeclaredSymbol(declaration)!;
if (!IsDataDefinition(type))
return;
if (!IsPartial(declaration))
@@ -137,7 +129,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
var containingType = type.ContainingType;
while (containingType != null)
{
var containingTypeDeclaration = (TypeDeclarationSyntax)containingType.DeclaringSyntaxReferences[0].GetSyntax();
var containingTypeDeclaration = (TypeDeclarationSyntax) containingType.DeclaringSyntaxReferences[0].GetSyntax();
if (!IsPartial(containingTypeDeclaration))
{
context.ReportDiagnostic(Diagnostic.Create(NestedDataDefinitionPartialRule, containingTypeDeclaration.Keyword.GetLocation(), containingType.Name, type.Name));
@@ -147,31 +139,32 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private static void AnalyzeDataField(SyntaxNodeAnalysisContext context)
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
{
if (context.Node is not FieldDeclarationSyntax field)
return;
if (context.ContainingSymbol?.ContainingType is not INamedTypeSymbol type)
var typeDeclaration = field.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration == null)
return;
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
if (!IsDataDefinition(type))
return;
foreach (var variable in field.Declaration.Variables)
{
var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (fieldSymbol == null)
continue;
if (!IsDataField(fieldSymbol, out _, out var datafieldAttribute))
continue;
if (IsReadOnlyDataField(type, fieldSymbol))
{
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
}
if (HasRedundantTag(fieldSymbol, datafieldAttribute))
if (HasRedundantTag(fieldSymbol))
{
TryGetAttributeLocation(field, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, fieldSymbol.Name, type.Name));
@@ -198,33 +191,30 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
{
if (context.Node is not PropertyDeclarationSyntax property)
return;
if (context.ContainingSymbol is not IPropertySymbol propertySymbol)
var typeDeclaration = property.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration == null)
return;
if (propertySymbol.ContainingType is not INamedTypeSymbol type)
return;
if (type.IsRecord || type.IsValueType)
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
if (!IsDataDefinition(type) || type.IsRecord || type.IsValueType)
return;
var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property);
if (propertySymbol == null)
return;
if (!IsDataField(propertySymbol, out _, out var datafieldAttribute))
return;
if (IsReadOnlyDataField(type, propertySymbol))
{
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
}
if (HasRedundantTag(propertySymbol, datafieldAttribute))
if (HasRedundantTag(propertySymbol))
{
TryGetAttributeLocation(property, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, propertySymbol.Name, type.Name));
@@ -252,6 +242,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
{
if (!IsDataField(field, out _, out _))
return false;
return IsReadOnlyMember(type, field);
}
@@ -376,14 +369,17 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool HasRedundantTag(ISymbol symbol, AttributeData datafieldAttribute)
private static bool HasRedundantTag(ISymbol symbol)
{
if (!IsDataField(symbol, out var _, out var attribute))
return false;
// No args, no problem
if (datafieldAttribute.ConstructorArguments.Length == 0)
if (attribute.ConstructorArguments.Length == 0)
return false;
// If a tag is explicitly specified, it will be the first argument...
var tagArgument = datafieldAttribute.ConstructorArguments[0];
var tagArgument = attribute.ConstructorArguments[0];
// ...but the first arg could also something else, since tag is optional
// so we make sure that it's a string
if (tagArgument.Value is not string explicitName)
@@ -398,6 +394,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static bool HasVVReadWrite(ISymbol symbol)
{
if (!IsDataField(symbol, out _, out _))
return false;
// Make sure it has ViewVariablesAttribute
AttributeData? viewVariablesAttribute = null;
foreach (var attr in symbol.GetAttributes())
@@ -423,6 +422,9 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static bool IsNotYamlSerializable(ISymbol field, ITypeSymbol type)
{
if (!IsDataField(field, out _, out _))
return false;
return HasAttribute(type, NotYamlSerializableName);
}

View File

@@ -1,48 +0,0 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PrototypeInstantiationAnalyzer : DiagnosticAnalyzer
{
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
public static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdPrototypeInstantiation,
"Do not instantiate prototypes directly",
"Do not instantiate prototypes directly. Prototypes should always be instantiated by the prototype manager.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(static ctx =>
{
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
if (prototypeInterface == null)
return;
ctx.RegisterOperationAction(symContext => Check(prototypeInterface, symContext), OperationKind.ObjectCreation);
});
}
private static void Check(INamedTypeSymbol prototypeInterface, OperationAnalysisContext ctx)
{
if (ctx.Operation is not IObjectCreationOperation { Type: { } resultType } creationOp)
return;
if (!TypeSymbolHelper.ImplementsInterface(resultType, prototypeInterface))
return;
ctx.ReportDiagnostic(Diagnostic.Create(Rule, creationOp.Syntax.GetLocation()));
}
}

View File

@@ -1,76 +0,0 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class PrototypeNetSerializableAnalyzer : DiagnosticAnalyzer
{
private const string PrototypeInterfaceType = "Robust.Shared.Prototypes.IPrototype";
private const string NetSerializableAttributeType = "Robust.Shared.Serialization.NetSerializableAttribute";
public static readonly DiagnosticDescriptor RuleNetSerializable = new(
Diagnostics.IdPrototypeNetSerializable,
"Prototypes should not be [NetSerializable]",
"Type {0} is a prototype and marked as [NetSerializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
"Usage",
DiagnosticSeverity.Warning,
true);
public static readonly DiagnosticDescriptor RuleSerializable = new(
Diagnostics.IdPrototypeSerializable,
"Prototypes should not be [Serializable]",
"Type {0} is a prototype and marked as [Serializable]. Prototypes should not be directly sent over the network, send their IDs instead.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [
RuleNetSerializable,
RuleSerializable
];
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterCompilationStartAction(static ctx =>
{
var prototypeInterface = ctx.Compilation.GetTypeByMetadataName(PrototypeInterfaceType);
var netSerializableAttribute = ctx.Compilation.GetTypeByMetadataName(NetSerializableAttributeType);
if (prototypeInterface == null || netSerializableAttribute == null)
return;
ctx.RegisterSymbolAction(symbolContext => CheckClass(prototypeInterface, netSerializableAttribute, symbolContext), SymbolKind.NamedType);
});
}
private static void CheckClass(
INamedTypeSymbol prototypeInterface,
INamedTypeSymbol netSerializableAttribute,
SymbolAnalysisContext symbolContext)
{
if (symbolContext.Symbol is not INamedTypeSymbol symbol)
return;
if (!TypeSymbolHelper.ImplementsInterface(symbol, prototypeInterface))
return;
if (AttributeHelper.HasAttribute(symbol, netSerializableAttribute, out _))
{
symbolContext.ReportDiagnostic(
Diagnostic.Create(RuleNetSerializable, symbol.Locations[0], symbol.ToDisplayString()));
}
if (symbol.IsSerializable)
{
symbolContext.ReportDiagnostic(
Diagnostic.Create(RuleSerializable, symbol.Locations[0], symbol.ToDisplayString()));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,8 @@ using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.Animations;
using Robust.Shared.Maths;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Animations
{
@@ -11,7 +13,7 @@ namespace Robust.Client.Animations
/// </summary>
public abstract class AnimationTrackProperty : AnimationTrack
{
public List<KeyFrame> KeyFrames { get; set; } = new();
public List<KeyFrame> KeyFrames { get; protected set; } = new();
/// <summary>
/// How to interpolate values when between two keyframes.
@@ -120,9 +122,9 @@ namespace Robust.Client.Animations
case Vector2 vector2:
return Vector2Helpers.InterpolateCubic((Vector2) preA, vector2, (Vector2) b, (Vector2) postB, t);
case Vector3 vector3:
return VectorHelpers.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
return Vector3.InterpolateCubic((Vector3) preA, vector3, (Vector3) b, (Vector3) postB, t);
case Vector4 vector4:
return VectorHelpers.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
return Vector4.InterpolateCubic((Vector4) preA, vector4, (Vector4) b, (Vector4) postB, t);
case float f:
return MathHelper.InterpolateCubic((float) preA, f, (float) b, (float) postB, t);
case double d:

View File

@@ -57,8 +57,8 @@ internal sealed partial class AudioManager : IAudioInternal
_checkAlError();
// Load up AL context extensions.
var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? "";
foreach (var extension in s.Split(' ', StringSplitOptions.RemoveEmptyEntries))
var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? "";
foreach (var extension in s.Split(' '))
{
_alContextExtensions.Add(extension);
}

View File

@@ -582,7 +582,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
if (TerminatingOrDeleted(entity))
{
LogAudioPlaybackOnInvalidEntity(specifier, entity);
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
return null;
}
@@ -626,7 +626,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
if (TerminatingOrDeleted(coordinates.EntityId))
{
LogAudioPlaybackOnInvalidEntity(specifier, coordinates.EntityId);
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
return null;
}
@@ -753,12 +753,6 @@ public sealed partial class AudioSystem : SharedAudioSystem
return _resourceCache.GetResource<AudioResource>(filename).AudioStream.Length;
}
private void LogAudioPlaybackOnInvalidEntity(ResolvedSoundSpecifier? specifier, EntityUid entityId)
{
var soundInfo = specifier?.ToString() ?? "unknown sound";
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entityId)}. Sound: {soundInfo}. Trace: {Environment.StackTrace}");
}
#region Jobs
private record struct UpdateAudioJob : IParallelRobustJob

View File

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

View File

@@ -6,7 +6,6 @@ using Robust.Shared.Audio.Midi;
using Robust.Shared.Audio.Sources;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Robust.Client.Audio.Midi;
@@ -157,13 +156,8 @@ public interface IMidiRenderer : IDisposable
/// <summary>
/// Loads a new soundfont into the renderer.
/// </summary>
[Obsolete("Use LoadSoundfontResource or LoadSoundfontUser instead")]
void LoadSoundfont(string filename, bool resetPresets = false);
void LoadSoundfontResource(ResPath path, bool resetPresets = false);
void LoadSoundfontUser(ResPath path, bool resetPresets = false);
/// <summary>
/// Invoked whenever a new midi event is registered.
/// </summary>

View File

@@ -1,262 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using NFluidsynth;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiManager
{
// For loading sound fonts, we have to use a callback model where we can only parse a string.
// This API, frankly, fucking sucks.
//
// These prefixes are used to separate the various places a file *can* be loaded from.
//
// We cannot prevent Fluidsynth from trying to load prefixed paths itself if they are invalid
// So if content specifies "/foobar.sf2" to be loaded and it doesn't exist,
// Fluidsynth *will* try to fopen("RES:/foobar.sf2"). For this reason I'm putting in some nonsense characters
// that will pass through Fluidsynth fine, but make sure the filename is *never* a practically valid OS path.
//
// NOTE: Raw disk paths *cannot* be prefixed as Fluidsynth needs to load those itself.
// Specifically, their .dls loader doesn't respect file callbacks.
// If you're curious why this is: it's two-fold:
// * The Fluidsynth C code for the .dls loader just doesn't use the file callbacks, period.
// * Even if it did, we're not specifying those file callbacks, as they're per loader,
// and we're only adding a *new* sound font loader with file callbacks, not modifying the existing ones.
// The loader for .sfX format and .dls format are different loader objects in Fluidsynth.
internal const string PrefixCommon = "!/ -?\x0001";
internal const string PrefixLegacy = PrefixCommon + "LEGACY";
internal const string PrefixUser = PrefixCommon + "USER";
internal const string PrefixResources = PrefixCommon + "RES";
private void LoadSoundFontSetup(MidiRenderer renderer)
{
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfontResource(FallbackSoundfont);
// Load system-specific soundfonts.
if (OperatingSystem.IsLinux())
{
foreach (var filepath in LinuxSoundfonts)
{
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath))
continue;
try
{
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
renderer.LoadSoundfontDisk(filepath);
}
catch (Exception)
{
continue;
}
break;
}
}
else if (OperatingSystem.IsMacOS())
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
renderer.LoadSoundfontDisk(OsxSoundfont);
}
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
renderer.LoadSoundfontDisk(WindowsSoundfont);
}
}
// Maybe load soundfont specified in environment variable.
// Load it here so it can override system soundfonts but not content or user data soundfonts.
if (Environment.GetEnvironmentVariable(SoundfontEnvironmentVariable) is { } soundfontOverride)
{
// Just to avoid funny shit: avoid people smuggling a prefix in here.
// I wish I could separate this properly...
var (prefix, _) = SplitPrefix(soundfontOverride);
if (IsValidPrefix(prefix))
{
_midiSawmill.Error($"Not respecting {SoundfontEnvironmentVariable} env variable: invalid file path");
}
else if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
{
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
renderer.LoadSoundfontDisk(soundfontOverride);
}
}
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading content soundfont {file}");
renderer.LoadSoundfontResource(file);
}
// Load every soundfont from the user data directory last, since those may override any other soundfont.
_midiSawmill.Debug($"Loading soundfonts from user data directory {CustomSoundfontDirectory}");
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
foreach (var file in enumerator)
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading user soundfont {file}");
renderer.LoadSoundfontUser(file);
}
}
internal static string PrefixPath(string prefix, string value)
{
return $"{prefix}:{value}";
}
internal static (string prefix, string? value) SplitPrefix(string filename)
{
var filenameSplit = filename.Split(':', 2);
if (filenameSplit.Length == 1)
return (filenameSplit[0], null);
return (filenameSplit[0], filenameSplit[1]);
}
internal static bool IsValidPrefix(string prefix)
{
return prefix is PrefixLegacy or PrefixUser or PrefixResources;
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
{
private readonly MidiManager _parent;
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public ResourceLoaderCallbacks(MidiManager parent)
{
_parent = parent;
}
public override IntPtr Open(string filename)
{
if (string.IsNullOrEmpty(filename))
{
return IntPtr.Zero;
}
Stream stream;
try
{
stream = OpenCore(filename);
}
catch (Exception e)
{
_parent._midiSawmill.Error($"Error while opening sound font: {e}");
return IntPtr.Zero;
}
var id = _nextStreamId++;
_openStreams.Add(id, stream);
return (IntPtr) id;
}
private Stream OpenCore(string filename)
{
var (prefix, value) = SplitPrefix(filename);
if (!IsValidPrefix(prefix) || value == null)
return File.OpenRead(filename);
var resourceCache = _parent._resourceManager;
var resourcePath = new ResPath(value);
switch (prefix)
{
case PrefixUser:
return resourceCache.UserData.OpenRead(resourcePath);
case PrefixResources:
return resourceCache.ContentFileRead(resourcePath);
case PrefixLegacy:
// Try resources first, then try user data.
if (resourceCache.TryContentFileRead(resourcePath, out var stream))
return stream;
return resourceCache.UserData.OpenRead(resourcePath);
default:
throw new UnreachableException("Invalid prefix specified!");
}
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
stream.ReadExact(buffer);
buffer.CopyTo(span);
}
else
{
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
}
catch (EndOfStreamException)
{
return -1;
}
return 0;
}
public override int Seek(IntPtr sfHandle, long offset, SeekOrigin origin)
{
var stream = _openStreams[(int) sfHandle];
stream.Seek(offset, origin);
return 0;
}
public override long Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
return (long) stream.Position;
}
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
return 0;
}
}
}

View File

@@ -119,7 +119,7 @@ internal sealed partial class MidiManager : IMidiManager
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
private static readonly ResPath FallbackSoundfont = new ResPath("/Midi/fallback.sf2");
private const string FallbackSoundfont = "/Midi/fallback.sf2";
private const string ContentCustomSoundfontDirectory = "/Audio/MidiCustom/";
@@ -265,7 +265,81 @@ internal sealed partial class MidiManager : IMidiManager
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
LoadSoundFontSetup(renderer);
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
// Load system-specific soundfonts.
if (OperatingSystem.IsLinux())
{
foreach (var filepath in LinuxSoundfonts)
{
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath))
continue;
try
{
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
renderer.LoadSoundfont(filepath);
}
catch (Exception)
{
continue;
}
break;
}
}
else if (OperatingSystem.IsMacOS())
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
renderer.LoadSoundfont(OsxSoundfont);
}
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
renderer.LoadSoundfont(WindowsSoundfont);
}
}
// Maybe load soundfont specified in environment variable.
// Load it here so it can override system soundfonts but not content or user data soundfonts.
if (Environment.GetEnvironmentVariable(SoundfontEnvironmentVariable) is {} soundfontOverride)
{
if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
{
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
renderer.LoadSoundfont(soundfontOverride);
}
}
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading content soundfont {file}");
renderer.LoadSoundfont(file.ToString());
}
var userDataPath = _resourceManager.UserData.RootDir == null
? CustomSoundfontDirectory
: new ResPath(_resourceManager.UserData.RootDir) / CustomSoundfontDirectory.ToRelativePath();
// Load every soundfont from the user data directory last, since those may override any other soundfont.
_midiSawmill.Debug($"Loading soundfonts from user data directory {userDataPath}");
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
foreach (var file in enumerator)
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading user soundfont {file}");
renderer.LoadSoundfont(file.ToString());
}
renderer.Source.Gain = _gain;
@@ -498,6 +572,130 @@ internal sealed partial class MidiManager : IMidiManager
midiEvent.Velocity);
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
{
private readonly MidiManager _parent;
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public ResourceLoaderCallbacks(MidiManager parent)
{
_parent = parent;
}
public override IntPtr Open(string filename)
{
if (string.IsNullOrEmpty(filename))
{
return IntPtr.Zero;
}
Stream? stream;
var resourceCache = _parent._resourceManager;
var resourcePath = new ResPath(filename);
if (resourcePath.IsRooted)
{
// is it in content?
if (resourceCache.ContentFileExists(filename))
{
if (!resourceCache.TryContentFileRead(filename, out stream))
return IntPtr.Zero;
}
// is it in userdata?
else if (resourceCache.UserData.Exists(resourcePath))
{
stream = resourceCache.UserData.OpenRead(resourcePath);
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
}
else
{
return IntPtr.Zero;
}
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
}
else
{
return IntPtr.Zero;
}
var id = _nextStreamId++;
_openStreams.Add(id, stream);
return (IntPtr) id;
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
stream.ReadExact(buffer);
buffer.CopyTo(span);
}
else
{
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
}
catch (EndOfStreamException)
{
return -1;
}
return 0;
}
public override int Seek(IntPtr sfHandle, long offset, SeekOrigin origin)
{
var stream = _openStreams[(int) sfHandle];
stream.Seek(offset, origin);
return 0;
}
public override long Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
return (long) stream.Position;
}
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
return 0;
}
}
#region Jobs
private record struct MidiUpdateJob : IParallelRobustJob

View File

@@ -1,45 +0,0 @@
using System;
using Robust.Shared.Utility;
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiRenderer
{
[Obsolete("Use LoadSoundfontResource or LoadSoundfontUser instead")]
public void LoadSoundfont(string filename, bool resetPresets = true)
{
LoadSoundfontCore(
MidiManager.PrefixPath(MidiManager.PrefixLegacy, filename),
resetPresets);
}
public void LoadSoundfontResource(ResPath path, bool resetPresets = false)
{
LoadSoundfontCore(
MidiManager.PrefixPath(MidiManager.PrefixResources, path.ToString()),
resetPresets);
}
public void LoadSoundfontUser(ResPath path, bool resetPresets = false)
{
LoadSoundfontCore(
MidiManager.PrefixPath(MidiManager.PrefixUser, path.ToString()),
resetPresets);
}
internal void LoadSoundfontDisk(string path, bool resetPresets = false)
{
LoadSoundfontCore(
path,
resetPresets);
}
private void LoadSoundfontCore(string filenameString, bool resetPresets)
{
lock (_playerStateLock)
{
_synth.LoadSoundFont(filenameString, resetPresets);
MidiSoundfont = 1;
}
}
}

View File

@@ -16,7 +16,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiRenderer : IMidiRenderer
internal sealed class MidiRenderer : IMidiRenderer
{
private readonly IMidiManager _midiManager;
private readonly ITaskManager _taskManager;
@@ -435,6 +435,15 @@ internal sealed partial class MidiRenderer : IMidiRenderer
_sequencer.RemoveEvents(SequencerClientId.Wildcard, SequencerClientId.Wildcard, -1);
}
public void LoadSoundfont(string filename, bool resetPresets = true)
{
lock (_playerStateLock)
{
_synth.LoadSoundFont(filename, resetPresets);
MidiSoundfont = 1;
}
}
void IMidiRenderer.Render()
{
Render();
@@ -575,28 +584,18 @@ internal sealed partial class MidiRenderer : IMidiRenderer
case RobustMidiCommand.NoteOff:
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
_synth.NoteOff(midiEvent.Channel, midiEvent.Key);
break;
case RobustMidiCommand.NoteOn:
// Velocity 0 *can* represent a NoteOff event.
var velocity = midiEvent.Velocity;
if (velocity == 0)
{
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
break;
}
if (FilteredChannels[midiEvent.Channel])
break;
velocity = VelocityOverride ?? midiEvent.Velocity;
var velocity = VelocityOverride ?? midiEvent.Velocity;
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
_synth.NoteOn(midiEvent.Channel, midiEvent.Key, velocity);
break;
case RobustMidiCommand.AfterTouch:
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = midiEvent.Value;
_synth.KeyPressure(midiEvent.Channel, midiEvent.Key, midiEvent.Value);

View File

@@ -144,7 +144,6 @@ namespace Robust.Client
deps.Register<IViewVariablesManager, ClientViewVariablesManager>();
deps.Register<IClientViewVariablesManager, ClientViewVariablesManager>();
deps.Register<IClientViewVariablesManagerInternal, ClientViewVariablesManager>();
deps.Register<IViewVariableControlFactory, ViewVariableControlFactory>();
deps.Register<IClientConGroupController, ClientConGroupController>();
deps.Register<IScriptClient, ScriptClient>();
deps.Register<IRobustSerializer, ClientRobustSerializer>();

View File

@@ -21,7 +21,7 @@ public sealed class SpriteTreeSystem : ComponentTreeSystem<SpriteTreeComponent,
protected override Box2 ExtractAabb(in ComponentTreeEntry<SpriteComponent> entry, Vector2 pos, Angle rot)
{
// TODO SPRITE optimize this
// Because the just take the BB of the rotated BB, I'm pretty sure we do a lot of unnecessary maths.
// Because the just take the BB of the rotated BB, I'mt pretty sure we do a lot of unnecessary maths.
return _sprite.CalculateBounds((entry.Uid, entry.Component), pos, rot, default).CalcBoundingBox();
}

View File

@@ -191,16 +191,8 @@ namespace Robust.Client.Console
var shell = new ConsoleShell(this, session ?? _player.LocalSession, session == null);
var cmdArgs = args.ToArray();
try
{
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
cmd.Execute(shell, command, cmdArgs);
}
catch (Exception e)
{
_conLogger.Error($"ExecuteError - {command}:\n{e}");
shell.WriteError($"There was an error while executing the command: {e}");
}
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
cmd.Execute(shell, command, cmdArgs);
}
private bool CanExecute(string cmdName)

View File

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

View File

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

View File

@@ -160,7 +160,6 @@ namespace Robust.Client
}
_serializationManager.Initialize();
_loc.Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
@@ -183,6 +182,7 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_loc.Initialize();
// Make sure this is done before we try to load prototypes,
// avoid any possibility of race conditions causing the check to not finish

View File

@@ -296,7 +296,7 @@ namespace Robust.Client.GameObjects
public override void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|| ent.Comp1.EntityDeleted
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;
@@ -322,7 +322,7 @@ namespace Robust.Client.GameObjects
{
if (IsQueuedForDeletion(ent.Owner)
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|| ent.Comp1.EntityDeleted
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;

View File

@@ -24,7 +24,9 @@ namespace Robust.Client.GameObjects
public sealed class SpriteBoundsSystem : EntitySystem
{
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly SpriteTreeSystem _spriteTree = default!;
private SpriteBoundsOverlay? _overlay;
@@ -40,7 +42,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
DebugTools.AssertNull(_overlay);
_overlay = new SpriteBoundsOverlay(EntityManager);
_overlay = new SpriteBoundsOverlay(_spriteTree, _xformSystem);
_overlayManager.AddOverlay(_overlay);
}
else
@@ -55,13 +57,18 @@ namespace Robust.Client.GameObjects
private bool _enabled;
}
public sealed class SpriteBoundsOverlay(IEntityManager entMan) : Overlay
public sealed class SpriteBoundsOverlay : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly SharedTransformSystem _xformSystem = entMan.System<SharedTransformSystem>();
private readonly SpriteSystem _spriteSystem = entMan.System<SpriteSystem>();
private readonly SpriteTreeSystem _renderTree = entMan.System<SpriteTreeSystem>();
private readonly SharedTransformSystem _xformSystem;
private SpriteTreeSystem _renderTree;
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, SharedTransformSystem xformSystem)
{
_renderTree = renderTree;
_xformSystem = xformSystem;
}
protected internal override void Draw(in OverlayDrawArgs args)
{
@@ -69,11 +76,10 @@ namespace Robust.Client.GameObjects
var currentMap = args.MapId;
var viewport = args.WorldBounds;
foreach (var entry in _renderTree.QueryAabb(currentMap, viewport))
foreach (var (sprite, xform) in _renderTree.QueryAabb(currentMap, viewport))
{
var (sprite, xform) = entry;
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
var bounds = _spriteSystem.CalculateBounds((entry.Uid, sprite), worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
// Get scaled down bounds used to indicate the "south" of a sprite.
var localBound = bounds.Box;

View File

@@ -30,6 +30,8 @@ using Robust.Shared.ViewVariables;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
using Direction = Robust.Shared.Maths.Direction;
using Vector4 = Robust.Shared.Maths.Vector4;
using SysVec4 = System.Numerics.Vector4;
#pragma warning disable CS0618 // Type or member is obsolete
namespace Robust.Client.GameObjects
@@ -1222,8 +1224,6 @@ namespace Robust.Client.GameObjects
return;
_visible = value;
Owner.Comp.BoundsDirty = true;
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
if (_parent.Owner != EntityUid.Invalid)
Owner.Comp.Sys?.QueueUpdateIsInert(Owner);
@@ -1791,15 +1791,76 @@ namespace Robust.Client.GameObjects
[Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")]
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
{
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
return sys.GetPrototypeTextures(prototype, out noRot);
var results = new List<IDirectionalTextureProvider>();
noRot = false;
// TODO when moving to a non-static method in a system, pass in IComponentFactory
if (prototype.TryGetComponent(out IconComponent? icon))
{
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
results.Add(sys.GetIcon(icon));
return results;
}
if (!prototype.Components.TryGetValue("Sprite", out _))
{
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
EntitySystem.Get<AppearanceSystem>().OnChangeData(dummy, spriteComponent);
foreach (var layer in spriteComponent.AllLayers)
{
if (!layer.Visible) continue;
if (layer.Texture != null)
{
results.Add(layer.Texture);
continue;
}
if (!layer.RsiState.IsValid) continue;
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
if (rsi == null ||
!rsi.TryGetState(layer.RsiState, out var state))
continue;
results.Add(state);
}
noRot = spriteComponent.NoRotation;
entityManager.DeleteEntity(dummy);
if (results.Count == 0)
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
[Obsolete("Use SpriteSystem.GetPrototypeIcon() instead")]
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
return sys.GetPrototypeIcon(prototype);
// TODO when moving to a non-static method in a system, pass in IComponentFactory
if (prototype.TryGetComponent(out IconComponent? icon))
return sys.GetIcon(icon);
if (!prototype.Components.ContainsKey("Sprite"))
return sys.GetFallbackState();
var entityManager = IoCManager.Resolve<IEntityManager>();
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
var result = spriteComponent.Icon ?? sys.GetFallbackState();
entityManager.DeleteEntity(dummy);
return result;
}
}
}

View File

@@ -97,7 +97,7 @@ namespace Robust.Client.GameObjects
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
{
component ??= EnsureComp<AnimationPlayerComponent>(uid);
component ??= EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
}
@@ -158,7 +158,7 @@ namespace Robust.Client.GameObjects
public bool HasRunningAnimation(EntityUid uid, string key)
{
return TryComp(uid, out AnimationPlayerComponent? component) &&
return EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? component) &&
component.PlayingAnimations.ContainsKey(key);
}

View File

@@ -223,12 +223,12 @@ namespace Robust.Client.GameObjects
private void SetEntityContextActive(IInputManager inputMan, EntityUid entity)
{
if(entity == default || !Exists(entity))
if(entity == default || !EntityManager.EntityExists(entity))
throw new ArgumentNullException(nameof(entity));
if (!TryComp(entity, out InputComponent? inputComp))
if (!EntityManager.TryGetComponent(entity, out InputComponent? inputComp))
{
_sawmillInputContext.Debug($"AttachedEnt has no InputComponent: entId={entity}, entProto={Comp<MetaDataComponent>(entity).EntityPrototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
_sawmillInputContext.Debug($"AttachedEnt has no InputComponent: entId={entity}, entProto={EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
return;
}
@@ -239,7 +239,7 @@ namespace Robust.Client.GameObjects
}
else
{
_sawmillInputContext.Error($"Unknown context: entId={entity}, entProto={Comp<MetaDataComponent>(entity).EntityPrototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
_sawmillInputContext.Error($"Unknown context: entId={entity}, entProto={EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
}
}

View File

@@ -51,7 +51,7 @@ public sealed class ShowPlayerVelocityDebugSystem : EntitySystem
var player = _playerManager.LocalEntity;
if (player == null || !TryComp(player.Value, out PhysicsComponent? body))
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
{
_label.Visible = false;
return;

View File

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

View File

@@ -56,6 +56,10 @@ public sealed partial class SpriteSystem
/// </summary>
public IRsiStateLike GetPrototypeIcon(string prototype)
{
// Check if this prototype has been cached before, and if so return the result.
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
return cachedResult;
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
{
// The specified prototype doesn't exist, return the fallback "error" sprite.
@@ -63,7 +67,11 @@ public sealed partial class SpriteSystem
return GetFallbackState();
}
return GetPrototypeIcon(entityPrototype);
// Generate the icon and cache it in case it's ever needed again.
var result = GetPrototypeIcon(entityPrototype);
_cachedPrototypeIcons[prototype] = result;
return result;
}
/// <summary>
@@ -71,19 +79,13 @@ public sealed partial class SpriteSystem
/// This method does NOT cache the result.
/// </summary>
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype)
{
// This method may spawn & delete an entity to get an accruate RSI state, hence we cache the results
if (_cachedPrototypeIcons.TryGetValue(prototype.ID, out var cachedResult))
return cachedResult;
return _cachedPrototypeIcons[prototype.ID] = GetPrototypeIconInternal(prototype);
}
private IRsiStateLike GetPrototypeIconInternal(EntityPrototype prototype)
{
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
if (prototype.TryGetComponent(out IconComponent? icon, _factory))
if (prototype.Components.TryGetValue("Icon", out var compData)
&& compData.Component is IconComponent icon)
{
return GetIcon(icon);
}
// If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback.
if (!prototype.Components.ContainsKey("Sprite"))
@@ -100,63 +102,6 @@ public sealed partial class SpriteSystem
return result;
}
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto) =>
GetPrototypeTextures(proto, out _);
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto, out bool noRot)
{
var results = new List<IDirectionalTextureProvider>();
noRot = false;
if (proto.TryGetComponent(out IconComponent? icon, _factory))
{
results.Add(GetIcon(icon));
return results;
}
if (!proto.Components.ContainsKey("Sprite"))
{
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
var dummy = Spawn(proto.ID, MapCoordinates.Nullspace);
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
// TODO SPRITE is this needed?
// And if it is, shouldn't GetPrototypeIconInternal also use this?
_appearance.OnChangeData(dummy, spriteComponent);
foreach (var layer in spriteComponent.AllLayers)
{
if (!layer.Visible)
continue;
if (layer.Texture != null)
{
results.Add(layer.Texture);
continue;
}
if (!layer.RsiState.IsValid)
continue;
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
continue;
results.Add(state);
}
noRot = spriteComponent.NoRotation;
Del(dummy);
if (results.Count == 0)
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
[Pure]
public RSI.State GetFallbackState()
{

View File

@@ -7,6 +7,8 @@ using Robust.Shared.Graphics.RSI;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent;
using Vector4 = Robust.Shared.Maths.Vector4;
using SysVec4 = System.Numerics.Vector4;
namespace Robust.Client.GameObjects;
@@ -155,7 +157,7 @@ public sealed partial class SpriteSystem
// Negative color modulation values are by the default shader to disable light shading.
// Specifically we set colour = - 1 - colour
// This is good enough to ensure that non-negative values become negative & is trivially invertible.
layerColor = new(new Vector4(-1) - layerColor.RGBA);
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
}
drawingHandle.DrawTextureRectRegion(texture, quad, layerColor);

View File

@@ -34,12 +34,8 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
// Note that any new system dependencies have to be added to RobustUnitTest.BaseSetup()
[Dependency] private readonly SharedTransformSystem _xforms = default!;
[Dependency] private readonly SpriteTreeSystem _tree = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();

View File

@@ -631,7 +631,7 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
if ((meta.Flags & MetaDataFlags.Detached) == 0 && compState != null)
if (compState != null)
{
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(entity, comp, ref handleState);

View File

@@ -13,8 +13,6 @@ namespace Robust.Client.GameStates
{
internal sealed class NetInterpOverlay : Overlay
{
private static readonly ProtoId<ShaderPrototype> UnshadedShader = "unshaded";
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -34,7 +32,7 @@ namespace Robust.Client.GameStates
{
IoCManager.InjectDependencies(this);
_lookup = lookup;
_shader = _prototypeManager.Index(UnshadedShader).Instance();
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
_container = _entityManager.System<SharedContainerSystem>();
_xform = _entityManager.System<SharedTransformSystem>();
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
@@ -255,9 +254,9 @@ namespace Robust.Client.Graphics.Clyde
region = regionMaybe[tile.Variant];
}
var rotationMirroring = (_tileDefinitionManager.TryGetDefinition(tile.TypeId, out var tileDef) && tileDef.AllowRotationMirror) ?
tile.RotationMirroring
: 0;
var rotationMirroring = _tileDefinitionManager[tile.TypeId].AllowRotationMirror
? tile.RotationMirroring
: 0;
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region, rotationMirroring);
i += 1;

View File

@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Vector3 = Robust.Shared.Maths.Vector3;
namespace Robust.Client.Graphics.Clyde
{

View File

@@ -18,6 +18,7 @@ using Robust.Shared.Graphics;
using static Robust.Shared.GameObjects.OccluderComponent;
using Robust.Shared.Utility;
using TextureWrapMode = Robust.Shared.Graphics.TextureWrapMode;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{

View File

@@ -11,6 +11,8 @@ using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using TKStencilOp = OpenToolkit.Graphics.OpenGL4.StencilOp;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -539,7 +541,7 @@ namespace Robust.Client.Graphics.Clyde
case Matrix3x2 matrix3:
program.SetUniform(name, matrix3);
break;
case Matrix4x4 matrix4:
case Matrix4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:

View File

@@ -10,6 +10,8 @@ using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -526,7 +528,7 @@ namespace Robust.Client.Graphics.Clyde
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
private protected override void SetParameterImpl(string name, in Matrix4 value)
{
var data = Parent._shaderInstances[Handle];
data.ParametersDirty = true;

View File

@@ -467,7 +467,7 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.RunOnWindowThread(a);
}
public IFileDialogManagerImplementation? FileDialogImpl => _windowing as IFileDialogManagerImplementation;
public IFileDialogManager? FileDialogImpl => _windowing as IFileDialogManager;
private abstract class WindowReg
{

View File

@@ -17,6 +17,8 @@ using Robust.Shared.Timing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Color = Robust.Shared.Maths.Color;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -305,7 +307,7 @@ namespace Robust.Client.Graphics.Clyde
action();
}
public IFileDialogManagerImplementation? FileDialogImpl => null;
public IFileDialogManager? FileDialogImpl => null;
private sealed class DummyCursor : ICursor
{
@@ -396,7 +398,7 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
private protected override void SetParameterImpl(string name, in Matrix4 value)
{
}

View File

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

View File

@@ -662,10 +662,6 @@ namespace Robust.Client.Graphics.Clyde
{
var icons = _clyde.LoadWindowIcons().ToArray();
// Done if no icon (e.g., macOS)
if (icons.Length == 0)
return;
// Turn each image into a byte[] so we can actually pin their contents.
// Wish I knew a clean way to do this without allocations.
var images = icons

View File

@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -11,16 +12,31 @@ namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl3WindowingImpl : IFileDialogManagerImplementation
private sealed partial class Sdl3WindowingImpl : IFileDialogManager
{
public async Task<string?> OpenFile(FileDialogFilters? filters)
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
{
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
if (fileName == null)
return null;
return File.OpenRead(fileName);
}
public async Task<string?> SaveFile(FileDialogFilters? filters)
public async Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
{
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
if (fileName == null)
return null;
try
{
return (File.Open(fileName, truncate ? FileMode.Truncate : FileMode.Open), true);
}
catch (FileNotFoundException)
{
return (File.Open(fileName, FileMode.Create), false);
}
}
private unsafe Task<string?> ShowFileDialogOfType(int type, FileDialogFilters? filters)
@@ -58,8 +74,6 @@ internal partial class Clyde
NativeMemory.Free(filter.name);
NativeMemory.Free(filter.pattern);
}
NativeMemory.Free(filtersAlloc);
}
return task;

View File

@@ -71,6 +71,6 @@ namespace Robust.Client.Graphics
void RunOnWindowThread(Action action);
IFileDialogManagerImplementation? FileDialogImpl { get; }
IFileDialogManager? FileDialogImpl { get; }
}
}

View File

@@ -4,6 +4,8 @@ using Robust.Shared.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics
{
@@ -167,7 +169,7 @@ namespace Robust.Client.Graphics
SetParameterImpl(name, value);
}
public void SetParameter(string name, in Matrix4x4 value)
public void SetParameter(string name, in Matrix4 value)
{
EnsureAlive();
EnsureMutable();
@@ -234,7 +236,7 @@ namespace Robust.Client.Graphics
private protected abstract void SetParameterImpl(string name, bool value);
private protected abstract void SetParameterImpl(string name, bool[] value);
private protected abstract void SetParameterImpl(string name, in Matrix3x2 value);
private protected abstract void SetParameterImpl(string name, in Matrix4x4 value);
private protected abstract void SetParameterImpl(string name, in Matrix4 value);
private protected abstract void SetParameterImpl(string name, Texture value);
private protected abstract void SetStencilImpl(StencilParameters value);
}

View File

@@ -11,6 +11,8 @@ using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using YamlDotNet.RepresentationModel;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics
{
@@ -220,7 +222,7 @@ namespace Robust.Client.Graphics
case Matrix3x2 i:
instance.SetParameter(key, i);
break;
case Matrix4x4 i:
case Matrix4 i:
instance.SetParameter(key, i);
break;
}

View File

@@ -47,7 +47,7 @@ namespace Robust.Client.Physics
* This will draw above every body involved in a particular island solve.
*/
public readonly Queue<(TimeSpan Time, List<Entity<PhysicsComponent, TransformComponent>> Bodies)> IslandSolve = new();
public readonly Queue<(TimeSpan Time, List<PhysicsComponent> Bodies)> IslandSolve = new();
public const float SolveDuration = 0.1f;
public override void Initialize()

View File

@@ -67,7 +67,7 @@ namespace Robust.Client.Physics
// Add new joint (if possible).
// Need to wait for BOTH joint components to come in first before we can add it. Yay dependencies!
if (!HasComp<JointComponent>(other))
if (!EntityManager.HasComponent<JointComponent>(other))
continue;
// TODO: if (other entity is outside of PVS range) continue;

View File

@@ -90,10 +90,9 @@ public sealed partial class PhysicsSystem
// existing contacts for predicted entities before performing any actual prediction.
var contacts = new List<Contact>();
var maps = new HashSet<EntityUid>();
var enumerator = AllEntityQuery<PredictedPhysicsComponent, PhysicsComponent, TransformComponent>();
_broadphase.FindNewContacts();
while (enumerator.MoveNext(out _, out var physics, out var xform))
{
DebugTools.Assert(physics.Predict);
@@ -101,6 +100,10 @@ public sealed partial class PhysicsSystem
if (xform.MapUid is not { } map)
continue;
if (maps.Add(map) && PhysMapQuery.TryGetComponent(map, out var physMap) &&
MapQuery.TryGetComponent(map, out var mapComp))
_broadphase.FindNewContacts(physMap, mapComp.MapId);
contacts.AddRange(physics.Contacts);
}

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics;
@@ -24,23 +23,21 @@ namespace Robust.Client.Physics
SimulateWorld(frameTime, _gameTiming.InPrediction);
}
protected override void Cleanup(float frameTime)
protected override void Cleanup(PhysicsMapComponent component, float frameTime)
{
var toRemove = new ValueList<Entity<PhysicsComponent, TransformComponent>>();
var toRemove = new List<Entity<PhysicsComponent>>();
// Because we're not predicting 99% of bodies its sleep timer never gets incremented so we'll just do it ourselves.
// (and serializing it over the network isn't necessary?)
// This is a client-only problem.
// Also need to suss out having the client build the island anyway and just... not solving it?
foreach (var ent in AwakeBodies)
foreach (var body in component.AwakeBodies)
{
var body = ent.Comp1;
if (!body.SleepingAllowed || body.LinearVelocity.Length() > LinearToleranceSqr / 2f || body.AngularVelocity * body.AngularVelocity > AngularToleranceSqr / 2f) continue;
body.SleepTime += frameTime;
if (body.SleepTime > TimeToSleep)
{
toRemove.Add(ent);
toRemove.Add(new Entity<PhysicsComponent>(body.Owner, body));
}
}
@@ -49,38 +46,37 @@ namespace Robust.Client.Physics
SetAwake(body, false);
}
base.Cleanup(frameTime);
base.Cleanup(component, frameTime);
}
protected override void UpdateLerpData(List<Entity<PhysicsComponent, TransformComponent>> bodies)
protected override void UpdateLerpData(PhysicsMapComponent component, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery)
{
foreach (var bodyEnt in bodies)
foreach (var body in bodies)
{
var body = bodyEnt.Comp1;
var xform = bodyEnt.Comp2;
if (body.BodyType == BodyType.Static ||
LerpData.TryGetValue(bodyEnt, out var lerpData) ||
lerpData == xform.ParentUid)
component.LerpData.TryGetValue(body.Owner, out var lerpData) ||
!xformQuery.TryGetComponent(body.Owner, out var xform) ||
lerpData.ParentUid == xform.ParentUid)
{
continue;
}
LerpData[bodyEnt.Owner] = xform.ParentUid;
component.LerpData[xform.Owner] = (xform.ParentUid, xform.LocalPosition, xform.LocalRotation);
}
}
/// <summary>
/// Flush all of our lerping data.
/// </summary>
protected override void FinalStep()
protected override void FinalStep(PhysicsMapComponent component)
{
base.FinalStep();
base.FinalStep(component);
var xformQuery = GetEntityQuery<TransformComponent>();
foreach (var (uid, parentUid) in LerpData)
foreach (var (uid, (parentUid, position, rotation)) in component.LerpData)
{
// Can't just re-use xform from before as movement events may cause event subs to fire.
if (!XformQuery.TryGetComponent(uid, out var xform) || !parentUid.IsValid())
if (!xformQuery.TryGetComponent(uid, out var xform) ||
!parentUid.IsValid())
{
continue;
}
@@ -89,7 +85,7 @@ namespace Robust.Client.Physics
_transform.SetLocalPositionRotation(uid, xform.LocalPosition, xform.LocalRotation, xform);
}
LerpData.Clear();
component.LerpData.Clear();
}
}
}

View File

@@ -1,8 +1,5 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
@@ -21,10 +18,6 @@ namespace Robust.Client.Placement
PlacementMode? CurrentMode { get; set; }
PlacementInformation? CurrentPermission { get; set; }
IEntityManager EntityManager { get; }
IEyeManager EyeManager { get; }
IMapManager MapManager { get; }
/// <summary>
/// The direction to spawn the entity in (presently exposed for EntitySpawnWindow UI)
/// </summary>
@@ -56,15 +49,5 @@ namespace Robust.Client.Placement
void ToggleEraserHijacked(PlacementHijack hijack);
void FrameUpdate(FrameEventArgs e);
/// <summary>
/// The name of the placement mode option to just use the default for the selected entity.
/// </summary>
const string DefaultModeName = "Default";
/// <summary>
/// An array of the names of all available placement modes.
/// </summary>
string[] AllModeNames { get; }
}
}

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

View File

@@ -22,10 +22,10 @@ namespace Robust.Client.ResourceManagement
public override void Load(IDependencyCollection dependencies, ResPath path)
{
if (IsInRsi(path))
if (path.Directory.Filename.EndsWith(".rsi"))
{
var sawmill = dependencies.Resolve<ILogManager>().GetSawmill("res");
sawmill.Warning(
Logger.WarningS(
"res",
"Loading raw texture inside RSI: {Path}. Refer to the RSI state instead of the raw PNG.",
path);
}
@@ -38,15 +38,6 @@ namespace Robust.Client.ResourceManagement
LoadFinish(dependencies.Resolve<IResourceCache>(), data);
}
private static bool IsInRsi(ResPath path)
{
var dir = path.Directory;
if (dir == ResPath.Root)
return false;
return dir.Filename.EndsWith(".rsi");
}
internal static void LoadPreTextureData(IResourceManager cache, LoadStepData data)
{
using (var stream = cache.ContentFileRead(data.Path))

View File

@@ -22,7 +22,7 @@ public sealed class LoadPrototypeCommand : IConsoleCommand
var dialogManager = IoCManager.Resolve<IFileDialogManager>();
var loadManager = IoCManager.Resolve<IGamePrototypeLoadManager>();
var stream = await dialogManager.OpenFile(access: FileAccess.Read);
var stream = await dialogManager.OpenFile();
if (stream is null)
return;

View File

@@ -1,4 +1,3 @@
using System.IO;
using Robust.Client.UserInterface;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -37,7 +36,7 @@ public sealed class UploadFileCommand : IConsoleCommand
var path = new ResPath(args[0]).ToRelativePath();
var filters = new FileDialogFilters(new FileDialogFilters.Group(path.Extension));
await using var file = await _dialog.OpenFile(filters, FileAccess.Read);
await using var file = await _dialog.OpenFile(filters);
if (file == null)
{

View File

@@ -994,9 +994,6 @@ namespace Robust.Client.UserInterface
internal int DoFrameUpdateRecursive(FrameEventArgs args)
{
if (!Visible)
return 0;
var total = 1;
FrameUpdate(args);

View File

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

View File

@@ -1,9 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.UserInterface.Controllers;
@@ -14,14 +11,11 @@ namespace Robust.Client.UserInterface.Controllers;
/// and <see cref="UISystemDependencyAttribute"/> to depend on <see cref="EntitySystem"/>s, which will be automatically
/// injected once they are created.
/// </summary>
public abstract partial class UIController : IPostInjectInit
public abstract partial class UIController
{
[Dependency] protected readonly IUserInterfaceManager UIManager = default!;
[Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
[Dependency] protected readonly IEntityManager EntityManager = default!;
[Dependency] protected readonly ILogManager LogManager = default!;
public ISawmill Log { get; protected set; } = default!;
public virtual void Initialize()
{
@@ -30,39 +24,4 @@ public abstract partial class UIController : IPostInjectInit
public virtual void FrameUpdate(FrameEventArgs args)
{
}
protected virtual string SawmillName
{
get
{
var name = GetType().Name;
// Strip trailing "UIController"
if (name.EndsWith("UIController"))
name = name.Substring(0, name.Length - "UIController".Length);
// Convert CamelCase to snake_case
// Ignore if all uppercase, assume acronym (e.g. NPC or HTN)
if (name.All(char.IsUpper))
{
name = name.ToLower(CultureInfo.InvariantCulture);
}
else
{
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
name = name.Trim('_');
}
return $"ui.{name}";
}
}
public void PostInject()
{
Log = LogManager.GetSawmill(SawmillName);
#if !DEBUG
Log.Level = LogLevel.Info;
#endif
}
}

View File

@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.ColorNaming;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
@@ -11,17 +8,22 @@ namespace Robust.Client.UserInterface.Controls;
// condensed version of the original ColorSlider set
public sealed class ColorSelectorSliders : Control
{
[Dependency] private readonly ILocalizationManager _localization = default!;
public Color Color
{
get => _currentColor;
set
{
_currentColor = value;
_colorData = GetStrategy().ToColorData(value);
UpdateAllSliders();
switch (SelectorType)
{
case ColorSelectorType.Rgb:
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
break;
case ColorSelectorType.Hsv:
_colorData = Color.ToHsv(value);
break;
}
Update();
}
}
@@ -30,12 +32,18 @@ public sealed class ColorSelectorSliders : Control
get => _currentType;
set
{
switch ((_currentType, value))
{
case (ColorSelectorType.Rgb, ColorSelectorType.Hsv):
_colorData = Color.ToHsv(Color);
break;
case (ColorSelectorType.Hsv, ColorSelectorType.Rgb):
_colorData = new Vector4(_currentColor.R, _currentColor.G, _currentColor.B, _currentColor.A);
break;
}
_currentType = value;
_typeSelector.Select(_types.IndexOf(value));
_colorData = GetStrategy().ToColorData(_currentColor);
UpdateType();
UpdateAllSliders();
Update();
}
}
@@ -52,11 +60,7 @@ public sealed class ColorSelectorSliders : Control
public Action<Color>? OnColorChanged;
private readonly static HsvSliderStrategy _hsvStrategy = new();
private readonly static RgbSliderStrategy _rgbStrategy = new();
private const float AlphaDivisor = 100.0f;
private bool _updating = false;
private Color _currentColor = Color.White;
private Vector4 _colorData;
private ColorSelectorType _currentType = ColorSelectorType.Rgb;
@@ -78,7 +82,6 @@ public sealed class ColorSelectorSliders : Control
private Label _middleSliderLabel = new();
private Label _bottomSliderLabel = new();
private Label _alphaSliderLabel = new();
private Label _colorDescriptionLabel = new();
private OptionButton _typeSelector;
private List<ColorSelectorType> _types = new();
@@ -89,8 +92,6 @@ public sealed class ColorSelectorSliders : Control
public ColorSelectorSliders()
{
IoCManager.InjectDependencies(this);
_topColorSlider = new ColorableSlider
{
HorizontalExpand = true,
@@ -122,10 +123,10 @@ public sealed class ColorSelectorSliders : Control
MaxValue = 1.0f,
};
_topColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Top); };
_middleColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Middle); };
_bottomColorSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Bottom); };
_alphaSlider.OnValueChanged += r => { OnSliderValueChanged(ColorSliderOrder.Alpha); };
_topColorSlider.OnValueChanged += _ => { OnColorSet(); };
_middleColorSlider.OnValueChanged += _ => { OnColorSet(); };
_bottomColorSlider.OnValueChanged += _ => { OnColorSet(); };
_alphaSlider.OnValueChanged += _ => { OnColorSet(); };
_topInputBox = new SpinBox
{
@@ -151,10 +152,25 @@ public sealed class ColorSelectorSliders : Control
};
_alphaInputBox.InitDefaultButtons();
_topInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Top); };
_middleInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Middle); };
_bottomInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Bottom); };
_alphaInputBox.ValueChanged += value => { OnInputBoxValueChanged(value, ColorSliderOrder.Alpha); };
_topInputBox.ValueChanged += value =>
{
_topColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Top);
};
_middleInputBox.ValueChanged += value =>
{
_middleColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Middle);
};
_bottomInputBox.ValueChanged += value =>
{
_bottomColorSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Bottom);
};
_alphaInputBox.ValueChanged += value =>
{
_alphaSlider.Value = value.Value / GetColorValueDivisor(ColorSliderOrder.Alpha);
};
_alphaSliderLabel.Text = Loc.GetString("color-selector-sliders-alpha");
@@ -171,8 +187,6 @@ public sealed class ColorSelectorSliders : Control
_typeSelector.Select(args.Id);
};
_colorDescriptionLabel.Text = ColorNaming.Describe(_currentColor, _localization);
// TODO: Maybe some engine widgets could be laid out in XAML?
var rootBox = new BoxContainer
@@ -185,7 +199,6 @@ public sealed class ColorSelectorSliders : Control
rootBox.AddChild(headerBox);
headerBox.AddChild(_typeSelector);
headerBox.AddChild(_colorDescriptionLabel);
var bodyBox = new BoxContainer()
{
@@ -227,114 +240,165 @@ public sealed class ColorSelectorSliders : Control
Color = _currentColor;
}
private ColorSliderStrategy GetStrategy()
{
return SelectorType switch
{
ColorSelectorType.Rgb => _rgbStrategy,
ColorSelectorType.Hsv => _hsvStrategy,
_ => throw new ArgumentOutOfRangeException(),
};
}
private (Slider slider, SpinBox inputBox) GetSliderByOrder(ColorSliderOrder order)
{
return order switch
{
ColorSliderOrder.Top => (_topColorSlider, _topInputBox),
ColorSliderOrder.Middle => (_middleColorSlider, _middleInputBox),
ColorSliderOrder.Bottom => (_bottomColorSlider, _bottomInputBox),
ColorSliderOrder.Alpha => (_alphaSlider, _alphaInputBox),
_ => throw new ArgumentOutOfRangeException(),
};
}
private float GetColorValueDivisor(ColorSliderOrder order)
{
return order == ColorSliderOrder.Alpha
? AlphaDivisor
: GetStrategy().GetColorValueDivisor(order);
}
private void UpdateType()
{
var strategy = GetStrategy();
var labels = strategy.GetSliderLabelTexts();
_topSliderLabel.Text = labels.top;
_middleSliderLabel.Text = labels.middle;
_bottomSliderLabel.Text = labels.bottom;
(string topLabel, string middleLabel, string bottomLabel) labels = GetSliderLabels();
_topStyle.ConfigureSlider(strategy.TopSliderStyle);
_middleStyle.ConfigureSlider(strategy.MiddleSliderStyle);
_bottomStyle.ConfigureSlider(strategy.BottomSliderStyle);
_topSliderLabel.Text = labels.topLabel;
_middleSliderLabel.Text = labels.middleLabel;
_bottomSliderLabel.Text = labels.bottomLabel;
bool hsv = SelectorType == ColorSelectorType.Hsv;
_topStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Hue : ColorSelectorStyleBox.ColorSliderPreset.Red);
_middleStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Saturation : ColorSelectorStyleBox.ColorSliderPreset.Green);
_bottomStyle.ConfigureSlider( hsv ? ColorSelectorStyleBox.ColorSliderPreset.Value : ColorSelectorStyleBox.ColorSliderPreset.Blue);
}
private void UpdateSlider(ColorSliderOrder order)
private void Update()
{
var (slider, inputBox) = GetSliderByOrder(order);
var divisor = GetColorValueDivisor(order);
// This code is a mess of UI events causing stack overflows. Also, updating one slider triggers all sliders to
// update, which due to rounding errors causes them to actually change values, specifically for HSV sliders.
if (_updating)
return;
var dataValue = order switch
{
ColorSliderOrder.Top => _colorData.X,
ColorSliderOrder.Middle => _colorData.Y,
ColorSliderOrder.Bottom => _colorData.Z,
ColorSliderOrder.Alpha => _colorData.W,
_ => throw new ArgumentOutOfRangeException(nameof(order))
};
slider.SetValueWithoutEvent(dataValue);
inputBox.OverrideValue((int)(dataValue * divisor));
}
private void UpdateSliderVisuals()
{
_updating = true;
_topStyle.SetBaseColor(_colorData);
_middleStyle.SetBaseColor(_colorData);
_bottomStyle.SetBaseColor(_colorData);
_colorDescriptionLabel.Text = ColorNaming.Describe(Color, _localization);
}
private void UpdateAllSliders()
{
UpdateSliderVisuals();
UpdateSlider(ColorSliderOrder.Top);
UpdateSlider(ColorSliderOrder.Middle);
UpdateSlider(ColorSliderOrder.Bottom);
UpdateSlider(ColorSliderOrder.Alpha);
switch (SelectorType)
{
case ColorSelectorType.Rgb:
_topColorSlider.Value = _colorData.X;
_middleColorSlider.Value = _colorData.Y;
_bottomColorSlider.Value = _colorData.Z;
_topInputBox.Value = (int)(_colorData.X * 255.0f);
_middleInputBox.Value = (int)(_colorData.Y * 255.0f);
_bottomInputBox.Value = (int)(_colorData.Z * 255.0f);
break;
case ColorSelectorType.Hsv:
// dumb workaround because the formula for
// HSV calculation results in a negative
// number in any value past 300 degrees
if (_colorData.X > 0)
{
_topColorSlider.Value = _colorData.X;
_topInputBox.Value = (int)(_colorData.X * 360.0f);
}
else
{
_topInputBox.Value = (int)(_topColorSlider.Value * 360.0f);
}
_middleColorSlider.Value = _colorData.Y;
_bottomColorSlider.Value = _colorData.Z;
_middleInputBox.Value = (int)(_colorData.Y * 100.0f);
_bottomInputBox.Value = (int)(_colorData.Z * 100.0f);
break;
}
_alphaSlider.Value = Color.A;
_alphaInputBox.Value = (int)(Color.A * 100.0f);
_updating = false;
}
private bool IsSpinBoxValid(int value, ColorSliderOrder ordering)
{
var divisor = GetColorValueDivisor(ordering);
var channelValue = value / divisor;
if (value < 0)
{
return false;
}
return channelValue >= 0.0f && channelValue <= 1.0f;
if (ordering == ColorSliderOrder.Alpha)
{
return value <= 100;
}
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return value <= byte.MaxValue;
case ColorSelectorType.Hsv:
switch (ordering)
{
case ColorSliderOrder.Top:
return value <= 360;
default:
return value <= 100;
}
}
return false;
}
private void OnInputBoxValueChanged(ValueChangedEventArgs args, ColorSliderOrder order)
private (string, string, string) GetSliderLabels()
{
var (slider, _) = GetSliderByOrder(order);
var value = args.Value / GetColorValueDivisor(order);
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return (
Loc.GetString("color-selector-sliders-red"),
Loc.GetString("color-selector-sliders-green"),
Loc.GetString("color-selector-sliders-blue")
);
case ColorSelectorType.Hsv:
return (
Loc.GetString("color-selector-sliders-hue"),
Loc.GetString("color-selector-sliders-saturation"),
Loc.GetString("color-selector-sliders-value")
);
}
// We are intentionally triggering the slider OnValueChanged event here.
// This is so that the color data values of the sliders are updated accordingly.
slider.Value = value;
return ("ERR", "ERR", "ERR");
}
private void OnSliderValueChanged(ColorSliderOrder order)
private float GetColorValueDivisor(ColorSliderOrder order)
{
_colorData = new Vector4(
_topColorSlider.Value,
_middleColorSlider.Value,
_bottomColorSlider.Value,
_alphaSlider.Value);
if (order == ColorSliderOrder.Alpha)
{
return 100.0f;
}
_currentColor = GetStrategy().FromColorData(_colorData);
switch (SelectorType)
{
case ColorSelectorType.Rgb:
return 255.0f;
case ColorSelectorType.Hsv:
switch (order)
{
case ColorSliderOrder.Top:
return 360.0f;
default:
return 100.0f;
}
}
return 0.0f;
}
private void OnColorSet()
{
// stack overflow otherwise due to value sets
if (_updating)
{
return;
}
_colorData = new Vector4(_topColorSlider.Value, _middleColorSlider.Value, _bottomColorSlider.Value, _alphaSlider.Value);
_currentColor = SelectorType switch
{
ColorSelectorType.Hsv => Color.FromHsv(_colorData),
_ => new Color(_colorData.X, _colorData.Y, _colorData.Z, _colorData.W)
};
Update();
OnColorChanged?.Invoke(_currentColor);
UpdateSliderVisuals();
UpdateSlider(order);
}
private enum ColorSliderOrder
@@ -350,121 +414,4 @@ public sealed class ColorSelectorSliders : Control
Rgb,
Hsv,
}
private abstract class ColorSliderStrategy
{
/// <summary>
/// The style preset used by the top slider.
/// </summary>
public abstract ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle { get; }
/// <summary>
/// The style preset used by the middle slider.
/// </summary>
public abstract ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle { get; }
/// <summary>
/// The style preset used by the bottom slider.
/// </summary>
public abstract ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle { get; }
/// <summary>
/// Converts a Color to a Vector4 representation of its components.
/// </summary>
/// <remarks>
/// Each value in the Vector4 must be between 0.0f and 1.0f; this is used in the
/// context of slider values, which are between these ranges.
/// </remarks>
/// <param name="color">A Color to convert into Vector4 slider values.</param>
/// <returns>A Vector4 representation of a Color's slider values.</returns>
public abstract Vector4 ToColorData(Color color);
/// <summary>
/// Converts a Vector4 representation of color slider values into a Color.
/// </summary>
/// <param name="colorData">A Vector4 representation of color slider values.</param>
/// <returns>A color generated from slider values.</returns>
public abstract Color FromColorData(Vector4 colorData);
/// <summary>
/// Gets a color component divisor for the given slider.
/// </summary>
/// <remarks>
/// This is used for converting slider values to/from color component values.
/// For example, in RGB coloration, each channel ranges from 0 to 255,
/// so if you had a slider value of 0.2, you would multiply 0.2 * 255 = 51
/// for the "channel" value.
///
/// This does not apply to the Alpha channel, as the Alpha channel
/// always uses the same divisor; this is defined in ColorSelectorSliders.
/// </remarks>
/// <param name="order">The slider to retrieve a divisor for.</param>
/// <returns>The divisor for the given slider.</returns>
public abstract float GetColorValueDivisor(ColorSliderOrder order);
/// <summary>
/// Gets a label text string for the first three color sliders.
/// </summary>
/// <returns>Label text strings for the top, middle, and bottom sliders.</returns>
public abstract (string top, string middle, string bottom) GetSliderLabelTexts();
}
private sealed class RgbSliderStrategy : ColorSliderStrategy
{
private const float ChannelMaxValue = byte.MaxValue;
public override ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Red;
public override ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Green;
public override ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Blue;
public override Vector4 ToColorData(Color color) => new(color.R, color.G, color.B, color.A);
public override Color FromColorData(Vector4 colorData)
=> new(colorData.X, colorData.Y, colorData.Z, colorData.W);
public override float GetColorValueDivisor(ColorSliderOrder order) => ChannelMaxValue;
public override (string top, string middle, string bottom) GetSliderLabelTexts()
{
return (
Loc.GetString("color-selector-sliders-red"),
Loc.GetString("color-selector-sliders-green"),
Loc.GetString("color-selector-sliders-blue"));
}
}
private sealed class HsvSliderStrategy : ColorSliderStrategy
{
private const float HueMaxValue = 360.0f;
private const float SliderMaxValue = 100.0f;
public override ColorSelectorStyleBox.ColorSliderPreset TopSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Hue;
public override ColorSelectorStyleBox.ColorSliderPreset MiddleSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Saturation;
public override ColorSelectorStyleBox.ColorSliderPreset BottomSliderStyle
=> ColorSelectorStyleBox.ColorSliderPreset.Value;
public override Vector4 ToColorData(Color color) => Color.ToHsv(color);
public override Color FromColorData(Vector4 colorData) => Color.FromHsv(colorData);
public override float GetColorValueDivisor(ColorSliderOrder order)
{
return order switch
{
ColorSliderOrder.Top => HueMaxValue,
_ => SliderMaxValue,
};
}
public override (string top, string middle, string bottom) GetSliderLabelTexts()
{
return (
Loc.GetString("color-selector-sliders-hue"),
Loc.GetString("color-selector-sliders-saturation"),
Loc.GetString("color-selector-sliders-value"));
}
}
}

View File

@@ -21,19 +21,19 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
/// <summary>
/// Base background colour.
/// </summary>
public Vector4 BaseColor;
public Robust.Shared.Maths.Vector4 BaseColor;
/// <summary>
/// Colour to add to the background colour along the X-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Vector4 XAxis;
public Robust.Shared.Maths.Vector4 XAxis;
/// <summary>
/// Colour to add to the background colour along the y-axis.
/// I.e., from left to right the background colour will vary from (BaseColour) to (BaseColour + XAxis)
/// </summary>
public Vector4 YAxis;
public Robust.Shared.Maths.Vector4 YAxis;
/// <summary>
/// If true, then <see cref="BaseColor"/>, <see cref="XAxis"/>, and <see cref="YAxis"/> will be interpreted as HSVa
@@ -93,14 +93,14 @@ public sealed class ColorSelectorStyleBox : StyleBoxTexture
{
var colorData = Hsv
? Color.ToHsv(color)
: new Vector4(color.R, color.G, color.B, color.A);
: new Robust.Shared.Maths.Vector4(color.R, color.G, color.B, color.A);
SetBaseColor(colorData);
}
/// <summary>
/// Helper method that sets the base color by taking in some color and removing the components that are controlled by the x and y axes.
/// </summary>
public void SetBaseColor(Vector4 colorData)
public void SetBaseColor(Robust.Shared.Maths.Vector4 colorData)
{
BaseColor = colorData - colorData * XAxis - colorData * YAxis;
}

View File

@@ -107,11 +107,6 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar.Value = 0;
}
public FormattedMessage GetMessage(Index index)
{
return new FormattedMessage(_entries[index].Message);
}
public void RemoveEntry(Index index)
{
var entry = _entries[index];
@@ -143,31 +138,6 @@ namespace Robust.Client.UserInterface.Controls
_entries.Add(entry);
var font = _getFont();
AddNewItemHeight(font, entry);
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
if (_isAtBottom && ScrollFollowing)
{
_scrollBar.MoveToEnd();
}
}
public void SetMessage(Index index, FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
{
var oldEntry = _entries[index];
var font = _getFont();
_totalContentHeight -= oldEntry.Height + font.GetLineSeparation(UIScale);
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
var entry = new RichTextEntry(message, this, _tagManager, tagsAllowed, defaultColor);
entry.Update(_tagManager, _getFont(), _getContentBox().Width, UIScale);
_entries[index] = entry;
AddNewItemHeight(font, in entry);
}
private void AddNewItemHeight(Font font, in RichTextEntry entry)
{
_totalContentHeight += entry.Height;
if (_firstLine)
{
@@ -177,6 +147,12 @@ namespace Robust.Client.UserInterface.Controls
{
_totalContentHeight += font.GetLineSeparation(UIScale);
}
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
if (_isAtBottom && ScrollFollowing)
{
_scrollBar.MoveToEnd();
}
}
public void ScrollToBottom()

View File

@@ -30,12 +30,12 @@ namespace Robust.Client.UserInterface.Controls
get => _currentTab;
set
{
if (value < 0)
if (_currentTab < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "Current tab must be positive.");
}
if (value >= ChildCount)
if (_currentTab >= ChildCount)
{
throw new ArgumentOutOfRangeException(nameof(value), value,
"Current tab must less than the amount of tabs.");

View File

@@ -13,6 +13,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using Vector2 = System.Numerics.Vector2;
namespace Robust.Client.UserInterface.Controls;

View File

@@ -145,11 +145,6 @@ namespace Robust.Client.UserInterface.CustomControls
protected override void FrameUpdate(FrameEventArgs args)
{
// This is to avoid unnecessarily setting a position where our size isn't yet fully updated.
// This most commonly happens with saved window positions if your window position is <= 0.
if (!IsMeasureValid)
return;
var (spaceX, spaceY) = Parent!.Size;
var maxX = spaceX - ((AllowOffScreen & DirectionFlag.West) == 0 ? Size.X : WindowEdgeSeparation);

View File

@@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.Placement;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
@@ -16,24 +12,34 @@ namespace Robust.Client.UserInterface.CustomControls
[GenerateTypedNameReferences]
public sealed partial class EntitySpawnWindow : DefaultWindow
{
[Dependency] private readonly IPlacementManager _placement = default!;
public static readonly string[] InitOpts =
{
"Default",
"PlaceFree",
"PlaceNearby",
"SnapgridCenter",
"SnapgridBorder",
"AlignSimilar",
"AlignTileAny",
"AlignTileEmpty",
"AlignTileNonDense",
"AlignTileDense",
"AlignWall",
"AlignWallProper",
};
public EntitySpawnButton? SelectedButton;
public EntityPrototype? SelectedPrototype;
[Obsolete("Use IPlacementManager.AllModeNames")]
public static string[] InitOpts =>IoCManager.Resolve<IPlacementManager>().AllModeNames;
public EntitySpawnWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
MeasureButton.Measure(Vector2Helpers.Infinity);
var modes = _placement.AllModeNames;
for (var i = 0; i < modes.Length; i++)
for (var i = 0; i < InitOpts.Length; i++)
{
OverrideMenu.AddItem(modes[i], i);
OverrideMenu.AddItem(InitOpts[i], i);
}
}

View File

@@ -8,19 +8,12 @@ namespace Robust.Client.UserInterface
/// </summary>
internal sealed class DummyFileDialogManager : IFileDialogManager
{
public Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null)
public Task<Stream?> OpenFile(FileDialogFilters? filters = null)
{
return Task.FromResult<Stream?>(null);
}
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
FileDialogFilters? filters = null,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
{
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
}

View File

@@ -7,7 +7,9 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
@@ -18,41 +20,28 @@ namespace Robust.Client.UserInterface
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "CommentTypo")]
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal sealed class FileDialogManager : IFileDialogManager, IPostInjectInit
internal sealed class FileDialogManager : IFileDialogManager
{
// Uses nativefiledialog to open the file dialogs cross platform.
// On Linux, if the kdialog command is found, it will be used instead.
// TODO: Should we maybe try to avoid running kdialog if the DE isn't KDE?
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly ILogManager _log = default!;
private ISawmill _sawmill = default!;
private bool _kDialogAvailable;
private bool _checkedKDialogAvailable;
public async Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null)
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
{
if ((access & FileAccess.ReadWrite) != access)
throw new ArgumentException("Invalid file access specified");
var realShare = share ?? (access == FileAccess.Read ? FileShare.Read : FileShare.None);
if ((realShare & (FileShare.ReadWrite | FileShare.Delete)) != realShare)
throw new ArgumentException("Invalid file share specified");
string? name;
if (_clyde.FileDialogImpl is { } clydeImpl)
name = await clydeImpl.OpenFile(filters);
else
name = await GetOpenFileName(filters);
return await clydeImpl.OpenFile(filters);
var name = await GetOpenFileName(filters);
if (name == null)
{
return null;
}
return File.Open(name, FileMode.Open, access, realShare);
return File.Open(name, FileMode.Open);
}
private async Task<string?> GetOpenFileName(FileDialogFilters? filters)
@@ -65,34 +54,24 @@ namespace Robust.Client.UserInterface
return await OpenFileNfd(filters);
}
public async Task<(Stream, bool)?> SaveFile(
FileDialogFilters? filters,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
{
if ((access & FileAccess.ReadWrite) != access)
throw new ArgumentException("Invalid file access specified");
if ((share & (FileShare.ReadWrite | FileShare.Delete)) != share)
throw new ArgumentException("Invalid file share specified");
string? name;
if (_clyde.FileDialogImpl is { } clydeImpl)
name = await clydeImpl.SaveFile(filters);
else
name = await GetSaveFileName(filters);
return await clydeImpl.SaveFile(filters);
var name = await GetSaveFileName(filters);
if (name == null)
{
return null;
}
try
{
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open, access, share), true);
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
}
catch (FileNotFoundException)
{
return (File.Open(name, FileMode.Create, access, share), false);
return (File.Open(name, FileMode.Create), false);
}
}
@@ -270,7 +249,7 @@ namespace Robust.Client.UserInterface
if (_kDialogAvailable)
{
_sawmill.Debug("kdialog available.");
Logger.DebugS("filedialog", "kdialog available.");
}
}
catch
@@ -407,11 +386,6 @@ namespace Robust.Client.UserInterface
[DllImport("swnfd.dll")]
private static extern unsafe void sw_NFD_Free(void* ptr);
public void PostInject()
{
_sawmill = _log.GetSawmill("filedialog");
}
private enum sw_nfdresult
{
SW_NFD_ERROR,

View File

@@ -1,6 +1,5 @@
using System.IO;
using System.Threading.Tasks;
using Robust.Client.Graphics;
namespace Robust.Client.UserInterface
{
@@ -20,17 +19,7 @@ namespace Robust.Client.UserInterface
/// The file stream for the file the user opened.
/// <see langword="null" /> if the user cancelled the action.
/// </returns>
/// <param name="filters">Filters for file types that the user can select.</param>
/// <param name="access">What access is desired from the file operation.</param>
/// <param name="share">
/// What sharing mode is desired from the file operation.
/// If null is provided and <paramref name="access"/> is <see cref="FileAccess.Read"/>,
/// <see cref="FileShare.Read"/> is selected, otherwise <see cref="FileShare.None"/>.
/// </param>
Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null);
Task<Stream?> OpenFile(FileDialogFilters? filters = null);
/// <summary>
/// Open a file dialog used for saving a single file.
@@ -40,21 +29,6 @@ namespace Robust.Client.UserInterface
/// Null if the user cancelled the action.
/// </returns>
/// <param name="truncate">Should we truncate an existing file to 0-size then write or append.</param>
/// <param name="access">What access is desired from the file operation.</param>
/// <param name="share">Sharing mode for the opened file.</param>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
FileDialogFilters? filters = null,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None);
}
/// <summary>
/// Internal implementation interface used to connect <see cref="FileDialogManager"/> and <see cref="IClydeInternal"/>.
/// </summary>
internal interface IFileDialogManagerImplementation
{
Task<string?> OpenFile(FileDialogFilters? filters);
Task<string?> SaveFile(FileDialogFilters? filters);
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
}
}

View File

@@ -141,7 +141,7 @@ namespace Robust.Client.UserInterface
if (Controls == null || !Controls.TryGetValue(nodeIndex, out var control))
continue;
control.Measure(new Vector2(maxSizeX, Height));
control.Measure(new Vector2(Width, Height));
var desiredSize = control.DesiredPixelSize;
var controlMetrics = new CharMetrics(

View File

@@ -1,20 +1,27 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Threading.Tasks;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables.Editors;
using Robust.Client.ViewVariables.Instances;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using static Robust.Client.ViewVariables.Editors.VVPropEditorNumeric;
namespace Robust.Client.ViewVariables
{
@@ -24,7 +31,8 @@ namespace Robust.Client.ViewVariables
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IViewVariableControlFactory _controlFactory = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
private uint _nextReqId = 1;
private readonly Vector2i _defaultWindowSize = (640, 420);
@@ -56,7 +64,190 @@ namespace Robust.Client.ViewVariables
public VVPropEditor PropertyFor(Type? type)
{
return _controlFactory.CreateFor(type);
// TODO: make this more flexible.
if (type == null)
{
return new VVPropEditorDummy();
}
if (type == typeof(sbyte))
{
return new VVPropEditorNumeric(NumberType.SByte);
}
if (type == typeof(byte))
{
return new VVPropEditorNumeric(NumberType.Byte);
}
if (type == typeof(ushort))
{
return new VVPropEditorNumeric(NumberType.UShort);
}
if (type == typeof(short))
{
return new VVPropEditorNumeric(NumberType.Short);
}
if (type == typeof(uint))
{
return new VVPropEditorNumeric(NumberType.UInt);
}
if (type == typeof(int))
{
return new VVPropEditorNumeric(NumberType.Int);
}
if (type == typeof(ulong))
{
return new VVPropEditorNumeric(NumberType.ULong);
}
if (type == typeof(long))
{
return new VVPropEditorNumeric(NumberType.Long);
}
if (type == typeof(float))
{
return new VVPropEditorNumeric(NumberType.Float);
}
if (type == typeof(double))
{
return new VVPropEditorNumeric(NumberType.Double);
}
if (type == typeof(decimal))
{
return new VVPropEditorNumeric(NumberType.Decimal);
}
if (type == typeof(string))
{
return new VVPropEditorString();
}
if (type == typeof(EntProtoId?))
{
return new VVPropEditorNullableEntProtoId();
}
if (type == typeof(EntProtoId))
{
return new VVPropEditorEntProtoId();
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
{
var editor =
(VVPropEditor)Activator.CreateInstance(
typeof(VVPropEditorProtoId<>).MakeGenericType(type.GenericTypeArguments[0]))!;
IoCManager.InjectDependencies(editor);
return editor;
}
if (typeof(IPrototype).IsAssignableFrom(type) || typeof(ViewVariablesBlobMembers.PrototypeReferenceToken).IsAssignableFrom(type))
{
return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorIPrototype<>).MakeGenericType(type))!;
}
if (typeof(ISelfSerialize).IsAssignableFrom(type))
{
return (VVPropEditor)Activator.CreateInstance(typeof(VVPropEditorISelfSerializable<>).MakeGenericType(type))!;
}
if (type.IsEnum)
{
return new VVPropEditorEnum();
}
if (type == typeof(Vector2))
{
return new VVPropEditorVector2(intVec: false);
}
if (type == typeof(Vector2i))
{
return new VVPropEditorVector2(intVec: true);
}
if (type == typeof(bool))
{
return new VVPropEditorBoolean();
}
if (type == typeof(Angle))
{
return new VVPropEditorAngle();
}
if (type == typeof(Box2))
{
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2);
}
if (type == typeof(Box2i))
{
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2i);
}
if (type == typeof(UIBox2))
{
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2);
}
if (type == typeof(UIBox2i))
{
return new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2i);
}
if (type == typeof(EntityCoordinates))
{
return new VVPropEditorEntityCoordinates();
}
if (type == typeof(EntityUid))
{
return new VVPropEditorEntityUid();
}
if (type == typeof(NetEntity))
{
return new VVPropEditorNetEntity();
}
if (type == typeof(Color))
{
return new VVPropEditorColor();
}
if (type == typeof(TimeSpan))
{
return new VVPropEditorTimeSpan();
}
if (typeof(SoundSpecifier).IsAssignableFrom(type))
{
var control = new VVPropEditorSoundSpecifier(_protoManager, _resManager);
return control;
}
if (type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken) ||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{
return new VVPropEditorKeyValuePair();
}
if (type != typeof(ViewVariablesBlobMembers.ServerValueTypeToken) && !type.IsValueType)
{
return new VVPropEditorReference();
}
return new VVPropEditorDummy();
}
public void OpenVV(object obj)
@@ -72,7 +263,7 @@ namespace Robust.Client.ViewVariables
instance = new ViewVariablesInstanceObject(this, _robustSerializer);
}
var window = new DefaultWindow { Title = Loc.GetString("view-variables") };
var window = new DefaultWindow {Title = Loc.GetString("view-variables")};
instance.Initialize(window, obj);
window.OnClose += () => _closeInstance(instance, false);
_windows.Add(instance, window);
@@ -82,7 +273,7 @@ namespace Robust.Client.ViewVariables
public void OpenVV(string path)
{
if (ReadPath(path) is { } obj)
if (ReadPath(path) is {} obj)
OpenVV(obj);
}
@@ -93,7 +284,7 @@ namespace Robust.Client.ViewVariables
Title = Loc.GetString("view-variables"),
SetSize = _defaultWindowSize
};
var loadingLabel = new Label { Text = "Retrieving remote object data from server..." };
var loadingLabel = new Label {Text = "Retrieving remote object data from server..."};
window.Contents.AddChild(loadingLabel);
// We need to request the data, THEN create an instance.
@@ -161,7 +352,7 @@ namespace Robust.Client.ViewVariables
public async Task<T> RequestData<T>(ViewVariablesRemoteSession session, ViewVariablesRequest meta) where T : ViewVariablesBlob
{
return (T)await RequestData(session, meta);
return (T) await RequestData(session, meta);
}
public void CloseSession(ViewVariablesRemoteSession session)

View File

@@ -1,24 +1,16 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Robust.Client.ViewVariables.Editors;
internal sealed class VVPropEditorEntProtoId : VVPropEditor
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
public VVPropEditorEntProtoId()
{
IoCManager.InjectDependencies(this);
}
protected override Control MakeUI(object? value)
{
var lineEdit = new LineEdit
{
Text = (EntProtoId)(value ?? ""),
Text = (EntProtoId) (value ?? ""),
Editable = !ReadOnly,
HorizontalExpand = true,
};
@@ -27,9 +19,7 @@ internal sealed class VVPropEditorEntProtoId : VVPropEditor
{
lineEdit.OnTextEntered += e =>
{
var id = (EntProtoId)e.Text;
if (_protoMan.HasIndex(id))
ValueChanged(id);
ValueChanged((EntProtoId) e.Text);
};
}

View File

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

View File

@@ -1,24 +1,16 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Robust.Client.ViewVariables.Editors;
internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor
{
[Dependency] private readonly IPrototypeManager _protoMan = default!;
public VVPropEditorNullableEntProtoId()
{
IoCManager.InjectDependencies(this);
}
protected override Control MakeUI(object? value)
{
var lineEdit = new LineEdit
{
Text = value is EntProtoId protoId ? protoId.Id : "",
Text = value is EntProtoId protoId ? protoId.Id : "",
Editable = !ReadOnly,
HorizontalExpand = true,
};
@@ -33,9 +25,7 @@ internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor
}
else
{
var id = (EntProtoId)e.Text;
if (_protoMan.HasIndex(id))
ValueChanged(id);
ValueChanged((EntProtoId) e.Text);
}
};
}

View File

@@ -1,110 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
using static Robust.Client.UserInterface.Controls.BoxContainer;
using CS = System.Runtime.CompilerServices;
namespace Robust.Client.ViewVariables.Editors;
internal sealed class VVPropEditorTuple : VVPropEditor
{
[Dependency] private readonly IClientViewVariablesManagerInternal _viewVariables = default!;
private bool _readOnly;
private readonly List<object?> _tuple = [];
private readonly List<VVPropEditor> _editors = [];
private Type? _actualType;
public VVPropEditorTuple()
{
IoCManager.InjectDependencies(this);
}
protected override Control MakeUI(object? value)
{
var vBoxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Vertical,
MinSize = new Vector2(240, 0),
};
if (value is not CS.ITuple tuple)
return vBoxContainer;
// Zero-tuples exist?? I'm just not going to bother with that.
if (tuple.Length == 0)
return vBoxContainer;
_actualType = value.GetType();
// We disallow editing tuples with arity more than 7 since they would
// a pain to construct via reflection. And no one should have tuples
// that large. (8 is bad because last element becomes a ValueTuple<>)
_readOnly = ReadOnly
|| tuple.Length >= 8
|| !IsValueTuple(_actualType); // ToTuple only supports ValueTuples
for (var i = 0; i < tuple.Length; i++)
{
var editor = CreateBox(tuple[i], vBoxContainer);
var index = i; // thanks C#
editor.OnValueChanged += (o, reinterpret) => ValueChanged(ToTuple(o, index), reinterpret);
_tuple.Add(tuple[i]);
_editors.Add(editor);
}
return vBoxContainer;
}
private bool IsValueTuple(Type actualType)
{
if (!actualType.IsGenericType)
return false;
Type[] valueTupleTypes =
[
typeof(ValueTuple<>), typeof(ValueTuple<,>), typeof(ValueTuple<,,>), typeof(ValueTuple<,,,>),
typeof(ValueTuple<,,,,>), typeof(ValueTuple<,,,,,>), typeof(ValueTuple<,,,,,,>), typeof(ValueTuple<,,,,,,,>)
];
return valueTupleTypes.Contains(actualType.GetGenericTypeDefinition());
}
private CS.ITuple ToTuple(object? changed, int index)
{
_tuple[index] = changed;
// I can't seem to make this work using .GetMethod.
// If you know of a better way of doing this... please do.
return (CS.ITuple)typeof(ValueTuple).GetMethods()
.First(x => x is { Name: nameof(ValueTuple.Create), IsGenericMethod: true }
&& x.GetParameters().Length == _tuple.Count)
.MakeGenericMethod(_actualType!.GenericTypeArguments)
.Invoke(null, _tuple.ToArray())!;
}
private VVPropEditor CreateBox<T>(T? entry, BoxContainer parent)
{
var editor = _viewVariables.PropertyFor(entry?.GetType());
// We disallow editing of serverside-only tuples because, uh, I don't
// know how to make it work. Presumably it'd have to be something
// similarly cursed to what I did in ToTuple above.
parent.AddChild(editor.Initialize(entry, _readOnly));
return editor;
}
// Allow selecting, for example, dictionaries within the tuple.
// Wait, why do you have a field with a tuple that holds a dictionary??
public override void WireNetworkSelector(uint sessionId, object[] selectorChain)
{
for (var i = 0; i < _editors.Count; i++)
{
object[] chain = [..selectorChain, new ViewVariablesTupleIndexSelector(i)];
_editors[i].WireNetworkSelector(sessionId, chain);
}
}
}

View File

@@ -1,58 +0,0 @@
using System;
using Robust.Client.ViewVariables.Editors;
namespace Robust.Client.ViewVariables;
/// <summary>
/// Factory that creates UI controls for viewing variables based on provided property type.
/// </summary>
public interface IViewVariableControlFactory
{
/// <summary>
/// Creates UI control for viewing variable of type <paramref name="type"/>.
/// Returns <see cref="VVPropEditorDummy"/> if fails to find proper control.
/// First will look for control factory by type match (<see cref="RegisterForType{T}"/>),
/// then will try to check each registered conditional factory
/// (<see cref="RegisterWithCondition"/>, <see cref="RegisterForAssignableFrom{T}"/> and similar methods).
/// </summary>
VVPropEditor CreateFor(Type? type);
/// <summary>
/// Registers factory method for vv control. This factory method will be used if provided type will be exactly equal to <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">Type of property, for which this control factory should be used. Will only be used in case of exact match.</typeparam>
/// <param name="factoryMethod">Factory method that creates VV control.</param>
void RegisterForType<T>(Func<Type, VVPropEditor> factoryMethod);
/// <summary>
/// Registers factory method for vv control. This factory method will be used if provided type will be assignable to <typeparamref name="T"/>.
/// Conditions will be checked if none of <see cref="RegisterForType{T}"/> registrations were fitting.
/// </summary>
/// <param name="factoryMethod">Factory method that creates VV control.</param>
/// <param name="insertPosition">Where new condition should be inserted - at start or at the end of the list.</param>
void RegisterForAssignableFrom<T>(Func<Type, VVPropEditor> factoryMethod, InsertPosition insertPosition = InsertPosition.First);
/// <summary>
/// Registers factory method for vv control. This factory method will be used if <paramref name="condition"/> will return true for provided type.
/// Conditions will be checked if none of <see cref="RegisterForType{T}"/> registrations were fitting.
/// </summary>
/// <param name="condition">Condition, that will decide, if factory should be used for provided type.</param>
/// <param name="factory">Factory method that creates VV control.</param>
/// <param name="insertPosition">Where new condition should be inserted - at start or at the end of the list. </param>
void RegisterWithCondition(Func<Type, bool> condition, Func<Type, VVPropEditor> factory, InsertPosition insertPosition = InsertPosition.First);
}
/// <summary>
/// Indicator, where item should be inserted in list.
/// </summary>
public enum InsertPosition
{
/// <summary>
/// Item will be inserted as first in list.
/// </summary>
First,
/// <summary>
/// Item will be inserted as last in list.
/// </summary>
Last
}

View File

@@ -151,7 +151,7 @@ namespace Robust.Client.ViewVariables.Instances
ViewVariablesTraitMembers.CreateMemberGroupHeader(
ref first,
PrettyPrint.PrintUserFacingTypeShort(group.Key, 2),
clientVBox.Children);
clientVBox);
foreach (var control in group)
{
@@ -525,7 +525,7 @@ namespace Robust.Client.ViewVariables.Instances
var first = true;
foreach (var (groupName, groupMembers) in _membersBlob!.MemberGroups)
{
ViewVariablesTraitMembers.CreateMemberGroupHeader(ref first, groupName, _serverVariables.Children);
ViewVariablesTraitMembers.CreateMemberGroupHeader(ref first, groupName, _serverVariables);
foreach (var propertyData in groupMembers)
{

View File

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

View File

@@ -1,145 +0,0 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.ViewVariables.Editors;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using CS = System.Runtime.CompilerServices;
namespace Robust.Client.ViewVariables;
internal sealed class ViewVariableControlFactory : IViewVariableControlFactory
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
[Dependency] private readonly IDependencyCollection _dependencyManager = default!;
private readonly Dictionary<Type, Func<Type, VVPropEditor>> _factoriesByType = new();
private readonly List<ConditionalViewVariableFactoryMethodContainer> _factoriesWithCondition = new();
public ViewVariableControlFactory()
{
RegisterForType<sbyte>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.SByte));
RegisterForType<byte>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Byte));
RegisterForType<ushort>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.UShort));
RegisterForType<short>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Short));
RegisterForType<uint>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.UInt));
RegisterForType<int>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Int));
RegisterForType<ulong>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.ULong));
RegisterForType<long>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Long));
RegisterForType<float>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Float));
RegisterForType<float>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Float));
RegisterForType<double>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Double));
RegisterForType<decimal>(_ => new VVPropEditorNumeric(VVPropEditorNumeric.NumberType.Decimal));
RegisterForType<string>(_ => new VVPropEditorString());
RegisterForType<EntProtoId?>(_ => new VVPropEditorNullableEntProtoId());
RegisterForType<EntProtoId>(_ => new VVPropEditorEntProtoId());
RegisterForType<Vector2>(_ => new VVPropEditorVector2(intVec: false));
RegisterForType<Vector2i>(_ => new VVPropEditorVector2(intVec: true));
RegisterForType<bool>(_ => new VVPropEditorBoolean());
RegisterForType<Angle>(_ => new VVPropEditorAngle());
RegisterForType<Box2>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2));
RegisterForType<Box2i>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.Box2i));
RegisterForType<UIBox2>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2));
RegisterForType<UIBox2i>(_ => new VVPropEditorUIBox2(VVPropEditorUIBox2.BoxType.UIBox2i));
RegisterForType<EntityCoordinates>(_ => new VVPropEditorEntityCoordinates());
RegisterForType<EntityUid>(_ => new VVPropEditorEntityUid());
RegisterForType<NetEntity>(_ => new VVPropEditorNetEntity());
RegisterForType<Color>(_ => new VVPropEditorColor());
RegisterForType<TimeSpan>(_ => new VVPropEditorTimeSpan());
RegisterWithCondition(
type => type != typeof(ViewVariablesBlobMembers.ServerValueTypeToken) && !type.IsValueType,
_ => new VVPropEditorReference()
);
RegisterWithCondition(
type => type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken)
|| type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>),
_ => new VVPropEditorKeyValuePair()
);
RegisterForAssignableFrom<CS.ITuple>(_ => new VVPropEditorTuple());
RegisterForAssignableFrom<SoundSpecifier>(_ => new VVPropEditorSoundSpecifier(_protoManager, _resManager));
RegisterForAssignableFrom<ISelfSerialize>(type => CreateGenericEditor(type, typeof(VVPropEditorISelfSerializable<>)));
RegisterForAssignableFrom<ViewVariablesBlobMembers.PrototypeReferenceToken>(type => CreateGenericEditor(type, typeof(VVPropEditorIPrototype<>)));
RegisterForAssignableFrom<IPrototype>(type => CreateGenericEditor(type, typeof(VVPropEditorIPrototype<>)));
RegisterWithCondition(
type => type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>),
type =>
{
var typeArgumentType = type.GenericTypeArguments[0];
var editor = CreateGenericEditor(typeArgumentType, typeof(VVPropEditorProtoId<>));
_dependencyManager.InjectDependencies(editor);
return editor;
}
);
RegisterWithCondition(type => type.IsEnum, _ => new VVPropEditorEnum());
}
/// <inheritdoc />
public void RegisterForType<T>(Func<Type, VVPropEditor> factoryMethod)
{
_factoriesByType[typeof(T)] = factoryMethod;
}
/// <inheritdoc />
public void RegisterForAssignableFrom<T>(Func<Type, VVPropEditor> factoryMethod, InsertPosition insertPosition = InsertPosition.First)
{
int insertIndex = insertPosition == InsertPosition.Last && _factoriesWithCondition.Count > 0
? _factoriesWithCondition.Count - 1
: 0;
_factoriesWithCondition.Insert(insertIndex, new(type => typeof(T).IsAssignableFrom(type), factoryMethod));
}
/// <inheritdoc />
public void RegisterWithCondition(Func<Type, bool> condition, Func<Type, VVPropEditor> factory, InsertPosition insertPosition = InsertPosition.First)
{
int insertIndex = insertPosition == InsertPosition.Last && _factoriesWithCondition.Count > 0
? _factoriesWithCondition.Count - 1
: 0;
_factoriesWithCondition.Insert(insertIndex, new(condition, factory));
}
/// <inheritdoc />
public VVPropEditor CreateFor(Type? type)
{
if (type == null)
{
return new VVPropEditorDummy();
}
if (_factoriesByType.TryGetValue(type, out var factory))
{
return factory(type);
}
foreach (var factoryWithCondition in _factoriesWithCondition)
{
if (factoryWithCondition.CanExecute(type))
{
return factoryWithCondition.Create(type);
}
}
return new VVPropEditorDummy();
}
private static VVPropEditor CreateGenericEditor(Type typeArgumentType, Type genericConrolType)
{
var typeToCreate = genericConrolType.MakeGenericType(typeArgumentType);
return (VVPropEditor)Activator.CreateInstance(typeToCreate)!;
}
private sealed record ConditionalViewVariableFactoryMethodContainer(
Func<Type, bool> CanExecute,
Func<Type, VVPropEditor> Create
);
}

View File

@@ -73,10 +73,30 @@ namespace Robust.Client.ViewVariables
public VVPropEditor SetProperty(ViewVariablesBlobMembers.MemberData member)
{
NameLabel.Text = member.Name;
var type = member.Value?.GetType();
var type = Type.GetType(member.Type);
_bottomLabel.Text = $"Type: {member.TypePretty}";
var editor = _viewVariablesManager.PropertyFor(type);
VVPropEditor editor;
if (type == null || !_robustSerializer.CanSerialize(type))
{
// Type is server-side only.
// Info whether it's reference or value type can be figured out from the sent value.
if (type?.IsValueType == true || member.Value is ViewVariablesBlobMembers.ServerValueTypeToken)
{
// Value type, just display it stringified read-only.
editor = new VVPropEditorDummy();
}
else
{
// Has to be a reference type at this point.
DebugTools.Assert(member.Value is ViewVariablesBlobMembers.ReferenceToken || member.Value == null || type?.IsClass == true || type?.IsInterface == true);
editor = _viewVariablesManager.PropertyFor(type ?? typeof(object));
}
}
else
{
editor = _viewVariablesManager.PropertyFor(type);
}
var view = editor.Initialize(member.Value, !member.Editable);
if (!view.HorizontalExpand)

View File

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

View File

@@ -19,19 +19,7 @@ public sealed class RobustClientPackaging
AssetPass pass,
CancellationToken cancel = default)
{
await WriteClientResources(contentDir, pass, new HashSet<string>(), cancel);
}
public static async Task WriteClientResources(
string contentDir,
AssetPass pass,
IReadOnlySet<string> additionalIgnoredResources,
CancellationToken cancel = default)
{
var ignoreSet = ClientIgnoredResources
.Union(RobustSharedPackaging.SharedIgnoredResources)
.Union(additionalIgnoredResources)
.ToHashSet();
var ignoreSet = ClientIgnoredResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
await RobustSharedPackaging.DoResourceCopy(Path.Combine(contentDir, "Resources"), pass, ignoreSet, cancel: cancel);
}

View File

@@ -15,22 +15,13 @@ public sealed class RobustServerPackaging
string contentDir,
AssetPass pass,
CancellationToken cancel = default)
{
await WriteServerResources(contentDir, pass, new HashSet<string>(), cancel);
}
public static async Task WriteServerResources(
string contentDir,
AssetPass pass,
IReadOnlySet<string> additionalIgnoredResources,
CancellationToken cancel = default)
{
var ignoreSet = ServerIgnoresResources.Union(RobustSharedPackaging.SharedIgnoredResources).ToHashSet();
await RobustSharedPackaging.DoResourceCopy(
Path.Combine(contentDir, "Resources"),
pass,
ignoreSet.Union(additionalIgnoredResources).ToHashSet(),
ignoreSet,
cancel: cancel);
await RobustSharedPackaging.DoResourceCopy(

View File

@@ -43,22 +43,4 @@ public static class AttributeHelper
return defaultValue;
}
public static bool HasAttribute(
INamedTypeSymbol symbol,
INamedTypeSymbol attribute,
[NotNullWhen(true)] out AttributeData? matchedAttribute)
{
matchedAttribute = null;
foreach (var typeAttribute in symbol.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(typeAttribute.AttributeClass, attribute))
{
matchedAttribute = typeAttribute;
return true;
}
}
return false;
}
}

View File

@@ -40,9 +40,6 @@ public static class Diagnostics
public const string IdObsoleteInheritance = "RA0034";
public const string IdObsoleteInheritanceWithMessage = "RA0035";
public const string IdDataFieldYamlSerializable = "RA0036";
public const string IdPrototypeNetSerializable = "RA0037";
public const string IdPrototypeSerializable = "RA0038";
public const string IdPrototypeInstantiation = "RA0039";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -6,7 +6,7 @@ namespace Robust.Roslyn.Shared;
public static class TypeSymbolHelper
{
public static bool ShittyTypeMatch(ITypeSymbol type, string attributeMetadataName)
public static bool ShittyTypeMatch(INamedTypeSymbol type, string attributeMetadataName)
{
// Doing it like this only allocates when the type actually matches, which is good enough for me right now.
if (!attributeMetadataName.EndsWith(type.Name))
@@ -15,7 +15,7 @@ public static class TypeSymbolHelper
return type.ToDisplayString() == attributeMetadataName;
}
public static bool ImplementsInterface(ITypeSymbol type, string interfaceTypeName)
public static bool ImplementsInterface(INamedTypeSymbol type, string interfaceTypeName)
{
foreach (var interfaceType in type.AllInterfaces)
{
@@ -25,33 +25,4 @@ public static class TypeSymbolHelper
return false;
}
public static bool ImplementsInterface(ITypeSymbol type, INamedTypeSymbol interfaceType)
{
foreach (var @interface in type.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(@interface, interfaceType))
return true;
}
return false;
}
/// <summary>
/// Gets all Members of a symbol, including those that are inherited.
/// We need this because sometimes Components have abstract parents with autonetworked datafields.
/// </summary>
public static IEnumerable<ISymbol> GetAllMembersIncludingInherited(INamedTypeSymbol type)
{
var current = type;
while (current != null)
{
foreach (var member in current.GetMembers())
{
yield return member;
}
current = current.BaseType;
}
}
}

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