Compare commits

...

22 Commits

Author SHA1 Message Date
Pieter-Jan Briers
c7f7030b89 Version: 227.0.2 2024-08-11 19:54:40 +02:00
Pieter-Jan Briers
54218fb40b Use absolute path for explorer.exe
frick me

(cherry picked from commit 0284eb0430)
2024-08-11 19:54:40 +02:00
Pieter-Jan Briers
7b2c1701f3 Version: 227.0.1 2024-08-11 17:54:18 +02:00
Pieter-Jan Briers
0e3930d7d7 Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

(cherry picked from commit 7d778248ee)
(cherry picked from commit f66cda74e95619ddba2221bda644bf4394619805)
2024-08-11 17:54:18 +02:00
metalgearsloth
a9aea7027f Version: 227.0.0 2024-07-01 15:54:45 +10:00
metalgearsloth
2a49c2d9b8 Add loop support for SpriteSystem.GetFrame (#5265)
For Ftl I just want it played once.
2024-07-01 15:50:49 +10:00
metalgearsloth
a0c069f1ea Add LocalTilesIntersecting for circles (#5262)
* Add LocalTilesIntersecting for circles

* Update Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs

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

---------

Co-authored-by: Tayrtahn <tayrtahn@gmail.com>
2024-06-29 14:57:06 +10:00
Pieter-Jan Briers
2c6fb95e53 Add EntityManager dependency to base LocalizedEntityCommands 2024-06-28 17:00:14 +02:00
Nemanja
afe337644e Make spin box controls disable buttons that can't be pressed (#5221)
* spin box changes

* make SpinboxButton private

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-06-28 17:53:10 +10:00
Plykiya
b8924f3ddf Removes obsolete visibility system functions (#5209)
* Removes obsolete visibility system functions

* guh, forgot to add the test

---------

Co-authored-by: plykiya <plykiya@protonmail.com>
2024-06-28 17:30:02 +10:00
Pieter-Jan Briers
08970e745b Entity console commands system. (#5267)
* Entity console commands system.

This adds a new base type, LocalizedEntityCommands, which is able to import entity systems as dependencies. This is done by only registering these while the entity system is active.

Handling registration separately like this required a bit of changes around ConsoleHost to make it more suitable for this purpose:

You can now directly register command instances, and also have a system to suppress `UpdateAvailableCommands` on the client so there's no bad O(N*M) behavior.

* Convert TeleportCommands.cs to new entity commands.

Removes some obsoletion warnings without pain from having to manually import transform system.

* Fix RobustServerSimulation dependency issue.

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-06-28 17:29:24 +10:00
Leon Friedrich
0ba4a66787 Always process networked events via the priority queue (#5205) 2024-06-28 17:02:14 +10:00
Pieter-Jan Briers
75b3431ee6 New "must call base" analyzer. (#5266)
* New "must call base" analyzer.

This enforces that you actually call base when overriding stuff. This is intended for base methods like entity system's, where server/client systems overriding shared ones SHOULD call Initialize() and such.

* Add MustCallBase to entity system methods
2024-06-28 14:44:49 +10:00
geraeumig
c0ef976588 Make PvsSystem consider offset and zoom from EyeComponent (#5228)
* Make PvsSystem consider offset and zoom from EyeComponent

* Just use PvsScale float

* float.IsFinite

---------

Co-authored-by: geraeumig <alfenos@proton.me>
Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-06-28 14:24:22 +10:00
Pieter-Jan Briers
fe5cdf9e3c Fix loading of replays if string package is compressed in zip.
This happened when I had to re-compress a recovered replay from a server crash, and then loaded it up in a dev environment.
2024-06-27 16:07:49 +02:00
Morb
450349188b Dispose memory stream after deserialization exception (#4840)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-06-26 23:16:56 +02:00
metalgearsloth
897ad998d9 Add option for left or right-aligned checkboxes (#4739)
* Add option for left or right-aligned checkboxes

I think right-aligned is better but this is less of a breaking change.

* Cleanup
2024-06-26 23:11:17 +02:00
metalgearsloth
635ae3c353 Remove IGameTiming from TransformComponent (#5273) 2024-06-26 17:10:34 +02:00
Pieter-Jan Briers
a4ea5a4620 Add AnimationCompletedEvent "Finished" boolean.
Content has multiple cases where AnimationCompletedEvent is used to loop an animation. #5238 broke some of these by making this event raised even when manually removed.

Luckily most cases in content tie the animation looping to the presence of a component, so the component getting removed means there's nothing to refresh the loop. LightBehavior is not as fortunate however, causing bugs like https://github.com/space-wizards/space-station-14/issues/29144

This boolean allows looping code to properly distinguish the event, so it won't try to restart an animation after removing it directly.
2024-06-25 15:35:53 +02:00
Pieter-Jan Briers
90e87526d0 Quote tab completions containing spaces. 2024-06-24 16:05:00 +02:00
metalgearsloth
cd6576ddf9 Mark EntityCoordinates.Offset as pure (#5264)
Doesn't do anything just being called and sometimes I forget.
2024-06-24 10:53:41 +02:00
Leon Friedrich
e2cf4ee3db SIMD Colour multiplication (#5251)
* color simd

* removed wrong one

* A

* Use Unsafe.BitCast

* Color4 -> Color

* remove constructor

* remove `in`
2024-06-22 16:42:40 +02:00
42 changed files with 914 additions and 220 deletions

View File

@@ -55,7 +55,7 @@
<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.SharpFont" Version="1.0.2" />

View File

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

View File

@@ -54,6 +54,39 @@ END TEMPLATE-->
*None yet*
## 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

View 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));
}
}

View File

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

View 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;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}

View File

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

View File

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

View 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();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,12 +84,146 @@ Types:
- "bool get_HasContents()"
Lidgren.Network:
NetBuffer:
All: True
Methods:
- "byte[] get_Data()"
- "void set_Data(byte[])"
- "int get_LengthBytes()"
- "void set_LengthBytes(int)"
- "int get_LengthBits()"
- "void set_LengthBits(int)"
- "long get_Position()"
- "void set_Position(long)"
- "int get_PositionInBytes()"
- "byte[] PeekDataBuffer()"
- "bool PeekBoolean()"
- "byte PeekByte()"
- "sbyte PeekSByte()"
- "byte PeekByte(int)"
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
- "byte[] PeekBytes(int)"
- "void PeekBytes(byte[], int, int)"
- "short PeekInt16()"
- "ushort PeekUInt16()"
- "int PeekInt32()"
- "int PeekInt32(int)"
- "uint PeekUInt32()"
- "uint PeekUInt32(int)"
- "ulong PeekUInt64()"
- "long PeekInt64()"
- "ulong PeekUInt64(int)"
- "long PeekInt64(int)"
- "float PeekFloat()"
- "System.Half PeekHalf()"
- "float PeekSingle()"
- "double PeekDouble()"
- "string PeekString()"
- "int PeekStringSize()"
- "bool ReadBoolean()"
- "byte ReadByte()"
- "bool ReadByte(ref byte)"
- "sbyte ReadSByte()"
- "byte ReadByte(int)"
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
- "byte[] ReadBytes(int)"
- "bool ReadBytes(int, ref byte[])"
- "bool TryReadBytes(System.Span`1<byte>)"
- "void ReadBytes(byte[], int, int)"
- "void ReadBits(System.Span`1<byte>, int)"
- "void ReadBits(byte[], int, int)"
- "short ReadInt16()"
- "ushort ReadUInt16()"
- "int ReadInt32()"
- "bool ReadInt32(ref int)"
- "int ReadInt32(int)"
- "uint ReadUInt32()"
- "bool ReadUInt32(ref uint)"
- "uint ReadUInt32(int)"
- "ulong ReadUInt64()"
- "long ReadInt64()"
- "ulong ReadUInt64(int)"
- "long ReadInt64(int)"
- "float ReadFloat()"
- "System.Half ReadHalf()"
- "float ReadSingle()"
- "bool ReadSingle(ref float)"
- "double ReadDouble()"
- "uint ReadVariableUInt32()"
- "bool ReadVariableUInt32(ref uint)"
- "int ReadVariableInt32()"
- "long ReadVariableInt64()"
- "ulong ReadVariableUInt64()"
- "float ReadSignedSingle(int)"
- "float ReadUnitSingle(int)"
- "float ReadRangedSingle(float, float, int)"
- "int ReadRangedInteger(int, int)"
- "long ReadRangedInteger(long, long)"
- "string ReadString()"
- "bool ReadString(ref string)"
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
- "System.Net.IPEndPoint ReadIPEndPoint()"
- "void SkipPadBits()"
- "void ReadPadBits()"
- "void SkipPadBits(int)"
- "void EnsureBufferSize(int)"
- "void Write(bool)"
- "void Write(byte)"
- "void WriteAt(int, byte)"
- "void Write(sbyte)"
- "void Write(byte, int)"
- "void Write(byte[])"
- "void Write(System.ReadOnlySpan`1<byte>)"
- "void Write(byte[], int, int)"
- "void Write(ushort)"
- "void WriteAt(int, ushort)"
- "void Write(ushort, int)"
- "void Write(short)"
- "void WriteAt(int, short)"
- "void Write(int)"
- "void WriteAt(int, int)"
- "void Write(uint)"
- "void WriteAt(int, uint)"
- "void Write(uint, int)"
- "void Write(int, int)"
- "void Write(ulong)"
- "void WriteAt(int, ulong)"
- "void Write(ulong, int)"
- "void Write(long)"
- "void Write(long, int)"
- "void Write(System.Half)"
- "void Write(float)"
- "void Write(double)"
- "int WriteVariableUInt32(uint)"
- "int WriteVariableInt32(int)"
- "int WriteVariableInt64(long)"
- "int WriteVariableUInt64(ulong)"
- "void WriteSignedSingle(float, int)"
- "void WriteUnitSingle(float, int)"
- "void WriteRangedSingle(float, float, float, int)"
- "int WriteRangedInteger(int, int, int)"
- "int WriteRangedInteger(long, long, long)"
- "void Write(string)"
- "void Write(System.Net.IPEndPoint)"
- "void WriteTime(bool)"
- "void WriteTime(double, bool)"
- "void WritePadBits()"
- "void WritePadBits(int)"
- "void Write(Lidgren.Network.NetBuffer)"
- "void Zero(int)"
- "void .ctor()"
NetDeliveryMethod: { }
NetIncomingMessage:
All: True
Methods:
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
- "int get_SequenceChannel()"
- "System.Net.IPEndPoint get_SenderEndPoint()"
- "Lidgren.Network.NetConnection get_SenderConnection()"
- "double get_ReceiveTime()"
- "double ReadTime(bool)"
- "string ToString()"
NetOutgoingMessage:
All: True
Methods:
- "string ToString()"
Nett:
CommentLocation: { } # Enum
Toml:

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
path = path.Directory;
var fullPath = GetFullPath(path);
Process.Start(new ProcessStartInfo
if (OperatingSystem.IsWindows())
{
UseShellExecute = true,
FileName = fullPath,
});
Process.Start(new ProcessStartInfo
{
FileName = $"{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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>();
}
}
}

View File

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

View File

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