mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26263f2632 | ||
|
|
8efcbc1cc9 | ||
|
|
10b191dff8 | ||
|
|
92ab3fb64b | ||
|
|
92a0c14383 | ||
|
|
5aaf6d0994 | ||
|
|
15f4da5e4b | ||
|
|
a528e87f3d | ||
|
|
4af67b1394 | ||
|
|
e8de9b98d3 | ||
|
|
a0ffeff4e5 | ||
|
|
07654564f3 | ||
|
|
7fbf8d05eb | ||
|
|
c12971cb9b | ||
|
|
2b6381c332 | ||
|
|
8149a3aaad | ||
|
|
4b39bf1f2d | ||
|
|
53394fff44 | ||
|
|
4bed20e070 | ||
|
|
e4b6af09f1 | ||
|
|
1ef29ae781 | ||
|
|
5686950421 | ||
|
|
2b54aa8984 | ||
|
|
859f150404 | ||
|
|
558f4b5b16 | ||
|
|
108366152b | ||
|
|
c55327e1d1 | ||
|
|
370e0fa0d0 | ||
|
|
4f9f82c20c | ||
|
|
43670a8ddd | ||
|
|
250313e1ed | ||
|
|
18d511d4b6 | ||
|
|
da9e5fb370 | ||
|
|
e3bac382ce | ||
|
|
179c6790b6 | ||
|
|
a7db5634df | ||
|
|
2daa86ff59 | ||
|
|
d6803f5294 | ||
|
|
bdcc0f7b9d | ||
|
|
ce49aa47cf | ||
|
|
c7d48b2526 | ||
|
|
6bb7f5b4ef | ||
|
|
2974310450 | ||
|
|
2694dce076 | ||
|
|
8960d1d995 | ||
|
|
0a4683d33e | ||
|
|
379bcfabe0 | ||
|
|
1d91838166 | ||
|
|
a5d4b8096f | ||
|
|
a77eee5658 | ||
|
|
156187a0dd | ||
|
|
852f002f59 | ||
|
|
9dc49c1904 | ||
|
|
1995b13e5d | ||
|
|
f985d10ed9 | ||
|
|
ae6cebbfbb | ||
|
|
ef0bc1a2e4 | ||
|
|
72ba484f5b | ||
|
|
a70e511fcb | ||
|
|
e7f9e95525 | ||
|
|
bd908f9db6 | ||
|
|
f8cb1729a3 | ||
|
|
fd9d5c8aa8 | ||
|
|
4677296934 | ||
|
|
708f5dd376 | ||
|
|
4a06acda32 | ||
|
|
e7beb2032b | ||
|
|
c7bd75f800 |
@@ -45,17 +45,20 @@
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
|
||||
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageVersion Include="System.Memory" Version="4.5.5" />
|
||||
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
|
||||
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.22621.5" />
|
||||
<PackageVersion Include="TerraFX.Interop.Xlib" Version="6.4.0" />
|
||||
<PackageVersion Include="VorbisPizza" Version="1.3.0" />
|
||||
<PackageVersion Include="YamlDotNet" Version="13.7.1" />
|
||||
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||
<PackageVersion Include="PolySharp" Version="1.14.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -24,12 +24,16 @@
|
||||
<RobustInjectorsConfiguration>$(Configuration)</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'DebugOpt'">Debug</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(Configuration)' == 'Tools'">Release</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true' And '$(RuntimeIdentifier)' != ''">$(RobustInjectorsConfiguration)_$(RuntimeIdentifier)</RobustInjectorsConfiguration>
|
||||
<RobustInjectorsConfiguration Condition="'$(UseArtifactsOutput)' == 'true'">$(RobustInjectorsConfiguration.ToLower())</RobustInjectorsConfiguration>
|
||||
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' != 'true'">$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
|
||||
<CompileRobustXamlTaskAssemblyFile Condition="'$(UseArtifactsOutput)' == 'true'">$(MSBuildThisFileDirectory)\..\..\artifacts\bin\Robust.Client.Injectors\$(RobustInjectorsConfiguration)\Robust.Client.Injectors.dll</CompileRobustXamlTaskAssemblyFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<UsingTask
|
||||
Condition="'$(_RobustUseExternalMSBuild)' != 'true' And $(DesignTimeBuild) != true"
|
||||
TaskName="CompileRobustXamlTask"
|
||||
AssemblyFile="$(MSBuildThisFileDirectory)\..\Robust.Client.Injectors\bin\$(RobustInjectorsConfiguration)\netstandard2.0\Robust.Client.Injectors.dll"/>
|
||||
AssemblyFile="$(CompileRobustXamlTaskAssemblyFile)"/>
|
||||
<Target
|
||||
Name="CompileRobustXaml"
|
||||
Condition="Exists('@(IntermediateAssembly)')"
|
||||
|
||||
170
RELEASE-NOTES.md
170
RELEASE-NOTES.md
@@ -54,6 +54,176 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 214.1.2
|
||||
|
||||
|
||||
## 214.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed connection denial always causing redial.
|
||||
|
||||
|
||||
## 214.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added the `pvs_override_info` command for debugging PVS overrides.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix VV for prototype structs.
|
||||
* Fix audio limits for clientside audio.
|
||||
|
||||
|
||||
## 214.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `NetStructuredDisconnectMessages` has received a complete overhaul and has been moved to `NetDisconnectMessage`. The API is no longer designed such that consumers must pass around JSON nodes, as they are not in sandbox (and clunky).
|
||||
|
||||
### New features
|
||||
|
||||
* Add a basic default concurrent audio limit of 16 for a single filepath to avoid overflowing audio sources.
|
||||
* `NetConnectingArgs.Deny()` can now pass along structured data that will be received by the client.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed cursor position bugs when an empty `TextEdit` has a multi-line place holder.
|
||||
* Fixed empty `TextEdit` throwing exception if cursor is moved left.
|
||||
|
||||
|
||||
## 213.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Remove obsoleted BaseContainer methods.
|
||||
|
||||
### New features
|
||||
|
||||
* Add EntityManager.RaiseSharedEvent where the event won't go to the attached client but will be predicted locally on their end.
|
||||
* Add GetEntitiesInRange override that takes in EntityCoordinates and an EntityUid hashset.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Check if a sprite entity is deleted before drawing in SpriteView.
|
||||
|
||||
|
||||
## 212.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add IsHardCollidable to SharedPhysicsSystem to determine if 2 entities would collide.
|
||||
|
||||
### Other
|
||||
|
||||
* Double the default maximum replay size.
|
||||
|
||||
|
||||
## 212.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add nullable methods for TryIndex / HasIndex on IPrototypeManager.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix TextureRect alignment where the strech mode is KeepCentered.
|
||||
|
||||
|
||||
## 212.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix passing array by `this` instead of by `ref`.
|
||||
|
||||
|
||||
## 212.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Change Collapsible controls default orientations to Vertical.
|
||||
|
||||
### New features
|
||||
|
||||
* Expose the Label control for Collapsible controls.
|
||||
* Add GetGridPosition that considers physics center-of-mass.
|
||||
* Add TileToVector methods to get the LocalPosition of tile-coords (taking into account tile size).
|
||||
* Add some more helper methods to PVS filters around EntityUids.
|
||||
* Add support for Dictionary AutoNetworkedFields.
|
||||
* Add EnsureLength method for arrays.
|
||||
* Add PushMarkup to FormattedMessage.
|
||||
* Add DrawPrimitives overload for `List<Vector2>`
|
||||
* Add more ValueList ctors that are faster.
|
||||
* Add ToMapCoordinates method for NetCoordinates.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove ISerializationHooks obsoletion as they are useful in some rare cases.
|
||||
|
||||
### Internal
|
||||
|
||||
* Bump max pool size for robust jobs.
|
||||
|
||||
|
||||
## 211.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix TextureRect scaling not handling UIScale correctly.
|
||||
|
||||
|
||||
## 211.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix GridChunkEnumerator on maps.
|
||||
|
||||
|
||||
## 211.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Moved ChunkIndicesEnumerator to engine and to a re-useable namespace at Robust.Shared/Maps.
|
||||
|
||||
### New features
|
||||
|
||||
* Added an Enlarged method for Box2Rotated.
|
||||
|
||||
### Internal
|
||||
|
||||
* Significantly optimise ChunkEnumerator / FindGridsIntersecting in certain use cases by intersecting the grid's AABB with the local AABB to avoid iterating dummy chunks.
|
||||
|
||||
|
||||
## 210.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed multiple recent bugs with key binding storage.
|
||||
|
||||
### Other
|
||||
|
||||
* Change default of `ButtonGroup.IsNoneSetAllowed` to `true`. This makes it default again to the previous (unintentional) behavior.
|
||||
|
||||
|
||||
## 210.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* `NetUserId` implements `ISelfSerialize` so can be used in data fields.
|
||||
* `ButtonGroup.IsNoneSetAllowed` to allow a button group to have no buttons pressed by default.
|
||||
|
||||
|
||||
## 210.0.3
|
||||
|
||||
|
||||
## 210.0.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Revert changes to `TextureRect` too.
|
||||
|
||||
|
||||
## 210.0.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -490,7 +490,7 @@ cmd-net_entityreport-help = Usage: net_entityreport
|
||||
cmd-net_refresh-desc = Requests a full server state.
|
||||
cmd-net_refresh-help = Usage: net_refresh
|
||||
|
||||
cmd-net_graph-desc = Toggles the net statistics pannel.
|
||||
cmd-net_graph-desc = Toggles the net statistics panel.
|
||||
cmd-net_graph-help = Usage: net_graph
|
||||
|
||||
cmd-net_watchent-desc = Dumps all network updates for an EntityId to the console.
|
||||
@@ -566,3 +566,9 @@ cmd-reloadtiletextures-help = Usage: reloadtiletextures
|
||||
cmd-audio_length-desc = Shows the length of an audio file
|
||||
cmd-audio_length-help = Usage: audio_length { cmd-audio_length-arg-file-name }
|
||||
cmd-audio_length-arg-file-name = <file name>
|
||||
|
||||
## PVS
|
||||
cmd-pvs-override-info-desc = Prints information about any PVS overrides associated with an entity.
|
||||
cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
|
||||
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
|
||||
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
|
||||
|
||||
@@ -70,9 +70,9 @@ command-description-ls-in =
|
||||
command-description-methods-get =
|
||||
Returns all methods associated with the input type.
|
||||
command-description-methods-overrides =
|
||||
Returns all methods overriden on the input type.
|
||||
Returns all methods overridden on the input type.
|
||||
command-description-methods-overridesfrom =
|
||||
Returns all methods overriden from the given type on the input type.
|
||||
Returns all methods overridden from the given type on the input type.
|
||||
command-description-cmd-moo =
|
||||
Asks the important questions.
|
||||
command-description-cmd-descloc =
|
||||
@@ -418,6 +418,6 @@ command-description-tee =
|
||||
This essentially lets you have a branch in your code to do multiple operations on one value.
|
||||
command-description-cmd-info =
|
||||
Returns a CommandSpec for the given command.
|
||||
On it's own, this means it'll print the comamnd's help message.
|
||||
On its own, this means it'll print the command's help message.
|
||||
command-description-comp-rm =
|
||||
Removes the given component from the entity.
|
||||
|
||||
8
Robust.Analyzers.Tests/Aliases.cs
Normal file
8
Robust.Analyzers.Tests/Aliases.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
// OH BOY. TURNS OUT IT GETS EVEN MORE CURSED.
|
||||
//
|
||||
// So because we're compiling a copy of Robust.Roslyn.Shared into every analyzer project,
|
||||
// the test project sees multiple copies of it. This would make it impossible to use.
|
||||
// UNLESS you use this obscure C# feature called "extern alias"
|
||||
// that I guarantee you you've never heard of before, and are now concerned about.
|
||||
|
||||
extern alias SerializationGenerator;
|
||||
340
Robust.Analyzers.Tests/ComponentPauseGeneratorTest.cs
Normal file
340
Robust.Analyzers.Tests/ComponentPauseGeneratorTest.cs
Normal file
@@ -0,0 +1,340 @@
|
||||
extern alias SerializationGenerator;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using NUnit.Framework;
|
||||
using SerializationGenerator::Robust.Roslyn.Shared;
|
||||
using SerializationGenerator::Robust.Serialization.Generator;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(ComponentPauseGenerator))]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
public sealed class ComponentPauseGeneratorTest
|
||||
{
|
||||
private const string TypesCode = """
|
||||
global using System;
|
||||
global using Robust.Shared.Analyzers;
|
||||
global using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Analyzers
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class AutoGenerateComponentPauseAttribute : Attribute
|
||||
{
|
||||
public bool Dirty = false;
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class AutoPausedFieldAttribute : Attribute;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class AutoNetworkedFieldAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
public interface IComponent;
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public void TestBasic()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.Foo += args.PausedTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullable()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan? Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
if (component.Foo.HasValue)
|
||||
component.Foo = component.Foo.Value + args.PausedTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutoState()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField, AutoNetworkedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.Foo += args.PausedTime;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExplicitDirty()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause(Dirty = true)]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoDiagnostics(result);
|
||||
ExpectSource(
|
||||
result,
|
||||
"""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
public partial class FooComponent
|
||||
{
|
||||
[RobustAutoGenerated]
|
||||
public sealed class FooComponent_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<FooComponent, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, FooComponent component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
component.Foo += args.PausedTime;
|
||||
Dirty(uid, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
""");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticNotIComponent()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseNotComponent, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
|
||||
]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticNoFields()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
public TimeSpan Foo;
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseNoFields, new LinePositionSpan(new LinePosition(1, 28), new LinePosition(1, 40)))
|
||||
]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticNoParentAttribute()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public TimeSpan Foo, Fooz;
|
||||
|
||||
[AutoPausedField]
|
||||
public TimeSpan Bar { get; set; }
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 20), new LinePosition(3, 23))),
|
||||
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(3, 25), new LinePosition(3, 29))),
|
||||
(Diagnostics.IdComponentPauseNoParentAttribute, new LinePositionSpan(new LinePosition(6, 20), new LinePosition(6, 23)))
|
||||
]);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDiagnosticWrongType()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
[AutoGenerateComponentPause]
|
||||
public sealed partial class FooComponent : IComponent
|
||||
{
|
||||
[AutoPausedField]
|
||||
public int Foo, Fooz;
|
||||
|
||||
[AutoPausedField]
|
||||
public int Bar { get; set; }
|
||||
}
|
||||
""");
|
||||
|
||||
ExpectNoSource(result);
|
||||
ExpectDiagnostics(result, [
|
||||
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 15), new LinePosition(4, 18))),
|
||||
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(4, 20), new LinePosition(4, 24))),
|
||||
(Diagnostics.IdComponentPauseWrongTypeAttribute, new LinePositionSpan(new LinePosition(7, 15), new LinePosition(7, 18)))
|
||||
]);
|
||||
}
|
||||
|
||||
private static void ExpectSource(GeneratorRunResult result, string expected)
|
||||
{
|
||||
Assert.That(result.GeneratedSources, Has.Length.EqualTo(1));
|
||||
|
||||
var source = result.GeneratedSources[0];
|
||||
|
||||
Assert.That(source.SourceText.ToString(), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
private static void ExpectNoSource(GeneratorRunResult result)
|
||||
{
|
||||
Assert.That(result.GeneratedSources, Is.Empty);
|
||||
}
|
||||
|
||||
private static void ExpectNoDiagnostics(GeneratorRunResult result)
|
||||
{
|
||||
Assert.That(result.Diagnostics, Is.Empty);
|
||||
}
|
||||
|
||||
private static void ExpectDiagnostics(GeneratorRunResult result, (string code, LinePositionSpan span)[] diagnostics)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(result.Diagnostics, Has.Length.EqualTo(diagnostics.Length));
|
||||
foreach (var (code, span) in diagnostics)
|
||||
{
|
||||
Assert.That(
|
||||
result.Diagnostics.Any(x => x.Id == code && x.Location.GetLineSpan().Span == span),
|
||||
$"Expected diagnostic with code {code} and location {span}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static GeneratorRunResult RunGenerator(string source)
|
||||
{
|
||||
var compilation = (Compilation)CSharpCompilation.Create("compilation",
|
||||
new[]
|
||||
{
|
||||
CSharpSyntaxTree.ParseText(source, path: "Source.cs"),
|
||||
CSharpSyntaxTree.ParseText(TypesCode, path: "Types.cs")
|
||||
},
|
||||
new[] { MetadataReference.CreateFromFile(typeof(Binder).GetTypeInfo().Assembly.Location) },
|
||||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||||
|
||||
var generator = new ComponentPauseGenerator();
|
||||
GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
|
||||
driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out _);
|
||||
var result = driver.GetRunResult();
|
||||
|
||||
return result.Results[0];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<SkipRobustAnalyzer>true</SkipRobustAnalyzer>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="..\MSBuild\Robust.Properties.targets"/>
|
||||
<Import Project="..\MSBuild\Robust.Engine.props"/>
|
||||
|
||||
@@ -23,5 +27,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Robust.Analyzers\Robust.Analyzers.csproj"/>
|
||||
<ProjectReference Include="..\Robust.Serialization.Generator\Robust.Serialization.Generator.csproj" Aliases="SerializationGenerator" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Robust.Shared.Analyzers.Implementation;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
using static Microsoft.CodeAnalysis.SymbolEqualityComparer;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
@@ -16,7 +17,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor ByRefEventSubscribedByValueRule = new(
|
||||
Diagnostics.IdByRefEventSubscribedByValue,
|
||||
"By-ref event subscribed to by value",
|
||||
"Tried to subscribe to a by-ref event '{0}' by value.",
|
||||
"Tried to subscribe to a by-ref event '{0}' by value",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -26,7 +27,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor ByRefEventRaisedByValueRule = new(
|
||||
Diagnostics.IdByRefEventRaisedByValue,
|
||||
"By-ref event raised by value",
|
||||
"Tried to raise a by-ref event '{0}' by value.",
|
||||
"Tried to raise a by-ref event '{0}' by value",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -36,7 +37,7 @@ public sealed class ByRefEventAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor ByValueEventRaisedByRefRule = new(
|
||||
Diagnostics.IdValueEventRaisedByRef,
|
||||
"Value event raised by-ref",
|
||||
"Tried to raise a value event '{0}' by-ref.",
|
||||
"Tried to raise a value event '{0}' by-ref",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -18,7 +19,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
|
||||
Diagnostics.IdDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} is a DataDefinition but is not partial.",
|
||||
"Type {0} is a DataDefinition but is not partial",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -28,7 +29,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
|
||||
Diagnostics.IdNestedDataDefinitionPartial,
|
||||
"Type must be partial",
|
||||
"Type {0} contains nested data definition {1} but is not partial.",
|
||||
"Type {0} contains nested data definition {1} but is not partial",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -38,7 +39,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
Diagnostics.IdDataFieldWritable,
|
||||
"Data field must not be readonly",
|
||||
"Data field {0} in data definition {1} is readonly.",
|
||||
"Data field {0} in data definition {1} is readonly",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -48,7 +49,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
Diagnostics.IdDataFieldPropertyWritable,
|
||||
"Data field property must have a setter",
|
||||
"Data field property {0} in data definition {1} does not have a setter.",
|
||||
"Data field property {0} in data definition {1} does not have a setter",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
|
||||
@@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxKind;
|
||||
using static Robust.Analyzers.Diagnostics;
|
||||
using static Robust.Roslyn.Shared.Diagnostics;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Document = Microsoft.CodeAnalysis.Document;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -31,7 +32,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static readonly DiagnosticDescriptor InvalidNotNullableImplementationRule = new (
|
||||
Diagnostics.IdInvalidNotNullableFlagImplementation,
|
||||
"Invalid NotNullable flag implementation.",
|
||||
"Invalid NotNullable flag implementation",
|
||||
"NotNullable flag is either not typed as bool, or does not have a default value equaling false",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
@@ -41,7 +42,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
|
||||
private static readonly DiagnosticDescriptor InvalidNotNullableTypeRule = new (
|
||||
Diagnostics.IdInvalidNotNullableFlagType,
|
||||
"Failed to resolve type parameter",
|
||||
"Failed to resolve type parameter \"{0}\".",
|
||||
"Failed to resolve type parameter \"{0}\"",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true,
|
||||
@@ -49,7 +50,7 @@ public sealed class NotNullableFlagAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
private static readonly DiagnosticDescriptor NotNullableFlagValueTypeRule = new (
|
||||
Diagnostics.IdNotNullableFlagValueType,
|
||||
"NotNullable flag not supported for value types.",
|
||||
"NotNullable flag not supported for value types",
|
||||
"Value types as generic arguments are not supported for NotNullable flags",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
|
||||
@@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for NotNullableFlagAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
|
||||
@@ -28,4 +16,10 @@
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CodeFixes;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Link="XamlX\filename" Include="../XamlX/src/XamlX/**/*.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/**/SreTypeSystem.cs" />
|
||||
<Compile Remove="../XamlX/src/XamlX/obj/**" />
|
||||
<Compile Include="..\Robust.Client\UserInterface\ControlPropertyAccess.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- XamlX doesn't do NRTs. -->
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
57
Robust.Client/Audio/AudioSystem.Limits.cs
Normal file
57
Robust.Client/Audio/AudioSystem.Limits.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Client.Audio;
|
||||
|
||||
public sealed partial class AudioSystem
|
||||
{
|
||||
/*
|
||||
* Handles limiting concurrent sounds for audio to avoid blowing the source budget on one sound getting spammed.
|
||||
*/
|
||||
|
||||
private readonly Dictionary<string, int> _playingCount = new();
|
||||
|
||||
private int _maxConcurrent;
|
||||
|
||||
private void InitializeLimit()
|
||||
{
|
||||
Subs.CVar(CfgManager, CVars.AudioDefaultConcurrent, SetConcurrentLimit, true);
|
||||
}
|
||||
|
||||
private void SetConcurrentLimit(int obj)
|
||||
{
|
||||
_maxConcurrent = obj;
|
||||
}
|
||||
|
||||
private bool TryAudioLimit(string sound)
|
||||
{
|
||||
if (string.IsNullOrEmpty(sound))
|
||||
return true;
|
||||
|
||||
ref var count = ref CollectionsMarshal.GetValueRefOrAddDefault(_playingCount, sound, out _);
|
||||
|
||||
if (count >= _maxConcurrent)
|
||||
return false;
|
||||
|
||||
count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void RemoveAudioLimit(string sound)
|
||||
{
|
||||
if (!_playingCount.TryGetValue(sound, out var count))
|
||||
return;
|
||||
|
||||
count--;
|
||||
|
||||
if (count <= 0)
|
||||
{
|
||||
_playingCount.Remove(sound);
|
||||
return;
|
||||
}
|
||||
|
||||
_playingCount[sound] = count;
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
private EntityUid? _listenerGrid;
|
||||
private UpdateAudioJob _updateAudioJob;
|
||||
|
||||
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
|
||||
private float _maxRayLength;
|
||||
@@ -108,6 +109,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
Subs.CVar(CfgManager, CVars.AudioAttenuation, OnAudioAttenuation, true);
|
||||
Subs.CVar(CfgManager, CVars.AudioRaycastLength, OnRaycastLengthChanged, true);
|
||||
InitializeLimit();
|
||||
}
|
||||
|
||||
private void OnAudioState(EntityUid uid, AudioComponent component, ref AfterAutoHandleStateEvent args)
|
||||
@@ -169,16 +171,23 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
|
||||
private void SetupSource(AudioComponent component, AudioResource audioResource, TimeSpan? length = null)
|
||||
{
|
||||
var source = _audio.CreateAudioSource(audioResource);
|
||||
var source = component.Source;
|
||||
|
||||
if (source == null)
|
||||
if (TryAudioLimit(component.FileName))
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}");
|
||||
DebugTools.Assert(false);
|
||||
source = component.Source;
|
||||
}
|
||||
var newSource = _audio.CreateAudioSource(audioResource);
|
||||
|
||||
component.Source = source;
|
||||
if (newSource == null)
|
||||
{
|
||||
Log.Error($"Error creating audio source for {audioResource}");
|
||||
DebugTools.Assert(false);
|
||||
source = newSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
component.Source = newSource;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to set all initial data for first frame.
|
||||
ApplyAudioParams(component.Params, component);
|
||||
@@ -202,6 +211,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
{
|
||||
// Breaks with prediction?
|
||||
component.Source.Dispose();
|
||||
|
||||
RemoveAudioLimit(component.FileName);
|
||||
}
|
||||
|
||||
private void OnAudioAttenuation(int obj)
|
||||
|
||||
@@ -114,7 +114,7 @@ internal sealed partial class MidiManager : IMidiManager
|
||||
"/usr/share/sounds/sf2/TimGM6mb.sf2",
|
||||
};
|
||||
|
||||
private const string WindowsSoundfont = @"C:\WINDOWS\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";
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var type = Type.GetType(args[0]);
|
||||
var type = GetType(args[0]);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
|
||||
shell.WriteLine(sig);
|
||||
}
|
||||
}
|
||||
|
||||
private Type? GetType(string name)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetType(name) is { } type)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ Suspendisse hendrerit blandit urna ut laoreet. Suspendisse ac elit at erat males
|
||||
{
|
||||
var textEdit = new TextEdit
|
||||
{
|
||||
Placeholder = new Rope.Leaf("You deleted the lipsum OwO")
|
||||
Placeholder = new Rope.Leaf("You deleted the lipsum\nOwO")
|
||||
};
|
||||
TabContainer.SetTabTitle(textEdit, "TextEdit");
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Network.Messages;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Replays;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -134,6 +135,24 @@ namespace Robust.Client.GameObjects
|
||||
EventBus.RaiseEvent(EventSource.Local, new EntitySessionMessage<T>(eventArgs, msg));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RaiseSharedEvent<T>(T message, EntityUid? user = null)
|
||||
{
|
||||
if (user == null || user != _playerManager.LocalEntity || !_gameTiming.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
EventBus.RaiseEvent(EventSource.Local, ref message);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RaiseSharedEvent<T>(T message, ICommonSession? user = null)
|
||||
{
|
||||
if (user == null || user != _playerManager.LocalSession || !_gameTiming.IsFirstTimePredicted)
|
||||
return;
|
||||
|
||||
EventBus.RaiseEvent(EventSource.Local, ref message);
|
||||
}
|
||||
|
||||
#region IEntityNetworkManager impl
|
||||
|
||||
public override IEntityNetworkManager EntityNetManager => this;
|
||||
|
||||
@@ -1201,7 +1201,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void LightResolutionScaleChanged(float newValue)
|
||||
{
|
||||
_lightResolutionScale = newValue;
|
||||
_lightResolutionScale = newValue > 0.05f ? newValue : 0.05f;
|
||||
RegenAllLightRts();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Graphics;
|
||||
using System.Runtime.Intrinsics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
@@ -63,6 +64,19 @@ namespace Robust.Client.Graphics
|
||||
|
||||
// ---- DrawPrimitives: Vector2 API ----
|
||||
|
||||
/// <summary>
|
||||
/// Draws arbitrary geometry primitives with a flat color.
|
||||
/// </summary>
|
||||
/// <param name="primitiveTopology">The topology of the primitives to draw.</param>
|
||||
/// <param name="vertices">The list of vertices to render.</param>
|
||||
/// <param name="color">The color to draw with.</param>
|
||||
public void DrawPrimitives(DrawPrimitiveTopology primitiveTopology, List<Vector2> vertices,
|
||||
Color color)
|
||||
{
|
||||
var span = CollectionsMarshal.AsSpan(vertices);
|
||||
DrawPrimitives(primitiveTopology, span, color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws arbitrary geometry primitives with a flat color.
|
||||
/// </summary>
|
||||
@@ -100,12 +114,43 @@ namespace Robust.Client.Graphics
|
||||
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
|
||||
}
|
||||
|
||||
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
{
|
||||
Color colorLinear = Color.FromSrgb(color);
|
||||
for (var i = 0; i < output.Length; i++)
|
||||
if (input.Length == 0)
|
||||
return;
|
||||
|
||||
if (input.Length != output.Length)
|
||||
{
|
||||
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
throw new InvalidOperationException("Invalid lengths!");
|
||||
}
|
||||
|
||||
var colorLinear = Color.FromSrgb(color);
|
||||
var colorVec = Unsafe.As<Color, Vector128<float>>(ref colorLinear);
|
||||
var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f);
|
||||
var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle();
|
||||
|
||||
var simdVectors = (nuint)(input.Length / 2);
|
||||
ref readonly var srcBase = ref Unsafe.As<Vector2, float>(ref Unsafe.AsRef(in input[0]));
|
||||
ref var dstBase = ref Unsafe.As<DrawVertexUV2DColor, float>(ref output[0]);
|
||||
|
||||
for (nuint i = 0; i < simdVectors; i++)
|
||||
{
|
||||
var positions = Vector128.LoadUnsafe(in srcBase, i * 4);
|
||||
|
||||
var posColorLower = (positions & maskVec) | uvVec;
|
||||
var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec;
|
||||
|
||||
posColorLower.StoreUnsafe(ref dstBase, i * 16);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 4);
|
||||
posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 12);
|
||||
}
|
||||
|
||||
var lastPos = (int)simdVectors * 2;
|
||||
if (lastPos != output.Length)
|
||||
{
|
||||
// Odd number of vertices. Handle the last manually.
|
||||
output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace Robust.Client.Input
|
||||
void KeyDown(KeyEventArgs e);
|
||||
void KeyUp(KeyEventArgs e);
|
||||
|
||||
IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified=true);
|
||||
IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified=true, bool invalid=false);
|
||||
|
||||
void RemoveBinding(IKeyBinding binding, bool markModified=true);
|
||||
|
||||
|
||||
@@ -523,13 +523,16 @@ namespace Robust.Client.Input
|
||||
{
|
||||
var baseKeyRegs = _serialization.Read<KeyBindingRegistration[]>(BaseKeyRegsNode, notNullableOverride: true);
|
||||
|
||||
|
||||
foreach (var reg in baseKeyRegs)
|
||||
{
|
||||
var invalid = false;
|
||||
|
||||
if (reg.Type != KeyBindingType.Command && !NetworkBindMap.FunctionExists(reg.Function.FunctionName))
|
||||
{
|
||||
Logger.ErrorS("input", "Key function in {0} does not exist: '{1}'", file,
|
||||
Logger.DebugS("input", "Key function in {0} does not exist: '{1}'.", file,
|
||||
reg.Function);
|
||||
continue;
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
if (defaultRegistration)
|
||||
@@ -544,7 +547,7 @@ namespace Robust.Client.Input
|
||||
}
|
||||
}
|
||||
|
||||
RegisterBinding(reg, markModified: defaultRegistration);
|
||||
RegisterBinding(reg, markModified: !defaultRegistration, invalid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,11 +555,16 @@ namespace Robust.Client.Input
|
||||
{
|
||||
var leaveEmpty = _serialization.Read<BoundKeyFunction[]>(node, notNullableOverride: true);
|
||||
|
||||
if (leaveEmpty.Length > 0)
|
||||
foreach (var bind in leaveEmpty)
|
||||
{
|
||||
// Adding to _modifiedKeyFunctions means that these keybinds won't be loaded from the base file.
|
||||
// Because they've been explicitly cleared.
|
||||
_modifiedKeyFunctions.UnionWith(leaveEmpty);
|
||||
_modifiedKeyFunctions.Add(bind);
|
||||
|
||||
// Adding to bindingsByFunction because if the keybind is not valid(For example if it's from another
|
||||
// server then we will have problems saving the file)
|
||||
_bindingsByFunction.GetOrNew(bind);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -584,7 +592,7 @@ namespace Robust.Client.Input
|
||||
return binding;
|
||||
}
|
||||
|
||||
public IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified = true)
|
||||
public IKeyBinding RegisterBinding(in KeyBindingRegistration reg, bool markModified = true, bool invalid = false)
|
||||
{
|
||||
var binding = new KeyBinding(this, reg.Function.FunctionName, reg.Type, reg.BaseKey, reg.CanFocus, reg.CanRepeat,
|
||||
reg.AllowSubCombs, reg.Priority, reg.Mod1, reg.Mod2, reg.Mod3);
|
||||
@@ -615,7 +623,7 @@ namespace Robust.Client.Input
|
||||
|
||||
public void InputModeChanged() => OnInputModeChanged?.Invoke();
|
||||
|
||||
private void RegisterBinding(KeyBinding binding, bool markModified = true)
|
||||
private void RegisterBinding(KeyBinding binding, bool markModified = true, bool invalid = false)
|
||||
{
|
||||
// we sort larger combos first so they take priority over smaller (single key) combos,
|
||||
// so they get processed first in KeyDown and such.
|
||||
@@ -630,7 +638,8 @@ namespace Robust.Client.Input
|
||||
_modifiedKeyFunctions.Add(binding.Function);
|
||||
}
|
||||
|
||||
_bindings.Insert(pos, binding);
|
||||
if (!invalid)
|
||||
_bindings.Insert(pos, binding);
|
||||
_bindingsByFunction.GetOrNew(binding.Function).Add(binding);
|
||||
OnKeyBindingAdded?.Invoke(binding);
|
||||
}
|
||||
|
||||
@@ -53,8 +53,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
value.InternalButtons.Add(this);
|
||||
ToggleMode = true;
|
||||
|
||||
// Set us to pressed if we're the first button. Doesn't go through the setter to avoid setting off our own error check.
|
||||
_pressed = value.InternalButtons.Count == 1;
|
||||
if (value.IsNoneSetAllowed)
|
||||
{
|
||||
// Still UNPRESS if there's another pressed button, but don't PRESS it otherwise.
|
||||
if (value.Pressed != this)
|
||||
_pressed = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set us to pressed if we're the first button. Doesn't go through the setter to avoid setting off our own error check.
|
||||
_pressed = value.InternalButtons.Count == 1;
|
||||
}
|
||||
DrawModeChanged();
|
||||
}
|
||||
}
|
||||
@@ -99,7 +108,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value && Group != null)
|
||||
if (!value && Group is { IsNoneSetAllowed: false })
|
||||
{
|
||||
throw new InvalidOperationException("Cannot directly unset a grouped button. Set another button in the group instead.");
|
||||
}
|
||||
@@ -444,6 +453,26 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </remarks>
|
||||
public sealed class ButtonGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether it is legal for this button group to have no selected button.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If true, it's legal for no button in the group to be active.
|
||||
/// This is then the initial state of a new group of buttons (no button is automatically selected),
|
||||
/// and it becomes legal to manually clear the active button through code.
|
||||
/// The user cannot manually unselect the active button regardless, only by selecting a difference button.
|
||||
/// </remarks>
|
||||
public bool IsNoneSetAllowed { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ButtonGroup"/>
|
||||
/// </summary>
|
||||
/// <param name="isNoneSetAllowed">The value of <see cref="IsNoneSetAllowed"/> on the new button group.</param>
|
||||
public ButtonGroup(bool isNoneSetAllowed = true)
|
||||
{
|
||||
IsNoneSetAllowed = isNoneSetAllowed;
|
||||
}
|
||||
|
||||
internal readonly List<BaseButton> InternalButtons = new();
|
||||
public IReadOnlyList<BaseButton> Buttons => InternalButtons;
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
public Collapsible()
|
||||
{}
|
||||
{
|
||||
Orientation = LayoutOrientation.Vertical;
|
||||
}
|
||||
|
||||
public Collapsible(CollapsibleHeading header, CollapsibleBody body)
|
||||
public Collapsible(CollapsibleHeading header, CollapsibleBody body) : this()
|
||||
{
|
||||
AddChild(header);
|
||||
AddChild(body);
|
||||
@@ -39,12 +41,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public Collapsible(string title, CollapsibleBody body)
|
||||
public Collapsible(string title, CollapsibleBody body) : this(new CollapsibleHeading(title), body)
|
||||
{
|
||||
AddChild(new CollapsibleHeading(title));
|
||||
AddChild(body);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
@@ -105,11 +104,15 @@ namespace Robust.Client.UserInterface.Controls
|
||||
set => _chevron.Margin = value;
|
||||
}
|
||||
|
||||
private Label _title = new();
|
||||
/// <summary>
|
||||
/// Exposes the label for this heading.
|
||||
/// </summary>
|
||||
public Label Label { get; }
|
||||
|
||||
public string? Title
|
||||
{
|
||||
get => _title.Text;
|
||||
set => _title.Text = value;
|
||||
get => Label.Text;
|
||||
set => Label.Text = value;
|
||||
}
|
||||
|
||||
public CollapsibleHeading()
|
||||
@@ -118,8 +121,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var box = new BoxContainer();
|
||||
AddChild(box);
|
||||
box.AddChild(_chevron);
|
||||
_title = new Label();
|
||||
box.AddChild(_title);
|
||||
Label = new Label();
|
||||
box.AddChild(Label);
|
||||
}
|
||||
|
||||
public CollapsibleHeading(string title) : this()
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private float _value;
|
||||
private float _page;
|
||||
private bool _rounded;
|
||||
private int _roundingDecimals = 0;
|
||||
|
||||
public event Action<Range>? OnValueChanged;
|
||||
|
||||
@@ -86,6 +87,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public int RoundingDecimals
|
||||
{
|
||||
get => _roundingDecimals;
|
||||
set
|
||||
{
|
||||
_roundingDecimals = value;
|
||||
_ensureValueClamped();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetValueWithoutEvent(float newValue)
|
||||
{
|
||||
newValue = ClampValue(newValue);
|
||||
@@ -107,7 +119,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
if (_rounded)
|
||||
{
|
||||
value = MathF.Round(value);
|
||||
value = MathF.Round(value, _roundingDecimals);
|
||||
}
|
||||
return MathHelper.Clamp(value, _minValue, _maxValue-_page);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
private bool _suppressScrollValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// If true then if we have a y-axis scroll it will convert it to an x-axis scroll.
|
||||
/// </summary>
|
||||
public bool FallbackDeltaScroll { get; set; } = true;
|
||||
|
||||
public int ScrollSpeedX { get; set; } = 50;
|
||||
public int ScrollSpeedY { get; set; } = 50;
|
||||
|
||||
@@ -246,9 +251,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
if (_hScrollEnabled)
|
||||
{
|
||||
_hScrollBar.ValueTarget += args.Delta.X * ScrollSpeedX;
|
||||
var delta =
|
||||
args.Delta.X == 0f &&
|
||||
!_vScrollEnabled &&
|
||||
FallbackDeltaScroll ?
|
||||
-args.Delta.Y :
|
||||
args.Delta.X;
|
||||
|
||||
_hScrollBar.ValueTarget += delta * ScrollSpeedX;
|
||||
}
|
||||
|
||||
if (!_vScrollVisible && !_hScrollVisible)
|
||||
return;
|
||||
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
private SpriteSystem? _sprite;
|
||||
private SharedTransformSystem? _transform;
|
||||
IEntityManager _entMan;
|
||||
private readonly IEntityManager _entMan;
|
||||
|
||||
[ViewVariables]
|
||||
public SpriteComponent? Sprite => Entity?.Comp1;
|
||||
@@ -143,6 +143,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
if (netEnt == NetEnt)
|
||||
return;
|
||||
|
||||
// The Entity is getting set later in the ResolveEntity method
|
||||
// because the client may not have received it yet.
|
||||
Entity = null;
|
||||
NetEnt = netEnt;
|
||||
}
|
||||
@@ -256,28 +258,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[NotNullWhen(true)] out SpriteComponent? sprite,
|
||||
[NotNullWhen(true)] out TransformComponent? xform)
|
||||
{
|
||||
if (NetEnt != null && Entity == null && _entMan.TryGetEntity(NetEnt, out var ent))
|
||||
SetEntity(ent);
|
||||
|
||||
if (Entity != null)
|
||||
{
|
||||
(uid, sprite, xform) = Entity.Value;
|
||||
return true;
|
||||
return !_entMan.Deleted(uid);
|
||||
}
|
||||
|
||||
sprite = null;
|
||||
xform = null;
|
||||
uid = default;
|
||||
|
||||
if (NetEnt == null)
|
||||
return false;
|
||||
|
||||
if (!_entMan.TryGetEntity(NetEnt, out var ent))
|
||||
return false;
|
||||
|
||||
SetEntity(ent);
|
||||
if (Entity == null)
|
||||
return false;
|
||||
|
||||
(uid, sprite, xform) = Entity.Value;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +576,7 @@ public sealed class TextEdit : Control
|
||||
|
||||
var newPos = CursorShiftedLeft();
|
||||
// Explicit newlines work kinda funny with bias, so keep it at top there.
|
||||
var bias = Rope.Index(TextRope, newPos) == '\n'
|
||||
var bias = _cursorPosition.Index == TextLength || Rope.Index(TextRope, newPos) == '\n'
|
||||
? LineBreakBias.Top
|
||||
: LineBreakBias.Bottom;
|
||||
|
||||
@@ -940,6 +940,13 @@ public sealed class TextEdit : Control
|
||||
|
||||
private CursorPos GetIndexAtHorizontalPos(int line, float horizontalPos)
|
||||
{
|
||||
// If the placeholder is visible, this function does not return correct results because it looks at TextRope,
|
||||
// but _lineBreaks is configured for the display rope.
|
||||
// Bail out early in this case, the function is not currently used in any situation in any location
|
||||
// where something else is desired if the placeholder is visible.
|
||||
if (IsPlaceholderVisible)
|
||||
return default;
|
||||
|
||||
var contentBox = PixelSizeBox;
|
||||
var font = GetFont();
|
||||
var uiScale = UIScale;
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
public const string StylePropertyTexture = "texture";
|
||||
public const string StylePropertyShader = "shader";
|
||||
public const string StylePropertyTextureStretch = "texture-stretch";
|
||||
public const string StylePropertyTextureScale = "texture-scale";
|
||||
public const string StylePropertyTextureSizeTarget = "texture-size-target";
|
||||
|
||||
private bool _canShrink;
|
||||
private Texture? _texture;
|
||||
@@ -29,7 +32,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public Texture? Texture
|
||||
{
|
||||
get => _texture;
|
||||
get
|
||||
{
|
||||
if (_texture is null)
|
||||
{
|
||||
if (TryGetStyleProperty(StylePropertyTexture, out Texture? texture))
|
||||
{
|
||||
return texture;
|
||||
}
|
||||
}
|
||||
|
||||
return _texture;
|
||||
}
|
||||
set
|
||||
{
|
||||
var oldSize = _texture?.Size;
|
||||
@@ -43,6 +57,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
private string? _texturePath;
|
||||
private StretchMode _stretch = StretchMode.Keep;
|
||||
|
||||
public string TexturePath
|
||||
{
|
||||
@@ -54,21 +69,45 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
}
|
||||
|
||||
protected override void StylePropertiesChanged()
|
||||
{
|
||||
base.StylePropertiesChanged();
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
protected override void OnThemeUpdated()
|
||||
{
|
||||
if (_texturePath != null) Texture = Theme.ResolveTexture(_texturePath);
|
||||
base.OnThemeUpdated();
|
||||
}
|
||||
|
||||
public Vector2 TextureSizeTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!TryGetStyleProperty(StylePropertyTextureSizeTarget, out Vector2 target))
|
||||
target = _textureScale * Texture?.Size ?? Vector2.Zero;
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scales the texture displayed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This does not apply to the following stretch modes: <see cref="StretchMode.Scale"/>.
|
||||
/// This additionally does not apply if a size target is set.
|
||||
/// </remarks>
|
||||
public Vector2 TextureScale
|
||||
{
|
||||
get => _textureScale;
|
||||
get
|
||||
{
|
||||
if (!TryGetStyleProperty(StylePropertyTextureScale, out Vector2 scale))
|
||||
scale = _textureScale;
|
||||
|
||||
return scale;
|
||||
}
|
||||
set
|
||||
{
|
||||
_textureScale = value;
|
||||
@@ -96,23 +135,27 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// <summary>
|
||||
/// Controls how the texture should be drawn if the control is larger than the size of the texture.
|
||||
/// </summary>
|
||||
public StretchMode Stretch { get; set; } = StretchMode.Keep;
|
||||
public StretchMode Stretch
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!TryGetStyleProperty(StylePropertyTextureStretch, out StretchMode stretch))
|
||||
stretch = _stretch;
|
||||
return stretch;
|
||||
}
|
||||
set => _stretch = value;
|
||||
}
|
||||
|
||||
protected internal override void Draw(DrawingHandleScreen handle)
|
||||
{
|
||||
base.Draw(handle);
|
||||
|
||||
var texture = _texture;
|
||||
ShaderInstance? shader = null;
|
||||
var texture = Texture;
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
TryGetStyleProperty(StylePropertyTexture, out texture);
|
||||
if (texture == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (texture is null)
|
||||
return;
|
||||
|
||||
ShaderInstance? shader = null;
|
||||
|
||||
if (ShaderOverride != null)
|
||||
{
|
||||
@@ -167,17 +210,17 @@ namespace Robust.Client.UserInterface.Controls
|
||||
case StretchMode.Tile:
|
||||
// TODO: Implement Tile.
|
||||
case StretchMode.Keep:
|
||||
return UIBox2.FromDimensions(Vector2.Zero, texture.Size * _textureScale * UIScale);
|
||||
return UIBox2.FromDimensions(Vector2.Zero, TextureSizeTarget * UIScale);
|
||||
case StretchMode.KeepCentered:
|
||||
{
|
||||
var position = (PixelSize - texture.Size * _textureScale * UIScale) / 2;
|
||||
return UIBox2.FromDimensions(position, texture.Size * _textureScale * UIScale);
|
||||
var position = (Size - TextureSizeTarget) / 2;
|
||||
return UIBox2.FromDimensions(position, TextureSizeTarget * UIScale);
|
||||
}
|
||||
|
||||
case StretchMode.KeepAspect:
|
||||
case StretchMode.KeepAspectCentered:
|
||||
{
|
||||
var (texWidth, texHeight) = texture.Size * _textureScale;
|
||||
var (texWidth, texHeight) = TextureSizeTarget;
|
||||
var width = texWidth * (PixelSize.Y / texHeight);
|
||||
var height = (float)PixelSize.Y;
|
||||
if (width > PixelSize.X)
|
||||
@@ -197,7 +240,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
|
||||
case StretchMode.KeepAspectCovered:
|
||||
var texSize = texture.Size * _textureScale;
|
||||
var texSize = TextureSizeTarget;
|
||||
// Calculate the scale necessary to fit width and height to control size.
|
||||
var (scaleX, scaleY) = PixelSize / texSize;
|
||||
// Use whichever scale is greater.
|
||||
@@ -259,19 +302,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||
{
|
||||
var texture = _texture;
|
||||
|
||||
if (texture == null)
|
||||
{
|
||||
TryGetStyleProperty(StylePropertyTexture, out texture);
|
||||
}
|
||||
|
||||
if (texture == null || CanShrink)
|
||||
{
|
||||
if (CanShrink || Texture == null)
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
return texture.Size * TextureScale;
|
||||
return TextureSizeTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,22 @@ namespace Robust.Client.ViewVariables
|
||||
return new VVPropEditorString();
|
||||
}
|
||||
|
||||
if (type == typeof(EntProtoId) ||
|
||||
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))!;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Robust.Client.ViewVariables.Editors;
|
||||
|
||||
internal sealed class VVPropEditorEntProtoId : VVPropEditor
|
||||
{
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var lineEdit = new LineEdit
|
||||
{
|
||||
Text = (EntProtoId) (value ?? ""),
|
||||
Editable = !ReadOnly,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
if (!ReadOnly)
|
||||
{
|
||||
lineEdit.OnTextEntered += e =>
|
||||
{
|
||||
ValueChanged((EntProtoId) e.Text);
|
||||
};
|
||||
}
|
||||
|
||||
return lineEdit;
|
||||
}
|
||||
}
|
||||
38
Robust.Client/ViewVariables/Editors/VVPropEditorProtoId.cs
Normal file
38
Robust.Client/ViewVariables/Editors/VVPropEditorProtoId.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
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 VVPropEditorProtoId<T> : VVPropEditor where T : class, IPrototype
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
|
||||
protected override Control MakeUI(object? value)
|
||||
{
|
||||
var lineEdit = new LineEdit
|
||||
{
|
||||
Text = (ProtoId<T>) (value ?? ""),
|
||||
Editable = !ReadOnly,
|
||||
HorizontalExpand = true,
|
||||
};
|
||||
|
||||
if (!ReadOnly)
|
||||
{
|
||||
lineEdit.OnTextEntered += e =>
|
||||
{
|
||||
var id = (ProtoId<T>)e.Text;
|
||||
|
||||
if (!_protoManager.HasIndex(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ValueChanged(id);
|
||||
};
|
||||
}
|
||||
|
||||
return lineEdit;
|
||||
}
|
||||
}
|
||||
46
Robust.Roslyn.Shared/AttributeHelper.cs
Normal file
46
Robust.Roslyn.Shared/AttributeHelper.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Roslyn.Shared;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public static class AttributeHelper
|
||||
{
|
||||
public static bool HasAttribute(ISymbol symbol, string attributeMetadataName, [NotNullWhen(true)] out AttributeData? matchedAttribute)
|
||||
{
|
||||
foreach (var attribute in symbol.GetAttributes())
|
||||
{
|
||||
if (attribute.AttributeClass == null)
|
||||
continue;
|
||||
|
||||
if (TypeSymbolHelper.ShittyTypeMatch(attribute.AttributeClass, attributeMetadataName))
|
||||
{
|
||||
matchedAttribute = attribute;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
matchedAttribute = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool GetNamedArgumentBool(AttributeData data, string name, bool defaultValue)
|
||||
{
|
||||
foreach (var kv in data.NamedArguments)
|
||||
{
|
||||
if (kv.Key != name)
|
||||
continue;
|
||||
|
||||
if (kv.Value.Kind != TypedConstantKind.Primitive)
|
||||
continue;
|
||||
|
||||
if (kv.Value.Value is not bool value)
|
||||
continue;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
namespace Robust.Roslyn.Shared;
|
||||
|
||||
public static class Diagnostics
|
||||
{
|
||||
@@ -24,6 +24,10 @@ public static class Diagnostics
|
||||
public const string IdNestedDataDefinitionPartial = "RA0018";
|
||||
public const string IdDataFieldWritable = "RA0019";
|
||||
public const string IdDataFieldPropertyWritable = "RA0020";
|
||||
public const string IdComponentPauseNotComponent = "RA0021";
|
||||
public const string IdComponentPauseNoFields = "RA0022";
|
||||
public const string IdComponentPauseNoParentAttribute = "RA0023";
|
||||
public const string IdComponentPauseWrongTypeAttribute = "RA0024";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
201
Robust.Roslyn.Shared/Helpers/EquatableArray{T}.cs
Normal file
201
Robust.Roslyn.Shared/Helpers/EquatableArray{T}.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// Taken from https://github.com/CommunityToolkit/dotnet/blob/ecd1711b740f4f88d2bb943ce292ae4fc90df1bc/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/EquatableArray%7BT%7D.cs
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Robust.Roslyn.Shared.Helpers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for <see cref="EquatableArray{T}"/>.
|
||||
/// </summary>
|
||||
public static class EquatableArray
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of items in the input array.</typeparam>
|
||||
/// <param name="array">The input <see cref="ImmutableArray{T}"/> instance.</param>
|
||||
/// <returns>An <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.</returns>
|
||||
public static EquatableArray<T> AsEquatableArray<T>(this ImmutableArray<T> array)
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
return new(array);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An immutable, equatable array. This is equivalent to <see cref="ImmutableArray{T}"/> but with value equality support.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of values in the array.</typeparam>
|
||||
public readonly struct EquatableArray<T> : IEquatable<EquatableArray<T>>, IEnumerable<T>
|
||||
where T : IEquatable<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// The underlying <typeparamref name="T"/> array.
|
||||
/// </summary>
|
||||
private readonly T[]? array;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="EquatableArray{T}"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="array">The input <see cref="ImmutableArray{T}"/> to wrap.</param>
|
||||
public EquatableArray(ImmutableArray<T> array)
|
||||
{
|
||||
this.array = Unsafe.As<ImmutableArray<T>, T[]?>(ref array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to an item at a specified position within the array.
|
||||
/// </summary>
|
||||
/// <param name="index">The index of the item to retrieve a reference to.</param>
|
||||
/// <returns>A reference to an item at a specified position within the array.</returns>
|
||||
public ref readonly T this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => ref AsImmutableArray().ItemRef(index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the current array is empty.
|
||||
/// </summary>
|
||||
public bool IsEmpty
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => AsImmutableArray().IsEmpty;
|
||||
}
|
||||
|
||||
/// <sinheritdoc/>
|
||||
public bool Equals(EquatableArray<T> array)
|
||||
{
|
||||
return AsSpan().SequenceEqual(array.AsSpan());
|
||||
}
|
||||
|
||||
/// <sinheritdoc/>
|
||||
public override bool Equals([NotNullWhen(true)] object? obj)
|
||||
{
|
||||
return obj is EquatableArray<T> array && Equals(this, array);
|
||||
}
|
||||
|
||||
/// <sinheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
if (this.array is not T[] array)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
HashCode hashCode = default;
|
||||
|
||||
foreach (T item in array)
|
||||
{
|
||||
hashCode.Add(item);
|
||||
}
|
||||
|
||||
return hashCode.ToHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="ImmutableArray{T}"/> instance from the current <see cref="EquatableArray{T}"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="ImmutableArray{T}"/> from the current <see cref="EquatableArray{T}"/>.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ImmutableArray<T> AsImmutableArray()
|
||||
{
|
||||
return Unsafe.As<T[]?, ImmutableArray<T>>(ref Unsafe.AsRef(in this.array));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="array">The input <see cref="ImmutableArray{T}"/> instance.</param>
|
||||
/// <returns>An <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.</returns>
|
||||
public static EquatableArray<T> FromImmutableArray(ImmutableArray<T> array)
|
||||
{
|
||||
return new(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="ReadOnlySpan{T}"/> wrapping the current items.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ReadOnlySpan{T}"/> wrapping the current items.</returns>
|
||||
public ReadOnlySpan<T> AsSpan()
|
||||
{
|
||||
return AsImmutableArray().AsSpan();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the contents of this <see cref="EquatableArray{T}"/> instance to a mutable array.
|
||||
/// </summary>
|
||||
/// <returns>The newly instantiated array.</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
return AsImmutableArray().ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="ImmutableArray{T}.Enumerator"/> value to traverse items in the current array.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ImmutableArray{T}.Enumerator"/> value to traverse items in the current array.</returns>
|
||||
public ImmutableArray<T>.Enumerator GetEnumerator()
|
||||
{
|
||||
return AsImmutableArray().GetEnumerator();
|
||||
}
|
||||
|
||||
/// <sinheritdoc/>
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable<T>)AsImmutableArray()).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <sinheritdoc/>
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)AsImmutableArray()).GetEnumerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="ImmutableArray{T}"/> to <see cref="EquatableArray{T}"/>.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="EquatableArray{T}"/> instance from a given <see cref="ImmutableArray{T}"/>.</returns>
|
||||
public static implicit operator EquatableArray<T>(ImmutableArray<T> array)
|
||||
{
|
||||
return FromImmutableArray(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts an <see cref="EquatableArray{T}"/> to <see cref="ImmutableArray{T}"/>.
|
||||
/// </summary>
|
||||
/// <returns>An <see cref="ImmutableArray{T}"/> instance from a given <see cref="EquatableArray{T}"/>.</returns>
|
||||
public static implicit operator ImmutableArray<T>(EquatableArray<T> array)
|
||||
{
|
||||
return array.AsImmutableArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two <see cref="EquatableArray{T}"/> values are the same.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="EquatableArray{T}"/> value.</param>
|
||||
/// <param name="right">The second <see cref="EquatableArray{T}"/> value.</param>
|
||||
/// <returns>Whether <paramref name="left"/> and <paramref name="right"/> are equal.</returns>
|
||||
public static bool operator ==(EquatableArray<T> left, EquatableArray<T> right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two <see cref="EquatableArray{T}"/> values are not the same.
|
||||
/// </summary>
|
||||
/// <param name="left">The first <see cref="EquatableArray{T}"/> value.</param>
|
||||
/// <param name="right">The second <see cref="EquatableArray{T}"/> value.</param>
|
||||
/// <returns>Whether <paramref name="left"/> and <paramref name="right"/> are not equal.</returns>
|
||||
public static bool operator !=(EquatableArray<T> left, EquatableArray<T> right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
190
Robust.Roslyn.Shared/Helpers/HashCode.cs
Normal file
190
Robust.Roslyn.Shared/Helpers/HashCode.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// Taken from https://raw.githubusercontent.com/CommunityToolkit/dotnet/ecd1711b740f4f88d2bb943ce292ae4fc90df1bc/src/CommunityToolkit.Mvvm.SourceGenerators/Helpers/HashCode.cs
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
#pragma warning disable CS0809
|
||||
|
||||
namespace System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// A polyfill type that mirrors some methods from <see cref="HashCode"/> on .NET 6.
|
||||
/// </summary>
|
||||
public struct HashCode
|
||||
{
|
||||
private const uint Prime1 = 2654435761U;
|
||||
private const uint Prime2 = 2246822519U;
|
||||
private const uint Prime3 = 3266489917U;
|
||||
private const uint Prime4 = 668265263U;
|
||||
private const uint Prime5 = 374761393U;
|
||||
|
||||
private static readonly uint seed = GenerateGlobalSeed();
|
||||
|
||||
private uint v1, v2, v3, v4;
|
||||
private uint queue1, queue2, queue3;
|
||||
private uint length;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the default seed.
|
||||
/// </summary>
|
||||
/// <returns>A random seed.</returns>
|
||||
private static unsafe uint GenerateGlobalSeed()
|
||||
{
|
||||
byte[] bytes = new byte[4];
|
||||
|
||||
using (RandomNumberGenerator generator = RandomNumberGenerator.Create())
|
||||
{
|
||||
generator.GetBytes(bytes);
|
||||
}
|
||||
|
||||
return BitConverter.ToUInt32(bytes, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single value to the current hash.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the value to add into the hash code.</typeparam>
|
||||
/// <param name="value">The value to add into the hash code.</param>
|
||||
public void Add<T>(T value)
|
||||
{
|
||||
Add(value?.GetHashCode() ?? 0);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Initialize(out uint v1, out uint v2, out uint v3, out uint v4)
|
||||
{
|
||||
v1 = seed + Prime1 + Prime2;
|
||||
v2 = seed + Prime2;
|
||||
v3 = seed;
|
||||
v4 = seed - Prime1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint Round(uint hash, uint input)
|
||||
{
|
||||
return RotateLeft(hash + input * Prime2, 13) * Prime1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint QueueRound(uint hash, uint queuedValue)
|
||||
{
|
||||
return RotateLeft(hash + queuedValue * Prime3, 17) * Prime4;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint MixState(uint v1, uint v2, uint v3, uint v4)
|
||||
{
|
||||
return RotateLeft(v1, 1) + RotateLeft(v2, 7) + RotateLeft(v3, 12) + RotateLeft(v4, 18);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint MixEmptyState()
|
||||
{
|
||||
return seed + Prime5;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint MixFinal(uint hash)
|
||||
{
|
||||
hash ^= hash >> 15;
|
||||
hash *= Prime2;
|
||||
hash ^= hash >> 13;
|
||||
hash *= Prime3;
|
||||
hash ^= hash >> 16;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
private void Add(int value)
|
||||
{
|
||||
uint val = (uint)value;
|
||||
uint previousLength = this.length++;
|
||||
uint position = previousLength % 4;
|
||||
|
||||
if (position == 0)
|
||||
{
|
||||
this.queue1 = val;
|
||||
}
|
||||
else if (position == 1)
|
||||
{
|
||||
this.queue2 = val;
|
||||
}
|
||||
else if (position == 2)
|
||||
{
|
||||
this.queue3 = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (previousLength == 3)
|
||||
{
|
||||
Initialize(out this.v1, out this.v2, out this.v3, out this.v4);
|
||||
}
|
||||
|
||||
this.v1 = Round(this.v1, this.queue1);
|
||||
this.v2 = Round(this.v2, this.queue2);
|
||||
this.v3 = Round(this.v3, this.queue3);
|
||||
this.v4 = Round(this.v4, val);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the resulting hashcode from the current instance.
|
||||
/// </summary>
|
||||
/// <returns>The resulting hashcode from the current instance.</returns>
|
||||
public int ToHashCode()
|
||||
{
|
||||
uint length = this.length;
|
||||
uint position = length % 4;
|
||||
uint hash = length < 4 ? MixEmptyState() : MixState(this.v1, this.v2, this.v3, this.v4);
|
||||
|
||||
hash += length * 4;
|
||||
|
||||
if (position > 0)
|
||||
{
|
||||
hash = QueueRound(hash, this.queue1);
|
||||
|
||||
if (position > 1)
|
||||
{
|
||||
hash = QueueRound(hash, this.queue2);
|
||||
|
||||
if (position > 2)
|
||||
{
|
||||
hash = QueueRound(hash, this.queue3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hash = MixFinal(hash);
|
||||
|
||||
return (int)hash;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes. Use ToHashCode to retrieve the computed hash code.", error: true)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override int GetHashCode() => throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc/>
|
||||
[Obsolete("HashCode is a mutable struct and should not be compared with other HashCodes.", error: true)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override bool Equals(object? obj) => throw new NotSupportedException();
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the specified value left by the specified number of bits.
|
||||
/// Similar in behavior to the x86 instruction ROL.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to rotate.</param>
|
||||
/// <param name="offset">The number of bits to rotate by.
|
||||
/// Any value outside the range [0..31] is treated as congruent mod 32.</param>
|
||||
/// <returns>The rotated value.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static uint RotateLeft(uint value, int offset)
|
||||
{
|
||||
return (value << offset) | (value >> (32 - offset));
|
||||
}
|
||||
}
|
||||
121
Robust.Roslyn.Shared/PartialTypeHelper.cs
Normal file
121
Robust.Roslyn.Shared/PartialTypeHelper.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Robust.Roslyn.Shared.Helpers;
|
||||
|
||||
namespace Robust.Roslyn.Shared;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// All the information to make a partial type alternative for a type.
|
||||
/// </summary>
|
||||
public sealed record PartialTypeInfo(
|
||||
string? Namespace,
|
||||
string Name,
|
||||
string DisplayName,
|
||||
EquatableArray<string> TypeParameterNames,
|
||||
bool IsValid,
|
||||
Location SyntaxLocation,
|
||||
Accessibility Accessibility,
|
||||
TypeKind Kind,
|
||||
bool IsRecord,
|
||||
bool IsAbstract)
|
||||
{
|
||||
public static PartialTypeInfo FromSymbol(INamedTypeSymbol symbol, TypeDeclarationSyntax syntax)
|
||||
{
|
||||
var typeParameters = ImmutableArray<string>.Empty;
|
||||
if (symbol.TypeParameters.Length > 0)
|
||||
{
|
||||
var builder = ImmutableArray.CreateBuilder<string>(symbol.TypeParameters.Length);
|
||||
foreach (var typeParameter in symbol.TypeParameters)
|
||||
{
|
||||
builder.Add(typeParameter.Name);
|
||||
}
|
||||
|
||||
typeParameters = builder.MoveToImmutable();
|
||||
}
|
||||
|
||||
return new PartialTypeInfo(
|
||||
symbol.ContainingNamespace.IsGlobalNamespace ? null : symbol.ContainingNamespace.ToDisplayString(),
|
||||
symbol.Name,
|
||||
symbol.ToDisplayString(),
|
||||
typeParameters,
|
||||
syntax.Modifiers.Any(x => x.IsKind(SyntaxKind.PartialKeyword)),
|
||||
syntax.Keyword.GetLocation(),
|
||||
symbol.DeclaredAccessibility,
|
||||
symbol.TypeKind,
|
||||
symbol.IsRecord,
|
||||
symbol.IsAbstract);
|
||||
}
|
||||
|
||||
public bool CheckPartialDiagnostic(SourceProductionContext context, DiagnosticDescriptor diagnostic)
|
||||
{
|
||||
if (!IsValid)
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(diagnostic, SyntaxLocation, DisplayName));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetGeneratedFileName()
|
||||
{
|
||||
var name = Namespace == null ? Name : $"{Namespace}.{Name}";
|
||||
if (TypeParameterNames.AsImmutableArray().Length > 0)
|
||||
name += $"`{TypeParameterNames.AsImmutableArray().Length}";
|
||||
|
||||
name += ".g.cs";
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
public void WriteHeader(StringBuilder builder)
|
||||
{
|
||||
if (Namespace != null)
|
||||
builder.AppendLine($"namespace {Namespace};\n");
|
||||
|
||||
// TODO: Nested classes
|
||||
|
||||
var access = Accessibility switch
|
||||
{
|
||||
Accessibility.Private => "private",
|
||||
Accessibility.ProtectedAndInternal => "private protected",
|
||||
Accessibility.ProtectedOrInternal => "protected internal",
|
||||
Accessibility.Protected => "protected",
|
||||
Accessibility.Internal => "internal",
|
||||
_ => "public"
|
||||
};
|
||||
|
||||
string keyword;
|
||||
if (Kind == TypeKind.Interface)
|
||||
{
|
||||
keyword = "interface";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsRecord)
|
||||
{
|
||||
keyword = Kind == TypeKind.Struct ? "record struct" : "record";
|
||||
}
|
||||
else
|
||||
{
|
||||
keyword = Kind == TypeKind.Struct ? "struct" : "class";
|
||||
}
|
||||
}
|
||||
|
||||
builder.Append($"{access} {(IsAbstract ? "abstract " : "")}partial {keyword} {Name}");
|
||||
if (TypeParameterNames.AsSpan().Length > 0)
|
||||
{
|
||||
builder.Append($"<{string.Join(", ", TypeParameterNames.AsImmutableArray())}>");
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteFooter(StringBuilder builder)
|
||||
{
|
||||
// TODO: Nested classes
|
||||
}
|
||||
}
|
||||
38
Robust.Roslyn.Shared/Robust.Roslyn.Shared.props
Normal file
38
Robust.Roslyn.Shared/Robust.Roslyn.Shared.props
Normal file
@@ -0,0 +1,38 @@
|
||||
<Project>
|
||||
<!--
|
||||
I wanted to make a Robust.Roslyn.Shared library project,
|
||||
but doing that causes various random library load failures in practice.
|
||||
|
||||
Instead, you'll get this vomit. Enjoy.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PolySharpIncludeGeneratedTypes>System.Index;System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;System.Runtime.CompilerServices.IsExternalInit;System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute</PolySharpIncludeGeneratedTypes>
|
||||
<NoWarn>RS2008</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
|
||||
|
||||
<PackageReference Include="PolySharp">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\Robust.Roslyn.Shared\**\*.cs">
|
||||
<Link>Robust.Roslyn.Shared\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
</Compile>
|
||||
<Compile Remove="..\Robust.Roslyn.Shared\obj\**\*.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
28
Robust.Roslyn.Shared/TypeSymbolHelper.cs
Normal file
28
Robust.Roslyn.Shared/TypeSymbolHelper.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace Robust.Roslyn.Shared;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public static class TypeSymbolHelper
|
||||
{
|
||||
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))
|
||||
return false;
|
||||
|
||||
return type.ToDisplayString() == attributeMetadataName;
|
||||
}
|
||||
|
||||
public static bool ImplementsInterface(INamedTypeSymbol type, string interfaceTypeName)
|
||||
{
|
||||
foreach (var interfaceType in type.AllInterfaces)
|
||||
{
|
||||
if (ShittyTypeMatch(interfaceType, interfaceTypeName))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
252
Robust.Serialization.Generator/ComponentPauseGenerator.cs
Normal file
252
Robust.Serialization.Generator/ComponentPauseGenerator.cs
Normal file
@@ -0,0 +1,252 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Robust.Roslyn.Shared.Helpers;
|
||||
|
||||
namespace Robust.Serialization.Generator;
|
||||
|
||||
/// <summary>
|
||||
/// Automatically generates implementations for handling timer unpausing.
|
||||
/// </summary>
|
||||
[Generator(LanguageNames.CSharp)]
|
||||
public sealed class ComponentPauseGenerator : IIncrementalGenerator
|
||||
{
|
||||
private const string AutoGenerateComponentPauseAttributeName = "Robust.Shared.Analyzers.AutoGenerateComponentPauseAttribute";
|
||||
private const string AutoPausedFieldAttributeName = "Robust.Shared.Analyzers.AutoPausedFieldAttribute";
|
||||
private const string AutoNetworkFieldAttributeName = "Robust.Shared.Analyzers.AutoNetworkedFieldAttribute";
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private const string IComponentTypeName = "Robust.Shared.GameObjects.IComponent";
|
||||
|
||||
private static readonly DiagnosticDescriptor NotComponentDiagnostic = new(
|
||||
Diagnostics.IdComponentPauseNotComponent,
|
||||
"Class must be an IComponent to use AutoGenerateComponentPause",
|
||||
"Class '{0}' must implement IComponent to be used with [AutoGenerateComponentPause]",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true);
|
||||
|
||||
private static readonly DiagnosticDescriptor NoFieldsDiagnostic = new(
|
||||
Diagnostics.IdComponentPauseNoFields,
|
||||
"AutoGenerateComponentPause has no fields",
|
||||
"Class '{0}' has [AutoGenerateComponentPause] but has no fields or properties with [AutoPausedField]",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
private static readonly DiagnosticDescriptor NoParentAttributeDiagnostic = new(
|
||||
Diagnostics.IdComponentPauseNoParentAttribute,
|
||||
"AutoPausedField on type of field without AutoGenerateComponentPause",
|
||||
"Field '{0}' has [AutoPausedField] but its containing type does not have [AutoGenerateComponentPause]",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true);
|
||||
|
||||
private static readonly DiagnosticDescriptor WrongTypeAttributeDiagnostic = new(
|
||||
Diagnostics.IdComponentPauseWrongTypeAttribute,
|
||||
"AutoPausedField has wrong type",
|
||||
"Field '{0}' has [AutoPausedField] but is not of type TimeSpan",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Error,
|
||||
true);
|
||||
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
var componentInfos = context.SyntaxProvider.ForAttributeWithMetadataName(
|
||||
AutoGenerateComponentPauseAttributeName,
|
||||
(syntaxNode, _) => syntaxNode is TypeDeclarationSyntax,
|
||||
(syntaxContext, _) =>
|
||||
{
|
||||
var symbol = (INamedTypeSymbol)syntaxContext.TargetSymbol;
|
||||
|
||||
var typeDeclarationSyntax = (TypeDeclarationSyntax) syntaxContext.TargetNode;
|
||||
var partialTypeInfo = PartialTypeInfo.FromSymbol(
|
||||
symbol,
|
||||
typeDeclarationSyntax);
|
||||
|
||||
var dirty = AttributeHelper.GetNamedArgumentBool(syntaxContext.Attributes[0], "Dirty", false);
|
||||
|
||||
var fieldBuilder = ImmutableArray.CreateBuilder<FieldInfo>();
|
||||
foreach (var member in symbol.GetMembers())
|
||||
{
|
||||
if (!AttributeHelper.HasAttribute(member, AutoPausedFieldAttributeName, out var _))
|
||||
continue;
|
||||
|
||||
var type = member switch
|
||||
{
|
||||
IPropertySymbol property => property.Type,
|
||||
IFieldSymbol field => field.Type,
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (type is not INamedTypeSymbol namedType)
|
||||
continue;
|
||||
|
||||
var invalid = false;
|
||||
var nullable = false;
|
||||
if (namedType.Name != "TimeSpan")
|
||||
{
|
||||
if (namedType is { Name: "Nullable", TypeArguments: [{Name: "TimeSpan"}] })
|
||||
{
|
||||
nullable = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
invalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If any pause field has [AutoNetworkedField], automatically mark it to dirty on unpause.
|
||||
if (AttributeHelper.HasAttribute(member, AutoNetworkFieldAttributeName, out var _))
|
||||
dirty = true;
|
||||
|
||||
fieldBuilder.Add(new FieldInfo(member.Name, nullable, invalid, member.Locations[0]));
|
||||
}
|
||||
|
||||
return new ComponentInfo(
|
||||
partialTypeInfo,
|
||||
EquatableArray<FieldInfo>.FromImmutableArray(fieldBuilder.ToImmutable()),
|
||||
dirty,
|
||||
!TypeSymbolHelper.ImplementsInterface(symbol, IComponentTypeName),
|
||||
typeDeclarationSyntax.Identifier.GetLocation());
|
||||
});
|
||||
|
||||
context.RegisterImplementationSourceOutput(componentInfos, static (productionContext, info) =>
|
||||
{
|
||||
if (info.NotComponent)
|
||||
{
|
||||
productionContext.ReportDiagnostic(Diagnostic.Create(
|
||||
NotComponentDiagnostic,
|
||||
info.Location,
|
||||
info.PartialTypeInfo.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
// Component always have to be partial anyways due to the serialization generator.
|
||||
// So I can't be arsed to define a diagnostic for this.
|
||||
if (!info.PartialTypeInfo.IsValid)
|
||||
return;
|
||||
|
||||
if (info.Fields.AsImmutableArray().Length == 0)
|
||||
{
|
||||
productionContext.ReportDiagnostic(Diagnostic.Create(
|
||||
NoFieldsDiagnostic,
|
||||
info.Location,
|
||||
info.PartialTypeInfo.Name));
|
||||
return;
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("""
|
||||
// <auto-generated />
|
||||
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
""");
|
||||
|
||||
info.PartialTypeInfo.WriteHeader(builder);
|
||||
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("{");
|
||||
|
||||
builder.AppendLine($$"""
|
||||
[RobustAutoGenerated]
|
||||
public sealed class {{info.PartialTypeInfo.Name}}_AutoPauseSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
SubscribeLocalEvent<{{info.PartialTypeInfo.Name}}, EntityUnpausedEvent>(OnEntityUnpaused);
|
||||
}
|
||||
|
||||
private void OnEntityUnpaused(EntityUid uid, {{info.PartialTypeInfo.Name}} component, ref EntityUnpausedEvent args)
|
||||
{
|
||||
""");
|
||||
|
||||
var anyValidField = false;
|
||||
foreach (var field in info.Fields)
|
||||
{
|
||||
if (field.Invalid)
|
||||
{
|
||||
productionContext.ReportDiagnostic(Diagnostic.Create(WrongTypeAttributeDiagnostic, field.Location));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.Nullable)
|
||||
{
|
||||
builder.AppendLine($"""
|
||||
if (component.{field.Name}.HasValue)
|
||||
component.{field.Name} = component.{field.Name}.Value + args.PausedTime;
|
||||
""");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.AppendLine($" component.{field.Name} += args.PausedTime;");
|
||||
}
|
||||
|
||||
anyValidField = true;
|
||||
}
|
||||
|
||||
if (!anyValidField)
|
||||
return;
|
||||
|
||||
if (info.Dirty)
|
||||
builder.AppendLine(" Dirty(uid, component);");
|
||||
|
||||
builder.AppendLine("""
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
builder.AppendLine("}");
|
||||
|
||||
info.PartialTypeInfo.WriteFooter(builder);
|
||||
|
||||
productionContext.AddSource(info.PartialTypeInfo.GetGeneratedFileName(), builder.ToString());
|
||||
});
|
||||
|
||||
// Code to report diagnostic for fields that have it but don't have the attribute on the parent.
|
||||
var allFields = context.SyntaxProvider.ForAttributeWithMetadataName(
|
||||
AutoPausedFieldAttributeName,
|
||||
(syntaxNode, _) => syntaxNode is VariableDeclaratorSyntax or PropertyDeclarationSyntax,
|
||||
(syntaxContext, _) =>
|
||||
{
|
||||
var errorTarget = syntaxContext.TargetNode is PropertyDeclarationSyntax prop
|
||||
? prop.Identifier.GetLocation()
|
||||
: syntaxContext.TargetNode.GetLocation();
|
||||
return new AllFieldInfo(
|
||||
syntaxContext.TargetSymbol.Name,
|
||||
syntaxContext.TargetSymbol.ContainingType.ToDisplayString(),
|
||||
errorTarget);
|
||||
});
|
||||
|
||||
var allComponentsTogether = componentInfos.Collect();
|
||||
var allFieldsTogether = allFields.Collect();
|
||||
var componentFieldJoin = allFieldsTogether.Combine(allComponentsTogether);
|
||||
|
||||
context.RegisterImplementationSourceOutput(componentFieldJoin, (productionContext, info) =>
|
||||
{
|
||||
var componentsByName = new HashSet<string>(info.Right.Select(x => x.PartialTypeInfo.DisplayName));
|
||||
foreach (var field in info.Left)
|
||||
{
|
||||
if (!componentsByName.Contains(field.ParentDisplayName))
|
||||
{
|
||||
productionContext.ReportDiagnostic(
|
||||
Diagnostic.Create(NoParentAttributeDiagnostic, field.Location, field.Name));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public sealed record ComponentInfo(
|
||||
PartialTypeInfo PartialTypeInfo,
|
||||
EquatableArray<FieldInfo> Fields,
|
||||
bool Dirty,
|
||||
bool NotComponent,
|
||||
Location Location);
|
||||
|
||||
public sealed record FieldInfo(string Name, bool Nullable, bool Invalid, Location Location);
|
||||
|
||||
public sealed record AllFieldInfo(string Name, string ParentDisplayName, Location Location);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"Generators": {
|
||||
"commandName": "DebugRoslynComponent",
|
||||
"targetProject": "../../Content.Shared/Content.Shared.csproj"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>11</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Common" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" />
|
||||
</ItemGroup>
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -653,8 +653,8 @@ namespace Robust.Server
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
|
||||
// shut down networking, kicking all players.
|
||||
var shutdownReasonWithRedial = NetStructuredDisconnectMessages.Encode($"Server shutting down: {_shutdownReason}", true);
|
||||
_network.Shutdown(shutdownReasonWithRedial);
|
||||
var shutdownReasonWithRedial = new NetDisconnectMessage($"Server shutting down: {_shutdownReason}", true);
|
||||
_network.Shutdown(shutdownReasonWithRedial.Encode());
|
||||
|
||||
// shutdown entities
|
||||
_entityManager.Cleanup();
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
@@ -283,25 +284,25 @@ namespace Robust.Server.Console
|
||||
/// Get completions. Non-null results imply that the command was handled. If it is empty, it implies that
|
||||
/// there are no completions for this command.
|
||||
/// </summary>
|
||||
private ValueTask<CompletionResult?> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
|
||||
private async ValueTask<CompletionResult?> CalcCompletions(IConsoleShell shell, string[] args, string argStr)
|
||||
{
|
||||
// Logger.Debug(string.Join(", ", args));
|
||||
|
||||
if (args.Length <= 1)
|
||||
{
|
||||
// Typing out command name, handle this ourselves.
|
||||
return ValueTask.FromResult<CompletionResult?>(CompletionResult.FromOptions(
|
||||
AvailableCommands.Values.Where(c => ShellCanExecute(shell, c.Command)).Select(c => new CompletionOption(c.Command, c.Description))));
|
||||
return CompletionResult.FromOptions(
|
||||
AvailableCommands.Values.Where(c => ShellCanExecute(shell, c.Command)).Select(c => new CompletionOption(c.Command, c.Description)));
|
||||
}
|
||||
|
||||
var cmdName = args[0];
|
||||
if (!RegisteredCommands.TryGetValue(cmdName, out var cmd))
|
||||
return ValueTask.FromResult<CompletionResult?>(null);
|
||||
return null;
|
||||
|
||||
if (!ShellCanExecute(shell, cmdName))
|
||||
return ValueTask.FromResult<CompletionResult?>(CompletionResult.Empty);
|
||||
return CompletionResult.Empty;
|
||||
|
||||
return ValueTask.FromResult<CompletionResult?>(cmd.GetCompletion(shell, args[1..]));
|
||||
return await cmd.GetCompletionAsync(shell, args[1..], argStr, CancellationToken.None);
|
||||
}
|
||||
|
||||
private sealed class SudoShell : IConsoleShell
|
||||
|
||||
@@ -103,6 +103,40 @@ namespace Robust.Server.GameObjects
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RaiseSharedEvent<T>(T message, EntityUid? user = null)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
var filter = Filter.Broadcast().RemoveWhereAttachedEntity(e => e == user.Value);
|
||||
foreach (var session in filter.Recipients)
|
||||
{
|
||||
EntityNetManager.SendSystemNetworkMessage(message, session.Channel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityNetManager.SendSystemNetworkMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void RaiseSharedEvent<T>(T message, ICommonSession? user = null)
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
var filter = Filter.Broadcast().RemovePlayer(user);
|
||||
foreach (var session in filter.Recipients)
|
||||
{
|
||||
EntityNetManager.SendSystemNetworkMessage(message, session.Channel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EntityNetManager.SendSystemNetworkMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearTicks(EntityUid entity, EntityPrototype prototype)
|
||||
{
|
||||
foreach (var (netId, component) in GetNetComponents(entity))
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Server.GameStates;
|
||||
|
||||
public struct ChunkIndicesEnumerator
|
||||
{
|
||||
private Vector2i _bottomLeft;
|
||||
private Vector2i _topRight;
|
||||
|
||||
private int _x;
|
||||
private int _y;
|
||||
|
||||
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
|
||||
{
|
||||
var rangeVec = new Vector2(range, range);
|
||||
|
||||
_bottomLeft = ((viewPos - rangeVec) / chunkSize).Floored();
|
||||
// Also floor this as we get the whole chunk anyway.
|
||||
_topRight = ((viewPos + rangeVec) / chunkSize).Floored();
|
||||
|
||||
_x = _bottomLeft.X;
|
||||
_y = _bottomLeft.Y;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out Vector2i? chunkIndices)
|
||||
{
|
||||
if (_y > _topRight.Y)
|
||||
{
|
||||
_x++;
|
||||
_y = _bottomLeft.Y;
|
||||
}
|
||||
|
||||
if (_x > _topRight.X)
|
||||
{
|
||||
chunkIndices = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
chunkIndices = new Vector2i(_x, _y);
|
||||
|
||||
_y++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -12,6 +14,7 @@ namespace Robust.Server.GameStates;
|
||||
public sealed class PvsOverrideSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IConsoleHost _console = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _hasOverride = new();
|
||||
|
||||
@@ -28,8 +31,64 @@ public sealed class PvsOverrideSystem : EntitySystem
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridCreated);
|
||||
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
|
||||
|
||||
// TODO console commands for adding/removing overrides?
|
||||
_console.RegisterCommand(
|
||||
"pvs_override_info",
|
||||
Loc.GetString("cmd-pvs-override-info-desc"),
|
||||
"pvs_override_info",
|
||||
GetPvsInfo,
|
||||
GetCompletion);
|
||||
}
|
||||
|
||||
#region Console Commands
|
||||
|
||||
/// <summary>
|
||||
/// Debug command for displaying PVS override information.
|
||||
/// </summary>
|
||||
private void GetPvsInfo(IConsoleShell shell, string argstr, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetEntity.TryParse(args[0], out var nuid) || !TryGetEntity(nuid, out var uid))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-uid"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_hasOverride.Contains(uid.Value))
|
||||
{
|
||||
shell.WriteLine(Loc.GetString("cmd-pvs-override-info-empty", ("nuid", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
if (GlobalOverride.Contains(uid.Value) || ForceSend.Contains(uid.Value))
|
||||
shell.WriteLine(Loc.GetString("cmd-pvs-override-info-global", ("nuid", args[0])));
|
||||
|
||||
HashSet<ICommonSession> sessions = new();
|
||||
sessions.UnionWith(SessionOverrides.Where(x => x.Value.Contains(uid.Value)).Select(x => x.Key));
|
||||
sessions.UnionWith(SessionForceSend.Where(x => x.Value.Contains(uid.Value)).Select(x => x.Key));
|
||||
if (sessions.Count == 0)
|
||||
return;
|
||||
|
||||
var clients = string.Join(", ", sessions.Select(x => x.ToString()));
|
||||
shell.WriteLine(Loc.GetString("cmd-pvs-override-info-clients", ("nuid", args[0]), ("clients", clients)));
|
||||
}
|
||||
|
||||
private CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
if (args.Length != 1)
|
||||
return CompletionResult.Empty;
|
||||
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.NetEntities(args[0], EntityManager), "NetEntity");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs ev)
|
||||
{
|
||||
if (ev.NewStatus != SessionStatus.Disconnected)
|
||||
|
||||
@@ -8,6 +8,7 @@ using Prometheus;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Map.Enumerators;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using static Microsoft.CodeAnalysis.SymbolDisplayFormat;
|
||||
using static Microsoft.CodeAnalysis.SymbolDisplayMiscellaneousOptions;
|
||||
|
||||
namespace Robust.Shared.CompNetworkGenerator
|
||||
{
|
||||
@@ -19,20 +17,26 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
private const string GlobalEntityUidName = "global::Robust.Shared.GameObjects.EntityUid";
|
||||
private const string GlobalNullableEntityUidName = "global::Robust.Shared.GameObjects.EntityUid?";
|
||||
|
||||
private const string GlobalNetEntityName = "global::Robust.Shared.GameObjects.NetEntity";
|
||||
private const string GlobalNetEntityNullableName = "global::Robust.Shared.GameObjects.NetEntity?";
|
||||
|
||||
private const string GlobalEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates";
|
||||
private const string GlobalNullableEntityCoordinatesName = "global::Robust.Shared.Map.EntityCoordinates?";
|
||||
|
||||
private const string GlobalEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.EntityUid>";
|
||||
private const string GlobalNetEntityUidSetName = "global::System.Collections.Generic.HashSet<global::Robust.Shared.GameObjects.NetEntity>";
|
||||
private const string GlobalNetEntityUidSetName = $"global::System.Collections.Generic.HashSet<{GlobalNetEntityName}>";
|
||||
|
||||
private const string GlobalEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.EntityUid>";
|
||||
private const string GlobalNetEntityUidListName = "global::System.Collections.Generic.List<global::Robust.Shared.GameObjects.NetEntity>";
|
||||
private const string GlobalNetEntityUidListName = $"global::System.Collections.Generic.List<{GlobalNetEntityName}>";
|
||||
|
||||
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 static string GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
|
||||
private static readonly SymbolDisplayFormat FullNullableFormat =
|
||||
FullyQualifiedFormat.WithMiscellaneousOptions(IncludeNullableReferenceTypeModifier);
|
||||
|
||||
private static string? GenerateSource(in GeneratorExecutionContext context, INamedTypeSymbol classSymbol, CSharpCompilation comp, bool raiseAfterAutoHandle)
|
||||
{
|
||||
var nameSpace = classSymbol.ContainingNamespace.ToDisplayString();
|
||||
var componentName = classSymbol.Name;
|
||||
@@ -132,7 +136,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
|
||||
foreach (var (type, name) in fields)
|
||||
{
|
||||
var typeDisplayStr = type.ToDisplayString(FullyQualifiedFormat);
|
||||
var typeDisplayStr = type.ToDisplayString(FullNullableFormat);
|
||||
var nullable = type.NullableAnnotation == NullableAnnotation.Annotated;
|
||||
var nullableAnnotation = nullable ? "?" : string.Empty;
|
||||
|
||||
@@ -181,6 +185,62 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
|
||||
break;
|
||||
default:
|
||||
if (type is INamedTypeSymbol { TypeArguments.Length: 2 } named &&
|
||||
named.ConstructedFrom.ToDisplayString(FullyQualifiedFormat) == GlobalDictionaryName)
|
||||
{
|
||||
var key = named.TypeArguments[0].ToDisplayString(FullNullableFormat);
|
||||
var keyNullable = key.EndsWith("?");
|
||||
|
||||
var value = named.TypeArguments[1].ToDisplayString(FullNullableFormat);
|
||||
var valueNullable = value.EndsWith("?");
|
||||
|
||||
if (key is GlobalEntityUidName or GlobalNullableEntityUidName)
|
||||
{
|
||||
key = keyNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
|
||||
|
||||
var ensureGeneric = $"{componentName}, {value}";
|
||||
if (value is GlobalEntityUidName or GlobalNullableEntityUidName)
|
||||
{
|
||||
value = valueNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
|
||||
ensureGeneric = componentName;
|
||||
}
|
||||
|
||||
stateFields.Append($@"
|
||||
public Dictionary<{key}, {value}> {name} = default!;");
|
||||
|
||||
getStateInit.Append($@"
|
||||
{name} = GetNetEntityDictionary(component.{name}),");
|
||||
|
||||
if (valueNullable && value is not GlobalNetEntityName and not GlobalNetEntityNullableName)
|
||||
{
|
||||
handleStateSetters.Append($@"
|
||||
EnsureEntityDictionaryNullableValue<{componentName}, {value}>(state.{name}, uid, component.{name});");
|
||||
}
|
||||
else
|
||||
{
|
||||
handleStateSetters.Append($@"
|
||||
EnsureEntityDictionary<{ensureGeneric}>(state.{name}, uid, component.{name});");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (value is GlobalEntityUidName or GlobalNullableEntityUidName)
|
||||
{
|
||||
value = valueNullable ? GlobalNetEntityNullableName : GlobalNetEntityName;
|
||||
|
||||
stateFields.Append($@"
|
||||
public Dictionary<{key}, {value}> {name} = default!;");
|
||||
|
||||
getStateInit.Append($@"
|
||||
{name} = GetNetEntityDictionary(component.{name}),");
|
||||
handleStateSetters.Append($@"
|
||||
EnsureEntityDictionary<{componentName}, {key}>(state.{name}, uid, component.{name});");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
stateFields.Append($@"
|
||||
public {typeDisplayStr} {name} = default!;");
|
||||
|
||||
@@ -193,7 +253,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
|
||||
handleStateSetters.Append($@"
|
||||
if (state.{name} == null)
|
||||
component.{name} = null;
|
||||
component.{name} = null!;
|
||||
else
|
||||
component.{name} = new(state.{name});");
|
||||
}
|
||||
@@ -219,6 +279,7 @@ namespace Robust.Shared.CompNetworkGenerator
|
||||
}
|
||||
|
||||
return $@"// <auto-generated />
|
||||
#nullable enable
|
||||
using System;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -280,10 +341,10 @@ public partial class {componentName}
|
||||
{
|
||||
var attr = type.Attribute;
|
||||
var raiseEv = false;
|
||||
if (attr.ConstructorArguments.Length == 1 && attr.ConstructorArguments[0].Value != null)
|
||||
if (attr.ConstructorArguments is [{Value: bool raise}])
|
||||
{
|
||||
// Get the afterautohandle bool, which is first constructor arg
|
||||
raiseEv = (bool) attr.ConstructorArguments[0].Value;
|
||||
raiseEv = raise;
|
||||
}
|
||||
|
||||
var source = GenerateSource(context, type.Type, comp, raiseEv);
|
||||
@@ -325,11 +386,11 @@ public partial class {componentName}
|
||||
attr.AttributeClass != null &&
|
||||
attr.AttributeClass.Equals(attributeSymbol, SymbolEqualityComparer.Default));
|
||||
|
||||
if (typeSymbol == null)
|
||||
continue;
|
||||
|
||||
if (relevantAttribute == null)
|
||||
{
|
||||
if (typeSymbol == null)
|
||||
continue;
|
||||
|
||||
foreach (var mem in typeSymbol.GetMembers())
|
||||
{
|
||||
var attribute = mem.GetAttributes().FirstOrDefault(a =>
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -186,10 +186,10 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 Intersect(in Box2 other)
|
||||
{
|
||||
var ourLeftBottom = new Vector2(Left, Bottom);
|
||||
var ourRightTop = new Vector2(Right, Top);
|
||||
var otherLeftBottom = new Vector2(other.Left, other.Bottom);
|
||||
var otherRightTop = new Vector2(other.Right, other.Top);
|
||||
var ourLeftBottom = BottomLeft;
|
||||
var ourRightTop = TopRight;
|
||||
var otherLeftBottom = other.BottomLeft;
|
||||
var otherRightTop = other.TopRight;
|
||||
|
||||
var max = Vector2.Max(ourLeftBottom, otherLeftBottom);
|
||||
var min = Vector2.Min(ourRightTop, otherRightTop);
|
||||
@@ -219,10 +219,10 @@ namespace Robust.Shared.Maths
|
||||
[Pure]
|
||||
public readonly Box2 Union(in Box2 other)
|
||||
{
|
||||
var ourLeftBottom = new Vector2(Left, Bottom);
|
||||
var otherLeftBottom = new Vector2(other.Left, other.Bottom);
|
||||
var ourRightTop = new Vector2(Right, Top);
|
||||
var otherRightTop = new Vector2(other.Right, other.Top);
|
||||
var ourLeftBottom = BottomLeft;
|
||||
var otherLeftBottom = other.BottomLeft;
|
||||
var ourRightTop = TopRight;
|
||||
var otherRightTop = other.TopRight;
|
||||
|
||||
var leftBottom = Vector2.Min(ourLeftBottom, otherLeftBottom);
|
||||
var rightTop = Vector2.Max(ourRightTop, otherRightTop);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
@@ -57,7 +58,17 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// calculates the smallest AABB that will encompass the rotated box. The AABB is in local space.
|
||||
/// Enlarges the box by the specified value.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public readonly Box2Rotated Enlarged(float value)
|
||||
{
|
||||
var box = Box.Enlarged(value);
|
||||
return new Box2Rotated(box, Rotation, Origin);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the smallest AABB that will encompass the rotated box. The AABB is in local space.
|
||||
/// </summary>
|
||||
public readonly Box2 CalcBoundingBox()
|
||||
{
|
||||
|
||||
29
Robust.Shared/Analyzers/ComponentPauseGeneratorAttributes.cs
Normal file
29
Robust.Shared/Analyzers/ComponentPauseGeneratorAttributes.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
/// <summary>
|
||||
/// Indicate that a <see cref="Component"/> should automatically handle unpausing of timer fields.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When this attribute is set on a <see cref="Component"/>, an <see cref="EntitySystem"/> will automatically be
|
||||
/// generated that increments any fields tagged with <see cref="AutoPausedFieldAttribute"/> when the entity is unpaused
|
||||
/// (<see cref="EntityUnpausedEvent"/>).
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
[BaseTypeRequired(typeof(IComponent))]
|
||||
public sealed class AutoGenerateComponentPauseAttribute : Attribute
|
||||
{
|
||||
public bool Dirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark a field or property to automatically handle unpausing with <see cref="AutoGenerateComponentPauseAttribute"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The type of the field or prototype must be <see cref="TimeSpan"/> (potentially nullable).
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class AutoPausedFieldAttribute : Attribute;
|
||||
@@ -1031,6 +1031,12 @@ namespace Robust.Shared
|
||||
* AUDIO
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Default limit for concurrently playing an audio file.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> AudioDefaultConcurrent =
|
||||
CVarDef.Create("audio.default_concurrent", 16, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
public static readonly CVarDef<int> AudioAttenuation =
|
||||
CVarDef.Create("audio.attenuation", (int) Attenuation.LinearDistanceClamped, CVar.REPLICATED | CVar.ARCHIVE);
|
||||
|
||||
@@ -1498,7 +1504,7 @@ namespace Robust.Shared
|
||||
/// Maximum compressed size of a replay recording (in kilobytes) before recording automatically stops.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<long> ReplayMaxCompressedSize = CVarDef.Create("replay.max_compressed_size",
|
||||
1024L * 256, CVar.ARCHIVE);
|
||||
1024L * 512, CVar.ARCHIVE);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum uncompressed size of a replay recording (in kilobytes) before recording automatically stops.
|
||||
|
||||
@@ -6,7 +6,9 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Robust.Shared.Collections;
|
||||
|
||||
@@ -50,6 +52,41 @@ public struct ValueList<T> : IEnumerable<T>
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list by copying the contents of the source list.
|
||||
/// </summary>
|
||||
public ValueList(List<T> list) : this(list, 0, list.Count)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list by copying the contents of the source list.
|
||||
/// </summary>
|
||||
public ValueList(List<T> list, int start, int count)
|
||||
{
|
||||
_items = new T[count];
|
||||
|
||||
var liSpan = CollectionsMarshal.AsSpan(list)[start..(start + count)];
|
||||
liSpan.CopyTo(_items);
|
||||
|
||||
Count = count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a list by copying the contents of the source list.
|
||||
/// </summary>
|
||||
public ValueList(IReadOnlyCollection<T> list)
|
||||
{
|
||||
var count = list.Count;
|
||||
_items = new T[count];
|
||||
|
||||
foreach (var entry in list)
|
||||
{
|
||||
var size = Count;
|
||||
AddNoResize(entry, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a list by copying the contents from another enumerable.
|
||||
/// </summary>
|
||||
@@ -157,12 +194,10 @@ public struct ValueList<T> : IEnumerable<T>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(T item)
|
||||
{
|
||||
var array = _items;
|
||||
var size = Count;
|
||||
if ((uint)size < (uint)Capacity)
|
||||
{
|
||||
Count = size + 1;
|
||||
array![size] = item;
|
||||
AddNoResize(item, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -170,6 +205,14 @@ public struct ValueList<T> : IEnumerable<T>
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddNoResize(T item, int size)
|
||||
{
|
||||
var array = _items;
|
||||
Count = size + 1;
|
||||
array![size] = item;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref T AddRef()
|
||||
{
|
||||
@@ -522,4 +565,46 @@ public struct ValueList<T> : IEnumerable<T>
|
||||
RemoveAt(Count - 1);
|
||||
return old;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a range of values from the source list.
|
||||
/// </summary>
|
||||
public void AddRange(ValueList<T> list)
|
||||
{
|
||||
var liSpan = list.Span;
|
||||
AddRange(liSpan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a range of values from the source list.
|
||||
/// </summary>
|
||||
public void AddRange(List<T> list)
|
||||
{
|
||||
var liSpan = CollectionsMarshal.AsSpan(list);
|
||||
AddRange(liSpan);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddRange(Span<T> span)
|
||||
{
|
||||
var spanCount = span.Length;
|
||||
EnsureCapacity(Count + spanCount);
|
||||
var target = new Span<T>(_items, Count, spanCount);
|
||||
span.CopyTo(target);
|
||||
Count += spanCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fills this with default data up to the specified count.
|
||||
/// </summary>
|
||||
public void EnsureLength(int newCount)
|
||||
{
|
||||
if (Count > newCount)
|
||||
return;
|
||||
|
||||
EnsureCapacity(newCount);
|
||||
var region = new Span<T>(_items, Count, (newCount - Count));
|
||||
region.Clear();
|
||||
Count = newCount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,46 +71,6 @@ namespace Robust.Shared.Containers
|
||||
[DataField("showEnts")]
|
||||
public bool ShowContents { get; set; }
|
||||
|
||||
[Obsolete("Use container system method")]
|
||||
public bool Insert(
|
||||
EntityUid toinsert,
|
||||
IEntityManager? entMan = null,
|
||||
TransformComponent? transform = null,
|
||||
TransformComponent? ownerTransform = null,
|
||||
MetaDataComponent? meta = null,
|
||||
PhysicsComponent? physics = null,
|
||||
bool force = false)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
return entMan.System<SharedContainerSystem>().Insert((toinsert, transform, meta, physics), this, ownerTransform, force);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given entity can be inserted into this container.
|
||||
/// </summary>
|
||||
/// <param name="assumeEmpty">Whether to assume that the container is currently empty.</param>
|
||||
protected internal virtual bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) => true;
|
||||
|
||||
[Obsolete("Use container system method")]
|
||||
public bool Remove(
|
||||
EntityUid toRemove,
|
||||
IEntityManager? entMan = null,
|
||||
TransformComponent? xform = null,
|
||||
MetaDataComponent? meta = null,
|
||||
bool reparent = true,
|
||||
bool force = false,
|
||||
EntityCoordinates? destination = null,
|
||||
Angle? localRotation = null
|
||||
)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
return entMan.System<SharedContainerSystem>().Remove((toRemove, xform, meta), this, reparent, force, destination, localRotation);
|
||||
}
|
||||
|
||||
[Obsolete("Use container system method")]
|
||||
public void ForceRemove(EntityUid toRemove, IEntityManager? entMan = null, MetaDataComponent? meta = null)
|
||||
=> Remove(toRemove, entMan, meta: meta, reparent: false, force: true);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the entity is contained in this container.
|
||||
/// This is not recursive, so containers of children are not checked.
|
||||
@@ -120,18 +80,10 @@ namespace Robust.Shared.Containers
|
||||
public abstract bool Contains(EntityUid contained);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the container and marks it as deleted.
|
||||
/// Whether the given entity can be inserted into this container.
|
||||
/// </summary>
|
||||
[Obsolete("use system method")]
|
||||
public void Shutdown(IEntityManager? entMan = null, INetManager? _ = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
entMan.System<SharedContainerSystem>().ShutdownContainer(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[Access(typeof(SharedContainerSystem))]
|
||||
protected internal abstract void InternalShutdown(IEntityManager entMan, SharedContainerSystem system, bool isClient);
|
||||
/// <param name="assumeEmpty">Whether to assume that the container is currently empty.</param>
|
||||
protected internal virtual bool CanInsert(EntityUid toInsert, bool assumeEmpty, IEntityManager entMan) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Implement to store the reference in whatever form you want
|
||||
@@ -148,5 +100,14 @@ namespace Robust.Shared.Containers
|
||||
/// <param name="entMan"></param>
|
||||
[Access(typeof(SharedContainerSystem))]
|
||||
protected internal abstract void InternalRemove(EntityUid toRemove, IEntityManager entMan);
|
||||
|
||||
/// <summary>
|
||||
/// Implement to clear the container and mark it as deleted.
|
||||
/// </summary>
|
||||
/// <param name="entMan"></param>
|
||||
/// <param name="system"></param>
|
||||
/// <param name=isClient"></param>
|
||||
[Access(typeof(SharedContainerSystem))]
|
||||
protected internal abstract void InternalShutdown(IEntityManager entMan, SharedContainerSystem system, bool isClient);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
|
||||
String("short").ThenReturn(PrimitiveTypeCode.Int16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
|
||||
String("int").ThenReturn(PrimitiveTypeCode.Int32);
|
||||
|
||||
@@ -84,12 +84,146 @@ Types:
|
||||
- "bool get_HasContents()"
|
||||
Lidgren.Network:
|
||||
NetBuffer:
|
||||
All: True
|
||||
Methods:
|
||||
- "byte[] get_Data()"
|
||||
- "void set_Data(byte[])"
|
||||
- "int get_LengthBytes()"
|
||||
- "void set_LengthBytes(int)"
|
||||
- "int get_LengthBits()"
|
||||
- "void set_LengthBits(int)"
|
||||
- "long get_Position()"
|
||||
- "void set_Position(long)"
|
||||
- "int get_PositionInBytes()"
|
||||
- "byte[] PeekDataBuffer()"
|
||||
- "bool PeekBoolean()"
|
||||
- "byte PeekByte()"
|
||||
- "sbyte PeekSByte()"
|
||||
- "byte PeekByte(int)"
|
||||
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
|
||||
- "byte[] PeekBytes(int)"
|
||||
- "void PeekBytes(byte[], int, int)"
|
||||
- "short PeekInt16()"
|
||||
- "ushort PeekUInt16()"
|
||||
- "int PeekInt32()"
|
||||
- "int PeekInt32(int)"
|
||||
- "uint PeekUInt32()"
|
||||
- "uint PeekUInt32(int)"
|
||||
- "ulong PeekUInt64()"
|
||||
- "long PeekInt64()"
|
||||
- "ulong PeekUInt64(int)"
|
||||
- "long PeekInt64(int)"
|
||||
- "float PeekFloat()"
|
||||
- "System.Half PeekHalf()"
|
||||
- "float PeekSingle()"
|
||||
- "double PeekDouble()"
|
||||
- "string PeekString()"
|
||||
- "int PeekStringSize()"
|
||||
- "bool ReadBoolean()"
|
||||
- "byte ReadByte()"
|
||||
- "bool ReadByte(ref byte)"
|
||||
- "sbyte ReadSByte()"
|
||||
- "byte ReadByte(int)"
|
||||
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
|
||||
- "byte[] ReadBytes(int)"
|
||||
- "bool ReadBytes(int, ref byte[])"
|
||||
- "bool TryReadBytes(System.Span`1<byte>)"
|
||||
- "void ReadBytes(byte[], int, int)"
|
||||
- "void ReadBits(System.Span`1<byte>, int)"
|
||||
- "void ReadBits(byte[], int, int)"
|
||||
- "short ReadInt16()"
|
||||
- "ushort ReadUInt16()"
|
||||
- "int ReadInt32()"
|
||||
- "bool ReadInt32(ref int)"
|
||||
- "int ReadInt32(int)"
|
||||
- "uint ReadUInt32()"
|
||||
- "bool ReadUInt32(ref uint)"
|
||||
- "uint ReadUInt32(int)"
|
||||
- "ulong ReadUInt64()"
|
||||
- "long ReadInt64()"
|
||||
- "ulong ReadUInt64(int)"
|
||||
- "long ReadInt64(int)"
|
||||
- "float ReadFloat()"
|
||||
- "System.Half ReadHalf()"
|
||||
- "float ReadSingle()"
|
||||
- "bool ReadSingle(ref float)"
|
||||
- "double ReadDouble()"
|
||||
- "uint ReadVariableUInt32()"
|
||||
- "bool ReadVariableUInt32(ref uint)"
|
||||
- "int ReadVariableInt32()"
|
||||
- "long ReadVariableInt64()"
|
||||
- "ulong ReadVariableUInt64()"
|
||||
- "float ReadSignedSingle(int)"
|
||||
- "float ReadUnitSingle(int)"
|
||||
- "float ReadRangedSingle(float, float, int)"
|
||||
- "int ReadRangedInteger(int, int)"
|
||||
- "long ReadRangedInteger(long, long)"
|
||||
- "string ReadString()"
|
||||
- "bool ReadString(ref string)"
|
||||
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
|
||||
- "System.Net.IPEndPoint ReadIPEndPoint()"
|
||||
- "void SkipPadBits()"
|
||||
- "void ReadPadBits()"
|
||||
- "void SkipPadBits(int)"
|
||||
- "void EnsureBufferSize(int)"
|
||||
- "void Write(bool)"
|
||||
- "void Write(byte)"
|
||||
- "void WriteAt(int, byte)"
|
||||
- "void Write(sbyte)"
|
||||
- "void Write(byte, int)"
|
||||
- "void Write(byte[])"
|
||||
- "void Write(System.ReadOnlySpan`1<byte>)"
|
||||
- "void Write(byte[], int, int)"
|
||||
- "void Write(ushort)"
|
||||
- "void WriteAt(int, ushort)"
|
||||
- "void Write(ushort, int)"
|
||||
- "void Write(short)"
|
||||
- "void WriteAt(int, short)"
|
||||
- "void Write(int)"
|
||||
- "void WriteAt(int, int)"
|
||||
- "void Write(uint)"
|
||||
- "void WriteAt(int, uint)"
|
||||
- "void Write(uint, int)"
|
||||
- "void Write(int, int)"
|
||||
- "void Write(ulong)"
|
||||
- "void WriteAt(int, ulong)"
|
||||
- "void Write(ulong, int)"
|
||||
- "void Write(long)"
|
||||
- "void Write(long, int)"
|
||||
- "void Write(System.Half)"
|
||||
- "void Write(float)"
|
||||
- "void Write(double)"
|
||||
- "int WriteVariableUInt32(uint)"
|
||||
- "int WriteVariableInt32(int)"
|
||||
- "int WriteVariableInt64(long)"
|
||||
- "int WriteVariableUInt64(ulong)"
|
||||
- "void WriteSignedSingle(float, int)"
|
||||
- "void WriteUnitSingle(float, int)"
|
||||
- "void WriteRangedSingle(float, float, float, int)"
|
||||
- "int WriteRangedInteger(int, int, int)"
|
||||
- "int WriteRangedInteger(long, long, long)"
|
||||
- "void Write(string)"
|
||||
- "void Write(System.Net.IPEndPoint)"
|
||||
- "void WriteTime(bool)"
|
||||
- "void WriteTime(double, bool)"
|
||||
- "void WritePadBits()"
|
||||
- "void WritePadBits(int)"
|
||||
- "void Write(Lidgren.Network.NetBuffer)"
|
||||
- "void Zero(int)"
|
||||
- "void .ctor()"
|
||||
NetDeliveryMethod: { }
|
||||
NetIncomingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
|
||||
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
|
||||
- "int get_SequenceChannel()"
|
||||
- "System.Net.IPEndPoint get_SenderEndPoint()"
|
||||
- "Lidgren.Network.NetConnection get_SenderConnection()"
|
||||
- "double get_ReceiveTime()"
|
||||
- "double ReadTime(bool)"
|
||||
- "string ToString()"
|
||||
NetOutgoingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "string ToString()"
|
||||
Nett:
|
||||
CommentLocation: { } # Enum
|
||||
Toml:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
|
||||
path = path.Directory;
|
||||
|
||||
var fullPath = GetFullPath(path);
|
||||
Process.Start(new ProcessStartInfo
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = fullPath,
|
||||
});
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "xdg-open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Opening OS windows not supported on this OS");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -279,6 +278,66 @@ public partial class EntityManager
|
||||
return entities;
|
||||
}
|
||||
|
||||
public Dictionary<EntityUid, T> GetEntityDictionary<T>(Dictionary<NetEntity, T> netEntities)
|
||||
{
|
||||
var entities = new Dictionary<EntityUid, T>(netEntities.Count);
|
||||
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(GetEntity(pair.Key), pair.Value);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public Dictionary<T, EntityUid> GetEntityDictionary<T>(Dictionary<T, NetEntity> netEntities) where T : notnull
|
||||
{
|
||||
var entities = new Dictionary<T, EntityUid>(netEntities.Count);
|
||||
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(pair.Key, GetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public Dictionary<T, EntityUid?> GetEntityDictionary<T>(Dictionary<T, NetEntity?> netEntities) where T : notnull
|
||||
{
|
||||
var entities = new Dictionary<T, EntityUid?>(netEntities.Count);
|
||||
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(pair.Key, GetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public Dictionary<EntityUid, EntityUid> GetEntityDictionary(Dictionary<NetEntity, NetEntity> netEntities)
|
||||
{
|
||||
var entities = new Dictionary<EntityUid, EntityUid>(netEntities.Count);
|
||||
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(GetEntity(pair.Key), GetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public Dictionary<EntityUid, EntityUid?> GetEntityDictionary(Dictionary<NetEntity, NetEntity?> netEntities)
|
||||
{
|
||||
var entities = new Dictionary<EntityUid, EntityUid?>(netEntities.Count);
|
||||
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(GetEntity(pair.Key), GetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> EnsureEntitySet<T>(HashSet<NetEntity> netEntities, EntityUid callerEntity)
|
||||
{
|
||||
var entities = new HashSet<EntityUid>(netEntities.Count);
|
||||
@@ -324,6 +383,72 @@ public partial class EntityManager
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureEntityDictionary<TComp, TValue>(Dictionary<NetEntity, TValue> netEntities, EntityUid callerEntity,
|
||||
Dictionary<EntityUid, TValue> entities)
|
||||
{
|
||||
entities.Clear();
|
||||
entities.EnsureCapacity(netEntities.Count);
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureEntityDictionaryNullableValue<TComp, TValue>(Dictionary<NetEntity, TValue?> netEntities, EntityUid callerEntity,
|
||||
Dictionary<EntityUid, TValue?> entities)
|
||||
{
|
||||
entities.Clear();
|
||||
entities.EnsureCapacity(netEntities.Count);
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), pair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity> netEntities, EntityUid callerEntity,
|
||||
Dictionary<TKey, EntityUid> entities) where TKey : notnull
|
||||
{
|
||||
entities.Clear();
|
||||
entities.EnsureCapacity(netEntities.Count);
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(pair.Key, EnsureEntity<TComp>(pair.Value, callerEntity));
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity?> netEntities, EntityUid callerEntity,
|
||||
Dictionary<TKey, EntityUid?> entities) where TKey : notnull
|
||||
{
|
||||
entities.Clear();
|
||||
entities.EnsureCapacity(netEntities.Count);
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(pair.Key, EnsureEntity<TComp>(pair.Value, callerEntity));
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity> netEntities, EntityUid callerEntity,
|
||||
Dictionary<EntityUid, EntityUid> entities)
|
||||
{
|
||||
entities.Clear();
|
||||
entities.EnsureCapacity(netEntities.Count);
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), EnsureEntity<TComp>(pair.Value, callerEntity));
|
||||
}
|
||||
}
|
||||
|
||||
public void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity?> netEntities, EntityUid callerEntity,
|
||||
Dictionary<EntityUid, EntityUid?> entities)
|
||||
{
|
||||
entities.Clear();
|
||||
entities.EnsureCapacity(netEntities.Count);
|
||||
foreach (var pair in netEntities)
|
||||
{
|
||||
entities.Add(EnsureEntity<TComp>(pair.Key, callerEntity), EnsureEntity<TComp>(pair.Value, callerEntity));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<EntityUid> GetEntityList(ICollection<NetEntity> netEntities)
|
||||
{
|
||||
@@ -467,6 +592,71 @@ public partial class EntityManager
|
||||
return netEntities;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<NetEntity, T> GetNetEntityDictionary<T>(Dictionary<EntityUid, T> entities)
|
||||
{
|
||||
var netEntities = new Dictionary<NetEntity, T>(entities.Count);
|
||||
|
||||
foreach (var pair in entities)
|
||||
{
|
||||
netEntities.Add(GetNetEntity(pair.Key), pair.Value);
|
||||
}
|
||||
|
||||
return netEntities;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<T, NetEntity> GetNetEntityDictionary<T>(Dictionary<T, EntityUid> entities) where T : notnull
|
||||
{
|
||||
var netEntities = new Dictionary<T, NetEntity>(entities.Count);
|
||||
|
||||
foreach (var pair in entities)
|
||||
{
|
||||
netEntities.Add(pair.Key, GetNetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return netEntities;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<T, NetEntity?> GetNetEntityDictionary<T>(Dictionary<T, EntityUid?> entities) where T : notnull
|
||||
{
|
||||
var netEntities = new Dictionary<T, NetEntity?>(entities.Count);
|
||||
|
||||
foreach (var pair in entities)
|
||||
{
|
||||
netEntities.Add(pair.Key, GetNetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return netEntities;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<NetEntity, NetEntity> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid> entities)
|
||||
{
|
||||
var netEntities = new Dictionary<NetEntity, NetEntity>(entities.Count);
|
||||
|
||||
foreach (var pair in entities)
|
||||
{
|
||||
netEntities.Add(GetNetEntity(pair.Key), GetNetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return netEntities;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<NetEntity, NetEntity?> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid?> entities)
|
||||
{
|
||||
var netEntities = new Dictionary<NetEntity, NetEntity?>(entities.Count);
|
||||
|
||||
foreach (var pair in entities)
|
||||
{
|
||||
netEntities.Add(GetNetEntity(pair.Key), GetNetEntity(pair.Value));
|
||||
}
|
||||
|
||||
return netEntities;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public HashSet<EntityCoordinates> GetEntitySet(HashSet<NetCoordinates> netEntities)
|
||||
{
|
||||
|
||||
@@ -906,6 +906,16 @@ namespace Robust.Shared.GameObjects
|
||||
DebugTools.Assert("Why are you raising predictive events on the server?");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises an event locally on client or networked on server.
|
||||
/// </summary>
|
||||
public abstract void RaiseSharedEvent<T>(T message, EntityUid? user = null) where T : EntityEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Raises an event locally on client or networked on server.
|
||||
/// </summary>
|
||||
public abstract void RaiseSharedEvent<T>(T message, ICommonSession? user = null) where T : EntityEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for generating a new EntityUid for an entity currently being created.
|
||||
/// </summary>
|
||||
|
||||
@@ -1049,6 +1049,42 @@ public partial class EntitySystem
|
||||
EntityManager.EnsureEntityList<T>(netEntities, callerEntity, entities);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void EnsureEntityDictionary<TComp, TValue>(Dictionary<NetEntity, TValue> netEntities, EntityUid callerEntity, Dictionary<EntityUid, TValue> entities)
|
||||
{
|
||||
EntityManager.EnsureEntityDictionary<TComp, TValue>(netEntities, callerEntity, entities);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void EnsureEntityDictionaryNullableValue<TComp, TValue>(Dictionary<NetEntity, TValue?> netEntities, EntityUid callerEntity, Dictionary<EntityUid, TValue?> entities)
|
||||
{
|
||||
EntityManager.EnsureEntityDictionaryNullableValue<TComp, TValue>(netEntities, callerEntity, entities);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity> netEntities, EntityUid callerEntity, Dictionary<TKey, EntityUid> entities) where TKey : notnull
|
||||
{
|
||||
EntityManager.EnsureEntityDictionary<TComp, TKey>(netEntities, callerEntity, entities);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity?> netEntities, EntityUid callerEntity, Dictionary<TKey, EntityUid?> entities) where TKey : notnull
|
||||
{
|
||||
EntityManager.EnsureEntityDictionary<TComp, TKey>(netEntities, callerEntity, entities);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity> netEntities, EntityUid callerEntity, Dictionary<EntityUid, EntityUid> entities)
|
||||
{
|
||||
EntityManager.EnsureEntityDictionary<TComp>(netEntities, callerEntity, entities);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity?> netEntities, EntityUid callerEntity, Dictionary<EntityUid, EntityUid?> entities)
|
||||
{
|
||||
EntityManager.EnsureEntityDictionary<TComp>(netEntities, callerEntity, entities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="EntityUid"/> of a <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
|
||||
/// </summary>
|
||||
@@ -1184,6 +1220,96 @@ public partial class EntitySystem
|
||||
return EntityManager.GetEntityArray(netEntities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<NetEntity, T> GetNetEntityDictionary<T>(Dictionary<EntityUid, T> uids)
|
||||
{
|
||||
return EntityManager.GetNetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<T, NetEntity> GetNetEntityDictionary<T>(Dictionary<T, EntityUid> uids) where T : notnull
|
||||
{
|
||||
return EntityManager.GetNetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<T, NetEntity?> GetNetEntityDictionary<T>(Dictionary<T, EntityUid?> uids) where T : notnull
|
||||
{
|
||||
return EntityManager.GetNetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<NetEntity, NetEntity> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid> uids)
|
||||
{
|
||||
return EntityManager.GetNetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="NetEntity"/> versions of the supplied entities. Logs an error if the entities do not exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<NetEntity, NetEntity?> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid?> uids)
|
||||
{
|
||||
return EntityManager.GetNetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<EntityUid, T> GetEntityDictionary<T>(Dictionary<NetEntity, T> uids)
|
||||
{
|
||||
return EntityManager.GetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<T, EntityUid> GetEntityDictionary<T>(Dictionary<T, NetEntity> uids) where T : notnull
|
||||
{
|
||||
return EntityManager.GetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<T, EntityUid?> GetEntityDictionary<T>(Dictionary<T, NetEntity?> uids) where T : notnull
|
||||
{
|
||||
return EntityManager.GetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<EntityUid, EntityUid> GetEntityDictionary(Dictionary<NetEntity, NetEntity> uids)
|
||||
{
|
||||
return EntityManager.GetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the <see cref="EntityUid"/> versions of the supplied <see cref="NetEntity"/>. Returns <see cref="EntityUid.Invalid"/> if it doesn't exist.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected Dictionary<EntityUid, EntityUid?> GetEntityDictionary(Dictionary<NetEntity, NetEntity?> uids)
|
||||
{
|
||||
return EntityManager.GetEntityDictionary(uids);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected NetCoordinates GetNetCoordinates(EntityCoordinates coordinates, MetaDataComponent? metadata = null)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Map;
|
||||
@@ -107,6 +106,16 @@ public partial interface IEntityManager
|
||||
/// </summary>
|
||||
EntityUid?[] GetEntityArray(NetEntity?[] netEntities);
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary version of <see cref="GetEntity"/>
|
||||
/// </summary>
|
||||
Dictionary<EntityUid, T> GetEntityDictionary<T>(Dictionary<NetEntity, T> netEntities);
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary version of <see cref="GetEntity"/>
|
||||
/// </summary>
|
||||
Dictionary<T, EntityUid> GetEntityDictionary<T>(Dictionary<T, NetEntity> netEntities) where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// HashSet version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
@@ -117,6 +126,11 @@ public partial interface IEntityManager
|
||||
/// </summary>
|
||||
public List<NetEntity> GetNetEntityList(List<EntityUid> entities);
|
||||
|
||||
/// <summary>
|
||||
/// List version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
List<NetEntity> GetNetEntityList(IReadOnlyList<EntityUid> entities);
|
||||
|
||||
/// <summary>
|
||||
/// List version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
@@ -128,15 +142,40 @@ public partial interface IEntityManager
|
||||
public List<NetEntity?> GetNetEntityList(List<EntityUid?> entities);
|
||||
|
||||
/// <summary>
|
||||
/// List version of <see cref="GetNetEntity"/>
|
||||
/// Array version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
NetEntity[] GetNetEntityArray(EntityUid[] entities);
|
||||
|
||||
/// <summary>
|
||||
/// List version of <see cref="GetNetEntity"/>
|
||||
/// Array version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
NetEntity?[] GetNetEntityArray(EntityUid?[] entities);
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
Dictionary<NetEntity, T> GetNetEntityDictionary<T>(Dictionary<EntityUid, T> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
Dictionary<T, NetEntity> GetNetEntityDictionary<T>(Dictionary<T, EntityUid> entities) where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
Dictionary<T, NetEntity?> GetNetEntityDictionary<T>(Dictionary<T, EntityUid?> entities) where T : notnull;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
Dictionary<NetEntity, NetEntity> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary version of <see cref="GetNetEntity"/>
|
||||
/// </summary>
|
||||
Dictionary<NetEntity, NetEntity?> GetNetEntityDictionary(Dictionary<EntityUid, EntityUid?> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the corresponding <see cref="NetCoordinates"/> for the specified local coordinates.
|
||||
/// </summary>
|
||||
@@ -178,6 +217,27 @@ public partial interface IEntityManager
|
||||
|
||||
public List<EntityUid> EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity);
|
||||
|
||||
void EnsureEntityList<T>(List<NetEntity> netEntities, EntityUid callerEntity, List<EntityUid> entities);
|
||||
|
||||
void EnsureEntityDictionary<TComp, TValue>(Dictionary<NetEntity, TValue> netEntities, EntityUid callerEntity,
|
||||
Dictionary<EntityUid, TValue> entities);
|
||||
|
||||
void EnsureEntityDictionaryNullableValue<TComp, TValue>(Dictionary<NetEntity, TValue?> netEntities,
|
||||
EntityUid callerEntity,
|
||||
Dictionary<EntityUid, TValue?> entities);
|
||||
|
||||
void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity> netEntities, EntityUid callerEntity,
|
||||
Dictionary<TKey, EntityUid> entities) where TKey : notnull;
|
||||
|
||||
void EnsureEntityDictionary<TComp, TKey>(Dictionary<TKey, NetEntity?> netEntities, EntityUid callerEntity,
|
||||
Dictionary<TKey, EntityUid?> entities) where TKey : notnull;
|
||||
|
||||
void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity> netEntities, EntityUid callerEntity,
|
||||
Dictionary<EntityUid, EntityUid> entities);
|
||||
|
||||
void EnsureEntityDictionary<TComp>(Dictionary<NetEntity, NetEntity?> netEntities, EntityUid callerEntity,
|
||||
Dictionary<EntityUid, EntityUid?> entities);
|
||||
|
||||
public List<EntityCoordinates> GetEntityList(ICollection<NetCoordinates> netEntities);
|
||||
|
||||
public List<EntityCoordinates?> GetEntityList(List<NetCoordinates?> netEntities);
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -744,9 +745,20 @@ public sealed partial class EntityLookupSystem
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetEntitiesInRange(EntityCoordinates coordinates, float range, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var ents = new HashSet<EntityUid>();
|
||||
GetEntitiesInRange(coordinates, range, ents, flags);
|
||||
return ents;
|
||||
}
|
||||
|
||||
public void GetEntitiesInRange(EntityCoordinates coordinates, float range, HashSet<EntityUid> entities, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var mapPos = coordinates.ToMap(EntityManager, _transform);
|
||||
return GetEntitiesInRange(mapPos, range, flags);
|
||||
|
||||
if (mapPos.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
GetEntitiesInRange(mapPos.MapId, mapPos.Position, range, entities, flags);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -127,7 +127,31 @@ public abstract partial class SharedMapSystem
|
||||
SubscribeLocalEvent<MapGridComponent, MoveEvent>(OnGridMove);
|
||||
}
|
||||
|
||||
public void OnGridBoundsChange(EntityUid uid, MapGridComponent component)
|
||||
/// <summary>
|
||||
/// <see cref="GetGridPosition(Robust.Shared.GameObjects.Entity{Robust.Shared.Physics.Components.PhysicsComponent?},System.Numerics.Vector2,Robust.Shared.Maths.Angle)"/>
|
||||
/// </summary>
|
||||
public Vector2 GetGridPosition(Entity<PhysicsComponent?> grid, Vector2 worldPos, Angle worldRot)
|
||||
{
|
||||
if (!Resolve(grid.Owner, ref grid.Comp))
|
||||
return Vector2.Zero;
|
||||
|
||||
return worldPos + worldRot.RotateVec(grid.Comp.LocalCenter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mapgrid's position considering its local physics center.
|
||||
/// </summary>
|
||||
public Vector2 GetGridPosition(Entity<PhysicsComponent?, TransformComponent?> grid)
|
||||
{
|
||||
if (!Resolve(grid.Owner, ref grid.Comp1, ref grid.Comp2))
|
||||
return Vector2.Zero;
|
||||
|
||||
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(grid.Comp2);
|
||||
|
||||
return GetGridPosition((grid.Owner, grid.Comp1), worldPos, worldRot);
|
||||
}
|
||||
|
||||
private void OnGridBoundsChange(EntityUid uid, MapGridComponent component)
|
||||
{
|
||||
// Just MapLoader things.
|
||||
if (component.MapProxy == DynamicTree.Proxy.Free) return;
|
||||
@@ -193,7 +217,7 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
if (xform.ParentUid != xform.MapUid && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
|
||||
{
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} it not parented to a map. y'all need jesus. {Environment.StackTrace}");
|
||||
Log.Error($"Grid {ToPrettyString(uid, meta)} is not parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -974,19 +998,34 @@ public abstract partial class SharedMapSystem
|
||||
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2 worldAABB)
|
||||
{
|
||||
var localAABB = _transform.GetInvWorldMatrix(uid).TransformBox(worldAABB);
|
||||
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
|
||||
return GetLocalMapChunks(uid, grid, localAABB);
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetMapChunks(EntityUid uid, MapGridComponent grid, Box2Rotated worldArea)
|
||||
{
|
||||
var matrix = _transform.GetInvWorldMatrix(uid);
|
||||
var localArea = matrix.TransformBox(worldArea);
|
||||
return new ChunkEnumerator(grid.Chunks, localArea, grid.ChunkSize);
|
||||
return GetLocalMapChunks(uid, grid, localArea);
|
||||
}
|
||||
|
||||
internal ChunkEnumerator GetLocalMapChunks(EntityUid uid, MapGridComponent grid, Box2 localAABB)
|
||||
{
|
||||
return new ChunkEnumerator(grid.Chunks, localAABB, grid.ChunkSize);
|
||||
Box2 compAABB;
|
||||
|
||||
// The entire area intersects.
|
||||
if (_mapQuery.HasComponent(uid))
|
||||
{
|
||||
compAABB = localAABB;
|
||||
}
|
||||
else
|
||||
{
|
||||
compAABB = grid.LocalAABB.Intersect(localAABB);
|
||||
}
|
||||
|
||||
if (compAABB.IsEmpty())
|
||||
return ChunkEnumerator.Empty;
|
||||
|
||||
return new ChunkEnumerator(grid.Chunks, compAABB, grid.ChunkSize);
|
||||
}
|
||||
|
||||
#endregion ChunkAccess
|
||||
@@ -1348,8 +1387,36 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
public EntityCoordinates GridTileToLocal(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
{
|
||||
return new(uid,
|
||||
new Vector2(gridTile.X * grid.TileSize + (grid.TileSize / 2f), gridTile.Y * grid.TileSize + (grid.TileSize / 2f)));
|
||||
var position = TileCenterToVector(uid, grid, gridTile);
|
||||
|
||||
return new(uid, position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns a gridtile origin into a Vector2, accounting for tile size.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 TileToVector(Entity<MapGridComponent> grid, Vector2i gridTile)
|
||||
{
|
||||
return new Vector2(gridTile.X * grid.Comp.TileSize, gridTile.Y * grid.Comp.TileSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns a gridtile center into a Vector2, accounting for tile size.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 TileCenterToVector(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
{
|
||||
return TileCenterToVector((uid, grid), gridTile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns a gridtile center into a Vector2, accounting for tile size.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vector2 TileCenterToVector(Entity<MapGridComponent> grid, Vector2i gridTile)
|
||||
{
|
||||
return new Vector2(gridTile.X * grid.Comp.TileSize, gridTile.Y * grid.Comp.TileSize) + grid.Comp.TileSizeHalfVector;
|
||||
}
|
||||
|
||||
public Vector2 GridTileToWorldPos(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -67,6 +68,16 @@ public abstract partial class SharedTransformSystem
|
||||
return new MapCoordinates(worldPos, xform.MapID);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts entity-local coordinates into map terms.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public MapCoordinates ToMapCoordinates(NetCoordinates coordinates)
|
||||
{
|
||||
var eCoords = GetCoordinates(coordinates);
|
||||
return ToMapCoordinates(eCoords);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates EntityCoordinates given an entity and some MapCoordinates.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map.Enumerators;
|
||||
|
||||
internal struct ChunkEnumerator
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty enumerator that will return nothing.
|
||||
/// </summary>
|
||||
public static ChunkEnumerator Empty => new(new Dictionary<Vector2i, MapChunk>(), Box2.Empty, 1);
|
||||
|
||||
private Dictionary<Vector2i, MapChunk> _chunks;
|
||||
private Vector2i _chunkLB;
|
||||
private Vector2i _chunkRT;
|
||||
|
||||
59
Robust.Shared/Map/Enumerators/ChunkIndicesEnumerator.cs
Normal file
59
Robust.Shared/Map/Enumerators/ChunkIndicesEnumerator.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Map.Enumerators;
|
||||
|
||||
/// <summary>
|
||||
/// Generic iterator for chunk indices for the specified bounds with the specified chunk size.
|
||||
/// </summary>
|
||||
public struct ChunkIndicesEnumerator
|
||||
{
|
||||
private readonly Vector2i _chunkLB;
|
||||
private readonly Vector2i _chunkRT;
|
||||
|
||||
private int _xIndex;
|
||||
private int _yIndex;
|
||||
|
||||
public ChunkIndicesEnumerator(Vector2 viewPos, float range, float chunkSize)
|
||||
{
|
||||
var rangeVec = new Vector2(range, range);
|
||||
|
||||
_chunkLB = ((viewPos - rangeVec) / chunkSize).Floored();
|
||||
// Also floor this as we get the whole chunk anyway.
|
||||
_chunkRT = ((viewPos + rangeVec) / chunkSize).Floored();
|
||||
|
||||
_xIndex = _chunkLB.X;
|
||||
_yIndex = _chunkLB.Y;
|
||||
}
|
||||
|
||||
public ChunkIndicesEnumerator(Box2 localAABB, int chunkSize)
|
||||
{
|
||||
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
|
||||
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
|
||||
|
||||
_xIndex = _chunkLB.X;
|
||||
_yIndex = _chunkLB.Y;
|
||||
}
|
||||
|
||||
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
|
||||
{
|
||||
if (_yIndex > _chunkRT.Y)
|
||||
{
|
||||
_yIndex = _chunkLB.Y;
|
||||
_xIndex++;
|
||||
}
|
||||
|
||||
if (_xIndex > _chunkRT.X)
|
||||
{
|
||||
indices = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
indices = new Vector2i(_xIndex, _yIndex);
|
||||
_yIndex++;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
{
|
||||
@@ -31,9 +30,10 @@ namespace Robust.Shared.Network
|
||||
/// </summary>
|
||||
public sealed class NetConnectingArgs : EventArgs
|
||||
{
|
||||
public bool IsDenied => DenyReason != null;
|
||||
public bool IsDenied => DenyReasonData != null;
|
||||
|
||||
public string? DenyReason { get; private set; }
|
||||
public string? DenyReason => DenyReasonData?.Text;
|
||||
public NetDenyReason? DenyReasonData { get; private set; }
|
||||
|
||||
public NetUserData UserData { get; }
|
||||
|
||||
@@ -48,7 +48,12 @@ namespace Robust.Shared.Network
|
||||
|
||||
public void Deny(string reason)
|
||||
{
|
||||
DenyReason = reason;
|
||||
Deny(new NetDenyReason(reason));
|
||||
}
|
||||
|
||||
public void Deny(NetDenyReason reason)
|
||||
{
|
||||
DenyReasonData = reason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,12 +70,29 @@ namespace Robust.Shared.Network
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains a reason for denying a client connection to the game server.
|
||||
/// </summary>
|
||||
/// <param name="Text">The textual reason, presented to the user.</param>
|
||||
/// <param name="AdditionalProperties">
|
||||
/// Additional JSON properties that will be included in the <see cref="NetDisconnectMessage"/>.
|
||||
/// Valid value types are: string, int, float, bool.
|
||||
/// </param>
|
||||
/// <seealso cref="NetDisconnectMessage"/>
|
||||
/// <seealso cref="NetConnectingArgs"/>
|
||||
public record NetDenyReason(string Text, Dictionary<string, object> AdditionalProperties)
|
||||
{
|
||||
public NetDenyReason(string Text) : this(Text, new Dictionary<string, object>())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Structured reason common interface.
|
||||
/// </summary>
|
||||
public interface INetStructuredReason
|
||||
{
|
||||
JsonObject StructuredReason { get; }
|
||||
NetDisconnectMessage Message { get; }
|
||||
string Reason { get; }
|
||||
bool RedialFlag { get; }
|
||||
}
|
||||
@@ -80,33 +102,33 @@ namespace Robust.Shared.Network
|
||||
/// </summary>
|
||||
public sealed class NetConnectFailArgs : EventArgs, INetStructuredReason
|
||||
{
|
||||
public NetConnectFailArgs(string reason) : this(NetStructuredDisconnectMessages.Decode(reason))
|
||||
public NetConnectFailArgs(string reason) : this(NetDisconnectMessage.Decode(reason))
|
||||
{
|
||||
}
|
||||
|
||||
public NetConnectFailArgs(JsonObject reason)
|
||||
internal NetConnectFailArgs(NetDisconnectMessage reason)
|
||||
{
|
||||
StructuredReason = reason;
|
||||
Message = reason;
|
||||
}
|
||||
|
||||
public JsonObject StructuredReason { get; }
|
||||
public string Reason => NetStructuredDisconnectMessages.ReasonOf(StructuredReason);
|
||||
public bool RedialFlag => NetStructuredDisconnectMessages.RedialFlagOf(StructuredReason);
|
||||
public NetDisconnectMessage Message { get; }
|
||||
public string Reason => Message.Reason;
|
||||
public bool RedialFlag => Message.RedialFlag;
|
||||
}
|
||||
|
||||
public sealed class NetDisconnectedArgs : NetChannelArgs, INetStructuredReason
|
||||
{
|
||||
public NetDisconnectedArgs(INetChannel channel, string reason) : this(channel, NetStructuredDisconnectMessages.Decode(reason))
|
||||
public NetDisconnectedArgs(INetChannel channel, string reason) : this(channel, NetDisconnectMessage.Decode(reason))
|
||||
{
|
||||
}
|
||||
|
||||
public NetDisconnectedArgs(INetChannel channel, JsonObject reason) : base(channel)
|
||||
internal NetDisconnectedArgs(INetChannel channel, NetDisconnectMessage reason) : base(channel)
|
||||
{
|
||||
StructuredReason = reason;
|
||||
Message = reason;
|
||||
}
|
||||
|
||||
public JsonObject StructuredReason { get; }
|
||||
public string Reason => NetStructuredDisconnectMessages.ReasonOf(StructuredReason);
|
||||
public bool RedialFlag => NetStructuredDisconnectMessages.RedialFlagOf(StructuredReason);
|
||||
public NetDisconnectMessage Message { get; }
|
||||
public string Reason => Message.Reason;
|
||||
public bool RedialFlag => Message.RedialFlag;
|
||||
}
|
||||
}
|
||||
|
||||
259
Robust.Shared/Network/NetDisconnectMessage.cs
Normal file
259
Robust.Shared/Network/NetDisconnectMessage.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Network;
|
||||
|
||||
// Why did this dinky class grow to this LOC...
|
||||
|
||||
/// <summary>
|
||||
/// Stores structured information about why a connection was denied or disconnected.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The core networking layer (Lidgren) allows passing plain strings for disconnect reasons.
|
||||
/// We can beam a structured format (like JSON) over this,
|
||||
/// but Lidgren also generates messages internally (such as on timeout).
|
||||
/// This class is responsible for bridging the two to produce consistent results.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Disconnect messages are just a simple key/value format.
|
||||
/// Valid value types are <see cref="int"/>, <see cref="float"/>, <see cref="bool"/>, and <see cref="string"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class NetDisconnectMessage
|
||||
{
|
||||
private const string LidgrenDisconnectedPrefix = "Disconnected: ";
|
||||
|
||||
/// <summary>
|
||||
/// The reason given if none was included in the structured message.
|
||||
/// </summary>
|
||||
internal const string DefaultReason = "unknown reason";
|
||||
|
||||
/// <summary>
|
||||
/// The default redial flag given if none was included in the structured message.
|
||||
/// </summary>
|
||||
internal const bool DefaultRedialFlag = false;
|
||||
|
||||
/// <summary>
|
||||
/// The key of the <see cref="Reason"/> value.
|
||||
/// </summary>
|
||||
public const string ReasonKey = "reason";
|
||||
|
||||
/// <summary>
|
||||
/// The key of the <see cref="RedialFlag"/> value.
|
||||
/// </summary>
|
||||
public const string RedialKey = "redial";
|
||||
|
||||
internal readonly Dictionary<string, object> Values;
|
||||
|
||||
internal NetDisconnectMessage(Dictionary<string, object> values)
|
||||
{
|
||||
Values = values;
|
||||
}
|
||||
|
||||
internal NetDisconnectMessage(
|
||||
string reason = DefaultReason,
|
||||
bool redialFlag = DefaultRedialFlag)
|
||||
{
|
||||
Values = new Dictionary<string, object>
|
||||
{
|
||||
{ ReasonKey, reason },
|
||||
{ RedialKey, redialFlag }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The human-readable reason for why the disconnection happened.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReasonKey"/>
|
||||
public string Reason => StringOf(ReasonKey, DefaultReason);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the client should "redial" to reconnect to the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Redial means the client gets restarted by the launcher, to enable an update to occur.
|
||||
/// This is generally set if the disconnection reason is some sort of version mismatch.
|
||||
/// </remarks>
|
||||
/// <seealso cref="RedialKey"/>
|
||||
public bool RedialFlag => BoolOf(RedialKey, DefaultRedialFlag);
|
||||
|
||||
/// <summary>
|
||||
/// Decode from a disconnect message.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If structured JSON can be extracted, it is used.
|
||||
/// Otherwise, or if the format is invalid, the entire input is returned as disconnect reason.
|
||||
/// </para>
|
||||
/// <para>Invalid JSON values (e.g. arrays) are discarded.</para>
|
||||
/// </remarks>
|
||||
/// <param name="text">The disconnect reason from Lidgren's disconnect message.</param>
|
||||
internal static NetDisconnectMessage Decode(string text)
|
||||
{
|
||||
var start = text.AsMemory().TrimStart();
|
||||
// Lidgren generates this prefix internally.
|
||||
if (start.Span.StartsWith(LidgrenDisconnectedPrefix))
|
||||
start = start[LidgrenDisconnectedPrefix.Length..];
|
||||
// If it starts with { it's probably a JSON object.
|
||||
if (start.Span.StartsWith("{"))
|
||||
{
|
||||
try
|
||||
{
|
||||
using var node = JsonDocument.Parse(start);
|
||||
DebugTools.Assert(node.RootElement.ValueKind == JsonValueKind.Object);
|
||||
return JsonToReason(node.RootElement);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Discard the exception
|
||||
}
|
||||
}
|
||||
|
||||
// Something went wrong. That probably means it's not a structured reason.
|
||||
// Or worst case scenario, some poor end-user has to look at half-broken JSON.
|
||||
return new NetDisconnectMessage(new Dictionary<string, object>
|
||||
{
|
||||
{ ReasonKey, text }
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode to a textual string, that can be embedded into a disconnect message.
|
||||
/// </summary>
|
||||
internal string Encode()
|
||||
{
|
||||
return JsonSerializer.Serialize(Values);
|
||||
}
|
||||
|
||||
private static NetDisconnectMessage JsonToReason(JsonElement obj)
|
||||
{
|
||||
DebugTools.Assert(obj.ValueKind == JsonValueKind.Object);
|
||||
|
||||
var dict = new Dictionary<string, object>();
|
||||
foreach (var property in obj.EnumerateObject())
|
||||
{
|
||||
object value;
|
||||
switch (property.Value.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
value = property.Value.GetString()!;
|
||||
break;
|
||||
case JsonValueKind.Number:
|
||||
if (property.Value.TryGetInt32(out var valueInt))
|
||||
value = valueInt;
|
||||
else
|
||||
value = property.Value.GetSingle();
|
||||
break;
|
||||
case JsonValueKind.True:
|
||||
case JsonValueKind.False:
|
||||
value = property.Value.GetBoolean();
|
||||
break;
|
||||
default:
|
||||
// Discard invalid values intentionally.
|
||||
continue;
|
||||
}
|
||||
|
||||
dict[property.Name] = value;
|
||||
}
|
||||
|
||||
return new NetDisconnectMessage(dict);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <returns>
|
||||
/// Null if no such value exists, otherwise an object of one of the valid types (int, float, string, bool).
|
||||
/// </returns>
|
||||
public object? ValueOf(string key)
|
||||
{
|
||||
return Values.GetValueOrDefault(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="string"/> value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="string"/> value with the given key,
|
||||
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
|
||||
/// </returns>
|
||||
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||
public string? StringOf(string key, string? defaultValue = null)
|
||||
{
|
||||
if (ValueOf(key) is not string valueString)
|
||||
return defaultValue;
|
||||
|
||||
return valueString;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="bool"/> value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="bool"/> value with the given key, or <see langword="null" /> if no such value exists or it's a different type.
|
||||
/// </returns>
|
||||
public bool? BoolOf(string key) => ValueOf(key) as bool?;
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="bool"/> value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="bool"/> value with the given key,
|
||||
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
|
||||
/// </returns>
|
||||
public bool BoolOf(string key, bool defaultValue) => BoolOf(key) ?? defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="int"/> value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="int"/> value with the given key, or <see langword="null" /> if no such value exists or it's a different type.
|
||||
/// </returns>
|
||||
public int? Int32Of(string key) => ValueOf(key) as int?;
|
||||
|
||||
/// <summary>
|
||||
/// Get an <see cref="Int32"/> value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="Int32"/> value with the given key,
|
||||
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
|
||||
/// </returns>
|
||||
public int Int32Of(string key, int defaultValue) => Int32Of(key) ?? defaultValue;
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="float"/> value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="float"/> value with the given key, or <see langword="null" /> if no such value exists or it's a different type.
|
||||
/// </returns>
|
||||
public float? SingleOf(string key)
|
||||
{
|
||||
var value = ValueOf(key);
|
||||
return value as float? ?? value as int?;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a <see cref="Single"/> value by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the value to look up.</param>
|
||||
/// <param name="defaultValue">Default value to return if the value does not exist or is the wrong type.</param>
|
||||
/// <returns>
|
||||
/// The <see cref="Single"/> value with the given key,
|
||||
/// or <paramref name="defaultValue"/> if no such value exists or it's a different type.
|
||||
/// </returns>
|
||||
public float SingleOf(string key, float defaultValue) => SingleOf(key) ?? defaultValue;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
partial class NetManager
|
||||
{
|
||||
private readonly static string DisconnectReasonWrongKey = NetStructuredDisconnectMessages.Encode("Token decryption failed.\nPlease reconnect to this server from the launcher.", true);
|
||||
private static readonly string DisconnectReasonWrongKey = new NetDisconnectMessage("Token decryption failed.\nPlease reconnect to this server from the launcher.", true).Encode();
|
||||
|
||||
private readonly byte[] _cryptoPrivateKey = new byte[CryptoBox.SecretKeyBytes];
|
||||
|
||||
@@ -211,9 +211,15 @@ namespace Robust.Shared.Network
|
||||
|
||||
var endPoint = connection.RemoteEndPoint;
|
||||
var connect = await OnConnecting(endPoint, userData, type);
|
||||
if (connect.IsDenied)
|
||||
if (connect.DenyReasonData is { } deny)
|
||||
{
|
||||
connection.Disconnect($"Connection denied: {connect.DenyReason}");
|
||||
var denyMsg = $"Connect denied: {deny.Text}";
|
||||
var structured = new NetDisconnectMessage(denyMsg);
|
||||
foreach (var (k, v) in deny.AdditionalProperties)
|
||||
{
|
||||
structured.Values[k] = v;
|
||||
}
|
||||
connection.Disconnect(structured.Encode());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace Robust.Shared.Network;
|
||||
|
||||
/// <summary>
|
||||
/// Structured disconnection utilities.
|
||||
/// These use JsonNode so that Content may add it's own data.
|
||||
/// Note that to prevent encoding a NetStructuredDisco value within a NetStructuredDisco value,
|
||||
/// these should be encoded at the "highest level".
|
||||
/// Whatever generates the final "reason" value is responsible for performing NetStructuredDisco.Encode.
|
||||
/// </summary>
|
||||
public static class NetStructuredDisconnectMessages
|
||||
{
|
||||
public const string ReasonKey = "reason";
|
||||
public const string RedialKey = "redial";
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a structured disconnect message into a JsonObject.
|
||||
/// That can then be extended with additional properties.
|
||||
/// </summary>
|
||||
public static JsonObject EncodeObject(string text, bool redialFlag = false)
|
||||
{
|
||||
JsonObject obj = new();
|
||||
obj[ReasonKey] = text;
|
||||
obj[RedialKey] = redialFlag;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a structured disconnect message.
|
||||
/// Note that using this kind of gets in the way of adding content properties.
|
||||
/// </summary>
|
||||
public static string Encode(string text, bool redialFlag = false) => Encode(EncodeObject(text, redialFlag));
|
||||
|
||||
/// <summary>
|
||||
/// Encodes a structured disconnect message from a JsonObject.
|
||||
/// </summary>
|
||||
public static string Encode(JsonObject obj) => obj.ToJsonString();
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a structured disconnect message.
|
||||
/// This is designed assuming the input isn't necessarily a structured disconnect message.
|
||||
/// As such this will always produce output that can be passed to ReasonOf.
|
||||
/// </summary>
|
||||
public static JsonObject Decode(string text)
|
||||
{
|
||||
var start = text.AsSpan().TrimStart();
|
||||
// Lidgren generates this prefix internally.
|
||||
var lidgrenDisconnectedPrefix = "Disconnected: ";
|
||||
if (start.StartsWith(lidgrenDisconnectedPrefix))
|
||||
start = start.Slice(lidgrenDisconnectedPrefix.Length);
|
||||
// If it starts with { it's probably a JSON object.
|
||||
if (start.StartsWith("{"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var node = JsonNode.Parse(new string(start));
|
||||
if (node != null)
|
||||
return (JsonObject)node;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Discard the exception
|
||||
}
|
||||
}
|
||||
|
||||
// Something went wrong, so...
|
||||
JsonObject fallback = new();
|
||||
fallback[ReasonKey] = text;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a property as a JsonValue.
|
||||
/// </summary>
|
||||
public static JsonValue? ValueOf(JsonObject obj, string key)
|
||||
{
|
||||
if (obj.TryGetPropertyValue(key, out var val))
|
||||
if (val is JsonValue)
|
||||
return ((JsonValue) val);
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a string property.
|
||||
/// </summary>
|
||||
public static string StringOf(JsonObject obj, string key, string def)
|
||||
{
|
||||
var val = ValueOf(obj, key);
|
||||
if (val != null && val.TryGetValue(out string? res))
|
||||
return res;
|
||||
return def;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Grab the redial flag.
|
||||
/// </summary>
|
||||
public static bool BoolOf(JsonObject obj, string key, bool def)
|
||||
{
|
||||
var val = ValueOf(obj, key);
|
||||
if (val != null && val.TryGetValue(out bool res))
|
||||
return res;
|
||||
return def;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decode a reason.
|
||||
/// </summary>
|
||||
public static string ReasonOf(JsonObject obj) => StringOf(obj, ReasonKey, "unknown reason");
|
||||
|
||||
/// <summary>
|
||||
/// Grab the redial flag.
|
||||
/// </summary>
|
||||
public static bool RedialFlagOf(JsonObject obj) => BoolOf(obj, RedialKey, false);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Serialization;
|
||||
namespace Robust.Shared.Network
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public struct NetUserId : IEquatable<NetUserId>
|
||||
public struct NetUserId : IEquatable<NetUserId>, ISelfSerialize
|
||||
{
|
||||
public readonly Guid UserId;
|
||||
|
||||
@@ -32,5 +32,15 @@ namespace Robust.Shared.Network
|
||||
|
||||
public static implicit operator Guid(NetUserId id) => id.UserId;
|
||||
public static explicit operator NetUserId(Guid id) => new(id);
|
||||
|
||||
void ISelfSerialize.Deserialize(string value)
|
||||
{
|
||||
this = (NetUserId) Guid.Parse(value);
|
||||
}
|
||||
|
||||
string ISelfSerialize.Serialize()
|
||||
{
|
||||
return ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
internal static class PhysicsConstants
|
||||
public static class PhysicsConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// The radius of the polygon/edge shape skin. This should not be modified. Making
|
||||
|
||||
@@ -73,6 +73,74 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
#region Collision Masks & Layers
|
||||
|
||||
/// <summary>
|
||||
/// Similar to IsHardCollidable but also checks whether both entities are set to CanCollide
|
||||
/// </summary>
|
||||
public bool IsCurrentlyHardCollidable(Entity<FixturesComponent?, PhysicsComponent?> bodyA, Entity<FixturesComponent?, PhysicsComponent?> bodyB)
|
||||
{
|
||||
if (!_fixturesQuery.Resolve(bodyA, ref bodyA.Comp1, false) ||
|
||||
!_fixturesQuery.Resolve(bodyB, ref bodyB.Comp1, false) ||
|
||||
!PhysicsQuery.Resolve(bodyA, ref bodyA.Comp2, false) ||
|
||||
!PhysicsQuery.Resolve(bodyA, ref bodyB.Comp2, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bodyA.Comp2.CanCollide ||
|
||||
!bodyB.Comp2.CanCollide)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsHardCollidable(bodyA, bodyB);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if both entities are hard-collidable with each other.
|
||||
/// </summary>
|
||||
public bool IsHardCollidable(Entity<FixturesComponent?, PhysicsComponent?> bodyA, Entity<FixturesComponent?, PhysicsComponent?> bodyB)
|
||||
{
|
||||
if (!_fixturesQuery.Resolve(bodyA, ref bodyA.Comp1, false) ||
|
||||
!_fixturesQuery.Resolve(bodyB, ref bodyB.Comp1, false) ||
|
||||
!PhysicsQuery.Resolve(bodyA, ref bodyA.Comp2, false) ||
|
||||
!PhysicsQuery.Resolve(bodyA, ref bodyB.Comp2, false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fast check
|
||||
if (!bodyA.Comp2.Hard ||
|
||||
!bodyB.Comp2.Hard ||
|
||||
((bodyA.Comp2.CollisionLayer & bodyB.Comp2.CollisionMask) == 0x0 &&
|
||||
(bodyA.Comp2.CollisionMask & bodyB.Comp2.CollisionLayer) == 0x0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Slow check
|
||||
foreach (var fix in bodyA.Comp1.Fixtures.Values)
|
||||
{
|
||||
if (!fix.Hard)
|
||||
continue;
|
||||
|
||||
foreach (var other in bodyB.Comp1.Fixtures.Values)
|
||||
{
|
||||
if (!other.Hard)
|
||||
continue;
|
||||
|
||||
if ((fix.CollisionLayer & other.CollisionMask) == 0x0 &&
|
||||
(fix.CollisionMask & other.CollisionLayer) == 0x0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddCollisionMask(EntityUid uid, string fixtureId, Fixture fixture, int mask, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if ((fixture.CollisionMask & mask) == mask) return;
|
||||
|
||||
@@ -56,17 +56,17 @@ namespace Robust.Shared.Physics
|
||||
return simplified;
|
||||
}
|
||||
|
||||
private bool IsCollinear(in Vector2 prev, in Vector2 current, in Vector2 next, float tolerance)
|
||||
public static bool IsCollinear(in Vector2 prev, in Vector2 current, in Vector2 next, float tolerance)
|
||||
{
|
||||
return FloatInRange(Area(in prev, in current, in next), -tolerance, tolerance);
|
||||
}
|
||||
|
||||
private float Area(in Vector2 a, in Vector2 b, in Vector2 c)
|
||||
private static float Area(in Vector2 a, in Vector2 b, in Vector2 c)
|
||||
{
|
||||
return a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y);
|
||||
}
|
||||
|
||||
private bool FloatInRange(float value, float min, float max)
|
||||
private static bool FloatInRange(float value, float min, float max)
|
||||
{
|
||||
return (value >= min && value <= max);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -197,6 +198,42 @@ namespace Robust.Shared.Player
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes players from the filter.
|
||||
/// </summary>
|
||||
public Filter RemovePlayers(IEnumerable<ICommonSession> players)
|
||||
{
|
||||
foreach (var player in players)
|
||||
_recipients.Remove(player);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes players from the filter.
|
||||
/// </summary>
|
||||
public Filter RemovePlayers(params ICommonSession[] players) => RemovePlayers(players);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a single player from the filter, specified by the entity to which they are attached.
|
||||
/// </summary>
|
||||
public Filter RemovePlayerByAttachedEntity(EntityUid uid)
|
||||
{
|
||||
return RemoveWhereAttachedEntity(e => e == uid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes players from the filter, specified by the entities to which they are attached.
|
||||
/// </summary>
|
||||
public Filter RemovePlayersByAttachedEntity(IEnumerable<EntityUid> uids)
|
||||
{
|
||||
return RemoveWhereAttachedEntity(e => uids.Contains(e));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes players from the filter, specified by the entities to which they are attached.
|
||||
/// </summary>
|
||||
public Filter RemovePlayersByAttachedEntity(params EntityUid[] uids) => RemovePlayersByAttachedEntity(uids);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all players from the filter that match a predicate.
|
||||
/// </summary>
|
||||
|
||||
@@ -108,6 +108,12 @@ public interface IPrototypeManager
|
||||
/// <inheritdoc cref="HasIndex{T}(string)"/>
|
||||
bool HasIndex<T>(ProtoId<T> id) where T : class, IPrototype;
|
||||
|
||||
/// <inheritdoc cref="HasIndex{T}(string)"/>
|
||||
bool HasIndex(EntProtoId? id);
|
||||
|
||||
/// <inheritdoc cref="HasIndex{T}(string)"/>
|
||||
bool HasIndex<T>(ProtoId<T>? id) where T : class, IPrototype;
|
||||
|
||||
bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
bool TryIndex(Type kind, string id, [NotNullWhen(true)] out IPrototype? prototype);
|
||||
|
||||
@@ -130,6 +136,12 @@ public interface IPrototypeManager
|
||||
/// <inheritdoc cref="TryIndex{T}(string, out T)"/>
|
||||
bool TryIndex<T>(ProtoId<T> id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
|
||||
/// <inheritdoc cref="TryIndex{T}(string, out T)"/>
|
||||
bool TryIndex(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype);
|
||||
|
||||
/// <inheritdoc cref="TryIndex{T}(string, out T)"/>
|
||||
bool TryIndex<T>(ProtoId<T>? id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype;
|
||||
|
||||
bool HasMapping<T>(string id);
|
||||
bool TryGetMapping(Type kind, string id, [NotNullWhen(true)] out MappingDataNode? mappings);
|
||||
|
||||
|
||||
@@ -644,6 +644,24 @@ namespace Robust.Shared.Prototypes
|
||||
return HasIndex<T>(id.Id);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasIndex(EntProtoId? id)
|
||||
{
|
||||
if (id == null)
|
||||
return false;
|
||||
|
||||
return HasIndex(id.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasIndex<T>(ProtoId<T>? id) where T : class, IPrototype
|
||||
{
|
||||
if (id == null)
|
||||
return false;
|
||||
|
||||
return HasIndex(id.Value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryIndex<T>(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype
|
||||
{
|
||||
@@ -675,6 +693,30 @@ namespace Robust.Shared.Prototypes
|
||||
return TryIndex(id.Id, out prototype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryIndex(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype)
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
prototype = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryIndex(id.Value, out prototype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryIndex<T>(ProtoId<T>? id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype
|
||||
{
|
||||
if (id == null)
|
||||
{
|
||||
prototype = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryIndex(id.Value, out prototype);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasMapping<T>(string id)
|
||||
{
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
using System;
|
||||
namespace Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Serialization
|
||||
/// <summary>
|
||||
/// Provides a method that gets executed after deserialization is complete and a method that gets executed before serialization
|
||||
/// </summary>
|
||||
[RequiresExplicitImplementation]
|
||||
public interface ISerializationHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a method that gets executed after deserialization is complete and a method that gets executed before serialization
|
||||
/// Gets executed after deserialization is complete
|
||||
/// </summary>
|
||||
[RequiresExplicitImplementation]
|
||||
[Obsolete($"Avoid using ISerializationHooks in favour of (Custom)TypeSerializers or ComponentInit-Events.")]
|
||||
public interface ISerializationHooks
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets executed after deserialization is complete
|
||||
/// </summary>
|
||||
void AfterDeserialization() {}
|
||||
}
|
||||
void AfterDeserialization() {}
|
||||
}
|
||||
|
||||
@@ -57,16 +57,16 @@ internal sealed class ParallelManager : IParallelManagerInternal
|
||||
// This lets us avoid re-allocating the ManualResetEventSlims constantly when we just need a way to signal job completion.
|
||||
|
||||
private readonly ObjectPool<InternalJob> _jobPool =
|
||||
new DefaultObjectPool<InternalJob>(new DefaultPooledObjectPolicy<InternalJob>(), 256);
|
||||
new DefaultObjectPool<InternalJob>(new DefaultPooledObjectPolicy<InternalJob>(), 1024);
|
||||
|
||||
private readonly ObjectPool<InternalParallelJob> _parallelPool =
|
||||
new DefaultObjectPool<InternalParallelJob>(new DefaultPooledObjectPolicy<InternalParallelJob>(), 256);
|
||||
new DefaultObjectPool<InternalParallelJob>(new DefaultPooledObjectPolicy<InternalParallelJob>(), 1024);
|
||||
|
||||
/// <summary>
|
||||
/// Used internally for Parallel jobs, for external callers it gets garbage collected.
|
||||
/// </summary>
|
||||
private readonly ObjectPool<ParallelTracker> _trackerPool =
|
||||
new DefaultObjectPool<ParallelTracker>(new DefaultPooledObjectPolicy<ParallelTracker>());
|
||||
new DefaultObjectPool<ParallelTracker>(new DefaultPooledObjectPolicy<ParallelTracker>(), 1024);
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
@@ -8,9 +8,27 @@ namespace Robust.Shared.Utility
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the specified array has the specified length.
|
||||
/// </summary>
|
||||
public static void EnsureLength<T>(ref T[] array, int length)
|
||||
{
|
||||
if (array.Length > length)
|
||||
return;
|
||||
|
||||
Array.Resize(ref array, length);
|
||||
}
|
||||
|
||||
public static IList<T> Clone<T>(this IList<T> listToClone) where T : ICloneable
|
||||
{
|
||||
return listToClone.Select(item => (T)item.Clone()).ToList();
|
||||
var clone = new List<T>(listToClone.Count);
|
||||
|
||||
foreach (var value in listToClone)
|
||||
{
|
||||
clone.Add((T) value.Clone());
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -35,6 +35,15 @@ public sealed partial class FormattedMessage
|
||||
_nodes.AddRange(ParseSafe(markup));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Same as <see cref="AddMarkup"/> but adds a newline too.
|
||||
/// </summary>
|
||||
public void PushMarkup(string markup)
|
||||
{
|
||||
AddMarkup(markup);
|
||||
PushNewline();
|
||||
}
|
||||
|
||||
// > wtf I love parser combinators now
|
||||
// - PJB 13 Oct 2019
|
||||
// this tbh - Julian 26 Jan 2023
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Numerics;
|
||||
using NUnit.Framework;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.UnitTesting.Client.UserInterface.Controls;
|
||||
|
||||
[TestFixture]
|
||||
[TestOf(typeof(TextEdit))]
|
||||
public sealed class TextEditTest : RobustUnitTest
|
||||
{
|
||||
public override UnitTestProject Project => UnitTestProject.Client;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void Setup()
|
||||
{
|
||||
IoCManager.Resolve<IUserInterfaceManagerInternal>().InitializeTesting();
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/space-wizards/RobustToolbox/issues/4953
|
||||
// It was possible to move the cursor up/down if there was multi-line placeholder text.
|
||||
[Test]
|
||||
public void TestInvalidMoveInPlaceholder()
|
||||
{
|
||||
var textEdit = new TextEdit { Placeholder = new Rope.Leaf("Foo\nBar") };
|
||||
textEdit.Arrange(new UIBox2(0, 0, 200, 200));
|
||||
|
||||
var click = new GUIBoundKeyEventArgs(EngineKeyFunctions.TextCursorDown, BoundKeyState.Down, new ScreenCoordinates(), true, Vector2.Zero, Vector2.Zero);
|
||||
textEdit.KeyBindDown(click);
|
||||
textEdit.KeyBindUp(click);
|
||||
|
||||
Assert.That(textEdit.CursorPosition.Index, Is.Zero);
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/space-wizards/RobustToolbox/issues/4957
|
||||
// Moving left (with the arrow keys) in an empty TextEdit would cause an exception.
|
||||
[Test]
|
||||
public void TestEmptyMoveLeft()
|
||||
{
|
||||
var textEdit = new TextEdit();
|
||||
textEdit.Arrange(new UIBox2(0, 0, 200, 200));
|
||||
|
||||
var click = new GUIBoundKeyEventArgs(EngineKeyFunctions.TextCursorLeft, BoundKeyState.Down, new ScreenCoordinates(), true, Vector2.Zero, Vector2.Zero);
|
||||
textEdit.KeyBindDown(click);
|
||||
textEdit.KeyBindUp(click);
|
||||
}
|
||||
}
|
||||
113
Robust.UnitTesting/Shared/Networking/NetDisconnectMessageTest.cs
Normal file
113
Robust.UnitTesting/Shared/Networking/NetDisconnectMessageTest.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.Network;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Networking;
|
||||
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.All)]
|
||||
[TestOf(typeof(NetDisconnectMessage))]
|
||||
internal sealed class NetDisconnectMessageTest
|
||||
{
|
||||
[Test]
|
||||
[TestCase("Disconnected: bye", "Disconnected: bye", NetDisconnectMessage.DefaultRedialFlag)]
|
||||
[TestCase("Disconnected: {\"reason\": \"bye\"}", "bye", NetDisconnectMessage.DefaultRedialFlag)]
|
||||
[TestCase("Disconnected: {}", NetDisconnectMessage.DefaultReason, NetDisconnectMessage.DefaultRedialFlag)]
|
||||
[TestCase("Disconnected: {\"redial\": true}", NetDisconnectMessage.DefaultReason, true)]
|
||||
[TestCase("Disconnected: {\"redial\": true, \"foobar\": 5}", NetDisconnectMessage.DefaultReason, true)]
|
||||
[TestCase("Disconnected: {\"redial\": true, \"foobar\": 5, \"reason\": \"asdf\"}", "asdf", true)]
|
||||
[TestCase("Disconnected: {", "Disconnected: {", NetDisconnectMessage.DefaultRedialFlag)]
|
||||
[TestCase("Disconnected: {\"a\":[]}", NetDisconnectMessage.DefaultReason, NetDisconnectMessage.DefaultRedialFlag)]
|
||||
[TestCase("{\"redial\": true, \"foobar\": 5, \"reason\": \"asdf\"}", "asdf", true)]
|
||||
public void TestBasicDecode(string encoded, string reasonExpected, bool redialExpected)
|
||||
{
|
||||
var parsed = NetDisconnectMessage.Decode(encoded);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parsed.Reason, Is.EqualTo(reasonExpected));
|
||||
Assert.That(parsed.RedialFlag, Is.EqualTo(redialExpected));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEncode()
|
||||
{
|
||||
var value = new NetDisconnectMessage("foobar", true)
|
||||
{
|
||||
Values =
|
||||
{
|
||||
["asdf"] = 20.5f
|
||||
}
|
||||
};
|
||||
|
||||
var encoded = value.Encode();
|
||||
TestContext.Write($"Encoded: {encoded}\n");
|
||||
var decodedAgain = NetDisconnectMessage.Decode(encoded);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(decodedAgain.Reason, Is.EqualTo("foobar"));
|
||||
Assert.That(decodedAgain.RedialFlag, Is.EqualTo(true));
|
||||
Assert.That(decodedAgain.SingleOf("asdf"), Is.EqualTo(20.5f));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDefaultConstructor()
|
||||
{
|
||||
var value = new NetDisconnectMessage("foobar");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(value.Reason, Is.EqualTo("foobar"));
|
||||
Assert.That(value.RedialFlag, Is.EqualTo(false));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestValueOfInt()
|
||||
{
|
||||
var parsed = NetDisconnectMessage.Decode("{\"foobar\": 5}");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parsed.Int32Of("foobar"), Is.EqualTo(5));
|
||||
Assert.That(parsed.Int32Of("asdf"), Is.Null);
|
||||
Assert.That(parsed.Int32Of("asdf", 7), Is.EqualTo(7));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestValueOfFloat()
|
||||
{
|
||||
var parsed = NetDisconnectMessage.Decode("{\"foobar\": 5.5}");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parsed.SingleOf("foobar"), Is.EqualTo(5.5f));
|
||||
Assert.That(parsed.SingleOf("asdf"), Is.Null);
|
||||
Assert.That(parsed.SingleOf("asdf", 7), Is.EqualTo(7f));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestValueOfFloatInt()
|
||||
{
|
||||
var parsed = NetDisconnectMessage.Decode("{\"foobar\": 5}");
|
||||
|
||||
Assert.That(parsed.SingleOf("foobar"), Is.EqualTo(5f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestValueOfString()
|
||||
{
|
||||
var parsed = NetDisconnectMessage.Decode("{\"foobar\": \"real\"}");
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(parsed.StringOf("foobar"), Is.EqualTo("real"));
|
||||
Assert.That(parsed.StringOf("asdf"), Is.Null);
|
||||
Assert.That(parsed.StringOf("asdf", "honk"), Is.EqualTo("honk"));
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user