Compare commits

..

6 Commits

Author SHA1 Message Date
PJB3005
c2d7e940c8 Version: 263.0.3 2025-09-26 13:40:42 +02:00
PJB3005
0eb9a75aeb Validate that content assemblies have a limited list of names.
Also, only read assemblies once from disk

(cherry picked from commit 443a8dfca65be7d60c4bd46181b4c749b4756114)
2025-09-26 13:40:42 +02:00
PJB3005
5adf0cdfa3 Version: 263.0.2 2025-09-19 09:17:25 +02:00
Skye
320543c2a6 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:25 +02:00
PJB3005
0d0d949752 Version: 263.0.1 2025-09-14 14:55:50 +02:00
PJB3005
d43fc89055 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:50 +02:00
211 changed files with 6025 additions and 5119 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

@@ -55,7 +55,7 @@
<PackageVersion Include="Serilog" Version="4.2.0" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />

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

@@ -16,10 +16,7 @@
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.NameGenerator\Robust.Client.NameGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false">
<SetConfiguration Condition="'$(Configuration)' == 'DebugOpt'">Configuration=Debug</SetConfiguration>
<SetConfiguration Condition="'$(Configuration)' == 'Tools'">Configuration=Release</SetConfiguration>
</ProjectReference>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\Robust.Client.Injectors.csproj" ReferenceOutputAssembly="false"/>
</ItemGroup>
<!-- XamlIL does not make use of special Robust configurations like DebugOpt. Convert these down. -->

View File

@@ -54,152 +54,13 @@ END TEMPLATE-->
*None yet*
## 266.0.2
## 263.0.3
## 266.0.1
## 263.0.2
## 266.0.0
### Breaking changes
* A new analyzer has been added that will error if you attempt to subscribe to `AfterAutoHandleStateEvent` on a
component that doesn't have the `AutoGenerateComponentState` attribute, or doesn't have the first argument of that
attribute set to `true`. In most cases you will want to set said argument to `true`.
* The fields on `AutoGenerateComponentStateAttribute` are now `readonly`. Setting these directly (instead of using the constructor arguments) never worked in the first place, so this change only catches existing programming errors.
* When a player disconnects, `ISharedPlayerManager.PlayerStatusChanged` is now fired *after* removing the session from the `Sessions` list.
* `.rsi` files are now compacted into individual `.rsic` files on packaging. This should significantly reduce file count & improve performance all over release builds, but breaks the ability to access `.png` files into RSIs directly. To avoid this, `"rsic": false` can be specified in the RSI's JSON metadata.
* The `scale` command has been removed, with the intent of it being moved to content instead.
### New features
* ViewVariables editors for `ProtoId` fields now have a Select button which opens a window listing all available prototypes of the appropriate type.
* added **IConfigurationManager**.*SubscribeMultiple* ext. method to provide simpler way to unsubscribe from multiple cvar at once
* Added `SharedMapSystem.QueueDeleteMap`, which deletes a map with the specified MapId in the next tick.
* Added generic version of `ComponentRegistry.TryGetComponent`.
* `AttributeHelper.HasAttribute` has had an overload's type signature loosened from `INamedTypeSymbol` to `ITypeSymbol`.
* Errors are now logged when sending messages to disconnected `INetChannel`s.
* Warnings are now logged if sending a message via Lidgren failed for some reason.
* `.yml` and `.ftl` files in the same directory are now concatenated onto each other, to reduce file count in packaged builds. This is done through the new `AssetPassMergeTextDirectories` pass.
* Added `System.Linq.ImmutableArrayExtensions` to sandbox.
* `ImmutableDictionary<TKey, TValue>` and `ImmutableHashSet<T>` can now be network serialized.
* `[AutoPausedField]` now works on fields of type `Dictionary<TKey, TimeSpan>`.
* `[NotYamlSerializable]` analyzer now detects nullable fields of the not-serializable type.
* `ItemList` items can now have a scale applied for the icon.
* Added new OS mouse cursor shapes for the SDL3 backend. These are not available on the GLFW backend.
* Added `IMidiRenderer.MinVolume` to scale the volume of MIDI notes.
* Added `SharedPhysicsSystem.ScaleFixtures`, to apply the physics-only changes of the prior `scale` command.
### Bugfixes
* `LayoutContainer.SetMarginsPreset` and `SetAnchorAndMarginPreset` now correctly use the provided control's top anchor when calculating the margins for its presets; it previously used the bottom anchor instead. This may result in a few UI differences, by a few pixels at most.
* `IConfigurationManager` no longer logs a warning when saving configuration in an integration test.
* Fixed impossible-to-source `ChannelClosedException`s when sending some net messages to disconnected `INetChannel`s.
* Fixed an edge case causing some color values to throw an error in `ColorNaming`.
* Fresh builds from specific projects should no longer cause errors related to `Robust.Client.Injectors` not being found.
* Stopped errors getting logged about `NoteOff` and `NoteOn` operations failing in MIDI.
* Fixed MIDI players not resuming properly when re-entering PVS range.
### Other
* Updated ImageSharp to 3.1.11 to stop the warning about a DoS vulnerability.
* Prototype YAML documents that are completely empty are now skipped by the prototype loader. Previously they would cause a load error for the whole file.
* `TileSpawnWindow` can now be localized.
* `BaseWindow` uses the new mouse cursor shapes for diagonal resizing.
* `NFluidsynth` has been updated to 0.2.0
### Internal
* Added `uitest` tab for standard mouse cursor shapes.
## 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.1
## 263.0.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,33 +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-unknown = unknown color, you should not see this
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

@@ -411,6 +411,9 @@ cmd-spawn-help = spawn <prototype> OR spawn <prototype> <relative entity ID> OR
cmd-cspawn-desc = Spawns a client-side entity with specific type at your feet.
cmd-cspawn-help = cspawn <entity type>
cmd-scale-desc = Increases or decreases an entity's size naively.
cmd-scale-help = scale <entityUid> <float>
cmd-dumpentities-desc = Dump entity list.
cmd-dumpentities-help = Dumps entity list of UIDs and prototype.

View File

@@ -1,6 +1,8 @@
## EntitySpawnWindow
entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-replace-button-text = Replace
entity-spawn-window-override-menu-tooltip = Override placement
@@ -20,5 +22,3 @@ output-panel-scroll-down-button-text = Scroll Down
## Common Used
window-erase-button-text = Erase Mode
window-search-bar-placeholder = Search
window-clear-button = Clear

View File

@@ -25,9 +25,3 @@ vv-sound-reference-distance = Reference Distance
vv-sound-loop = Loop
vv-sound-play-offset = Play Offset (s)
vv-sound-variation = Pitch variation
## ProtoId
vv-protoid-id-placeholder = Prototype ID
vv-protoid-select-button-label = Select
vv-protoid-addwindow-title = Set Prototype

View File

@@ -1,110 +0,0 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.AfterAutoHandleStateAnalyzer,
Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture, TestOf(typeof(AfterAutoHandleStateAnalyzer))]
public sealed class AfterAutoHandleStateAnalyzerTest
{
private const string SubscribeEventDef = """
using System;
namespace Robust.Shared.GameObjects;
public readonly struct EntityUid;
public abstract class EntitySystem
{
public void SubscribeLocalEvent<T, TEvent>() where TEvent : notnull { }
}
public interface IComponent;
public interface IComponentState;
""";
// A rare case for block-scoped namespace, I thought. Then I realized this
// only needed the one type definition.
private const string OtherTypeDefs = """
using System;
namespace JetBrains.Annotations
{
public sealed class BaseTypeRequiredAttribute(Type baseType) : Attribute;
}
""";
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<AfterAutoHandleStateAnalyzer, DefaultVerifier>
{
TestState = { Sources = { code } }
};
TestHelper.AddEmbeddedSources(test.TestState,
"Robust.Shared.Analyzers.ComponentNetworkGeneratorAuxiliary.cs",
"Robust.Shared.GameObjects.EventBusAttributes.cs");
test.TestState.Sources.Add(("EntitySystem.Subscriptions.cs", SubscribeEventDef));
test.TestState.Sources.Add(("Types.cs", OtherTypeDefs));
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
[AutoGenerateComponentState(true)]
public sealed class AutoGenTrue;
[AutoGenerateComponentState(true, true)]
public sealed class AutoGenTrueTrue;
public sealed class NotAutoGen;
[AutoGenerateComponentState]
public sealed class AutoGenNoArgs;
[AutoGenerateComponentState(false)]
public sealed class AutoGenFalse;
public sealed class Foo : EntitySystem
{
public void Good()
{
// Subscribing to other events works
SubscribeLocalEvent<AutoGenNoArgs, object>();
// First arg true allows subscribing
SubscribeLocalEvent<AutoGenTrue, AfterAutoHandleStateEvent>();
SubscribeLocalEvent<AutoGenTrueTrue, AfterAutoHandleStateEvent>();
}
public void Bad()
{
// Can't subscribe if AutoGenerateComponentState isn't even present
SubscribeLocalEvent<NotAutoGen, AfterAutoHandleStateEvent>();
// Can't subscribe if first arg is not specified/false
SubscribeLocalEvent<AutoGenNoArgs, AfterAutoHandleStateEvent>();
SubscribeLocalEvent<AutoGenFalse, AfterAutoHandleStateEvent>();
}
}
""";
await Verifier(code,
// /0/Test0.cs(29,9): error RA0040: Tried to subscribe to AfterAutoHandleStateEvent for 'NotAutoGen' which doesn't have an AutoGenerateComponentState attribute
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttribute).WithSpan(29, 9, 29, 69).WithArguments("NotAutoGen"),
// /0/Test0.cs(32,9): error RA0041: Tried to subscribe to AfterAutoHandleStateEvent for 'AutoGenNoArgs' which doesn't have raiseAfterAutoHandleState set
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttributeParam).WithSpan(32, 9, 32, 72).WithArguments("AutoGenNoArgs"),
// /0/Test0.cs(33,9): error RA0041: Tried to subscribe to AfterAutoHandleStateEvent for 'AutoGenFalse' which doesn't have raiseAfterAutoHandleState set
VerifyCS.Diagnostic(AfterAutoHandleStateAnalyzer.MissingAttributeParam).WithSpan(33, 9, 33, 71).WithArguments("AutoGenFalse")
);
}
}

View File

@@ -1,4 +1,4 @@
extern alias SerializationGenerator;
extern alias SerializationGenerator;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
@@ -126,48 +126,6 @@ public sealed class ComponentPauseGeneratorTest
""");
}
[Test]
public void TestDictionary()
{
var result = RunGenerator("""
[AutoGenerateComponentPause]
public sealed partial class FooComponent : IComponent
{
[AutoPausedField]
public Dictionary<string, TimeSpan> Foo;
}
""");
ExpectNoDiagnostics(result);
ExpectSource(
result,
"""
// <auto-generated />
using Robust.Shared.GameObjects;
public partial class FooComponent
{
[RobustAutoGenerated]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
public sealed class FooComponent_AutoPauseSystem : EntitySystem
{
public override void Initialize()
{
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
}
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
{
foreach (var key in component.Foo.Keys)
component.Foo[key] += args.PausedTime;
}
}
}
""");
}
[Test]
public void TestAutoState()
{

View File

@@ -203,8 +203,6 @@ public sealed class DataDefinitionAnalyzerTest
[NotYamlSerializable]
public sealed class NotSerializableClass { }
[NotYamlSerializable]
public readonly struct NotSerializableStruct { }
[DataDefinition]
public sealed partial class Foo
@@ -215,21 +213,6 @@ public sealed class DataDefinitionAnalyzerTest
[DataField]
public NotSerializableClass BadProperty { get; set; }
[DataField]
public NotSerializableClass? BadNullableField;
[DataField]
public NotSerializableStruct BadStructField;
[DataField]
public NotSerializableStruct BadStructProperty { get; set; }
[DataField]
public NotSerializableStruct? BadNullableStructField;
[DataField]
public NotSerializableStruct? BadNullableStructProperty { get; set; }
public NotSerializableClass GoodField; // Not a DataField, not a problem
public NotSerializableClass GoodProperty { get; set; } // Not a DataField, not a problem
@@ -237,20 +220,10 @@ public sealed class DataDefinitionAnalyzerTest
""";
await Verifier(code,
// /0/Test0.cs(12,12): error RA0033: Data field BadField in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(12, 12, 12, 32).WithArguments("BadField", "Foo", "NotSerializableClass"),
// /0/Test0.cs(15,12): error RA0033: Data field BadProperty in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(15, 12, 15, 32).WithArguments("BadProperty", "Foo", "NotSerializableClass"),
// /0/Test0.cs(18,12): error RA0036: Data field BadNullableField in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(18, 12, 18, 33).WithArguments("BadNullableField", "Foo", "NotSerializableClass"),
// /0/Test0.cs(21,12): error RA0036: Data field BadStructField in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(21, 12, 21, 33).WithArguments("BadStructField", "Foo", "NotSerializableStruct"),
// /0/Test0.cs(24,12): error RA0036: Data field BadStructProperty in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(24, 12, 24, 33).WithArguments("BadStructProperty", "Foo", "NotSerializableStruct"),
// /0/Test0.cs(27,12): error RA0036: Data field BadNullableStructField in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(27, 12, 27, 34).WithArguments("BadNullableStructField", "Foo", "NotSerializableStruct"),
// /0/Test0.cs(30,12): error RA0036: Data field BadNullableStructProperty in data definition Foo is type NotSerializableStruct, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(30, 12, 30, 34).WithArguments("BadNullableStructProperty", "Foo", "NotSerializableStruct")
// /0/Test0.cs(10,12): error RA0033: Data field BadField in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(10, 12, 10, 32).WithArguments("BadField", "Foo", "NotSerializableClass"),
// /0/Test0.cs(13,12): error RA0033: Data field BadProperty in data definition Foo is type NotSerializableClass, which is not YAML serializable
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldYamlSerializableRule).WithSpan(13, 12, 13, 32).WithArguments("BadProperty", "Foo", "NotSerializableClass")
);
}
}

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

@@ -10,7 +10,6 @@
<ItemGroup>
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ComponentNetworkGeneratorAuxiliary.cs" LogicalName="Robust.Shared.Analyzers.ComponentNetworkGeneratorAuxiliary.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
@@ -18,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

@@ -1,85 +0,0 @@
#nullable enable
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 AfterAutoHandleStateAnalyzer : DiagnosticAnalyzer
{
private const string AfterAutoHandleStateEventName = "AfterAutoHandleStateEvent";
private const string AutoGenStateAttribute = "Robust.Shared.Analyzers.AutoGenerateComponentStateAttribute";
private const string SubscribeLocalEventName = "SubscribeLocalEvent";
public static readonly DiagnosticDescriptor MissingAttribute = new(
Diagnostics.IdAutoGenStateAttributeMissing,
"Unreachable AfterAutoHandleState subscription",
"Tried to subscribe to AfterAutoHandleStateEvent for '{0}' which doesn't have an "
+ "AutoGenerateComponentState attribute",
"Usage",
DiagnosticSeverity.Error,
true,
// Does this even show up anywhere in Rider? >:(
"You must mark your component with '[AutoGenerateComponentState(true)]' to subscribe to this event."
);
public static readonly DiagnosticDescriptor MissingAttributeParam = new(
Diagnostics.IdAutoGenStateParamMissing,
"Unreachable AfterAutoHandleState subscription",
"Tried to subscribe to AfterAutoHandleStateEvent for '{0}' which doesn't have "
+ "raiseAfterAutoHandleState set",
"Usage",
DiagnosticSeverity.Error,
true,
"The AutoGenerateComponentState attribute must be passed 'true' in order to subscribe to this event."
);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
[MissingAttribute, MissingAttributeParam];
public override void Initialize(AnalysisContext context)
{
// This is more to stop user error rather than code generation error
// (Plus this shouldn't affect code gen anyway)
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(compilationContext =>
{
var autoGenStateAttribute = compilationContext.Compilation.GetTypeByMetadataName(AutoGenStateAttribute);
// No attribute, no analyzer.
if (autoGenStateAttribute is null)
return;
compilationContext.RegisterOperationAction(
analysisContext => CheckEventSubscription(analysisContext, autoGenStateAttribute),
OperationKind.Invocation);
});
}
private static void CheckEventSubscription(OperationAnalysisContext context, ITypeSymbol autoGenStateAttribute)
{
if (context.Operation is not IInvocationOperation operation)
return;
// Check the method has the right name and has the right type args
if (operation.TargetMethod is not
{ Name: SubscribeLocalEventName, TypeArguments: [var component, { Name: AfterAutoHandleStateEventName }] })
return;
// Search the component's attributes for something matching autoGenStateAttribute
AttributeHelper.HasAttribute(component, autoGenStateAttribute, out var autoGenAttribute);
// First argument is raiseAfterAutoHandleState—note it shouldn't ever
// be null, since it has a default, but eh.
if (autoGenAttribute?.ConstructorArguments[0].Value is true)
return;
context.ReportDiagnostic(Diagnostic.Create(autoGenAttribute is null ? MissingAttribute : MissingAttributeParam,
operation.Syntax.GetLocation(),
component.Name));
}
}

View File

@@ -121,7 +121,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}, SymbolKind.NamedType);
}
private static void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
{
if (context.Node is not TypeDeclarationSyntax declaration)
return;
@@ -147,7 +147,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private static void AnalyzeDataField(SyntaxNodeAnalysisContext context)
private void AnalyzeDataField(SyntaxNodeAnalysisContext context)
{
if (context.Node is not FieldDeclarationSyntax field)
return;
@@ -186,8 +186,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (context.SemanticModel.GetSymbolInfo(field.Declaration.Type).Symbol is not ITypeSymbol fieldTypeSymbol)
continue;
fieldTypeSymbol = TypeSymbolHelper.GetNullableUnderlyingTypeOrSelf(fieldTypeSymbol);
if (IsNotYamlSerializable(fieldSymbol, fieldTypeSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldYamlSerializableRule,
@@ -200,7 +198,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
}
}
private static void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
private void AnalyzeDataFieldProperty(SyntaxNodeAnalysisContext context)
{
if (context.Node is not PropertyDeclarationSyntax property)
return;
@@ -241,8 +239,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (context.SemanticModel.GetSymbolInfo(property.Type).Symbol is not ITypeSymbol propertyTypeSymbol)
return;
propertyTypeSymbol = TypeSymbolHelper.GetNullableUnderlyingTypeOrSelf(propertyTypeSymbol);
if (IsNotYamlSerializable(propertySymbol, propertyTypeSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldYamlSerializableRule,

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

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

@@ -213,6 +213,4 @@ public interface IMidiRenderer : IDisposable
/// Actually disposes of this renderer. Do NOT use outside the MIDI thread.
/// </summary>
internal void InternalDispose();
byte MinVolume { get; set; }
}

View File

@@ -42,7 +42,7 @@ internal sealed partial class MidiManager : IMidiManager
[Dependency] private readonly IRuntimeLog _runtime = default!;
private AudioSystem _audioSys = default!;
private SharedPhysicsSystem _physics = default!;
private SharedPhysicsSystem _broadPhaseSystem = default!;
private SharedTransformSystem _xformSystem = default!;
public IReadOnlyList<IMidiRenderer> Renderers
@@ -81,7 +81,7 @@ internal sealed partial class MidiManager : IMidiManager
private Thread? _midiThread;
private ISawmill _midiSawmill = default!;
private float _gain = 0f;
private bool _gainDirty = true;
private bool _volumeDirty = true;
// Not reliable until Fluidsynth is initialized!
[ViewVariables(VVAccess.ReadWrite)]
@@ -96,7 +96,7 @@ internal sealed partial class MidiManager : IMidiManager
return;
_cfgMan.SetCVar(CVars.MidiVolume, clamped);
_gainDirty = true;
_volumeDirty = true;
}
}
@@ -114,8 +114,7 @@ internal sealed partial class MidiManager : IMidiManager
"/usr/share/sounds/sf2/TimGM6mb.sf2",
};
private static readonly string WindowsSoundfont =
$@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
private static readonly string WindowsSoundfont = $@"{Environment.GetEnvironmentVariable("SystemRoot")}\system32\drivers\gm.dls";
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
@@ -146,13 +145,11 @@ internal sealed partial class MidiManager : IMidiManager
{
if (FluidsynthInitialized || _failedInitialize) return;
_cfgMan.OnValueChanged(CVars.MidiVolume,
value =>
{
_gain = value;
_gainDirty = true;
},
true);
_cfgMan.OnValueChanged(CVars.MidiVolume, value =>
{
_gain = value;
_volumeDirty = true;
}, true);
_midiSawmill = _logger.GetSawmill("midi");
#if DEBUG
@@ -170,15 +167,13 @@ internal sealed partial class MidiManager : IMidiManager
// not a directory, preserve the old file and create an actual directory
else if (!_resourceManager.UserData.IsDir(CustomSoundfontDirectory))
{
_resourceManager.UserData.Rename(CustomSoundfontDirectory,
CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
_resourceManager.UserData.Rename(CustomSoundfontDirectory, CustomSoundfontDirectory.WithName(CustomSoundfontDirectory.Filename + ".old"));
_resourceManager.UserData.CreateDir(CustomSoundfontDirectory);
}
try
{
NFluidsynth.Logger
.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
NFluidsynth.Logger.SetLoggerMethod(_loggerDelegate); // Will cause a safe DllNotFoundException if not available.
_settings = new Settings();
_settings["synth.sample-rate"].DoubleValue = 44100;
@@ -198,7 +193,7 @@ internal sealed partial class MidiManager : IMidiManager
//_settings["synth.verbose"].IntValue = 1; // Useful for debugging.
var midiParallel = _cfgMan.GetCVar(CVars.MidiParallelism);
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int) (Math.Log2(midiParallel) * 2048), 1, 65535);
_settings["synth.polyphony"].IntValue = Math.Clamp(1024 + (int)(Math.Log2(midiParallel) * 2048), 1, 65535);
_settings["synth.cpu-cores"].IntValue = Math.Clamp(midiParallel, 1, 256);
_midiSawmill.Debug($"Synth Cores: {_settings["synth.cpu-cores"].IntValue}");
@@ -224,7 +219,7 @@ internal sealed partial class MidiManager : IMidiManager
};
_audioSys = _entityManager.EntitySysManager.GetEntitySystem<AudioSystem>();
_physics = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
_broadPhaseSystem = _entityManager.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
_xformSystem = _entityManager.System<SharedTransformSystem>();
_entityManager.GetEntityQuery<PhysicsComponent>();
_entityManager.GetEntityQuery<TransformComponent>();
@@ -268,8 +263,7 @@ internal sealed partial class MidiManager : IMidiManager
{
soundfontLoader.SetCallbacks(_soundfontLoaderCallbacks);
var renderer =
new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
LoadSoundFontSetup(renderer);
@@ -279,7 +273,6 @@ internal sealed partial class MidiManager : IMidiManager
{
_renderers.Add(renderer);
}
return renderer;
}
finally
@@ -316,23 +309,99 @@ internal sealed partial class MidiManager : IMidiManager
_updateSemaphore.Release();
_gainDirty = false;
_volumeDirty = false;
}
private void UpdateRenderer(IMidiRenderer renderer, MapCoordinates listener)
{
// TODO: This should be sharing more code with AudioSystem.
try
{
if (renderer.Disposed)
return;
if (!renderer.Mono)
renderer.Source.Global = true;
if (_volumeDirty)
{
renderer.Source.Gain = Gain;
}
if (!renderer.Source.Global)
UpdateLocalRenderer(renderer, listener);
if (!renderer.Mono)
{
renderer.Source.Global = true;
return;
}
MapCoordinates mapPos;
if (renderer.TrackingEntity is {} trackedEntity && !_entityManager.Deleted(trackedEntity))
{
renderer.TrackingCoordinates = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
// Pause it if the attached entity is paused.
if (_entityManager.IsPaused(renderer.TrackingEntity))
{
renderer.Source.Pause();
return;
}
}
else if (renderer.TrackingCoordinates == null)
{
renderer.Source.Pause();
return;
}
mapPos = renderer.TrackingCoordinates.Value;
// If it's on a different map then just mute it, not pause.
if (mapPos.MapId == MapId.Nullspace || mapPos.MapId != listener.MapId)
{
renderer.Source.Gain = 0f;
return;
}
// Was previously muted maybe so try unmuting it?
if (renderer.Source.Gain == 0f)
{
renderer.Source.Gain = Gain;
}
var worldPos = mapPos.Position;
var delta = worldPos - listener.Position;
var distance = delta.Length();
// Update position
// Out of range so just clip it for us.
if (distance > renderer.Source.MaxDistance)
{
// Still keeps the source playing, just with no volume.
renderer.Source.Gain = 0f;
return;
}
// Same imprecision suppression as audiosystem.
if (distance > 0f && distance < 0.01f)
{
worldPos = listener.Position;
delta = Vector2.Zero;
distance = 0f;
}
renderer.Source.Position = worldPos;
// Update velocity (doppler).
if (!_entityManager.Deleted(renderer.TrackingEntity))
{
var velocity = _broadPhaseSystem.GetMapLinearVelocity(renderer.TrackingEntity.Value);
renderer.Source.Velocity = velocity;
}
else
UpdateGlobalRenderer(renderer);
{
renderer.Source.Velocity = Vector2.Zero;
}
// Update occlusion
var occlusion = _audioSys.GetOcclusion(listener, delta, distance, renderer.TrackingEntity);
renderer.Source.Occlusion = occlusion;
}
catch (Exception ex)
{
@@ -340,58 +409,6 @@ internal sealed partial class MidiManager : IMidiManager
}
}
private void UpdateLocalRenderer(IMidiRenderer renderer, MapCoordinates listener)
{
if (_entityManager.Deleted(renderer.TrackingEntity) || _entityManager.IsPaused(renderer.TrackingEntity))
{
renderer.Source.Gain = 0f;
return;
}
MapCoordinates mapCoords = _xformSystem.GetMapCoordinates(renderer.TrackingEntity.Value);
renderer.TrackingCoordinates = mapCoords;
if (mapCoords.MapId == MapId.Nullspace || mapCoords.MapId != listener.MapId)
{
renderer.Source.Gain = 0f;
return;
}
Vector2 mapPosition = mapCoords.Position;
Vector2 listenerDelta = mapPosition - listener.Position;
var listenerDeltaLength = listenerDelta.Length();
if (listenerDeltaLength > renderer.Source.MaxDistance)
{
renderer.Source.Gain = 0f;
return;
}
if (listenerDeltaLength is > 0f and < 0.01f)
{
mapPosition = listener.Position;
listenerDelta = Vector2.Zero;
listenerDeltaLength = 0f;
}
if (_gainDirty || renderer.Source.Gain == 0f)
renderer.Source.Gain = Gain;
renderer.Source.Position = mapPosition;
renderer.Source.Velocity = _physics.GetMapLinearVelocity(renderer.TrackingEntity.Value);
renderer.Source.Occlusion =
_audioSys.GetOcclusion(listener, listenerDelta, listenerDeltaLength, renderer.TrackingEntity);
}
private void UpdateGlobalRenderer(IMidiRenderer renderer)
{
if (_gainDirty)
renderer.Source.Gain = Gain;
}
/// <summary>
/// Main method for the thread rendering the midi audio.
/// </summary>
@@ -411,7 +428,7 @@ internal sealed partial class MidiManager : IMidiManager
{
if (!renderer.Disposed)
{
if (renderer.Master is {Disposed: true})
if (renderer.Master is { Disposed: true })
renderer.Master = null;
renderer.Render();

View File

@@ -214,11 +214,6 @@ internal sealed partial class MidiRenderer : IMidiRenderer
[ViewVariables]
public BitArray FilteredChannels { get; } = new(RobustMidiEvent.MaxChannels);
[ViewVariables]
public byte MinVolume { get => _minVolume; set => _minVolume = value; }
private byte _minVolume;
[ViewVariables(VVAccess.ReadWrite)]
public byte? VelocityOverride { get; set; } = null;
@@ -544,7 +539,14 @@ internal sealed partial class MidiRenderer : IMidiRenderer
if (velocity <= 0)
continue;
_synth.TryNoteOn(channel, key, velocity);
try
{
_synth.NoteOn(channel, key, velocity);
}
catch (FluidSynthInteropException e)
{
_midiSawmill.Error($"CH:{channel} KEY:{key} VEL:{velocity} {e.ToStringBetter()}");
}
}
}
@@ -572,32 +574,19 @@ internal sealed partial class MidiRenderer : IMidiRenderer
{
case RobustMidiCommand.NoteOff:
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = 0;
_synth.TryNoteOff(midiEvent.Channel, midiEvent.Key);
_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.TryNoteOn(midiEvent.Channel, midiEvent.Key, velocity);
break;
}
if (FilteredChannels[midiEvent.Channel])
break;
if (MinVolume > 0)
velocity = (byte)Math.Floor(MathHelper.Lerp(MinVolume, 127, (float)velocity / 127));
velocity = VelocityOverride ?? velocity;
var velocity = VelocityOverride ?? midiEvent.Velocity;
_rendererState.NoteVelocities.AsSpan[midiEvent.Channel].AsSpan[midiEvent.Key] = velocity;
_synth.TryNoteOn(midiEvent.Channel, 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

@@ -154,7 +154,6 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
_sprite = new TabSpriteView();
_tabContainer.AddChild(_sprite);
_tabContainer.AddChild(TabCursorShapes());
}
public void OnClosed()
@@ -211,53 +210,6 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
return label;
}
private Control TabCursorShapes()
{
var box = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
};
var styleBox = new StyleBoxFlat
{
BackgroundColor = Color.Black
};
foreach (var cursorName in Enum.GetNames<CursorShape>())
{
// Go over names due to duplicate definitions in the enum.
var cursor = Enum.Parse<CursorShape>(cursorName);
// Wow was I bad at API design.
if (cursor == CursorShape.Custom)
continue;
var panel = new PanelContainer
{
PanelOverride = styleBox,
DefaultCursorShape = cursor,
MouseFilter = MouseFilterMode.Stop,
MinHeight = 30,
Children =
{
new Label
{
Text = cursorName,
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(4)
}
}
};
box.AddChild(panel);
}
return new ScrollContainer
{
Children = { box },
VScrollEnabled = true,
HScrollEnabled = false,
Name = nameof(Tab.TabCursorShapes),
};
}
public void SelectTab(Tab tab)
{
_tabContainer.CurrentTab = (int)tab;
@@ -274,7 +226,6 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
TextEdit = 6,
RichText = 7,
SpriteView = 8,
TabCursorShapes = 9,
}
}

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 (TryComp<MetaDataComponent>(ent, out var meta))
{
text.AppendLine($"uid: {ent}, {meta.EntityName}");
}

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

View File

@@ -0,0 +1,25 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Robust.Client.GameObjects;
public sealed class ScaleVisualsSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ScaleVisualsComponent, AppearanceChangeEvent>(OnChangeData);
}
private void OnChangeData(EntityUid uid, ScaleVisualsComponent component, ref AppearanceChangeEvent ev)
{
if (!ev.AppearanceData.TryGetValue(ScaleVisuals.Scale, out var scale) ||
ev.Sprite == null) return;
var vecScale = (Vector2)scale;
// Set it directly because prediction may call this multiple times.
ev.Sprite.Scale = vecScale;
}
}

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

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

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

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

@@ -17,6 +17,8 @@ using Robust.Shared.Timing;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using Color = Robust.Shared.Maths.Color;
using Vector3 = Robust.Shared.Maths.Vector3;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics.Clyde
{
@@ -396,7 +398,7 @@ namespace Robust.Client.Graphics.Clyde
{
}
private protected override void SetParameterImpl(string name, in Matrix4x4 value)
private protected override void SetParameterImpl(string name, in Matrix4 value)
{
}

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

@@ -128,19 +128,6 @@ namespace Robust.Client.Graphics.Clyde
AddStandardCursor(StandardCursorShape.Hand, CursorShape.Hand);
AddStandardCursor(StandardCursorShape.HResize, CursorShape.HResize);
AddStandardCursor(StandardCursorShape.VResize, CursorShape.VResize);
AddStandardCursor(StandardCursorShape.Progress, CursorShape.Arrow);
AddStandardCursor(StandardCursorShape.NWSEResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.NESWResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.Move, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.NotAllowed, CursorShape.Arrow);
AddStandardCursor(StandardCursorShape.NWResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.NResize, CursorShape.VResize);
AddStandardCursor(StandardCursorShape.NEResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.EResize, CursorShape.HResize);
AddStandardCursor(StandardCursorShape.SEResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.SResize, CursorShape.VResize);
AddStandardCursor(StandardCursorShape.SWResize, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.WResize, CursorShape.HResize);
}
private sealed class CursorImpl : ICursor

View File

@@ -94,19 +94,6 @@ internal partial class Clyde
Add(StandardCursorShape.Hand, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_POINTER);
Add(StandardCursorShape.HResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_EW_RESIZE);
Add(StandardCursorShape.VResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NS_RESIZE);
Add(StandardCursorShape.Progress, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_PROGRESS);
Add(StandardCursorShape.NWSEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NWSE_RESIZE);
Add(StandardCursorShape.NESWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NESW_RESIZE);
Add(StandardCursorShape.Move, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_MOVE);
Add(StandardCursorShape.NotAllowed, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NOT_ALLOWED);
Add(StandardCursorShape.NWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NW_RESIZE);
Add(StandardCursorShape.NResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_N_RESIZE);
Add(StandardCursorShape.NEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_NE_RESIZE);
Add(StandardCursorShape.EResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_E_RESIZE);
Add(StandardCursorShape.SEResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SE_RESIZE);
Add(StandardCursorShape.SResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_S_RESIZE);
Add(StandardCursorShape.SWResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_SW_RESIZE);
Add(StandardCursorShape.WResize, SDL.SDL_SystemCursor.SDL_SYSTEM_CURSOR_W_RESIZE);
void Add(StandardCursorShape shape, SDL.SDL_SystemCursor sysCursor)
{

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

@@ -15,11 +15,6 @@ namespace Robust.Client.Graphics
/// </summary>
IBeam,
/// <summary>
/// Alias for <see cref="IBeam"/>.
/// </summary>
Text = IBeam,
/// <summary>
/// The crosshair shape. Used when dragging and dropping.
/// </summary>
@@ -30,135 +25,16 @@ namespace Robust.Client.Graphics
/// </summary>
Hand,
/// <summary>
/// Alias for <see cref="Hand"/>
/// </summary>
Pointer = Hand,
/// <summary>
/// The horizontal resize shape. Used when mousing over something that can be horizontally resized.
/// </summary>
HResize,
/// <summary>
/// Alias for <see cref="EWResize"/>
/// </summary>
EWResize = HResize,
/// <summary>
/// The vertical resize shape. Used when mousing over something that can be vertically resized.
/// </summary>
VResize,
/// <summary>
/// Alias for <see cref="VResize"/>.
/// </summary>
NSResize = VResize,
/// <summary>
/// Program is busy doing something.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
Progress,
/// <summary>
/// Diagonal resize shape for northwest-southeast resizing.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NWSEResize,
/// <summary>
/// Diagonal resize shape for northeast-southwest resizing.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NESWResize,
/// <summary>
/// 4-way arrow move icon.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
Move,
/// <summary>
/// An action is not allowed.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NotAllowed,
/// <summary>
/// One-directional resize to the northwest.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NWResize,
/// <summary>
/// One-directional resize to the north.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NResize,
/// <summary>
/// One-directional resize to the northeast.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
NEResize,
/// <summary>
/// One-directional resize to the east.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
EResize,
/// <summary>
/// One-directional resize to the southeast.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
SEResize,
/// <summary>
/// One-directional resize to the south.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
SResize,
/// <summary>
/// One-directional resize to the southwest.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
SWResize,
/// <summary>
/// One-directional resize to the west.
/// </summary>
/// <remarks>
/// This cursor is not always available and may be substituted.
/// </remarks>
WResize,
/// <summary>
/// Not a real value
/// </summary>

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

@@ -138,16 +138,9 @@ namespace Robust.Client.ResourceManagement
var sw = Stopwatch.StartNew();
var resList = GetTypeData<RSIResource>().Resources;
var foundRsiList = _manager.ContentFindFiles("/Textures/")
var rsiList = _manager.ContentFindFiles("/Textures/")
.Where(p => p.ToString().EndsWith(".rsi/meta.json"))
.Select(c => c.Directory);
var foundRsicList = _manager.ContentFindFiles("/Textures/")
.Where(p => p.Extension == "rsic")
.Select(c => c.WithExtension("rsi"));
var rsiList = foundRsiList
.Concat(foundRsicList)
.Select(c => c.Directory)
.Where(p => !resList.ContainsKey(p))
.Select(p => new RSIResource.LoadStepData {Path = p})
.ToArray();

View File

@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.ContentPack;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
@@ -57,88 +58,12 @@ namespace Robust.Client.ResourceManagement
internal static void LoadPreTexture(IResourceManager manager, LoadStepData data)
{
var manifestPath = data.Path / "meta.json";
if (manager.TryContentFileRead(manifestPath, out var manifestFile))
{
LoadPreTextureFolder(manager, data, manifestFile);
}
else
{
var rsicPath = data.Path.WithExtension("rsic");
if (manager.TryContentFileRead(rsicPath, out var rsicFile))
{
LoadPreTextureRsic(data, rsicFile);
}
else
{
throw new FileNotFoundException($"Unable to find .rsi file: {data.Path}");
}
}
}
private static void LoadPreTextureFolder(IResourceManager manager, LoadStepData data, Stream manifestFile)
{
RsiLoading.RsiMetadata metadata;
using (manifestFile)
using (var manifestFile = manager.ContentFileRead(manifestPath))
{
metadata = RsiLoading.LoadRsiMetadata(manifestFile);
}
data.FrameCounts = RsiLoading.CalculateFrameCounts(metadata);
data.Images = RsiLoading.LoadImages(
metadata,
SixLabors.ImageSharp.Configuration.Default,
name =>
{
var texPath = data.Path / (name + ".png");
return manager.ContentFileRead(texPath);
});
var sheet = RsiLoading.GenerateAtlas(
metadata,
data.FrameCounts,
data.Images,
SixLabors.ImageSharp.Configuration.Default,
out var dimensionX);
LoadPreTextureCommon(metadata, data);
data.AtlasSheet = sheet;
data.DimX = dimensionX;
data.LoadParameters = metadata.LoadParameters;
data.MetaAtlas = metadata.MetaAtlas;
}
private static void LoadPreTextureRsic(LoadStepData data, Stream rsicFile)
{
Image<Rgba32> image;
using (rsicFile)
{
image = Image.Load<Rgba32>(rsicFile);
}
data.AtlasSheet = image;
var textDataList = image.Metadata.GetPngMetadata().TextData;
if (!textDataList.TryFirstOrNull(
static data => data.Keyword == RsiLoading.RsicPngField,
out var pngMetadata))
throw new InvalidDataException(".rsic does not have metadata field");
var metadata = RsiLoading.LoadRsiMetadata(pngMetadata.Value.Value);
data.FrameCounts = RsiLoading.CalculateFrameCounts(metadata);
LoadPreTextureCommon(metadata, data);
data.DimX = image.Width / metadata.Size.X;
data.LoadParameters = metadata.LoadParameters;
data.MetaAtlas = metadata.MetaAtlas;
}
private static void LoadPreTextureCommon(
RsiLoading.RsiMetadata metadata,
LoadStepData data)
{
var stateCount = metadata.States.Length;
var toAtlas = new StateReg[stateCount];
@@ -147,12 +72,40 @@ namespace Robust.Client.ResourceManagement
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>(stateCount);
// Check for duplicate states
for (var i = 0; i < metadata.States.Length; i++)
{
var stateId = metadata.States[i].StateId;
for (int j = i + 1; j < metadata.States.Length; j++)
{
if (stateId == metadata.States[j].StateId)
throw new RSILoadException($"RSI '{data.Path}' has a duplicate stateId '{stateId}'.");
}
}
// Do every state.
for (var index = 0; index < metadata.States.Length; index++)
{
ref var reg = ref toAtlas[index];
var stateObject = metadata.States[index];
// Load image from disk.
var texPath = data.Path / (stateObject.StateId + ".png");
using (var stream = manager.ContentFileRead(texPath))
{
reg.Src = Image.Load<Rgba32>(stream);
}
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
var regDims = $"{reg.Src.Width}x{reg.Src.Height}";
var iconDims = $"{frameSize.X}x{frameSize.Y}";
throw new RSILoadException($"State '{stateObject.StateId}' image size ({regDims}) is not a multiple of the icon size ({iconDims}).");
}
// Load all frames into a list so we can operate on it more sanely.
reg.TotalFrameCount = stateObject.Delays.Sum(delayList => delayList.Length);
var (foldedDelays, foldedIndices) = FoldDelays(stateObject.Delays);
@@ -177,23 +130,60 @@ namespace Robust.Client.ResourceManagement
_ => throw new InvalidOperationException()
};
var state = new RSI.State(
frameSize,
rsi,
stateObject.StateId,
dirType,
foldedDelays,
var state = new RSI.State(frameSize, rsi, stateObject.StateId, dirType, foldedDelays,
textures);
rsi.AddState(state);
callbackOffsets[stateObject.StateId] = callbackOffset;
}
// Poorly hacked in texture atlas support here.
var totalFrameCount = toAtlas.Sum(p => p.TotalFrameCount);
// Generate atlas.
var dimensionX = (int) MathF.Ceiling(MathF.Sqrt(totalFrameCount));
var dimensionY = (int) MathF.Ceiling((float) totalFrameCount / dimensionX);
var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheetIndex = 0;
for (var index = 0; index < toAtlas.Length; index++)
{
ref var reg = ref toAtlas[index];
// Blit all the frames over.
for (var i = 0; i < reg.TotalFrameCount; i++)
{
var srcWidth = (reg.Src.Width / frameSize.X);
var srcColumn = i % srcWidth;
var srcRow = i / srcWidth;
var srcPos = (srcColumn * frameSize.X, srcRow * frameSize.Y);
var sheetColumn = (sheetIndex + i) % dimensionX;
var sheetRow = (sheetIndex + i) / dimensionX;
var sheetPos = (sheetColumn * frameSize.X, sheetRow * frameSize.Y);
var srcBox = UIBox2i.FromDimensions(srcPos, frameSize);
reg.Src.Blit(srcBox, sheet, sheetPos);
}
sheetIndex += reg.TotalFrameCount;
}
for (var i = 0; i < toAtlas.Length; i++)
{
ref var reg = ref toAtlas[i];
reg.Src.Dispose();
}
data.Rsi = rsi;
data.CallbackOffsets = callbackOffsets;
data.AtlasSheet = sheet;
data.AtlasList = toAtlas;
data.FrameSize = frameSize;
data.DimX = dimensionX;
data.CallbackOffsets = callbackOffsets;
data.LoadParameters = metadata.LoadParameters;
data.MetaAtlas = metadata.MetaAtlas;
}
internal static void LoadPostTexture(LoadStepData data)
@@ -226,7 +216,7 @@ namespace Robust.Client.ResourceManagement
}
}
sheetOffset += data.FrameCounts[toAtlasIndex];
sheetOffset += reg.TotalFrameCount;
}
}
@@ -391,8 +381,6 @@ namespace Robust.Client.ResourceManagement
public Image<Rgba32> AtlasSheet = default!;
public int DimX;
public StateReg[] AtlasList = default!;
public int[] FrameCounts = default!;
public Image<Rgba32>[] Images = default!;
public Vector2i FrameSize;
public Dictionary<RSI.StateId, Vector2i[][]> CallbackOffsets = default!;
public Texture AtlasTexture = default!;
@@ -404,9 +392,11 @@ namespace Robust.Client.ResourceManagement
internal struct StateReg
{
public Image<Rgba32> Src;
public Texture[][] Output;
public int[][] Indices;
public Vector2i[][] Offsets;
public int TotalFrameCount;
}
}
}

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

@@ -11,124 +11,14 @@ namespace Robust.Client.UserInterface
/// <summary>
/// Default common cursor shapes available in the UI.
/// </summary>
/// <seealso cref="StandardCursorShape"/>
public enum CursorShape: byte
{
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.Arrow"/>
/// </summary>
Arrow,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.IBeam"/>
/// </summary>
IBeam,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.Text"/>
/// </summary>
Text = IBeam,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.Crosshair"/>
/// </summary>
Crosshair,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.Hand"/>
/// </summary>
Hand,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.Pointer"/>
/// </summary>
Pointer = Hand,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.HResize"/>
/// </summary>
HResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.EWResize"/>
/// </summary>
EWResize = HResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.VResize"/>
/// </summary>
VResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.NSResize"/>
/// </summary>
NSResize = VResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.Progress"/>
/// </summary>
Progress,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.NWSEResize"/>
/// </summary>
NWSEResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.NESWResize"/>
/// </summary>
NESWResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.Move"/>
/// </summary>
Move,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.NotAllowed"/>
/// </summary>
NotAllowed,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.NWResize"/>
/// </summary>
NWResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.NResize"/>
/// </summary>
NResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.NEResize"/>
/// </summary>
NEResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.EResize"/>
/// </summary>
EResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.SEResize"/>
/// </summary>
SEResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.SResize"/>
/// </summary>
SResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.SWResize"/>
/// </summary>
SWResize,
/// <summary>
/// Corresponds to <see cref="StandardCursorShape.WResize"/>
/// </summary>
WResize,
/// <summary>
/// Special cursor shape indicating that <see cref="CustomCursorShape"/> is set and being used.
/// </summary>

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,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,19 @@ 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 +61,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 +83,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 +93,6 @@ public sealed class ColorSelectorSliders : Control
public ColorSelectorSliders()
{
IoCManager.InjectDependencies(this);
_topColorSlider = new ColorableSlider
{
HorizontalExpand = true,
@@ -122,10 +124,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 +153,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 +188,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 +200,6 @@ public sealed class ColorSelectorSliders : Control
rootBox.AddChild(headerBox);
headerBox.AddChild(_typeSelector);
headerBox.AddChild(_colorDescriptionLabel);
var bodyBox = new BoxContainer()
{
@@ -227,114 +241,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 +415,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

@@ -69,7 +69,7 @@ namespace Robust.Client.UserInterface.Controls
var itemHeight = 0f;
if (item.Icon != null)
{
itemHeight = item.IconSize.Y * item.IconScale;
itemHeight = item.IconSize.Y;
}
itemHeight = Math.Max(itemHeight, ActualFont.GetHeight(UIScale));
@@ -111,21 +111,21 @@ namespace Robust.Client.UserInterface.Controls
Recalculate();
}
public void AddItems(IEnumerable<string> texts, Texture? icon = null, bool selectable = true, object? metadata = null, float iconScale = 1)
public void AddItems(IEnumerable<string> texts, Texture? icon = null, bool selectable = true, object? metadata = null)
{
var items = new ValueList<Item>();
foreach (var text in texts)
{
items.Add(new Item(this) {Text = text, Icon = icon, IconScale = iconScale, Selectable = selectable, Metadata = metadata});
items.Add(new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata});
}
Add(items);
}
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null, float iconScale = 1)
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null)
{
var item = new Item(this) {Text = text, Icon = icon, IconScale = iconScale, Selectable = selectable, Metadata = metadata};
var item = new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata};
Add(item);
return item;
}
@@ -477,7 +477,7 @@ namespace Robust.Client.UserInterface.Controls
var itemHeight = 0f;
if (item.Icon != null)
{
itemHeight = item.IconSize.Y * item.IconScale;
itemHeight = item.IconSize.Y;
}
itemHeight = Math.Max(itemHeight, font.GetHeight(UIScale));
@@ -496,19 +496,19 @@ namespace Robust.Client.UserInterface.Controls
{
if (item.IconRegion.Size == Vector2.Zero)
{
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size * item.IconScale),
handle.DrawTextureRect(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
item.IconModulate);
}
else
{
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size * item.IconScale),
handle.DrawTextureRectRegion(item.Icon, UIBox2.FromDimensions(drawOffset, item.Icon.Size),
item.IconRegion, item.IconModulate);
}
}
if (item.Text != null)
{
var textBox = new UIBox2(contentBox.Left + item.IconSize.X * item.IconScale, contentBox.Top, contentBox.Right,
var textBox = new UIBox2(contentBox.Left + item.IconSize.X, contentBox.Top, contentBox.Right,
contentBox.Bottom);
DrawTextInternal(handle, item.Text, textBox);
}
@@ -722,7 +722,6 @@ namespace Robust.Client.UserInterface.Controls
public Texture? Icon { get; set; }
public UIBox2 IconRegion { get; set; }
public Color IconModulate { get; set; } = Color.White;
public float IconScale { get; set; } = 1;
public bool Selectable { get; set; } = true;
public bool TooltipEnabled { get; set; } = true;
public UIBox2? Region { get; set; }

View File

@@ -326,7 +326,7 @@ namespace Robust.Client.UserInterface.Controls
var parentSize = control.Parent?.Size ?? Vector2.Zero;
var anchorLeft = control.GetValue<float>(AnchorLeftProperty);
var anchorTop = control.GetValue<float>(AnchorTopProperty);
var anchorTop = control.GetValue<float>(AnchorBottomProperty);
var anchorRight = control.GetValue<float>(AnchorRightProperty);
var anchorBottom = control.GetValue<float>(AnchorBottomProperty);

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

@@ -119,12 +119,9 @@ namespace Robust.Client.UserInterface.CustomControls
case DragMode.Bottom | DragMode.Left:
case DragMode.Top | DragMode.Right:
cursor = CursorShape.NESWResize;
break;
case DragMode.Bottom | DragMode.Right:
case DragMode.Top | DragMode.Left:
cursor = CursorShape.NWSEResize;
cursor = CursorShape.Crosshair;
break;
}

View File

@@ -5,8 +5,8 @@
MinSize="350 200">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<LineEdit Name="SearchBar" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc window-search-bar-placeholder}"/>
<Button Name="ClearButton" Access="Public" Disabled="True" Text="{Loc window-clear-button}" />
<LineEdit Name="SearchBar" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc entity-spawn-window-search-bar-placeholder}"/>
<Button Name="ClearButton" Access="Public" Disabled="True" Text="{Loc entity-spawn-window-clear-button}" />
</BoxContainer>
<ScrollContainer Name="PrototypeScrollContainer" Access="Public" MinSize="200 0" VerticalExpand="True">
<PrototypeListContainer Name="PrototypeList" Access="Public"/>

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

@@ -5,8 +5,8 @@
MinSize="300 200">
<BoxContainer Orientation="Vertical">
<BoxContainer Orientation="Horizontal">
<LineEdit Name="SearchBar" Access="Public" HorizontalExpand="True" PlaceHolder="{Loc window-search-bar-placeholder}"/>
<Button Name="ClearButton" Access="Public" Text="{Loc window-clear-button}"/>
<LineEdit Name="SearchBar" Access="Public" HorizontalExpand="True" PlaceHolder="Search"/>
<Button Name="ClearButton" Access="Public" Text="Clear"/>
</BoxContainer>
<ItemList Name="TileList" Access="Public" VerticalExpand="True"/>
<BoxContainer Orientation="Horizontal">

View File

@@ -202,14 +202,7 @@ internal partial class UserInterfaceManager
return;
}
var shape = MapCursorShape(cursorTarget.DefaultCursorShape);
_clyde.SetCursor(_clyde.GetStandardCursor(shape));
}
private static StandardCursorShape MapCursorShape(Control.CursorShape shape)
{
return shape switch
var shape = cursorTarget.DefaultCursorShape switch
{
Control.CursorShape.Arrow => StandardCursorShape.Arrow,
Control.CursorShape.IBeam => StandardCursorShape.IBeam,
@@ -217,21 +210,10 @@ internal partial class UserInterfaceManager
Control.CursorShape.Crosshair => StandardCursorShape.Crosshair,
Control.CursorShape.VResize => StandardCursorShape.VResize,
Control.CursorShape.HResize => StandardCursorShape.HResize,
Control.CursorShape.Progress => StandardCursorShape.Progress,
Control.CursorShape.NWSEResize => StandardCursorShape.NWSEResize,
Control.CursorShape.NESWResize => StandardCursorShape.NESWResize,
Control.CursorShape.Move => StandardCursorShape.Move,
Control.CursorShape.NotAllowed => StandardCursorShape.NotAllowed,
Control.CursorShape.NWResize => StandardCursorShape.NWResize,
Control.CursorShape.NResize => StandardCursorShape.NResize,
Control.CursorShape.NEResize => StandardCursorShape.NEResize,
Control.CursorShape.EResize => StandardCursorShape.EResize,
Control.CursorShape.SEResize => StandardCursorShape.SEResize,
Control.CursorShape.SResize => StandardCursorShape.SResize,
Control.CursorShape.SWResize => StandardCursorShape.SWResize,
Control.CursorShape.WResize => StandardCursorShape.WResize,
_ => StandardCursorShape.Arrow
};
_clyde.SetCursor(_clyde.GetStandardCursor(shape));
}
public void MouseWheel(MouseWheelEventArgs args)

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,85 +1,38 @@
using System.Linq;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
namespace Robust.Client.ViewVariables.Editors;
internal sealed class VVPropEditorProtoId<T> : VVPropEditor where T : class, IPrototype
{
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private ViewVariablesAddWindow? _addWindow;
private LineEdit? _lineEdit;
protected override Control MakeUI(object? value)
{
// ID LineEdit
_lineEdit = new LineEdit
var lineEdit = new LineEdit
{
Text = (ProtoId<T>)(value ?? ""),
PlaceHolder = _loc.GetString("vv-protoid-id-placeholder"),
Text = (ProtoId<T>) (value ?? ""),
Editable = !ReadOnly,
HorizontalExpand = true,
};
if (!ReadOnly)
{
_lineEdit.OnTextEntered += e =>
lineEdit.OnTextEntered += e =>
{
SetValue(e.Text);
var id = (ProtoId<T>)e.Text;
if (!_protoManager.HasIndex(id))
{
return;
}
ValueChanged(id);
};
}
// Select button
var selectButton = new Button
{
Text = _loc.GetString("vv-protoid-select-button-label"),
Disabled = ReadOnly,
};
selectButton.OnPressed += OnListButtonPressed;
// Container
var hBox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
Children =
{
_lineEdit,
selectButton,
}
};
return hBox;
}
private void OnListButtonPressed(BaseButton.ButtonEventArgs args)
{
_addWindow?.Close();
var list = _protoManager.EnumeratePrototypes<T>().Select(p => p.ID);
_addWindow = new ViewVariablesAddWindow(list, _loc.GetString("vv-protoid-addwindow-title"));
_addWindow.AddButtonPressed += OnAddButtonPressed;
_addWindow.OpenCentered();
}
private void OnAddButtonPressed(ViewVariablesAddWindow.AddButtonPressedEventArgs args)
{
_lineEdit?.SetText(args.Entry);
_addWindow?.Close();
SetValue(args.Entry);
}
private void SetValue(string value)
{
var proto = (ProtoId<T>)value;
if (_protoManager.HasIndex(proto))
ValueChanged(proto, false);
return lineEdit;
}
}

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

@@ -1,5 +1,4 @@
using System.Text;
using Robust.Shared.Analyzers;
using Robust.Shared.Analyzers;
using Robust.Shared.Collections;
namespace Robust.Packaging.AssetProcessing;
@@ -200,17 +199,6 @@ public class AssetPass
/// </summary>
public void InjectFileFromMemory(string path, byte[] memory) => InjectFile(new AssetFileMemory(path, memory));
/// <summary>
/// Convenience method to <see cref="InjectFile"/> a <see cref="AssetFileMemory"/>.
/// </summary>
public void InjectFileFromMemory(string path, ReadOnlySpan<byte> memory) => InjectFile(new AssetFileMemory(path, memory.ToArray()));
/// <summary>
/// Convenience method to <see cref="InjectFile"/> a <see cref="AssetFileMemory"/> made from text.
/// </summary>
public void InjectFileFromText(string path, string text) =>
InjectFile(new AssetFileMemory(path, Encoding.UTF8.GetBytes(text)));
/// <summary>
/// Called when all depended-on passes have finished processing, meaning no more files will come in.
/// </summary>

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

@@ -1,99 +0,0 @@
using Robust.Shared.Utility;
namespace Robust.Packaging.AssetProcessing.Passes;
public sealed class AssetPassMergeTextDirectories : AssetPass
{
private readonly ResPath _prefixPath;
private readonly string _extension;
private readonly Func<string, string>? _formatterHead;
private readonly Func<string, string>? _formatterTail;
private readonly Dictionary<ResPath, DirectoryDatum> _data = new();
public AssetPassMergeTextDirectories(
string prefixPath,
string extension,
Func<string, string>? formatterHead = null,
Func<string, string>? formatterTail = null)
{
_prefixPath = new ResPath(prefixPath);
_extension = extension;
_formatterHead = formatterHead;
_formatterTail = formatterTail;
}
protected override AssetFileAcceptResult AcceptFile(AssetFile file)
{
var resPath = new ResPath(file.Path);
if (!resPath.TryRelativeTo(_prefixPath, out _))
return AssetFileAcceptResult.Pass;
if (resPath.Extension != _extension)
return AssetFileAcceptResult.Pass;
var directory = resPath.Directory;
lock (_data)
{
var datum = _data.GetOrNew(directory);
datum.Files.Add(file);
}
return AssetFileAcceptResult.Consumed;
}
protected override void AcceptFinished()
{
RunJob(() =>
{
lock (_data)
{
var ms = new MemoryStream();
var writer = new StreamWriter(ms, EncodingHelpers.UTF8);
foreach (var (directory, datum) in _data)
{
ms.Position = 0;
var mergedFile = directory / $"__merged.{_extension}";
WriteForDatum(datum, writer);
writer.Flush();
SendFileFromMemory(mergedFile.ToString(), ms.GetBuffer()[..(int)ms.Position]);
}
_data.Clear();
}
});
}
private void WriteForDatum(DirectoryDatum datum, StreamWriter writer)
{
foreach (var file in datum.Files.OrderBy(f => f.Path, StringComparer.Ordinal))
{
if (_formatterHead != null)
{
writer.Write(_formatterHead(file.Path));
writer.Write('\n');
}
using var stream = file.Open();
using var reader = new StreamReader(stream);
while (reader.ReadLine() is { } line)
{
writer.Write(line);
writer.Write('\n');
}
if (_formatterTail != null)
{
writer.Write(_formatterTail(file.Path));
writer.Write('\n');
}
}
}
private sealed class DirectoryDatum
{
public readonly List<AssetFile> Files = [];
}
}

View File

@@ -1,8 +1,11 @@
using System.Text.RegularExpressions;
using Robust.Shared.Maths;
using Robust.Shared.Resources;
using Robust.Shared.Utility;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.PixelFormats;
namespace Robust.Packaging.AssetProcessing.Passes;
@@ -12,21 +15,13 @@ namespace Robust.Packaging.AssetProcessing.Passes;
/// Packs .rsi bundles into .rsic files,
/// that are single pre-atlassed PNG files with JSON metadata embedded in the PNG header.
/// </summary>
public sealed class AssetPassPackRsis : AssetPass
internal sealed class AssetPassPackRsis : AssetPass
{
private readonly Dictionary<string, RsiDat> _foundRsis = new();
private static readonly Regex RegexMetaJson = new(@"^(.+)\.rsi/meta\.json$");
private static readonly Regex RegexPng = new(@"^(.+)\.rsi/(.+)\.png$");
private readonly Configuration _imageConfiguration;
public AssetPassPackRsis()
{
_imageConfiguration = Configuration.Default.Clone();
_imageConfiguration.PreferContiguousImageBuffers = true;
}
protected override AssetFileAcceptResult AcceptFile(AssetFile file)
{
if (!file.Path.Contains(".rsi/"))
@@ -74,27 +69,12 @@ public sealed class AssetPassPackRsis : AssetPass
// Console.WriteLine($"Packing RSI: {key}");
var result = PackRsi($"{key}.rsi", dat);
if (result == null)
{
// Don't rsic pack this one.
SkipRsiPack(dat);
return;
}
SendFile(result);
});
}
}
private void SkipRsiPack(RsiDat dat)
{
SendFile(dat.MetaJson!);
foreach (var file in dat.StatesFound.Values)
{
SendFile(file);
}
}
private AssetFile? PackRsi(string rsiPath, RsiDat dat)
private static AssetFile PackRsi(string rsiPath, RsiDat dat)
{
RsiLoading.RsiMetadata metadata;
string metaJson;
@@ -107,30 +87,100 @@ public sealed class AssetPassPackRsis : AssetPass
metaJson = sr.ReadToEnd();
}
if (!metadata.Rsic)
return null;
var frameCounts = RsiLoading.CalculateFrameCounts(metadata);
var images = RsiLoading.LoadImages(metadata, _imageConfiguration, name => dat.StatesFound[name].Open());
try
// Check for duplicate states
for (var i = 0; i < metadata.States.Length; i++)
{
using var sheet = RsiLoading.GenerateAtlas(metadata, frameCounts, images, _imageConfiguration, out _);
var ms = new MemoryStream();
sheet.Metadata.GetPngMetadata().TextData.Add(new PngTextData(RsiLoading.RsicPngField, metaJson, "", ""));
sheet.SaveAsPng(ms);
var stateId = metadata.States[i].StateId;
Logger?.Verbose($"Done packing {rsiPath}");
return new AssetFileMemory($"{rsiPath}c", ms.ToArray());
}
finally
{
foreach (var image in images)
for (int j = i + 1; j < metadata.States.Length; j++)
{
image.Dispose();
if (stateId == metadata.States[j].StateId)
throw new RSILoadException($"RSI '{rsiPath}' has a duplicate stateId '{stateId}'.");
}
}
var stateCount = metadata.States.Length;
var toAtlas = new StateReg[stateCount];
var frameSize = metadata.Size;
// Do every state.
for (var index = 0; index < metadata.States.Length; index++)
{
ref var reg = ref toAtlas[index];
var stateObject = metadata.States[index];
// Load image from disk.
var texFile = dat.StatesFound[stateObject.StateId];
using (var stream = texFile.Open())
{
reg.Src = Image.Load<Rgba32>(stream);
}
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
var regDims = $"{reg.Src.Width}x{reg.Src.Height}";
var iconDims = $"{frameSize.X}x{frameSize.Y}";
throw new RSILoadException(
$"State '{stateObject.StateId}' image size ({regDims}) is not a multiple of the icon size ({iconDims}).");
}
// Load all frames into a list so we can operate on it more sanely.
reg.TotalFrameCount = stateObject.Delays.Sum(delayList => delayList.Length);
}
// Poorly hacked in texture atlas support here.
var totalFrameCount = toAtlas.Sum(p => p.TotalFrameCount);
// Generate atlas.
var dimensionX = (int) MathF.Ceiling(MathF.Sqrt(totalFrameCount));
var dimensionY = (int) MathF.Ceiling((float) totalFrameCount / dimensionX);
var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheetIndex = 0;
for (var index = 0; index < toAtlas.Length; index++)
{
ref var reg = ref toAtlas[index];
// Blit all the frames over.
for (var i = 0; i < reg.TotalFrameCount; i++)
{
var srcWidth = (reg.Src.Width / frameSize.X);
var srcColumn = i % srcWidth;
var srcRow = i / srcWidth;
var srcPos = (srcColumn * frameSize.X, srcRow * frameSize.Y);
var sheetColumn = (sheetIndex + i) % dimensionX;
var sheetRow = (sheetIndex + i) / dimensionX;
var sheetPos = (sheetColumn * frameSize.X, sheetRow * frameSize.Y);
var srcBox = UIBox2i.FromDimensions(srcPos, frameSize);
ImageOps.Blit(reg.Src, srcBox, sheet, sheetPos);
}
sheetIndex += reg.TotalFrameCount;
}
for (var i = 0; i < toAtlas.Length; i++)
{
ref var reg = ref toAtlas[i];
reg.Src.Dispose();
}
var ms = new MemoryStream();
sheet.Metadata.GetPngMetadata().TextData.Add(new PngTextData("Description", metaJson, "", ""));
sheet.SaveAsPng(ms);
sheet.Dispose();
return new AssetFileMemory($"{rsiPath}c", ms.ToArray());
}
internal struct StateReg
{
public Image<Rgba32> Src;
public int TotalFrameCount;
}
private sealed class RsiDat

View File

@@ -15,9 +15,6 @@ public sealed class RobustClientAssetGraph
public AssetPassPipe PresetPasses { get; }
public AssetPassPipe Output { get; }
public AssetPassNormalizeText NormalizeText { get; }
public AssetPassMergeTextDirectories MergePrototypeDirectories { get; }
public AssetPassMergeTextDirectories MergeLocaleDirectories { get; }
public AssetPassPackRsis PackRsis { get; }
/// <summary>
/// Collection of all passes in this preset graph.
@@ -33,41 +30,11 @@ public sealed class RobustClientAssetGraph
PresetPasses = new AssetPassPipe { Name = "RobustClientAssetGraphPresetPasses" };
Output = new AssetPassPipe { Name = "RobustClientAssetGraphOutput", CheckDuplicates = true };
NormalizeText = new AssetPassNormalizeText { Name = "RobustClientAssetGraphNormalizeText" };
MergePrototypeDirectories = new AssetPassMergeTextDirectories(
"Prototypes",
"yml",
// Separate each merged YAML file with a document to provide proper isolation.
formatterHead: file => $"--- # BEGIN {file}",
formatterTail: file => $"# END {file}")
{
Name = "RobustClientAssetGraphMergePrototypeDirectories"
};
MergeLocaleDirectories = new AssetPassMergeTextDirectories(
"Locale",
"ftl",
formatterHead: file => $"# BEGIN {file}",
formatterTail: file => $"# END {file}")
{
Name = "RobustClientAssetGraphMergeLocaleDirectories"
};
PackRsis = new AssetPassPackRsis
{
Name = "RobustClientAssetGraphPackRsis",
};
PresetPasses.AddDependency(Input);
PackRsis.AddDependency(PresetPasses).AddBefore(NormalizeText);
MergePrototypeDirectories.AddDependency(PresetPasses).AddBefore(NormalizeText);
MergeLocaleDirectories.AddDependency(PresetPasses).AddBefore(NormalizeText);
NormalizeText.AddDependency(PresetPasses).AddBefore(Output);
// RSI packing goes through text normalization,
// to catch meta.jsons that have been skipped by the RSI packing pass.
NormalizeText.AddDependency(PackRsis).AddBefore(Output);
Output.AddDependency(PresetPasses);
Output.AddDependency(NormalizeText);
Output.AddDependency(MergePrototypeDirectories);
Output.AddDependency(MergeLocaleDirectories);
Output.AddDependency(PackRsis);
AllPasses = new AssetPass[]
{
@@ -75,9 +42,6 @@ public sealed class RobustClientAssetGraph
PresetPasses,
Output,
NormalizeText,
MergePrototypeDirectories,
MergeLocaleDirectories,
PackRsis
};
}
}

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(
ITypeSymbol symbol,
ITypeSymbol 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,11 +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 const string IdAutoGenStateAttributeMissing = "RA0040";
public const string IdAutoGenStateParamMissing = "RA0041";
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,48 +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;
}
}
/// <summary>
/// If <paramref name="type"/> is a Nullable{T}, returns the <see cref="ITypeSymbol"/> of the underlying type.
/// Otherwise, returns <paramref name="type"/>.
/// </summary>
// Modified from https://www.meziantou.net/working-with-types-in-a-roslyn-analyzer.htm
public static ITypeSymbol GetNullableUnderlyingTypeOrSelf(ITypeSymbol type)
{
if (type is INamedTypeSymbol namedType && namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
{
return namedType.TypeArguments[0];
}
return type;
}
}

View File

@@ -1,4 +1,4 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -85,17 +85,12 @@ public sealed class ComponentPauseGenerator : IIncrementalGenerator
var invalid = false;
var nullable = false;
var dictionary = false;
if (namedType.Name != "TimeSpan")
{
if (namedType is { Name: "Nullable", TypeArguments: [{Name: "TimeSpan"}] })
{
nullable = true;
}
else if (namedType is { Name: "Dictionary", TypeArguments: [{}, {Name: "TimeSpan"}]})
{
dictionary = true;
}
else
{
invalid = true;
@@ -106,7 +101,7 @@ public sealed class ComponentPauseGenerator : IIncrementalGenerator
if (AttributeHelper.HasAttribute(member, AutoNetworkFieldAttributeName, out var _))
dirty = true;
fieldBuilder.Add(new FieldInfo(member.Name, nullable, invalid, dictionary, member.Locations[0]));
fieldBuilder.Add(new FieldInfo(member.Name, nullable, invalid, member.Locations[0]));
}
return new ComponentInfo(
@@ -186,13 +181,6 @@ public sealed class ComponentPauseGenerator : IIncrementalGenerator
component.{field.Name} = component.{field.Name}.Value + args.PausedTime;
""");
}
else if (field.Dictionary)
{
builder.AppendLine($"""
foreach (var key in component.{field.Name}.Keys)
component.{field.Name}[key] += args.PausedTime;
""");
}
else
{
builder.AppendLine($" component.{field.Name} += args.PausedTime;");
@@ -259,7 +247,7 @@ public sealed class ComponentPauseGenerator : IIncrementalGenerator
bool NotComponent,
Location Location);
public sealed record FieldInfo(string Name, bool Nullable, bool Invalid, bool Dictionary, Location Location);
public sealed record FieldInfo(string Name, bool Nullable, bool Invalid, Location Location);
public sealed record AllFieldInfo(string Name, string ParentDisplayName, Location Location);
}

View File

@@ -154,7 +154,7 @@ namespace Robust.Server.Console.Commands
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return LoadMap.GetCompletionResult(shell, args, _resource, Loc);
return LoadMap.GetCompletionResult(shell, args, _resource);
}
}
@@ -235,24 +235,24 @@ namespace Robust.Server.Console.Commands
public override string Command => "loadmap";
public static CompletionResult GetCompletionResult(IConsoleShell shell, string[] args, IResourceManager resource, ILocalizationManager loc)
public static CompletionResult GetCompletionResult(IConsoleShell shell, string[] args, IResourceManager resource)
{
switch (args.Length)
{
case 1:
return CompletionResult.FromHint(loc.GetString("cmd-hint-savemap-id"));
return CompletionResult.FromHint(Loc.GetString("cmd-hint-savemap-id"));
case 2:
var opts = CompletionHelper.UserFilePath(args[1], resource.UserData)
.Concat(CompletionHelper.ContentFilePath(args[1], resource));
return CompletionResult.FromHintOptions(opts, loc.GetString("cmd-hint-savemap-path"));
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-savemap-path"));
case 3:
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-x-position"));
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-x-position"));
case 4:
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-y-position"));
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-y-position"));
case 5:
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-rotation"));
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-rotation"));
case 6:
return CompletionResult.FromHint(loc.GetString("cmd-hint-loadmap-uids"));
return CompletionResult.FromHint(Loc.GetString("cmd-hint-loadmap-uids"));
}
return CompletionResult.Empty;
@@ -260,7 +260,7 @@ namespace Robust.Server.Console.Commands
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
return GetCompletionResult(shell, args, _resource, Loc);
return GetCompletionResult(shell, args, _resource);
}
public override void Execute(IConsoleShell shell, string argStr, string[] args)

View File

@@ -0,0 +1,112 @@
using System;
using System.Numerics;
using Robust.Server.GameObjects;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Systems;
namespace Robust.Server.Console.Commands;
public sealed class ScaleCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entityManager = default!;
public override string Command => "scale";
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
switch (args.Length)
{
case 1:
return CompletionResult.FromOptions(CompletionHelper.NetEntities(args[0], entManager: _entityManager));
case 2:
return CompletionResult.FromHint(Loc.GetString("cmd-hint-float"));
default:
return CompletionResult.Empty;
}
}
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteError($"Insufficient number of args supplied: expected 2 and received {args.Length}");
return;
}
if (!NetEntity.TryParse(args[0], out var netEntity))
{
shell.WriteError($"Unable to find entity {args[0]}");
return;
}
if (!float.TryParse(args[1], out var scale))
{
shell.WriteError($"Invalid scale supplied of {args[0]}");
return;
}
if (scale < 0f)
{
shell.WriteError($"Invalid scale supplied that is negative!");
return;
}
// Event for content to use
// We'll just set engine stuff here
var physics = _entityManager.System<SharedPhysicsSystem>();
var appearance = _entityManager.System<AppearanceSystem>();
var uid = _entityManager.GetEntity(netEntity);
_entityManager.EnsureComponent<ScaleVisualsComponent>(uid);
var @event = new ScaleEntityEvent();
_entityManager.EventBus.RaiseLocalEvent(uid, ref @event);
var appearanceComponent = _entityManager.EnsureComponent<AppearanceComponent>(uid);
if (!appearance.TryGetData<Vector2>(uid, ScaleVisuals.Scale, out var oldScale, appearanceComponent))
oldScale = Vector2.One;
appearance.SetData(uid, ScaleVisuals.Scale, oldScale * scale, appearanceComponent);
if (_entityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
foreach (var (id, fixture) in manager.Fixtures)
{
switch (fixture.Shape)
{
case EdgeShape edge:
physics.SetVertices(uid, id, fixture,
edge,
edge.Vertex0 * scale,
edge.Vertex1 * scale,
edge.Vertex2 * scale,
edge.Vertex3 * scale, manager);
break;
case PhysShapeCircle circle:
physics.SetPositionRadius(uid, id, fixture, circle, circle.Position * scale, circle.Radius * scale, manager);
break;
case PolygonShape poly:
var verts = poly.Vertices;
for (var i = 0; i < poly.VertexCount; i++)
{
verts[i] *= scale;
}
physics.SetVertices(uid, id, fixture, poly, verts, manager);
break;
default:
throw new NotImplementedException();
}
}
}
}
[ByRefEvent]
public readonly record struct ScaleEntityEvent(EntityUid Uid) {}
}

View File

@@ -32,7 +32,7 @@ internal sealed partial class PvsSystem
if (component.Deleted || !component.Initialized)
{
Log.Error($"Entity manager returned deleted or uninitialized component of type {component.GetType()} on entity {ToPrettyString(entityUid)} while generating entity state data for {player?.Name ?? "replay"}");
Log.Error("Entity manager returned deleted or uninitialized components while sending entity data");
continue;
}

View File

@@ -103,7 +103,6 @@ namespace Robust.Server.Player
if (!TryGetSessionById(user, out var session))
return;
RemoveSession(session.UserId);
SetStatus(session, SessionStatus.Disconnected);
SetAttachedEntity(session, null, out _, true);
@@ -113,6 +112,7 @@ namespace Robust.Server.Player
viewSys.RemoveViewSubscriber(eye, session);
}
RemoveSession(session.UserId);
PlayerCountMetric.Set(PlayerCount);
Dirty();
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Robust.Server.ViewVariables.Traits;
using Robust.Shared.GameObjects;
using Robust.Shared.Log;
@@ -136,10 +135,6 @@ namespace Robust.Server.ViewVariables
dynamic kv = value;
value = kvPair.Key ? kv.Key : kv.Value;
break;
case ViewVariablesTupleIndexSelector indexSelector
when value is ITuple tuple:
value = indexSelector.Index <= tuple.Length - 1 ? tuple[indexSelector.Index] : null;
break;
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Robust.Server.ViewVariables.Traits;
using Robust.Shared.Audio;
using Robust.Shared.GameObjects;
@@ -142,19 +141,6 @@ namespace Robust.Server.ViewVariables
};
}
// Handle ValueTuples
if (typeof(ITuple).IsAssignableFrom(valType))
{
var tuple = (ITuple)value;
var items = new object?[tuple.Length];
for (var i = 0; i < tuple.Length; i++)
{
items[i] = MakeValueNetSafe(tuple[i]);
}
return new ViewVariablesBlobMembers.ServerTupleToken { Items = items };
}
// Can't send this value type over the wire.
return new ViewVariablesBlobMembers.ServerValueTypeToken
{

View File

@@ -5,7 +5,6 @@ using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.SymbolDisplayFormat;
using static Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions;
using Robust.Roslyn.Shared;
// Yes dude I know this source generator isn't incremental, I'll fix it eventually.
#pragma warning disable RS1035
@@ -36,7 +35,6 @@ namespace Robust.Shared.CompNetworkGenerator
private const string GlobalDictionaryName = "global::System.Collections.Generic.Dictionary<TKey, TValue>";
private const string GlobalHashSetName = "global::System.Collections.Generic.HashSet<T>";
private const string GlobalListName = "global::System.Collections.Generic.List<T>";
private const string GlobalIRobustCloneableName = "global::Robust.Shared.Serialization.IRobustCloneable";
private static readonly SymbolDisplayFormat FullNullableFormat =
FullyQualifiedFormat.WithMiscellaneousOptions(IncludeNullableReferenceTypeModifier);
@@ -49,7 +47,7 @@ namespace Robust.Shared.CompNetworkGenerator
var componentName = classSymbol.Name;
var stateName = $"{componentName}_AutoState";
var members = TypeSymbolHelper.GetAllMembersIncludingInherited(classSymbol);
var members = classSymbol.GetMembers();
var fields = new List<(ITypeSymbol Type, string FieldName)>();
var fieldAttr = comp.GetTypeByMetadataName(MemberAttributeName);
@@ -377,39 +375,7 @@ namespace Robust.Shared.CompNetworkGenerator
stateFields.Append($@"
public {networkedType} {name} = default!;");
if (ImplementsInterface(type, GlobalIRobustCloneableName))
{
getField = $"component.{name}";
cast = $"({castString})";
var nullCast = nullable ? castString.Substring(0, castString.Length - 1) : castString;
if (nullable)
{
handleStateSetters.Append($@"
component.{name} = state.{name} == null ? null! : state.{name}.Clone();");
deltaHandleFields.Append($@"
var {name}Value = {cast} {fieldHandleValue};
if ({name}Value == null)
component.{name} = null!;
else
component.{name} = {nullCast}({name}Value.Clone());");
shallowClone.Append($@"
{name} = this.{name},");
deltaApply.Add($"fullState.{name} = {name} == null ? null! : {name}.Clone();");
}
else
{
handleStateSetters.Append($@"
component.{name} = state.{name}.Clone();");
deltaHandleFields.Append($@"
component.{name} = {cast}({fieldHandleValue}.Clone());");
shallowClone.Append($@"
{name} = this.{name},");
deltaApply.Add($"fullState.{name} = {name}.Clone();");
}
}
else if (IsCloneType(type))
if (IsCloneType(type))
{
getField = $"component.{name}";
cast = $"({castString})";
@@ -716,7 +682,7 @@ public partial class {componentName}{deltaInterface}
if (relevantAttribute == null)
{
foreach (var mem in TypeSymbolHelper.GetAllMembersIncludingInherited(typeSymbol))
foreach (var mem in typeSymbol.GetMembers())
{
var attribute = mem.GetAttributes().FirstOrDefault(a =>
a.AttributeClass != null &&
@@ -792,19 +758,5 @@ public partial class {componentName}{deltaInterface}
_ => false
};
}
private static bool ImplementsInterface(ITypeSymbol type, string interfaceName)
{
foreach (var interfaceType in type.AllInterfaces)
{
if (interfaceType.ToDisplayString(FullyQualifiedFormat).Contains(interfaceName)
|| interfaceType.ConstructedFrom.ToDisplayString(FullyQualifiedFormat).Contains(interfaceName))
{
return true;
}
}
return false;
}
}
}

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