mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea9d3e9ed0 | ||
|
|
e97596ef51 | ||
|
|
d70ea7f78e | ||
|
|
95eeffb7eb | ||
|
|
0123cbcc57 | ||
|
|
588543d3c0 | ||
|
|
3a86430ce0 | ||
|
|
ba0cbffdc3 | ||
|
|
c7f7030b89 | ||
|
|
54218fb40b | ||
|
|
7b2c1701f3 | ||
|
|
0e3930d7d7 | ||
|
|
a9aea7027f | ||
|
|
2a49c2d9b8 | ||
|
|
a0c069f1ea | ||
|
|
2c6fb95e53 | ||
|
|
afe337644e | ||
|
|
b8924f3ddf | ||
|
|
08970e745b | ||
|
|
0ba4a66787 | ||
|
|
75b3431ee6 | ||
|
|
c0ef976588 | ||
|
|
fe5cdf9e3c | ||
|
|
450349188b | ||
|
|
897ad998d9 | ||
|
|
635ae3c353 | ||
|
|
a4ea5a4620 | ||
|
|
90e87526d0 | ||
|
|
cd6576ddf9 | ||
|
|
e2cf4ee3db | ||
|
|
860c9af2bf | ||
|
|
87bb29408a | ||
|
|
738cfbe992 | ||
|
|
90edc02259 |
@@ -55,9 +55,9 @@
|
||||
<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.3" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<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" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,59 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 227.0.6
|
||||
|
||||
|
||||
## 227.0.5
|
||||
|
||||
|
||||
## 227.0.4
|
||||
|
||||
|
||||
## 227.0.3
|
||||
|
||||
|
||||
## 227.0.2
|
||||
|
||||
|
||||
## 227.0.1
|
||||
|
||||
|
||||
## 227.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Add a `loop` arg to SpriteSystem.GetFrame in case you don't want to get a looping animation.
|
||||
* Remove obsolete VisibileSystem methods.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `LocalizedEntityCommands`, which are console commands that have the ability to take entity system dependencies.
|
||||
* Added `BeginRegistrationRegion` to `IConsoleHost` to allow efficient bulk-registration of console commands.
|
||||
* Added `IConsoleHost.RegisterCommand` overload that takes an `IConsoleCommand`.
|
||||
* Added a `Finished` boolean to `AnimationCompletedEvent` which allows distinguishing if an animation was removed prematurely or completed naturally.
|
||||
* Add GetLocalTilesIntersecting for MapSystem.
|
||||
* Add an analyzer for methods that should call the base implementation and use it for EntitySystems.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix loading replays if string package is compressed inside a zip.
|
||||
|
||||
### Other
|
||||
|
||||
* Tab completions containing spaces are now properly quoted, so the command will actually work properly once entered.
|
||||
* Mark EntityCoordinates.Offset as Pure so it shows as warnings if the variable is unused.
|
||||
* Networked events will always be processed in order even if late.
|
||||
|
||||
|
||||
## 226.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `System.Collections.IList` and `System.Collections.ICollection` are now sandbox safe, this fixes some collection expression cases.
|
||||
* The sandboxing system will now report the methods responsible for references to illegal items.
|
||||
|
||||
|
||||
## 226.2.0
|
||||
|
||||
### New features
|
||||
|
||||
92
Robust.Analyzers.Tests/MustCallBaseAnalyzerTest.cs
Normal file
92
Robust.Analyzers.Tests/MustCallBaseAnalyzerTest.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.MustCallBaseAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class MustCallBaseAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<MustCallBaseAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.IoC.MustCallBaseAttribute.cs"
|
||||
);
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public class Foo
|
||||
{
|
||||
[MustCallBase]
|
||||
public virtual void Function()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[MustCallBase(true)]
|
||||
public virtual void Function2()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class Bar : Foo
|
||||
{
|
||||
public override void Function()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void Function2()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class Baz : Foo
|
||||
{
|
||||
public override void Function()
|
||||
{
|
||||
base.Function();
|
||||
}
|
||||
}
|
||||
|
||||
public class Bal : Bar
|
||||
{
|
||||
public override void Function2()
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(20,26): warning RA0028: Overriders of this function must always call the base function
|
||||
VerifyCS.Diagnostic().WithSpan(20, 26, 20, 34),
|
||||
// /0/Test0.cs(41,26): warning RA0028: Overriders of this function must always call the base function
|
||||
VerifyCS.Diagnostic().WithSpan(41, 26, 41, 35));
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
111
Robust.Analyzers/MustCallBaseAnalyzer.cs
Normal file
111
Robust.Analyzers/MustCallBaseAnalyzer.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Enforces <c>MustCallBaseAttribute</c>.
|
||||
/// </summary>
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class MustCallBaseAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string Attribute = "Robust.Shared.Analyzers.MustCallBaseAttribute";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new(
|
||||
Diagnostics.IdMustCallBase,
|
||||
"No base call in overriden function",
|
||||
"Overriders of this function must always call the base function",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
isEnabledByDefault: true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.Method);
|
||||
}
|
||||
|
||||
private static void AnalyzeSymbol(SymbolAnalysisContext context)
|
||||
{
|
||||
if (context.Symbol is not IMethodSymbol { IsOverride: true } method)
|
||||
return;
|
||||
|
||||
var attrSymbol = context.Compilation.GetTypeByMetadataName(Attribute);
|
||||
if (attrSymbol == null)
|
||||
return;
|
||||
|
||||
if (DoesMethodOverriderHaveAttribute(method, attrSymbol) is not { } data)
|
||||
return;
|
||||
|
||||
if (data is { onlyOverrides: true, depth: < 2 })
|
||||
return;
|
||||
|
||||
var syntax = (MethodDeclarationSyntax) method.DeclaringSyntaxReferences[0].GetSyntax();
|
||||
if (HasBaseCall(syntax))
|
||||
return;
|
||||
|
||||
var diag = Diagnostic.Create(Rule, syntax.Identifier.GetLocation());
|
||||
context.ReportDiagnostic(diag);
|
||||
}
|
||||
|
||||
private static (int depth, bool onlyOverrides)? DoesMethodOverriderHaveAttribute(
|
||||
IMethodSymbol method,
|
||||
INamedTypeSymbol attributeSymbol)
|
||||
{
|
||||
var depth = 0;
|
||||
while (method.OverriddenMethod != null)
|
||||
{
|
||||
depth += 1;
|
||||
method = method.OverriddenMethod;
|
||||
if (GetAttribute(method, attributeSymbol) is not { } attribute)
|
||||
continue;
|
||||
|
||||
var onlyOverrides = attribute.ConstructorArguments is [{Kind: TypedConstantKind.Primitive, Value: true}];
|
||||
return (depth, onlyOverrides);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool HasBaseCall(MethodDeclarationSyntax syntax)
|
||||
{
|
||||
return syntax.Accept(new BaseCallLocator());
|
||||
}
|
||||
|
||||
private static AttributeData? GetAttribute(ISymbol namedTypeSymbol, INamedTypeSymbol attrSymbol)
|
||||
{
|
||||
return namedTypeSymbol.GetAttributes()
|
||||
.SingleOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attrSymbol));
|
||||
}
|
||||
|
||||
private sealed class BaseCallLocator : CSharpSyntaxVisitor<bool>
|
||||
{
|
||||
public override bool VisitBaseExpression(BaseExpressionSyntax node)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool DefaultVisit(SyntaxNode node)
|
||||
{
|
||||
foreach (var childNode in node.ChildNodes())
|
||||
{
|
||||
if (childNode is not CSharpSyntaxNode cSharpSyntax)
|
||||
continue;
|
||||
|
||||
if (cSharpSyntax.Accept(this))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal static class Program
|
||||
{
|
||||
// This was supposed to be the main entry for the subprocess program... It doesn't work.
|
||||
public static int Main(string[] args)
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Robust.Client.Console;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +25,7 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IGameControllerInternal _gameController = default!;
|
||||
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
|
||||
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -76,7 +76,8 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var key in remie)
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, new AnimationCompletedEvent {Uid = uid, Key = key}, true);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -187,7 +188,8 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, new AnimationCompletedEvent {Uid = entity.Owner, Key = key}, true);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
|
||||
}
|
||||
|
||||
public void Stop(EntityUid uid, AnimationPlayerComponent? component, string key)
|
||||
@@ -203,5 +205,11 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
public EntityUid Uid { get; init; }
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the animation finished by getting to its natural end.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// </summary>
|
||||
public bool Finished { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,8 @@ namespace Robust.Client.GameObjects
|
||||
/// <summary>
|
||||
/// Gets the specified frame for this sprite at the specified time.
|
||||
/// </summary>
|
||||
public Texture GetFrame(SpriteSpecifier spriteSpec, TimeSpan curTime)
|
||||
/// <param name="loop">Should we clamp on the last frame and not loop</param>
|
||||
public Texture GetFrame(SpriteSpecifier spriteSpec, TimeSpan curTime, bool loop = true)
|
||||
{
|
||||
Texture? sprite = null;
|
||||
|
||||
@@ -196,19 +197,29 @@ namespace Robust.Client.GameObjects
|
||||
var frames = state!.GetFrames(RsiDirection.South);
|
||||
var delays = state.GetDelays();
|
||||
var totalDelay = delays.Sum();
|
||||
var time = curTime.TotalSeconds % totalDelay;
|
||||
var delaySum = 0f;
|
||||
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
// No looping
|
||||
if (!loop && curTime.TotalSeconds >= totalDelay)
|
||||
{
|
||||
var delay = delays[i];
|
||||
delaySum += delay;
|
||||
sprite = frames[^1];
|
||||
}
|
||||
// Loopable
|
||||
else
|
||||
{
|
||||
var time = curTime.TotalSeconds % totalDelay;
|
||||
var delaySum = 0f;
|
||||
|
||||
if (time > delaySum)
|
||||
continue;
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
{
|
||||
var delay = delays[i];
|
||||
delaySum += delay;
|
||||
|
||||
sprite = frames[i];
|
||||
break;
|
||||
if (time > delaySum)
|
||||
continue;
|
||||
|
||||
sprite = frames[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sprite ??= Frame0(spriteSpec);
|
||||
|
||||
@@ -162,9 +162,7 @@ public sealed partial class ReplayLoadManager
|
||||
}
|
||||
|
||||
using var stringFile = fileReader.Open(FileStrings);
|
||||
var stringData = new byte[stringFile.Length];
|
||||
stringFile.ReadExactly(stringData);
|
||||
_serializer.SetStringSerializerPackage(stringHash, stringData);
|
||||
_serializer.SetStringSerializerPackage(stringHash, stringFile.CopyToArray());
|
||||
|
||||
using var cvarsFile = fileReader.Open(FileCvars);
|
||||
// Note, this does not invoke the received-initial-cvars event. But at least currently, that doesn't matter
|
||||
|
||||
@@ -15,6 +15,36 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public Label Label { get; }
|
||||
public TextureRect TextureRect { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the checkbox be to the left or the right of the label.
|
||||
/// </summary>
|
||||
public bool LeftAlign
|
||||
{
|
||||
get => _leftAlign;
|
||||
set
|
||||
{
|
||||
if (_leftAlign == value)
|
||||
return;
|
||||
|
||||
_leftAlign = value;
|
||||
|
||||
if (value)
|
||||
{
|
||||
Label.HorizontalExpand = false;
|
||||
TextureRect.SetPositionFirst();
|
||||
Label.SetPositionInParent(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Label.HorizontalExpand = true;
|
||||
Label.SetPositionFirst();
|
||||
TextureRect.SetPositionInParent(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _leftAlign = true;
|
||||
|
||||
public CheckBox()
|
||||
{
|
||||
ToggleMode = true;
|
||||
@@ -31,10 +61,21 @@ namespace Robust.Client.UserInterface.Controls
|
||||
StyleClasses = { StyleClassCheckBox },
|
||||
VerticalAlignment = VAlignment.Center,
|
||||
};
|
||||
hBox.AddChild(TextureRect);
|
||||
|
||||
Label = new Label();
|
||||
hBox.AddChild(Label);
|
||||
|
||||
if (LeftAlign)
|
||||
{
|
||||
Label.HorizontalExpand = false;
|
||||
hBox.AddChild(TextureRect);
|
||||
hBox.AddChild(Label);
|
||||
}
|
||||
else
|
||||
{
|
||||
Label.HorizontalExpand = true;
|
||||
hBox.AddChild(Label);
|
||||
hBox.AddChild(TextureRect);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DrawModeChanged()
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string StylePseudoClassHover = "hover";
|
||||
public const string StylePseudoClassDisabled = "disabled";
|
||||
|
||||
public StyleBox? StyleBoxOverride { get; set; }
|
||||
|
||||
public ContainerButton()
|
||||
{
|
||||
DrawModeChanged();
|
||||
@@ -24,6 +26,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
get
|
||||
{
|
||||
if (StyleBoxOverride != null)
|
||||
{
|
||||
return StyleBoxOverride;
|
||||
}
|
||||
|
||||
if (TryGetStyleProperty<StyleBox>(StylePropertyStyleBox, out var box))
|
||||
{
|
||||
return box;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using Robust.Shared.Maths;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
@@ -15,9 +14,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public const string RightButtonStyle = "spinbox-right";
|
||||
public const string MiddleButtonStyle = "spinbox-middle";
|
||||
public LineEdit LineEditControl { get; }
|
||||
private List<Button> _leftButtons = new();
|
||||
private List<Button> _rightButtons = new();
|
||||
private List<SpinBoxButton> _leftButtons = new();
|
||||
private List<SpinBoxButton> _rightButtons = new();
|
||||
private int _stepSize = 1;
|
||||
private bool _buttonsDisabled;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the SpinBox value gets changed by the input text.
|
||||
@@ -30,12 +30,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
if (IsValid != null && !IsValid(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
_value = value;
|
||||
LineEditControl.Text = value.ToString();
|
||||
OverrideValue(value);
|
||||
ValueChanged?.Invoke(new ValueChangedEventArgs(value));
|
||||
}
|
||||
}
|
||||
@@ -52,6 +47,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return;
|
||||
}
|
||||
_value = value;
|
||||
UpdateButtonCanPress();
|
||||
LineEditControl.Text = value.ToString();
|
||||
}
|
||||
|
||||
@@ -87,6 +83,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
ClearButtons();
|
||||
AddLeftButton(-1, "-");
|
||||
AddRightButton(1, "+");
|
||||
UpdateButtonCanPress();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,8 +91,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public void AddRightButton(int num, string text)
|
||||
{
|
||||
var button = new Button { Text = text };
|
||||
button.OnPressed += (args) => Value += num;
|
||||
var button = new SpinBoxButton(num) { Text = text };
|
||||
button.OnPressed += _ => Value += num;
|
||||
AddChild(button);
|
||||
button.AddStyleClass(RightButtonStyle);
|
||||
if (_rightButtons.Count > 0)
|
||||
@@ -111,8 +108,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
/// </summary>
|
||||
public void AddLeftButton(int num, string text)
|
||||
{
|
||||
var button = new Button { Text = text };
|
||||
button.OnPressed += (args) => Value += num;
|
||||
var button = new SpinBoxButton(num) { Text = text };
|
||||
button.OnPressed += _ => Value += num;
|
||||
AddChild(button);
|
||||
button.SetPositionInParent(_leftButtons.Count);
|
||||
button.AddStyleClass(_leftButtons.Count == 0 ? LeftButtonStyle : MiddleButtonStyle);
|
||||
@@ -162,6 +159,24 @@ namespace Robust.Client.UserInterface.Controls
|
||||
{
|
||||
rightButton.Disabled = disabled;
|
||||
}
|
||||
|
||||
_buttonsDisabled = disabled;
|
||||
}
|
||||
|
||||
private void UpdateButtonCanPress()
|
||||
{
|
||||
if (IsValid == null)
|
||||
return;
|
||||
|
||||
foreach (var button in _leftButtons)
|
||||
{
|
||||
button.Disabled = !IsValid(_value + button.Value) || _buttonsDisabled;
|
||||
}
|
||||
|
||||
foreach (var button in _rightButtons)
|
||||
{
|
||||
button.Disabled = !IsValid(_value + button.Value) || _buttonsDisabled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -195,6 +210,16 @@ namespace Robust.Client.UserInterface.Controls
|
||||
else if (args.Delta.Y < 0)
|
||||
Value -= _stepSize;
|
||||
}
|
||||
|
||||
private sealed class SpinBoxButton : Button
|
||||
{
|
||||
public readonly int Value;
|
||||
|
||||
public SpinBoxButton(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ValueChangedEventArgs : EventArgs
|
||||
|
||||
@@ -65,6 +65,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public StyleBox? PanelStyleBoxOverride { get; set; }
|
||||
public Color? TabFontColorOverride { get; set; }
|
||||
public Color? TabFontColorInactiveOverride { get; set; }
|
||||
|
||||
public event Action<int>? OnTabChanged;
|
||||
|
||||
public TabContainer()
|
||||
@@ -361,6 +365,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
private Color _getTabFontColorActive()
|
||||
{
|
||||
if (TabFontColorOverride != null)
|
||||
return TabFontColorOverride.Value;
|
||||
|
||||
if (TryGetStyleProperty(stylePropertyTabFontColor, out Color color))
|
||||
{
|
||||
return color;
|
||||
@@ -371,6 +378,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
private Color _getTabFontColorInactive()
|
||||
{
|
||||
if (TabFontColorInactiveOverride != null)
|
||||
return TabFontColorInactiveOverride.Value;
|
||||
|
||||
if (TryGetStyleProperty(StylePropertyTabFontColorInactive, out Color color))
|
||||
{
|
||||
return color;
|
||||
@@ -381,6 +391,9 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[System.Diagnostics.Contracts.Pure]
|
||||
private StyleBox? _getPanel()
|
||||
{
|
||||
if (PanelStyleBoxOverride != null)
|
||||
return PanelStyleBoxOverride;
|
||||
|
||||
TryGetStyleProperty<StyleBox>(StylePropertyPanelStyleBox, out var box);
|
||||
return box;
|
||||
}
|
||||
|
||||
@@ -277,8 +277,21 @@ public sealed partial class DebugConsole
|
||||
CommandBar.CursorPosition = lastRange.end;
|
||||
CommandBar.SelectionStart = lastRange.start;
|
||||
var insertValue = CommandParsing.Escape(completion);
|
||||
|
||||
// If the replacement contains a space, we must quote it to treat it as a single argument.
|
||||
var mustQuote = insertValue.Contains(' ');
|
||||
if ((completionFlags & CompletionOptionFlags.PartialCompletion) == 0)
|
||||
{
|
||||
if (mustQuote)
|
||||
insertValue = $"\"{insertValue}\"";
|
||||
|
||||
insertValue += " ";
|
||||
}
|
||||
else if (mustQuote)
|
||||
{
|
||||
// If it's a partial completion, only quote the start.
|
||||
insertValue = '"' + insertValue;
|
||||
}
|
||||
|
||||
CommandBar.InsertAtCursor(insertValue);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public static class Diagnostics
|
||||
public const string IdDependencyFieldAssigned = "RA0025";
|
||||
public const string IdUncachedRegex = "RA0026";
|
||||
public const string IdDataFieldRedundantTag = "RA0027";
|
||||
public const string IdMustCallBase = "RA0028";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -296,7 +296,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -40,12 +40,6 @@ namespace Robust.Server.GameObjects
|
||||
EntityManager.EntityInitialized -= OnEntityInit;
|
||||
}
|
||||
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public void AddLayer(EntityUid uid, VisibilityComponent component, int layer, bool refresh = true)
|
||||
{
|
||||
AddLayer((uid, component), (ushort)layer, refresh);
|
||||
}
|
||||
|
||||
public void AddLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
ent.Comp ??= _visibilityQuery.CompOrNull(ent.Owner) ?? AddComp<VisibilityComponent>(ent.Owner);
|
||||
@@ -59,13 +53,6 @@ namespace Robust.Server.GameObjects
|
||||
RefreshVisibility(ent);
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public void RemoveLayer(EntityUid uid, VisibilityComponent component, int layer, bool refresh = true)
|
||||
{
|
||||
RemoveLayer((uid, component), (ushort)layer, refresh);
|
||||
}
|
||||
|
||||
public void RemoveLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
if (!_visibilityQuery.Resolve(ent.Owner, ref ent.Comp, false))
|
||||
@@ -80,12 +67,6 @@ namespace Robust.Server.GameObjects
|
||||
RefreshVisibility(ent);
|
||||
}
|
||||
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public void SetLayer(EntityUid uid, VisibilityComponent component, int layer, bool refresh = true)
|
||||
{
|
||||
SetLayer((uid, component), (ushort)layer, refresh);
|
||||
}
|
||||
|
||||
public void SetLayer(Entity<VisibilityComponent?> ent, ushort layer, bool refresh = true)
|
||||
{
|
||||
ent.Comp ??= _visibilityQuery.CompOrNull(ent.Owner) ?? AddComp<VisibilityComponent>(ent.Owner);
|
||||
|
||||
@@ -229,21 +229,6 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private void HandleEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
var msgT = message.SourceTick;
|
||||
var cT = _gameTiming.CurTick;
|
||||
|
||||
if (msgT <= cT)
|
||||
{
|
||||
if (msgT < cT && _logLateMsgs)
|
||||
{
|
||||
_netEntSawmill.Warning("Got late MsgEntity! Diff: {0}, msgT: {2}, cT: {3}, player: {1}",
|
||||
(int) msgT.Value - (int) cT.Value, message.MsgChannel.UserName, msgT, cT);
|
||||
}
|
||||
|
||||
DispatchEntityNetworkMessage(message);
|
||||
return;
|
||||
}
|
||||
|
||||
_queue.Add(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,14 +80,17 @@ internal sealed partial class PvsSystem
|
||||
// Update visibility masks & viewer positions
|
||||
// TODO PVS do this before sending state.
|
||||
// I,e, we already enumerate over all eyes when computing visible chunks.
|
||||
Span<MapCoordinates> positions = stackalloc MapCoordinates[session.Viewers.Length];
|
||||
Span<(MapCoordinates pos, float scale)> positions = stackalloc (MapCoordinates, float)[session.Viewers.Length];
|
||||
int i = 0;
|
||||
foreach (var viewer in session.Viewers)
|
||||
{
|
||||
if (viewer.Comp2 != null)
|
||||
session.VisMask |= viewer.Comp2.VisibilityMask;
|
||||
|
||||
positions[i++] = _transform.GetMapCoordinates(viewer.Owner, viewer.Comp1);
|
||||
var mapCoordinates = _transform.GetMapCoordinates(viewer.Owner, viewer.Comp1);
|
||||
mapCoordinates = mapCoordinates.Offset(viewer.Comp2?.Offset ?? Vector2.Zero);
|
||||
var scale = MathF.Max((viewer.Comp2?.PvsScale ?? 1), 0.1f);
|
||||
positions[i++] = (mapCoordinates, scale);
|
||||
}
|
||||
|
||||
if (!CullingEnabled || session.DisableCulling)
|
||||
@@ -112,7 +115,7 @@ internal sealed partial class PvsSystem
|
||||
DebugTools.Assert(!chunk.UpdateQueued);
|
||||
DebugTools.Assert(!chunk.Dirty);
|
||||
|
||||
foreach (var pos in positions)
|
||||
foreach (var (pos, scale) in positions)
|
||||
{
|
||||
if (pos.MapId != chunk.Position.MapId)
|
||||
continue;
|
||||
@@ -120,8 +123,9 @@ internal sealed partial class PvsSystem
|
||||
dist = Math.Min(dist, (pos.Position - chunk.Position.Position).LengthSquared());
|
||||
|
||||
var relative = Vector2.Transform(pos.Position, chunk.InvWorldMatrix) - chunk.Centre;
|
||||
|
||||
relative = Vector2.Abs(relative);
|
||||
chebDist = Math.Min(chebDist, Math.Max(relative.X, relative.Y));
|
||||
chebDist = Math.Min(chebDist, Math.Max(relative.X, relative.Y) / scale);
|
||||
}
|
||||
|
||||
distances.Add(dist);
|
||||
|
||||
@@ -362,8 +362,19 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
|
||||
private (Vector2 worldPos, float range, EntityUid? map) CalcViewBounds(Entity<TransformComponent, EyeComponent?> eye)
|
||||
{
|
||||
var size = Math.Max(eye.Comp2?.PvsSize ?? _priorityViewSize, 1);
|
||||
return (_transform.GetWorldPosition(eye.Comp1), size / 2f, eye.Comp1.MapUid);
|
||||
var size = _priorityViewSize;
|
||||
var worldPos = _transform.GetWorldPosition(eye.Comp1);
|
||||
|
||||
if (eye.Comp2 is not null)
|
||||
{
|
||||
// not using EyeComponent.Eye.Position, because it's updated only on the client's side
|
||||
worldPos += eye.Comp2.Offset;
|
||||
size *= eye.Comp2.PvsScale;
|
||||
}
|
||||
|
||||
size = Math.Max(size, 1);
|
||||
|
||||
return (worldPos, size / 2f, eye.Comp1.MapUid);
|
||||
}
|
||||
|
||||
private void CullDeletionHistoryUntil(GameTick tick)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
@@ -35,11 +36,6 @@ using Robust.Shared.Utility;
|
||||
using SysVector3 = System.Numerics.Vector3;
|
||||
using SysVector4 = System.Numerics.Vector4;
|
||||
|
||||
#if NETCOREAPP
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
#endif
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
/// <summary>
|
||||
@@ -50,37 +46,43 @@ namespace Robust.Shared.Maths
|
||||
public struct Color : IEquatable<Color>, ISpanFormattable
|
||||
{
|
||||
/// <summary>
|
||||
/// The red component of this Color4 structure.
|
||||
/// The red component of this Color structure.
|
||||
/// </summary>
|
||||
public float R;
|
||||
|
||||
/// <summary>
|
||||
/// The green component of this Color4 structure.
|
||||
/// The green component of this Color structure.
|
||||
/// </summary>
|
||||
public float G;
|
||||
|
||||
/// <summary>
|
||||
/// The blue component of this Color4 structure.
|
||||
/// The blue component of this Color structure.
|
||||
/// </summary>
|
||||
public float B;
|
||||
|
||||
/// <summary>
|
||||
/// The alpha component of this Color4 structure.
|
||||
/// The alpha component of this Color structure.
|
||||
/// </summary>
|
||||
public float A;
|
||||
|
||||
/// <summary>
|
||||
/// Vector representation, for easy SIMD operations.
|
||||
/// </summary>
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public readonly SysVector4 RGBA => Unsafe.BitCast<Color, SysVector4>(this);
|
||||
|
||||
public readonly byte RByte => (byte) (R * byte.MaxValue);
|
||||
public readonly byte GByte => (byte) (G * byte.MaxValue);
|
||||
public readonly byte BByte => (byte) (B * byte.MaxValue);
|
||||
public readonly byte AByte => (byte) (A * byte.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new Color4 structure from the specified components.
|
||||
/// Constructs a new <see cref="Color"/> structure from the specified components.
|
||||
/// </summary>
|
||||
/// <param name="r">The red component of the new Color4 structure.</param>
|
||||
/// <param name="g">The green component of the new Color4 structure.</param>
|
||||
/// <param name="b">The blue component of the new Color4 structure.</param>
|
||||
/// <param name="a">The alpha component of the new Color4 structure.</param>
|
||||
/// <param name="r">The red component of the new Color structure.</param>
|
||||
/// <param name="g">The green component of the new Color structure.</param>
|
||||
/// <param name="b">The blue component of the new Color structure.</param>
|
||||
/// <param name="a">The alpha component of the new Color structure.</param>
|
||||
public Color(float r, float g, float b, float a = 1)
|
||||
{
|
||||
R = r;
|
||||
@@ -90,14 +92,23 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new Color4 structure from the specified components.
|
||||
/// Constructs a new Color structure from the components in a <see cref="SysVector4"/>.
|
||||
/// </summary>
|
||||
/// <param name="r">The red component of the new Color4 structure.</param>
|
||||
/// <param name="g">The green component of the new Color4 structure.</param>
|
||||
/// <param name="b">The blue component of the new Color4 structure.</param>
|
||||
/// <param name="a">The alpha component of the new Color4 structure.</param>
|
||||
public Color(in SysVector4 vec)
|
||||
{
|
||||
this = Unsafe.BitCast<SysVector4, Color>(vec);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new Color structure from the specified components.
|
||||
/// </summary>
|
||||
/// <param name="r">The red component of the new Color structure.</param>
|
||||
/// <param name="g">The green component of the new Color structure.</param>
|
||||
/// <param name="b">The blue component of the new Color structure.</param>
|
||||
/// <param name="a">The alpha component of the new Color structure.</param>
|
||||
public Color(byte r, byte g, byte b, byte a = 255)
|
||||
{
|
||||
Unsafe.SkipInit(out this);
|
||||
R = r / (float) byte.MaxValue;
|
||||
G = g / (float) byte.MaxValue;
|
||||
B = b / (float) byte.MaxValue;
|
||||
@@ -124,7 +135,7 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the specified Color4 structures for equality.
|
||||
/// Compares the specified Color structures for equality.
|
||||
/// </summary>
|
||||
/// <param name="left">The left-hand side of the comparison.</param>
|
||||
/// <param name="right">The right-hand side of the comparison.</param>
|
||||
@@ -135,7 +146,7 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the specified Color4 structures for inequality.
|
||||
/// Compares the specified Color structures for inequality.
|
||||
/// </summary>
|
||||
/// <param name="left">The left-hand side of the comparison.</param>
|
||||
/// <param name="right">The right-hand side of the comparison.</param>
|
||||
@@ -146,10 +157,10 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified System.Drawing.Color to a Color4 structure.
|
||||
/// Converts the specified System.Drawing.Color to a Color structure.
|
||||
/// </summary>
|
||||
/// <param name="color">The System.Drawing.Color to convert.</param>
|
||||
/// <returns>A new Color4 structure containing the converted components.</returns>
|
||||
/// <returns>A new Color structure containing the converted components.</returns>
|
||||
public static implicit operator Color(System.Drawing.Color color)
|
||||
{
|
||||
return new(color.R, color.G, color.B, color.A);
|
||||
@@ -181,9 +192,9 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified Color4 to a System.Drawing.Color structure.
|
||||
/// Converts the specified Color to a System.Drawing.Color structure.
|
||||
/// </summary>
|
||||
/// <param name="color">The Color4 to convert.</param>
|
||||
/// <param name="color">The Color to convert.</param>
|
||||
/// <returns>A new System.Drawing.Color structure containing the converted components.</returns>
|
||||
public static explicit operator System.Drawing.Color(Color color)
|
||||
{
|
||||
@@ -210,11 +221,11 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether this Color4 structure is equal to the specified object.
|
||||
/// Compares whether this Color structure is equal to the specified object.
|
||||
/// </summary>
|
||||
/// <param name="obj">An object to compare to.</param>
|
||||
/// <returns>True obj is a Color4 structure with the same components as this Color4; false otherwise.</returns>
|
||||
public override readonly bool Equals(object? obj)
|
||||
/// <returns>True obj is a Color structure with the same components as this Color; false otherwise.</returns>
|
||||
public readonly override bool Equals(object? obj)
|
||||
{
|
||||
if (!(obj is Color))
|
||||
return false;
|
||||
@@ -223,19 +234,19 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the hash code for this Color4 structure.
|
||||
/// Calculates the hash code for this Color structure.
|
||||
/// </summary>
|
||||
/// <returns>A System.Int32 containing the hash code of this Color4 structure.</returns>
|
||||
public override readonly int GetHashCode()
|
||||
/// <returns>A System.Int32 containing the hash code of this Color structure.</returns>
|
||||
public readonly override int GetHashCode()
|
||||
{
|
||||
return ToArgb();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a System.String that describes this Color4 structure.
|
||||
/// Creates a System.String that describes this Color structure.
|
||||
/// </summary>
|
||||
/// <returns>A System.String that describes this Color4 structure.</returns>
|
||||
public override readonly string ToString()
|
||||
/// <returns>A System.String that describes this Color structure.</returns>
|
||||
public readonly override string ToString()
|
||||
{
|
||||
return $"{{(R, G, B, A) = ({R}, {G}, {B}, {A})}}";
|
||||
}
|
||||
@@ -309,7 +320,6 @@ namespace Robust.Shared.Maths
|
||||
public static Color FromSrgb(Color srgb)
|
||||
{
|
||||
float r, g, b;
|
||||
#if NETCOREAPP
|
||||
if (srgb.R <= 0.04045f)
|
||||
r = srgb.R / 12.92f;
|
||||
else
|
||||
@@ -324,22 +334,6 @@ namespace Robust.Shared.Maths
|
||||
b = srgb.B / 12.92f;
|
||||
else
|
||||
b = MathF.Pow((srgb.B + 0.055f) / (1.0f + 0.055f), 2.4f);
|
||||
#else
|
||||
if (srgb.R <= 0.04045f)
|
||||
r = srgb.R / 12.92f;
|
||||
else
|
||||
r = (float) Math.Pow((srgb.R + 0.055f) / (1.0f + 0.055f), 2.4f);
|
||||
|
||||
if (srgb.G <= 0.04045f)
|
||||
g = srgb.G / 12.92f;
|
||||
else
|
||||
g = (float) Math.Pow((srgb.G + 0.055f) / (1.0f + 0.055f), 2.4f);
|
||||
|
||||
if (srgb.B <= 0.04045f)
|
||||
b = srgb.B / 12.92f;
|
||||
else
|
||||
b = (float) Math.Pow((srgb.B + 0.055f) / (1.0f + 0.055f), 2.4f);
|
||||
#endif
|
||||
|
||||
return new Color(r, g, b, srgb.A);
|
||||
}
|
||||
@@ -355,7 +349,6 @@ namespace Robust.Shared.Maths
|
||||
{
|
||||
float r, g, b;
|
||||
|
||||
#if NETCOREAPP
|
||||
if (rgb.R <= 0.0031308)
|
||||
r = 12.92f * rgb.R;
|
||||
else
|
||||
@@ -370,22 +363,6 @@ namespace Robust.Shared.Maths
|
||||
b = 12.92f * rgb.B;
|
||||
else
|
||||
b = (1.0f + 0.055f) * MathF.Pow(rgb.B, 1.0f / 2.4f) - 0.055f;
|
||||
#else
|
||||
if (rgb.R <= 0.0031308)
|
||||
r = 12.92f * rgb.R;
|
||||
else
|
||||
r = (1.0f + 0.055f) * (float) Math.Pow(rgb.R, 1.0f / 2.4f) - 0.055f;
|
||||
|
||||
if (rgb.G <= 0.0031308)
|
||||
g = 12.92f * rgb.G;
|
||||
else
|
||||
g = (1.0f + 0.055f) * (float) Math.Pow(rgb.G, 1.0f / 2.4f) - 0.055f;
|
||||
|
||||
if (rgb.B <= 0.0031308)
|
||||
b = 12.92f * rgb.B;
|
||||
else
|
||||
b = (1.0f + 0.055f) * (float) Math.Pow(rgb.B, 1.0f / 2.4f) - 0.055f;
|
||||
#endif
|
||||
|
||||
return new Color(r, g, b, rgb.A);
|
||||
}
|
||||
@@ -471,6 +448,7 @@ namespace Robust.Shared.Maths
|
||||
/// Each has a range of 0.0 to 1.0.
|
||||
/// </returns>
|
||||
/// <param name="rgb">Color value to convert.</param>
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
||||
public static Vector4 ToHsl(Color rgb)
|
||||
{
|
||||
var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B));
|
||||
@@ -582,6 +560,7 @@ namespace Robust.Shared.Maths
|
||||
/// Each has a range of 0.0 to 1.0.
|
||||
/// </returns>
|
||||
/// <param name="rgb">Color value to convert.</param>
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
||||
public static Vector4 ToHsv(Color rgb)
|
||||
{
|
||||
var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B));
|
||||
@@ -770,6 +749,7 @@ namespace Robust.Shared.Maths
|
||||
/// Each has a range of 0.0 to 1.0.
|
||||
/// </returns>
|
||||
/// <param name="rgb">Color value to convert.</param>
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
||||
public static Vector4 ToHcy(Color rgb)
|
||||
{
|
||||
var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B));
|
||||
@@ -828,23 +808,10 @@ namespace Robust.Shared.Maths
|
||||
/// with 0.5 being 50% of both colors, 0.25 being 25% of <paramref name="β" /> and 75%
|
||||
/// <paramref name="α" />.
|
||||
/// </param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Color InterpolateBetween(Color α, Color β, float λ)
|
||||
{
|
||||
if (Sse.IsSupported && Fma.IsSupported)
|
||||
{
|
||||
var vecA = Unsafe.As<Color, Vector128<float>>(ref α);
|
||||
var vecB = Unsafe.As<Color, Vector128<float>>(ref β);
|
||||
|
||||
vecB = Fma.MultiplyAdd(Sse.Subtract(vecB, vecA), Vector128.Create(λ), vecA);
|
||||
|
||||
return Unsafe.As<Vector128<float>, Color>(ref vecB);
|
||||
}
|
||||
ref var svA = ref Unsafe.As<Color, SysVector4>(ref α);
|
||||
ref var svB = ref Unsafe.As<Color, SysVector4>(ref β);
|
||||
|
||||
var res = SysVector4.Lerp(svA, svB, λ);
|
||||
|
||||
return Unsafe.As<SysVector4, Color>(ref res);
|
||||
return new(SysVector4.Lerp(α.RGBA, β.RGBA, λ));
|
||||
}
|
||||
|
||||
public static Color? TryFromHex(ReadOnlySpan<char> hexColor)
|
||||
@@ -1000,13 +967,8 @@ namespace Robust.Shared.Maths
|
||||
/// <summary>
|
||||
/// Component wise multiplication of two colors.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public static Color operator *(Color a, Color b)
|
||||
{
|
||||
return new(a.R * b.R, a.G * b.G, a.B * b.B, a.A * b.A);
|
||||
}
|
||||
public static Color operator *(in Color a, in Color b)
|
||||
=> new(a.RGBA * b.RGBA);
|
||||
|
||||
public readonly string ToHex()
|
||||
{
|
||||
@@ -1030,17 +992,16 @@ namespace Robust.Shared.Maths
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares whether this Color4 structure is equal to the specified Color4.
|
||||
/// Compares whether this Color structure is equal to the specified Color.
|
||||
/// </summary>
|
||||
/// <param name="other">The Color4 structure to compare to.</param>
|
||||
/// <returns>True if both Color4 structures contain the same components; false otherwise.</returns>
|
||||
/// <param name="other">The Color structure to compare to.</param>
|
||||
/// <returns>True if both Color structures contain the same components; false otherwise.</returns>
|
||||
public readonly bool Equals(Color other)
|
||||
{
|
||||
return
|
||||
MathHelper.CloseToPercent(R, other.R) &&
|
||||
MathHelper.CloseToPercent(G, other.G) &&
|
||||
MathHelper.CloseToPercent(B, other.B) &&
|
||||
MathHelper.CloseToPercent(A, other.A);
|
||||
// TODO COLOR why is this approximate
|
||||
// This method literally doesn't do what its docstring says it does.
|
||||
// If people wanted approximate equality, they can check that manually.
|
||||
return MathHelper.CloseToPercent(this, other);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
@@ -1942,7 +1903,7 @@ namespace Robust.Shared.Maths
|
||||
|
||||
public readonly string? Name()
|
||||
{
|
||||
return DefaultColorsInverted.TryGetValue(this, out var name) ? name : null;
|
||||
return DefaultColorsInverted.GetValueOrDefault(this);
|
||||
}
|
||||
|
||||
public static bool TryParse(string input, out Color color)
|
||||
|
||||
@@ -15,6 +15,7 @@ using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Vec4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
@@ -525,6 +526,27 @@ namespace Robust.Shared.Maths
|
||||
return Math.Abs(a - b) <= epsilon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether two vectors are within <paramref name="percentage"/> of each other
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CloseToPercent(Vec4 a, Vec4 b, float percentage = .00001f)
|
||||
{
|
||||
a = Vec4.Abs(a);
|
||||
b = Vec4.Abs(b);
|
||||
var p = new Vec4(percentage);
|
||||
var epsilon = Vec4.Max(Vec4.Max(a, b) * p, p);
|
||||
var delta = Vec4.Abs(a - b);
|
||||
return delta.X <= epsilon.X && delta.Y <= epsilon.Y && delta.Z <= epsilon.Z && delta.W <= epsilon.W;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether two colours are within <paramref name="percentage"/> of each other
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool CloseToPercent(Color a, Color b, float percentage = .00001f)
|
||||
=> CloseToPercent(a.RGBA, b.RGBA, percentage);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether two floating point numbers are within <paramref name="percentage"/> of eachother
|
||||
/// </summary>
|
||||
|
||||
@@ -6,4 +6,3 @@
|
||||
|
||||
[assembly: InternalsVisibleTo("Robust.Client")]
|
||||
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
|
||||
[assembly: InternalsVisibleTo("Content.Benchmarks")]
|
||||
|
||||
17
Robust.Shared/Analyzers/MustCallBaseAttribute.cs
Normal file
17
Robust.Shared/Analyzers/MustCallBaseAttribute.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that overriders of this method must always call the base function.
|
||||
/// </summary>
|
||||
/// <param name="onlyOverrides">
|
||||
/// If true, only base calls to *overrides* are necessary.
|
||||
/// This is intended for base classes where the base function is always empty,
|
||||
/// so a base call from the first override may be ommitted.
|
||||
/// </param>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public sealed class MustCallBaseAttribute(bool onlyOverrides = false) : Attribute
|
||||
{
|
||||
public bool OnlyOverrides { get; } = onlyOverrides;
|
||||
}
|
||||
@@ -9,18 +9,17 @@ using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console.Commands;
|
||||
|
||||
internal sealed class TeleportCommand : LocalizedCommands
|
||||
internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override string Command => "tp";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -36,11 +35,10 @@ internal sealed class TeleportCommand : LocalizedCommands
|
||||
return;
|
||||
}
|
||||
|
||||
var xformSystem = _entitySystem.GetEntitySystem<SharedTransformSystem>();
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(entity);
|
||||
var position = new Vector2(posX, posY);
|
||||
|
||||
xformSystem.AttachToGridOrMap(entity, transform);
|
||||
_transform.AttachToGridOrMap(entity, transform);
|
||||
|
||||
MapId mapId;
|
||||
if (args.Length == 3 && int.TryParse(args[2], out var intMapId))
|
||||
@@ -56,25 +54,26 @@ internal sealed class TeleportCommand : LocalizedCommands
|
||||
|
||||
if (_map.TryFindGridAt(mapId, position, out var gridUid, out var grid))
|
||||
{
|
||||
var gridPos = Vector2.Transform(position, xformSystem.GetInvWorldMatrix(gridUid));
|
||||
var gridPos = Vector2.Transform(position, _transform.GetInvWorldMatrix(gridUid));
|
||||
|
||||
xformSystem.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
|
||||
_transform.SetCoordinates(entity, transform, new EntityCoordinates(gridUid, gridPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
var mapEnt = _map.GetMapEntityIdOrThrow(mapId);
|
||||
xformSystem.SetWorldPosition(transform, position);
|
||||
xformSystem.SetParent(entity, transform, mapEnt);
|
||||
_transform.SetWorldPosition(transform, position);
|
||||
_transform.SetParent(entity, transform, mapEnt);
|
||||
}
|
||||
|
||||
shell.WriteLine($"Teleported {shell.Player} to {mapId}:{posX},{posY}.");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TeleportToCommand : LocalizedCommands
|
||||
public sealed class TeleportToCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly ISharedPlayerManager _players = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override string Command => "tpto";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -89,7 +88,6 @@ public sealed class TeleportToCommand : LocalizedCommands
|
||||
if (!TryGetTransformFromUidOrUsername(target, shell, out var targetUid, out _))
|
||||
return;
|
||||
|
||||
var transformSystem = _entities.System<SharedTransformSystem>();
|
||||
var targetCoords = new EntityCoordinates(targetUid.Value, Vector2.Zero);
|
||||
|
||||
if (_entities.TryGetComponent(targetUid, out PhysicsComponent? targetPhysics))
|
||||
@@ -127,8 +125,8 @@ public sealed class TeleportToCommand : LocalizedCommands
|
||||
|
||||
foreach (var victim in victims)
|
||||
{
|
||||
transformSystem.SetCoordinates(victim.Entity, targetCoords);
|
||||
transformSystem.AttachToGridOrMap(victim.Entity, victim.Transform);
|
||||
_transform.SetCoordinates(victim.Entity, targetCoords);
|
||||
_transform.AttachToGridOrMap(victim.Entity, victim.Transform);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,9 +176,10 @@ public sealed class TeleportToCommand : LocalizedCommands
|
||||
}
|
||||
}
|
||||
|
||||
sealed class LocationCommand : LocalizedCommands
|
||||
sealed class LocationCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public override string Command => "loc";
|
||||
|
||||
@@ -192,18 +191,19 @@ sealed class LocationCommand : LocalizedCommands
|
||||
var pt = _ent.GetComponent<TransformComponent>(entity);
|
||||
var pos = pt.Coordinates;
|
||||
|
||||
shell.WriteLine($"MapID:{pos.GetMapId(_ent)} GridUid:{pos.GetGridUid(_ent)} X:{pos.X:N2} Y:{pos.Y:N2}");
|
||||
var mapId = _transform.GetMapId(pos);
|
||||
var gridUid = _transform.GetGrid(pos);
|
||||
|
||||
shell.WriteLine($"MapID:{mapId} GridUid:{gridUid} X:{pos.X:N2} Y:{pos.Y:N2}");
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TpGridCommand : LocalizedCommands
|
||||
sealed class TpGridCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _ent = default!;
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly SharedMapSystem _map = default!;
|
||||
|
||||
public override string Command => "tpgrid";
|
||||
public override string Description => Loc.GetString("cmd-tpgrid-desc");
|
||||
public override string Help => Loc.GetString("cmd-tpgrid-help");
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
@@ -246,14 +246,14 @@ sealed class TpGridCommand : LocalizedCommands
|
||||
mapId = new MapId(map);
|
||||
}
|
||||
|
||||
var id = _map.GetMapEntityId(mapId);
|
||||
var id = _map.GetMap(mapId);
|
||||
if (id == EntityUid.Invalid)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", mapId.Value)));
|
||||
return;
|
||||
}
|
||||
|
||||
var pos = new EntityCoordinates(_map.GetMapEntityId(mapId), new Vector2(xPos, yPos));
|
||||
var pos = new EntityCoordinates(id, new Vector2(xPos, yPos));
|
||||
_ent.System<SharedTransformSystem>().SetCoordinates(uid.Value, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace Robust.Shared.Console
|
||||
[Dependency] protected readonly ILocalizationManager LocalizationManager = default!;
|
||||
|
||||
[ViewVariables] protected readonly Dictionary<string, IConsoleCommand> RegisteredCommands = new();
|
||||
[ViewVariables] private readonly HashSet<string> _autoRegisteredCommands = [];
|
||||
|
||||
private bool _isInRegistrationRegion;
|
||||
|
||||
private readonly CommandBuffer _commandBuffer = new CommandBuffer();
|
||||
|
||||
@@ -61,6 +64,11 @@ namespace Robust.Shared.Console
|
||||
// search for all client commands in all assemblies, and register them
|
||||
foreach (var type in ReflectionManager.GetAllChildren<IConsoleCommand>())
|
||||
{
|
||||
// This sucks but I can't come up with anything better
|
||||
// that won't just be 10x worse complexity for no gain.
|
||||
if (type.IsAssignableTo(typeof(IEntityConsoleCommand)))
|
||||
continue;
|
||||
|
||||
var instance = (IConsoleCommand)_typeFactory.CreateInstanceUnchecked(type, true);
|
||||
if (AvailableCommands.TryGetValue(instance.Command, out var duplicate))
|
||||
{
|
||||
@@ -69,6 +77,7 @@ namespace Robust.Shared.Console
|
||||
}
|
||||
|
||||
RegisteredCommands[instance.Command] = instance;
|
||||
_autoRegisteredCommands.Add(instance.Command);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +85,23 @@ namespace Robust.Shared.Console
|
||||
{
|
||||
}
|
||||
|
||||
public void BeginRegistrationRegion()
|
||||
{
|
||||
if (_isInRegistrationRegion)
|
||||
throw new InvalidOperationException("Cannot enter registration region twice!");
|
||||
|
||||
_isInRegistrationRegion = true;
|
||||
}
|
||||
|
||||
public void EndRegistrationRegion()
|
||||
{
|
||||
if (!_isInRegistrationRegion)
|
||||
throw new InvalidOperationException("Was not in registration region.");
|
||||
|
||||
_isInRegistrationRegion = false;
|
||||
UpdateAvailableCommands();
|
||||
}
|
||||
|
||||
#region RegisterCommand
|
||||
public void RegisterCommand(
|
||||
string command,
|
||||
@@ -88,8 +114,7 @@ namespace Robust.Shared.Console
|
||||
throw new InvalidOperationException($"Command already registered: {command}");
|
||||
|
||||
var newCmd = new RegisteredCommand(command, description, help, callback, requireServerOrSingleplayer);
|
||||
RegisteredCommands.Add(command, newCmd);
|
||||
UpdateAvailableCommands();
|
||||
RegisterCommand(newCmd);
|
||||
}
|
||||
|
||||
public void RegisterCommand(
|
||||
@@ -104,8 +129,7 @@ namespace Robust.Shared.Console
|
||||
throw new InvalidOperationException($"Command already registered: {command}");
|
||||
|
||||
var newCmd = new RegisteredCommand(command, description, help, callback, completionCallback, requireServerOrSingleplayer);
|
||||
RegisteredCommands.Add(command, newCmd);
|
||||
UpdateAvailableCommands();
|
||||
RegisterCommand(newCmd);
|
||||
}
|
||||
|
||||
public void RegisterCommand(
|
||||
@@ -120,8 +144,7 @@ namespace Robust.Shared.Console
|
||||
throw new InvalidOperationException($"Command already registered: {command}");
|
||||
|
||||
var newCmd = new RegisteredCommand(command, description, help, callback, completionCallback, requireServerOrSingleplayer);
|
||||
RegisteredCommands.Add(command, newCmd);
|
||||
UpdateAvailableCommands();
|
||||
RegisterCommand(newCmd);
|
||||
}
|
||||
|
||||
public void RegisterCommand(string command, ConCommandCallback callback,
|
||||
@@ -153,6 +176,15 @@ namespace Robust.Shared.Console
|
||||
var help = LocalizationManager.TryGetString($"cmd-{command}-help", out var val) ? val : "";
|
||||
RegisterCommand(command, description, help, callback, completionCallback, requireServerOrSingleplayer);
|
||||
}
|
||||
|
||||
public void RegisterCommand(IConsoleCommand command)
|
||||
{
|
||||
RegisteredCommands.Add(command.Command, command);
|
||||
|
||||
if (!_isInRegistrationRegion)
|
||||
UpdateAvailableCommands();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -161,12 +193,14 @@ namespace Robust.Shared.Console
|
||||
if (!RegisteredCommands.TryGetValue(command, out var cmd))
|
||||
throw new KeyNotFoundException($"Command {command} is not registered.");
|
||||
|
||||
if (cmd is not RegisteredCommand)
|
||||
if (_autoRegisteredCommands.Contains(command))
|
||||
throw new InvalidOperationException(
|
||||
"You cannot unregister commands that have been registered automatically.");
|
||||
|
||||
RegisteredCommands.Remove(command);
|
||||
UpdateAvailableCommands();
|
||||
|
||||
if (!_isInRegistrationRegion)
|
||||
UpdateAvailableCommands();
|
||||
}
|
||||
|
||||
//TODO: Pull up
|
||||
|
||||
54
Robust.Shared/Console/EntityConsoleHost.cs
Normal file
54
Robust.Shared/Console/EntityConsoleHost.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Manages registration for "entity" console commands.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// See <see cref="LocalizedEntityCommands"/> for details on what "entity" console commands are.
|
||||
/// </remarks>
|
||||
internal sealed class EntityConsoleHost
|
||||
{
|
||||
[Dependency] private readonly IConsoleHost _consoleHost = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
private readonly HashSet<string> _entityCommands = [];
|
||||
|
||||
public void Startup()
|
||||
{
|
||||
DebugTools.Assert(_entityCommands.Count == 0);
|
||||
|
||||
var deps = ((EntitySystemManager)_entitySystemManager).SystemDependencyCollection;
|
||||
|
||||
_consoleHost.BeginRegistrationRegion();
|
||||
|
||||
// search for all client commands in all assemblies, and register them
|
||||
foreach (var type in _reflectionManager.GetAllChildren<IEntityConsoleCommand>())
|
||||
{
|
||||
var instance = (IConsoleCommand)Activator.CreateInstance(type)!;
|
||||
deps.InjectDependencies(instance, oneOff: true);
|
||||
|
||||
_entityCommands.Add(instance.Command);
|
||||
_consoleHost.RegisterCommand(instance);
|
||||
}
|
||||
|
||||
_consoleHost.EndRegistrationRegion();
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
foreach (var command in _entityCommands)
|
||||
{
|
||||
_consoleHost.UnregisterCommand(command);
|
||||
}
|
||||
|
||||
_entityCommands.Clear();
|
||||
}
|
||||
}
|
||||
@@ -83,4 +83,11 @@ namespace Robust.Shared.Console
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Special marker interface used to indicate "entity" commands.
|
||||
/// See <see cref="LocalizedEntityCommands"/> for an overview.
|
||||
/// </summary>
|
||||
/// <seealso cref="EntityConsoleHost"/>
|
||||
internal interface IEntityConsoleCommand : IConsoleCommand;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Console
|
||||
@@ -173,6 +174,33 @@ namespace Robust.Shared.Console
|
||||
ConCommandCallback callback,
|
||||
ConCommandCompletionAsyncCallback completionCallback,
|
||||
bool requireServerOrSingleplayer = false);
|
||||
|
||||
/// <summary>
|
||||
/// Register an existing console command instance directly.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For this to be useful, the command has to be somehow excluded from automatic registration,
|
||||
/// such as by using the <see cref="ReflectAttribute"/>.
|
||||
/// </remarks>
|
||||
/// <param name="command">The command to register.</param>
|
||||
/// <seealso cref="BeginRegistrationRegion"/>
|
||||
void RegisterCommand(IConsoleCommand command);
|
||||
|
||||
/// <summary>
|
||||
/// Begin a region for registering many console commands in one go.
|
||||
/// The region can be ended with <see cref="EndRegistrationRegion"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Commands registered inside this region temporarily suppress some updating
|
||||
/// logic that would cause significant wasted work. This logic runs when the region is ended instead.
|
||||
/// </remarks>
|
||||
void BeginRegistrationRegion();
|
||||
|
||||
/// <summary>
|
||||
/// End a registration region started with <see cref="BeginRegistrationRegion"/>.
|
||||
/// </summary>
|
||||
void EndRegistrationRegion();
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
@@ -34,3 +35,21 @@ public abstract class LocalizedCommands : IConsoleCommand
|
||||
return ValueTask.FromResult(GetCompletion(shell, args));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for localized console commands that run in "entity space".
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This type of command is registered only while the entity system is active.
|
||||
/// On the client this means that the commands are only available while connected to a server or in single player.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// These commands are allowed to take dependencies on entity systems, reducing boilerplate for many usages.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public abstract class LocalizedEntityCommands : LocalizedCommands, IEntityConsoleCommand
|
||||
{
|
||||
[Dependency]
|
||||
protected readonly EntityManager EntityManager = default!;
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ namespace Robust.Shared.ContentPack
|
||||
public string SystemAssemblyName = default!;
|
||||
public HashSet<VerifierError> AllowedVerifierErrors = default!;
|
||||
public List<string> WhitelistedNamespaces = default!;
|
||||
public List<string> AllowedAssemblyPrefixes = default!;
|
||||
public Dictionary<string, Dictionary<string, TypeConfig>> Types = default!;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
#if TOOLS
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Robust.Shared.ContentPack;
|
||||
|
||||
internal sealed partial class AssemblyTypeChecker
|
||||
{
|
||||
// This part of the code tries to find the originator of bad sandbox references.
|
||||
|
||||
private void ReportBadReferences(PEReader peReader, MetadataReader reader, IEnumerable<EntityHandle> reference)
|
||||
{
|
||||
_sawmill.Info("Started search for originator of bad references...");
|
||||
|
||||
var refs = reference.ToHashSet();
|
||||
ExpandReferences(reader, refs);
|
||||
|
||||
foreach (var methodDefHandle in reader.MethodDefinitions)
|
||||
{
|
||||
var methodDef = reader.GetMethodDefinition(methodDefHandle);
|
||||
if (methodDef.RelativeVirtualAddress == 0)
|
||||
continue;
|
||||
|
||||
var methodName = reader.GetString(methodDef.Name);
|
||||
|
||||
var body = peReader.GetMethodBody(methodDef.RelativeVirtualAddress);
|
||||
var bytes = body.GetILBytes()!;
|
||||
|
||||
var ilReader = new ILReader(bytes);
|
||||
var prefPosition = 0;
|
||||
while (ilReader.MoveNext(out var instruction))
|
||||
{
|
||||
if (instruction.TryGetEntityHandle(out var handle))
|
||||
{
|
||||
if (refs.Contains(handle))
|
||||
{
|
||||
var type = GetTypeFromDefinition(reader, methodDef.GetDeclaringType());
|
||||
_sawmill.Error(
|
||||
$"Found reference to {DisplayHandle(reader, handle)} in method {type}.{methodName} at IL 0x{prefPosition:X4}");
|
||||
}
|
||||
}
|
||||
|
||||
prefPosition = ilReader.Position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string DisplayHandle(MetadataReader reader, EntityHandle handle)
|
||||
{
|
||||
switch (handle.Kind)
|
||||
{
|
||||
case HandleKind.MemberReference:
|
||||
var memberRef = reader.GetMemberReference((MemberReferenceHandle)handle);
|
||||
var name = reader.GetString(memberRef.Name);
|
||||
var parent = DisplayHandle(reader, memberRef.Parent);
|
||||
return $"{parent}.{name}";
|
||||
|
||||
case HandleKind.TypeReference:
|
||||
return $"{ParseTypeReference(reader, (TypeReferenceHandle)handle)}";
|
||||
|
||||
case HandleKind.TypeSpecification:
|
||||
var typeSpec = reader.GetTypeSpecification((TypeSpecificationHandle)handle);
|
||||
var provider = new TypeProvider();
|
||||
var type = typeSpec.DecodeSignature(provider, 0);
|
||||
return $"{type}";
|
||||
|
||||
default:
|
||||
return $"({handle.Kind} handle)";
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExpandReferences(MetadataReader reader, HashSet<EntityHandle> handles)
|
||||
{
|
||||
var toAdd = new List<EntityHandle>();
|
||||
|
||||
foreach (var memberRefHandle in reader.MemberReferences)
|
||||
{
|
||||
var memberRef = reader.GetMemberReference(memberRefHandle);
|
||||
if (handles.Contains(memberRef.Parent))
|
||||
{
|
||||
toAdd.Add(memberRefHandle);
|
||||
}
|
||||
}
|
||||
|
||||
handles.UnionWith(toAdd);
|
||||
}
|
||||
|
||||
private readonly struct ILInstruction
|
||||
{
|
||||
public readonly ILOpCode OpCode;
|
||||
public readonly long Argument;
|
||||
public readonly int[]? SwitchTargets;
|
||||
|
||||
public ILInstruction(ILOpCode opCode)
|
||||
{
|
||||
OpCode = opCode;
|
||||
}
|
||||
|
||||
public ILInstruction(ILOpCode opCode, long argument)
|
||||
{
|
||||
OpCode = opCode;
|
||||
Argument = argument;
|
||||
}
|
||||
|
||||
public ILInstruction(ILOpCode opCode, long argument, int[] switchTargets)
|
||||
{
|
||||
OpCode = opCode;
|
||||
Argument = argument;
|
||||
SwitchTargets = switchTargets;
|
||||
}
|
||||
|
||||
public bool TryGetEntityHandle(out EntityHandle handle)
|
||||
{
|
||||
switch (OpCode)
|
||||
{
|
||||
case ILOpCode.Call:
|
||||
case ILOpCode.Callvirt:
|
||||
case ILOpCode.Newobj:
|
||||
case ILOpCode.Jmp:
|
||||
case ILOpCode.Box:
|
||||
case ILOpCode.Castclass:
|
||||
case ILOpCode.Cpobj:
|
||||
case ILOpCode.Initobj:
|
||||
case ILOpCode.Isinst:
|
||||
case ILOpCode.Ldelem:
|
||||
case ILOpCode.Ldelema:
|
||||
case ILOpCode.Ldfld:
|
||||
case ILOpCode.Ldflda:
|
||||
case ILOpCode.Ldobj:
|
||||
case ILOpCode.Ldstr:
|
||||
case ILOpCode.Ldtoken:
|
||||
case ILOpCode.Ldvirtftn:
|
||||
case ILOpCode.Mkrefany:
|
||||
case ILOpCode.Newarr:
|
||||
case ILOpCode.Refanyval:
|
||||
case ILOpCode.Sizeof:
|
||||
case ILOpCode.Stelem:
|
||||
case ILOpCode.Stfld:
|
||||
case ILOpCode.Stobj:
|
||||
case ILOpCode.Stsfld:
|
||||
case ILOpCode.Throw:
|
||||
case ILOpCode.Unbox_any:
|
||||
handle = Unsafe.BitCast<int, EntityHandle>((int)Argument);
|
||||
return true;
|
||||
|
||||
default:
|
||||
handle = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ILReader(byte[] body)
|
||||
{
|
||||
public int Position;
|
||||
|
||||
public bool MoveNext(out ILInstruction instruction)
|
||||
{
|
||||
if (Position >= body.Length)
|
||||
{
|
||||
instruction = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var firstByte = body[Position++];
|
||||
var opCode = (ILOpCode)firstByte;
|
||||
if (firstByte == 0xFE)
|
||||
opCode = 0xFE00 + (ILOpCode)body[Position++];
|
||||
|
||||
switch (opCode)
|
||||
{
|
||||
// no args.
|
||||
case ILOpCode.Readonly:
|
||||
case ILOpCode.Tail:
|
||||
case ILOpCode.Volatile:
|
||||
case ILOpCode.Add:
|
||||
case ILOpCode.Add_ovf:
|
||||
case ILOpCode.Add_ovf_un:
|
||||
case ILOpCode.And:
|
||||
case ILOpCode.Arglist:
|
||||
case ILOpCode.Break:
|
||||
case ILOpCode.Ceq:
|
||||
case ILOpCode.Cgt:
|
||||
case ILOpCode.Cgt_un:
|
||||
case ILOpCode.Ckfinite:
|
||||
case ILOpCode.Clt:
|
||||
case ILOpCode.Clt_un:
|
||||
case ILOpCode.Conv_i1:
|
||||
case ILOpCode.Conv_i2:
|
||||
case ILOpCode.Conv_i4:
|
||||
case ILOpCode.Conv_i8:
|
||||
case ILOpCode.Conv_r4:
|
||||
case ILOpCode.Conv_r8:
|
||||
case ILOpCode.Conv_u1:
|
||||
case ILOpCode.Conv_u2:
|
||||
case ILOpCode.Conv_u4:
|
||||
case ILOpCode.Conv_u8:
|
||||
case ILOpCode.Conv_i:
|
||||
case ILOpCode.Conv_u:
|
||||
case ILOpCode.Conv_r_un:
|
||||
case ILOpCode.Conv_ovf_i1:
|
||||
case ILOpCode.Conv_ovf_i2:
|
||||
case ILOpCode.Conv_ovf_i4:
|
||||
case ILOpCode.Conv_ovf_i8:
|
||||
case ILOpCode.Conv_ovf_u4:
|
||||
case ILOpCode.Conv_ovf_u8:
|
||||
case ILOpCode.Conv_ovf_i:
|
||||
case ILOpCode.Conv_ovf_u:
|
||||
case ILOpCode.Conv_ovf_i1_un:
|
||||
case ILOpCode.Conv_ovf_i2_un:
|
||||
case ILOpCode.Conv_ovf_i4_un:
|
||||
case ILOpCode.Conv_ovf_i8_un:
|
||||
case ILOpCode.Conv_ovf_u4_un:
|
||||
case ILOpCode.Conv_ovf_u8_un:
|
||||
case ILOpCode.Conv_ovf_i_un:
|
||||
case ILOpCode.Conv_ovf_u_un:
|
||||
case ILOpCode.Cpblk:
|
||||
case ILOpCode.Div:
|
||||
case ILOpCode.Div_un:
|
||||
case ILOpCode.Dup:
|
||||
case ILOpCode.Endfilter:
|
||||
case ILOpCode.Endfinally:
|
||||
case ILOpCode.Initblk:
|
||||
case ILOpCode.Ldarg_0:
|
||||
case ILOpCode.Ldarg_1:
|
||||
case ILOpCode.Ldarg_2:
|
||||
case ILOpCode.Ldarg_3:
|
||||
case ILOpCode.Ldc_i4_0:
|
||||
case ILOpCode.Ldc_i4_1:
|
||||
case ILOpCode.Ldc_i4_2:
|
||||
case ILOpCode.Ldc_i4_3:
|
||||
case ILOpCode.Ldc_i4_4:
|
||||
case ILOpCode.Ldc_i4_5:
|
||||
case ILOpCode.Ldc_i4_6:
|
||||
case ILOpCode.Ldc_i4_7:
|
||||
case ILOpCode.Ldc_i4_8:
|
||||
case ILOpCode.Ldc_i4_m1:
|
||||
case ILOpCode.Ldind_i1:
|
||||
case ILOpCode.Ldind_u1:
|
||||
case ILOpCode.Ldind_i2:
|
||||
case ILOpCode.Ldind_u2:
|
||||
case ILOpCode.Ldind_i4:
|
||||
case ILOpCode.Ldind_u4:
|
||||
case ILOpCode.Ldind_i8:
|
||||
case ILOpCode.Ldind_i:
|
||||
case ILOpCode.Ldind_r4:
|
||||
case ILOpCode.Ldind_r8:
|
||||
case ILOpCode.Ldind_ref:
|
||||
case ILOpCode.Ldloc_0:
|
||||
case ILOpCode.Ldloc_1:
|
||||
case ILOpCode.Ldloc_2:
|
||||
case ILOpCode.Ldloc_3:
|
||||
case ILOpCode.Ldnull:
|
||||
case ILOpCode.Localloc:
|
||||
case ILOpCode.Mul:
|
||||
case ILOpCode.Mul_ovf:
|
||||
case ILOpCode.Mul_ovf_un:
|
||||
case ILOpCode.Neg:
|
||||
case ILOpCode.Nop:
|
||||
case ILOpCode.Not:
|
||||
case ILOpCode.Or:
|
||||
case ILOpCode.Pop:
|
||||
case ILOpCode.Rem:
|
||||
case ILOpCode.Rem_un:
|
||||
case ILOpCode.Ret:
|
||||
case ILOpCode.Shl:
|
||||
case ILOpCode.Shr:
|
||||
case ILOpCode.Shr_un:
|
||||
case ILOpCode.Stind_i1:
|
||||
case ILOpCode.Stind_i2:
|
||||
case ILOpCode.Stind_i4:
|
||||
case ILOpCode.Stind_i8:
|
||||
case ILOpCode.Stind_r4:
|
||||
case ILOpCode.Stind_r8:
|
||||
case ILOpCode.Stind_i:
|
||||
case ILOpCode.Stind_ref:
|
||||
case ILOpCode.Stloc_0:
|
||||
case ILOpCode.Stloc_1:
|
||||
case ILOpCode.Stloc_2:
|
||||
case ILOpCode.Stloc_3:
|
||||
case ILOpCode.Sub:
|
||||
case ILOpCode.Sub_ovf:
|
||||
case ILOpCode.Sub_ovf_un:
|
||||
case ILOpCode.Xor:
|
||||
case ILOpCode.Ldelem_i1:
|
||||
case ILOpCode.Ldelem_u1:
|
||||
case ILOpCode.Ldelem_i2:
|
||||
case ILOpCode.Ldelem_u2:
|
||||
case ILOpCode.Ldelem_i4:
|
||||
case ILOpCode.Ldelem_u4:
|
||||
case ILOpCode.Ldelem_i8:
|
||||
case ILOpCode.Ldelem_i:
|
||||
case ILOpCode.Ldelem_r4:
|
||||
case ILOpCode.Ldelem_r8:
|
||||
case ILOpCode.Ldelem_ref:
|
||||
case ILOpCode.Ldlen:
|
||||
case ILOpCode.Refanytype:
|
||||
case ILOpCode.Rethrow:
|
||||
case ILOpCode.Stelem_i1:
|
||||
case ILOpCode.Stelem_i2:
|
||||
case ILOpCode.Stelem_i4:
|
||||
case ILOpCode.Stelem_i8:
|
||||
case ILOpCode.Stelem_i:
|
||||
case ILOpCode.Stelem_r4:
|
||||
case ILOpCode.Stelem_r8:
|
||||
case ILOpCode.Stelem_ref:
|
||||
case ILOpCode.Throw:
|
||||
instruction = new ILInstruction(opCode);
|
||||
break;
|
||||
|
||||
// 1-byte arg.
|
||||
case ILOpCode.Unaligned:
|
||||
case ILOpCode.Beq_s:
|
||||
case ILOpCode.Bge_s:
|
||||
case ILOpCode.Bge_un_s:
|
||||
case ILOpCode.Bgt_s:
|
||||
case ILOpCode.Bgt_un_s:
|
||||
case ILOpCode.Ble_s:
|
||||
case ILOpCode.Ble_un_s:
|
||||
case ILOpCode.Blt_s:
|
||||
case ILOpCode.Blt_un_s:
|
||||
case ILOpCode.Bne_un_s:
|
||||
case ILOpCode.Br_s:
|
||||
case ILOpCode.Brfalse_s:
|
||||
case ILOpCode.Brtrue_s:
|
||||
case ILOpCode.Ldarg_s:
|
||||
case ILOpCode.Ldarga_s:
|
||||
case ILOpCode.Ldc_i4_s:
|
||||
case ILOpCode.Ldloc_s:
|
||||
case ILOpCode.Ldloca_s:
|
||||
case ILOpCode.Leave_s:
|
||||
case ILOpCode.Starg_s:
|
||||
case ILOpCode.Stloc_s:
|
||||
instruction = new ILInstruction(opCode, body[Position]);
|
||||
Position += 1;
|
||||
break;
|
||||
|
||||
// 2-byte value
|
||||
case ILOpCode.Ldarg:
|
||||
case ILOpCode.Ldarga:
|
||||
case ILOpCode.Ldloc:
|
||||
case ILOpCode.Ldloca:
|
||||
case ILOpCode.Starg:
|
||||
case ILOpCode.Stloc:
|
||||
var shortValue = BinaryPrimitives.ReadInt16LittleEndian(body.AsSpan(Position, 2));
|
||||
Position += 2;
|
||||
instruction = new ILInstruction(opCode, shortValue);
|
||||
break;
|
||||
|
||||
// 4-byte value
|
||||
case ILOpCode.Constrained:
|
||||
case ILOpCode.Beq:
|
||||
case ILOpCode.Bge:
|
||||
case ILOpCode.Bge_un:
|
||||
case ILOpCode.Bgt:
|
||||
case ILOpCode.Bgt_un:
|
||||
case ILOpCode.Ble:
|
||||
case ILOpCode.Ble_un:
|
||||
case ILOpCode.Blt:
|
||||
case ILOpCode.Blt_un:
|
||||
case ILOpCode.Bne_un:
|
||||
case ILOpCode.Br:
|
||||
case ILOpCode.Brfalse:
|
||||
case ILOpCode.Brtrue:
|
||||
case ILOpCode.Call:
|
||||
case ILOpCode.Calli:
|
||||
case ILOpCode.Jmp:
|
||||
case ILOpCode.Ldc_i4:
|
||||
case ILOpCode.Ldc_r4:
|
||||
case ILOpCode.Ldftn:
|
||||
case ILOpCode.Leave:
|
||||
case ILOpCode.Box:
|
||||
case ILOpCode.Callvirt:
|
||||
case ILOpCode.Castclass:
|
||||
case ILOpCode.Cpobj:
|
||||
case ILOpCode.Initobj:
|
||||
case ILOpCode.Isinst:
|
||||
case ILOpCode.Ldelem:
|
||||
case ILOpCode.Ldelema:
|
||||
case ILOpCode.Ldfld:
|
||||
case ILOpCode.Ldflda:
|
||||
case ILOpCode.Ldobj:
|
||||
case ILOpCode.Ldsfld:
|
||||
case ILOpCode.Ldsflda:
|
||||
case ILOpCode.Ldstr:
|
||||
case ILOpCode.Ldtoken:
|
||||
case ILOpCode.Ldvirtftn:
|
||||
case ILOpCode.Mkrefany:
|
||||
case ILOpCode.Newarr:
|
||||
case ILOpCode.Newobj:
|
||||
case ILOpCode.Refanyval:
|
||||
case ILOpCode.Sizeof:
|
||||
case ILOpCode.Stelem:
|
||||
case ILOpCode.Stfld:
|
||||
case ILOpCode.Stobj:
|
||||
case ILOpCode.Stsfld:
|
||||
case ILOpCode.Unbox:
|
||||
case ILOpCode.Unbox_any:
|
||||
var intValue = BinaryPrimitives.ReadInt32LittleEndian(body.AsSpan(Position, 4));
|
||||
Position += 4;
|
||||
instruction = new ILInstruction(opCode, intValue);
|
||||
break;
|
||||
|
||||
// 8-byte value
|
||||
case ILOpCode.Ldc_i8:
|
||||
case ILOpCode.Ldc_r8:
|
||||
var longValue = BinaryPrimitives.ReadInt64LittleEndian(body.AsSpan(Position, 8));
|
||||
Position += 8;
|
||||
instruction = new ILInstruction(opCode, longValue);
|
||||
break;
|
||||
|
||||
// Switch
|
||||
case ILOpCode.Switch:
|
||||
var switchLength = BinaryPrimitives.ReadInt32LittleEndian(body.AsSpan(Position, 4));
|
||||
Position += 4;
|
||||
var switchArgs = new int[switchLength];
|
||||
for (var i = 0; i < switchLength; i++)
|
||||
{
|
||||
switchArgs[i] = BinaryPrimitives.ReadInt32LittleEndian(body.AsSpan(Position, 4));
|
||||
Position += 4;
|
||||
}
|
||||
|
||||
instruction = new ILInstruction(opCode, switchLength, switchArgs);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Unknown opcode: {opCode}");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -131,6 +131,16 @@ namespace Robust.Shared.ContentPack
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma warning disable RA0004
|
||||
var loadedConfig = _config.Result;
|
||||
#pragma warning restore RA0004
|
||||
|
||||
if (!loadedConfig.AllowedAssemblyPrefixes.Any(allowedNamePrefix => asmName.StartsWith(allowedNamePrefix)))
|
||||
{
|
||||
_sawmill.Error($"Assembly name '{asmName}' is not allowed for a content assembly");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VerifyIL)
|
||||
{
|
||||
if (!DoVerifyIL(asmName, resolver, peReader, reader))
|
||||
@@ -148,7 +158,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
if ((Dump & DumpFlags.Types) != 0)
|
||||
{
|
||||
foreach (var mType in types)
|
||||
foreach (var (_, mType) in types)
|
||||
{
|
||||
_sawmill.Debug($"RefType: {mType}");
|
||||
}
|
||||
@@ -156,7 +166,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
if ((Dump & DumpFlags.Members) != 0)
|
||||
{
|
||||
foreach (var memberRef in members)
|
||||
foreach (var (_, memberRef) in members)
|
||||
{
|
||||
_sawmill.Debug($"RefMember: {memberRef}");
|
||||
}
|
||||
@@ -179,18 +189,17 @@ namespace Robust.Shared.ContentPack
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma warning disable RA0004
|
||||
var loadedConfig = _config.Result;
|
||||
#pragma warning restore RA0004
|
||||
var badRefs = new ConcurrentBag<EntityHandle>();
|
||||
|
||||
// We still do explicit type reference scanning, even though the actual whitelists work with raw members.
|
||||
// This is so that we can simplify handling of generic type specifications during member checking:
|
||||
// we won't have to check that any types in their type arguments are whitelisted.
|
||||
foreach (var type in types)
|
||||
foreach (var (handle, type) in types)
|
||||
{
|
||||
if (!IsTypeAccessAllowed(loadedConfig, type, out _))
|
||||
{
|
||||
errors.Add(new SandboxError($"Access to type not allowed: {type}"));
|
||||
badRefs.Add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,13 +217,20 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
_sawmill.Debug($"Type abuse... {fullStopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
CheckMemberReferences(loadedConfig, members, errors);
|
||||
CheckMemberReferences(loadedConfig, members, errors, badRefs);
|
||||
|
||||
foreach (var error in errors)
|
||||
{
|
||||
_sawmill.Error($"Sandbox violation: {error.Message}");
|
||||
}
|
||||
|
||||
#if TOOLS
|
||||
if (!badRefs.IsEmpty)
|
||||
{
|
||||
ReportBadReferences(peReader, reader, badRefs);
|
||||
}
|
||||
#endif
|
||||
|
||||
_sawmill.Debug($"Checked assembly in {fullStopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
return errors.IsEmpty;
|
||||
@@ -351,11 +367,13 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
private void CheckMemberReferences(
|
||||
SandboxConfig sandboxConfig,
|
||||
List<MMemberRef> members,
|
||||
ConcurrentBag<SandboxError> errors)
|
||||
List<(MemberReferenceHandle handle, MMemberRef parsed)> members,
|
||||
ConcurrentBag<SandboxError> errors,
|
||||
ConcurrentBag<EntityHandle> badReferences)
|
||||
{
|
||||
Parallel.ForEach(members, memberRef =>
|
||||
Parallel.ForEach(members, entry =>
|
||||
{
|
||||
var (handle, memberRef) = entry;
|
||||
MType baseType = memberRef.ParentType;
|
||||
while (!(baseType is MTypeReferenced))
|
||||
{
|
||||
@@ -416,6 +434,7 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
|
||||
errors.Add(new SandboxError($"Access to field not allowed: {mMemberRefField}"));
|
||||
badReferences.Add(handle);
|
||||
break;
|
||||
}
|
||||
case MMemberRefMethod mMemberRefMethod:
|
||||
@@ -444,6 +463,7 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
|
||||
errors.Add(new SandboxError($"Access to method not allowed: {mMemberRefMethod}"));
|
||||
badReferences.Add(handle);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(memberRef));
|
||||
@@ -458,18 +478,18 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
// This inheritance whitelisting primarily serves to avoid content doing funny stuff
|
||||
// by e.g. inheriting Type.
|
||||
foreach (var (_, baseType, interfaces) in inherited)
|
||||
foreach (var (type, baseType, interfaces) in inherited)
|
||||
{
|
||||
if (!CanInherit(baseType))
|
||||
{
|
||||
errors.Add(new SandboxError($"Inheriting of type not allowed: {baseType}"));
|
||||
errors.Add(new SandboxError($"Inheriting of type not allowed: {baseType} (by {type})"));
|
||||
}
|
||||
|
||||
foreach (var @interface in interfaces)
|
||||
{
|
||||
if (!CanInherit(@interface))
|
||||
{
|
||||
errors.Add(new SandboxError($"Implementing of interface not allowed: {@interface}"));
|
||||
errors.Add(new SandboxError($"Implementing of interface not allowed: {@interface} (by {type})"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,25 +567,25 @@ namespace Robust.Shared.ContentPack
|
||||
return nsDict.TryGetValue(type.Name, out cfg);
|
||||
}
|
||||
|
||||
private List<MTypeReferenced> GetReferencedTypes(MetadataReader reader, ConcurrentBag<SandboxError> errors)
|
||||
private List<(TypeReferenceHandle handle, MTypeReferenced parsed)> GetReferencedTypes(MetadataReader reader, ConcurrentBag<SandboxError> errors)
|
||||
{
|
||||
return reader.TypeReferences.Select(typeRefHandle =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return ParseTypeReference(reader, typeRefHandle);
|
||||
return (typeRefHandle, ParseTypeReference(reader, typeRefHandle));
|
||||
}
|
||||
catch (UnsupportedMetadataException e)
|
||||
{
|
||||
errors.Add(new SandboxError(e));
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
})
|
||||
.Where(p => p != null)
|
||||
.Where(p => p.Item2 != null)
|
||||
.ToList()!;
|
||||
}
|
||||
|
||||
private List<MMemberRef> GetReferencedMembers(MetadataReader reader, ConcurrentBag<SandboxError> errors)
|
||||
private List<(MemberReferenceHandle handle, MMemberRef parsed)> GetReferencedMembers(MetadataReader reader, ConcurrentBag<SandboxError> errors)
|
||||
{
|
||||
return reader.MemberReferences.AsParallel()
|
||||
.Select(memRefHandle =>
|
||||
@@ -586,7 +606,7 @@ namespace Robust.Shared.ContentPack
|
||||
catch (UnsupportedMetadataException u)
|
||||
{
|
||||
errors.Add(new SandboxError(u));
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -600,7 +620,7 @@ namespace Robust.Shared.ContentPack
|
||||
catch (UnsupportedMetadataException u)
|
||||
{
|
||||
errors.Add(new SandboxError(u));
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -616,7 +636,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
// Ensure this isn't a self-defined type.
|
||||
// This can happen due to generics since MethodSpec needs to point to MemberRef.
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -625,18 +645,18 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
errors.Add(new SandboxError(
|
||||
$"Module global variables and methods are unsupported. Name: {memName}"));
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
case HandleKind.MethodDefinition:
|
||||
{
|
||||
errors.Add(new SandboxError($"Vararg calls are unsupported. Name: {memName}"));
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
default:
|
||||
{
|
||||
errors.Add(new SandboxError(
|
||||
$"Unsupported member ref parent type: {memRef.Parent.Kind}. Name: {memName}"));
|
||||
return null;
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -667,9 +687,9 @@ namespace Robust.Shared.ContentPack
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return memberRef;
|
||||
return (memRefHandle, memberRef);
|
||||
})
|
||||
.Where(p => p != null)
|
||||
.Where(p => p.memberRef != null)
|
||||
.ToList()!;
|
||||
}
|
||||
|
||||
@@ -780,7 +800,6 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <exception href="UnsupportedMetadataException">
|
||||
/// Thrown if the metadata does something funny we don't "support" like type forwarding.
|
||||
/// </exception>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()));
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
void Initialize(string? userData);
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -93,19 +93,23 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
Sawmill.Debug("LOADING modules");
|
||||
var files = new Dictionary<string, (ResPath Path, string[] references)>();
|
||||
var files = new Dictionary<string, (ResPath Path, MemoryStream data, string[] references)>();
|
||||
|
||||
// Find all modules we want to load.
|
||||
foreach (var fullPath in paths)
|
||||
{
|
||||
using var asmFile = _res.ContentFileRead(fullPath);
|
||||
var refData = GetAssemblyReferenceData(asmFile);
|
||||
var ms = new MemoryStream();
|
||||
asmFile.CopyTo(ms);
|
||||
|
||||
ms.Position = 0;
|
||||
var refData = GetAssemblyReferenceData(ms);
|
||||
if (refData == null)
|
||||
continue;
|
||||
|
||||
var (asmRefs, asmName) = refData.Value;
|
||||
|
||||
if (!files.TryAdd(asmName, (fullPath, asmRefs)))
|
||||
if (!files.TryAdd(asmName, (fullPath, ms, asmRefs)))
|
||||
{
|
||||
Sawmill.Error("Found multiple modules with the same assembly name " +
|
||||
$"'{asmName}', A: {files[asmName].Path}, B: {fullPath}.");
|
||||
@@ -122,10 +126,10 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
Parallel.ForEach(files, pair =>
|
||||
{
|
||||
var (name, (path, _)) = pair;
|
||||
var (name, (_, data, _)) = pair;
|
||||
|
||||
using var stream = _res.ContentFileRead(path);
|
||||
if (!typeChecker.CheckAssembly(stream, resolver))
|
||||
data.Position = 0;
|
||||
if (!typeChecker.CheckAssembly(data, resolver))
|
||||
{
|
||||
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
|
||||
}
|
||||
@@ -137,14 +141,15 @@ namespace Robust.Shared.ContentPack
|
||||
var nodes = TopologicalSort.FromBeforeAfter(
|
||||
files,
|
||||
kv => kv.Key,
|
||||
kv => kv.Value.Path,
|
||||
kv => kv.Value,
|
||||
_ => Array.Empty<string>(),
|
||||
kv => kv.Value.references,
|
||||
allowMissing: true); // missing refs would be non-content assemblies so allow that.
|
||||
|
||||
// Actually load them in the order they depend on each other.
|
||||
foreach (var path in TopologicalSort.Sort(nodes))
|
||||
foreach (var item in TopologicalSort.Sort(nodes))
|
||||
{
|
||||
var (path, memory, _) = item;
|
||||
Sawmill.Debug($"Loading module: '{path}'");
|
||||
try
|
||||
{
|
||||
@@ -156,9 +161,9 @@ namespace Robust.Shared.ContentPack
|
||||
}
|
||||
else
|
||||
{
|
||||
using var assemblyStream = _res.ContentFileRead(path);
|
||||
memory.Position = 0;
|
||||
using var symbolsStream = _res.ContentFileReadOrNull(path.WithExtension("pdb"));
|
||||
LoadGameAssembly(assemblyStream, symbolsStream, skipVerify: true);
|
||||
LoadGameAssembly(memory, symbolsStream, skipVerify: true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -174,7 +179,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
private (string[] refs, string name)? GetAssemblyReferenceData(Stream stream)
|
||||
{
|
||||
using var reader = ModLoader.MakePEReader(stream);
|
||||
using var reader = ModLoader.MakePEReader(stream, leaveOpen: true);
|
||||
var metaReader = reader.GetMetadataReader();
|
||||
|
||||
var name = metaReader.GetString(metaReader.GetAssemblyDefinition().Name);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -63,5 +64,27 @@ namespace Robust.Shared.ContentPack
|
||||
!OperatingSystem.IsWindows()
|
||||
&& !OperatingSystem.IsMacOS();
|
||||
|
||||
|
||||
internal static string SafeGetResourcePath(string baseDir, ResPath path)
|
||||
{
|
||||
var relSysPath = path.ToRelativeSystemPath();
|
||||
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
|
||||
// better safe than sorry check
|
||||
if (!retPath.StartsWith(baseDir))
|
||||
{
|
||||
// Allow path to match if it's just missing the directory separator at the end.
|
||||
if (retPath != baseDir.TrimEnd(Path.DirectorySeparatorChar))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData)
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -379,7 +379,13 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
if (root is DirLoader loader)
|
||||
{
|
||||
yield return new ResPath(loader.GetPath(new ResPath(@"/")));
|
||||
var rootDir = loader.GetPath(new ResPath(@"/"));
|
||||
|
||||
// TODO: GET RID OF THIS.
|
||||
// This code shouldn't be passing OS disk paths through ResPath.
|
||||
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
|
||||
|
||||
yield return new ResPath(rootDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ WhitelistedNamespaces:
|
||||
- Content
|
||||
- OpenDreamShared
|
||||
|
||||
AllowedAssemblyPrefixes:
|
||||
- OpenDream
|
||||
- Content
|
||||
|
||||
# The type whitelist does NOT care about which assembly types come from.
|
||||
# This is because types switch assembly all the time.
|
||||
# Just look up stuff like StreamReader on https://apisof.net.
|
||||
@@ -84,12 +88,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:
|
||||
@@ -320,9 +458,10 @@ Types:
|
||||
ConcurrentStack`1: { All: True }
|
||||
System.Collections:
|
||||
BitArray: { All: True }
|
||||
ICollection: { All: True }
|
||||
IEnumerable: { All: True }
|
||||
IEnumerator: { All: True }
|
||||
IReadOnlyList`1: { All: True }
|
||||
IList: { All: True }
|
||||
System.ComponentModel:
|
||||
CancelEventArgs: { All: True }
|
||||
PropertyDescriptor: { }
|
||||
|
||||
@@ -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
|
||||
@@ -9,17 +10,22 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
public string RootDir { get; }
|
||||
|
||||
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs an instance of <see cref="WritableDirProvider"/>.
|
||||
/// </summary>
|
||||
/// <param name="rootDir">Root file system directory to allow writing.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -118,7 +124,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo);
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -135,11 +141,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 = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\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
|
||||
@@ -153,20 +185,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
return GetFullPath(RootDir, path);
|
||||
}
|
||||
|
||||
private static string GetFullPath(string root, ResPath path)
|
||||
{
|
||||
var relPath = path.ToRelativeSystemPath();
|
||||
if (relPath.Contains("\\..") || relPath.Contains("/.."))
|
||||
{
|
||||
// Hard cap on any exploit smuggling a .. in there.
|
||||
// Since that could allow leaving sandbox.
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return Path.GetFullPath(Path.Combine(root, relPath));
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Numerics;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
|
||||
@@ -41,6 +40,9 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("zoom")]
|
||||
public Vector2 Zoom = Vector2.One;
|
||||
|
||||
/// <summary>
|
||||
/// Eye offset, relative to the map, and not affected by <see cref="Rotation"/>
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite), DataField("offset"), AutoNetworkedField]
|
||||
public Vector2 Offset;
|
||||
|
||||
@@ -52,9 +54,13 @@ namespace Robust.Shared.GameObjects
|
||||
public int VisibilityMask = DefaultVisibilityMask;
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the PVS view range of this eye, Effectively a per-eye <see cref="CVars.NetMaxUpdateRange"/> cvar.
|
||||
/// Scaling factor for the PVS view range of this eye. This effectively allows the
|
||||
/// <see cref="CVars.NetMaxUpdateRange"/> and <see cref="CVars.NetPvsPriorityRange"/> cvars to be configured per
|
||||
/// eye.
|
||||
/// </summary>
|
||||
[DataField] public float? PvsSize;
|
||||
[Access(typeof(SharedEyeSystem))]
|
||||
[DataField]
|
||||
public float PvsScale = 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,7 +23,6 @@ namespace Robust.Shared.GameObjects
|
||||
public sealed partial class TransformComponent : Component, IComponentDebug
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entMan = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
// Currently this field just exists for VV. In future, it might become a real field
|
||||
[ViewVariables, PublicAPI]
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Prometheus;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Log;
|
||||
@@ -42,6 +43,7 @@ namespace Robust.Shared.GameObjects
|
||||
[IoC.Dependency] private readonly ProfManager _prof = default!;
|
||||
[IoC.Dependency] private readonly INetManager _netMan = default!;
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
[IoC.Dependency] private readonly EntityConsoleHost _entityConsoleHost = default!;
|
||||
|
||||
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
|
||||
// positions on spawn....
|
||||
@@ -216,6 +218,7 @@ namespace Robust.Shared.GameObjects
|
||||
TransformQuery = GetEntityQuery<TransformComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_actorQuery = GetEntityQuery<ActorComponent>();
|
||||
_entityConsoleHost.Startup();
|
||||
}
|
||||
|
||||
public virtual void Shutdown()
|
||||
@@ -227,6 +230,7 @@ namespace Robust.Shared.GameObjects
|
||||
ClearComponents();
|
||||
ShuttingDown = false;
|
||||
Started = false;
|
||||
_entityConsoleHost.Shutdown();
|
||||
}
|
||||
|
||||
public virtual void Cleanup()
|
||||
|
||||
@@ -74,6 +74,7 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[MustCallBase(true)]
|
||||
public virtual void Initialize() { }
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -81,12 +82,15 @@ namespace Robust.Shared.GameObjects
|
||||
/// Not ran on the client if prediction is disabled and
|
||||
/// <see cref="UpdatesOutsidePrediction"/> is false (the default).
|
||||
/// </remarks>
|
||||
[MustCallBase(true)]
|
||||
public virtual void Update(float frameTime) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MustCallBase(true)]
|
||||
public virtual void FrameUpdate(float frameTime) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
[MustCallBase(true)]
|
||||
public virtual void Shutdown()
|
||||
{
|
||||
ShutdownSubscriptions();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -97,6 +98,23 @@ public abstract class SharedEyeSystem : EntitySystem
|
||||
eyeComponent.Eye.Zoom = value;
|
||||
}
|
||||
|
||||
public void SetPvsScale(Entity<EyeComponent?> eye, float scale)
|
||||
{
|
||||
if (!Resolve(eye.Owner, ref eye.Comp, false))
|
||||
return;
|
||||
|
||||
// Prevent a admin or some other fuck-up from causing exception spam in PVS system due to divide-by-zero or
|
||||
// other such issues
|
||||
if (!float.IsFinite(scale))
|
||||
{
|
||||
Log.Error($"Attempted to set pvs scale to invalid value: {scale}. Eye: {ToPrettyString(eye)}");
|
||||
SetPvsScale(eye, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
eye.Comp.PvsScale = Math.Clamp(scale, 0.1f, 100f);
|
||||
}
|
||||
|
||||
public void SetVisibilityMask(EntityUid uid, int value, EyeComponent? eyeComponent = null)
|
||||
{
|
||||
if (!Resolve(uid, ref eyeComponent))
|
||||
|
||||
@@ -909,6 +909,26 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetLocalTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle localCircle, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
var aabb = new Box2(localCircle.Position.X - localCircle.Radius, localCircle.Position.Y - localCircle.Radius,
|
||||
localCircle.Position.X + localCircle.Radius, localCircle.Position.Y + localCircle.Radius);
|
||||
|
||||
var tileEnumerator = GetLocalTilesEnumerator(uid, grid, aabb, ignoreEmpty, predicate);
|
||||
|
||||
while (tileEnumerator.MoveNext(out var tile))
|
||||
{
|
||||
var tileCenter = tile.GridIndices + grid.TileSizeHalfVector;
|
||||
var direction = tileCenter - localCircle.Position;
|
||||
|
||||
if (direction.IsShorterThanOrEqualTo(localCircle.Radius))
|
||||
{
|
||||
yield return tile;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TileRef> GetTilesIntersecting(EntityUid uid, MapGridComponent grid, Circle worldArea, bool ignoreEmpty = true,
|
||||
Predicate<TileRef>? predicate = null)
|
||||
{
|
||||
|
||||
@@ -31,6 +31,9 @@ namespace Robust.Shared.Graphics
|
||||
set => _coords = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Eye offset, relative to the map, and not affected by <see cref="Rotation"/>
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 Offset { get; set; }
|
||||
|
||||
|
||||
@@ -248,6 +248,7 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="position">The vector to offset by local to the entity.</param>
|
||||
/// <returns>Newly offset coordinates.</returns>
|
||||
[Pure]
|
||||
public EntityCoordinates Offset(Vector2 position)
|
||||
{
|
||||
return new(EntityId, Position + position);
|
||||
|
||||
@@ -59,9 +59,16 @@ namespace Robust.Shared.Network.Messages
|
||||
buffer.ReadAlignedMemory(finalStream, uncompressedLength);
|
||||
}
|
||||
|
||||
serializer.DeserializeDirect(finalStream, out State);
|
||||
try
|
||||
{
|
||||
serializer.DeserializeDirect(finalStream, out State);
|
||||
}
|
||||
finally
|
||||
{
|
||||
finalStream.Dispose();
|
||||
}
|
||||
|
||||
State.PayloadSize = uncompressedLength;
|
||||
finalStream.Dispose();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
|
||||
[assembly: InternalsVisibleTo("OpenToolkit.GraphicsLibraryFramework")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] // Gives access to Castle(Moq)
|
||||
[assembly: InternalsVisibleTo("Content.Benchmarks")]
|
||||
[assembly: InternalsVisibleTo("Robust.Benchmarks")]
|
||||
[assembly: InternalsVisibleTo("Robust.Client.WebView")]
|
||||
[assembly: InternalsVisibleTo("Robust.Packaging")]
|
||||
|
||||
78
Robust.Shared/Serialization/NetBitArraySerializer.cs
Normal file
78
Robust.Shared/Serialization/NetBitArraySerializer.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using NetSerializer;
|
||||
|
||||
namespace Robust.Shared.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Custom serializer implementation for <see cref="BitArray"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This type is necessary as, since .NET 10, the internal layout of <see cref="BitArray"/> was changed.
|
||||
/// The type now (internally) implements <see cref="ISerializable"/> for backwards compatibility with existing
|
||||
/// <c>BinaryFormatter</c> code, but NetSerializer does not support <see cref="ISerializable"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This code is designed to be backportable & network compatible with the previous behavior on .NET 9.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal sealed class NetBitArraySerializer : IStaticTypeSerializer
|
||||
{
|
||||
// For reference, the layout of BitArray before .NET 10 was:
|
||||
// private int[] m_array;
|
||||
// private int m_length;
|
||||
// private int _version;
|
||||
// NetSerializer serialized these in the following order (sorted by name):
|
||||
// _version, m_array, m_length
|
||||
|
||||
public bool Handles(Type type)
|
||||
{
|
||||
return type == typeof(BitArray);
|
||||
}
|
||||
|
||||
public IEnumerable<Type> GetSubtypes(Type type)
|
||||
{
|
||||
return [typeof(int[]), typeof(int)];
|
||||
}
|
||||
|
||||
public MethodInfo GetStaticWriter(Type type)
|
||||
{
|
||||
return typeof(NetBitArraySerializer).GetMethod("Write", BindingFlags.Static | BindingFlags.NonPublic)!;
|
||||
}
|
||||
|
||||
public MethodInfo GetStaticReader(Type type)
|
||||
{
|
||||
return typeof(NetBitArraySerializer).GetMethod("Read", BindingFlags.Static | BindingFlags.NonPublic)!;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Write(Serializer serializer, Stream stream, BitArray value)
|
||||
{
|
||||
var intCount = (31 + value.Length) >> 5;
|
||||
var ints = new int[intCount];
|
||||
value.CopyTo(ints, 0);
|
||||
|
||||
serializer.SerializeDirect(stream, 0); // _version
|
||||
serializer.SerializeDirect(stream, ints); // m_array
|
||||
serializer.SerializeDirect(stream, value.Length); // m_length
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
private static void Read(Serializer serializer, Stream stream, out BitArray value)
|
||||
{
|
||||
serializer.DeserializeDirect<int>(stream, out _); // _version
|
||||
serializer.DeserializeDirect<int[]>(stream, out var array); // m_array
|
||||
serializer.DeserializeDirect<int>(stream, out var length); // m_length
|
||||
|
||||
value = new BitArray(array)
|
||||
{
|
||||
Length = length
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,7 @@ namespace Robust.Shared.Serialization
|
||||
MappedStringSerializer.TypeSerializer,
|
||||
new Vector2Serializer(),
|
||||
new Matrix3x2Serializer(),
|
||||
new NetBitArraySerializer()
|
||||
}
|
||||
};
|
||||
_serializer = new Serializer(types, settings);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Robust.Shared.Asynchronous;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Exceptions;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -50,6 +51,7 @@ namespace Robust.Shared
|
||||
deps.Register<ToolshedManager>();
|
||||
deps.Register<HttpClientHolder>();
|
||||
deps.Register<RobustMemoryManager>();
|
||||
deps.Register<EntityConsoleHost>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +232,7 @@ namespace Robust.UnitTesting.Server
|
||||
container.Register<IParallelManagerInternal, TestingParallelManager>();
|
||||
// Needed for grid fixture debugging.
|
||||
container.Register<IConGroupController, ConGroupController>();
|
||||
container.Register<EntityConsoleHost>();
|
||||
|
||||
// I just wanted to load pvs system
|
||||
container.Register<IServerEntityManager, ServerEntityManager>();
|
||||
|
||||
@@ -44,7 +44,7 @@ public sealed partial class VisibilityTest : RobustIntegrationTest
|
||||
metaComp[i] = server.EntMan.GetComponent<MetaDataComponent>(ent);
|
||||
visComp[i] = server.EntMan.AddComponent<VisibilityComponent>(ent);
|
||||
|
||||
vis.AddLayer(ent, visComp[i], 1 << i);
|
||||
vis.AddLayer((ent, visComp[i]), (ushort)(1 << i));
|
||||
if (i > 0)
|
||||
xforms.SetParent(ent, ents[i - 1]);
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public sealed partial class VisibilityTest : RobustIntegrationTest
|
||||
// Adding a layer to the root entity's mask will apply it to all children
|
||||
var extraMask = 1 << (N + 1);
|
||||
mask = RequiredMask | extraMask;
|
||||
vis.AddLayer(ents[0], visComp[0], extraMask);
|
||||
vis.AddLayer((ents[0], visComp[0]), (ushort)extraMask);
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
mask |= 1 << i;
|
||||
@@ -71,7 +71,7 @@ public sealed partial class VisibilityTest : RobustIntegrationTest
|
||||
}
|
||||
|
||||
// Removing the removes it from all children.
|
||||
vis.RemoveLayer(ents[0], visComp[0], extraMask);
|
||||
vis.RemoveLayer((ents[0], visComp[0]), (ushort)extraMask);
|
||||
mask = RequiredMask;
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
@@ -101,7 +101,7 @@ public sealed partial class VisibilityTest : RobustIntegrationTest
|
||||
}
|
||||
|
||||
// Re-attaching the entity also updates the masks.
|
||||
await server.WaitPost(() => xforms.SetParent(ents[split], ents[split-1]));
|
||||
await server.WaitPost(() => xforms.SetParent(ents[split], ents[split - 1]));
|
||||
mask = RequiredMask;
|
||||
for (int i = 0; i < N; i++)
|
||||
{
|
||||
@@ -111,7 +111,7 @@ public sealed partial class VisibilityTest : RobustIntegrationTest
|
||||
}
|
||||
|
||||
// Setting a mask on a child does not propagate upwards, only downwards
|
||||
vis.AddLayer(ents[split], visComp[split], extraMask);
|
||||
vis.AddLayer((ents[split], visComp[split]), (ushort)extraMask);
|
||||
mask = RequiredMask;
|
||||
for (int i = 0; i < split; i++)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Robust.UnitTesting.Shared.Resources
|
||||
_testDir = Directory.CreateDirectory(_testDirPath);
|
||||
var subDir = Path.Combine(_testDirPath, "writable");
|
||||
|
||||
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir));
|
||||
_dirProvider = new WritableDirProvider(Directory.CreateDirectory(subDir), hideRootDir: false);
|
||||
}
|
||||
|
||||
[OneTimeTearDown]
|
||||
|
||||
Reference in New Issue
Block a user