Compare commits

...

20 Commits

Author SHA1 Message Date
PJB3005
bb8fab82d7 Version: 253.0.2 2025-09-19 09:17:31 +02:00
Skye
4930c97819 Fix resource loading on non-Windows platforms (#6201)
(cherry picked from commit 51bbc5dc45)
2025-09-19 09:17:31 +02:00
PJB3005
7122cf90e3 Version: 253.0.1 2025-09-14 14:55:56 +02:00
PJB3005
84d0110477 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:55 +02:00
metalgearsloth
6f28c396cf Version: 253.0.0 2025-04-14 14:12:32 +10:00
metalgearsloth
b631f408f2 ItemList optimisation (#5796)
- VV AddComponent window no longer takes 300ms every time you press a key.
- Significantly optimise ItemList internally.
2025-04-14 13:55:48 +10:00
Leon Friedrich
34f4cf9452 More map validator fixes (#5820) 2025-04-14 13:55:02 +10:00
B_Kirill
6a4e4cf3b4 Cleanup warnings: Commands (#5825) 2025-04-14 13:54:42 +10:00
metalgearsloth
8aefa5c53e Make TestPoint use generics (#5828) 2025-04-14 01:49:07 +10:00
DrSmugleaf
2cfc981aa3 Fix popup text overflowing the sides of the screen (#5788)
* Fix popup text overflowing the sides of the screen

* Fix not escaping text
2025-04-13 12:59:23 +02:00
Leon Friedrich
4987c324d9 Fix bad debug assert (#5826) 2025-04-13 20:50:46 +10:00
DrSmugleaf
5450ddd0ba Fix SharedJointSystem.CreateDistanceJoint taking in an int for minimumDistance instead of a float (#5824) 2025-04-13 16:22:22 +10:00
Tayrtahn
378a10678c Improve location reporting for non-writeable DataFields (#5715)
* Better location reporting for readonly DataField errors

* Better location reporting for readonly DataField property errors

* Use SyntaxKind instead of string for TryGetModifierLocation
2025-04-13 16:22:05 +10:00
Leon Friedrich
2e0735b92f Fix NRE in screen-space overlays (#5823)
* Fix pre-init screen-space overlays

* Debug asserts
2025-04-13 16:21:02 +10:00
Leon Friedrich
5756d15333 Make BoundUserInterfaceMessageAttempt broadcast again (#5821) 2025-04-12 18:22:13 +10:00
Leon Friedrich
b6f74b8dea Fix logMissing in EntitySystem.Resolve() (#5822) 2025-04-12 17:40:08 +10:00
Leon Friedrich
3800c5707e Add new SerializationManager.PushComposition overload (#5785) 2025-04-12 12:58:40 +10:00
Leon Friedrich
8f49785b4e Fix RemCompDeferred not always setting lifestage (#5786)
* Fix RemCompDeferred not setting lifestage

* spaelling
2025-04-12 12:57:11 +10:00
metalgearsloth
f274de0f10 Version: 252.0.0 2025-04-12 01:43:29 +10:00
Milon
e128338f9d hi (#5811) 2025-04-12 01:29:41 +10:00
35 changed files with 400 additions and 112 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,43 @@ END TEMPLATE-->
*None yet*
## 253.0.2
## 253.0.1
## 253.0.0
### New features
* Add a new `SerializationManager.PushComposition()` overload that takes in a single parent instead of an array of parents.
* `BoundUserInterfaceMessageAttempt` once again gets raised as a broadcast event, in addition to being directed.
* This effectively reverts the breaking part of the changes made in v252.0.0
* Fix CreateDistanceJoint using an int instead of a float for minimum distance.
### Bugfixes
* Fix deferred component removal not setting the component's life stage to `ComponentLifeStage.Stopped` if the component has not yet been initialised.
* Fix some `EntitySystem.Resolve()` overloads not respecting the optional `logMissing` argument.
* Fix screen-space overlays not being useable without first initializing/starting entity manager & systems
* ItemList is now significantly optimized. VV's `AddComponent` window in particular should be much faster.
* Fix some more MapValidator fields.
* Fix popup text overflowing the sides of the screen.
* Improve location reporting for non-writeable datafields via analyzer.
### Other
* TestPoint now uses generics rather than IPhysShape directly.
## 252.0.0
### Breaking changes
* BoundUserInterfaceMessageAttempt is raised directed against entities and no longer broadcast.
## 251.0.0
### Breaking changes

View File

@@ -87,4 +87,66 @@ public sealed class DataDefinitionAnalyzerTest
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
);
}
[Test]
public async Task ReadOnlyFieldTest()
{
const string code = """
using System;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Serialization.Manager.Attributes
{
public class DataFieldBaseAttribute : Attribute;
public class DataFieldAttribute : DataFieldBaseAttribute;
public sealed class DataDefinitionAttribute : Attribute;
}
[DataDefinition]
public sealed partial class Foo
{
[DataField]
public readonly int Bad;
[DataField]
public int Good;
}
""";
await Verifier(code,
// /0/Test0.cs(15,12): error RA0019: Data field Bad in data definition Foo is readonly
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldWritableRule).WithSpan(15, 12, 15, 20).WithArguments("Bad", "Foo")
);
}
[Test]
public async Task ReadOnlyPropertyTest()
{
const string code = """
using System;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.Serialization.Manager.Attributes
{
public class DataFieldBaseAttribute : Attribute;
public class DataFieldAttribute : DataFieldBaseAttribute;
public sealed class DataDefinitionAttribute : Attribute;
}
[DataDefinition]
public sealed partial class Foo
{
[DataField]
public int Bad { get; }
[DataField]
public int Good { get; private set; }
}
""";
await Verifier(code,
// /0/Test0.cs(15,20): error RA0020: Data field property Bad in data definition Foo does not have a setter
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldPropertyWritableRule).WithSpan(15, 20, 15, 28).WithArguments("Bad", "Foo")
);
}
}

View File

@@ -41,7 +41,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to mark any type containing a nested data definition as partial."
);
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
public static readonly DiagnosticDescriptor DataFieldWritableRule = new(
Diagnostics.IdDataFieldWritable,
"Data field must not be readonly",
"Data field {0} in data definition {1} is readonly",
@@ -51,7 +51,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to remove the readonly modifier."
);
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
public static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
Diagnostics.IdDataFieldPropertyWritable,
"Data field property must have a setter",
"Data field property {0} in data definition {1} does not have a setter",
@@ -149,7 +149,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (IsReadOnlyDataField(type, fieldSymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
}
if (HasRedundantTag(fieldSymbol))
@@ -185,7 +186,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (IsReadOnlyDataField(type, propertySymbol))
{
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
}
if (HasRedundantTag(propertySymbol))
@@ -285,6 +287,20 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool TryGetModifierLocation(MemberDeclarationSyntax syntax, SyntaxKind modifierKind, out Location location)
{
foreach (var modifier in syntax.Modifiers)
{
if (modifier.IsKind(modifierKind))
{
location = modifier.GetLocation();
return true;
}
}
location = syntax.GetLocation();
return false;
}
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
{
if (member is IFieldSymbol field)

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

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

@@ -123,9 +123,13 @@ namespace Robust.Client.Graphics.Clyde
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
{
// Check that entity manager has started.
// This is required for us to be able to use MapSystem.
DebugTools.Assert(_entityManager.Started, "Entity manager should be started/initialized before rendering world-space overlays");
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
var mapId = vp.Eye!.Position.MapId;
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapSystem.GetMapOrInvalid(mapId), mapId, worldBox, worldBounds);
if (!overlay.BeforeDraw(args))
@@ -152,6 +156,7 @@ namespace Robust.Client.Graphics.Clyde
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
{
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
using (DebugGroup($"Overlays: {space}"))
{
foreach (var overlay in GetOverlaysForSpace(space))
@@ -176,9 +181,18 @@ namespace Robust.Client.Graphics.Clyde
var worldBounds = CalcWorldBounds(vp);
var worldAABB = worldBounds.CalcBoundingBox();
var mapId = vp.Eye!.Position.MapId;
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
var mapUid = EntityUid.Invalid;
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapSystem.GetMapOrInvalid(mapId), mapId, worldAABB, worldBounds);
// Screen space overlays may be getting used before entity manager & entity systems have been initialized.
// This might mean that _mapSystem is currently null.
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (_entityManager.Started && _mapSystem != null)
mapUid = _mapSystem.GetMapOrInvalid(mapId);
DebugTools.Assert(_mapSystem != null || !_entityManager.Started);
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, mapUid, mapId, worldAABB, worldBounds);
foreach (var overlay in list)
{

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Collections;
using Robust.Shared.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
@@ -83,9 +84,23 @@ namespace Robust.Client.UserInterface.Controls
_updateScrollbarVisibility();
}
public void Add(IEnumerable<Item> items)
{
foreach (var item in items)
{
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
_itemList.Add(item);
item.OnSelected += Select;
item.OnDeselected += Deselect;
}
Recalculate();
}
public void Add(Item item)
{
if (item == null) return;
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
_itemList.Add(item);
@@ -93,9 +108,19 @@ namespace Robust.Client.UserInterface.Controls
item.OnSelected += Select;
item.OnDeselected += Deselect;
RecalculateContentHeight();
if (_isAtBottom && ScrollFollowing)
_scrollBar.MoveToEnd();
Recalculate();
}
public void AddItems(IEnumerable<string> texts, Texture? icon = null, bool selectable = true, object? metadata = null)
{
var items = new ValueList<Item>();
foreach (var text in texts)
{
items.Add(new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata});
}
Add(items);
}
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null)
@@ -107,11 +132,15 @@ namespace Robust.Client.UserInterface.Controls
public void Clear()
{
foreach (var item in _itemList.ToArray())
// Handle this manually so we can just clear all at once.
foreach (var item in _itemList)
{
Remove(item);
item.OnSelected -= Select;
item.OnDeselected -= Deselect;
}
_itemList.Clear();
Recalculate();
_totalContentHeight = 0;
}
@@ -125,25 +154,35 @@ namespace Robust.Client.UserInterface.Controls
_itemList.CopyTo(array, arrayIndex);
}
private void InternalRemoveAt(int index)
{
if (_itemList.Count <= index)
return;
// If you modify this then also make sure to update Clear!
var item = _itemList[index];
_itemList.RemoveAt(index);
item.OnSelected -= Select;
item.OnDeselected -= Deselect;
}
public bool Remove(Item item)
{
if (item == null) return false;
var value = _itemList.Remove(item);
item.OnSelected -= Select;
item.OnDeselected -= Deselect;
RecalculateContentHeight();
if (_isAtBottom && ScrollFollowing)
_scrollBar.MoveToEnd();
Recalculate();
return value;
}
public void RemoveAt(int index)
{
Remove(this[index]);
InternalRemoveAt(index);
Recalculate();
}
public IEnumerator<Item> GetEnumerator()
@@ -161,16 +200,24 @@ namespace Robust.Client.UserInterface.Controls
return _itemList.IndexOf(item);
}
public void Insert(int index, Item item)
private void InternalInsert(int index, Item item)
{
if (item == null) return;
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
_itemList.Insert(index, item);
item.OnSelected += Select;
item.OnDeselected += Deselect;
}
public void Insert(int index, Item item)
{
InternalInsert(index, item);
Recalculate();
}
private void Recalculate()
{
RecalculateContentHeight();
if (_isAtBottom && ScrollFollowing)
_scrollBar.MoveToEnd();
@@ -191,7 +238,6 @@ namespace Robust.Client.UserInterface.Controls
SetItems(newItems, (a,b) => string.Compare(a.Text, b.Text));
}
/// <inheritdoc />
/// <summary>
/// This variant allows for a custom equality operator to compare items, when
/// comparing the Item text is not desired.
@@ -215,13 +261,13 @@ namespace Robust.Client.UserInterface.Controls
else if (cmpResult > 0)
{
// Item exists in our list, but not in `newItems`. Remove it.
RemoveAt(i);
InternalRemoveAt(i);
i--;
}
else if (cmpResult < 0)
{
// A new entry which doesn't exist in our list. Insert it.
Insert(i + 1, newItems[j]);
InternalInsert(i + 1, newItems[j]);
j--;
}
}
@@ -229,16 +275,18 @@ namespace Robust.Client.UserInterface.Controls
// Any remaining items in our list don't exist in `newItems` so remove them
while (i >= 0)
{
RemoveAt(i);
InternalRemoveAt(i);
i--;
}
// And finally, any remaining items in `newItems` don't exist in our list. Create them.
while (j >= 0)
{
Insert(0, newItems[j]);
InternalInsert(0, newItems[j]);
j--;
}
Recalculate();
}
// Without this attribute, this would compile into a property called "Item", causing problems with the Item class.
@@ -290,9 +338,12 @@ namespace Robust.Client.UserInterface.Controls
public void ClearSelected(int? except = null)
{
foreach (var item in GetSelected())
for (var i = 0; i < _itemList.Count; i++)
{
if(IndexOf(item) == except) continue;
if (i == except)
continue;
var item = _itemList[i];
item.Selected = false;
}
}

View File

@@ -5,9 +5,9 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Utility;
namespace Robust.Client.UserInterface;
internal sealed partial class UserInterfaceManager
@@ -61,7 +61,7 @@ internal sealed partial class UserInterfaceManager
Title = string.IsNullOrEmpty(title) ? Loc.GetString("popup-title") : title,
};
var label = new Label { Text = contents };
var label = new RichTextLabel { Text = $"[color=white]{FormattedMessage.EscapeText(contents)}[/color]" };
var vBox = new BoxContainer
{

View File

@@ -2,11 +2,15 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Collections;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.ViewVariables
{
@@ -51,15 +55,19 @@ namespace Robust.Client.ViewVariables
_lastSearch = search;
EntryItemList.ClearSelected();
EntryItemList.Clear();
AddButton.Disabled = true;
var items = new ValueList<string>();
foreach (var component in _entries)
{
if(!string.IsNullOrEmpty(search) && !component.Contains(search, StringComparison.InvariantCultureIgnoreCase))
continue;
EntryItemList.AddItem(component);
items.Add(component);
}
EntryItemList.AddItems(items);
}
private void OnSearchTextChanged(LineEdit.LineEditEventArgs obj)

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

@@ -37,7 +37,7 @@ sealed class AddMapCommand : LocalizedEntityCommands
sealed class RemoveMapCommand : LocalizedCommands
{
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
public override string Command => "rmmap";
public override bool RequireServerOrSingleplayer => true;
@@ -51,14 +51,15 @@ sealed class RemoveMapCommand : LocalizedCommands
}
var mapId = new MapId(int.Parse(args[0]));
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
if (!_map.MapExists(mapId))
if (!mapSystem.MapExists(mapId))
{
shell.WriteError($"Map {mapId.Value} does not exist.");
return;
}
_map.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
shell.WriteLine($"Map {mapId.Value} was removed.");
}
}

View File

@@ -20,6 +20,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
public override string Command => "tp";
public override bool RequireServerOrSingleplayer => true;
@@ -46,7 +47,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
else
mapId = transform.MapID;
if (!_map.MapExists(mapId))
if (!_mapSystem.MapExists(mapId))
{
shell.WriteError($"Map {mapId} doesn't exist!");
return;
@@ -60,9 +61,11 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
}
else
{
var mapEnt = _map.GetMapEntityIdOrThrow(mapId);
_transform.SetWorldPosition((entity, transform), position);
_transform.SetParent(entity, transform, mapEnt);
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
{
_transform.SetWorldPosition((entity, transform), position);
_transform.SetParent(entity, transform, mapEnt.Value);
}
}
shell.WriteLine($"Teleported {shell.Player} to {mapId}:{posX},{posY}.");

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

@@ -155,9 +155,9 @@ public sealed class EntitySerializer : ISerializationContext,
public event IsSerializableDelegate? OnIsSerializeable;
public delegate void IsSerializableDelegate(Entity<MetaDataComponent> ent, ref bool serializable);
public EntitySerializer(IDependencyCollection _dependency, SerializationOptions options)
public EntitySerializer(IDependencyCollection dependency, SerializationOptions options)
{
_dependency.InjectDependencies(this);
dependency.InjectDependencies(this);
_log = _logMan.GetSawmill("entity_serializer");
SerializerProvider.RegisterSerializer(this);

View File

@@ -595,12 +595,23 @@ namespace Robust.Shared.GameObjects
if (!_deleteSet.Add(component))
{
// already deferred deletion
// Already deferring deletion
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Stopped);
return;
}
if (component.LifeStage >= ComponentLifeStage.Initialized && component.LifeStage <= ComponentLifeStage.Running)
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Added);
if (component.LifeStage is >= ComponentLifeStage.Initialized and < ComponentLifeStage.Stopping)
LifeShutdown(uid, component, _componentFactory.GetIndex(component.GetType()));
else if (component.LifeStage == ComponentLifeStage.Added)
{
// The component was added, but never initialized or started. It's kinda weird to add and then
// immediately defer-remove a component, but oh well. Let's just set the life stage directly and not
// raise shutdown events? The removal events will still get called later.
// This is also what LifeShutdown() would also do, albeit behind a DebugAssert.
component.LifeStage = ComponentLifeStage.Stopped;
}
#if EXCEPTION_TOLERANCE
}
catch (Exception e)

View File

@@ -16,6 +16,7 @@ public partial class EntityManager
/// </summary>
internal void LifeAddToEntity(EntityUid uid, IComponent component, CompIdx idx)
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.PreAdd);
#pragma warning disable CS0618 // Type or member is obsolete
@@ -34,6 +35,7 @@ public partial class EntityManager
/// </summary>
internal void LifeInitialize(EntityUid uid, IComponent component, CompIdx idx)
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Added);
component.LifeStage = ComponentLifeStage.Initializing;
@@ -47,6 +49,7 @@ public partial class EntityManager
/// </summary>
internal void LifeStartup(EntityUid uid, IComponent component, CompIdx idx)
{
DebugTools.Assert(!_deleteSet.Contains(component));
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Initialized);
component.LifeStage = ComponentLifeStage.Starting;

View File

@@ -37,18 +37,22 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
protected bool Resolve(
EntityUid uid,
[NotNullWhen(true)] ref MetaDataComponent? component,
bool logMissing = true)
{
return EntityManager.MetaQuery.Resolve(uid, ref component);
return EntityManager.MetaQuery.Resolve(uid, ref component, logMissing);
}
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
protected bool Resolve(
EntityUid uid,
[NotNullWhen(true)] ref TransformComponent? component,
bool logMissing = true)
{
return EntityManager.TransformQuery.Resolve(uid, ref component);
return EntityManager.TransformQuery.Resolve(uid, ref component, logMissing);
}
/// <summary>

View File

@@ -121,9 +121,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (msg.Message is not CloseBoundInterfaceMessage && ui.RequireInputValidation)
{
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey, msg.Message);
RaiseLocalEvent(attempt);
if (attempt.Cancelled)
return;
RaiseLocalEvent(uid, attempt);
if (attempt.Cancelled)
return;
}
// get the wrapped message and populate it with the sender & UI key information.

View File

@@ -11,7 +11,6 @@ namespace Robust.Shared.Map.Commands;
/// </summary>
public sealed class AmbientLightCommand : IConsoleCommand
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntitySystemManager _systems = default!;
public string Command => $"setambientlight";
@@ -32,10 +31,11 @@ public sealed class AmbientLightCommand : IConsoleCommand
}
var mapId = new MapId(mapInt);
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
if (!_mapManager.MapExists(mapId))
if (!mapSystem.MapExists(mapId))
{
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid"));
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", mapId.Value)));
return;
}
@@ -49,7 +49,6 @@ public sealed class AmbientLightCommand : IConsoleCommand
}
var color = Color.FromSrgb(new Color(r, g, b, a));
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
mapSystem.SetAmbientLight(mapId, color);
}
}

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.Physics.Systems
/// <summary>
/// Tests whether a particular point is contained in the shape.
/// </summary>
public bool TestPoint(IPhysShape shape, Transform xform, Vector2 worldPoint)
public bool TestPoint<T>(T shape, Transform xform, Vector2 worldPoint) where T : IPhysShape
{
switch (shape)
{

View File

@@ -19,6 +19,7 @@ public abstract partial class SharedJointSystem : EntitySystem
[Dependency] private readonly SharedContainerSystem _container = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<JointComponent> _jointsQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
@@ -237,7 +238,7 @@ public abstract partial class SharedJointSystem : EntitySystem
string? id = null,
TransformComponent? xformA = null,
TransformComponent? xformB = null,
int? minimumDistance = null)
float? minimumDistance = null)
{
if (!Resolve(bodyA, ref xformA) || !Resolve(bodyB, ref xformB))
{
@@ -247,8 +248,8 @@ public abstract partial class SharedJointSystem : EntitySystem
anchorA ??= Vector2.Zero;
anchorB ??= Vector2.Zero;
var vecA = Vector2.Transform(anchorA.Value, xformA.WorldMatrix);
var vecB = Vector2.Transform(anchorB.Value, xformB.WorldMatrix);
var vecA = Vector2.Transform(anchorA.Value, _transform.GetWorldMatrix(xformA));
var vecB = Vector2.Transform(anchorB.Value, _transform.GetWorldMatrix(xformB));
var length = (vecA - vecB).Length();
if (minimumDistance != null)
length = Math.Max(minimumDistance.Value, length);

View File

@@ -408,16 +408,26 @@ namespace Robust.Shared.Prototypes
if (nonPushedParent)
continue;
var parentMaps = new MappingDataNode[parents.Length];
for (var i = 0; i < parentMaps.Length; i++)
if (parents.Length == 1)
{
parentMaps[i] = kindData.Results[parents[i]];
kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode(
kind,
kindData.Results[parents[0]],
kindData.RawResults[id]);
}
else
{
var parentMaps = new MappingDataNode[parents.Length];
for (var i = 0; i < parentMaps.Length; i++)
{
parentMaps[i] = kindData.Results[parents[i]];
}
kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode(
kind,
parentMaps,
kindData.RawResults[id]);
kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode(
kind,
parentMaps,
kindData.RawResults[id]);
}
}
else
{
@@ -629,16 +639,26 @@ namespace Robust.Shared.Prototypes
{
if (tree.TryGetParents(id, out var parents))
{
var parentNodes = new MappingDataNode[parents.Length];
for (var i = 0; i < parents.Length; i++)
if (parents.Length == 1)
{
parentNodes[i] = results[parents[i]].Result;
datum.Result = _serializationManager.PushCompositionWithGenericNode(
kind,
results[parents[0]].Result,
datum.Result);
}
else
{
var parentNodes = new MappingDataNode[parents.Length];
for (var i = 0; i < parents.Length; i++)
{
parentNodes[i] = results[parents[i]].Result;
}
datum.Result = _serializationManager.PushCompositionWithGenericNode(
kind,
parentNodes,
datum.Result);
datum.Result = _serializationManager.PushCompositionWithGenericNode(
kind,
parentNodes,
datum.Result);
}
}
if (tree.TryGetChildren(id, out var children))

View File

@@ -421,6 +421,7 @@ namespace Robust.Shared.Serialization.Manager
#region Composition
DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null);
DataNode PushComposition(Type type, DataNode parent, DataNode child, ISerializationContext? context = null);
public TNode PushComposition<TType, TNode>(TNode[] parents, TNode child, ISerializationContext? context = null) where TNode : DataNode
{
@@ -428,6 +429,12 @@ namespace Robust.Shared.Serialization.Manager
return (TNode)PushComposition(typeof(TType), parents, child, context);
}
public TNode PushComposition<TType, TNode>(TNode parent, TNode child, ISerializationContext? context = null)
where TNode : DataNode
{
return (TNode) PushComposition(typeof(TType), parent, child, context);
}
TNode PushInheritance<TType, TNode>(ITypeInheritanceHandler<TType, TNode> inheritanceHandler, TNode parent, TNode child,
ISerializationContext? context = null) where TNode : DataNode;
@@ -441,6 +448,12 @@ namespace Robust.Shared.Serialization.Manager
return (TNode) PushComposition(type, parents, child, context);
}
public TNode PushCompositionWithGenericNode<TNode>(Type type, TNode parent, TNode child, ISerializationContext? context = null)
where TNode : DataNode
{
return (TNode) PushComposition(type, parent, child, context);
}
/// <summary>
/// Simple <see cref="MappingDataNode"/> inheritance pusher clones data and overrides a parent's values with
/// the child's.

View File

@@ -24,27 +24,23 @@ public partial class SerializationManager
private readonly ConcurrentDictionary<(Type value, Type node), PushCompositionDelegate> _compositionPushers = new();
public DataNode PushComposition(Type type, DataNode[] parents, DataNode child, ISerializationContext? context = null)
public DataNode PushComposition(
Type type,
DataNode[] parents,
DataNode child,
ISerializationContext? context = null)
{
// TODO SERIALIZATION
// Add variant that doesn't require a parent array.
// TODO SERIALIZATION
// Change inheritance pushing so that it modifies the passed in child. This avoids re-creating the child
// multiple times when there are multiple children.
// multiple times when there are multiple parents.
//
// I.e., change the PushCompositionDelegate signature to not have a return value, and also add an override
// of this method that modified the given child.
// of this method that modifies the given child.
if (parents.Length == 0)
return child.Copy();
DebugTools.Assert(parents.All(x => x.GetType() == child.GetType()));
// the child.Clone() statement to the beginning here, then make the delegate modify the clone.
// Currently pusing more than one parent requires multiple unnecessary clones.
var pusher = GetOrCreatePushCompositionDelegate(type, child);
var node = child;
@@ -61,6 +57,22 @@ public partial class SerializationManager
return node;
}
public DataNode PushComposition(
Type type,
DataNode parent,
DataNode child,
ISerializationContext? context = null)
{
DebugTools.AssertEqual(parent.GetType(), child.GetType());
var pusher = GetOrCreatePushCompositionDelegate(type, child);
var newNode = pusher(type, parent, child, context);
// Currently delegate pusher should be returning a new instance, and not modifying the passed in child.
DebugTools.Assert(!ReferenceEquals(newNode, child));
return newNode;
}
private PushCompositionDelegate GetOrCreatePushCompositionDelegate(Type type, DataNode node)
{
return _compositionPushers.GetOrAdd((type, node.GetType()), static (tuple, vfArgument) =>
@@ -185,7 +197,7 @@ public partial class SerializationManager
{
if (field.InheritanceBehavior == InheritanceBehavior.Always)
{
newMapping[key] = PushComposition(field.FieldType, new[] { parentValue }, childValue, context);
newMapping[key] = PushComposition(field.FieldType, parentValue, childValue, context);
}
}
else

View File

@@ -207,7 +207,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
{
newCompReg[idx] = serializationManager.PushCompositionWithGenericNode(
reg.Type,
new[] { parent[mapping] },
parent[mapping],
newCompReg[idx],
context);

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

@@ -21,6 +21,8 @@ proto:
---
entity:
uid: int()
paused: bool(required=False)
mapInit: bool(required=False)
components: list(comp())
missingComponents: list(str(), required=False)