Compare commits

..

32 Commits

Author SHA1 Message Date
PJB3005
b422d3fb3e Version: 237.2.2 2025-09-19 09:17:39 +02:00
Skye
e4101aae8b Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:39 +02:00
PJB3005
74fe177985 Version: 237.2.1 2025-09-14 14:58:28 +02:00
PJB3005
bb90d79a3f 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)
(cherry picked from commit 4413695c77fb705054c2f81fa18ec0a189b685dd)
2025-09-14 14:58:27 +02:00
Pieter-Jan Briers
92b0e7f1a8 Version: 237.2.0 2024-11-21 00:03:19 +01:00
Pieter-Jan Briers
47d1c372b2 Mute null error from Rider 2024-11-20 23:45:47 +01:00
SpaceManiac
453f763128 Fix minor layout bugs in SplitContainer and BoxContainer (#5529)
* Fix SplitContainer using invalidated measures when clamping SplitCenter

* Fix BoxContainer adding separation for invisible children
2024-11-20 23:41:10 +01:00
Nikolai Korolev
65a7942d63 Remove unused variable, local function and private field (#5528)
* Remove unused local function

* Remove unused variable

* Remove private field
2024-11-20 02:51:46 +01:00
Saphire Lattice
f1f3c60d1f Improve Toolshed type intersection mechanism, add WithCommand for ProtoId (#5515) 2024-11-20 01:00:13 +01:00
SpaceManiac
d4e8a27c23 Add FormattedMessage.TrimEnd (#5524)
* Add FormattedMessage.TrimTrailingNewlines

* Trim all trailing whitespace
2024-11-19 19:55:19 +01:00
Pieter-Jan Briers
18a17da8fa .NET 9 forward compatibility changes
This doesn't switch the projects over to .NET 9, but it does make them work on .NET 9 when we decide to switch in the future.
2024-11-19 19:54:01 +01:00
Nikolai Korolev
90a8c66e96 Fix System.ArgumentException: '0' cannot be greater than -0.01 for very fast audios (#5521)
* Fix `System.ArgumentException: '0' cannot be greater than -0.01`

In case of playing empty audio files

* Add semicolon
2024-11-18 18:46:01 +01:00
MilenVolf
45c14b2bc3 Replace remaining obsolete TileAccess methods (#5519)
* Replace remaining TileAccess methods

* Small fix
2024-11-18 17:19:57 +01:00
SpaceManiac
d227613997 Fix cursor getting stuck when click-dragging off of a control (#5523) 2024-11-16 23:51:32 +01:00
Partmedia
7557cc703c Add FreeBSD packaging target (#5522) 2024-11-16 02:01:17 +01:00
Leon Friedrich
7b81d0d881 Make PVS ignore duplicate view subscriber (#5502) 2024-11-13 00:07:53 +01:00
Leon Friedrich
b59f7801ac More UniqueIndex fixes (#5501) 2024-11-12 23:40:01 +01:00
FluffMe
d724c5b3eb Add conditional formatting to SpinBox buttons text (#5511) 2024-11-12 23:32:14 +01:00
Saphire Lattice
f812dc4dac Hopefully fix the dreaded VV refresh blink (#5517) 2024-11-12 23:31:39 +01:00
MilenVolf
2a1bcb6f1e Replace some obsolete TileAccess methods (#5516)
* Replace some obsolete TileAccess methods

* Guh
2024-11-12 23:17:40 +01:00
slarticodefast
fa9030e59c correct sandbox whitelist for Regex.Matches Method (#5513) 2024-11-12 21:48:39 +01:00
Pieter-Jan Briers
8dcae8631b Update NetSerializer
This enables sending of ImmutableArray<T>
2024-11-11 21:36:41 +01:00
Pieter-Jan Briers
21c3535486 Avoid unhandled exception handlers logging into disposed sawmills on shutdown
This caused a crash & exception swallow on Salamander.
2024-11-11 16:26:04 +01:00
lzk
4e100d96bc Add dative case function to loc manager (#5510)
* dative

* slipped it

* slipped it twice
2024-11-05 19:55:30 +01:00
qrtDaniil
14d3699ae2 Fix for server consoles without width and length (#5507)
* Update SystemConsoleManager.cs

* Update SystemConsoleManager.cs
2024-11-01 23:39:30 +01:00
Amy
350fa8736d add ref readonly to sandbox (#5506) 2024-10-30 02:49:25 +01:00
eoineoineoin
5a82df216d Fixes for rendering in multiple windows (#5497)
* Fix race condition when swapping buffers of secondary windows

* Avoid creating opengl 3.3 windows, to avoid Steam overlay bug

* Revert "Avoid creating opengl 3.3 windows, to avoid Steam overlay bug"

This reverts commit 97b5e7f461.

* Add CVar to perform unlocking test
2024-10-19 16:13:29 +02:00
Pieter-Jan Briers
32bca7cfd4 Version: 237.1.0 2024-10-19 12:03:52 +02:00
wixoa
008babebc6 Fix some window UIScale bugs (#5499)
* Fix some window UIScale bugs

* Use CalculateAutoScale()
2024-10-19 00:08:16 +02:00
Pieter-Jan Briers
c65c4ba57e Made csi reflection helpers get members up the inheritance chain too 2024-10-18 18:40:39 +02:00
Pieter-Jan Briers
eb5b838e61 Made csi type auto-completion aware of generic types 2024-10-18 18:40:39 +02:00
Pieter-Jan Briers
6b43036c9d Fix UniqueIndexHkm memory leaking
Yeah that's just great this goddamn data structure had no damn API to ever remove anything from it. Incredible.
2024-10-18 18:40:39 +02:00
60 changed files with 426 additions and 189 deletions

View File

@@ -58,7 +58,7 @@
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="System.Numerics.Vectors" Version="4.5.0" />

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,59 @@ END TEMPLATE-->
*None yet*
## 237.2.2
## 237.2.1
## 237.2.0
### Breaking changes
* `SharedEyeSystem..SetTarget()` will now also automatically remove the old target from the session's ViewSubscriptions
### New features
* `ImmutableArray<T>` can now be serialized by `RobustSerializer`.
* `RequiresLocationAttribute`, used by `ref readonly`, is now allowed by the sandbox.
* Added `DAT-OBJ()` localization function, for the dative case in certain languages.
* Client builds for FreeBSD are now made.
* Added `FormattedMessage.TrimEnd()`.
* Added Toolshed `with` for `ProtoId<T>`.
### Bugfixes
* Fix `UniqueIndex<,>.RemoveRange()` and`UniqueIndexHkm<,>.RemoveRange()` clearing the whole set instead of just removing the specified values.
* Avoid server crashes on some weird console setups (notably Pterodactyl).
* Avoid unhandled exceptions during server shutdown getting swallowed due logging into a disposed logger.
* Fix sandbox definitions for `Regex` functions returning `MatchCollection`.
* Fix minor layout bugs with `SplitContainer` and `BoxContainer`.
### Other
* Changed how multi-window rendering presents to the screen with a new CVar `display.thread_unlock_before_swap`. This is an experiment to see if it solves some synchronization issues.
* View Variables no longer clears the window on refresh while waiting on response from server.
* `SpinBox` buttons now have a `+` prefix for the positive ones.
* Improve Toolshed type intersection mechanism
### Internal
* Warning cleanup.
## 237.1.0
### New features
* csi's auto import-system can now handle generic types.
* csi's reflection helpers (like `fld()`) handle private members up the inheritance chain.
### Bugfixes
* Fix `UniqueIndexHkm<,>` and, by extension, entity data storage memory leaking.
* Fix bugs related to UIScale on `OSWindow`s.
## 237.0.0
### Breaking changes

View File

@@ -20,6 +20,15 @@ zzzz-object-pronoun = { GENDER($ent) ->
*[neuter] it
}
# Used internally by the DAT-OBJ() function.
# Not used in en-US. Created for supporting other languages.
zzzz-dat-object = { GENDER($ent) ->
[male] him
[female] her
[epicene] them
*[neuter] it
}
# Used internally by the POSS-PRONOUN() function.
zzzz-possessive-pronoun = { GENDER($ent) ->
[male] his

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

@@ -669,7 +669,8 @@ public sealed partial class AudioSystem : SharedAudioSystem
// TODO clamp the offset inside of SetPlaybackPosition() itself.
var offset = audioP.PlayOffsetSeconds;
offset = Math.Clamp(offset, 0f, (float) stream.Length.TotalSeconds - 0.01f);
var maxOffset = Math.Max((float) stream.Length.TotalSeconds - 0.01f, 0f);
offset = Math.Clamp(offset, 0f, maxOffset);
source.PlaybackPosition = offset;
// For server we will rely on the adjusted one but locally we will have to adjust it ourselves.

View File

@@ -382,7 +382,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

@@ -46,7 +46,6 @@ namespace Robust.Client.GameStates
// sum of all data point sizes in bytes
private int _totalHistoryPayload;
private int _totalUncompressed;
public EntityUid WatchEntId { get; set; }

View File

@@ -343,6 +343,8 @@ namespace Robust.Client.Graphics.Clyde
if (isMain)
_mainWindow = reg;
reg.IsVisible = parameters.Visible;
_windows.Add(reg);
_windowHandles.Add(reg.Handle);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Channels;
@@ -176,6 +176,7 @@ namespace Robust.Client.Graphics.Clyde
window.BlitDoneEvent!.Reset();
window.BlitStartEvent!.Set();
window.BlitDoneEvent.Wait();
window.UnlockBeforeSwap = Clyde._cfg.GetCVar(CVars.DisplayThreadUnlockBeforeSwap);
}
}
else
@@ -212,8 +213,15 @@ namespace Robust.Client.Graphics.Clyde
GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
Clyde.CheckGlError();
window.BlitDoneEvent?.Set();
if (window.UnlockBeforeSwap)
{
window.BlitDoneEvent?.Set();
}
Clyde._windowing!.WindowSwapBuffers(window.Reg);
if (!window.UnlockBeforeSwap)
{
window.BlitDoneEvent?.Set();
}
}
private unsafe void BlitThreadInit(WindowData reg)
@@ -336,6 +344,7 @@ namespace Robust.Client.Graphics.Clyde
public Thread? BlitThread;
public ManualResetEventSlim? BlitStartEvent;
public ManualResetEventSlim? BlitDoneEvent;
public bool UnlockBeforeSwap;
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
@@ -14,16 +15,16 @@ namespace Robust.Client.Placement.Modes
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
{
// Go over diagonal size so when placing in a line it doesn't stop snapping.
const float SearchBoxSize = 2f; // size of search box in meters
const float searchBoxSize = 2f; // size of search box in meters
MouseCoords = ScreenToCursorGrid(mouseScreen).AlignWithClosestGridTile(SearchBoxSize, pManager.EntityManager, pManager.MapManager);
MouseCoords = ScreenToCursorGrid(mouseScreen).AlignWithClosestGridTile(searchBoxSize, pManager.EntityManager, pManager.MapManager);
var gridId = MouseCoords.GetGridUid(pManager.EntityManager);
var gridId = pManager.EntityManager.System<SharedTransformSystem>().GetGrid(MouseCoords);
if (!pManager.EntityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
return;
CurrentTile = mapGrid.GetTileRef(MouseCoords);
CurrentTile = pManager.EntityManager.System<SharedMapSystem>().GetTileRef(gridId.Value, mapGrid, MouseCoords);
float tileSize = mapGrid.TileSize; //convert from ushort to float
GridDistancing = tileSize;

View File

@@ -1,6 +1,5 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
@@ -19,12 +18,12 @@ namespace Robust.Client.Placement.Modes
MouseCoords = ScreenToCursorGrid(mouseScreen);
var tileSize = 1f;
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
var gridIdOpt = pManager.EntityManager.System<SharedTransformSystem>().GetGrid(MouseCoords);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
if (gridIdOpt is { } gridId && gridId.IsValid())
{
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
CurrentTile = pManager.EntityManager.System<SharedMapSystem>().GetTileRef(gridId, mapGrid ,MouseCoords);
tileSize = mapGrid.TileSize; //convert from ushort to float
}
@@ -50,12 +49,12 @@ namespace Robust.Client.Placement.Modes
return false;
}
var map = MouseCoords.GetMapId(pManager.EntityManager);
var map = pManager.EntityManager.System<SharedTransformSystem>().GetMapId(MouseCoords);
var bottomLeft = new Vector2(CurrentTile.X, CurrentTile.Y);
var topRight = new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f);
var box = new Box2(bottomLeft, topRight);
return !EntitySystem.Get<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
return !pManager.EntityManager.System<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
}
}
}

View File

@@ -754,14 +754,14 @@ namespace Robust.Client.Placement
if (CurrentPermission.IsTile)
{
var gridIdOpt = coordinates.GetGridUid(EntityManager);
var gridIdOpt = EntityManager.System<SharedTransformSystem>().GetGrid(coordinates);
// If we have actually placed something on a valid grid...
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
if (gridIdOpt is { } gridId && gridId.IsValid())
{
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);
// no point changing the tile to the same thing.
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)
if (EntityManager.System<SharedMapSystem>().GetTileRef(gridId, grid, coordinates).Tile.TypeId == CurrentPermission.TileType)
return;
}

View File

@@ -197,8 +197,9 @@ namespace Robust.Client.Placement
/// </summary>
public TileRef GetTileRef(EntityCoordinates coordinates)
{
var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager);
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent<MapGridComponent>(gridUid).GetTileRef(MouseCoords)
var gridUidOpt = pManager.EntityManager.System<SharedTransformSystem>().GetGrid(coordinates);
return gridUidOpt is { } gridUid && gridUid.IsValid()
? pManager.EntityManager.System<SharedMapSystem>().GetTileRef(gridUid, pManager.EntityManager.GetComponent<MapGridComponent>(gridUid), MouseCoords)
: new TileRef(gridUidOpt ?? EntityUid.Invalid,
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager, pManager.EntityManager.System<SharedTransformSystem>()), Tile.Empty);
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Robust.Shared.Maths;
@@ -56,7 +57,7 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 MeasureOverride(Vector2 availableSize)
{
// Account for separation.
var separation = ActualSeparation * (ChildCount - 1);
var separation = ActualSeparation * (Children.Where(c => c.Visible).Count() - 1);
var desiredSize = Vector2.Zero;
if (Vertical)
{
@@ -136,13 +137,14 @@ namespace Robust.Client.UserInterface.Controls
protected override Vector2 ArrangeOverride(Vector2 finalSize)
{
var separation = ActualSeparation;
var visibleChildCount = Children.Where(c => c.Visible).Count();
var stretchAvail = Vertical ? finalSize.Y : finalSize.X;
stretchAvail -= separation * (ChildCount - 1);
stretchAvail -= separation * (visibleChildCount - 1);
stretchAvail = Math.Max(0, stretchAvail);
// Step one: figure out the sizes of all our children and whether they want to stretch.
var sizeList = new List<(Control control, float size, bool stretch)>(ChildCount);
var sizeList = new List<(Control control, float size, bool stretch)>(visibleChildCount);
var totalStretchRatio = 0f;
foreach (var child in Children)
{

View File

@@ -1,6 +1,5 @@
using System;
using System.ComponentModel;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
@@ -138,6 +137,8 @@ namespace Robust.Client.UserInterface.Controls
_root = UserInterfaceManager.CreateWindowRoot(ClydeWindow);
_root.AddChild(this);
// Resize the window by our UIScale
ClydeWindow.Size = new((int)(ClydeWindow.Size.X * UIScale), (int)(ClydeWindow.Size.Y * UIScale));
return ClydeWindow;
}
@@ -192,7 +193,7 @@ namespace Robust.Client.UserInterface.Controls
private void OnWindowResized(WindowResizedEventArgs obj)
{
SetSize = obj.NewSize;
SetSize = obj.NewSize / UIScale;
}
private void RealClosed()

View File

@@ -127,11 +127,11 @@ namespace Robust.Client.UserInterface.Controls
ClearButtons();
foreach (var num in leftButtons)
{
AddLeftButton(num, num.ToString());
AddLeftButton(num, num.ToString("+#;-#;0"));
}
foreach (var num in rightButtons)
{
AddRightButton(num, num.ToString());
AddRightButton(num, num.ToString("+#;-#;0"));
}
}

View File

@@ -268,12 +268,15 @@ namespace Robust.Client.UserInterface.Controls
var first = GetChild(0);
var second = GetChild(1);
firstMinSize ??= (Vertical ? first.DesiredSize.Y : first.DesiredSize.X);
secondMinSize ??= (Vertical ? second.DesiredSize.Y : second.DesiredSize.X);
var size = Vertical ? controlSize.Y : controlSize.X;
if (first.IsMeasureValid && second.IsMeasureValid)
{
firstMinSize ??= (Vertical ? first.DesiredSize.Y : first.DesiredSize.X);
secondMinSize ??= (Vertical ? second.DesiredSize.Y : second.DesiredSize.X);
var size = Vertical ? controlSize.Y : controlSize.X;
_splitStart = MathHelper.Clamp(_splitStart, firstMinSize.Value,
size - (secondMinSize.Value + _splitWidth));
_splitStart = MathHelper.Clamp(_splitStart, firstMinSize.Value,
size - (secondMinSize.Value + _splitWidth));
}
}
}

View File

@@ -131,11 +131,6 @@ internal sealed class TextEditRopeViz : OSWindow
throw new ArgumentOutOfRangeException(nameof(node));
}
}
static UIBox2 Around(Vector2 vec, float size)
{
return new UIBox2(vec - new Vector2(size, size), vec + new Vector2(size, size));
}
}
private static Color[] CalcLeafColors()

View File

@@ -35,6 +35,7 @@ internal partial class UserInterfaceManager
return;
_controlFocused?.ControlFocusExited();
_controlFocused = value;
_needUpdateActiveCursor = true;
}
}

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared;
using Robust.Shared.Map;
using Robust.Shared.Utility;
@@ -28,10 +29,11 @@ internal sealed partial class UserInterfaceManager
{
MouseFilter = Control.MouseFilterMode.Ignore,
HorizontalAlignment = Control.HAlignment.Stretch,
VerticalAlignment = Control.VAlignment.Stretch,
UIScaleSet = window.ContentScale.X
VerticalAlignment = Control.VAlignment.Stretch
};
newRoot.UIScaleSet = CalculateAutoScale(newRoot);
_roots.Add(newRoot);
_windowsToRoot.Add(window.Id, newRoot);

View File

@@ -1,4 +1,5 @@
using System.Numerics;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.ViewVariables.Instances;
@@ -36,7 +37,7 @@ namespace Robust.Client.ViewVariables.Traits
public override async void Refresh()
{
_memberList.DisposeAllChildren();
List<Control> replacementControls = [];
if (Instance.Object != null)
{
@@ -51,7 +52,7 @@ namespace Robust.Client.ViewVariables.Traits
foreach (var control in group)
{
_memberList.AddChild(control);
replacementControls.Add(control);
}
}
}
@@ -82,10 +83,16 @@ namespace Robust.Client.ViewVariables.Traits
selectorChain, o, r);
};
_memberList.AddChild(propertyEdit);
replacementControls.Add(propertyEdit);
}
}
}
_memberList.DisposeAllChildren();
foreach (var item in replacementControls)
{
_memberList.AddChild(item);
}
}
internal static void CreateMemberGroupHeader(ref bool first, string groupName, Control container)

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

@@ -174,7 +174,8 @@ namespace Robust.Server.Console
while (Con.KeyAvailable)
{
ConsoleKeyInfo key = Con.ReadKey(true);
Con.SetCursorPosition(0, Con.CursorTop);
if (Con.WindowWidth > 0)
Con.SetCursorPosition(0, Con.CursorTop);
if (!Char.IsControl(key.KeyChar))
{
currentBuffer = currentBuffer.Insert(internalCursor++, key.KeyChar.ToString());
@@ -277,6 +278,7 @@ namespace Robust.Server.Console
public void DrawCommandLine()
{
if (Con.WindowWidth <= 0) return;
ClearCurrentLine();
Con.SetCursorPosition(0, Con.CursorTop);
Con.Write("> " + currentBuffer);

View File

@@ -184,10 +184,9 @@ internal sealed partial class PvsSystem
return;
}
int i = 0;
var i = 0;
if (session.AttachedEntity is { } local)
{
DebugTools.Assert(!session.ViewSubscriptions.Contains(local));
Array.Resize(ref pvsSession.Viewers, session.ViewSubscriptions.Count + 1);
pvsSession.Viewers[i++] = (local, Transform(local), _eyeQuery.CompOrNull(local));
}
@@ -198,7 +197,8 @@ internal sealed partial class PvsSystem
foreach (var ent in session.ViewSubscriptions)
{
pvsSession.Viewers[i++] = (ent, Transform(ent), _eyeQuery.CompOrNull(ent));
if (ent != session.AttachedEntity)
pvsSession.Viewers[i++] = (ent, Transform(ent), _eyeQuery.CompOrNull(ent));
}
}
@@ -241,7 +241,7 @@ internal sealed partial class PvsSystem
{
chunk.Initialize(location, _metaQuery, _xformQuery);
}
catch (Exception e)
catch (Exception)
{
_chunks.Remove(location);
throw;

View File

@@ -126,13 +126,29 @@ namespace Robust.Server
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
var message = ((Exception) args.ExceptionObject).ToString();
uh.Log(args.IsTerminating ? LogLevel.Fatal : LogLevel.Error, message);
try
{
uh.Log(args.IsTerminating ? LogLevel.Fatal : LogLevel.Error, message);
}
catch (ObjectDisposedException)
{
// Avoid eating the exception if it's during shutdown and the sawmill is already gone.
System.Console.WriteLine($"UnhandledException but sawmill is disposed! {message}");
}
};
var uo = mgr.GetSawmill("unobserved");
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
uo.Error(args.Exception!.ToString());
try
{
uo.Error(args.Exception!.ToString());
}
catch (ObjectDisposedException)
{
// Avoid eating the exception if it's during shutdown and the sawmill is already gone.
System.Console.WriteLine($"UnobservedTaskException but sawmill is disposed! {args.Exception}");
}
#if EXCEPTION_TOLERANCE
args.SetObserved(); // don't crash
#endif

View File

@@ -87,33 +87,33 @@ namespace Robust.Shared.Scripting
public object? prop(object target, string name)
{
return target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic)
!.GetValue(target);
var prop = (PropertyInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Property, name);
return prop!.GetValue(target);
}
public void setprop(object target, string name, object? value)
{
target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
!.SetValue(target, value);
var prop = (PropertyInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Property, name);
prop!.SetValue(target, value);
}
public object? fld(object target, string name)
{
return target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
!.GetValue(target);
var fld = (FieldInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Field, name);
return fld!.GetValue(target);
}
public void setfld(object target, string name, object? value)
{
target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
!.SetValue(target, value);
var fld = (FieldInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Field, name);
fld!.SetValue(target, value);
}
public object? call(object target, string name, params object[] args)
{
var t = target.GetType();
// TODO: overloads
var m = t.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var m = (MethodInfo?) ReflectionGetInstanceMember(t, MemberTypes.Method, name);
return m!.Invoke(target, args);
}
@@ -287,5 +287,21 @@ namespace Robust.Shared.Scripting
}
public Dictionary<string, object?> Variables { get; } = new();
private static MemberInfo? ReflectionGetInstanceMember(Type type, MemberTypes memberType, string name)
{
for (var curType = type; curType != null; curType = curType.BaseType)
{
var member = curType.GetMember(
name,
memberType,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (member.Length > 0)
return member[0];
}
return null;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Lidgren.Network;
using Microsoft.CodeAnalysis;
@@ -14,14 +15,13 @@ using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Text;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Scripting
{
internal static class ScriptInstanceShared
internal static partial class ScriptInstanceShared
{
public static CSharpParseOptions ParseOptions { get; } =
new(kind: SourceCodeKind.Script, languageVersion: LanguageVersion.Latest);
@@ -186,11 +186,12 @@ namespace Robust.Shared.Scripting
var assemblies = ScriptInstanceShared.GetAutoImportAssemblies(refl).ToArray();
foreach (var m in missing)
{
var mName = ConvertMissingTypeName(m);
foreach (var assembly in assemblies)
{
foreach (var type in assembly.DefinedTypes)
{
if (type.IsPublic && type.Name == m)
if (type.IsPublic && type.Name == mName)
{
found.Add(type.Namespace!);
goto nextMissing;
@@ -225,5 +226,22 @@ namespace Robust.Shared.Scripting
return "<CSharpObjectFormatter.FormatObject threw>";
}
}
private static string ConvertMissingTypeName(string name)
{
var match = TypeMissingParserRegex().Match(name);
var typeName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
// We have generics
var genericCount = match.Groups[2].Length + 1;
return $"{typeName}`{genericCount}";
}
return match.Groups[1].Value;
}
[GeneratedRegex("^(.+?)(?:<(,*)>)?$")]
private static partial Regex TypeMissingParserRegex();
}
}

View File

@@ -1105,6 +1105,14 @@ namespace Robust.Shared
public static readonly CVarDef<bool> DisplayThreadWindowBlit =
CVarDef.Create("display.thread_window_blit", true, CVar.CLIENTONLY);
/// <summary>
/// Diagnostic flag for testing. When using a separate thread for multi-window blitting,
/// should the worker be unblocked before the SwapBuffers(). Setting to true may improve
/// performance but may cause crashes or rendering errors.
/// </summary>
public static readonly CVarDef<bool> DisplayThreadUnlockBeforeSwap =
CVarDef.Create("display.thread_unlock_before_swap", false, CVar.CLIENTONLY);
/// <summary>
/// Buffer size of input command channel from windowing thread to main game thread.
/// </summary>

View File

@@ -60,7 +60,7 @@ namespace Robust.Shared.ContentPack
internal string GetPath(ResPath relPath)
{
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()));
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
}
/// <inheritdoc />

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
{
@@ -379,7 +379,13 @@ namespace Robust.Shared.ContentPack
{
if (root is DirLoader loader)
{
yield return new ResPath(loader.GetPath(new ResPath(@"/")));
var rootDir = loader.GetPath(new ResPath(@"/"));
// TODO: GET RID OF THIS.
// This code shouldn't be passing OS disk paths through ResPath.
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
yield return new ResPath(rootDir);
}
}
}

View File

@@ -654,6 +654,7 @@ Types:
PreserveBaseOverridesAttribute: { All: True }
RefSafetyRulesAttribute: { All: True }
RequiredMemberAttribute: { All: True }
RequiresLocationAttribute: { All: True }
RuntimeCompatibilityAttribute: { All: True }
RuntimeHelpers: { All: True }
SwitchExpressionException: { All: True }
@@ -722,11 +723,11 @@ Types:
- "System.Text.RegularExpressions.Match Match(string, string)"
- "System.Text.RegularExpressions.Match Match(string, string, System.Text.RegularExpressions.RegexOptions)"
- "System.Text.RegularExpressions.Match Match(string, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
- "System.Text.RegularExpressions.Match[] Matches(string)"
- "System.Text.RegularExpressions.Match[] Matches(string, int)"
- "System.Text.RegularExpressions.Match[] Matches(string, string)"
- "System.Text.RegularExpressions.Match[] Matches(string, string, System.Text.RegularExpressions.RegexOptions)"
- "System.Text.RegularExpressions.Match[] Matches(string, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
- "System.Text.RegularExpressions.MatchCollection Matches(string)"
- "System.Text.RegularExpressions.MatchCollection Matches(string, int)"
- "System.Text.RegularExpressions.MatchCollection Matches(string, string)"
- "System.Text.RegularExpressions.MatchCollection Matches(string, string, System.Text.RegularExpressions.RegexOptions)"
- "System.Text.RegularExpressions.MatchCollection Matches(string, string, System.Text.RegularExpressions.RegexOptions, System.TimeSpan)"
- "System.Text.RegularExpressions.RegexOptions get_Options()"
- "System.TimeSpan get_MatchTimeout()"
- "void .ctor()"

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

@@ -120,14 +120,10 @@ public abstract class SharedEyeSystem : EntitySystem
if (TryComp(uid, out ActorComponent? actorComp))
{
if (value != null)
{
_views.AddViewSubscriber(value.Value, actorComp.PlayerSession);
}
else
{
// Should never be null here
_views.RemoveViewSubscriber(eyeComponent.Target!.Value, actorComp.PlayerSession);
}
if (eyeComponent.Target is { } old)
_views.RemoveViewSubscriber(old, actorComp.PlayerSession);
}
eyeComponent.Target = value;

View File

@@ -1064,8 +1064,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
/// </summary>
private record struct ActorRangeCheckJob() : IParallelRobustJob
{
public EntityQuery<TransformComponent> XformQuery;
public SharedUserInterfaceSystem System;
public required EntityQuery<TransformComponent> XformQuery;
public required SharedUserInterfaceSystem System;
public readonly List<(EntityUid Ui, Enum Key, InterfaceData Data, EntityUid Actor, bool Result)> ActorRanges = new();
public void Execute(int index)

View File

@@ -22,6 +22,7 @@ namespace Robust.Shared.Localization
AddCtxFunction(bundle, "GENDER", FuncGender);
AddCtxFunction(bundle, "SUBJECT", FuncSubject);
AddCtxFunction(bundle, "OBJECT", FuncObject);
AddCtxFunction(bundle, "DAT-OBJ", FuncDatObj);
AddCtxFunction(bundle, "POSS-ADJ", FuncPossAdj);
AddCtxFunction(bundle, "POSS-PRONOUN", FuncPossPronoun);
AddCtxFunction(bundle, "REFLEXIVE", FuncReflexive);
@@ -203,6 +204,16 @@ namespace Robust.Shared.Localization
return new LocValueString(GetString("zzzz-object-pronoun", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the dative form pronoun for the entity's gender.
/// This method is intended for languages with a dative case, where indirect objects
/// (e.g., "to him," "for her") require specific forms. Not applicable for en-US locale.
/// </summary>
private ILocValue FuncDatObj(LocArgs args)
{
return new LocValueString(GetString("zzzz-dat-object", ("ent", args.Args[0])));
}
/// <summary>
/// Returns the respective possessive adjective (his, her, their, its) for the entity's gender.
/// </summary>

View File

@@ -67,10 +67,11 @@ public sealed class CommandInvocationContextAttribute : Attribute
}
/// <summary>
/// Marks a command implementation as taking the type of the previous command in sequence as a generic argument.
/// Marks a command implementation as taking the type of the previous command in sequence as a generic argument. Supports only one generic type.
/// </summary>
/// <remarks>
/// If the argument marked with <see cref="PipedArgumentAttribute"/> is not <c>T</c> but instead a pattern like <c>IEnumerable&lt;T&gt;</c>, Toolshed will account for this.
/// If the argument marked with <see cref="PipedArgumentAttribute"/> is not <c>T</c> but instead a pattern like <c>IEnumerable&lt;T&gt;</c>,
/// Toolshed will account for this by using <see cref="ReflectionExtensions.IntersectWithGeneric"/>. It's not very precise.
/// </remarks>
[AttributeUsage(AttributeTargets.Method)]
public sealed class TakesPipedTypeAsGenericAttribute : Attribute

View File

@@ -11,6 +11,7 @@ namespace Robust.Shared.Toolshed.Commands.Entities;
internal sealed class WithCommand : ToolshedCommand
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[CommandImplementation]
public IEnumerable<EntityUid> With(
@@ -32,4 +33,14 @@ internal sealed class WithCommand : ToolshedCommand
var name = _componentFactory.GetComponentName(ty.Ty);
return input.Where(x => x.Components.ContainsKey(name) ^ inverted);
}
[CommandImplementation, TakesPipedTypeAsGeneric]
public IEnumerable<ProtoId<T>> With<T>(
[PipedArgument] IEnumerable<ProtoId<T>> input,
[CommandArgument] ProtoId<T> protoId,
[CommandInverted] bool inverted
) where T : class, IPrototype
{
return input.Where(x => (x == protoId) ^ inverted);
}
}

View File

@@ -5,8 +5,12 @@ namespace Robust.Shared.Toolshed.Commands.Generic;
[ToolshedCommand]
public sealed class AsCommand : ToolshedCommand
{
public override Type[] TypeParameterParsers => new[] {typeof(Type)};
public override Type[] TypeParameterParsers => [ typeof(Type) ];
/// <summary>
/// Uses a typecast to convert a type. It does not handle implicit casts, nor explicit ones.
/// If you're thinking to extend this, you probably want to look into making a <see cref="TypeParser{T}"/>
/// </summary>
[CommandImplementation, TakesPipedTypeAsGeneric]
public TOut? As<TOut, TIn>([PipedArgument] TIn value)
=> (TOut?)(object?)value;

View File

@@ -159,18 +159,6 @@ internal static class ReflectionExtensions
throw new NotImplementedException();
}
// IEnumerable<EntityUid> ^ IEnumerable<T> -> EntityUid
public static Type Intersect(this Type left, Type right)
{
if (!left.IsGenericType)
return left;
if (!right.IsGenericType)
return left;
return left.GetGenericArguments().First();
}
public static void DumpGenericInfo(this Type t)
{
Logger.Debug($"Info for {t.PrettyName()}");
@@ -187,9 +175,18 @@ internal static class ReflectionExtensions
}
public static bool IsAssignableToGeneric(this Type left, Type right, ToolshedManager toolshed, bool recursiveDescent = true)
{
return left.IntersectWithGeneric(right, toolshed, recursiveDescent) is not null;
}
/// <summary>
/// Hopefully allows to figure out all the relevant type arguments when intersecting concrete with a generic one. Returns null if no intersection is possible
/// Pseudocode: <c>IEnumerable&lt;EntityUid&gt; ^ IEnumerable&lt;T&gt; -&gt; [EntityUid]</c>
/// </summary>
public static Type[]? IntersectWithGeneric(this Type left, Type right, ToolshedManager toolshed, bool recursiveDescent)
{
if (left.IsAssignableTo(right))
return true;
return [left];
if (right.IsInterface && !left.IsInterface)
{
@@ -197,15 +194,15 @@ internal static class ReflectionExtensions
{
if (right.GetMostGenericPossible() != i.GetMostGenericPossible())
continue;
if (right.IsAssignableToGeneric(i, toolshed, recursiveDescent))
return true;
if (right.IntersectWithGeneric(i, toolshed, recursiveDescent) is var outType && outType is not null)
return outType;
}
}
if (left.Constructable() && right.IsGenericParameter)
{
// TODO: We need a constraint solver and a general overhaul of how toolshed constructs implementations.
return true;
return [left];
}
if (left.IsGenericType && right.IsGenericType && left.GenericTypeArguments.Length == right.GenericTypeArguments.Length)
@@ -215,10 +212,11 @@ internal static class ReflectionExtensions
if (!equal)
goto next;
var res = true;
Type[]? res = null;
foreach (var (leftTy, rightTy) in left.GenericTypeArguments.Zip(right.GenericTypeArguments))
{
res &= leftTy.IsAssignableToGeneric(rightTy, toolshed, false);
if (leftTy.IntersectWithGeneric(rightTy, toolshed, false) is var outType && outType is not null)
res = [ .. res ?? [], .. outType ];
}
return res;
@@ -229,14 +227,14 @@ internal static class ReflectionExtensions
{
foreach (var leftSubTy in toolshed.AllSteppedTypes(left))
{
if (leftSubTy.IsAssignableToGeneric(right, toolshed, false))
if (leftSubTy.IntersectWithGeneric(right, toolshed, false) is var outType && outType is not null)
{
return true;
return outType;
}
}
}
return false;
return null;
}
public static bool IsGenericRelated(this Type t)

View File

@@ -29,6 +29,9 @@ public abstract partial class ToolshedCommand
return false;
}
/// <summary>
/// Does its best to find an implementation that can deal with the given types
/// </summary>
internal List<MethodInfo> GetConcreteImplementations(Type? pipedType, Type[] typeArguments,
string? subCommand)
{
@@ -97,8 +100,8 @@ public abstract partial class ToolshedCommand
if (x.HasCustomAttribute<TakesPipedTypeAsGenericAttribute>())
{
var paramT = x.ConsoleGetPipedArgument()!.ParameterType;
var t = pipedType!.Intersect(paramT);
return x.MakeGenericMethod(typeArguments.Append(t).ToArray());
var t = pipedType!.IntersectWithGeneric(paramT, Toolshed, true);
return x.MakeGenericMethod([.. typeArguments, .. t!]);
}
else
return x.MakeGenericMethod(typeArguments);

View File

@@ -182,6 +182,31 @@ public sealed partial class FormattedMessage : IReadOnlyList<MarkupNode>
AddText("\n");
}
/// <summary>
/// Removes extraneous whitespace from the end of the message.
/// </summary>
public void TrimEnd()
{
while (_nodes.Count > 1)
{
var last = _nodes[^1];
if (last.Name == null && last.Value.TryGetString(out var text))
{
string trimmed = text.TrimEnd();
if (trimmed.Length == 0)
{
_nodes.Pop();
continue;
}
else if (trimmed != text)
{
_nodes[^1] = new MarkupNode(trimmed);
}
}
break;
}
}
/// <summary>
/// Adds a new open node to the formatted message.
/// The method for inserting closed nodes: <see cref="Pop"/>. It needs to be

View File

@@ -87,7 +87,7 @@ namespace Robust.Shared.Utility
return false;
}
var newIndex = _index.SetItem(key, new HashSet<TValue>());
var newIndex = _index.Remove(key);
if (_index != newIndex)
{
@@ -126,7 +126,7 @@ namespace Robust.Shared.Utility
var c = set.Count;
set.ExceptWith(set);
set.ExceptWith(values);
return c - set.Count;
}

View File

@@ -70,13 +70,7 @@ namespace Robust.Shared.Utility
{
InitializedCheck();
var c = _index.Count;
if (c == 0) return false;
_index[key] = new HashSet<TValue>();
return c > _index.Count;
return _index.Remove(key);
}
/// <inheritdoc />
@@ -95,14 +89,11 @@ namespace Robust.Shared.Utility
{
InitializedCheck();
if (_index.Count == 0) return 0;
if (!_index.TryGetValue(key, out var set)) return 0;
if (!_index.TryGetValue(key, out var set))
return 0;
var c = set.Count;
set.ExceptWith(set);
set.ExceptWith(values);
return c - set.Count;
}

View File

@@ -36,6 +36,7 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
var cPlayerMan = client.ResolveDependency<ISharedPlayerManager>();
var sPlayerMan = server.ResolveDependency<ISharedPlayerManager>();
var xformSys = sEntMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
var mapSys = sEntMan.EntitySysManager.GetEntitySystem<SharedMapSystem>();
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => netMan.ClientConnect(null!, 0, null!));
@@ -56,18 +57,18 @@ public sealed class DeletionNetworkingTests : RobustIntegrationTest
EntityUid grid2 = default;
NetEntity grid1Net = default;
NetEntity grid2Net = default;
server.System<SharedMapSystem>().CreateMap(out var mapId);
mapSys.CreateMap(out var mapId);
await server.WaitPost(() =>
{
var gridComp = mapMan.CreateGridEntity(mapId);
gridComp.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSys.SetTile(gridComp, Vector2i.Zero, new Tile(1));
grid1 = gridComp.Owner;
xformSys.SetLocalPosition(grid1, new Vector2(-2,0));
grid1Net = sEntMan.GetNetEntity(grid1);
gridComp = mapMan.CreateGridEntity(mapId);
gridComp.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSys.SetTile(gridComp, Vector2i.Zero, new Tile(1));
grid2 = gridComp.Owner;
xformSys.SetLocalPosition(grid2, new Vector2(2,0));
grid2Net = sEntMan.GetNetEntity(grid2);

View File

@@ -22,6 +22,7 @@ namespace Robust.UnitTesting.Shared.Map
var mapManager = server.ResolveDependency<IMapManager>();
var entManager = server.ResolveDependency<IEntityManager>();
var physSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
var mapSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedMapSystem>();
MapId mapId;
Entity<MapGridComponent>? gridId1 = null;
@@ -69,8 +70,8 @@ namespace Robust.UnitTesting.Shared.Map
await server.WaitAssertion(() =>
{
gridId1?.Comp.SetTile(new Vector2i(0, 0), new Tile(1));
gridId2?.Comp.SetTile(new Vector2i(0, 0), new Tile(1));
mapSystem.SetTile(gridId1!.Value, new Vector2i(0, 0), new Tile(1));
mapSystem.SetTile(gridId2!.Value, new Vector2i(0, 0), new Tile(1));
});
await server.WaitRunTicks(1);

View File

@@ -18,6 +18,7 @@ namespace Robust.UnitTesting.Shared.Map
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var mapSystem = entManager.EntitySysManager.GetEntitySystem<SharedMapSystem>();
await server.WaitAssertion(() =>
{
@@ -27,12 +28,12 @@ namespace Robust.UnitTesting.Shared.Map
for (var i = 0; i < 10; i++)
{
grid.Comp.SetTile(new Vector2i(i, 0), new Tile(1));
mapSystem.SetTile(grid, new Vector2i(i, 0), new Tile(1));
}
for (var i = 10; i >= 0; i--)
{
grid.Comp.SetTile(new Vector2i(i, 0), Tile.Empty);
mapSystem.SetTile(grid, new Vector2i(i, 0), Tile.Empty);
}
Assert.That(entManager.Deleted(gridEntity));
@@ -56,6 +57,7 @@ namespace Robust.UnitTesting.Shared.Map
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
await server.WaitAssertion(() =>
{
@@ -64,12 +66,12 @@ namespace Robust.UnitTesting.Shared.Map
for (var i = 0; i < 10; i++)
{
grid.Comp.SetTile(new Vector2i(i, 0), new Tile(1));
mapSystem.SetTile(grid, new Vector2i(i, 0), new Tile(1));
}
for (var i = 10; i >= 0; i--)
{
grid.Comp.SetTile(new Vector2i(i, 0), Tile.Empty);
mapSystem.SetTile(grid, new Vector2i(i, 0), Tile.Empty);
}
Assert.That(!((!entManager.EntityExists(grid) ? EntityLifeStage.Deleted : entManager.GetComponent<MetaDataComponent>(grid).EntityLifeStage) >= EntityLifeStage.Deleted));

View File

@@ -59,6 +59,7 @@ public sealed class GridFixtures_Tests : RobustIntegrationTest
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var physSystem = server.ResolveDependency<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
var mapSystem = entManager.EntitySysManager.GetEntitySystem<SharedMapSystem>();
await server.WaitAssertion(() =>
{
@@ -72,7 +73,7 @@ public sealed class GridFixtures_Tests : RobustIntegrationTest
Assert.That(gridBody!.BodyType, Is.EqualTo(BodyType.Static));
// 1 fixture if we only ever update the 1 chunk
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1));
Assert.That(manager.FixtureCount, Is.EqualTo(1));
// Also should only be a single tile.
@@ -81,7 +82,7 @@ public sealed class GridFixtures_Tests : RobustIntegrationTest
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 1.0f, 0.1f));
// Now do 2 tiles (same chunk)
grid.Comp.SetTile(new Vector2i(0, 1), new Tile(1));
mapSystem.SetTile(grid, new Vector2i(0, 1), new Tile(1));
Assert.That(manager.FixtureCount, Is.EqualTo(1));
bounds = manager.Fixtures.First().Value.Shape.ComputeAABB(new Transform(Vector2.Zero, (float) Angle.Zero.Theta), 0);
@@ -90,7 +91,7 @@ public sealed class GridFixtures_Tests : RobustIntegrationTest
Assert.That(MathHelper.CloseToPercent(Box2.Area(bounds), 2.0f, 0.1f));
// If we add a new chunk should be 2 now
grid.Comp.SetTile(new Vector2i(0, -1), new Tile(1));
mapSystem.SetTile(grid, new Vector2i(0, -1), new Tile(1));
Assert.That(manager.FixtureCount, Is.EqualTo(2));
physSystem.SetLinearVelocity(grid, Vector2.One, manager: manager, body: gridBody);

View File

@@ -30,7 +30,7 @@ namespace Robust.UnitTesting.Shared.Map
await server.WaitAssertion(() =>
{
entMan.System<SharedMapSystem>().CreateMap(out var mapId);
mapSystem.CreateMap(out var mapId);
var grid = mapMan.CreateGridEntity(mapId);
var gridEnt = grid.Owner;
var coordinates = new EntityCoordinates(gridEnt, new Vector2(10, 0));
@@ -41,18 +41,18 @@ namespace Robust.UnitTesting.Shared.Map
Assert.That(mapSystem.WorldToLocal(grid.Owner, grid.Comp, coordinates.Position), Is.EqualTo(coordinates.Position));
// Rotate 180 degrees should show -10, 0 for the position in map-terms and 10, 0 for the position in entity terms (i.e. no change).
entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation += new Angle(MathF.PI);
transformSystem.SetWorldRotation(gridEnt, transformSystem.GetWorldRotation(gridEnt) + new Angle(MathF.PI));
Assert.That(transformSystem.GetWorldRotation(gridEnt), Is.EqualTo(new Angle(MathF.PI)));
// Check the map coordinate rotates correctly
Assert.That(mapSystem.WorldToLocal(grid.Owner, grid.Comp, new Vector2(10, 0)).EqualsApprox(new Vector2(-10, 0), 0.01f));
Assert.That(mapSystem.LocalToWorld(grid.Owner, grid.Comp, coordinates.Position).EqualsApprox(new Vector2(-10, 0), 0.01f));
Assert.That(mapSystem.WorldToLocal(gridEnt, grid.Comp, new Vector2(10, 0)).EqualsApprox(new Vector2(-10, 0), 0.01f));
Assert.That(mapSystem.LocalToWorld(gridEnt, grid.Comp, coordinates.Position).EqualsApprox(new Vector2(-10, 0), 0.01f));
// Now we'll do the same for 180 degrees.
entMan.GetComponent<TransformComponent>(gridEnt).WorldRotation += MathF.PI / 2f;
transformSystem.SetWorldRotation(gridEnt, transformSystem.GetWorldRotation(gridEnt) + MathF.PI / 2f);
// If grid facing down then worldpos of 10, 0 gets rotated 90 degrees CCW and hence should be 0, 10
Assert.That(mapSystem.WorldToLocal(grid.Owner, grid.Comp, new Vector2(10, 0)).EqualsApprox(new Vector2(0, 10), 0.01f));
Assert.That(mapSystem.WorldToLocal(gridEnt, grid.Comp, new Vector2(10, 0)).EqualsApprox(new Vector2(0, 10), 0.01f));
// If grid facing down then local 10,0 pos should just return 0, -10 given it's aligned with the rotation.
Assert.That(mapSystem.LocalToWorld(grid.Owner, grid.Comp, coordinates.Position).EqualsApprox(new Vector2(0, -10), 0.01f));
Assert.That(mapSystem.LocalToWorld(gridEnt, grid.Comp, coordinates.Position).EqualsApprox(new Vector2(0, -10), 0.01f));
});
}
@@ -70,7 +70,7 @@ namespace Robust.UnitTesting.Shared.Map
await server.WaitAssertion(() =>
{
entMan.System<SharedMapSystem>().CreateMap(out var mapId);
mapSystem.CreateMap(out var mapId);
var grid = mapMan.CreateGridEntity(mapId);
var gridEnt = grid.Owner;
@@ -85,7 +85,7 @@ namespace Robust.UnitTesting.Shared.Map
}
}
var chunks = mapSystem.GetMapChunks(grid.Owner, grid.Comp).Select(c => c.Value).ToList();
var chunks = mapSystem.GetMapChunks(gridEnt, grid.Comp).Select(c => c.Value).ToList();
Assert.That(chunks.Count, Is.EqualTo(1));
var chunk = chunks[0];

View File

@@ -42,6 +42,7 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
var sPlayerMan = server.ResolveDependency<ISharedPlayerManager>();
var fixturesSystem = sEntMan.EntitySysManager.GetEntitySystem<FixtureSystem>();
var physicsSystem = sEntMan.EntitySysManager.GetEntitySystem<SharedPhysicsSystem>();
var mapSystem = sEntMan.EntitySysManager.GetEntitySystem<SharedMapSystem>();
Assert.DoesNotThrow(() => client.SetConnectTarget(server));
client.Post(() => netMan.ClientConnect(null!, 0, null!));
@@ -58,9 +59,9 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
EntityUid map1 = default;
await server.WaitPost(() =>
{
map1 = sEntMan.System<SharedMapSystem>().CreateMap(out var mapId);
map1 = mapSystem.CreateMap(out var mapId);
var gridEnt = mapMan.CreateGridEntity(mapId);
gridEnt.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSystem.SetTile(gridEnt, Vector2i.Zero, new Tile(1));
grid1 = gridEnt.Owner;
});
@@ -129,9 +130,9 @@ public sealed class BroadphaseNetworkingTest : RobustIntegrationTest
await server.WaitPost(() =>
{
// Create grid
map2 = sEntMan.System<SharedMapSystem>().CreateMap(out var mapId);
map2 = mapSystem.CreateMap(out var mapId);
var gridEnt = mapMan.CreateGridEntity(mapId);
gridEnt.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSystem.SetTile(gridEnt, Vector2i.Zero, new Tile(1));
grid2 = gridEnt.Owner;
// Move player

View File

@@ -42,6 +42,8 @@ namespace Robust.UnitTesting.Shared.Physics
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
var transformSystem = entManager.System<SharedTransformSystem>();
Entity<MapGridComponent> grid = default!;
MapId mapId = default!;
@@ -51,14 +53,15 @@ namespace Robust.UnitTesting.Shared.Physics
await server.WaitPost(() =>
{
entManager.System<SharedMapSystem>().CreateMap(out mapId);
mapSystem.CreateMap(out mapId);
grid = mapManager.CreateGridEntity(mapId);
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1));
var entityOne = entManager.SpawnEntity("CollisionWakeTestItem", new MapCoordinates(Vector2.One * 2f, mapId));
entityOnePhysics = entManager.GetComponent<PhysicsComponent>(entityOne);
xform = entManager.GetComponent<TransformComponent>(entityOne);
Assert.That(xform.ParentUid == mapManager.GetMapEntityId(mapId));
mapSystem.TryGetMap(mapId, out var mapUid);
Assert.That(xform.ParentUid == mapUid);
var entityTwo = entManager.SpawnEntity("CollisionWakeTestItem", new EntityCoordinates(grid, new Vector2(0.5f, 0.5f)));
entityTwoPhysics = entManager.GetComponent<PhysicsComponent>(entityTwo);

View File

@@ -28,6 +28,8 @@ public sealed class GridMovement_Test : RobustIntegrationTest
var mapManager = server.ResolveDependency<IMapManager>();
var entManager = server.ResolveDependency<IEntityManager>();
var physSystem = systems.GetEntitySystem<SharedPhysicsSystem>();
var transformSystem = entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
var mapSystem = entManager.EntitySysManager.GetEntitySystem<SharedMapSystem>();
await server.WaitAssertion(() =>
{
@@ -35,7 +37,7 @@ public sealed class GridMovement_Test : RobustIntegrationTest
var grid = mapManager.CreateGridEntity(mapId);
// Setup 1 body on grid, 1 body off grid, and assert that it's all gucci.
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1));
var fixtures = entManager.GetComponent<FixturesComponent>(grid);
Assert.That(fixtures.FixtureCount, Is.EqualTo(1));
@@ -67,7 +69,7 @@ public sealed class GridMovement_Test : RobustIntegrationTest
Assert.That(onGridBody.ContactCount, Is.EqualTo(0));
// Alright now move the grid on top of the off grid body, run physics for a frame and see if they contact
entManager.GetComponent<TransformComponent>(grid.Owner).LocalPosition = new Vector2(10f, 10f);
transformSystem.SetLocalPosition(grid.Owner, new Vector2(10f, 10f));
physSystem.Update(0.001f);
Assert.That(onGridBody.ContactCount, Is.EqualTo(1));

View File

@@ -23,12 +23,13 @@ public sealed class RecursiveUpdateTest
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var xforms = entManager.System<SharedTransformSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var containers = entManager.System<ContainerSystem>();
var mapId = sim.CreateMap().MapId;
var grid = mapManager.CreateGridEntity(mapId);
var guid = grid.Owner;
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1));
Assert.That(entManager.HasComponent<BroadphaseComponent>(guid));
var broadphase = entManager.GetComponent<BroadphaseComponent>(guid);
@@ -161,11 +162,12 @@ public sealed class RecursiveUpdateTest
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var mapSystem = entManager.EntitySysManager.GetEntitySystem<SharedMapSystem>();
var transforms = entManager.EntitySysManager.GetEntitySystem<SharedTransformSystem>();
var lookup = entManager.EntitySysManager.GetEntitySystem<EntityLookupSystem>();
var mapId = sim.CreateMap().MapId;
var map = mapManager.GetMapEntityId(mapId);
var map = mapSystem.GetMapOrInvalid(mapId);
var mapBroadphase = entManager.GetComponent<BroadphaseComponent>(map);
var coords = new EntityCoordinates(map, new Vector2(0.5f, 0.5f));
@@ -216,7 +218,7 @@ public sealed class RecursiveUpdateTest
// Try again, but this time with a parent change.
var grid = mapManager.CreateGridEntity(mapId);
var guid = grid.Owner;
grid.Comp.SetTile(Vector2i.Zero, new Tile(1));
mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1));
var gridBroadphase = entManager.GetComponent<BroadphaseComponent>(guid);
var gridBroadData = new BroadphaseData(guid, EntityUid.Invalid, false, false);

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

@@ -32,6 +32,7 @@ PLATFORM_WINDOWS = "windows"
PLATFORM_LINUX = "linux"
PLATFORM_LINUX_ARM64 = "linux-arm64"
PLATFORM_MACOS = "mac"
PLATFORM_FREEBSD = "freebsd"
IGNORED_RESOURCES = {
".gitignore",
@@ -85,7 +86,7 @@ def main() -> None:
parser.add_argument("--platform",
"-p",
action="store",
choices=[PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX],
choices=[PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX, PLATFORM_FREEBSD],
nargs="*",
help="Which platform to build for. If not provided, all platforms will be built")
@@ -98,7 +99,7 @@ def main() -> None:
skip_build = args.skip_build
if not platforms:
platforms = [PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX]
platforms = [PLATFORM_WINDOWS, PLATFORM_MACOS, PLATFORM_LINUX, PLATFORM_FREEBSD]
if os.path.exists("release"):
print(Fore.BLUE + Style.DIM +
@@ -117,7 +118,7 @@ def main() -> None:
if PLATFORM_LINUX in platforms:
if not skip_build:
wipe_bin()
build_linux(skip_build)
build_linux(skip_build, "linux", "Linux")
if PLATFORM_LINUX_ARM64 in platforms:
if not skip_build:
@@ -129,6 +130,11 @@ def main() -> None:
wipe_bin()
build_macos(skip_build)
if PLATFORM_FREEBSD in platforms:
if not skip_build:
wipe_bin()
build_linux(skip_build, "freebsd", "FreeBSD")
def wipe_bin():
print(Fore.BLUE + Style.DIM +
@@ -177,20 +183,22 @@ def build_macos(skip_build: bool) -> None:
client_zip.close()
def build_linux(skip_build: bool) -> None:
def build_linux(skip_build: bool, platform, name) -> None:
"""Build on Unix-like platforms including Linux and FreeBSD."""
# Run a full build.
print(Fore.GREEN + "Building project for Linux x64..." + Style.RESET_ALL)
rid = "%s-x64" % platform
print(Fore.GREEN + "Building project for %s..." % rid + Style.RESET_ALL)
if not skip_build:
publish_client("linux-x64", "Linux")
publish_client(rid, name)
print(Fore.GREEN + "Packaging Linux x64 client..." + Style.RESET_ALL)
print(Fore.GREEN + "Packaging %s client..." % rid + Style.RESET_ALL)
client_zip = zipfile.ZipFile(
p("release", "Robust.Client_linux-x64.zip"), "w",
p("release", "Robust.Client_%s.zip" % rid), "w",
compression=zipfile.ZIP_DEFLATED)
copy_dir_into_zip(p("bin", "Client", "linux-x64", "publish"), "", client_zip, IGNORED_FILES_LINUX)
copy_dir_into_zip(p("bin", "Client", rid, "publish"), "", client_zip, IGNORED_FILES_LINUX)
copy_resources("Resources", client_zip)
# Cool we're done.
client_zip.close()