Compare commits

...

31 Commits

Author SHA1 Message Date
PJB3005
0011f48023 Version: 250.0.2 2025-09-19 09:17:34 +02:00
Skye
0e609dc22d Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:34 +02:00
PJB3005
4c37977eaa Version: 250.0.1 2025-09-14 14:55:58 +02:00
PJB3005
7023306c09 Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

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

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

    Move CEF cache out of data directory

    Don't want content messing with this...

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

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

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

    Update SpaceWizards.NFluidSynth to 0.2.2

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

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
2025-09-14 14:55:58 +02:00
metalgearsloth
5a5f238d9a Version: 250.0.0 2025-03-27 15:12:01 +11:00
Tayrtahn
089224cd44 Cleanup warnings in EntityLookup_Test (#5775)
* Replace MapManager.DeleteMap calls with MapSystem.DeleteMap

* Remove unused resolves
2025-03-27 15:07:05 +11:00
Tayrtahn
9f807f1ad2 Cleanup warnings in ClientGameStateManager (#5774)
* Fix unreachable code

* Remove unused variable
2025-03-27 15:06:37 +11:00
metalgearsloth
4be95ea375 Add OtherBody API to contacts (#5779)
* Add OtherBody API to contacts

Thought I had a pr for this.

* Update Robust.Shared/Physics/Dynamics/Contacts/Contact.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-03-27 15:06:04 +11:00
Jerry
03010bf4be Fix DirectoryNotFoundException when saving map or grid on Unix systems (#5773)
* fix(maploader): Fix DirectoryNotFoundException

Someone forgor to create directory before saving map or grid, this caused
an exception on trying to save smth anywhere else than /

* update release notes
2025-03-27 13:56:26 +11:00
Tayrtahn
dacaa974d4 Replace MapManager.DeleteMap with SharedMapSystem.DeleteMap in misc tests (#5777)
* Replace MapManager.DeleteMap with SharedMapSystem.DeleteMap in various tests

* Poke tests

* I guess this is was technically a breaking change?
2025-03-27 13:50:56 +11:00
DrSmugleaf
9f73e0398a Make MappingDataNode.Equals take 2952 times less time to run when loading maps (#5781) 2025-03-27 12:52:29 +11:00
Tayrtahn
ccc383b1bf Cleanup: Remove redundant Prototype names (#5721)
* Cleanup: Remove redundant Prototype names

* Actually this one probably should stay

* Suppress warning

* Remove warning suppression on AudioMetadataPrototype

* But wait, there's more!
2025-03-26 01:39:10 +01:00
PJB3005
ceb59402a1 Make status and info APIs have CORS allow-origin: *
Allows it to be queried from browser JS. No harm in not allowing this.

Added helper function StatusExt.AddAllowOriginAny to make this easy to add.
2025-03-26 01:16:23 +01:00
Errant
5a6b29fcd2 equatable FormattedMessage (#5772) 2025-03-25 15:32:47 +01:00
PJB3005
6b87cd1e1c Deprecate HCY color space functions
These functions use the wrong color primaries (Rec 601 instead of 709) to calculate the luminance, so I'm just gonna throw them out.

Also, I don't actually trust anybody to know to do sRGB conversion before using them.

Discovered this as a result of reviewing #5360
2025-03-24 04:47:34 +01:00
Leon Friedrich
cf2d6a1dbf Make unshaded sprite layers not require shader/texture changes. (#5248)
* Make unshaded sprite layers not require shader/texture changes.

* Always sample texture

* revert some variable name changes

* Use color SIMD
2025-03-24 02:37:04 +01:00
chromiumboy
f8c838f425 Pass AnimationPlayerComponent in AnimationCompletionEvent (#5755)
* Added a field for the animation player component to the animation completion event

* Addressed review comment

* Updated
2025-03-24 02:22:46 +01:00
Tayrtahn
7405904041 Add entity description as tooltip on entity spawn panel (#5761) 2025-03-23 14:07:22 +01:00
metalgearsloth
2eeebab275 Add pure to some angle methods (#5763)
Saw some of these floating around in mover code.
2025-03-22 13:43:18 +01:00
PJB3005
2856bb3626 Shut up RS1038 warnings
We aren't going to fix these until #5610 is figured out, and these aren't even an indicator of an issue itself.

They indicate that we have code fixes in the same assembly as analyzers, meaning the analyzers COULD fail if we relied on some code fix libs - something we don't do.
2025-03-22 06:20:43 +01:00
PJB3005
be0189748b Fix serialization source gen with partial types
This fixes RMC compilation
2025-03-22 06:09:48 +01:00
Tayrtahn
4529a7569a Replace uses of ProtoId<EntityPrototype> with ProtoId<EntityCategoryPrototype> (#5762) 2025-03-21 04:08:12 +01:00
metalgearsloth
2b16e4db96 Version: 249.0.0 2025-03-21 00:52:45 +11:00
metalgearsloth
64f2245194 Add UpdateVisibilityMask method (#5745)
* Add UpdateVisibilityMask method

We tipped over to the point of systems stepping on each other's toes. Now we do the normal thing and just use the eventbus and it makes content a whole lot cleaner.

* Update resolve

* Update name in line with normal.

* Unserialize this

* weh
2025-03-21 00:47:40 +11:00
Milon
1029047e2f fix (#5752) 2025-03-20 22:30:59 +11:00
metalgearsloth
45dc9ad80e Inline polygon vertices (#5758)
* FastPoly

* Inline polygon vertices

No more pooling, pooling bad. No more arrays in engine, only span.

I made a slim version that's only the 4 verts so no padding on it compared to Polygon. Slightly more clamplicated code but entitylookup + mapmanager are both hotpaths and it's easy to do. Memory usage will likely go up for now but heap allocations should drop significantly due to removing the pooling.

* Unhide these

* Fixes

* More fixes

* More fixes

* Avoid potential bomb
2025-03-20 21:26:05 +11:00
metalgearsloth
54ad808eea Add GetWorldManifold overload (#5756)
* Add GetWorldManifold overload

* revert
2025-03-20 21:10:04 +11:00
metalgearsloth
37c75df6a2 Fix showvelocities (#5759)
* Fix showvelocities

Can't use StateRoot anymore so just pretend it's a window.

* Also file-scoped
2025-03-20 21:05:58 +11:00
slarticodefast
e93c1fae61 Add velocity and angular velocity debug overlays (#5693)
* add velocity and angular velocity debug overlays

* minor improvement

* add descriptions

* fix

* review

* Update RELEASE-NOTES.md

* Update RELEASE-NOTES.md

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-03-20 13:10:16 +11:00
metalgearsloth
cd0a35f542 Fix light Aabb query (#5744)
Forgot when this got dropped but we need it for grid-tree query as lights spill over grids.
2025-03-13 19:18:23 +11:00
PJB3005
80f2dc6dd3 Fix BoxContainer stretching causing controls to be made too small
In which I fix a bug by just deleting a ton of code and doing nothing else.

(also I added unit tests)
2025-03-13 01:01:18 +01:00
86 changed files with 1233 additions and 547 deletions

View File

@@ -57,7 +57,7 @@
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<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="TerraFX.Interop.Windows" Version="10.0.26100.1" />

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,67 @@ END TEMPLATE-->
*None yet*
## 250.0.2
## 250.0.1
## 250.0.0
### Breaking changes
* The default shader now interprets negative color modulation as a flag that indicates that the light map should be ignored.
* This can be used to avoid having to change the light map texture, thus reducing draw batches.
* Sprite layers that are set to use the "unshaded" shader prototype now use this.
* Any fragment shaders that previously the `VtxModulate` colour modulation variable should instead use the new `MODULATE` variable, as the former may now contain negative values.
### New features
* Add OtherBody API to contacts.
* Make FormattedMessages Equatable.
* AnimationCompletionEvent now has the AnimationPlayerComponent.
* Add entity description as a tooltip on the entity spawn panel.
### Bugfixes
* Fix serialization source generator breaking if a class has two partial locations.
* Fix map saving throwing a `DirectoryNotFoundException` when given a path with a non-existent directory. Now it once again creates any missing directories.
* Fix map loading taking a significant time due to MappingDataNode.Equals calls being slow.
### Other
* Add Pure to some Angle methods.
### Internal
* Cleanup some warnings in classes.
## 249.0.0
### Breaking changes
* Layer is now read-only on VisibilityComponent and isn't serialized.
### New features
* Added a debug overlay for the linear and angular velocity of all entities on the screen. Use the `showvel` and `showangvel` commands to toggle it.
* Add a GetWorldManifold overload that doesn't require a span of points.
* Added a GetVisMaskEvent. Calling `RefreshVisibilityMask` will raise it and subscribers can update the vismask via the event rather than subscribers having to each manually try and handle the vismask directly.
### Bugfixes
* `BoxContainer` no longer causes stretching children to go below their minimum size.
* Fix lights on other grids getting clipped due to ignoring the light range cvar.
* Fix the `showvelocities` command.
* Fix the DirtyFields overload not being sandbox safe for content.
### Internal
* Polygon vertices are now inlined with FixedArray8 and a separate SlimPolygon using FixedArray4 for hot paths rather than using pooled arrays.
## 248.0.2
### Bugfixes

View File

@@ -428,11 +428,20 @@ cmd-entfo-help = Usage: entfo <entityuid>
The entity UID can be prefixed with 'c' to convert it to a client entity UID.
cmd-fuck-desc = Throws an exception
cmd-fuck-help = Throws an exception
cmd-fuck-help = Usage: fuck
cmd-showpos-desc = Enables debug drawing over all entity positions in the game.
cmd-showpos-desc = Show the position of all entities on the screen.
cmd-showpos-help = Usage: showpos
cmd-showrot-desc = Show the rotation of all entities on the screen.
cmd-showrot-help = Usage: showrot
cmd-showvel-desc = Show the local velocity of all entites on the screen.
cmd-showvel-help = Usage: showvel
cmd-showangvel-desc = Show the angular velocity of all entities on the screen.
cmd-showangvel-help = Usage: showangvel
cmd-sggcell-desc = Lists entities on a snap grid cell.
cmd-sggcell-help = Usage: sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.

View File

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

View File

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

View File

@@ -173,29 +173,51 @@ namespace Robust.Client.Console.Commands
}
}
internal sealed class ShowPositionsCommand : LocalizedCommands
internal sealed class ShowPositionsCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
public override string Command => "showpos";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
mgr.DebugPositions = !mgr.DebugPositions;
_debugDrawing.DebugPositions = !_debugDrawing.DebugPositions;
}
}
internal sealed class ShowRotationsCommand : LocalizedCommands
internal sealed class ShowRotationsCommand : LocalizedEntityCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
public override string Command => "showrot";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
mgr.DebugRotations = !mgr.DebugRotations;
_debugDrawing.DebugRotations = !_debugDrawing.DebugRotations;
}
}
internal sealed class ShowVelocitiesCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
public override string Command => "showvel";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_debugDrawing.DebugVelocities = !_debugDrawing.DebugVelocities;
}
}
internal sealed class ShowAngularVelocitiesCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
public override string Command => "showangvel";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_debugDrawing.DebugAngularVelocities = !_debugDrawing.DebugAngularVelocities;
}
}

View File

@@ -5,15 +5,15 @@ using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
public sealed class VelocitiesCommand : LocalizedCommands
public sealed class ShowPlayerVelocityCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
public override string Command => "showvelocities";
public override string Command => "showplayervelocity";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_entitySystems.GetEntitySystem<VelocityDebugSystem>().Enabled ^= true;
_entitySystems.GetEntitySystem<ShowPlayerVelocityDebugSystem>().Enabled ^= true;
}
}
}

View File

@@ -1,140 +1,221 @@
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using System.Numerics;
namespace Robust.Client.Debugging
namespace Robust.Client.Debugging;
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public sealed class DebugDrawingSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private bool _debugPositions;
private bool _debugRotations;
private bool _debugVelocities;
private bool _debugAngularVelocities;
/// <summary>
/// A collection of visual debug overlays for the client game.
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
public sealed class DebugDrawingSystem : EntitySystem
public bool DebugPositions
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly TransformSystem _transform = default!;
private bool _debugPositions;
private bool _debugRotations;
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
public bool DebugPositions
get => _debugPositions;
set
{
get => _debugPositions;
set
if (value == DebugPositions)
{
if (value == DebugPositions)
{
return;
}
return;
}
_debugPositions = value;
_debugPositions = value;
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
/// <summary>
/// Toggles the visual overlay of the local rotation.
/// </summary>
public bool DebugRotations
/// <summary>
/// Toggles the visual overlay of the rotation for each entity on screen.
/// </summary>
public bool DebugRotations
{
get => _debugRotations;
set
{
get => _debugRotations;
set
if (value == DebugRotations)
{
if (value == DebugRotations)
{
return;
}
return;
}
_debugRotations = value;
_debugRotations = value;
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
{
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, EntityManager));
}
else
{
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
}
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
{
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
}
}
}
private sealed class EntityPositionOverlay : Overlay
/// <summary>
/// Toggles the visual overlay of the local velocity for each entity on screen.
/// </summary>
public bool DebugVelocities
{
get => _debugVelocities;
set
{
private readonly EntityLookupSystem _lookup;
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _transform;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager, SharedTransformSystem transform)
if (value == DebugVelocities)
{
_lookup = lookup;
_entityManager = entityManager;
_transform = transform;
return;
}
protected internal override void Draw(in OverlayDrawArgs args)
_debugVelocities = value;
if (value && !_overlayManager.HasOverlay<EntityVelocityOverlay>())
{
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = _transform.GetWorldPositionRotation(entity);
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
}
_overlayManager.AddOverlay(new EntityVelocityOverlay(EntityManager, _lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityVelocityOverlay>();
}
}
}
private sealed class EntityRotationOverlay : Overlay
/// <summary>
/// Toggles the visual overlay of the angular velocity for each entity on screen.
/// </summary>
public bool DebugAngularVelocities
{
get => _debugAngularVelocities;
set
{
private readonly EntityLookupSystem _lookup;
private readonly IEntityManager _entityManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityRotationOverlay(EntityLookupSystem lookup, IEntityManager entityManager)
if (value == DebugAngularVelocities)
{
_lookup = lookup;
_entityManager = entityManager;
return;
}
protected internal override void Draw(in OverlayDrawArgs args)
_debugAngularVelocities = value;
if (value && !_overlayManager.HasOverlay<EntityAngularVelocityOverlay>())
{
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
_overlayManager.AddOverlay(new EntityAngularVelocityOverlay(EntityManager, _lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityAngularVelocityOverlay>();
}
}
}
private sealed class EntityPositionOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
protected internal override void Draw(in OverlayDrawArgs args)
{
const float stubLength = 0.25f;
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
}
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
}
}
}
private sealed class EntityRotationOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
protected internal override void Draw(in OverlayDrawArgs args)
{
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
}
}
}
private sealed class EntityVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
protected internal override void Draw(in OverlayDrawArgs args)
{
const float multiplier = 0.2f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
continue;
var center = _transform.GetWorldPosition(uid);
var localVelocity = physicsComp.LinearVelocity;
if (localVelocity != Vector2.Zero)
worldHandle.DrawLine(center, center + localVelocity * multiplier, Color.Yellow);
}
}
}
private sealed class EntityAngularVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
protected internal override void Draw(in OverlayDrawArgs args)
{
const float multiplier = (float)(0.2 / (2 * System.Math.PI));
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
continue;
var center = _transform.GetWorldPosition(uid);
var angularVelocity = physicsComp.AngularVelocity;
if (angularVelocity != 0.0f)
worldHandle.DrawCircle(center, angularVelocity * multiplier, angularVelocity > 0 ? Color.Magenta : Color.Blue, false);
}
}
}
}

View File

@@ -384,7 +384,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)

View File

@@ -117,7 +117,7 @@ namespace Robust.Client.GameObjects
base.DirtyField(uid, comp, fieldName, metadata);
}
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
{
// TODO Prediction
// does the client actually need to dirty the field?

View File

@@ -30,6 +30,7 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
using Direction = Robust.Shared.Maths.Direction;
using Vector4 = Robust.Shared.Maths.Vector4;
using SysVec4 = System.Numerics.Vector4;
namespace Robust.Client.GameObjects
{
@@ -753,12 +754,20 @@ namespace Robust.Client.GameObjects
if (layerDatum.Shader == string.Empty)
{
layer.ShaderPrototype = null;
layer.UnShaded = false;
layer.Shader = null;
}
else if (layerDatum.Shader == SpriteSystem.UnshadedId.Id)
{
layer.ShaderPrototype = SpriteSystem.UnshadedId;
layer.UnShaded = true;
layer.Shader = null;
}
else if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.ShaderPrototype = layerDatum.Shader;
layer.Shader = prototype.Instance();
layer.UnShaded = false;
}
else
{
@@ -835,11 +844,28 @@ namespace Robust.Client.GameObjects
if (!TryGetLayer(layer, out var theLayer, true))
return;
if (shader == null)
{
theLayer.UnShaded = false;
theLayer.Shader = null;
theLayer.ShaderPrototype = null;
return;
}
if (prototype == SpriteSystem.UnshadedId.Id)
{
theLayer.UnShaded = true;
theLayer.ShaderPrototype = SpriteSystem.UnshadedId;
theLayer.Shader = null;
return;
}
theLayer.UnShaded = false;
theLayer.Shader = shader;
theLayer.ShaderPrototype = prototype;
}
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
public void LayerSetShader(object layerKey, ShaderInstance? shader, string? prototype = null)
{
if (!LayerMapTryGet(layerKey, out var layer, true))
return;
@@ -1493,10 +1519,18 @@ namespace Robust.Client.GameObjects
{
[ViewVariables] private readonly SpriteComponent _parent;
[ViewVariables] public string? ShaderPrototype;
[ViewVariables] public ProtoId<ShaderPrototype>? ShaderPrototype;
[ViewVariables] public ShaderInstance? Shader;
[ViewVariables] public Texture? Texture;
/// <summary>
/// If true, then this layer is drawn without lighting applied.
/// Unshaded layers are given special treatment and don't just use the unshaded-shader to avoid having to
/// unnecessarily swap out the light texture. This helps the number of batches that need to be sent to the
/// GPU while drawing sprites.
/// </summary>
[ViewVariables] internal bool UnShaded;
private RSI? _rsi;
[ViewVariables] public RSI? RSI
{
@@ -1663,6 +1697,7 @@ namespace Robust.Client.GameObjects
if (toClone.Shader != null)
{
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
UnShaded = toClone.UnShaded;
ShaderPrototype = toClone.ShaderPrototype;
}
Texture = toClone.Texture;
@@ -2078,6 +2113,20 @@ namespace Robust.Client.GameObjects
drawingHandle.UseShader(Shader);
var layerColor = _parent.color * Color;
DebugTools.Assert(layerColor is {R: >= 0, G: >= 0, B: >= 0, A: >= 0}, "Negative colour modulation");
if (UnShaded)
{
DebugTools.AssertNull(Shader);
// Negative modulation values are used to disable light shading in the default shader.
// Specifically we set colour = -1 - colour
// This ensures that non-negative values become negative & is trivially invertible.
// Alternatively we could just clamp the colour to [0,1] and subtract a constant.
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
}
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(textureSize/-2, textureSize);

View File

@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
foreach (var key in remie)
{
component.PlayingAnimations.Remove(key);
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
var completedEvent = new AnimationCompletedEvent(uid, component, key, true);
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
}
@@ -187,7 +187,7 @@ namespace Robust.Client.GameObjects
return;
}
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
var completedEvent = new AnimationCompletedEvent(entity.Owner, entity.Comp, key, false);
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
}
@@ -202,13 +202,33 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class AnimationCompletedEvent : EntityEventArgs
{
/// <summary>
/// The entity associated with the event.
/// </summary>
public EntityUid Uid { get; init; }
/// <summary>
/// The animation player component associated with the entity this event was raised on.
/// </summary>
public AnimationPlayerComponent AnimationPlayer { get; init; }
/// <summary>
/// The key associated with the animation that was completed.
/// </summary>
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.
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(EntityUid,AnimationPlayerComponent,string)"/> or similar overloads.
/// </summary>
public bool Finished { get; init; }
public AnimationCompletedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key, bool finished = true)
{
Uid = uid;
AnimationPlayer = animationPlayer;
Key = key;
Finished = finished;
}
}
}

View File

@@ -0,0 +1,66 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics.Components;
namespace Robust.Client.GameObjects;
public sealed class ShowPlayerVelocityDebugSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
internal bool Enabled
{
get => _label.Parent != null;
set
{
if (value)
{
_uiManager.WindowRoot.AddChild(_label);
}
else
{
_label.Orphan();
}
}
}
private Label _label = default!;
public override void Initialize()
{
base.Initialize();
_label = new Label();
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled)
{
_label.Visible = false;
return;
}
var player = _playerManager.LocalEntity;
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
{
_label.Visible = false;
return;
}
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
_label.Visible = true;
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
}
}

View File

@@ -37,6 +37,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedTransformSystem _xforms = default!;
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
/// <summary>

View File

@@ -1,54 +0,0 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
namespace Robust.Client.GameObjects
{
public sealed class VelocityDebugSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly TransformSystem _transform = default!;
internal bool Enabled { get; set; }
private Label _label = default!;
public override void Initialize()
{
base.Initialize();
_label = new Label();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled)
{
_label.Visible = false;
return;
}
var player = _playerManager.LocalEntity;
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
{
_label.Visible = false;
return;
}
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
_label.Visible = true;
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
}
}
}

View File

@@ -384,7 +384,6 @@ namespace Robust.Client.GameStates
_processor.UpdateFullRep(curState);
}
IEnumerable<NetEntity> createdEntities;
using (_prof.Group("ApplyGameState"))
{
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
@@ -699,8 +698,9 @@ namespace Robust.Client.GameStates
#if !EXCEPTION_TOLERANCE
throw new KeyNotFoundException();
#endif
#else
continue;
#endif
}
var compData = _compDataPool.Get();
@@ -961,8 +961,9 @@ namespace Robust.Client.GameStates
RequestFullState();
#if !EXCEPTION_TOLERANCE
throw;
#endif
#else
return;
#endif
}
if (data.Created)
@@ -980,8 +981,9 @@ namespace Robust.Client.GameStates
RequestFullState();
#if !EXCEPTION_TOLERANCE
throw;
#endif
#else
return;
#endif
}
}

View File

@@ -21,7 +21,7 @@ namespace Robust.Client.Graphics.Clyde
private const string UniModelMatrix = "modelMatrix";
private const string UniTexturePixelSize = "TEXTURE_PIXEL_SIZE";
private const string UniMainTexture = "TEXTURE";
private const string UniLightTexture = "lightMap";
private const string UniLightTexture = "lightMap"; // TODO CLYDE consistent shader variable naming
private const string UniProjViewMatrices = "projectionViewMatrices";
private const string UniUniformConstants = "uniformConstants";

View File

@@ -98,9 +98,11 @@ namespace Robust.Client.Graphics.Clyde
private LightCapacityComparer _lightCap = new();
private ShadowCapacityComparer _shadowCap = new ShadowCapacityComparer();
private float _maxLightRadius;
private unsafe void InitLighting()
{
_cfg.OnValueChanged(CVars.MaxLightRadius, val => { _maxLightRadius = val;}, true);
// Other...
LoadLightingShaders();
@@ -617,8 +619,9 @@ namespace Robust.Client.Graphics.Clyde
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
var state = (this, count: 0, shadowCastingCount: 0, xforms, worldAABB);
var lightAabb = worldAABB.Enlarged(_maxLightRadius);
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, worldAABB))
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, lightAabb))
{
var bounds = _transformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
comp.Tree.QueryAabb(ref state, LightQuery, bounds);

View File

@@ -1,8 +1,22 @@
// UV coordinates in texture-space. I.e., (0,0) is the corner of the texture currently being used to draw.
// When drawing a sprite from a texture atlas, (0,0) is the corner of the atlas, not the specific sprite being drawn.
varying highp vec2 UV;
// UV coordinates in quad-space. I.e., when drawing a sprite from a texture atlas (0,0) is the corner of the sprite
// currently being drawn.
varying highp vec2 UV2;
// TBH I'm not sure what this is for. I think it is scree UV coordiantes, i.e., FRAGCOORD.xy * SCREEN_PIXEL_SIZE ?
// TODO CLYDE Is this still needed?
varying highp vec2 Pos;
// Vertex colour modulation. Note that negative values imply that the LIGHTMAP should be ignored. This is used to avoid
// having to set the texture to a white/blank texture for sprites that have no light shading applied.
varying highp vec4 VtxModulate;
// The current light map. Unless disabled, this is automatically sampled to create the LIGHT vector, which is then used
// to modulate the output colour.
// TODO CLYDE consistent shader variable naming
uniform sampler2D lightMap;
// [SHADER_HEADER_CODE]
@@ -11,11 +25,37 @@ void main()
{
highp vec4 FRAGCOORD = gl_FragCoord;
// The output colour. This should get set by the shader code block.
// This will get modified by the LIGHT and MODULATE vectors.
lowp vec4 COLOR;
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
// The light colour, usually sampled from the LIGHTMAP
lowp vec4 LIGHT;
// Colour modulation vector.
highp vec4 MODULATE;
// Sample the texture outside of the branch / with uniform control flow.
LIGHT = texture2D(lightMap, Pos);
if (VtxModulate.x < 0.0)
{
// Negative VtxModulate implies unshaded/no lighting.
MODULATE = -1.0 - VtxModulate;
LIGHT = vec4(1.0);
}
else
{
MODULATE = VtxModulate;
}
// TODO CLYDE consistent shader variable naming
// Requires breaking changes.
lowp vec3 lightSample = LIGHT.xyz;
// [SHADER_CODE]
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
LIGHT.xyz = lightSample;
gl_FragColor = zAdjustResult(COLOR * MODULATE * LIGHT);
}

View File

@@ -18,6 +18,7 @@ uniform mat3 modelMatrix;
// Allows us to do texture atlassing with texture coordinates 0->1
// Input texture coordinates get mapped to this range.
uniform vec4 modifyUV;
// TODO CLYDE Is this still needed?
// [SHADER_HEADER_CODE]
@@ -39,5 +40,15 @@ void main()
Pos = (VERTEX + 1.0) / 2.0;
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
UV2 = tCoord2;
VtxModulate = zFromSrgb(modulate);
// Negative modulation is being used as a hacky way to squeeze in lighting data.
// I.e., negative modulation implies we ignore the lighting.
if (modulate.x < 0.0)
{
VtxModulate = -1.0 - zFromSrgb(-1.0 - modulate);
}
else
{
VtxModulate = zFromSrgb(modulate);
}
}

View File

@@ -1,6 +1,7 @@
varying highp vec2 UV;
varying highp vec2 UV2;
// TODO CLYDE consistent shader variable naming
uniform sampler2D lightMap;
// [SHADER_HEADER_CODE]

View File

@@ -16,7 +16,7 @@ using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
[Prototype]
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
{
[ViewVariables]

View File

@@ -71,21 +71,11 @@ namespace Robust.Client.UserInterface.Controls
}
// First, we measure non-stretching children.
var stretching = new List<Control>();
float totalStretchRatio = 0;
foreach (var child in Children)
{
if (!child.Visible)
continue;
var stretch = Vertical ? child.VerticalExpand : child.HorizontalExpand;
if (stretch)
{
totalStretchRatio += child.SizeFlagsStretchRatio;
stretching.Add(child);
continue;
}
child.Measure(availableSize);
if (Vertical)
@@ -102,35 +92,6 @@ namespace Robust.Client.UserInterface.Controls
}
}
if (stretching.Count == 0)
return desiredSize;
// Then we measure stretching children
foreach (var child in stretching)
{
var size = availableSize;
if (Vertical)
{
size.Y *= child.SizeFlagsStretchRatio / totalStretchRatio;
child.Measure(size);
desiredSize.Y += child.DesiredSize.Y;
desiredSize.X = Math.Max(desiredSize.X, child.DesiredSize.X);
}
else
{
size.X *= child.SizeFlagsStretchRatio / totalStretchRatio;
child.Measure(size);
desiredSize.X += child.DesiredSize.X;
desiredSize.Y = Math.Max(desiredSize.Y, child.DesiredSize.Y);
}
// TODO Maybe make BoxContainer.MeasureOverride more rigorous.
// This should check if size < desired size. If it is, treat child as non-stretching (see the code in
// ArrangeOverride). This requires remeasuring all stretching controls + the control that just became
// non-stretching. But the re-measured controls might then become smaller (e.g. rich text wrapping),
// leading to a recursion problem.
}
return desiredSize;
}

View File

@@ -59,6 +59,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
button.EntityLabel.Text = entityLabelText;
button.ActualButton.ToolTip = prototype.Description;
if (prototype == SelectedPrototype)
{

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.RichText;
[Prototype("font")]
[Prototype]
public sealed partial class FontPrototype : IPrototype
{
[IdDataField]

View File

@@ -14,7 +14,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ImplicitUsings>enable</ImplicitUsings>
<PolySharpIncludeGeneratedTypes>System.Index;System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;System.Runtime.CompilerServices.IsExternalInit;System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute</PolySharpIncludeGeneratedTypes>
<NoWarn>RS2008</NoWarn>
<NoWarn>RS2008;RS1038</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@@ -35,13 +35,20 @@ public class Generator : IIncrementalGenerator
.Where(static type => type != null);
initContext.RegisterSourceOutput(
dataDefinitions,
static (sourceContext, source) =>
dataDefinitions.Collect(),
static (sourceContext, sources) =>
{
// TODO: deduplicate based on name?
var (name, code) = source!.Value;
var done = new HashSet<string>();
sourceContext.AddSource(name, code);
foreach (var source in sources)
{
var (name, code) = source!.Value;
if (!done.Add(name))
continue;
sourceContext.AddSource(name, code);
}
}
);
}

View File

@@ -297,7 +297,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;

View File

@@ -0,0 +1,15 @@
namespace Robust.Server.ServerStatus;
/// <summary>
/// Helper functions for working with <see cref="IStatusHandlerContext"/>.
/// </summary>
public static class StatusExt
{
/// <summary>
/// Add <c>Access-Control-Allow-Origin: *</c> to the response headers for this request.
/// </summary>
public static void AddAllowOriginAny(this IStatusHandlerContext context)
{
context.ResponseHeaders.Add("Access-Control-Allow-Origin", "*");
}
}

View File

@@ -61,6 +61,7 @@ namespace Robust.Server.ServerStatus
OnStatusRequest?.Invoke(jObject);
context.AddAllowOriginAny();
await context.RespondJsonAsync(jObject);
return true;
@@ -121,6 +122,7 @@ namespace Robust.Server.ServerStatus
OnInfoRequest?.Invoke(jObject);
context.AddAllowOriginAny();
await context.RespondJsonAsync(jObject);
return true;

View File

@@ -93,6 +93,7 @@ namespace Robust.Shared.Maths
private const double CardinalSegment = 2 * Math.PI / 4.0; // Cut the circle into 4 pieces
private const double CardinalOffset = CardinalSegment / 2.0; // offset the pieces by 1/2 their size
[Pure]
public readonly Direction GetCardinalDir()
{
var ang = Theta % (2 * Math.PI);
@@ -167,6 +168,7 @@ namespace Robust.Shared.Maths
/// <summary>
/// Removes revolutions from a positive or negative angle to make it as small as possible.
/// </summary>
[Pure]
public readonly Angle Reduced()
{
return new(Reduce(Theta));
@@ -213,11 +215,13 @@ namespace Robust.Shared.Maths
return !(a == b);
}
[Pure]
public readonly Angle Opposite()
{
return new Angle(FlipPositive(Theta-Math.PI));
}
[Pure]
public readonly Angle FlipPositive()
{
return new(FlipPositive(Theta));

View File

@@ -682,6 +682,7 @@ namespace Robust.Shared.Maths
/// (which is copied to the output's Alpha value).
/// Each has a range of 0.0 to 1.0.
/// </param>
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
public static Color FromHcy(Vector4 hcy)
{
var hue = hcy.X * 360.0f;
@@ -750,6 +751,7 @@ namespace Robust.Shared.Maths
/// </returns>
/// <param name="rgb">Color value to convert.</param>
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
public static Vector4 ToHcy(Color rgb)
{
var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B));

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.Contracts;
using System.Numerics;
namespace Robust.Shared.Maths
@@ -238,6 +239,7 @@ namespace Robust.Shared.Maths
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
[Pure]
public static Angle ToAngle(this Direction dir)
{
var ang = Segment * (int) dir;

View File

@@ -8,7 +8,7 @@ namespace Robust.Shared.Audio;
/// Contains audio defaults to set for sounds.
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
/// </summary>
[Prototype("audioPreset")]
[Prototype]
public sealed partial class AudioPresetPrototype : IPrototype
{
[IdDataField]

View File

@@ -6,7 +6,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.Audio;
[Prototype("soundCollection")]
[Prototype]
public sealed partial class SoundCollectionPrototype : IPrototype
{
[ViewVariables]

View File

@@ -60,9 +60,7 @@ namespace Robust.Shared.ContentPack
internal string GetPath(ResPath relPath)
{
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
// Sanitise platform-specific path and standardize it for engine use.
.Replace(Path.DirectorySeparatorChar, '/');
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
}
/// <inheritdoc />

View File

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

View File

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

View File

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

View File

@@ -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
{
@@ -381,6 +381,10 @@ namespace Robust.Shared.ContentPack
{
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);
}
}

View File

@@ -10,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
@@ -119,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 />
@@ -180,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);
}
}
}

View File

@@ -47,6 +47,7 @@ public sealed partial class MapLoaderSystem : EntitySystem
Log.Info($"Saving serialized results to {path}");
path = path.ToRootedPath();
var document = new YamlDocument(data.ToYaml());
_resourceManager.UserData.CreateDir(path.Directory);
using var writer = _resourceManager.UserData.OpenWriteText(path);
{
var stream = new YamlStream {document};

View File

@@ -1,20 +1,22 @@
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects
namespace Robust.Shared.GameObjects;
/// <summary>
/// Controls PVS visibility of entities. THIS COMPONENT CONTROLS WHETHER ENTITIES ARE NETWORKED TO PLAYERS
/// AND SHOULD NOT BE USED AS THE SOLE WAY TO HIDE AN ENTITY FROM A PLAYER.
/// </summary>
[RegisterComponent]
[Access(typeof(SharedVisibilitySystem))]
public sealed partial class VisibilityComponent : Component
{
/// <summary>
/// Controls PVS visibility of entities. THIS COMPONENT CONTROLS WHETHER ENTITIES ARE NETWORKED TO PLAYERS
/// AND SHOULD NOT BE USED AS THE SOLE WAY TO HIDE AN ENTITY FROM A PLAYER.
/// The visibility layer for the entity.
/// Players whose visibility masks don't match this won't get state updates for it.
/// </summary>
[RegisterComponent]
[Access(typeof(SharedVisibilitySystem))]
public sealed partial class VisibilityComponent : Component
{
/// <summary>
/// The visibility layer for the entity.
/// Players whose visibility masks don't match this won't get state updates for it.
/// </summary>
[DataField("layer")]
public ushort Layer = 1;
}
/// <remarks>
/// Not serialized as visibility is normally immediate (i.e. prior to MapInit) and content should be handling it as such.
/// </remarks>
[DataField(readOnly: true)]
public ushort Layer = 1;
}

View File

@@ -59,7 +59,7 @@ public abstract partial class EntityManager
Dirty(uid, comp, metadata);
}
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
where T : IComponentDelta
{
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());

View File

@@ -172,12 +172,22 @@ public partial class EntitySystem
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
where T : IComponentDelta
{
EntityManager.DirtyFields(uid, comp, meta, fields);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void DirtyFields<T>(Entity<T?> ent, MetaDataComponent? meta, params string[] fields)
where T : IComponentDelta
{
if (!Resolve(ent, ref ent.Comp))
return;
EntityManager.DirtyFields(ent, ent.Comp, meta, fields);
}
/// <summary>
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
/// </summary>

View File

@@ -9,6 +9,7 @@ using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Shapes;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;
@@ -406,7 +407,7 @@ public sealed partial class EntityLookupSystem
var broadphaseInv = _transform.GetInvWorldMatrix(lookupUid);
var localBounds = broadphaseInv.TransformBounds(worldBounds);
var polygon = _physics.GetPooled(localBounds);
var polygon = new SlimPolygon(localBounds);
var result = AnyEntitiesIntersecting(lookupUid,
polygon,
localBounds.CalcBoundingBox(),
@@ -414,7 +415,6 @@ public sealed partial class EntityLookupSystem
flags,
ignored);
_physics.ReturnPooled(polygon);
return result;
}
@@ -458,9 +458,8 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return false;
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
return result;
}
@@ -475,9 +474,8 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return;
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
}
#endregion
@@ -487,18 +485,16 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
// Don't need to check contained entities as they have the same bounds as the parent.
var polygon = _physics.GetPooled(worldBounds);
var polygon = new SlimPolygon(worldBounds);
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
return result;
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet<EntityUid>();
var polygon = _physics.GetPooled(worldBounds);
var polygon = new SlimPolygon(worldBounds);
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
return intersecting;
}
@@ -761,11 +757,10 @@ public sealed partial class EntityLookupSystem
return;
var localAABB = _transform.GetInvWorldMatrix(gridId).TransformBox(worldAABB);
var polygon = _physics.GetPooled(localAABB);
var polygon = new SlimPolygon(localAABB);
AddEntitiesIntersecting(gridId, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, lookup);
AddContained(intersecting, flags);
_physics.ReturnPooled(polygon);
}
public void GetEntitiesIntersecting(EntityUid gridId, Box2Rotated worldBounds, HashSet<EntityUid> intersecting, LookupFlags flags = DefaultFlags)
@@ -774,11 +769,10 @@ public sealed partial class EntityLookupSystem
return;
var localBounds = _transform.GetInvWorldMatrix(gridId).TransformBounds(worldBounds);
var polygon = _physics.GetPooled(localBounds);
var polygon = new SlimPolygon(localBounds);
AddEntitiesIntersecting(gridId, intersecting, polygon, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, lookup);
AddContained(intersecting, flags);
_physics.ReturnPooled(polygon);
}
#endregion

View File

@@ -121,9 +121,8 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return;
var polygon = _physics.GetPooled(localAABB);
var polygon = new SlimPolygon(localAABB);
AddEntitiesIntersecting(lookupUid, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, query, lookup);
_physics.ReturnPooled(polygon);
}
private void AddEntitiesIntersecting<T, TShape>(
@@ -252,11 +251,10 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return false;
var polygon = _physics.GetPooled(localAABB);
var polygon = new SlimPolygon(localAABB);
var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid);
var transform = new Transform(lookupPos, lookupRot);
var result = AnyComponentsIntersecting(lookupUid, polygon, localAABB, transform, flags, query, ignored, lookup);
_physics.ReturnPooled(polygon);
return result;
}
@@ -427,10 +425,9 @@ public sealed partial class EntityLookupSystem
public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, EntityUid? ignored = null, LookupFlags flags = DefaultFlags)
{
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
var transform = Physics.Transform.Empty;
var result = AnyComponentsIntersecting(type, mapId, polygon, transform, ignored, flags);
_physics.ReturnPooled(polygon);
return result;
}
@@ -496,33 +493,30 @@ public sealed partial class EntityLookupSystem
if (mapId == MapId.Nullspace)
return;
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
var transform = Physics.Transform.Empty;
GetEntitiesIntersecting(type, mapId, polygon, transform, intersecting, flags);
_physics.ReturnPooled(polygon);
}
public void GetEntitiesIntersecting<T>(MapId mapId, Box2Rotated worldBounds, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
if (mapId == MapId.Nullspace) return;
var polygon = _physics.GetPooled(worldBounds);
var polygon = new SlimPolygon(worldBounds);
var shapeTransform = Physics.Transform.Empty;
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
_physics.ReturnPooled(polygon);
}
public void GetEntitiesIntersecting<T>(MapId mapId, Box2 worldAABB, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
if (mapId == MapId.Nullspace) return;
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
var shapeTransform = Physics.Transform.Empty;
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
_physics.ReturnPooled(polygon);
}
#endregion

View File

@@ -26,7 +26,7 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return;
var lookupPoly = new Polygon(localAABB);
var lookupPoly = new SlimPolygon(localAABB);
AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, localAABB, Physics.Transform.Empty, flags, lookup);
}
@@ -40,7 +40,7 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return;
var shape = new Polygon(localBounds);
var shape = new SlimPolygon(localBounds);
var localAABB = localBounds.CalcBoundingBox();
AddEntitiesIntersecting(lookupUid, intersecting, shape, localAABB, Physics.Transform.Empty, flags);
@@ -55,7 +55,7 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return false;
var shape = new Polygon(localAABB);
var shape = new SlimPolygon(localAABB);
return AnyEntitiesIntersecting(lookupUid, shape, localAABB, Physics.Transform.Empty, flags, ignored, lookup);
}

View File

@@ -159,6 +159,10 @@ public abstract class SharedEyeSystem : EntitySystem
eye.Comp.PvsScale = Math.Clamp(scale, 0.1f, 100f);
}
/// <summary>
/// Overwrites visibility mask of an entity's eye.
/// If you wish for other systems to potentially change it consider raising <see cref="RefreshVisibilityMask"/>.
/// </summary>
public void SetVisibilityMask(EntityUid uid, int value, EyeComponent? eyeComponent = null)
{
if (!Resolve(uid, ref eyeComponent))
@@ -170,4 +174,32 @@ public abstract class SharedEyeSystem : EntitySystem
eyeComponent.VisibilityMask = value;
DirtyField(uid, eyeComponent, nameof(EyeComponent.VisibilityMask));
}
/// <summary>
/// Updates the visibility mask for an entity by raising a <see cref="GetVisMaskEvent"/>
/// </summary>
public void RefreshVisibilityMask(Entity<EyeComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp, false))
return;
var ev = new GetVisMaskEvent()
{
Entity = entity.Owner,
};
RaiseLocalEvent(entity.Owner, ref ev, true);
SetVisibilityMask(entity.Owner, ev.VisibilityMask, entity.Comp);
}
}
/// <summary>
/// Event raised to update the vismask of an entity's eye.
/// </summary>
[ByRefEvent]
public record struct GetVisMaskEvent()
{
public EntityUid Entity;
public int VisibilityMask = EyeComponent.DefaultVisibilityMask;
}

View File

@@ -8,6 +8,7 @@ using Robust.Shared.Map.Enumerators;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Shapes;
namespace Robust.Shared.Map;
@@ -224,48 +225,42 @@ internal partial class MapManager
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2 worldAABB, ref TState state, GridCallback<TState> callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref state, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, ref List<Entity<MapGridComponent>> grids,
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = _physics.GetPooled(worldAABB);
var polygon = new SlimPolygon(worldAABB);
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref grids, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, GridCallback callback, bool approx = IMapManager.Approximate,
bool includeMap = IMapManager.IncludeMap)
{
var polygon = _physics.GetPooled(worldBounds);
var polygon = new SlimPolygon(worldBounds);
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback,
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = _physics.GetPooled(worldBounds);
var polygon = new SlimPolygon(worldBounds);
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref state, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, ref List<Entity<MapGridComponent>> grids,
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = _physics.GetPooled(worldBounds);
var polygon = new SlimPolygon(worldBounds);
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref grids, approx, includeMap);
_physics.ReturnPooled(polygon);
}
#endregion

View File

@@ -12,7 +12,9 @@ internal sealed partial class CollisionManager
/// <param name="xfA">The transform for the first shape.</param>
/// <param name="xfB">The transform for the seconds shape.</param>
/// <returns></returns>
public bool TestOverlap<T, U>(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB) where T : IPhysShape where U : IPhysShape
public bool TestOverlap<T, U>(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB)
where T : IPhysShape
where U : IPhysShape
{
var input = new DistanceInput();

View File

@@ -21,12 +21,9 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Shapes;
@@ -46,7 +43,7 @@ internal ref struct DistanceProxy
// GJK using Voronoi regions (Christer Ericson) and Barycentric coordinates.
internal DistanceProxy(Vector2[] vertices, float radius)
internal DistanceProxy(ReadOnlySpan<Vector2> vertices, float radius)
{
Vertices = vertices;
Radius = radius;
@@ -71,9 +68,18 @@ internal ref struct DistanceProxy
case ShapeType.Polygon:
if (shape is Polygon poly)
{
Vertices = poly.Vertices.AsSpan()[..poly.VertexCount];
Span<Vector2> verts = new Vector2[poly.VertexCount];
poly._vertices.AsSpan[..poly.VertexCount].CopyTo(verts);
Vertices = verts;
Radius = poly.Radius;
}
else if (shape is SlimPolygon fast)
{
Span<Vector2> verts = new Vector2[fast.VertexCount];
fast._vertices.AsSpan[..fast.VertexCount].CopyTo(verts);
Vertices = verts;
Radius = fast.Radius;
}
else
{
var polyShape = Unsafe.As<PolygonShape>(shape);
@@ -151,7 +157,7 @@ internal ref struct DistanceProxy
return Vertices[bestIndex];
}
internal static DistanceProxy MakeProxy(Vector2[] vertices, int count, float radius )
internal static DistanceProxy MakeProxy(ReadOnlySpan<Vector2> vertices, int count, float radius )
{
count = Math.Min(count, PhysicsConstants.MaxPolygonVertices);
var proxy = new DistanceProxy(vertices[..count], radius);

View File

@@ -173,10 +173,25 @@ namespace Robust.Shared.Physics.Collision.Shapes
{
}
internal PolygonShape(SlimPolygon poly)
{
Vertices = new Vector2[poly.VertexCount];
Normals = new Vector2[poly.VertexCount];
poly._vertices.AsSpan[..VertexCount].CopyTo(Vertices);
poly._normals.AsSpan[..VertexCount].CopyTo(Normals);
Centroid = poly.Centroid;
}
internal PolygonShape(Polygon poly)
{
Vertices = poly.Vertices;
Normals = poly.Normals;
Vertices = new Vector2[poly.VertexCount];
Normals = new Vector2[poly.VertexCount];
poly._vertices.AsSpan[..VertexCount].CopyTo(Vertices);
poly._normals.AsSpan[..VertexCount].CopyTo(Normals);
Centroid = poly.Centroid;
}

View File

@@ -150,6 +150,15 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
Friction = MathF.Sqrt((FixtureA?.Friction ?? 0.0f) * (FixtureB?.Friction ?? 0.0f));
}
public void GetWorldManifold(Transform transformA, Transform transformB, out Vector2 normal)
{
var shapeA = FixtureA?.Shape!;
var shapeB = FixtureB?.Shape!;
Span<Vector2> points = stackalloc Vector2[PhysicsConstants.MaxPolygonVertices];
SharedPhysicsSystem.InitializeManifold(ref Manifold, transformA, transformB, shapeA.Radius, shapeB.Radius, out normal, points);
}
/// <summary>
/// Gets the world manifold.
/// </summary>
@@ -408,6 +417,28 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
throw new InvalidOperationException();
}
[Pure, PublicAPI]
public PhysicsComponent OurBody(EntityUid uid)
{
if (uid == EntityA)
return BodyA!;
else if (uid == EntityB)
return BodyB!;
throw new InvalidOperationException();
}
[Pure, PublicAPI]
public PhysicsComponent OtherBody(EntityUid uid)
{
if (uid == EntityA)
return BodyB!;
else if (uid == EntityB)
return BodyA!;
throw new InvalidOperationException();
}
}
[Flags]

View File

@@ -11,19 +11,28 @@ namespace Robust.Shared.Physics.Shapes;
// Internal so people don't use it when it will have breaking changes very soon.
internal record struct Polygon : IPhysShape
{
public static Polygon Empty = new(Box2.Empty);
[DataField]
public byte VertexCount { get; internal set; }
/// <summary>
/// Vertices associated with this polygon. Will be sliced to <see cref="VertexCount"/>
/// </summary>
/// <remarks>
/// Consider using _vertices if doing engine work.
/// </remarks>
public Vector2[] Vertices => _vertices.AsSpan[..VertexCount].ToArray();
public Vector2[] Normals => _normals.AsSpan[..VertexCount].ToArray();
[DataField]
public Vector2[] Vertices;
internal FixedArray8<Vector2> _vertices;
public byte VertexCount;
public Vector2[] Normals;
internal FixedArray8<Vector2> _normals;
public Vector2 Centroid;
public int ChildCount => 1;
public float Radius { get; set; }
public float Radius { get; set; } = PhysicsConstants.PolygonRadius;
public ShapeType ShapeType => ShapeType.Polygon;
// Hopefully this one is short-lived for a few months
@@ -35,67 +44,60 @@ internal record struct Polygon : IPhysShape
public Polygon(PolygonShape polyShape)
{
Unsafe.SkipInit(out this);
Vertices = new Vector2[polyShape.VertexCount];
Normals = new Vector2[polyShape.Normals.Length];
Radius = polyShape.Radius;
Centroid = polyShape.Centroid;
VertexCount = (byte) polyShape.VertexCount;
Array.Copy(polyShape.Vertices, Vertices, Vertices.Length);
Array.Copy(polyShape.Normals, Normals, Vertices.Length);
VertexCount = (byte) Vertices.Length;
polyShape.Vertices.AsSpan()[..VertexCount].CopyTo(_vertices.AsSpan);
polyShape.Normals.AsSpan()[..VertexCount].CopyTo(_normals.AsSpan);
}
/// <summary>
/// Manually constructed polygon for internal use to take advantage of pooling.
/// </summary>
internal Polygon(Vector2[] vertices, Vector2[] normals, Vector2 centroid, byte count)
public Polygon(Box2 box)
{
Unsafe.SkipInit(out this);
Vertices = vertices;
Normals = normals;
Centroid = centroid;
VertexCount = count;
}
public Polygon(Box2 aabb)
{
Unsafe.SkipInit(out this);
Vertices = new Vector2[4];
Normals = new Vector2[4];
Radius = 0f;
Vertices[0] = aabb.BottomLeft;
Vertices[1] = aabb.BottomRight;
Vertices[2] = aabb.TopRight;
Vertices[3] = aabb.TopLeft;
Normals[0] = new Vector2(0.0f, -1.0f);
Normals[1] = new Vector2(1.0f, 0.0f);
Normals[2] = new Vector2(0.0f, 1.0f);
Normals[3] = new Vector2(-1.0f, 0.0f);
VertexCount = 4;
Centroid = aabb.Center;
_vertices._00 = box.BottomLeft;
_vertices._01 = box.BottomRight;
_vertices._02 = box.TopRight;
_vertices._03 = box.TopLeft;
_normals._00 = new Vector2(0.0f, -1.0f);
_normals._01 = new Vector2(1.0f, 0.0f);
_normals._02 = new Vector2(0.0f, 1.0f);
_normals._03 = new Vector2(-1.0f, 0.0f);
}
public Polygon(Box2Rotated bounds)
{
Unsafe.SkipInit(out this);
Vertices = new Vector2[4];
Normals = new Vector2[4];
Radius = 0f;
Vertices[0] = bounds.BottomLeft;
Vertices[1] = bounds.BottomRight;
Vertices[2] = bounds.TopRight;
Vertices[3] = bounds.TopLeft;
CalculateNormals(Normals, 4);
VertexCount = 4;
_vertices._00 = bounds.BottomLeft;
_vertices._01 = bounds.BottomRight;
_vertices._02 = bounds.TopRight;
_vertices._03 = bounds.TopLeft;
CalculateNormals(_vertices.AsSpan, _normals.AsSpan, 4);
Centroid = bounds.Center;
}
/// <summary>
/// Manually constructed polygon for internal use to take advantage of pooling.
/// </summary>
internal Polygon(ReadOnlySpan<Vector2> vertices, ReadOnlySpan<Vector2> normals, Vector2 centroid, byte count)
{
Unsafe.SkipInit(out this);
vertices[..VertexCount].CopyTo(_vertices.AsSpan);
normals[..VertexCount].CopyTo(_normals.AsSpan);
Centroid = centroid;
VertexCount = count;
Radius = 0f;
}
public Polygon(Vector2[] vertices)
{
Unsafe.SkipInit(out this);
@@ -104,16 +106,15 @@ internal record struct Polygon : IPhysShape
if (hull.Count < 3)
{
VertexCount = 0;
Vertices = [];
Normals = [];
return;
}
VertexCount = (byte) vertices.Length;
Vertices = vertices;
Normals = new Vector2[vertices.Length];
var vertSpan = _vertices.AsSpan;
vertices.AsSpan().CopyTo(vertSpan);
Set(hull);
Centroid = ComputeCentroid(Vertices);
Centroid = ComputeCentroid(vertSpan);
}
public static explicit operator Polygon(PolygonShape polyShape)
@@ -125,32 +126,32 @@ internal record struct Polygon : IPhysShape
{
DebugTools.Assert(hull.Count >= 3);
var vertexCount = hull.Count;
Array.Resize(ref Vertices, vertexCount);
Array.Resize(ref Normals, vertexCount);
var verts = _vertices.AsSpan;
var norms = _normals.AsSpan;
for (var i = 0; i < vertexCount; i++)
{
Vertices[i] = hull.Points[i];
verts[i] = hull.Points[i];
}
// Compute normals. Ensure the edges have non-zero length.
CalculateNormals(Normals, vertexCount);
CalculateNormals(verts, norms, vertexCount);
}
internal void CalculateNormals(Span<Vector2> normals, int count)
public static void CalculateNormals(ReadOnlySpan<Vector2> vertices, Span<Vector2> normals, int count)
{
for (var i = 0; i < count; i++)
{
var next = i + 1 < count ? i + 1 : 0;
var edge = Vertices[next] - Vertices[i];
var edge = vertices[next] - vertices[i];
DebugTools.Assert(edge.LengthSquared() > float.Epsilon * float.Epsilon);
var temp = Vector2Helpers.Cross(edge, 1f);
Normals[i] = temp.Normalized();
normals[i] = temp.Normalized();
}
}
private static Vector2 ComputeCentroid(Vector2[] vs)
public static Vector2 ComputeCentroid(ReadOnlySpan<Vector2> vs)
{
var count = vs.Length;
DebugTools.Assert(count >= 3);
@@ -191,13 +192,15 @@ internal record struct Polygon : IPhysShape
public Box2 ComputeAABB(Transform transform, int childIndex)
{
DebugTools.Assert(VertexCount > 0);
DebugTools.Assert(childIndex == 0);
var lower = Transform.Mul(transform, Vertices[0]);
var verts = _vertices.AsSpan;
var lower = Transform.Mul(transform, verts[0]);
var upper = lower;
for (var i = 1; i < VertexCount; ++i)
{
var v = Transform.Mul(transform, Vertices[i]);
var v = Transform.Mul(transform, verts[i]);
lower = Vector2.Min(lower, v);
upper = Vector2.Max(upper, v);
}
@@ -208,12 +211,41 @@ internal record struct Polygon : IPhysShape
public bool Equals(IPhysShape? other)
{
if (other is not PolygonShape poly) return false;
if (VertexCount != poly.VertexCount) return false;
if (other is SlimPolygon slim)
{
return Equals(slim);
}
return other is Polygon poly && Equals(poly);
}
public bool Equals(Polygon other)
{
if (VertexCount != other.VertexCount) return false;
var ourVerts = _vertices.AsSpan;
var otherVerts = other._vertices.AsSpan;
for (var i = 0; i < VertexCount; i++)
{
var vert = Vertices[i];
if (!vert.Equals(poly.Vertices[i])) return false;
var vert = ourVerts[i];
if (!vert.Equals(otherVerts[i])) return false;
}
return true;
}
public bool Equals(SlimPolygon other)
{
if (VertexCount != other.VertexCount) return false;
var ourVerts = _vertices.AsSpan;
var otherVerts = other._vertices.AsSpan;
for (var i = 0; i < VertexCount; i++)
{
var vert = ourVerts[i];
if (!vert.Equals(otherVerts[i])) return false;
}
return true;
@@ -221,6 +253,6 @@ internal record struct Polygon : IPhysShape
public override int GetHashCode()
{
return HashCode.Combine(VertexCount, Vertices.AsSpan(0, VertexCount).ToArray(), Radius);
return HashCode.Combine(VertexCount, _vertices.AsSpan.ToArray(), Radius);
}
}

View File

@@ -0,0 +1,103 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Shapes;
/// <summary>
/// Polygon backed by FixedArray4 to be smaller.
/// Useful for internal ops where the inputs are boxes to avoid the additional padding.
/// </summary>
internal record struct SlimPolygon : IPhysShape
{
public Vector2[] Vertices => _vertices.AsSpan[..VertexCount].ToArray();
public Vector2[] Normals => _normals.AsSpan[..VertexCount].ToArray();
[DataField]
public FixedArray4<Vector2> _vertices;
public FixedArray4<Vector2> _normals;
public Vector2 Centroid;
public byte VertexCount => 4;
public int ChildCount => 1;
public float Radius { get; set; } = PhysicsConstants.PolygonRadius;
public ShapeType ShapeType => ShapeType.Polygon;
public SlimPolygon(Box2 box)
{
Unsafe.SkipInit(out this);
Radius = 0f;
_vertices._00 = box.BottomLeft;
_vertices._01 = box.BottomRight;
_vertices._02 = box.TopRight;
_vertices._03 = box.TopLeft;
_normals._00 = new Vector2(0.0f, -1.0f);
_normals._01 = new Vector2(1.0f, 0.0f);
_normals._02 = new Vector2(0.0f, 1.0f);
_normals._03 = new Vector2(-1.0f, 0.0f);
}
public SlimPolygon(Box2Rotated bounds)
{
Unsafe.SkipInit(out this);
Radius = 0f;
_vertices._00 = bounds.BottomLeft;
_vertices._01 = bounds.BottomRight;
_vertices._02 = bounds.TopRight;
_vertices._03 = bounds.TopLeft;
Polygon.CalculateNormals(_vertices.AsSpan, _normals.AsSpan, 4);
Centroid = bounds.Center;
}
public Box2 ComputeAABB(Transform transform, int childIndex)
{
DebugTools.Assert(VertexCount > 0);
DebugTools.Assert(childIndex == 0);
var verts = _vertices.AsSpan;
var lower = Transform.Mul(transform, verts[0]);
var upper = lower;
for (var i = 1; i < VertexCount; ++i)
{
var v = Transform.Mul(transform, verts[i]);
lower = Vector2.Min(lower, v);
upper = Vector2.Max(upper, v);
}
var r = new Vector2(Radius, Radius);
return new Box2(lower - r, upper + r);
}
public bool Equals(SlimPolygon other)
{
return Radius.Equals(other.Radius) && _vertices.AsSpan[..VertexCount].SequenceEqual(other._vertices.AsSpan[..VertexCount]);
}
public readonly override int GetHashCode()
{
return HashCode.Combine(_vertices, _normals, Centroid, Radius);
}
public bool Equals(IPhysShape? other)
{
if (other is Polygon poly)
{
return poly.Equals(this);
}
return other is SlimPolygon slim && Equals(slim);
}
}

View File

@@ -40,13 +40,29 @@ namespace Robust.Shared.Physics.Systems
return true;
}
case SlimPolygon slim:
{
var pLocal = Physics.Transform.MulT(xform.Quaternion2D, worldPoint - xform.Position);
var norms = slim._normals.AsSpan;
var verts = slim._vertices.AsSpan;
for (var i = 0; i < slim.VertexCount; i++)
{
var dot = Vector2.Dot(norms[i], pLocal - verts[i]);
if (dot > 0f) return false;
}
return true;
}
case Polygon poly:
{
var pLocal = Physics.Transform.MulT(xform.Quaternion2D, worldPoint - xform.Position);
var norms = poly._normals.AsSpan;
var verts = poly._vertices.AsSpan;
for (var i = 0; i < poly.VertexCount; i++)
{
var dot = Vector2.Dot(poly.Normals[i], pLocal - poly.Vertices[i]);
var dot = Vector2.Dot(norms[i], pLocal - verts[i]);
if (dot > 0f) return false;
}
@@ -86,6 +102,10 @@ namespace Robust.Shared.Physics.Systems
var polygon = (PolygonShape) aabb;
GetMassData(polygon, ref data, density);
break;
case Polygon fastPoly:
return GetMassData(new PolygonShape(fastPoly), density);
case SlimPolygon slim:
return GetMassData(new PolygonShape(slim), density);
case PolygonShape poly:
// Polygon mass, centroid, and inertia.
// Let rho be the polygon density in mass per unit area.
@@ -195,6 +215,12 @@ namespace Robust.Shared.Physics.Systems
var polygon = (PolygonShape) aabb;
GetMassData(polygon, ref data, density);
break;
case Polygon fastPoly:
GetMassData(new PolygonShape(fastPoly), ref data, density);
break;
case SlimPolygon slim:
GetMassData(new PolygonShape(slim), ref data, density);
break;
case PolygonShape poly:
// Polygon mass, centroid, and inertia.
// Let rho be the polygon density in mass per unit area.

View File

@@ -191,6 +191,12 @@ public sealed partial class RayCastSystem
private CastOutput RayCastPolygon(RayCastInput input, Polygon shape)
{
var verts = shape._vertices.AsSpan;
var output = new CastOutput()
{
Fraction = 0f,
};
if (shape.Radius == 0.0f)
{
// Put the ray into the polygon's frame of reference.
@@ -201,18 +207,15 @@ public sealed partial class RayCastSystem
var index = -1;
var output = new CastOutput()
{
Fraction = 0f,
};
var norms = shape._normals.AsSpan;
for ( var i = 0; i < shape.VertexCount; ++i )
{
// p = p1 + a * d
// dot(normal, p - v) = 0
// dot(normal, p1 - v) + a * dot(normal, d) = 0
float numerator = Vector2.Dot(shape.Normals[i], Vector2.Subtract( shape.Vertices[i], p1 ) );
float denominator = Vector2.Dot(shape.Normals[i], d );
float numerator = Vector2.Dot(norms[i], Vector2.Subtract( verts[i], p1 ) );
float denominator = Vector2.Dot(norms[i], d );
if ( denominator == 0.0f )
{
@@ -257,7 +260,7 @@ public sealed partial class RayCastSystem
if (index >= 0)
{
output.Fraction = lower;
output.Normal = shape.Normals[index];
output.Normal = norms[index];
output.Point = Vector2.Add(p1, lower * d);
output.Hit = true;
}
@@ -265,17 +268,24 @@ public sealed partial class RayCastSystem
return output;
}
// TODO_ERIN this is not working for ray vs box (zero radii)
Span<Vector2> proxyBVerts = new Vector2[]
{
input.Origin,
};
// TODO_ERIN this is not working for ray vs box (zero radii)
var castInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy(shape.Vertices, shape.VertexCount, shape.Radius),
ProxyB = DistanceProxy.MakeProxy([input.Origin], 1, 0.0f),
ProxyA = DistanceProxy.MakeProxy(verts, shape.VertexCount, shape.Radius),
ProxyB = DistanceProxy.MakeProxy(proxyBVerts, 1, 0.0f),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
TranslationB = input.Translation,
MaxFraction = input.MaxFraction
};
return ShapeCast(castInput);
ShapeCast(ref output, castInput);
return output;
}
// Ray vs line segment
@@ -436,12 +446,9 @@ public sealed partial class RayCastSystem
// "Smooth Mesh Contacts with GJK" in Game Physics Pearls. 2010
// todo this is failing when used to raycast a box
// todo this converges slowly with a radius
private CastOutput ShapeCast(ShapeCastPairInput input)
private void ShapeCast(ref CastOutput output, in ShapeCastPairInput input)
{
var output = new CastOutput()
{
Fraction = input.MaxFraction,
};
output.Fraction = input.MaxFraction;
var proxyA = input.ProxyA;
var count = input.ProxyB.Vertices.Length;
@@ -513,15 +520,15 @@ public sealed partial class RayCastSystem
if ( vr <= 0.0f )
{
// miss
return output;
}
return;
}
lambda = ( vp - sigma ) / vr;
if ( lambda > maxFraction )
{
// too far
return output;
}
return;
}
// reset the simplex
simplex.Count = 0;
@@ -562,7 +569,7 @@ public sealed partial class RayCastSystem
{
// Overlap
// Yes this means you need to manually query for overlaps.
return output;
return;
}
// Get search direction.
@@ -576,7 +583,7 @@ public sealed partial class RayCastSystem
if ( iter == 0 || lambda == 0.0f )
{
// Initial overlap
return output;
return;
}
// Prepare output.
@@ -591,7 +598,6 @@ public sealed partial class RayCastSystem
output.Fraction = lambda;
output.Iterations = iter;
output.Hit = true;
return output;
}
private int FindSupport(DistanceProxy proxy, Vector2 direction)
@@ -613,9 +619,14 @@ public sealed partial class RayCastSystem
private CastOutput ShapeCastCircle(ShapeCastInput input, PhysShapeCircle shape)
{
Span<Vector2> proxyAVerts = new[]
{
shape.Position,
};
var pairInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy([shape.Position], 1, shape.Radius ),
ProxyA = DistanceProxy.MakeProxy(proxyAVerts, 1, shape.Radius ),
ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius ),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
@@ -623,7 +634,8 @@ public sealed partial class RayCastSystem
MaxFraction = input.MaxFraction
};
var output = ShapeCast(pairInput);
var output = new CastOutput();
ShapeCast(ref output, pairInput);
return output;
}
@@ -631,7 +643,7 @@ public sealed partial class RayCastSystem
{
var pairInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy(shape.Vertices, shape.VertexCount, shape.Radius),
ProxyA = DistanceProxy.MakeProxy(shape._vertices.AsSpan, shape.VertexCount, shape.Radius),
ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
@@ -639,21 +651,30 @@ public sealed partial class RayCastSystem
MaxFraction = input.MaxFraction
};
var output = ShapeCast(pairInput);
var output = new CastOutput();
ShapeCast(ref output, pairInput);
return output;
}
private CastOutput ShapeCastSegment(ShapeCastInput input, EdgeShape shape)
{
var pairInput = new ShapeCastPairInput();
pairInput.ProxyA = DistanceProxy.MakeProxy([shape.Vertex0], 2, 0.0f);
pairInput.ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius);
pairInput.TransformA = Physics.Transform.Empty;
pairInput.TransformB = Physics.Transform.Empty;
pairInput.TranslationB = input.Translation;
pairInput.MaxFraction = input.MaxFraction;
Span<Vector2> proxyAVerts = new[]
{
shape.Vertex0,
};
var output = ShapeCast(pairInput);
var pairInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy(proxyAVerts, 2, 0.0f),
ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
TranslationB = input.Translation,
MaxFraction = input.MaxFraction
};
var output = new CastOutput();
ShapeCast(ref output, pairInput);
return output;
}

View File

@@ -275,6 +275,9 @@ public sealed partial class RayCastSystem : EntitySystem
case PhysShapeCircle circle:
CastCircle(entity, ref result, circle, originTransform, translation, filter, callback);
break;
case SlimPolygon slim:
CastPolygon(entity, ref result, new PolygonShape(slim), originTransform, translation, filter, callback);
break;
case Polygon poly:
CastPolygon(entity, ref result, new PolygonShape(poly), originTransform, translation, filter, callback);
break;

View File

@@ -1,55 +0,0 @@
using System.Buffers;
using System.Numerics;
using Microsoft.Extensions.ObjectPool;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Shapes;
namespace Robust.Shared.Physics.Systems;
public abstract partial class SharedPhysicsSystem
{
/// <summary>
/// Gets a polygon with pooled arrays backing it.
/// </summary>
internal Polygon GetPooled(Box2 box)
{
var vertices = ArrayPool<Vector2>.Shared.Rent(4);
var normals = ArrayPool<Vector2>.Shared.Rent(4);
var centroid = box.Center;
vertices[0] = box.BottomLeft;
vertices[1] = box.BottomRight;
vertices[2] = box.TopRight;
vertices[3] = box.TopLeft;
normals[0] = new Vector2(0.0f, -1.0f);
normals[1] = new Vector2(1.0f, 0.0f);
normals[2] = new Vector2(0.0f, 1.0f);
normals[3] = new Vector2(-1.0f, 0.0f);
return new Polygon(vertices, normals, centroid, 4);
}
internal Polygon GetPooled(Box2Rotated box)
{
var vertices = ArrayPool<Vector2>.Shared.Rent(4);
var normals = ArrayPool<Vector2>.Shared.Rent(4);
var centroid = box.Center;
vertices[0] = box.BottomLeft;
vertices[1] = box.BottomRight;
vertices[2] = box.TopRight;
vertices[3] = box.TopLeft;
var polygon = new Polygon(vertices, normals, centroid, 4);
polygon.CalculateNormals(normals, 4);
return polygon;
}
internal void ReturnPooled(Polygon polygon)
{
ArrayPool<Vector2>.Shared.Return(polygon.Vertices);
ArrayPool<Vector2>.Shared.Return(polygon.Normals);
}
}

View File

@@ -9,7 +9,7 @@ namespace Robust.Shared.Prototypes;
/// Prototype that represents some entity prototype category.
/// Useful for sorting or grouping entity prototypes for mapping/spawning UIs.
/// </summary>
[Prototype("entityCategory")]
[Prototype]
public sealed partial class EntityCategoryPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;

View File

@@ -21,7 +21,7 @@ namespace Robust.Shared.Prototypes
/// <summary>
/// Prototype that represents game entities.
/// </summary>
[Prototype("entity", -1)]
[Prototype(-1)]
public sealed partial class EntityPrototype : IPrototype, IInheritingPrototype, ISerializationHooks
{
private ILocalizationManager _loc = default!;

View File

@@ -6,7 +6,7 @@ namespace Robust.Shared.Prototypes;
/// <summary>
/// Prototype that represents an alias from one tile ID to another. These are used when deserializing entities from yaml.
/// </summary>
[Prototype("tileAlias")]
[Prototype]
public sealed partial class TileAliasPrototype : IPrototype
{
/// <summary>

View File

@@ -384,9 +384,16 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
if (_children.Count != other._children.Count)
return false;
// Given that keys are unique and we do not care about the ordering, we know that if removing identical
// key-value pairs leaves us with an empty list then the mappings are equal.
return Except(other) == null && Tag == other.Tag;
foreach (var (key, otherValue) in other)
{
if (!_children.TryGetValue(key, out var ownValue) ||
!otherValue.Equals(ownValue))
{
return false;
}
}
return Tag == other.Tag;
}
public override MappingDataNode PushInheritance(MappingDataNode node)

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
@@ -86,7 +87,7 @@ namespace Robust.Shared.Utility
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 2);
}
internal struct FixedArray4<T>
internal struct FixedArray4<T> : IEquatable<FixedArray4<T>>
{
public T _00;
public T _01;
@@ -94,9 +95,27 @@ namespace Robust.Shared.Utility
public T _03;
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 4);
public bool Equals(FixedArray4<T> other)
{
return _00?.Equals(other._00) == true &&
_01?.Equals(other._01) == true &&
_02?.Equals(other._02) == true &&
_03?.Equals(other._03) == true;
}
public override bool Equals(object? obj)
{
return obj is FixedArray4<T> other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_00, _01, _02, _03);
}
}
internal struct FixedArray8<T>
internal struct FixedArray8<T> : IEquatable<FixedArray8<T>>
{
public T _00;
public T _01;
@@ -108,6 +127,28 @@ namespace Robust.Shared.Utility
public T _07;
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 8);
public bool Equals(FixedArray8<T> other)
{
return _00?.Equals(other._00) == true &&
_01?.Equals(other._01) == true &&
_02?.Equals(other._02) == true &&
_03?.Equals(other._03) == true &&
_04?.Equals(other._04) == true &&
_05?.Equals(other._05) == true &&
_06?.Equals(other._06) == true &&
_07?.Equals(other._07) == true;
}
public override bool Equals(object? obj)
{
return obj is FixedArray8<T> other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(_00, _01, _02, _03, _04, _05, _06, _07);
}
}
internal struct FixedArray16<T>

View File

@@ -16,7 +16,7 @@ namespace Robust.Shared.Utility;
/// </summary>
[PublicAPI]
[Serializable, NetSerializable]
public sealed partial class FormattedMessage : IReadOnlyList<MarkupNode>
public sealed partial class FormattedMessage : IEquatable<FormattedMessage>, IReadOnlyList<MarkupNode>
{
public static FormattedMessage Empty => new();
@@ -278,6 +278,18 @@ public sealed partial class FormattedMessage : IReadOnlyList<MarkupNode>
return GetEnumerator();
}
/// <inheritdoc />
public bool Equals(FormattedMessage? other)
{
return other?.ToMarkup() == ToMarkup();
}
/// <inheritdoc />
public override int GetHashCode()
{
return ToMarkup().GetHashCode();
}
/// <returns>The string without markup tags.</returns>
public override string ToString()
{

View File

@@ -123,5 +123,71 @@ namespace Robust.UnitTesting.Client.UserInterface.Controls
Assert.That(control2.Position, Is.EqualTo(new Vector2(0, 65)));
Assert.That(control2.Size, Is.EqualTo(new Vector2(30, 15)));
}
[Test]
public void TestTwoExpandRatio()
{
var boxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
SetSize = new Vector2(100, 10),
Children =
{
new Control
{
MinWidth = 10,
HorizontalExpand = true,
SizeFlagsStretchRatio = 20,
},
new Control
{
MinWidth = 10,
HorizontalExpand = true,
SizeFlagsStretchRatio = 80
}
}
};
boxContainer.Arrange(UIBox2.FromDimensions(Vector2.Zero, boxContainer.SetSize));
Assert.Multiple(() =>
{
Assert.That(boxContainer.GetChild(0).Width, Is.EqualTo(20));
Assert.That(boxContainer.GetChild(1).Width, Is.EqualTo(80));
});
}
[Test]
public void TestTwoExpandOneSmall()
{
var boxContainer = new BoxContainer
{
Orientation = LayoutOrientation.Horizontal,
SetSize = new Vector2(100, 10),
Children =
{
new Control
{
MinWidth = 30,
HorizontalExpand = true,
SizeFlagsStretchRatio = 20,
},
new Control
{
MinWidth = 30,
HorizontalExpand = true,
SizeFlagsStretchRatio = 80
}
}
};
boxContainer.Arrange(UIBox2.FromDimensions(Vector2.Zero, boxContainer.SetSize));
Assert.Multiple(() =>
{
Assert.That(boxContainer.GetChild(0).Width, Is.EqualTo(30));
Assert.That(boxContainer.GetChild(1).Width, Is.EqualTo(70));
});
}
}
}

View File

@@ -26,8 +26,8 @@ public sealed partial class ComponentMapInitTest
var sim = simFactory.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
sim.Resolve<IEntityManager>().System<SharedMapSystem>().CreateMap(out var mapId);
var mapSystem = entManager.System<SharedMapSystem>();
mapSystem.CreateMap(out var mapId);
var ent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
Assert.That(entManager.GetComponent<MetaDataComponent>(ent).EntityLifeStage, Is.EqualTo(EntityLifeStage.MapInitialized));
@@ -36,7 +36,7 @@ public sealed partial class ComponentMapInitTest
Assert.That(comp.Count, Is.EqualTo(1));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Reflect(false)]

View File

@@ -102,7 +102,7 @@ namespace Robust.UnitTesting.Shared
var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All);
Assert.That(outcome, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(Box2Cases))]
@@ -127,7 +127,7 @@ namespace Robust.UnitTesting.Shared
var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All);
Assert.That(outcome, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -156,7 +156,7 @@ namespace Robust.UnitTesting.Shared
lookup.GetLocalEntitiesIntersecting(grid.Owner, queryBounds, entities);
Assert.That(entities.Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -185,7 +185,7 @@ namespace Robust.UnitTesting.Shared
lookup.GetLocalEntitiesIntersecting(grid.Owner, queryTile, entities);
Assert.That(entities.Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
#endregion
@@ -200,7 +200,7 @@ namespace Robust.UnitTesting.Shared
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
entManager.System<SharedMapSystem>().CreateMap(spawnPos.MapId);
@@ -210,7 +210,7 @@ namespace Robust.UnitTesting.Shared
entManager.Spawn(null, spawnPos);
Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(IntersectingCases))]
@@ -236,7 +236,7 @@ namespace Robust.UnitTesting.Shared
var bounds = new Box2Rotated(Box2.CenteredAround(queryPos.Position, new Vector2(range, range)));
Assert.That(lookup.GetEntitiesIntersecting(queryPos.MapId, bounds).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(InRangeCases))]
@@ -260,7 +260,7 @@ namespace Robust.UnitTesting.Shared
_ = entManager.SpawnEntity(null, spawnPos);
Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(InRangeCases))]
@@ -271,7 +271,7 @@ namespace Robust.UnitTesting.Shared
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
entManager.System<SharedMapSystem>().CreateMap(spawnPos.MapId);
@@ -281,7 +281,7 @@ namespace Robust.UnitTesting.Shared
entManager.Spawn(null, spawnPos);
Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -309,7 +309,7 @@ namespace Robust.UnitTesting.Shared
var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All);
Assert.That(outcome, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -338,7 +338,7 @@ namespace Robust.UnitTesting.Shared
lookup.GetLocalEntitiesIntersecting(grid.Owner, queryBounds, entities);
Assert.That(entities.Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
#endregion
@@ -383,7 +383,7 @@ namespace Robust.UnitTesting.Shared
entManager.DeleteEntity(dummy);
entManager.DeleteEntity(grid);
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}
}

View File

@@ -39,13 +39,13 @@ namespace Robust.UnitTesting.Shared.GameObjects
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(0));
var mapId = sim.CreateMap().MapId;
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(1));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(0));
}
}

View File

@@ -53,7 +53,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(2, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(2));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Test]
@@ -75,7 +75,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(1, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(2));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Test]
@@ -106,7 +106,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(1, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(2));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Test]
@@ -130,7 +130,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(1, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(3));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
/// <summary>
@@ -181,6 +181,6 @@ public sealed class GridSplit_Tests
Assert.That(dummyXform.GridUid, Is.EqualTo(newGrid.Owner));
Assert.That(newGridXform._children, Does.Contain(dummy));
});
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}

View File

@@ -39,6 +39,7 @@ public sealed class MapGridMap_Tests
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = sim.CreateMap().MapId;
mapManager.CreateGridEntity(mapId);
@@ -49,6 +50,6 @@ public sealed class MapGridMap_Tests
entManager.TickUpdate(0.016f, false);
});
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}

View File

@@ -20,10 +20,10 @@ public sealed class Fixtures_Test
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var sysManager = sim.Resolve<IEntitySystemManager>();
var fixturesSystem = sysManager.GetEntitySystem<FixtureSystem>();
var physicsSystem = sysManager.GetEntitySystem<SharedPhysicsSystem>();
var mapSystem = sysManager.GetEntitySystem<SharedMapSystem>();
var map = sim.CreateMap().MapId;
var ent = sim.SpawnEntity(null, new MapCoordinates(Vector2.Zero, map));
@@ -36,6 +36,6 @@ public sealed class Fixtures_Test
Assert.That(fixture.Density, Is.EqualTo(10f));
Assert.That(body.Mass, Is.EqualTo(10f));
mapManager.DeleteMap(map);
mapSystem.DeleteMap(map);
}
}

View File

@@ -24,8 +24,8 @@ public sealed class Joints_Test
var sim = factory.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var jointSystem = entManager.System<SharedJointSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = sim.CreateMap().MapId;
@@ -53,7 +53,7 @@ public sealed class Joints_Test
Assert.That(entManager.GetComponent<JointRelayTargetComponent>(uidC).Relayed, Is.Empty);
Assert.That(entManager.GetComponent<JointComponent>(uidA).Relay, Is.EqualTo(null));
});
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
/// <summary>
@@ -65,11 +65,11 @@ public sealed class Joints_Test
var factory = RobustServerSimulation.NewSimulation();
var server = factory.InitializeInstance();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
var fixtureSystem = entManager.EntitySysManager.GetEntitySystem<FixtureSystem>();
var jointSystem = entManager.EntitySysManager.GetEntitySystem<JointSystem>();
var broadphaseSystem = entManager.EntitySysManager.GetEntitySystem<SharedBroadphaseSystem>();
var physicsSystem = server.Resolve<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = server.CreateMap().MapId;
@@ -106,6 +106,6 @@ public sealed class Joints_Test
broadphaseSystem.FindNewContacts(mapId);
Assert.That(body1.Contacts, Has.Count.EqualTo(1));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}

View File

@@ -46,13 +46,13 @@ public sealed class RayCast_Test
// Polygon
// - Initial overlap, no shapecast
new(new Polygon(Box2.UnitCentered), new Transform(Vector2.UnitY / 2f, Angle.Zero), Vector2.UnitY, null),
new(new SlimPolygon(Box2.UnitCentered), new Transform(Vector2.UnitY / 2f, Angle.Zero), Vector2.UnitY, null),
// - Cast
new(new Polygon(Box2.UnitCentered), new Transform(Vector2.Zero, Angle.Zero), Vector2.UnitY, new Vector2(0.5f, 1f - PhysicsConstants.PolygonRadius)),
new(new SlimPolygon(Box2.UnitCentered), new Transform(Vector2.Zero, Angle.Zero), Vector2.UnitY, new Vector2(0.5f, 1f - PhysicsConstants.PolygonRadius)),
// - Miss
new(new Polygon(Box2.UnitCentered), new Transform(Vector2.Zero, Angle.Zero), -Vector2.UnitY, null),
new(new SlimPolygon(Box2.UnitCentered), new Transform(Vector2.Zero, Angle.Zero), -Vector2.UnitY, null),
};
[Test, TestCaseSource(nameof(_rayCases))]

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using NUnit.Framework;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision.Shapes;
namespace Robust.UnitTesting.Shared.Physics

View File

@@ -9,10 +9,23 @@ namespace Robust.UnitTesting.Shared.Physics;
[TestFixture]
public sealed class Polygon_Test
{
/// <summary>
/// Check that Slim and normal Polygon are equals
/// </summary>
[Test]
public void TestSlim()
{
var slim = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
var poly = new Polygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(slim.Equals(poly));
}
[Test]
public void TestAABB()
{
var shape = new Polygon(Box2.UnitCentered.Translated(Vector2.One));
var shape = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(shape.ComputeAABB(Transform.Empty, 0), Is.EqualTo(Box2.UnitCentered.Translated(Vector2.One)));
}
@@ -20,8 +33,8 @@ public sealed class Polygon_Test
[Test]
public void TestBox2()
{
var shape = new Polygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(shape.Vertices, Is.EqualTo(new Vector2[]
var shape = new SlimPolygon(Box2.UnitCentered.Translated(Vector2.One));
Assert.That(shape._vertices.AsSpan.ToArray(), Is.EqualTo(new Vector2[]
{
new Vector2(0.5f, 0.5f),
new Vector2(1.5f, 0.5f),
@@ -33,9 +46,9 @@ public sealed class Polygon_Test
[Test]
public void TestBox2Rotated()
{
var shape = new Polygon(new Box2Rotated(Box2.UnitCentered, Angle.FromDegrees(90)));
var shape = new SlimPolygon(new Box2Rotated(Box2.UnitCentered, Angle.FromDegrees(90)));
Assert.That(shape.Vertices, Is.EqualTo(new Vector2[]
Assert.That(shape._vertices.AsSpan.ToArray(), Is.EqualTo(new Vector2[]
{
new Vector2(0.5f, -0.5f),
new Vector2(0.5f, 0.5f),

View File

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

View File

@@ -20,10 +20,13 @@ public abstract partial class EntitySpawnHelpersTest : RobustIntegrationTest
{
protected ServerIntegrationInstance Server = default!;
protected IEntityManager EntMan = default!;
protected IMapManager MapMan = default!;
protected SharedMapSystem MapSys = default!;
protected SharedTransformSystem Xforms = default!;
protected SharedContainerSystem Container = default!;
// Even if unused, content / downstream tests might use this class, so removal would be a breaking change?
protected IMapManager MapMan = default!;
protected EntityUid Map;
protected MapId MapId;
protected EntityUid Parent; // entity parented to the map.
@@ -43,6 +46,7 @@ public abstract partial class EntitySpawnHelpersTest : RobustIntegrationTest
await Server.WaitIdleAsync();
MapMan = Server.ResolveDependency<IMapManager>();
EntMan = Server.ResolveDependency<IEntityManager>();
MapSys = EntMan.System<SharedMapSystem>();
Xforms = EntMan.System<SharedTransformSystem>();
Container = EntMan.System<SharedContainerSystem>();

View File

@@ -116,7 +116,7 @@ public sealed class SpawnInContainerOrDropTest : EntitySpawnHelpersTest
Assert.That(xform.GridUid, Is.Null);
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -121,7 +121,7 @@ public sealed class SpawnNextToOrDropTest : EntitySpawnHelpersTest
Assert.That(EntMan.GetComponent<MetaDataComponent>(uid).EntityLifeStage, Is.LessThan(EntityLifeStage.MapInitialized));
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -42,7 +42,7 @@ public sealed class TrySpawnInContainerTest : EntitySpawnHelpersTest
Assert.That(EntMan.EntityExists(uid), Is.False);
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -50,7 +50,7 @@ public sealed class TrySpawnNextToTest : EntitySpawnHelpersTest
Assert.That(EntMan.EntityExists(uid), Is.False);
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -197,21 +197,21 @@ public sealed class TestEnumerableCommand : ToolshedCommand
public sealed class TestNestedArrayCommand : ToolshedCommand
{
[CommandImplementation]
public ProtoId<EntityPrototype>[] Impl() => Array.Empty<ProtoId<EntityPrototype>>();
public ProtoId<EntityCategoryPrototype>[] Impl() => [];
}
[ToolshedCommand]
public sealed class TestNestedListCommand : ToolshedCommand
{
[CommandImplementation]
public List<ProtoId<EntityPrototype>> Impl() => new();
public List<ProtoId<EntityCategoryPrototype>> Impl() => new();
}
[ToolshedCommand]
public sealed class TestNestedEnumerableCommand : ToolshedCommand
{
private static ProtoId<EntityPrototype>[] _arr = Array.Empty<ProtoId<EntityPrototype>>();
private static ProtoId<EntityCategoryPrototype>[] _arr = [];
[CommandImplementation]
public IEnumerable<ProtoId<EntityPrototype>> Impl() => _arr.OrderByDescending(x => x.Id);
public IEnumerable<ProtoId<EntityCategoryPrototype>> Impl() => _arr.OrderByDescending(x => x.Id);
}

View File

@@ -383,11 +383,11 @@ public sealed class ToolshedTests : ToolshedTest
AssertResult("testenumerable testenumerableinfer 1", typeof(int));
// Repeat but with nested types. i.e. extracting T when piping ProtoId<T> -> IEnumerable<ProtoId<T>>
AssertResult("testnestedarray testnestedarrayinfer", typeof(EntityPrototype));
AssertResult("testnestedlist testnestedlistinfer", typeof(EntityPrototype));
AssertResult("testnestedarray testnestedenumerableinfer", typeof(EntityPrototype));
AssertResult("testnestedlist testnestedenumerableinfer", typeof(EntityPrototype));
AssertResult("testnestedenumerable testnestedenumerableinfer", typeof(EntityPrototype));
AssertResult("testnestedarray testnestedarrayinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedlist testnestedlistinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedarray testnestedenumerableinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedlist testnestedenumerableinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedenumerable testnestedenumerableinfer", typeof(EntityCategoryPrototype));
// The map command used to work when the piped type was passed as an IEnumerable<T> directly, but would fail
// when given a List<T> or something else that implemented the interface.