Compare commits

...

34 Commits

Author SHA1 Message Date
DrSmugleaf
58560f589f Defer MoveEvent out of TransformComponent.HandleComponentState (#1453)
* Defer MoveEvent out of TransformComponent.HandleComponentState

* Imports

* Make the update loop more readable and call ToArray

* Fix tests

* Fix tests HALLELUJAH
2020-12-19 13:09:16 +01:00
Pieter-Jan Briers
6e931ac175 Fix some CVars not saving. 2020-12-19 02:31:46 +01:00
Pieter-Jan Briers
a7eb5e8115 Use nvidia GPU on optimus laptops.
With an undocumented crappy hack, of course.
2020-12-19 02:25:10 +01:00
metalgearsloth
712e4acc66 Cache TryLooseGetType (#1448)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2020-12-19 01:42:51 +01:00
Pieter-Jan Briers
fdcfdffc0b Provide fallback for /status API if content does not override it. 2020-12-19 00:43:46 +01:00
Pieter-Jan Briers
74eb8e3e8d Allow build.json contents to be overriden by --cvar. 2020-12-17 17:13:07 +01:00
Pieter-Jan Briers
ae4c764e4f Whitelist System.Guid for sandbox. 2020-12-17 16:37:20 +01:00
Pieter-Jan Briers
7ef2cec121 Fix names parsed from build.json 2020-12-17 15:28:01 +01:00
Pieter-Jan Briers
40bff81017 Fix nullable warning. 2020-12-17 00:58:26 +01:00
Pieter-Jan Briers
f7c28992f8 Disable string map caching to hopefully fix connect. 2020-12-17 00:48:15 +01:00
Pieter-Jan Briers
920ae58019 Fix LoaderApiLoader.FindFiles() 2020-12-17 00:33:39 +01:00
Pieter-Jan Briers
5bb21e07de Engine versioning. 2020-12-16 23:53:51 +01:00
Pieter-Jan Briers
78ceaa50d5 Update Lidgren submodule. 2020-12-16 18:18:40 +01:00
Pieter-Jan Briers
7473b6dae1 Optimize assembly type checking.
It's now parallelized which cuts off ~200ms on its own for me.
Config is now shared between multiple loads which saves a lot as well.

All in all, pretty good.
2020-12-14 16:34:33 +01:00
Pieter-Jan Briers
c335170fc1 Add non-generic System.Nullable to sandbox whitelist. 2020-12-13 21:33:22 +01:00
Pieter-Jan Briers
13e9fe12ce Further fixes to loader exe.
Fix ordering of loads.
Fix loads.
2020-12-13 16:12:32 +01:00
Pieter-Jan Briers
7ef2fd46da Hail NuGet 2020-12-13 01:14:50 +01:00
Pieter-Jan Briers
f048209bf5 FUCK BOMs 2020-12-13 01:10:21 +01:00
chairbender
1bf9e2e87a Multiselect option button, tooltip delay (Action Hotbar Support) (#1435) 2020-12-13 01:01:00 +01:00
Pieter-Jan Briers
fd4f45e670 Use NuGet packages for engine natives.
Fixes #1434

This means that adding support for new architectures (e.g. ARM) is MUCH easier.

It removes  download_natives.py which simplifies the build process.

It's also way less painful to maintain.
2020-12-13 00:46:23 +01:00
Pieter-Jan Briers
f15c1c7a95 Allow engine to be loaded from a zip file itself. 2020-12-12 11:12:37 +01:00
DrSmugleaf
50f0a4389e Fix the server not setting IsConnected to false for disconnecting clients in integration tests (#1442) 2020-12-12 00:53:10 +01:00
komunre
cab6277b2d FixClipping() now check if entity is deleted (bug fix) (#1441)
* check for deletion in CanMove()

* Added deleted check in FixCollide

* Removed Owner.Deleted check from CanMove()
2020-12-12 04:37:32 +11:00
Pieter-Jan Briers
797fa9cffa Fix server failing to start due to non-int LogLevel enum. 2020-12-10 15:22:29 +01:00
20kdc
a20245d623 Fix grid bounds going out of sync with chunk collision regeneration (#1440)
Fixes #1439
2020-12-10 14:38:03 +01:00
Pieter-Jan Briers
04cc1f616d Permissive markup parsing. 2020-12-09 13:08:06 +01:00
Pieter-Jan Briers
8cd6f63f17 Make FormattedMessage tags records, clean up tests. 2020-12-09 13:08:06 +01:00
Ygg01
ad8b0b3c83 Add bytes or sbytes to enum where available (#1430)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2020-12-08 12:46:30 +01:00
Paul Ritter
f157cdce02 Rotatable bounding boxes (#1360)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2020-12-07 17:01:57 +01:00
DrSmugleaf
2504a42f88 Fix typo in exception message 2020-12-07 15:21:38 +01:00
Pieter-Jan Briers
d0191e063a Fix all cases of member references to array (not vector) types.
Yeah generics aren't the only one since you can do [,][,].
2020-12-07 00:12:05 +01:00
Pieter-Jan Briers
b96bcbd357 Fix member ref handling of non-vector generic arrays in type checker. 2020-12-05 23:13:04 +01:00
Pieter-Jan Briers
ae14031377 Fix version parsing. 2020-12-05 18:02:12 +01:00
Pieter-Jan Briers
1d19007012 Missing parentheses... 2020-12-05 17:42:32 +01:00
132 changed files with 1422 additions and 681 deletions

View File

@@ -1,11 +1,11 @@
root = true
root = true
[*]
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
charset = utf-8-bom
charset = utf-8
[*.{csproj,xml,yml,dll.config,targets,props}]
indent_size = 2

View File

@@ -13,8 +13,8 @@ jobs:
id: parse_version
shell: pwsh
run: |
$ver = [regex]::Match($env:GITHUB_REF, "v?(.+)").Groups[1].Value
echo "::set-output name=version::{0}" -f $ver
$ver = [regex]::Match($env:GITHUB_REF, "refs/tags/v?(.+)").Groups[1].Value
echo ("::set-output name=version::{0}" -f $ver)
- uses: actions/checkout@v2
with:

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "Lidgren.Network"]
path = Lidgren.Network/Lidgren.Network
url = https://github.com/space-wizards/lidgren-network-gen3.git
[submodule "Robust.LoaderApi"]
path = Robust.LoaderApi
url = https://github.com/space-wizards/Robust.LoaderApi.git

View File

@@ -1,12 +1,2 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
<PropertyGroup>
<RobustToolsPath>$(MSBuildThisFileDirectory)/../Tools/</RobustToolsPath>
</PropertyGroup>
<Target Name="CopyClientNatives">
<CombinePath BasePath="$(RobustToolsPath)" Paths="download_natives.py">
<Output TaskParameter="CombinedPaths" PropertyName="ScriptPath" />
</CombinePath>
<Exec Command="$(Python) &quot;$(ScriptPath)&quot; $(Platform) $(TargetOS) Client $(OutputPath)" CustomErrorRegularExpression="^Error" />
</Target>
<Target Name="ClientAfterBuild" DependsOnTargets="CopyClientNatives" />
</Project>

View File

@@ -13,7 +13,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
/// Defines event information for <see cref="GLFWCallbacks.KeyCallback"/>
/// or <see cref="GLFWCallbacks.MouseButtonCallback"/>.
/// </summary>
public enum InputAction
public enum InputAction : byte
{
/// <summary>
/// The key or mouse button was released.

View File

@@ -15,7 +15,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
/// Key modifiers, such as Shift or CTRL.
/// </summary>
[Flags]
public enum KeyModifiers
public enum KeyModifiers : byte
{
/// <summary>
/// if one or more Shift keys were held down.

View File

@@ -12,7 +12,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
/// <summary>
/// Specifies key codes and modifiers in US keyboard layout.
/// </summary>
public enum Keys
public enum Keys : short
{
/// <summary>
/// An unknown key.

View File

@@ -3,7 +3,7 @@ namespace OpenToolkit.GraphicsLibraryFramework
/// <summary>
/// Specifies the buttons of a mouse.
/// </summary>
public enum MouseButton
public enum MouseButton : byte
{
/// <summary>
/// The first button.

View File

@@ -14,7 +14,7 @@ using MidiEvent = NFluidsynth.MidiEvent;
namespace Robust.Client.Audio.Midi
{
public enum MidiRendererStatus
public enum MidiRendererStatus : byte
{
None,
Input,

View File

@@ -223,7 +223,7 @@ namespace Robust.Client
/// <summary>
/// Enumeration of the run levels of the BaseClient.
/// </summary>
public enum ClientRunLevel
public enum ClientRunLevel : byte
{
Error = 0,

View File

@@ -231,6 +231,11 @@ namespace Robust.Client.Debugging
_handle.DrawRect(box, color);
}
public override void DrawRect(in Box2Rotated box, in Color color)
{
_handle.DrawRect(box, color);
}
public override void DrawCircle(Vector2 origin, float radius, in Color color)
{
_handle.DrawCircle(origin, radius, color);

View File

@@ -17,6 +17,7 @@ using Robust.Client.Interfaces.Utility;
using Robust.Client.Player;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.LoaderApi;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
@@ -31,6 +32,7 @@ using Robust.Shared.Interfaces.Timers;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -64,9 +66,12 @@ namespace Robust.Client
[Dependency] private readonly IScriptClient _scriptClient = default!;
[Dependency] private readonly IComponentManager _componentManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
private CommandLineArgs? _commandLineArgs;
private bool _disableAssemblyLoadContext;
// Arguments for loader-load. Not used otherwise.
private IMainArgs? _loaderArgs;
public InitialLaunchState LaunchState { get; private set; } = default!;
@@ -119,7 +124,13 @@ namespace Robust.Client
_resourceCache.Initialize(LoadConfigAndUserData ? userDataDir : null);
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client");
ProgramShared.DoMounts(_resourceCache, _commandLineArgs?.MountOptions, "Content.Client", _loaderArgs != null);
if (_loaderArgs != null)
{
_stringSerializer.EnableCaching = false;
_resourceCache.MountLoaderApi(_loaderArgs.FileApi, "Resources/");
_modLoader.VerifierExtraLoadHandler = VerifierExtraLoadHandler;
}
// Bring display up as soon as resources are mounted.
if (!_clyde.Initialize())
@@ -185,6 +196,18 @@ namespace Robust.Client
return true;
}
private Stream? VerifierExtraLoadHandler(string arg)
{
DebugTools.AssertNotNull(_loaderArgs);
if (_loaderArgs!.FileApi.TryOpen(arg, out var stream))
{
return stream;
}
return null;
}
private void ReadInitialLaunchState()
{
if (_commandLineArgs == null)
@@ -347,7 +370,7 @@ namespace Robust.Client
}
internal enum DisplayMode
internal enum DisplayMode : byte
{
Headless,
Clyde,

View File

@@ -0,0 +1,18 @@
using Robust.Client;
using Robust.LoaderApi;
[assembly: LoaderEntryPoint(typeof(GameController.LoaderEntryPoint))]
namespace Robust.Client
{
internal partial class GameController
{
internal class LoaderEntryPoint : ILoaderEntryPoint
{
public void Main(IMainArgs args)
{
GameController.Start(args.Args, contentStart: false, args);
}
}
}
}

View File

@@ -1,6 +1,6 @@
using System;
using System.Threading;
using Robust.Client.Interfaces;
using Robust.LoaderApi;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -21,7 +21,7 @@ namespace Robust.Client
Start(args);
}
public static void Start(string[] args, bool contentStart = false)
public static void Start(string[] args, bool contentStart = false, IMainArgs? loaderArgs=null)
{
if (_hasStarted)
{
@@ -32,11 +32,11 @@ namespace Robust.Client
if (CommandLineArgs.TryParse(args, out var parsed))
{
ParsedMain(parsed, contentStart);
ParsedMain(parsed, contentStart, loaderArgs);
}
}
private static void ParsedMain(CommandLineArgs args, bool contentStart)
private static void ParsedMain(CommandLineArgs args, bool contentStart, IMainArgs? loaderArgs)
{
IoCManager.InitThread();
@@ -46,6 +46,7 @@ namespace Robust.Client
var gc = (GameController) IoCManager.Resolve<IGameController>();
gc.SetCommandLineArgs(args);
gc._loaderArgs = loaderArgs;
// When the game is ran with the startup executable being content,
// we have to disable the separate load context.

View File

@@ -16,7 +16,7 @@ namespace Robust.Client.GameObjects.EntitySystems
/// Handles interpolation of transform positions.
/// </summary>
[UsedImplicitly]
internal sealed class TransformSystem : EntitySystem
internal sealed class TransformSystem : SharedTransformSystem
{
// Max distance per tick how far an entity can move before it is considered teleporting.
// TODO: Make these values somehow dependent on server TPS.

View File

@@ -35,7 +35,7 @@ namespace Robust.Client.Graphics.Clyde
// To be clear: You shouldn't change this. This just helps with understanding where Primitive Restart is being used.
private const ushort PrimitiveRestartIndex = ushort.MaxValue;
private enum Renderer
private enum Renderer : short
{
// Default: Try all supported renderers (not necessarily the renderers shown here)
Default = default,

View File

@@ -11,6 +11,22 @@ namespace Robust.Client.Graphics.Clyde
{
static Clyde()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
RuntimeInformation.ProcessArchitecture == Architecture.X64)
{
try
{
// We force load nvapi64.dll so nvidia gives us the dedicated GPU on optimus laptops.
// This is 100x easier than nvidia's documented approach of NvOptimusEnablement,
// and works while developing.
NativeLibrary.Load("nvapi64.dll");
}
catch (Exception)
{
// If this fails whatever.
}
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return;

View File

@@ -970,7 +970,7 @@ namespace Robust.Client.Graphics.Clyde
public Color Color;
}
private enum RenderCommandType
private enum RenderCommandType : byte
{
DrawBatch,

View File

@@ -8,7 +8,7 @@ using Robust.Shared.Maths;
namespace Robust.Client.Graphics
{
public enum WindowMode
public enum WindowMode : byte
{
Windowed = 0,
Fullscreen = 1,

View File

@@ -6,7 +6,7 @@ namespace Robust.Client.Graphics.Drawing
/// <remarks>
/// See <see href="https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#drawing-point-lists">Vulkan's documentation</see> for descriptions of all these modes.
/// </remarks>
public enum DrawPrimitiveTopology
public enum DrawPrimitiveTopology : byte
{
PointList,
TriangleList,

View File

@@ -318,7 +318,7 @@ namespace Robust.Client.Graphics.Drawing
/// Describes margins of a style box.
/// </summary>
[Flags]
public enum Margin
public enum Margin : byte
{
None = 0,

View File

@@ -329,7 +329,7 @@ namespace Robust.Client.Graphics.Drawing
/// <summary>
/// Specifies how to stretch the sides and center of the style box.
/// </summary>
public enum StretchMode
public enum StretchMode : byte
{
Stretch,
Tile,

View File

@@ -98,7 +98,7 @@ namespace Robust.Client.Graphics.Overlays
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace
public enum OverlaySpace : byte
{
/// <summary>
/// Used for matching bit flags.

View File

@@ -1,6 +1,6 @@
namespace Robust.Client.Graphics.Shaders
{
public enum ShaderParamType
public enum ShaderParamType : byte
{
// Can this even happen?
Void = 0,

View File

@@ -106,7 +106,7 @@ namespace Robust.Client.Graphics.Shaders
[SuppressMessage("ReSharper", "InconsistentNaming")]
internal enum ShaderDataType
internal enum ShaderDataType : byte
{
Void,
Bool,
@@ -160,7 +160,7 @@ namespace Robust.Client.Graphics.Shaders
}
return Type.GetNativeType();
}
public bool TypePrecisionConsistent()
{
return Type.TypeHasPrecision() == (Precision != ShaderPrecisionQualifier.None);
@@ -190,7 +190,7 @@ namespace Robust.Client.Graphics.Shaders
throw new ArgumentOutOfRangeException(nameof(qualifier), qualifier, null);
}
}
public static bool TypeHasPrecision(this ShaderDataType type)
{
return
@@ -232,13 +232,13 @@ namespace Robust.Client.Graphics.Shaders
};
}
internal enum ShaderLightMode
internal enum ShaderLightMode : byte
{
Default = 0,
Unshaded = 1,
}
internal enum ShaderBlendMode
internal enum ShaderBlendMode : byte
{
None,
Mix,
@@ -247,7 +247,7 @@ namespace Robust.Client.Graphics.Shaders
Multiply
}
internal enum ShaderPreset
internal enum ShaderPreset : byte
{
Default,
Raw
@@ -255,7 +255,7 @@ namespace Robust.Client.Graphics.Shaders
// Yeah I had no idea what to name this.
[Flags]
internal enum ShaderParameterQualifiers
internal enum ShaderParameterQualifiers : byte
{
None = 0,
In = 1,
@@ -263,7 +263,7 @@ namespace Robust.Client.Graphics.Shaders
Inout = 3,
}
internal enum ShaderPrecisionQualifier
internal enum ShaderPrecisionQualifier : byte
{
None = 0,
Low = 1,

View File

@@ -593,7 +593,7 @@ namespace Robust.Client.Graphics.Shaders
public Symbols Symbol { get; }
}
private enum Symbols
private enum Symbols : byte
{
Semicolon,
Comma,

View File

@@ -296,7 +296,7 @@ namespace Robust.Client.Graphics.Shaders
}
}
private enum ShaderKind
private enum ShaderKind : byte
{
Source,
Canvas

View File

@@ -189,7 +189,7 @@ namespace Robust.Client.Graphics
/// Controls behavior when reading texture coordinates outside 0-1, which usually wraps the texture somehow.
/// </summary>
[PublicAPI]
public enum TextureWrapMode
public enum TextureWrapMode : byte
{
/// <summary>
/// Do not wrap, instead clamp to edge.

View File

@@ -10,7 +10,7 @@ namespace Robust.Client.Input
/// <summary>
/// Represents one of three mouse buttons.
/// </summary>
public enum Button
public enum Button : byte
{
Left = 1,
Middle = 2,

View File

@@ -772,14 +772,14 @@ namespace Robust.Client.Input
}
}
public enum KeyBindingType
public enum KeyBindingType : byte
{
Unknown = 0,
State,
Toggle,
}
public enum CommandState
public enum CommandState : byte
{
Unknown = 0,
Enabled,

View File

@@ -1,6 +1,6 @@
namespace Robust.Client.Interfaces.Graphics
{
internal enum ClydeDebugLayers
internal enum ClydeDebugLayers : byte
{
None,
Fov,

View File

@@ -1,6 +1,6 @@
namespace Robust.Client.Interfaces.Graphics
{
internal enum ClydeStockTexture
internal enum ClydeStockTexture : byte
{
White,
Black,

View File

@@ -3,7 +3,7 @@ namespace Robust.Client.Interfaces.Graphics
/// <summary>
/// Formats for the color component of a render target.
/// </summary>
public enum RenderTargetColorFormat
public enum RenderTargetColorFormat : byte
{
/// <summary>
/// 8 bits per channel linear RGBA.

View File

@@ -1,6 +1,6 @@
namespace Robust.Client.Interfaces.Graphics
{
public enum ScreenshotType
public enum ScreenshotType : byte
{
BeforeUI,
AfterUI

View File

@@ -3,7 +3,7 @@ namespace Robust.Client.Interfaces.Graphics
/// <summary>
/// OS-standard cursor shapes.
/// </summary>
public enum StandardCursorShape
public enum StandardCursorShape : byte
{
/// <summary>
/// The standard arrow shape. Used in almost all situations.

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Client.ResourceManagement;
using Robust.LoaderApi;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.Utility;
@@ -47,5 +48,7 @@ namespace Robust.Client.Interfaces.ResourceManagement
{
void TextureLoaded(TextureLoadedEventArgs eventArgs);
void RsiLoaded(RsiLoadedEventArgs eventArgs);
void MountLoaderApi(IFileApi api, string apiPrefix, ResourcePath? prefix=null);
}
}

View File

@@ -138,6 +138,13 @@ namespace Robust.Client.Interfaces.UserInterface
/// Hides the tooltip for the indicated control, if tooltip for that control is currently showing.
/// </summary>
void HideTooltipFor(Control control);
/// <summary>
/// If the control is currently showing a tooltip,
/// gets the tooltip that was supplied via TooltipSupplier (null if tooltip
/// was not supplied by tooltip supplier or tooltip is not showing for the control).
/// </summary>
Control? GetSuppliedTooltipFor(Control control);
}
}

View File

@@ -618,7 +618,7 @@ namespace Robust.Client.Placement
NetworkManager.ClientSendMessage(message);
}
public enum PlacementTypes
public enum PlacementTypes : byte
{
None = 0,
Line = 1,

View File

@@ -0,0 +1,58 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Robust.LoaderApi;
using Robust.Shared.Utility;
namespace Robust.Client.ResourceManagement
{
internal partial class ResourceCache
{
private sealed class LoaderApiLoader : IContentRoot
{
private readonly IFileApi _api;
private readonly string _prefix;
public LoaderApiLoader(IFileApi api, string prefix)
{
_api = api;
_prefix = prefix;
}
public void Mount()
{
}
public bool TryGetFile(ResourcePath relPath, [NotNullWhen(true)] out Stream? stream)
{
if (_api.TryOpen($"{_prefix}{relPath}", out stream))
{
return true;
}
stream = null;
return false;
}
public IEnumerable<ResourcePath> FindFiles(ResourcePath path)
{
foreach (var relPath in _api.AllFiles)
{
if (!relPath.StartsWith(_prefix))
continue;
var resP = new ResourcePath(relPath[_prefix.Length..]);
if (resP.TryRelativeTo(path, out _))
{
yield return resP;
}
}
}
public IEnumerable<string> GetRelativeFilePaths()
{
return _api.AllFiles;
}
}
}
}

View File

@@ -7,10 +7,11 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.LoaderApi;
namespace Robust.Client.ResourceManagement
{
internal class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable
internal partial class ResourceCache : ResourceManager, IResourceCacheInternal, IDisposable
{
private readonly Dictionary<Type, Dictionary<ResourcePath, BaseResource>> CachedResources =
new();
@@ -210,5 +211,12 @@ namespace Robust.Client.ResourceManagement
{
OnRsiLoaded?.Invoke(eventArgs);
}
public void MountLoaderApi(IFileApi api, string apiPrefix, ResourcePath? prefix=null)
{
prefix ??= ResourcePath.Root;
var root = new LoaderApiLoader(api, apiPrefix);
AddRoot(prefix, root);
}
}
}

View File

@@ -21,6 +21,7 @@
<PackageReference Include="OpenToolkit.Graphics" Version="4.0.0-pre9.1" />
<PackageReference Include="OpenToolkit.OpenAL" Version="4.0.0-pre9.1" />
<PackageReference Include="SpaceWizards.SharpFont" Version="1.0.1" />
<PackageReference Include="Robust.Natives" Version="0.1.0" />
</ItemGroup>
<ItemGroup Condition="'$(EnableClientScripting)' == 'True'">
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.8.0" />
@@ -32,6 +33,7 @@
<ItemGroup>
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />
<ProjectReference Include="..\OpenToolkit.GraphicsLibraryFramework\OpenToolkit.GraphicsLibraryFramework.csproj" />
<ProjectReference Include="..\Robust.LoaderApi\Robust.LoaderApi\Robust.LoaderApi.csproj" />
<ProjectReference Include="..\Robust.Physics\Robust.Physics.csproj" />
<ProjectReference Include="..\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
@@ -43,9 +45,4 @@
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
<PropertyGroup>
<RobustToolsPath>../Tools</RobustToolsPath>
</PropertyGroup>
<Target Name="RobustAfterBuild" DependsOnTargets="ClientAfterBuild" AfterTargets="Build" />
</Project>

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.UserInterface
/// <summary>
/// Default common cursor shapes available in the UI.
/// </summary>
public enum CursorShape
public enum CursorShape: byte
{
Arrow,
IBeam,

View File

@@ -193,7 +193,8 @@ namespace Robust.Client.UserInterface
/// <summary>
/// Simple text tooltip that is shown when the mouse is hovered over this control for a bit.
/// See <see cref="OnShowTooltip"/> for a more customizable alternative.
/// See <see cref="TooltipSupplier"/> or <see cref="OnShowTooltip"/> for a more customizable alternative.
/// No effect when TooltipSupplier is specified.
/// </summary>
/// <remarks>
/// If empty or null, no tooltip is shown in the first place (but OnShowTooltip and OnHideTooltip
@@ -201,9 +202,37 @@ namespace Robust.Client.UserInterface
/// </remarks>
public string? ToolTip { get; set; }
/// <summary>
/// Overrides the global tooltip delay, showing the tooltip for this
/// control within the specified number of seconds.
/// </summary>
public float? TooltipDelay { get; set; }
/// <summary>
/// When a tooltip should be shown for this control, this will be invoked to
/// produce a control which will serve as the tooltip (doing nothing if null is returned).
/// This is the generally recommended way to implement custom tooltips for controls, as it takes
/// care of the various edge cases for showing / hiding the tooltip.
/// For an even more customizable approach, <see cref="OnShowTooltip"/>
///
/// The returned control will be added to PopupRoot, and positioned
/// within the user interface under the current mouse position to avoid going off the edge of the
/// screen. When the tooltip should be hidden, the control will be hidden by removing it from the tree.
///
/// It is expected that the returned control remains within PopupRoot. Other classes should
/// not move it around in the tree or move it out of PopupRoot, but may access and modify
/// the control and its children via <see cref="SuppliedTooltip"/>.
/// </summary>
/// <remarks>
/// Returning a new instance of a tooltip control every time is usually fine. If for some
/// reason constructing the tooltip control is expensive, it MAY be fine to cache + reuse a single instance but this
/// approach has not yet been tested.
/// </remarks>
public TooltipSupplier? TooltipSupplier { get; set; }
/// <summary>
/// Invoked when the mouse is hovered over this control for a bit and a tooltip
/// should be shown. Can be used as an alternative to ToolTip to perform custom tooltip
/// should be shown. Can be used as an alternative to ToolTip or TooltipSupplier to perform custom tooltip
/// logic such as showing a more complex tooltip control.
///
/// Any custom tooltip controls should typically be added
@@ -213,6 +242,23 @@ namespace Robust.Client.UserInterface
/// </summary>
public event EventHandler? OnShowTooltip;
/// <summary>
/// If this control is currently showing a tooltip provided via TooltipSupplier,
/// returns that tooltip. Do not move this control within the tree, it should remain in PopupRoot.
/// Also, as it may be hidden (removed from tree) at any time, saving a reference to this is a Bad Idea.
/// </summary>
public Control? SuppliedTooltip => UserInterfaceManagerInternal.GetSuppliedTooltipFor(this);
/// <summary>
/// Manually hide the tooltip currently being shown for this control, if there is one.
/// </summary>
public void HideTooltip()
{
UserInterfaceManagerInternal.HideTooltipFor(this);
}
internal void PerformShowTooltip()
{
OnShowTooltip?.Invoke(this, EventArgs.Empty);
@@ -228,6 +274,7 @@ namespace Robust.Client.UserInterface
OnHideTooltip?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// The mode that controls how mouse filtering works. See the enum for how it functions.
/// </summary>
@@ -757,7 +804,7 @@ namespace Robust.Client.UserInterface
/// <summary>
/// Mode that will be tested when testing controls to invoke mouse button events on.
/// </summary>
public enum MouseFilterMode
public enum MouseFilterMode : byte
{
/// <summary>
/// The control will be able to receive mouse buttons events.
@@ -865,4 +912,6 @@ namespace Robust.Client.UserInterface
}
}
}
public delegate Control? TooltipSupplier(Control sender);
}

View File

@@ -318,7 +318,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
public enum DrawModeEnum
public enum DrawModeEnum : byte
{
Normal = 0,
Pressed = 1,
@@ -361,7 +361,7 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// For use with <see cref="BaseButton.Mode"/>.
/// </summary>
public enum ActionMode
public enum ActionMode : byte
{
/// <summary>
/// <see cref="BaseButton.OnPressed"/> fires when the mouse button causing them is pressed down.

View File

@@ -210,7 +210,7 @@ namespace Robust.Client.UserInterface.Controls
return new Vector2(minWidth, minHeight);
}
public enum AlignMode
public enum AlignMode : byte
{
/// <summary>
/// Controls are laid out from the begin of the box container.

View File

@@ -557,13 +557,13 @@ namespace Robust.Client.UserInterface.Controls
}
}
public enum Dimension
public enum Dimension : byte
{
Column,
Row
}
public enum LimitType
public enum LimitType : byte
{
/// <summary>
/// Defined number of rows or columns

View File

@@ -570,7 +570,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
public enum ItemListSelectMode
public enum ItemListSelectMode : byte
{
None,
Single,

View File

@@ -194,7 +194,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
public enum AlignMode
public enum AlignMode : byte
{
Left = 0,
Center = 1,
@@ -202,7 +202,7 @@ namespace Robust.Client.UserInterface.Controls
Fill = 3
}
public enum VAlignMode
public enum VAlignMode : byte
{
Top = 0,
Center = 1,

View File

@@ -126,7 +126,16 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// Sets an anchor AND a margin preset. This is most likely the method you want.
///
/// </summary>
/// <remarks>
/// Note that the current size and minimum size of the control affects how
/// each of the margins will be set, so if your control needs to shrink beyond its
/// current size / min size, you should either not call this method or only call it when your
/// control has a size of (0, 0). Otherwise your control's size will never be able
/// to go below the size implied by the margins set in this method.
/// </remarks>
public static void SetAnchorAndMarginPreset(Control control, LayoutPreset preset,
LayoutPresetMode mode = LayoutPresetMode.MinSize,
int margin = 0)
@@ -274,6 +283,12 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// Changes all the margins of a control at once to common presets.
/// The result is that the control is laid out as specified by the preset.
///
/// Note that the current size and minimum size of the control affects how
/// each of the margins will be set, so if your control needs to shrink beyond its
/// current size / min size, you should either not call this method or only call it when your
/// control has a size of (0, 0). Otherwise your control's size will never be able
/// to go below the size implied by the margins set in this method.
/// </summary>
/// <param name="preset"></param>
/// <param name="resizeMode"></param>

View File

@@ -711,7 +711,7 @@ namespace Robust.Client.UserInterface.Controls
return CharClass.Other;
}
private enum CharClass
private enum CharClass : byte
{
Other,
AlphaNumeric,

View File

@@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls
{
/// <summary>
/// Option button which allows toggling multiple elements.
/// </summary>
/// <typeparam name="TKey">type to use as the unique key for each option. Functions similarly
/// to dictionary key, so the type should make sure to respect dictionary key semantics.</typeparam>
public class MultiselectOptionButton<TKey> : ContainerButton where TKey : notnull
{
public const string StyleClassOptionButton = "optionButton";
public const string StyleClassOptionTriangle = "optionTriangle";
private List<ButtonData> _buttonData = new();
// map from key to buttondata index
private Dictionary<TKey, int> _keyMap = new();
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly Label _label;
public event Action<ItemPressedEventArgs>? OnItemSelected;
/// <summary>
/// Tracks the order in which items were selected, latest going at the end.
/// </summary>
private List<TKey> _selectedKeys = new();
/// <summary>
/// Ids of all currently selected items, ordered by most recently selected = last
/// </summary>
public IReadOnlyList<TKey> SelectedKeys => _selectedKeys;
public int ItemCount => _buttonData.Count;
/// <summary>
/// Labels of all currently selected items, ordered by most recently selected = last
/// </summary>
public IEnumerable<string?> SelectedLabels => _selectedKeys
.Select(key => _buttonData[_keyMap[key]].Button.Label.Text);
/// <summary>
/// Metadata of all currently selected items, ordered by most recently selected = last
/// </summary>
public IEnumerable<object?> SelectedMetadata => _selectedKeys
.Select(key => _buttonData[_keyMap[key]].Metadata);
public string? Label
{
get => _label.Text;
set => _label.Text = value;
}
public MultiselectOptionButton()
{
AddStyleClass(StyleClassButton);
OnPressed += OnPressedInternal;
var hBox = new HBoxContainer();
AddChild(hBox);
_popup = new Popup();
_popupVBox = new VBoxContainer();
_popup.AddChild(_popupVBox);
_popup.OnPopupHide += OnPopupHide;
_label = new Label
{
StyleClasses = { StyleClassOptionButton },
SizeFlagsHorizontal = SizeFlags.FillExpand,
};
hBox.AddChild(_label);
var textureRect = new TextureRect
{
StyleClasses = { StyleClassOptionTriangle },
SizeFlagsVertical = SizeFlags.ShrinkCenter,
};
hBox.AddChild(textureRect);
}
public void AddItem(Texture icon, string label, TKey key)
{
AddItem(label, key);
}
public void AddItem(string label, TKey key)
{
if (_keyMap.ContainsKey(key))
{
throw new ArgumentException("An item with the same key already exists.");
}
var button = new Button
{
Text = label,
ToggleMode = true
};
button.OnPressed += ButtonOnPressed;
var data = new ButtonData(label, button, key);
_keyMap.Add(key, _buttonData.Count);
_buttonData.Add(data);
_popupVBox.AddChild(button);
}
private void TogglePopup(bool show)
{
if (show)
{
var globalPos = GlobalPosition;
var (minX, minY) = _popupVBox.CombinedMinimumSize;
var box = UIBox2.FromDimensions(globalPos, (Math.Max(minX, Width), minY));
UserInterfaceManager.ModalRoot.AddChild(_popup);
_popup.Open(box);
}
else
{
_popup.Close();
}
}
private void OnPopupHide()
{
UserInterfaceManager.ModalRoot.RemoveChild(_popup);
}
private void ButtonOnPressed(ButtonEventArgs obj)
{
TogglePopup(false);
foreach (var buttonData in _buttonData)
{
if (buttonData.Button == obj.Button)
{
if (obj.Button.Pressed)
{
_selectedKeys.Add(buttonData.Key);
}
else
{
_selectedKeys.Remove(buttonData.Key);
}
OnItemSelected?.Invoke(new ItemPressedEventArgs(buttonData.Key, obj.Button.Pressed, this));
return;
}
}
// Not reachable.
throw new InvalidOperationException();
}
public void Clear()
{
_keyMap.Clear();
foreach (var buttonDatum in _buttonData)
{
buttonDatum.Button.OnPressed -= ButtonOnPressed;
}
_buttonData.Clear();
_popupVBox.DisposeAllChildren();
_selectedKeys = new List<TKey>();
}
public TKey GetItemKey(int idx)
{
return _buttonData[idx].Key;
}
public object? GetItemMetadata(int idx)
{
return _buttonData[idx].Metadata;
}
public bool IsItemDisabled(int idx)
{
return _buttonData[idx].Disabled;
}
public void RemoveItem(int idx)
{
var data = _buttonData[idx];
data.Button.OnPressed -= ButtonOnPressed;
_keyMap.Remove(data.Key);
_popupVBox.RemoveChild(data.Button);
_buttonData.RemoveAt(idx);
var newIdx = 0;
foreach (var buttonData in _buttonData)
{
_keyMap[buttonData.Key] = newIdx++;
}
}
public void Select(int idx)
{
var data = _buttonData[idx];
if (data.Button.Pressed) return;
_selectedKeys.Add(data.Key);
data.Button.Pressed = true;
}
public void SelectKey(TKey key)
{
Select(GetIdx(key));
}
public void DeselectAll()
{
foreach (var buttonData in _buttonData)
{
Deselect(buttonData);
}
}
public void Deselect(int idx)
{
Deselect(_buttonData[idx]);
}
public void DeselectKey(TKey key)
{
Deselect(GetIdx(key));
}
private void Deselect(ButtonData data)
{
if (!data.Button.Pressed) return;
_selectedKeys.Remove(data.Key);
data.Button.Pressed = false;
}
public int GetIdx(TKey key)
{
return _keyMap[key];
}
public void SetItemDisabled(int idx, bool disabled)
{
var data = _buttonData[idx];
data.Disabled = disabled;
data.Button.Disabled = disabled;
}
public void SetItemKey(int idx, TKey key)
{
if (_keyMap.TryGetValue(key, out var existIdx) && existIdx != idx)
{
throw new InvalidOperationException("An item with said key already exists.");
}
var data = _buttonData[idx];
_keyMap.Remove(data.Key);
_keyMap.Add(key, idx);
data.Key = key;
}
public void SetItemMetadata(int idx, object metadata)
{
_buttonData[idx].Metadata = metadata;
}
public void SetItemText(int idx, string text)
{
var data = _buttonData[idx];
data.Text = text;
data.Button.Text = text;
}
private void OnPressedInternal(ButtonEventArgs args)
{
TogglePopup(true);
}
protected override void ExitedTree()
{
base.ExitedTree();
TogglePopup(false);
}
public class ItemPressedEventArgs : EventArgs
{
public readonly MultiselectOptionButton<TKey> Button;
/// <summary>
/// True if item is being selected, false if being unselected
/// </summary>
public readonly bool Selected;
/// <summary>
/// True if item is being deselected, false if being selected
/// </summary>
public bool Deselected => !Selected;
/// <summary>
/// The key of the item that has been selected or deselected.
/// </summary>
public readonly TKey Key;
public ItemPressedEventArgs(TKey key, bool selected, MultiselectOptionButton<TKey> button)
{
Key = key;
Selected = selected;
Button = button;
}
}
private sealed class ButtonData
{
public string Text;
public bool Disabled;
public object? Metadata;
public TKey Key;
public Button Button;
public ButtonData(string text, Button button, TKey key)
{
Text = text;
Button = button;
Key = key;
}
}
}
}

View File

@@ -10,29 +10,31 @@ namespace Robust.Client.UserInterface.Controls
public const string StyleClassOptionButton = "optionButton";
public const string StyleClassOptionTriangle = "optionTriangle";
private List<ButtonData> _buttonData = new();
private Dictionary<int, int> _idMap = new();
private Popup _popup;
private VBoxContainer _popupVBox;
private Label _label;
private readonly List<ButtonData> _buttonData = new();
private readonly Dictionary<int, int> _idMap = new();
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly Label _label;
public int ItemCount => _buttonData.Count;
public event Action<ItemSelectedEventArgs>? OnItemSelected;
public string Prefix { get; set; }
public OptionButton() : base()
public OptionButton()
{
AddStyleClass(StyleClassButton);
Prefix = "";
OnPressed += _onPressed;
OnPressed += OnPressedInternal;
var hBox = new HBoxContainer();
AddChild(hBox);
_popup = new Popup();
UserInterfaceManager.ModalRoot.AddChild(_popup);
_popupVBox = new VBoxContainer();
_popup.AddChild(_popupVBox);
_popup.OnPopupHide += OnPopupHide;
_label = new Label
{
@@ -85,10 +87,31 @@ namespace Robust.Client.UserInterface.Controls
}
}
private void TogglePopup(bool show)
{
if (show)
{
var globalPos = GlobalPosition;
var (minX, minY) = _popupVBox.CombinedMinimumSize;
var box = UIBox2.FromDimensions(globalPos, (Math.Max(minX, Width), minY));
UserInterfaceManager.ModalRoot.AddChild(_popup);
_popup.Open(box);
}
else
{
_popup.Close();
}
}
private void OnPopupHide()
{
UserInterfaceManager.ModalRoot.RemoveChild(_popup);
}
private void ButtonOnPressed(ButtonEventArgs obj)
{
obj.Button.Pressed = false;
_popup.Visible = false;
TogglePopup(false);
foreach (var buttonData in _buttonData)
{
if (buttonData.Button == obj.Button)
@@ -105,13 +128,15 @@ namespace Robust.Client.UserInterface.Controls
public void Clear()
{
_idMap.Clear();
foreach (var buttonDatum in _buttonData)
{
buttonDatum.Button.OnPressed -= ButtonOnPressed;
}
_buttonData.Clear();
_popupVBox.DisposeAllChildren();
SelectedId = 0;
}
public int ItemCount => _buttonData.Count;
public int GetItemId(int idx)
{
return _buttonData[idx].Id;
@@ -134,9 +159,15 @@ namespace Robust.Client.UserInterface.Controls
public void RemoveItem(int idx)
{
var data = _buttonData[idx];
data.Button.OnPressed -= ButtonOnPressed;
_idMap.Remove(data.Id);
_popupVBox.RemoveChild(data.Button);
_buttonData.RemoveAt(idx);
var newIdx = 0;
foreach (var buttonData in _buttonData)
{
_idMap[buttonData.Id] = newIdx++;
}
}
public void Select(int idx)
@@ -168,13 +199,9 @@ namespace Robust.Client.UserInterface.Controls
data.Button.Disabled = disabled;
}
public void SetItemIcon(int idx, Texture texture)
{
}
public void SetItemId(int idx, int id)
{
if (_idMap.TryGetValue(id, out var existIdx) && existIdx != id)
if (_idMap.TryGetValue(id, out var existIdx) && existIdx != idx)
{
throw new InvalidOperationException("An item with said ID already exists.");
}
@@ -202,19 +229,15 @@ namespace Robust.Client.UserInterface.Controls
data.Button.Text = text;
}
private void _onPressed(ButtonEventArgs args)
private void OnPressedInternal(ButtonEventArgs args)
{
var globalPos = GlobalPosition;
var (minX, minY) = _popupVBox.CombinedMinimumSize;
var box = UIBox2.FromDimensions(globalPos, (Math.Max(minX, Width), minY));
_popup.Open(box);
TogglePopup(true);
}
protected override void Dispose(bool disposing)
protected override void ExitedTree()
{
base.Dispose(disposing);
_popup?.Dispose();
base.ExitedTree();
TogglePopup(false);
}
public class ItemSelectedEventArgs : EventArgs

View File

@@ -21,7 +21,10 @@ namespace Robust.Client.UserInterface.Controls
UserInterfaceManagerInternal.RemoveModal(this);
}
if (box != null && _desiredSize != box.Value.Size)
if (box != null &&
(_desiredSize != box.Value.Size ||
PopupContainer.GetPopupOrigin(this) != box.Value.TopLeft ||
PopupContainer.GetAltOrigin(this) != altPos))
{
PopupContainer.SetPopupOrigin(this, box.Value.TopLeft);
PopupContainer.SetAltOrigin(this, altPos);
@@ -34,6 +37,13 @@ namespace Robust.Client.UserInterface.Controls
UserInterfaceManagerInternal.PushModal(this);
}
public void Close()
{
if (!Visible) return;
UserInterfaceManagerInternal.RemoveModal(this);
}
protected internal override void ModalRemoved()
{
base.ModalRemoved();

View File

@@ -35,6 +35,16 @@ namespace Robust.Client.UserInterface.Controls
control.SetValue(PopupOriginProperty, origin);
}
public static Vector2 GetPopupOrigin(Control control)
{
return control.GetValue<Vector2>(PopupOriginProperty);
}
public static Vector2? GetAltOrigin(Control control)
{
return control.GetValue<Vector2?>(AltOriginProperty);
}
public static void SetAltOrigin(Control control, Vector2? origin)
{
control.SetValue(AltOriginProperty, origin);

View File

@@ -225,7 +225,7 @@ namespace Robust.Client.UserInterface.Controls
return _getGrabberStyleBox()?.MinimumSize ?? Vector2.Zero;
}
protected enum OrientationMode
protected enum OrientationMode : byte
{
Horizontal,
Vertical

View File

@@ -230,7 +230,7 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// Defines how user-initiated moving of the split should work
/// </summary>
public enum SplitResizeMode
public enum SplitResizeMode : sbyte
{
/// <summary>
/// Don't allow user to move the split.
@@ -249,7 +249,7 @@ namespace Robust.Client.UserInterface.Controls
/// <summary>
/// Defines how the split position should be determined
/// </summary>
private enum SplitState
private enum SplitState : byte
{
/// <summary>
/// Automatically adjust the split based on the width of the children

View File

@@ -173,7 +173,7 @@ namespace Robust.Client.UserInterface.Controls
}
}
public enum StretchMode
public enum StretchMode : byte
{
/// <summary>
/// The texture is stretched to fit the entire area of the control.

View File

@@ -255,7 +255,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
[Flags]
protected enum DragMode
protected enum DragMode : byte
{
None = 0,
Move = 1,

View File

@@ -77,8 +77,11 @@ namespace Robust.Client.UserInterface
private bool _rendering = true;
private float _tooltipTimer;
// set to null when not counting down
private float? _tooltipDelay;
private Tooltip _tooltip = default!;
private bool showingTooltip;
private Control? _suppliedTooltip;
private const float TooltipDelay = 1;
private readonly Queue<Control> _styleUpdateQueue = new();
@@ -208,10 +211,15 @@ namespace Robust.Client.UserInterface
control.DoLayoutUpdate();
}
_tooltipTimer -= args.DeltaSeconds;
if (_tooltipTimer <= 0)
// count down tooltip delay if we're not showing one yet and
// are hovering the mouse over a control without moving it
if (_tooltipDelay != null && !showingTooltip)
{
_showTooltip();
_tooltipTimer += args.DeltaSeconds;
if (_tooltipTimer >= _tooltipDelay)
{
_showTooltip();
}
}
if (_needUpdateActiveCursor)
@@ -332,6 +340,14 @@ namespace Robust.Client.UserInterface
CurrentlyHovered?.MouseExited();
CurrentlyHovered = newHovered;
CurrentlyHovered?.MouseEntered();
if (CurrentlyHovered != null)
{
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
}
else
{
_tooltipDelay = null;
}
_needUpdateActiveCursor = true;
}
@@ -509,6 +525,7 @@ namespace Robust.Client.UserInterface
{
control.MouseExited();
CurrentlyHovered = null;
_clearTooltip();
}
if (control == _controlFocused)
@@ -695,6 +712,11 @@ namespace Robust.Client.UserInterface
{
if (!showingTooltip) return;
_tooltip.Visible = false;
if (_suppliedTooltip != null)
{
PopupRoot.RemoveChild(_suppliedTooltip);
_suppliedTooltip = null;
}
CurrentlyHovered?.PerformHideTooltip();
_resetTooltipTimer();
showingTooltip = false;
@@ -709,9 +731,14 @@ namespace Robust.Client.UserInterface
}
}
public Control? GetSuppliedTooltipFor(Control control)
{
return CurrentlyHovered == control ? _suppliedTooltip : null;
}
private void _resetTooltipTimer()
{
_tooltipTimer = TooltipDelay;
_tooltipTimer = 0;
}
private void _showTooltip()
@@ -724,9 +751,19 @@ namespace Robust.Client.UserInterface
return;
}
// show simple tooltip if there is one
if (!String.IsNullOrWhiteSpace(hovered.ToolTip))
// show supplied tooltip if there is one
if (hovered.TooltipSupplier != null)
{
_suppliedTooltip = hovered.TooltipSupplier.Invoke(hovered);
if (_suppliedTooltip != null)
{
PopupRoot.AddChild(_suppliedTooltip);
Tooltips.PositionTooltip(_suppliedTooltip);
}
}
else if (!String.IsNullOrWhiteSpace(hovered.ToolTip))
{
// show simple tooltip if there is one
_tooltip.Visible = true;
_tooltip.Text = hovered.ToolTip;
Tooltips.PositionTooltip(_tooltip);

View File

@@ -67,7 +67,7 @@ namespace Robust.Client.ViewVariables.Editors
return convert.ToString(CultureInfo.InvariantCulture);
}
public enum NumberType
public enum NumberType : byte
{
Byte,
SByte,

View File

@@ -174,7 +174,7 @@ namespace Robust.Client.ViewVariables.Editors
return hBoxContainer;
}
public enum BoxType
public enum BoxType : byte
{
Box2,
Box2i,

1
Robust.LoaderApi Submodule

Submodule Robust.LoaderApi added at 0d5c015792

View File

@@ -38,13 +38,6 @@ namespace Robust.Server.ServerStatus
return false;
}
if (OnStatusRequest == null)
{
_httpSawmill.Warning("OnStatusRequest is not set, responding with a 501.");
response.Respond(method, "Not Implemented", HttpStatusCode.NotImplemented);
return true;
}
response.StatusCode = (int) HttpStatusCode.OK;
response.ContentType = "application/json";
@@ -53,7 +46,13 @@ namespace Robust.Server.ServerStatus
return true;
}
var jObject = new JObject();
var jObject = new JObject
{
// We need to send at LEAST name and player count to have the launcher work with us.
// Content can override these if it wants (e.g. stealthmins).
["name"] = _serverNameCache,
["players"] = _playerManager.PlayerCount
};
OnStatusRequest?.Invoke(jObject);
@@ -83,32 +82,29 @@ namespace Robust.Server.ServerStatus
return true;
}
var downloadUrlWindows = _configurationManager.GetCVar(CVars.BuildDownloadUrlWindows);
var downloadUrl = _configurationManager.GetCVar(CVars.BuildDownloadUrl);
JObject? buildInfo;
if (string.IsNullOrEmpty(downloadUrlWindows))
if (string.IsNullOrEmpty(downloadUrl))
{
buildInfo = null;
}
else
{
var hash = _configurationManager.GetCVar(CVars.BuildHash);
if (hash == "")
{
hash = null;
}
buildInfo = new JObject
{
["download_urls"] = new JObject
{
["Windows"] = downloadUrlWindows,
["MacOS"] = _configurationManager.GetCVar(CVars.BuildDownloadUrlMacOS),
["Linux"] = _configurationManager.GetCVar(CVars.BuildDownloadUrlLinux)
},
["engine_version"] = _configurationManager.GetCVar(CVars.BuildEngineVersion),
["fork_id"] = _configurationManager.GetCVar(CVars.BuildForkId),
["version"] = _configurationManager.GetCVar(CVars.BuildVersion),
["hashes"] = new JObject
{
["Windows"] = _configurationManager.GetCVar(CVars.BuildHashWindows),
["MacOS"] = _configurationManager.GetCVar(CVars.BuildHashMacOS),
["Linux"] = _configurationManager.GetCVar(CVars.BuildHashLinux),
},
["download_url"] = downloadUrl,
["hash"] = hash,
};
}

View File

@@ -7,8 +7,11 @@ using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Robust.Server.Interfaces;
using Robust.Server.Interfaces.Player;
using Robust.Server.Interfaces.ServerStatus;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.Log;
@@ -28,6 +31,8 @@ namespace Robust.Server.ServerStatus
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly IServerNetManager _netManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IBaseServer _baseServer = default!;
private static readonly JsonSerializer JsonSerializer = new();
private readonly List<StatusHostHandler> _handlers = new();
@@ -35,6 +40,8 @@ namespace Robust.Server.ServerStatus
private TaskCompletionSource? _stopSource;
private ISawmill _httpSawmill = default!;
private string? _serverNameCache;
public Task ProcessRequestAsync(HttpListenerContext context)
{
var response = context.Response;
@@ -85,6 +92,10 @@ namespace Robust.Server.ServerStatus
_httpSawmill = Logger.GetSawmill($"{Sawmill}.http");
RegisterCVars();
// Cache this in a field to avoid thread safety shenanigans.
// Writes/reads of references are atomic in C# so no further synchronization necessary.
_configurationManager.OnValueChanged(CVars.GameHostName, n => _serverNameCache = n);
if (!_configurationManager.GetCVar(CVars.StatusEnabled))
{
return;
@@ -141,24 +152,27 @@ namespace Robust.Server.ServerStatus
private void RegisterCVars()
{
BuildInfo? info = null;
try
{
var buildInfo = File.ReadAllText(PathHelpers.ExecutableRelativeFile("build.json"));
info = JsonConvert.DeserializeObject<BuildInfo>(buildInfo);
var info = JsonConvert.DeserializeObject<BuildInfo>(buildInfo);
// Don't replace cvars with contents of build.json if overriden by --cvar or such.
SetCVarIfUnmodified(CVars.BuildEngineVersion, info.EngineVersion);
SetCVarIfUnmodified(CVars.BuildForkId, info.ForkId);
SetCVarIfUnmodified(CVars.BuildVersion, info.Version);
SetCVarIfUnmodified(CVars.BuildDownloadUrl, info.Download ?? "");
SetCVarIfUnmodified(CVars.BuildHash, info.Hash ?? "");
}
catch (FileNotFoundException)
{
}
_configurationManager.SetCVar(CVars.BuildForkId, info?.ForkId ?? "");
_configurationManager.SetCVar(CVars.BuildVersion, info?.Version ?? "");
_configurationManager.SetCVar(CVars.BuildDownloadUrlWindows, info?.Downloads.Windows ?? "");
_configurationManager.SetCVar(CVars.BuildDownloadUrlMacOS, info?.Downloads.MacOS ?? "");
_configurationManager.SetCVar(CVars.BuildDownloadUrlLinux, info?.Downloads.Linux ?? "");
_configurationManager.SetCVar(CVars.BuildHashWindows, info?.Hashes.Windows ?? "");
_configurationManager.SetCVar(CVars.BuildHashMacOS, info?.Hashes.MacOS ?? "");
_configurationManager.SetCVar(CVars.BuildHashLinux, info?.Hashes.Linux ?? "");
void SetCVarIfUnmodified(CVarDef<string> cvar, string val)
{
if (_configurationManager.GetCVar(cvar) == "")
_configurationManager.SetCVar(cvar, val);
}
}
public void Dispose()
@@ -175,18 +189,11 @@ namespace Robust.Server.ServerStatus
[JsonObject(ItemRequired = Required.DisallowNull)]
private sealed class BuildInfo
{
[JsonProperty("hashes")] public PlatformData Hashes { get; set; } = default!;
[JsonProperty("downloads")] public PlatformData Downloads { get; set; } = default!;
[JsonProperty("fork_id")] public string ForkId { get; set; } = default!;
[JsonProperty("version")] public string Version { get; set; } = default!;
}
[JsonObject(ItemRequired = Required.DisallowNull)]
private sealed class PlatformData
{
[JsonProperty("windows")] public string Windows { get; set; } = default!;
[JsonProperty("linux")] public string Linux { get; set; } = default!;
[JsonProperty("macos")] public string MacOS { get; set; } = default!;
[JsonProperty("engine_version")] public string EngineVersion = default!;
[JsonProperty("hash")] public string? Hash;
[JsonProperty("download")] public string? Download;
[JsonProperty("fork_id")] public string ForkId = default!;
[JsonProperty("version")] public string Version = default!;
}
}
}

View File

@@ -87,8 +87,10 @@ namespace Robust.Shared.Maths
public Vector2 RotateVec(in Vector2 vec)
{
var (x, y) = vec;
var dx = Math.Cos(Theta) * x - Math.Sin(Theta) * y;
var dy = Math.Sin(Theta) * x + Math.Cos(Theta) * y;
var cos = Math.Cos(Theta);
var sin = Math.Sin(Theta);
var dx = cos * x - sin * y;
var dy = sin * x + cos * y;
return new Vector2((float)dx, (float)dy);
}
@@ -184,6 +186,11 @@ namespace Robust.Shared.Maths
return !(a == b);
}
public Angle Opposite()
{
return new Angle(FlipPositive(Theta-Math.PI));
}
public Angle FlipPositive()
{
return new(FlipPositive(Theta));

View File

@@ -10,7 +10,7 @@ namespace Robust.Shared.Maths
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Explicit)]
public struct Box2 : IEquatable<Box2>
public struct Box2 : IEquatable<Box2>, IApproxEquatable<Box2>
{
/// <summary>
/// The X coordinate of the left edge of the box.
@@ -336,5 +336,21 @@ namespace Robust.Shared.Maths
return new Vector2(cx, cy);
}
public bool EqualsApprox(Box2 other)
{
return MathHelper.CloseTo(Left, other.Left)
&& MathHelper.CloseTo(Bottom, other.Bottom)
&& MathHelper.CloseTo(Right, other.Right)
&& MathHelper.CloseTo(Top, other.Top);
}
public bool EqualsApprox(Box2 other, double tolerance)
{
return MathHelper.CloseTo(Left, other.Left, tolerance)
&& MathHelper.CloseTo(Bottom, other.Bottom, tolerance)
&& MathHelper.CloseTo(Right, other.Right, tolerance)
&& MathHelper.CloseTo(Top, other.Top, tolerance);
}
}
}

View File

@@ -1,4 +1,7 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Robust.Shared.Maths
{
@@ -10,6 +13,7 @@ namespace Robust.Shared.Maths
{
public Box2 Box;
public Angle Rotation;
/// <summary>
/// The point about which the rotation occurs.
/// </summary>
@@ -26,13 +30,19 @@ namespace Robust.Shared.Maths
public readonly Vector2 BottomLeft => Origin + Rotation.RotateVec(Box.BottomLeft - Origin);
public Box2Rotated(Vector2 bottomLeft, Vector2 topRight)
: this(new Box2(bottomLeft, topRight)) { }
: this(new Box2(bottomLeft, topRight))
{
}
public Box2Rotated(Box2 box)
: this(box, 0) { }
: this(box, 0)
{
}
public Box2Rotated(Box2 box, Angle rotation)
: this(box, rotation, Vector2.Zero) { }
: this(box, rotation, Vector2.Zero)
{
}
public Box2Rotated(Box2 box, Angle rotation, Vector2 origin)
{
@@ -46,24 +56,97 @@ namespace Robust.Shared.Maths
/// </summary>
public readonly Box2 CalcBoundingBox()
{
// https://stackoverflow.com/a/19830964
if (Sse.IsSupported && NumericsHelpers.Enabled)
{
return CalcBoundingBoxSse();
}
var (X0, Y0) = Box.BottomLeft;
var (X1, Y1) = Box.TopRight;
return CalcBoundingBoxSlow();
}
var Fi = Rotation.Theta;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly unsafe Box2 CalcBoundingBoxSse()
{
Vector128<float> boxVec;
fixed (float* lPtr = &Box.Left)
{
boxVec = Sse.LoadVector128(lPtr);
}
var CX = (X0 + X1) / 2; //Center point
var CY = (Y0 + Y1) / 2;
var WX = (X1 - X0) / 2; //Half-width
var WY = (Y1 - Y0) / 2;
var originX = Vector128.Create(Origin.X);
var originY = Vector128.Create(Origin.Y);
var SF = Math.Sin(Fi);
var CF = Math.Cos(Fi);
var cos = Vector128.Create((float) Math.Cos(Rotation));
var sin = Vector128.Create((float) Math.Sin(Rotation));
var NH = Math.Abs(WX * SF) + Math.Abs(WY * CF); //boundrect half-height
var NW = Math.Abs(WX * CF) + Math.Abs(WY * SF); //boundrect half-width
return new Box2((float) (CX - NW), (float) (CY - NH), (float) (CX + NW), (float) (CY + NH)); //draw bound rectangle
var allX = Sse.Shuffle(boxVec, boxVec, 0b10_10_00_00);
var allY = Sse.Shuffle(boxVec, boxVec, 0b01_11_11_01);
allX = Sse.Subtract(allX, originX);
allY = Sse.Subtract(allY, originY);
var modX = Sse.Subtract(Sse.Multiply(allX, cos), Sse.Multiply(allY, sin));
var modY = Sse.Add(Sse.Multiply(allX, sin), Sse.Multiply(allY, cos));
allX = Sse.Add(modX, originX);
allY = Sse.Add(modY, originY);
var l = SimdHelpers.MinHorizontalSse(allX);
var b = SimdHelpers.MinHorizontalSse(allY);
var r = SimdHelpers.MaxHorizontalSse(allX);
var t = SimdHelpers.MaxHorizontalSse(allY);
var lb = Sse.UnpackLow(l, b);
var rt = Sse.UnpackLow(r, t);
var lbrt = Sse.Shuffle(lb, rt, 0b11_10_01_00);
return Unsafe.As<Vector128<float>, Box2>(ref lbrt);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly unsafe Box2 CalcBoundingBoxSlow()
{
Span<float> allX = stackalloc float[4];
Span<float> allY = stackalloc float[4];
(allX[0], allY[0]) = BottomLeft;
(allX[1], allY[1]) = TopRight;
(allX[2], allY[2]) = TopLeft;
(allX[3], allY[3]) = BottomRight;
var X0 = allX[0];
var X1 = allX[0];
for (int i = 1; i < allX.Length; i++)
{
if (allX[i] > X1)
{
X1 = allX[i];
continue;
}
if (allX[i] < X0)
{
X0 = allX[i];
}
}
var Y0 = allY[0];
var Y1 = allY[0];
for (int i = 1; i < allY.Length; i++)
{
if (allY[i] > Y1)
{
Y1 = allY[i];
continue;
}
if (allY[i] < Y0)
{
Y0 = allY[i];
}
}
return new Box2(X0, Y0, X1, Y1);
}
#region Equality

View File

@@ -1009,7 +1009,7 @@ namespace Robust.Shared.Maths
}
[PublicAPI]
public enum BlendFactor
public enum BlendFactor : byte
{
Zero,
One,

View File

@@ -3,7 +3,7 @@
namespace Robust.Shared.Maths
{
[Flags]
public enum Direction
public enum Direction : sbyte
{
Invalid = -1,
East = 0,

View File

@@ -3,3 +3,6 @@
#if NET5_0
[module: SkipLocalsInit]
#endif
[assembly: InternalsVisibleTo("Robust.UnitTesting")]
[assembly: InternalsVisibleTo("Content.Benchmarks")]

View File

@@ -0,0 +1,29 @@
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace Robust.Shared.Maths
{
/// <summary>
/// Helper stuff for SIMD code.
/// </summary>
internal static class SimdHelpers
{
/// <returns>The min value is broadcast to the whole vector.</returns>
public static Vector128<float> MinHorizontalSse(Vector128<float> v)
{
var b = Sse.Shuffle(v, v, 0b10_11_00_01);
var m = Sse.Min(b, v);
var c = Sse.Shuffle(m, m, 0b01_00_11_10);
return Sse.Min(c, m);
}
/// <returns>The max value is broadcast to the whole vector.</returns>
public static Vector128<float> MaxHorizontalSse(Vector128<float> v)
{
var b = Sse.Shuffle(v, v, 0b10_11_00_01);
var m = Sse.Max(b, v);
var c = Sse.Shuffle(m, m, 0b01_00_11_10);
return Sse.Max(c, m);
}
}
}

View File

@@ -3,7 +3,7 @@ namespace Robust.Shared.Animations
/// <summary>
/// Specifies how animated properties are interpolated between two keyframes.
/// </summary>
public enum AnimationInterpolationMode
public enum AnimationInterpolationMode: byte
{
/// <summary>
/// Use a linear interpolation for supported values.

View File

@@ -118,29 +118,20 @@ namespace Robust.Shared
* BUILD
*/
public static readonly CVarDef<string> BuildEngineVersion =
CVarDef.Create("build.engine_version", "", CVar.SERVERONLY);
public static readonly CVarDef<string> BuildForkId =
CVarDef.Create("build.fork_id", "", CVar.ARCHIVE | CVar.SERVERONLY);
CVarDef.Create("build.fork_id", "", CVar.SERVERONLY);
public static readonly CVarDef<string> BuildVersion =
CVarDef.Create("build.version", "", CVar.ARCHIVE | CVar.SERVERONLY);
CVarDef.Create("build.version", "", CVar.SERVERONLY);
public static readonly CVarDef<string> BuildDownloadUrlWindows =
CVarDef.Create("build.download_url_windows", string.Empty, CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<string> BuildDownloadUrl =
CVarDef.Create("build.download_url", string.Empty, CVar.SERVERONLY);
public static readonly CVarDef<string> BuildDownloadUrlMacOS =
CVarDef.Create("build.download_url_macos", "", CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<string> BuildDownloadUrlLinux =
CVarDef.Create("build.download_url_linux", "", CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<string> BuildHashWindows =
CVarDef.Create("build.hash_windows", "", CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<string> BuildHashMacOS =
CVarDef.Create("build.hash_macos", "", CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<string> BuildHashLinux =
CVarDef.Create("build.hash_linux", "", CVar.ARCHIVE | CVar.SERVERONLY);
public static readonly CVarDef<string> BuildHash =
CVarDef.Create("build.hash", "", CVar.SERVERONLY);
/*
* WATCHDOG
@@ -242,10 +233,10 @@ namespace Robust.Shared
CVarDef.Create("display.height", 720, CVar.CLIENTONLY);
public static readonly CVarDef<int> DisplayLightMapDivider =
CVarDef.Create("display.lightmapdivider", 2, CVar.CLIENTONLY);
CVarDef.Create("display.lightmapdivider", 2, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<bool> DisplaySoftShadows =
CVarDef.Create("display.softshadows", true, CVar.CLIENTONLY);
CVarDef.Create("display.softshadows", true, CVar.CLIENTONLY | CVar.ARCHIVE);
public static readonly CVarDef<float> DisplayUIScale =
CVarDef.Create("display.uiScale", 0f, CVar.ARCHIVE | CVar.CLIENTONLY);
@@ -270,7 +261,7 @@ namespace Robust.Shared
CVarDef.Create("audio.device", string.Empty, CVar.CLIENTONLY);
public static readonly CVarDef<float> AudioMasterVolume =
CVarDef.Create("audio.mastervolume", 1.0f, CVar.CLIENTONLY);
CVarDef.Create("audio.mastervolume", 1.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
/*
* PLAYER

View File

@@ -6,7 +6,7 @@ namespace Robust.Shared.Configuration
/// Extra flags for changing the behavior of a config var.
/// </summary>
[Flags]
public enum CVar
public enum CVar : short
{
/// <summary>
/// No special flags.

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Robust.Shared.Utility;
namespace Robust.Shared.Configuration
{
@@ -203,6 +204,9 @@ namespace Robust.Shared.Configuration
private void RegisterCVar(string name, Type type, object? defaultValue, CVar flags, Action<object>? onValueChanged)
{
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
$"{name}: Enum cvars must have int as underlying type.");
var only = _isServer ? CVar.CLIENTONLY : CVar.SERVERONLY;
if ((flags & only) != 0)

View File

@@ -108,7 +108,7 @@ namespace Robust.Shared.ContentPack
}
#pragma warning restore 649
private enum InheritMode
private enum InheritMode : byte
{
// Allow if All is set, block otherwise
Default,

View File

@@ -181,6 +181,7 @@ namespace Robust.Shared.ContentPack
}
}
// Normal single dimensional array with zero lower bound.
internal sealed record MTypeSZArray(MType ElementType) : MType
{
public override string ToString()
@@ -199,7 +200,8 @@ namespace Robust.Shared.ContentPack
}
}
internal sealed record MTypeArray(MType ElementType, ArrayShape Shape) : MType
// Multi-dimension arrays with funny lower and upper bounds.
internal sealed record MTypeWackyArray(MType ElementType, ArrayShape Shape) : MType
{
public override string ToString()
{
@@ -213,7 +215,7 @@ namespace Robust.Shared.ContentPack
public override bool WhitelistEquals(MType other)
{
return other is MTypeArray arr && ShapesEqual(Shape, arr.Shape) && ElementType.WhitelistEquals(arr);
return other is MTypeWackyArray arr && ShapesEqual(Shape, arr.Shape) && ElementType.WhitelistEquals(arr);
}
private static bool ShapesEqual(in ArrayShape a, in ArrayShape b)

View File

@@ -11,11 +11,17 @@ using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Threading.Tasks;
using ILVerify;
using Pidgin;
using Robust.Shared.Interfaces.Log;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.Log;
using Robust.Shared.Utility;
// psst
// You know ECMA-335 right? The specification for the CLI that .NET runs on?
// Yeah, you need it to understand a lot of this code. So get a copy.
// You know the cool thing?
// ISO has a version that has correct PDF metadata so there's an actual table of contents.
// Right here: https://standards.iso.org/ittf/PubliclyAvailableStandards/c058046_ISO_IEC_23271_2012(E).zip
namespace Robust.Shared.ContentPack
{
/// <summary>
@@ -28,29 +34,46 @@ namespace Robust.Shared.ContentPack
/// <summary>
/// Completely disables type checking, allowing everything.
/// </summary>
public bool DisableTypeCheck { get; set; } = false;
public bool DisableTypeCheck { get; init; }
public DumpFlags Dump { get; set; } = DumpFlags.None;
public bool VerifyIL { get; set; } = true;
public DumpFlags Dump { get; init; } = DumpFlags.None;
public bool VerifyIL { get; init; } = true;
private bool WouldNoOp => Dump == DumpFlags.None && DisableTypeCheck && !VerifyIL;
public AssemblyTypeChecker(IResourceManager res)
// Necessary for loads with launcher loader.
public Func<string, Stream?>? ExtraRobustLoader { get; init; }
private readonly ISawmill _sawmill;
private readonly SandboxConfig _config;
public AssemblyTypeChecker(IResourceManager res, ISawmill sawmill)
{
_res = res;
_sawmill = sawmill;
_config = LoadConfig();
}
private Resolver CreateResolver()
{
var dotnetDir = Path.GetDirectoryName(typeof(int).Assembly.Location)!;
var ourDir = Path.GetDirectoryName(typeof(AssemblyTypeChecker).Assembly.Location)!;
var ourPath = typeof(AssemblyTypeChecker).Assembly.Location;
string[] loadDirs;
if (string.IsNullOrEmpty(ourPath))
{
_sawmill.Debug("Robust directory not available");
loadDirs = new[] {dotnetDir};
}
else
{
_sawmill.Debug("Robust directory is {0}", ourPath);
loadDirs = new[] {dotnetDir, Path.GetDirectoryName(ourPath)!};
}
Logger.DebugS("res.typecheck", ".NET runtime directory is {0}", dotnetDir);
Logger.DebugS("res.typecheck", "Robust directory is {0}", ourDir);
_sawmill.Debug(".NET runtime directory is {0}", dotnetDir);
return new Resolver(
this,
new[] {dotnetDir, ourDir},
loadDirs,
new[] {new ResourcePath("/Assemblies/")}
);
}
@@ -69,17 +92,18 @@ namespace Robust.Shared.ContentPack
return true;
}
Logger.DebugS("res.typecheck", "Checking assembly...");
_sawmill.Debug("Checking assembly...");
var fullStopwatch = Stopwatch.StartNew();
var config = LoadConfig();
var resolver = CreateResolver();
using var peReader = new PEReader(assembly, PEStreamOptions.LeaveOpen);
var reader = peReader.GetMetadataReader();
var asmName = reader.GetString(reader.GetAssemblyDefinition().Name);
if (VerifyIL)
{
if (!DoVerifyIL(resolver, config, peReader, reader))
if (!DoVerifyIL(asmName, resolver, peReader, reader))
{
return false;
}
@@ -90,13 +114,13 @@ namespace Robust.Shared.ContentPack
var types = GetReferencedTypes(reader, errors);
var members = GetReferencedMembers(reader, errors);
var inherited = GetExternalInheritedTypes(reader, errors);
Logger.DebugS("res.typecheck", $"References loaded... {fullStopwatch.ElapsedMilliseconds}ms");
_sawmill.Debug($"References loaded... {fullStopwatch.ElapsedMilliseconds}ms");
if ((Dump & DumpFlags.Types) != 0)
{
foreach (var mType in types)
{
Logger.DebugS("res.typecheck", $"RefType: {mType}");
_sawmill.Debug($"RefType: {mType}");
}
}
@@ -104,7 +128,7 @@ namespace Robust.Shared.ContentPack
{
foreach (var memberRef in members)
{
Logger.DebugS("res.typecheck", $"RefMember: {memberRef}");
_sawmill.Debug($"RefMember: {memberRef}");
}
}
@@ -112,10 +136,10 @@ namespace Robust.Shared.ContentPack
{
foreach (var (name, baseType, interfaces) in inherited)
{
Logger.DebugS("res.typecheck", $"Inherit: {name} -> {baseType}");
_sawmill.Debug($"Inherit: {name} -> {baseType}");
foreach (var @interface in interfaces)
{
Logger.DebugS("res.typecheck", $" Interface: {@interface}");
_sawmill.Debug($" Interface: {@interface}");
}
}
}
@@ -130,46 +154,49 @@ namespace Robust.Shared.ContentPack
// we won't have to check that any types in their type arguments are whitelisted.
foreach (var type in types)
{
if (!IsTypeAccessAllowed(type, config, out _))
if (!IsTypeAccessAllowed(type, out _))
{
errors.Add(new SandboxError($"Access to type not allowed: {type}"));
}
}
Logger.DebugS("res.typecheck", $"Types... {fullStopwatch.ElapsedMilliseconds}ms");
_sawmill.Debug($"Types... {fullStopwatch.ElapsedMilliseconds}ms");
CheckInheritance(inherited, errors, config);
CheckInheritance(inherited, errors);
Logger.DebugS("res.typecheck", $"Inheritance... {fullStopwatch.ElapsedMilliseconds}ms");
_sawmill.Debug($"Inheritance... {fullStopwatch.ElapsedMilliseconds}ms");
CheckMemberReferences(members, config, errors);
CheckMemberReferences(members, errors);
foreach (var error in errors)
{
Logger.ErrorS("res.typecheck", $"Sandbox violation: {error.Message}");
_sawmill.Error($"Sandbox violation: {error.Message}");
}
Logger.DebugS("res.typecheck", $"Checked assembly in {fullStopwatch.ElapsedMilliseconds}ms");
_sawmill.Debug($"Checked assembly in {fullStopwatch.ElapsedMilliseconds}ms");
return errors.IsEmpty;
}
private static bool DoVerifyIL(Resolver resolver, SandboxConfig config, PEReader peReader,
private bool DoVerifyIL(
string name,
IResolver resolver,
PEReader peReader,
MetadataReader reader)
{
Logger.DebugS("res.typecheck", "Verifying IL...");
_sawmill.Debug($"{name}: Verifying IL...");
var sw = Stopwatch.StartNew();
var ver = new Verifier(resolver);
ver.SetSystemModuleName(new AssemblyName(config.SystemAssemblyName));
ver.SetSystemModuleName(new AssemblyName(_config.SystemAssemblyName));
var verifyErrors = false;
foreach (var res in ver.Verify(peReader))
{
if (config.AllowedVerifierErrors.Contains(res.Code))
if (_config.AllowedVerifierErrors.Contains(res.Code))
{
continue;
}
var msg = $"ILVerify: {res.Message}";
var msg = $"{name}: ILVerify: {res.Message}";
try
{
@@ -193,14 +220,14 @@ namespace Robust.Shared.ContentPack
}
catch (UnsupportedMetadataException e)
{
Logger.ErrorS("res.typecheck", $"{e}");
_sawmill.Error($"{e}");
}
verifyErrors = true;
Logger.ErrorS("res.typecheck", msg);
_sawmill.Error(msg);
}
Logger.DebugS("res.typecheck", $"Verified IL in {sw.Elapsed.TotalMilliseconds}ms");
_sawmill.Debug($"{name}: Verified IL in {sw.Elapsed.TotalMilliseconds}ms");
if (verifyErrors)
{
@@ -210,7 +237,8 @@ namespace Robust.Shared.ContentPack
return true;
}
private static void CheckMemberReferences(List<MMemberRef> members, SandboxConfig config,
private void CheckMemberReferences(
List<MMemberRef> members,
ConcurrentBag<SandboxError> errors)
{
Parallel.ForEach(members, memberRef =>
@@ -226,15 +254,11 @@ namespace Robust.Shared.ContentPack
break;
}
case MTypeArray array:
case MTypeWackyArray:
{
// For this kind of array we just need access to the type itself.
if (!IsTypeAccessAllowed((MTypeReferenced) array.ElementType, config, out _))
{
errors.Add(new SandboxError($"Access to type not allowed: {array}"));
}
return; // Found
// Members on arrays (not to be confused with vectors) are all fine.
// See II.14.2 in ECMA-335.
return;
}
default:
{
@@ -245,8 +269,11 @@ namespace Robust.Shared.ContentPack
var baseTypeReferenced = (MTypeReferenced) baseType;
if (!IsTypeAccessAllowed(baseTypeReferenced, config, out var typeCfg))
if (!IsTypeAccessAllowed(baseTypeReferenced, out var typeCfg))
{
// Technically this error isn't necessary since we have an earlier pass
// checking all referenced types. That should have caught this
// We still need the typeCfg so that's why we're checking. Might as well.
errors.Add(new SandboxError($"Access to type not allowed: {baseTypeReferenced}"));
return;
}
@@ -306,9 +333,9 @@ namespace Robust.Shared.ContentPack
});
}
private static void CheckInheritance(
private void CheckInheritance(
List<(MType type, MType parent, ArraySegment<MType> interfaceImpls)> inherited,
ConcurrentBag<SandboxError> errors, SandboxConfig config)
ConcurrentBag<SandboxError> errors)
{
// This inheritance whitelisting primarily serves to avoid content doing funny stuff
// by e.g. inheriting Type.
@@ -336,7 +363,7 @@ namespace Robust.Shared.ContentPack
_ => throw new InvalidOperationException() // Can't happen.
};
if (!IsTypeAccessAllowed(realBaseType, config, out var cfg))
if (!IsTypeAccessAllowed(realBaseType, out var cfg))
{
return false;
}
@@ -346,14 +373,13 @@ namespace Robust.Shared.ContentPack
}
}
private static bool IsTypeAccessAllowed(MTypeReferenced type, SandboxConfig config,
[NotNullWhen(true)] out TypeConfig? cfg)
private bool IsTypeAccessAllowed(MTypeReferenced type, [NotNullWhen(true)] out TypeConfig? cfg)
{
if (type.Namespace == null)
{
if (type.ResolutionScope is MResScopeType parentType)
{
if (!IsTypeAccessAllowed((MTypeReferenced) parentType.Type, config, out var parentCfg))
if (!IsTypeAccessAllowed((MTypeReferenced) parentType.Type, out var parentCfg))
{
cfg = null;
return false;
@@ -383,7 +409,7 @@ namespace Robust.Shared.ContentPack
}
// Check if in whitelisted namespaces.
foreach (var whNamespace in config.WhitelistedNamespaces)
foreach (var whNamespace in _config.WhitelistedNamespaces)
{
if (type.Namespace.StartsWith(whNamespace))
{
@@ -392,7 +418,7 @@ namespace Robust.Shared.ContentPack
}
}
if (!config.Types.TryGetValue(type.Namespace, out var nsDict))
if (!_config.Types.TryGetValue(type.Namespace, out var nsDict))
{
cfg = null;
return false;
@@ -733,6 +759,12 @@ namespace Robust.Shared.ContentPack
}
}
var extraStream = _parent.ExtraRobustLoader?.Invoke(dllName);
if (extraStream != null)
{
return new PEReader(extraStream);
}
foreach (var resLoadPath in _resLoadPaths)
{
try
@@ -758,7 +790,7 @@ namespace Robust.Shared.ContentPack
public MType GetArrayType(MType elementType, ArrayShape shape)
{
return new MTypeArray(elementType, shape);
return new MTypeWackyArray(elementType, shape);
}
public MType GetByReferenceType(MType elementType)
@@ -839,7 +871,7 @@ namespace Robust.Shared.ContentPack
}
[Flags]
public enum DumpFlags
public enum DumpFlags : byte
{
None = 0,
Types = 1,

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
@@ -41,12 +42,13 @@ namespace Robust.Shared.ContentPack
/// </summary>
/// <param name="assembly">Byte array of the assembly.</param>
/// <param name="symbols">Optional byte array of the debug symbols.</param>
void LoadGameAssembly(Stream assembly, Stream? symbols = null);
/// <param name="skipVerify">Whether to skip checking the loaded assembly for sandboxing.</param>
void LoadGameAssembly(Stream assembly, Stream? symbols = null, bool skipVerify = false);
/// <summary>
/// Loads an assembly into the current AppDomain.
/// </summary>
void LoadGameAssembly(string diskPath);
void LoadGameAssembly(string diskPath, bool skipVerify = false);
/// <summary>
/// Broadcasts a run level change to all loaded entry point.
@@ -65,5 +67,7 @@ namespace Robust.Shared.ContentPack
void SetUseLoadContext(bool useLoadContext);
void SetEnableSandboxing(bool sandboxing);
Func<string, Stream?>? VerifierExtraLoadHandler { get; set; }
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -8,6 +9,7 @@ using System.Reflection.PortableExecutable;
using System.Runtime.ExceptionServices;
using System.Runtime.Loader;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Interfaces.Log;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.IoC;
@@ -19,13 +21,11 @@ namespace Robust.Shared.ContentPack
/// <summary>
/// Class for managing the loading of assemblies into the engine.
/// </summary>
internal sealed class ModLoader : BaseModLoader, IModLoaderInternal, IDisposable, IPostInjectInit
internal sealed class ModLoader : BaseModLoader, IModLoaderInternal, IDisposable
{
[Dependency] private readonly IResourceManagerInternal _res = default!;
[Dependency] private readonly ILogManager _logManager = default!;
private AssemblyTypeChecker _typeChecker = default!;
// List of extra assemblies side-loaded from the /Assemblies/ mounted path.
private readonly List<Assembly> _sideModules = new();
@@ -50,11 +50,6 @@ namespace Robust.Shared.ContentPack
AssemblyLoadContext.Default.Resolving += DefaultOnResolving;
}
void IPostInjectInit.PostInject()
{
_typeChecker = new AssemblyTypeChecker(_res);
}
public void SetUseLoadContext(bool useLoadContext)
{
_useLoadContext = useLoadContext;
@@ -64,13 +59,15 @@ namespace Robust.Shared.ContentPack
public void SetEnableSandboxing(bool sandboxing)
{
_sandboxingEnabled = sandboxing;
_typeChecker.VerifyIL = sandboxing;
_typeChecker.DisableTypeCheck = !sandboxing;
Logger.DebugS("res", "{0} sandboxing", sandboxing ? "ENABLING" : "DISABLING");
}
public Func<string, Stream?>? VerifierExtraLoadHandler { get; set; }
public bool TryLoadModulesFrom(ResourcePath mountPath, string filterPrefix)
{
var sw = Stopwatch.StartNew();
Logger.DebugS("res.mod", "LOADING modules");
var files = new Dictionary<string, (ResourcePath Path, string[] references)>();
// Find all modules we want to load.
@@ -91,6 +88,22 @@ namespace Robust.Shared.ContentPack
}
}
if (_sandboxingEnabled)
{
var typeChecker = MakeTypeChecker();
Parallel.ForEach(files, pair =>
{
var (name, (path, _)) = pair;
using var stream = _res.ContentFileRead(path);
if (!typeChecker.CheckAssembly(stream))
{
throw new TypeCheckFailedException($"Assembly {name} failed type checks.");
}
});
}
// Actually load them in the order they depend on each other.
foreach (var path in TopologicalSortModules(files))
{
@@ -101,13 +114,13 @@ namespace Robust.Shared.ContentPack
// This probably improves performance or something and makes debugging etc more reliable.
if (_res.TryGetDiskFilePath(path, out var diskPath))
{
LoadGameAssembly(diskPath);
LoadGameAssembly(diskPath, skipVerify: true);
}
else
{
var assemblyStream = _res.ContentFileRead(path);
var symbolsStream = _res.ContentFileReadOrNull(path.WithExtension("pdb"));
LoadGameAssembly(assemblyStream, symbolsStream);
LoadGameAssembly(assemblyStream, symbolsStream, skipVerify: true);
}
}
catch (Exception e)
@@ -116,6 +129,7 @@ namespace Robust.Shared.ContentPack
return false;
}
}
Logger.DebugS("res.mod", $"DONE loading modules: {sw.Elapsed}");
return true;
}
@@ -164,9 +178,9 @@ namespace Robust.Shared.ContentPack
.Select(a => metaReader.GetString(a.Name)).ToArray(), name);
}
public void LoadGameAssembly(Stream assembly, Stream? symbols = null)
public void LoadGameAssembly(Stream assembly, Stream? symbols = null, bool skipVerify = false)
{
if (!_typeChecker.CheckAssembly(assembly))
if (!skipVerify && !MakeTypeChecker().CheckAssembly(assembly))
{
throw new TypeCheckFailedException();
}
@@ -186,9 +200,9 @@ namespace Robust.Shared.ContentPack
InitMod(gameAssembly);
}
public void LoadGameAssembly(string diskPath)
public void LoadGameAssembly(string diskPath, bool skipVerify = false)
{
if (!_typeChecker.CheckAssembly(diskPath))
if (!skipVerify && !MakeTypeChecker().CheckAssembly(diskPath))
{
throw new TypeCheckFailedException();
}
@@ -215,7 +229,7 @@ namespace Robust.Shared.ContentPack
Logger.DebugS("srv", $"Loading {assemblyName} DLL");
try
{
LoadGameAssembly(path);
LoadGameAssembly(path, skipVerify: false);
return true;
}
catch (Exception e)
@@ -343,5 +357,15 @@ namespace Robust.Shared.ContentPack
return null;
}
private AssemblyTypeChecker MakeTypeChecker()
{
return new(_res, Logger.GetSawmill("res.typecheck"))
{
VerifyIL = _sandboxingEnabled,
DisableTypeCheck = !_sandboxingEnabled,
ExtraRobustLoader = VerifierExtraLoadHandler
};
}
}
}

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Run levels of the Content entry point.
/// </summary>
public enum ModRunLevel
public enum ModRunLevel: byte
{
Error = 0,
Init = 1,

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Levels at which point the content assemblies are getting updates.
/// </summary>
public enum ModUpdateLevel
public enum ModUpdateLevel : byte
{
/// <summary>
/// This update is called before the main state manager on process frames.

View File

@@ -70,7 +70,8 @@ namespace Robust.Shared.ContentPack
{
prefix = SanitizePrefix(prefix);
pack = PathHelpers.ExecutableRelativeFile(pack);
if (!Path.IsPathRooted(pack))
pack = PathHelpers.ExecutableRelativeFile(pack);
var packInfo = new FileInfo(pack);
@@ -93,7 +94,7 @@ namespace Robust.Shared.ContentPack
AddRoot(prefix, loader);
}
private void AddRoot(ResourcePath prefix, IContentRoot loader)
protected void AddRoot(ResourcePath prefix, IContentRoot loader)
{
loader.Mount();
_contentRootsLock.EnterWriteLock();
@@ -126,7 +127,9 @@ namespace Robust.Shared.ContentPack
{
prefix = SanitizePrefix(prefix);
path = PathHelpers.ExecutableRelativeFile(path);
if (!Path.IsPathRooted(path))
path = PathHelpers.ExecutableRelativeFile(path);
var pathInfo = new DirectoryInfo(path);
if (!pathInfo.Exists)
{
@@ -293,22 +296,8 @@ namespace Robust.Shared.ContentPack
public void MountStreamAt(MemoryStream stream, ResourcePath path)
{
if (!path.IsRooted)
{
throw new ArgumentException("Path must be rooted.", nameof(path));
}
var loader = new SingleStreamLoader(stream, path.ToRelativePath());
loader.Mount();
_contentRootsLock.EnterWriteLock();
try
{
_contentRoots.Add((ResourcePath.Root, loader));
}
finally
{
_contentRootsLock.ExitWriteLock();
}
AddRoot(ResourcePath.Root, loader);
}
internal static bool IsPathValid(ResourcePath path)

View File

@@ -845,6 +845,7 @@ Types:
Func`15: { All: True }
Func`16: { All: True }
Func`17: { All: True }
Guid: { All: True }
HashCode: { All: True }
IAsyncDisposable: { All: True }
IAsyncResult: { }
@@ -886,6 +887,7 @@ Types:
MulticastDelegate:
Inherit: Allow
NotSupportedException: { All: True }
Nullable: { All: True }
Nullable`1: { All: True }
NullReferenceException: { All: True }
Object: { All: True }

View File

@@ -1,6 +1,6 @@
namespace Robust.Shared.Enums
{
public enum PlacementManagerMessage
public enum PlacementManagerMessage : byte
{
StartPlacement,
CancelPlacement,
@@ -18,7 +18,7 @@
Disconnected
}
public enum NetworkDataType
public enum NetworkDataType: byte
{
d_enum,
d_bool,

View File

@@ -505,7 +505,7 @@ namespace Robust.Shared.GameObjects.Components
}
[Serializable, NetSerializable]
public enum BodyStatus
public enum BodyStatus: byte
{
OnGround,
InAir

View File

@@ -196,7 +196,7 @@ namespace Robust.Shared.GameObjects.Components.Transform
}
}
public enum SnapGridOffset
public enum SnapGridOffset: byte
{
/// <summary>
/// Center snap grid (wires, pipes, ...).

View File

@@ -5,6 +5,7 @@ using Robust.Shared.Animations;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.GameObjects.EntitySystemMessages;
using Robust.Shared.GameObjects.Systems;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
@@ -722,8 +723,9 @@ namespace Robust.Shared.GameObjects.Components.Transform
var oldPos = Coordinates;
SetPosition(newState.LocalPosition);
Owner.EntityManager.EventBus.RaiseEvent(
EventSource.Local, new MoveEvent(Owner, oldPos, Coordinates));
var ev = new MoveEvent(Owner, oldPos, Coordinates);
EntitySystem.Get<SharedTransformSystem>().DeferMoveEvent(ev);
rebuildMatrices = true;
}
@@ -873,6 +875,7 @@ namespace Robust.Shared.GameObjects.Components.Transform
public IEntity Sender { get; }
public EntityCoordinates OldPosition { get; }
public EntityCoordinates NewPosition { get; }
public bool Handled { get; set; }
}
public class RotateEvent : EntitySystemMessage

View File

@@ -83,7 +83,7 @@ namespace Robust.Shared.GameObjects
}
[Flags]
public enum EventSource
public enum EventSource : byte
{
None = 0b0000,
Local = 0b0001,

View File

@@ -672,7 +672,7 @@ namespace Robust.Shared.GameObjects
}
public enum EntityMessageType
public enum EntityMessageType : byte
{
Error = 0,
ComponentMessage,

View File

@@ -427,6 +427,9 @@ namespace Robust.Shared.GameObjects.Systems
continue;
}
if (collision.A.Owner.Deleted || collision.B.Owner.Deleted)
continue;
var penetration = _physicsManager.CalculatePenetration(collision.A, collision.B);
if (penetration <= allowance)

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.GameObjects.Components.Map;
using Robust.Shared.GameObjects.Components.Transform;
namespace Robust.Shared.GameObjects.Systems
{
internal class SharedTransformSystem : EntitySystem
{
private readonly List<MoveEvent> _deferredMoveEvents = new();
public void DeferMoveEvent(MoveEvent moveEvent)
{
_deferredMoveEvents.Add(moveEvent);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var events = _deferredMoveEvents
.OrderBy(e => e.Sender.HasComponent<IMapGridComponent>())
.ToArray();
foreach (var ev in events)
{
ev.Sender.EntityManager.EventBus.RaiseEvent(EventSource.Local, ev);
ev.Handled = true;
}
_deferredMoveEvents.RemoveAll(e => e.Handled);
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Robust.Shared.Interfaces.GameObjects
/// This distinction is important because prototypes are shared across client and server, but the two might have different components.
/// </summary>
/// <seealso cref="IComponentFactory" />
public enum ComponentAvailability
public enum ComponentAvailability : byte
{
/// <summary>
/// The component is available and can be instantiated.
@@ -213,7 +213,7 @@ namespace Robust.Shared.Interfaces.GameObjects
/// <param name="registration">The registration if found, null otherwise.</param>
/// <returns>true it found, false otherwise.</returns>
bool TryGetRegistration(IComponent component, [NotNullWhen(true)] out IComponentRegistration? registration);
/// <summary>
/// Automatically create registrations for all components with a <see cref="RegisterComponentAttribute" />
/// </summary>

View File

@@ -43,7 +43,7 @@ namespace Robust.Shared.Interfaces.Network
void ClientDisconnect(string reason);
}
public enum ClientConnectionState
public enum ClientConnectionState : byte
{
/// <summary>
/// We are not connected and not trying to get connected either. Quite lonely huh.

View File

@@ -6,7 +6,7 @@ namespace Robust.Shared.Interfaces.Network
/// Defines on which side of the network a net message can be accepted.
/// </summary>
[Flags]
public enum NetMessageAccept
public enum NetMessageAccept : byte
{
None = 0,

View File

@@ -40,6 +40,8 @@ namespace Robust.Shared.Serialization
/// </exception>
ReadOnlySpan<byte> MappedStringsHash { get; }
bool EnableCaching { get; set; }
/// <summary>
/// Add a string to the constant mapping.
/// </summary>

View File

@@ -6,7 +6,7 @@ namespace Robust.Shared.Localization.Macros
/// <summary>
/// Genders for grammatical usage only.
/// </summary>
public enum Gender
public enum Gender : byte
{
Epicene,
Female,

View File

@@ -19,6 +19,13 @@ namespace Robust.Shared.Map
void NotifyTileChanged(in TileRef tileRef, in Tile oldTile);
/// <summary>
/// Regenerates anything that is based on chunk collision data.
/// This wouldn't even be separate if not for the whole "ability to suppress automatic collision regeneration" thing.
/// As it is, YamlGridSerializer performs manual collision regeneration and that wasn't properly getting propagated to the grid. Thus, this needs to exist.
/// </summary>
void NotifyChunkCollisionRegenerated();
/// <summary>
/// Returns the chunk at the given indices. If the chunk does not exist,
/// then a new one is generated that is filled with empty space.

View File

@@ -247,6 +247,7 @@ namespace Robust.Shared.Map
{
// generate collision rects
GridChunkPartition.PartitionChunk(this, ref _colBoxes, out _cachedBounds);
_grid.NotifyChunkCollisionRegenerated();
}
/// <inheritdoc />

Some files were not shown because too many files have changed in this diff Show More