mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab99d5be07 | ||
|
|
11c05509cb | ||
|
|
e1120b0d4c | ||
|
|
73c22ff6ac | ||
|
|
eb63809999 | ||
|
|
4c3c74865c | ||
|
|
b624f5b70f | ||
|
|
6566a7658a | ||
|
|
9e3e1cc929 | ||
|
|
4e87d93009 | ||
|
|
1031ae4cc5 |
6
.github/workflows/publish-client.yml
vendored
6
.github/workflows/publish-client.yml
vendored
@@ -33,10 +33,10 @@ jobs:
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
|
||||
- name: Upload files to centcomm
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
We actually set ManagePackageVersionsCentrally manually in another import file.
|
||||
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
|
||||
https://github.com/NuGet/NuGet.Client/pull/5572
|
||||
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
|
||||
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
|
||||
-->
|
||||
<ManagePackageVersionsCentrally />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
@@ -45,7 +55,7 @@
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,35 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 219.2.2
|
||||
|
||||
|
||||
## 219.2.1
|
||||
|
||||
|
||||
## 219.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add SetMapCoordinates to TransformSystem.
|
||||
* Improve YAML Linter and validation of static fields.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix DebugCoordsPanel freezing when hovering a control.
|
||||
|
||||
### Other
|
||||
|
||||
* Optimise physics networking to not dirty every tick of movement.
|
||||
|
||||
|
||||
## 219.1.3
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map.
|
||||
|
||||
|
||||
## 219.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -285,6 +285,7 @@ namespace Robust.Client
|
||||
/// <summary>
|
||||
/// Enumeration of the run levels of the BaseClient.
|
||||
/// </summary>
|
||||
/// <seealso cref="ClientRunLevelExt"/>
|
||||
public enum ClientRunLevel : byte
|
||||
{
|
||||
Error = 0,
|
||||
@@ -315,6 +316,21 @@ namespace Robust.Client
|
||||
SinglePlayerGame,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="ClientRunLevel"/>.
|
||||
/// </summary>
|
||||
public static class ClientRunLevelExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a <see cref="ClientRunLevel"/> is <see cref="ClientRunLevel.InGame"/>
|
||||
/// or <see cref="ClientRunLevel.SinglePlayerGame"/>.
|
||||
/// </summary>
|
||||
public static bool IsInGameLike(this ClientRunLevel runLevel)
|
||||
{
|
||||
return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for when something changed with the player.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var type = Type.GetType(args[0]);
|
||||
var type = GetType(args[0]);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
|
||||
shell.WriteLine(sig);
|
||||
}
|
||||
}
|
||||
|
||||
private Type? GetType(string name)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetType(name) is { } type)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IClyde _displayManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IBaseClient _baseClient = default!;
|
||||
|
||||
private readonly StringBuilder _textBuilder = new();
|
||||
private readonly char[] _textBuffer = new char[1024];
|
||||
@@ -58,30 +59,36 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
|
||||
|
||||
_textBuilder.Clear();
|
||||
|
||||
var isInGame = _baseClient.RunLevel.IsInGameLike();
|
||||
var mouseScreenPos = _inputManager.MouseScreenPosition;
|
||||
var screenSize = _displayManager.ScreenSize;
|
||||
var screenScale = _displayManager.MainWindow.ContentScale;
|
||||
|
||||
EntityCoordinates mouseGridPos;
|
||||
TileRef tile;
|
||||
EntityCoordinates mouseGridPos = default;
|
||||
TileRef tile = default;
|
||||
MapCoordinates mouseWorldMap = default;
|
||||
|
||||
var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
|
||||
if (mouseWorldMap == MapCoordinates.Nullspace)
|
||||
return;
|
||||
|
||||
var mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
|
||||
if (isInGame)
|
||||
{
|
||||
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
|
||||
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
|
||||
mouseWorldMap.Position);
|
||||
tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
|
||||
mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
|
||||
if (mouseWorldMap != MapCoordinates.Nullspace)
|
||||
{
|
||||
var mapSystem = _entityManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
|
||||
{
|
||||
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
|
||||
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
|
||||
mouseWorldMap.Position);
|
||||
tile = new TileRef(EntityUid.Invalid,
|
||||
mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var controlHovered = UserInterfaceManager.CurrentlyHovered;
|
||||
@@ -95,32 +102,37 @@ Mouse Pos:
|
||||
{tile}
|
||||
GUI: {controlHovered}");
|
||||
|
||||
_textBuilder.AppendLine("\nAttached NetEntity:");
|
||||
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
|
||||
|
||||
if (controlledEntity == EntityUid.Invalid)
|
||||
if (isInGame)
|
||||
{
|
||||
_textBuilder.AppendLine("No attached netentity.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
|
||||
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
|
||||
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
|
||||
var xformSystem = _entityManager.System<SharedTransformSystem>();
|
||||
|
||||
var playerCoordinates = entityTransform.Coordinates;
|
||||
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
|
||||
var gridRotation = entityTransform.GridUid != null
|
||||
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
|
||||
: Angle.Zero;
|
||||
_textBuilder.AppendLine("\nAttached NetEntity:");
|
||||
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
|
||||
|
||||
_textBuilder.Append($@" Screen: {playerScreen}
|
||||
if (controlledEntity == EntityUid.Invalid)
|
||||
{
|
||||
_textBuilder.AppendLine("No attached netentity.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
|
||||
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
|
||||
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
|
||||
|
||||
var playerCoordinates = entityTransform.Coordinates;
|
||||
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
|
||||
var gridRotation = entityTransform.GridUid != null
|
||||
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
|
||||
: Angle.Zero;
|
||||
|
||||
_textBuilder.Append($@" Screen: {playerScreen}
|
||||
{playerWorldOffset}
|
||||
{_entityManager.GetNetCoordinates(playerCoordinates)}
|
||||
Rotation: {playerRotation.Degrees:F2}°
|
||||
NEntId: {_entityManager.GetNetEntity(controlledEntity)}
|
||||
Grid NEntId: {_entityManager.GetNetEntity(entityTransform.GridUid)}
|
||||
Grid Rotation: {gridRotation.Degrees:F2}°");
|
||||
}
|
||||
}
|
||||
|
||||
_contents.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer);
|
||||
|
||||
@@ -706,6 +706,7 @@ public sealed class MapLoaderSystem : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
data.MapIsPaused = !data.MapIsPostInit;
|
||||
mapComp.MapId = data.TargetMap;
|
||||
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
|
||||
EnsureComp<LoadedMapComponent>(rootNode);
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
|
||||
String("short").ThenReturn(PrimitiveTypeCode.Int16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
|
||||
String("int").ThenReturn(PrimitiveTypeCode.Int32);
|
||||
|
||||
@@ -84,12 +84,146 @@ Types:
|
||||
- "bool get_HasContents()"
|
||||
Lidgren.Network:
|
||||
NetBuffer:
|
||||
All: True
|
||||
Methods:
|
||||
- "byte[] get_Data()"
|
||||
- "void set_Data(byte[])"
|
||||
- "int get_LengthBytes()"
|
||||
- "void set_LengthBytes(int)"
|
||||
- "int get_LengthBits()"
|
||||
- "void set_LengthBits(int)"
|
||||
- "long get_Position()"
|
||||
- "void set_Position(long)"
|
||||
- "int get_PositionInBytes()"
|
||||
- "byte[] PeekDataBuffer()"
|
||||
- "bool PeekBoolean()"
|
||||
- "byte PeekByte()"
|
||||
- "sbyte PeekSByte()"
|
||||
- "byte PeekByte(int)"
|
||||
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
|
||||
- "byte[] PeekBytes(int)"
|
||||
- "void PeekBytes(byte[], int, int)"
|
||||
- "short PeekInt16()"
|
||||
- "ushort PeekUInt16()"
|
||||
- "int PeekInt32()"
|
||||
- "int PeekInt32(int)"
|
||||
- "uint PeekUInt32()"
|
||||
- "uint PeekUInt32(int)"
|
||||
- "ulong PeekUInt64()"
|
||||
- "long PeekInt64()"
|
||||
- "ulong PeekUInt64(int)"
|
||||
- "long PeekInt64(int)"
|
||||
- "float PeekFloat()"
|
||||
- "System.Half PeekHalf()"
|
||||
- "float PeekSingle()"
|
||||
- "double PeekDouble()"
|
||||
- "string PeekString()"
|
||||
- "int PeekStringSize()"
|
||||
- "bool ReadBoolean()"
|
||||
- "byte ReadByte()"
|
||||
- "bool ReadByte(ref byte)"
|
||||
- "sbyte ReadSByte()"
|
||||
- "byte ReadByte(int)"
|
||||
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
|
||||
- "byte[] ReadBytes(int)"
|
||||
- "bool ReadBytes(int, ref byte[])"
|
||||
- "bool TryReadBytes(System.Span`1<byte>)"
|
||||
- "void ReadBytes(byte[], int, int)"
|
||||
- "void ReadBits(System.Span`1<byte>, int)"
|
||||
- "void ReadBits(byte[], int, int)"
|
||||
- "short ReadInt16()"
|
||||
- "ushort ReadUInt16()"
|
||||
- "int ReadInt32()"
|
||||
- "bool ReadInt32(ref int)"
|
||||
- "int ReadInt32(int)"
|
||||
- "uint ReadUInt32()"
|
||||
- "bool ReadUInt32(ref uint)"
|
||||
- "uint ReadUInt32(int)"
|
||||
- "ulong ReadUInt64()"
|
||||
- "long ReadInt64()"
|
||||
- "ulong ReadUInt64(int)"
|
||||
- "long ReadInt64(int)"
|
||||
- "float ReadFloat()"
|
||||
- "System.Half ReadHalf()"
|
||||
- "float ReadSingle()"
|
||||
- "bool ReadSingle(ref float)"
|
||||
- "double ReadDouble()"
|
||||
- "uint ReadVariableUInt32()"
|
||||
- "bool ReadVariableUInt32(ref uint)"
|
||||
- "int ReadVariableInt32()"
|
||||
- "long ReadVariableInt64()"
|
||||
- "ulong ReadVariableUInt64()"
|
||||
- "float ReadSignedSingle(int)"
|
||||
- "float ReadUnitSingle(int)"
|
||||
- "float ReadRangedSingle(float, float, int)"
|
||||
- "int ReadRangedInteger(int, int)"
|
||||
- "long ReadRangedInteger(long, long)"
|
||||
- "string ReadString()"
|
||||
- "bool ReadString(ref string)"
|
||||
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
|
||||
- "System.Net.IPEndPoint ReadIPEndPoint()"
|
||||
- "void SkipPadBits()"
|
||||
- "void ReadPadBits()"
|
||||
- "void SkipPadBits(int)"
|
||||
- "void EnsureBufferSize(int)"
|
||||
- "void Write(bool)"
|
||||
- "void Write(byte)"
|
||||
- "void WriteAt(int, byte)"
|
||||
- "void Write(sbyte)"
|
||||
- "void Write(byte, int)"
|
||||
- "void Write(byte[])"
|
||||
- "void Write(System.ReadOnlySpan`1<byte>)"
|
||||
- "void Write(byte[], int, int)"
|
||||
- "void Write(ushort)"
|
||||
- "void WriteAt(int, ushort)"
|
||||
- "void Write(ushort, int)"
|
||||
- "void Write(short)"
|
||||
- "void WriteAt(int, short)"
|
||||
- "void Write(int)"
|
||||
- "void WriteAt(int, int)"
|
||||
- "void Write(uint)"
|
||||
- "void WriteAt(int, uint)"
|
||||
- "void Write(uint, int)"
|
||||
- "void Write(int, int)"
|
||||
- "void Write(ulong)"
|
||||
- "void WriteAt(int, ulong)"
|
||||
- "void Write(ulong, int)"
|
||||
- "void Write(long)"
|
||||
- "void Write(long, int)"
|
||||
- "void Write(System.Half)"
|
||||
- "void Write(float)"
|
||||
- "void Write(double)"
|
||||
- "int WriteVariableUInt32(uint)"
|
||||
- "int WriteVariableInt32(int)"
|
||||
- "int WriteVariableInt64(long)"
|
||||
- "int WriteVariableUInt64(ulong)"
|
||||
- "void WriteSignedSingle(float, int)"
|
||||
- "void WriteUnitSingle(float, int)"
|
||||
- "void WriteRangedSingle(float, float, float, int)"
|
||||
- "int WriteRangedInteger(int, int, int)"
|
||||
- "int WriteRangedInteger(long, long, long)"
|
||||
- "void Write(string)"
|
||||
- "void Write(System.Net.IPEndPoint)"
|
||||
- "void WriteTime(bool)"
|
||||
- "void WriteTime(double, bool)"
|
||||
- "void WritePadBits()"
|
||||
- "void WritePadBits(int)"
|
||||
- "void Write(Lidgren.Network.NetBuffer)"
|
||||
- "void Zero(int)"
|
||||
- "void .ctor()"
|
||||
NetDeliveryMethod: { }
|
||||
NetIncomingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
|
||||
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
|
||||
- "int get_SequenceChannel()"
|
||||
- "System.Net.IPEndPoint get_SenderEndPoint()"
|
||||
- "Lidgren.Network.NetConnection get_SenderConnection()"
|
||||
- "double get_ReceiveTime()"
|
||||
- "double ReadTime(bool)"
|
||||
- "string ToString()"
|
||||
NetOutgoingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "string ToString()"
|
||||
Nett:
|
||||
CommentLocation: { } # Enum
|
||||
Toml:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
|
||||
path = path.Directory;
|
||||
|
||||
var fullPath = GetFullPath(path);
|
||||
Process.Start(new ProcessStartInfo
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = fullPath,
|
||||
});
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "explorer.exe",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "xdg-open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Opening OS windows not supported on this OS");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -870,6 +870,28 @@ public abstract partial class SharedTransformSystem
|
||||
return GetMapCoordinates(entity.Comp);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetMapCoordinates(EntityUid entity, MapCoordinates coordinates)
|
||||
{
|
||||
var xform = XformQuery.GetComponent(entity);
|
||||
SetMapCoordinates((entity, xform), coordinates);
|
||||
}
|
||||
|
||||
public void SetMapCoordinates(Entity<TransformComponent> entity, MapCoordinates coordinates)
|
||||
{
|
||||
var mapUid = _map.GetMap(coordinates.MapId);
|
||||
if (!_gridQuery.HasComponent(entity) &&
|
||||
_mapManager.TryFindGridAt(mapUid, coordinates.Position, out var targetGrid, out _))
|
||||
{
|
||||
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
|
||||
SetCoordinates(entity, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(coordinates.Position)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCoordinates(entity, new EntityCoordinates(mapUid, coordinates.Position));
|
||||
}
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
|
||||
{
|
||||
|
||||
@@ -313,55 +313,60 @@ public partial class SharedPhysicsSystem
|
||||
Dirty(uid, body);
|
||||
}
|
||||
|
||||
public void SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
public bool SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (body.BodyType == BodyType.Static)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (value * value > 0.0f)
|
||||
{
|
||||
if (!WakeBody(uid, manager: manager, body: body))
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// CloseToPercent tolerance needs to be small enough such that an angular velocity just above
|
||||
// sleep-tolerance can damp down to sleeping.
|
||||
|
||||
if (MathHelper.CloseToPercent(body.AngularVelocity, value, 0.00001f))
|
||||
return;
|
||||
return false;
|
||||
|
||||
body.AngularVelocity = value;
|
||||
|
||||
if (dirty)
|
||||
Dirty(uid, body);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to set the body to collidable, wake it, then move it.
|
||||
/// </summary>
|
||||
public void SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
public bool SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (body.BodyType == BodyType.Static) return;
|
||||
if (body.BodyType == BodyType.Static)
|
||||
return false;
|
||||
|
||||
if (wakeBody && Vector2.Dot(velocity, velocity) > 0.0f)
|
||||
{
|
||||
if (!WakeBody(uid, manager: manager, body: body))
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (body.LinearVelocity.EqualsApprox(velocity, 0.0000001f))
|
||||
return;
|
||||
return false;
|
||||
|
||||
body.LinearVelocity = velocity;
|
||||
|
||||
if (dirty)
|
||||
Dirty(uid, body);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetAngularDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true)
|
||||
|
||||
@@ -660,13 +660,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
});
|
||||
|
||||
// Update data sequentially
|
||||
var metaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
|
||||
for (var i = 0; i < actualIslands.Length; i++)
|
||||
{
|
||||
var island = actualIslands[i];
|
||||
|
||||
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery, metaQuery);
|
||||
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery);
|
||||
SleepBodies(in island, sleepStatus);
|
||||
}
|
||||
|
||||
@@ -1001,8 +999,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
float[] angles,
|
||||
Vector2[] linearVelocities,
|
||||
float[] angularVelocities,
|
||||
EntityQuery<TransformComponent> xformQuery,
|
||||
EntityQuery<MetaDataComponent> metaQuery)
|
||||
EntityQuery<TransformComponent> xformQuery)
|
||||
{
|
||||
foreach (var (joint, error) in island.BrokenJoints)
|
||||
{
|
||||
@@ -1035,21 +1032,22 @@ public abstract partial class SharedPhysicsSystem
|
||||
}
|
||||
|
||||
var linVelocity = linearVelocities[offset + i];
|
||||
var physicsDirtied = false;
|
||||
|
||||
if (!float.IsNaN(linVelocity.X) && !float.IsNaN(linVelocity.Y))
|
||||
{
|
||||
SetLinearVelocity(uid, linVelocity, false, body: body);
|
||||
physicsDirtied |= SetLinearVelocity(uid, linVelocity, false, body: body);
|
||||
}
|
||||
|
||||
var angVelocity = angularVelocities[offset + i];
|
||||
|
||||
if (!float.IsNaN(angVelocity))
|
||||
{
|
||||
SetAngularVelocity(uid, angVelocity, false, body: body);
|
||||
physicsDirtied |= SetAngularVelocity(uid, angVelocity, false, body: body);
|
||||
}
|
||||
|
||||
// TODO: Should check if the values update.
|
||||
Dirty(uid, body, metaQuery.GetComponent(uid));
|
||||
if (physicsDirtied)
|
||||
Dirty(uid, body);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
@@ -272,7 +273,8 @@ public interface IPrototypeManager
|
||||
out Dictionary<Type, HashSet<string>> prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// This method uses reflection to validate that prototype id fields correspond to valid prototypes.
|
||||
/// This method uses reflection to validate that all static prototype id fields correspond to valid prototypes.
|
||||
/// This will validate all known to <see cref="IReflectionManager"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will validate any field that has either a <see cref="ValidatePrototypeIdAttribute{T}"/> attribute, or a
|
||||
@@ -280,7 +282,12 @@ public interface IPrototypeManager
|
||||
/// </remarks>
|
||||
/// <param name="prototypes">A collection prototypes to use for validation. Any prototype not in this collection
|
||||
/// will be considered invalid.</param>
|
||||
List<string> ValidateFields(Dictionary<Type, HashSet<string>> prototypes);
|
||||
List<string> ValidateStaticFields(Dictionary<Type, HashSet<string>> prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// This is a variant of <see cref="ValidateStaticFields(System.Collections.Generic.Dictionary{System.Type,System.Collections.Generic.HashSet{string}})"/> that only validates a single type.
|
||||
/// </summary>
|
||||
List<string> ValidateStaticFields(Type type, Dictionary<Type, HashSet<string>> prototypes);
|
||||
|
||||
/// <summary>
|
||||
/// This method will serialize all loaded prototypes into yaml and then validate them. This can be used to ensure
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Utility;
|
||||
using BindingFlags = System.Reflection.BindingFlags;
|
||||
|
||||
@@ -13,35 +12,41 @@ namespace Robust.Shared.Prototypes;
|
||||
public partial class PrototypeManager
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public List<string> ValidateFields(Dictionary<Type, HashSet<string>> prototypes)
|
||||
public List<string> ValidateStaticFields(Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
var errors = new List<string>();
|
||||
foreach (var type in _reflectionManager.FindAllTypes())
|
||||
{
|
||||
// TODO validate public static fields on abstract classes that have no implementations?
|
||||
if (!type.IsAbstract)
|
||||
ValidateType(type, errors, prototypes);
|
||||
ValidateStaticFieldsInternal(type, errors, prototypes);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate all fields defined on this type and all base types.
|
||||
/// </summary>
|
||||
private void ValidateType(Type type, List<string> errors, Dictionary<Type, HashSet<string>> prototypes)
|
||||
/// <inheritdoc/>
|
||||
public List<string> ValidateStaticFields(Type type, Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
object? instance = null;
|
||||
Type? baseType = type;
|
||||
var errors = new List<string>();
|
||||
ValidateStaticFieldsInternal(type, errors, prototypes);
|
||||
return errors;
|
||||
}
|
||||
|
||||
var flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public |
|
||||
BindingFlags.DeclaredOnly;
|
||||
/// <summary>
|
||||
/// Validate all static fields defined on this type and all base types.
|
||||
/// </summary>
|
||||
private void ValidateStaticFieldsInternal(Type type, List<string> errors, Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
var baseType = type;
|
||||
var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly;
|
||||
|
||||
while (baseType != null)
|
||||
{
|
||||
foreach (var field in baseType.GetFields(flags))
|
||||
{
|
||||
ValidateField(field, type, ref instance, errors, prototypes);
|
||||
DebugTools.Assert(field.IsStatic);
|
||||
ValidateStaticField(field, type, errors, prototypes);
|
||||
}
|
||||
|
||||
// We need to get the fields on the base type separately in order to get the private fields
|
||||
@@ -49,92 +54,110 @@ public partial class PrototypeManager
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateField(
|
||||
private void ValidateStaticField(
|
||||
FieldInfo field,
|
||||
Type type,
|
||||
ref object? instance,
|
||||
List<string> errors,
|
||||
Dictionary<Type, HashSet<string>> prototypes)
|
||||
{
|
||||
DebugTools.Assert(field.IsStatic);
|
||||
DebugTools.Assert(!field.HasCustomAttribute<DataFieldAttribute>(), "Datafields should not be static");
|
||||
|
||||
// Is this even a prototype id related field?
|
||||
if (!TryGetFieldPrototype(field, out var proto, out var canBeNull, out var canBeEmpty))
|
||||
if (!TryGetFieldPrototype(field, out var proto))
|
||||
return;
|
||||
|
||||
if (!TryGetFieldValue(field, type, ref instance, errors, out var value))
|
||||
return;
|
||||
|
||||
var id = value?.ToString();
|
||||
|
||||
if (id == null)
|
||||
if (!prototypes.TryGetValue(proto, out var validIds))
|
||||
{
|
||||
if (!canBeNull)
|
||||
errors.Add($"Prototype id field failed validation. Fields should not be null. Field: {field.Name} in {type.FullName}");
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype kind {proto.Name}. Field: {field.Name} in {type.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
if (!TryGetIds(field, proto, out var ids))
|
||||
{
|
||||
if (!canBeEmpty)
|
||||
errors.Add($"Prototype id field failed validation. Non-optional non-nullable data-fields must have a default value. Field: {field.Name} in {type.FullName}");
|
||||
TryGetIds(field, proto, out _);
|
||||
DebugTools.Assert($"Failed to get ids, despite resolving the field into a prototype kind?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prototypes.TryGetValue(proto, out var ids))
|
||||
foreach (var id in ids)
|
||||
{
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype kind. Field: {field.Name} in {type.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ids.Contains(id))
|
||||
{
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype: {id}. Field: {field.Name} in {type.FullName}");
|
||||
if (!validIds.Contains(id))
|
||||
errors.Add($"Prototype id field failed validation. Unknown prototype: {id} of type {proto.Name}. Field: {field.Name} in {type.FullName}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of some field. If this is not a static field, this will create instance of the object in order to
|
||||
/// validate default field values.
|
||||
/// Extract prototype ids from a string, IEnumerable{string}, EntProtoId, IEnumerable{EntProtoId}, ProtoId{T}, or IEnumerable{ProtoId{T}} field.
|
||||
/// </summary>
|
||||
private bool TryGetFieldValue(FieldInfo field, Type type, ref object? instance, List<string> errors, out object? value)
|
||||
private bool TryGetIds(FieldInfo field, Type proto, [NotNullWhen(true)] out string[]? ids)
|
||||
{
|
||||
value = null;
|
||||
ids = null;
|
||||
var value = field.GetValue(null);
|
||||
if (value == null)
|
||||
return false;
|
||||
|
||||
if (field.IsStatic || instance != null)
|
||||
if (value is string str)
|
||||
{
|
||||
value = field.GetValue(instance);
|
||||
ids = [str];
|
||||
return true;
|
||||
}
|
||||
|
||||
var constructor = type.GetConstructor(
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
|
||||
Type.EmptyTypes);
|
||||
|
||||
// TODO handle parameterless record constructors.
|
||||
// Figure out how ISerializationManager does it, or just re-use that code somehow.
|
||||
// In the meantime, record data fields need an explicit parameterless ctor.
|
||||
|
||||
if (constructor == null)
|
||||
if (value is IEnumerable<string> strEnum)
|
||||
{
|
||||
errors.Add($"Prototype id field failed validation. Could not create instance to validate default value. Field: {field.Name} in {type.FullName}");
|
||||
return false;
|
||||
ids = strEnum.ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
instance = constructor.Invoke(Array.Empty<object>());
|
||||
value = field.GetValue(instance);
|
||||
if (value is EntProtoId protoId)
|
||||
{
|
||||
ids = [protoId];
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (value is IEnumerable<EntProtoId> protoIdEnum)
|
||||
{
|
||||
ids = protoIdEnum.Select(x=> x.Id).ToArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
{
|
||||
ids = [value.ToString()!];
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var iface in field.FieldType.GetInterfaces())
|
||||
{
|
||||
if (!iface.IsGenericType)
|
||||
continue;
|
||||
|
||||
if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>))
|
||||
continue;
|
||||
|
||||
var enumType = iface.GetGenericArguments().Single();
|
||||
if (!enumType.IsGenericType)
|
||||
continue;
|
||||
|
||||
if (enumType.GetGenericTypeDefinition() != typeof(ProtoId<>))
|
||||
continue;
|
||||
|
||||
ids = GetIdsMethod.MakeGenericMethod(proto).Invoke(null, [value]) as string[];
|
||||
return ids != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetFieldPrototype(
|
||||
FieldInfo field,
|
||||
[NotNullWhen(true)] out Type? proto,
|
||||
out bool canBeNull,
|
||||
out bool canBeEmpty)
|
||||
private static MethodInfo GetIdsMethod = typeof(PrototypeManager).GetMethod(nameof(GetIds), BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||
private static string[] GetIds<T>(IEnumerable<ProtoId<T>> enumerable) where T : class, IPrototype
|
||||
{
|
||||
proto = null;
|
||||
canBeNull = false;
|
||||
canBeEmpty = false;
|
||||
return enumerable.Select(x => x.Id).ToArray();
|
||||
}
|
||||
|
||||
private bool TryGetFieldPrototype(FieldInfo field, [NotNullWhen(true)] out Type? proto)
|
||||
{
|
||||
// Validate anything with the attribute
|
||||
var attrib = field.GetCustomAttribute(typeof(ValidatePrototypeIdAttribute<>), false);
|
||||
if (attrib != null)
|
||||
{
|
||||
@@ -142,46 +165,40 @@ public partial class PrototypeManager
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!field.TryGetCustomAttribute(out DataFieldAttribute? dataField))
|
||||
return false;
|
||||
if (TryGetPrototypeFromType(field.FieldType, out proto))
|
||||
return true;
|
||||
|
||||
var fieldType = field.FieldType;
|
||||
canBeEmpty = dataField.Required;
|
||||
DebugTools.Assert(!field.IsStatic);
|
||||
|
||||
// Resolve nullable structs
|
||||
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
// Allow validating arrays or lists.
|
||||
foreach (var iface in field.FieldType.GetInterfaces().Where(x => x.IsGenericType))
|
||||
{
|
||||
fieldType = fieldType.GetGenericArguments().Single();
|
||||
canBeNull = true;
|
||||
if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>))
|
||||
continue;
|
||||
|
||||
var enumType = iface.GetGenericArguments().Single();
|
||||
if (TryGetPrototypeFromType(enumType, out proto))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fieldType == typeof(EntProtoId))
|
||||
proto = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetPrototypeFromType(Type type, [NotNullWhen(true)] out Type? proto)
|
||||
{
|
||||
if (type == typeof(EntProtoId))
|
||||
{
|
||||
proto = typeof(EntityPrototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
|
||||
{
|
||||
proto = field.FieldType.GetGenericArguments().Single();
|
||||
proto = type.GetGenericArguments().Single();
|
||||
DebugTools.Assert(proto != typeof(EntityPrototype), "Use EntProtoId instead of ProtoId<EntityPrototype>");
|
||||
return true;
|
||||
}
|
||||
|
||||
// As far as I know there is no way to check for the nullability of a string field, so we will assume that null
|
||||
// values imply that the field itself is properly marked as nullable.
|
||||
canBeNull = true;
|
||||
|
||||
if (dataField.CustomTypeSerializer == null)
|
||||
return false;
|
||||
|
||||
if (!dataField.CustomTypeSerializer.IsGenericType)
|
||||
return false;
|
||||
|
||||
if (dataField.CustomTypeSerializer.GetGenericTypeDefinition() != typeof(PrototypeIdSerializer<>))
|
||||
return false;
|
||||
|
||||
proto = dataField.CustomTypeSerializer.GetGenericArguments().First();
|
||||
return true;
|
||||
proto = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ public partial class PrototypeManager
|
||||
var mapping = node.ToDataNodeCast<MappingDataNode>();
|
||||
var id = mapping.Get<ValueDataNode>("id").Value;
|
||||
|
||||
var data = new PrototypeValidationData(mapping, resourcePath.ToString());
|
||||
var data = new PrototypeValidationData(id, mapping, resourcePath.ToString());
|
||||
mapping.Remove("type");
|
||||
|
||||
if (prototypes.GetOrNew(type).TryAdd(id, data))
|
||||
@@ -65,10 +65,14 @@ public partial class PrototypeManager
|
||||
}
|
||||
}
|
||||
|
||||
var ctx = new YamlValidationContext();
|
||||
var errors = new List<ErrorNode>();
|
||||
foreach (var (type, instances) in prototypes)
|
||||
{
|
||||
foreach (var data in instances.Values)
|
||||
var defaultErrorOccurred = false;
|
||||
foreach (var (id, data) in instances)
|
||||
{
|
||||
errors.Clear();
|
||||
EnsurePushed(data, instances, type);
|
||||
if (data.Mapping.TryGet("abstract", out ValueDataNode? abstractNode)
|
||||
&& bool.Parse(abstractNode.Value))
|
||||
@@ -76,9 +80,25 @@ public partial class PrototypeManager
|
||||
continue;
|
||||
}
|
||||
|
||||
var result = _serializationManager.ValidateNode(type, data.Mapping).GetErrors().ToHashSet();
|
||||
if (result.Count > 0)
|
||||
dict.GetOrNew(data.File).UnionWith(result);
|
||||
// Validate yaml directly
|
||||
errors.AddRange(_serializationManager.ValidateNode(type, data.Mapping).GetErrors());
|
||||
if (errors.Count > 0)
|
||||
dict.GetOrNew(data.File).UnionWith(errors);
|
||||
|
||||
// Create instance & re-serialize it, to validate the default values of data-fields. We still validate
|
||||
// the yaml directly just in case reading & writing the fields somehow modifies their values.
|
||||
try
|
||||
{
|
||||
var instance = _serializationManager.Read(type, data.Mapping, ctx);
|
||||
var mapping = _serializationManager.WriteValue(type, instance, alwaysWrite: true, ctx);
|
||||
errors.AddRange(_serializationManager.ValidateNode(type, mapping, ctx).GetErrors());
|
||||
if (errors.Count > 0)
|
||||
dict.GetOrNew(data.File).UnionWith(errors);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errors.Add(new ErrorNode(new ValueDataNode(), $"Caught Exception while validating {type} prototype {id}. Exception: {ex}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,12 +172,17 @@ public partial class PrototypeManager
|
||||
|
||||
private sealed class PrototypeValidationData
|
||||
{
|
||||
public readonly string Id;
|
||||
public MappingDataNode Mapping;
|
||||
public readonly string File;
|
||||
public bool Pushed;
|
||||
|
||||
public PrototypeValidationData(MappingDataNode mapping, string file)
|
||||
public string[]? Parents;
|
||||
public MappingDataNode[]? ParentMappings;
|
||||
|
||||
public PrototypeValidationData(string id, MappingDataNode mapping, string file)
|
||||
{
|
||||
Id = id;
|
||||
File = file;
|
||||
Mapping = mapping;
|
||||
}
|
||||
@@ -176,23 +201,22 @@ public partial class PrototypeManager
|
||||
if (!data.Mapping.TryGet(ParentDataFieldAttribute.Name, out var parentNode))
|
||||
return;
|
||||
|
||||
var parents = _serializationManager.Read<string[]>(parentNode, notNullableOverride: true);
|
||||
var parentNodes = new MappingDataNode[parents.Length];
|
||||
DebugTools.AssertNull(data.Parents);
|
||||
DebugTools.AssertNull(data.ParentMappings);
|
||||
data.Parents = _serializationManager.Read<string[]>(parentNode, notNullableOverride: true);
|
||||
data.ParentMappings = new MappingDataNode[data.Parents.Length];
|
||||
|
||||
foreach (var parentId in parents)
|
||||
var i = 0;
|
||||
foreach (var parentId in data.Parents)
|
||||
{
|
||||
var parent = prototypes[parentId];
|
||||
EnsurePushed(parent, prototypes, type);
|
||||
|
||||
for (var i = 0; i < parents.Length; i++)
|
||||
{
|
||||
parentNodes[i] = parent.Mapping;
|
||||
}
|
||||
data.ParentMappings[i++] = parent.Mapping;
|
||||
}
|
||||
|
||||
data.Mapping = _serializationManager.PushCompositionWithGenericNode(
|
||||
type,
|
||||
parentNodes,
|
||||
data.ParentMappings,
|
||||
data.Mapping);
|
||||
}
|
||||
}
|
||||
|
||||
62
Robust.Shared/Prototypes/YamlValidationContext.cs
Normal file
62
Robust.Shared/Prototypes/YamlValidationContext.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
|
||||
namespace Robust.Shared.Prototypes;
|
||||
|
||||
internal sealed class YamlValidationContext : ISerializationContext, ITypeSerializer<EntityUid, ValueDataNode>
|
||||
{
|
||||
public SerializationManager.SerializerProvider SerializerProvider { get; } = new();
|
||||
public bool WritingReadingPrototypes => true;
|
||||
|
||||
public YamlValidationContext()
|
||||
{
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
}
|
||||
|
||||
ValidationNode ITypeValidator<EntityUid, ValueDataNode>.Validate(ISerializationManager serializationManager,
|
||||
ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context)
|
||||
{
|
||||
if (node.Value == "null" || node.Value == "invalid")
|
||||
return new ValidatedValueNode(node);
|
||||
|
||||
return new ErrorNode(node, "Prototypes should not contain EntityUids", true);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serializationManager, EntityUid value,
|
||||
IDependencyCollection dependencies, bool alwaysWrite = false,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
if (!value.Valid)
|
||||
return new ValueDataNode("invalid");
|
||||
|
||||
return new ValueDataNode(value.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
EntityUid ITypeReader<EntityUid, ValueDataNode>.Read(ISerializationManager serializationManager,
|
||||
ValueDataNode node,
|
||||
IDependencyCollection dependencies,
|
||||
SerializationHookContext hookCtx,
|
||||
ISerializationContext? context, ISerializationManager.InstantiationDelegate<EntityUid>? _)
|
||||
{
|
||||
if (node.Value == "invalid")
|
||||
return EntityUid.Invalid;
|
||||
|
||||
return EntityUid.Parse(node.Value);
|
||||
}
|
||||
|
||||
[MustUseReturnValue]
|
||||
public EntityUid Copy(ISerializationManager serializationManager, EntityUid source, EntityUid target,
|
||||
bool skipHook,
|
||||
ISerializationContext? context = null)
|
||||
{
|
||||
return new((int)source);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ using Robust.Shared.Prototypes;
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// This attribute should be used on string fields to validate that they correspond to a valid YAML prototype id.
|
||||
/// If the field needs to be have a default value.
|
||||
/// This attribute should be used on static string or string collection fields to validate that they correspond to
|
||||
/// valid YAML prototype ids. This attribute is not required for static <see cref="ProtoId{T}"/> and
|
||||
/// <see cref="EntProtoId"/> fields, as they automatically get validated.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public sealed class ValidatePrototypeIdAttribute<T> : Attribute where T : IPrototype
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
|
||||
nodeVariable),
|
||||
call,
|
||||
dfa.Required
|
||||
? ExpressionUtils.ThrowExpression<RequiredFieldNotMappedException>(fieldDefinition.FieldType, tagConst)
|
||||
? ExpressionUtils.ThrowExpression<RequiredFieldNotMappedException>(fieldDefinition.FieldType, tagConst, typeof(T))
|
||||
: AssignIfNotDefaultExpression(i, targetParam, Expression.Constant(DefaultValues[i], fieldDefinition.FieldType))
|
||||
)));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace Robust.Shared.Serialization.Manager.Exceptions;
|
||||
|
||||
public sealed class RequiredFieldNotMappedException : Exception
|
||||
{
|
||||
public RequiredFieldNotMappedException(Type type, string field) : base($"Required field {field} of type {type} wasn't mapped.")
|
||||
public RequiredFieldNotMappedException(Type type, string field, Type dataDef) : base($"Required field {field} of type {type} in {dataDef} wasn't mapped.")
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Pro
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a string corresponds to a valid prototype id. Note that any data fields using this serializer will
|
||||
/// also be validated by <see cref="IPrototypeManager.ValidateFields"/>
|
||||
/// also be validated by <see cref="IPrototypeManager.ValidateStaticFields"/>
|
||||
/// </summary>
|
||||
[Virtual]
|
||||
public class PrototypeIdSerializer<TPrototype> : ITypeValidator<string, ValueDataNode> where TPrototype : class, IPrototype
|
||||
|
||||
Reference in New Issue
Block a user