Compare commits

...

31 Commits

Author SHA1 Message Date
DrSmugleaf
e2a4dcdff1 Fix comparing by name and not ID for entity prototype updates (#1578) 2021-02-20 02:41:51 +01:00
DrSmugleaf
68b0d7bf2e Fix not clearing the queue after hot reload (#1576) 2021-02-20 01:43:56 +01:00
DrSmugleaf
a9b163992b Fix and add test for PrototypeManager LoadString (#1574) 2021-02-20 01:43:43 +01:00
DrSmugleaf
2409965cf8 Fix build (#1575) 2021-02-20 00:15:43 +01:00
DrSmugleaf
eada37378a Add YAML hot reloading (#1571)
* Implement hot reloading for entity prototypes

* Implement automatic prototype hot-reloading

* Merge fixes

* Add yaml hot reloading and a message to notify the client

* Add reloading only changed files, remove cooldown, add retries and remove IPrototype

* Remove reload command

* Make the client listen for reloads instead and only when focused

* Fix errors

* Only queue a reload when the queue has items in it

* Make fails after 10 retries log instead of throw if reloading

Co-authored-by: Jackson Lewis <inquisitivepenguin@protonmail.com>
2021-02-20 00:02:04 +01:00
DrSmugleaf
0f1da1ba2a Add window focused callback to Clyde (#1573) 2021-02-19 22:10:03 +01:00
Acruid
e0cdcd228e Fixed Timer Namespace in unit tests. 2021-02-18 20:35:34 -08:00
Acruid
fdb5e014b5 PauseManager moved to Shared (#1553)
* Moved IPauseManager from server to shared.

* Moved ITimerManager from Timers to Timing.

* Added missing IConsoleHost to server/client RegisterIoC. Tests work again.
2021-02-18 20:12:26 -08:00
DrSmugleaf
cefcad775b Make addcomp and rmcomp give better feedback and case insensitive (#1570)
* Make addcomp and rmcomp case insensitive

* Fix up names

* Make addcomp and rmcomp give better feedback

* Make addcomp and rmcomp less fail happy
2021-02-18 20:01:14 -08:00
Vera Aguilera Puerto
e40feac1f1 Adds VV autorefresh when right-clicking the refresh button. (#1558)
* Adds VV autorefresh when right-clicking the refresh button.

* cancel token on close

* button tooltip
2021-02-18 00:14:11 -08:00
DrSmugleaf
3ef4ac7452 Make component states dependant on the player getting them (#1569) 2021-02-17 23:48:17 -08:00
Pieter-Jan Briers
93bf1b09e7 Fix disconnecting while connecting causes you to be locked out of the server. 2021-02-17 23:22:11 +01:00
DrSmugleaf
a1e557e870 Add IPrototypeManager method to load a string (#1567) 2021-02-17 13:20:39 -08:00
Pieter-Jan Briers
864adb7445 Add DateTimeStyles to sandbox. 2021-02-17 11:52:36 +01:00
mirrorcult
9e3f3f0c1c vec2i serializer (#1563)
Co-authored-by: cyclowns <cyclowns@protonmail.ch>
2021-02-16 12:19:45 -08:00
DrSmugleaf
a40c4a435c Fix file not found exceptions when starting up the game with a debugger (#1562)
* Fix exceptions when starting up the game

* Remove try catches
2021-02-16 20:05:22 +01:00
DrSmugleaf
17182dd0e8 Engine PR for enabling nullability in Content.Client (#1565) 2021-02-16 20:05:06 +01:00
DrSmugleaf
d8b50044a2 Add (de)serialization for immutable lists (#1549) 2021-02-16 20:04:28 +01:00
Pieter-Jan Briers
4dc396e73d Fixes warning in TypePropertySerialization_Test.cs 2021-02-16 09:20:06 +01:00
Pieter-Jan Briers
6ae0b0e892 Fix [GenerateTypedNameReferences] with sealed types.
Fixes #1546
2021-02-16 09:19:57 +01:00
Pieter-Jan Briers
7162ca3456 Probably fix the bug where people get locked out of the server due to duplicate connction attempts. 2021-02-16 09:02:14 +01:00
Pieter-Jan Briers
1b44c1a1b8 Allow NotImplementedException in sandbox. 2021-02-15 17:57:38 +01:00
Clyybber
5b80b33e00 Change GetFileSystemInfos to EnumerateFileSystemInfos for iteration (#1561) 2021-02-15 16:26:16 +01:00
DrSmugleaf
f05c1b2395 Add Attribute generic constraint to IReflectionManager.FindTypesWithAttribute (#1548) 2021-02-14 02:14:00 +01:00
Pieter-Jan Briers
d9b2c73440 XamlUI improvements.
1. Add XAML namespaces https://spacestation14.io with Avalonia hacks.
2. Make markup extensions work with Avalonia hacks.
3. Add LocExtension for localized strings.
4. Add Vector2 parsing type converter to XAML compilation.
5. Make SS14Window better thanks to these improvements.
2021-02-14 02:09:37 +01:00
Pieter-Jan Briers
29a39f8e0a Adds LocExtension markup extension for XAML.
Does Loc.GetString().
2021-02-13 15:13:05 +01:00
Pieter-Jan Briers
2d72a2bdb5 Server timing improvements.
Add helpers.
Fix desynchronized NetTime.
2021-02-13 11:42:12 +01:00
Pieter-Jan Briers
91da635631 Fix Robust.Shared.Interfaces namespace in ScriptInstanceShared 2021-02-13 11:42:12 +01:00
chairbender
68ab3d504a Various fixes to support new chatbox (#1547)
* #272 no arrow, actually change id on channel changer

* #272 ability to apply style class to child

* #272 try methods for selecting items in OptionButton.cs

* #272 allow escaping right angle bracket in formatted message

* #272 ability to detect when local player is set / unset

* #272 make RemoveModal public since PushModal is as well, so modals can be removed on-demand if needed rather than relying on a click elsewhere

* #272 revert
2021-02-12 18:20:29 -08:00
Pieter-Jan Briers
5187040a64 Remove unsafe code from GrowableStack 2021-02-13 00:47:05 +01:00
DrSmugleaf
e0c63e7ce6 Add SerializedType attribute to specify the id used in !type tags (#1545)
* Add SerializedType attribute to serialize types without the !type tag

* Fix nulls in tests

* Fix null in tests maybe

* Return to type tags

* Fix imports
2021-02-11 13:50:55 -08:00
109 changed files with 1965 additions and 603 deletions

View File

@@ -0,0 +1,37 @@
using System.Linq;
using Pidgin;
using static Pidgin.Parser;
namespace Robust.Build.Tasks
{
public static class MathParsing
{
public static Parser<char, float> Single { get; } = Real.Select(c => (float) c);
public static Parser<char, float> Single1 { get; }
= Single.Between(SkipWhitespaces);
public static Parser<char, (float, float)> Single2 { get; }
= Single.Before(SkipWhitespaces).Repeat(2).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1]);
});
public static Parser<char, (float, float, float, float)> Single4 { get; }
= Single.Before(SkipWhitespaces).Repeat(4).Select(e =>
{
var arr = e.ToArray();
return (arr[0], arr[1], arr[2], arr[3]);
});
public static Parser<char, float[]> Thickness { get; }
= SkipWhitespaces.Then(
OneOf(
Try(Single1.Select(c => new[] {c})),
Try(Single2.Select(c => new[] {c.Item1, c.Item2})),
Try(Single4.Select(c => new[] {c.Item1, c.Item2, c.Item3, c.Item4}))
));
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Linq;
using System.Reflection.Emit;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
public abstract class RXamlVecLikeConstAstNode<T>
: XamlAstNode, IXamlAstValueNode, IXamlAstILEmitableNode
where T : unmanaged
{
private readonly IXamlConstructor _constructor;
protected readonly T[] Values;
public RXamlVecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, T[] values)
: base(lineInfo)
{
_constructor = constructor;
Values = values;
var @params = constructor.Parameters;
if (@params.Count != values.Length)
throw new ArgumentException("Invalid amount of parameters");
if (@params.Any(c => c != componentType))
throw new ArgumentException("Invalid constructor: not all parameters match component type");
Type = new XamlAstClrTypeReference(lineInfo, type, false);
}
public IXamlAstTypeReference Type { get; }
public virtual XamlILNodeEmitResult Emit(
XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
codeGen.Newobj(_constructor);
return XamlILNodeEmitResult.Type(0, Type.GetClrType());
}
}
public sealed class RXamlSingleVecLikeConstAstNode : RXamlVecLikeConstAstNode<float>
{
public RXamlSingleVecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, float[] values)
: base(lineInfo, type, constructor, componentType, values)
{
}
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
foreach (var value in Values)
{
codeGen.Emit(OpCodes.Ldc_R4, value);
}
return base.Emit(context, codeGen);
}
}
public sealed class RXamlInt32VecLikeConstAstNode : RXamlVecLikeConstAstNode<int>
{
public RXamlInt32VecLikeConstAstNode(
IXamlLineInfo lineInfo,
IXamlType type, IXamlConstructor constructor,
IXamlType componentType, int[] values)
: base(lineInfo, type, constructor, componentType, values)
{
}
public override XamlILNodeEmitResult Emit(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter codeGen)
{
foreach (var value in Values)
{
codeGen.Emit(OpCodes.Ldc_I4, value);
}
return base.Emit(context, codeGen);
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Linq;
using XamlX.Transform;
using XamlX.TypeSystem;
namespace Robust.Build.Tasks
{
class RXamlWellKnownTypes
{
public XamlTypeWellKnownTypes XamlIlTypes { get; }
public IXamlType Single { get; }
public IXamlType Int32 { get; }
public IXamlType Vector2 { get; }
public IXamlConstructor Vector2ConstructorFull { get; }
public IXamlType Vector2i { get; }
public IXamlConstructor Vector2iConstructorFull { get; }
public RXamlWellKnownTypes(TransformerConfiguration cfg)
{
var ts = cfg.TypeSystem;
XamlIlTypes = cfg.WellKnownTypes;
Single = ts.GetType("System.Single");
Int32 = ts.GetType("System.Int32");
(Vector2, Vector2ConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2", Single, 2);
(Vector2i, Vector2iConstructorFull) = GetNumericTypeInfo("Robust.Shared.Maths.Vector2i", Int32, 2);
(IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount)
{
var type = cfg.TypeSystem.GetType(name);
var ctor = type.GetConstructor(Enumerable.Repeat(componentType, componentCount).ToList());
return (type, ctor);
}
}
}
static class RXamlWellKnownTypesExtensions
{
public static RXamlWellKnownTypes GetRobustTypes(this AstTransformationContext ctx)
{
if (ctx.TryGetItem<RXamlWellKnownTypes>(out var rv))
return rv;
ctx.SetItem(rv = new RXamlWellKnownTypes(ctx.Configuration));
return rv;
}
}
}

View File

@@ -8,6 +8,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="16.8.0" />
<PackageReference Include="Mono.Cecil" Version="0.11.3" />
<PackageReference Include="Pidgin" Version="2.5.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -6,6 +6,7 @@ using Microsoft.Build.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Pidgin;
using XamlX;
using XamlX.Ast;
using XamlX.Emit;
@@ -70,7 +71,7 @@ namespace Robust.Build.Tasks
{
XmlnsAttributes =
{
typeSystem.GetType("Robust.Client.UserInterface.XAML.XmlnsDefinitionAttribute"),
typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute"),
},
ContentAttributes =
@@ -98,7 +99,7 @@ namespace Robust.Build.Tasks
typeSystem,
typeSystem.TargetAssembly,
xamlLanguage,
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage));
XamlXmlnsMappings.Resolve(typeSystem, xamlLanguage), CustomValueConverter);
var contextDef = new TypeDefinition("CompiledRobustXaml", "XamlIlContext",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
@@ -297,6 +298,41 @@ namespace Robust.Build.Tasks
return true;
}
private static bool CustomValueConverter(
AstTransformationContext context,
IXamlAstValueNode node,
IXamlType type,
out IXamlAstValueNode result)
{
if (!(node is XamlAstTextNode textNode))
{
result = null;
return false;
}
var text = textNode.Text;
var types = context.GetRobustTypes();
if (type.Equals(types.Vector2))
{
var foo = MathParsing.Single2.Parse(text);
if (!foo.Success)
throw new XamlLoadException($"Unable to parse \"{text}\" as a Vector2", node);
var (x, y) = foo.Value;
result = new RXamlSingleVecLikeConstAstNode(
node,
types.Vector2, types.Vector2ConstructorFull,
types.Single, new[] {x, y});
return true;
}
result = null;
return false;
}
public const string ContextNameScopeFieldName = "RobustNameScope";
private static void EmitNameScopeField(XamlLanguageTypeMappings xamlLanguage, CecilTypeSystem typeSystem, IXamlTypeBuilder<IXamlILEmitter> typeBuilder, IXamlILEmitter constructor)

View File

@@ -24,6 +24,7 @@ namespace Robust.Client.NameGenerator
{
private const string AttributeName = "Robust.Client.AutoGenerated.GenerateTypedNameReferencesAttribute";
private const string AttributeFile = "GenerateTypedNameReferencesAttribute";
private const string AttributeCode = @"// <auto-generated />
using System;
namespace Robust.Client.AutoGenerated
@@ -54,8 +55,8 @@ namespace Robust.Client.AutoGenerated
{
var clrtype = objectNode.Type.GetClrType();
var isControl = IsControl(clrtype);
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
//clrtype.Interfaces.Any(i =>
//i.IsInterface && i.FullName == "Robust.Client.UserInterface.IControl");
if (!isControl)
return node;
@@ -80,9 +81,13 @@ namespace Robust.Client.AutoGenerated
return node;
}
public void Push(IXamlAstNode node) { }
public void Push(IXamlAstNode node)
{
}
public void Pop() { }
public void Pop()
{
}
}
private static string GenerateSourceCode(
@@ -97,16 +102,20 @@ namespace Robust.Client.AutoGenerated
var compiler =
new XamlILCompiler(
new TransformerConfiguration(typeSystem, typeSystem.Assemblies[0],
new XamlLanguageTypeMappings(typeSystem)),
new XamlLanguageTypeMappings(typeSystem)
{
XmlnsAttributes = {typeSystem.GetType("Avalonia.Metadata.XmlnsDefinitionAttribute")}
}),
new XamlLanguageEmitMappings<IXamlILEmitter, XamlILNodeEmitResult>(), false);
compiler.Transformers.Add(new TypeReferenceResolver());
compiler.Transform(parsed);
var initialRoot = (XamlAstObjectNode) parsed.Root;
var names = NameVisitor.GetNames(initialRoot);
var fieldAccess = classSymbol.IsSealed ? "private" : "protected";
//var names = NameVisitor.GetNames((XamlAstObjectNode)XDocumentXamlParser.Parse(xamlFile).Root);
var namedControls = names.Select(info => " " +
$"protected global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
$"{fieldAccess} global::{info.type} {info.name} => " +
$"this.FindControl<global::{info.type}>(\"{info.name}\");");
return $@"// <auto-generated />
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -124,7 +133,7 @@ namespace {nameSpace}
public void Execute(GeneratorExecutionContext context)
{
var comp = (CSharpCompilation) context.Compilation;
if(comp.GetTypeByMetadataName(AttributeName) == null)
if (comp.GetTypeByMetadataName(AttributeName) == null)
context.AddSource(AttributeFile, SourceText.From(AttributeCode, Encoding.UTF8));
if (!(context.SyntaxReceiver is NameReferenceSyntaxReceiver receiver))
{
@@ -132,7 +141,7 @@ namespace {nameSpace}
}
var symbols = UnpackAnnotatedTypes(context, comp, receiver);
if(symbols == null)
if (symbols == null)
return;
foreach (var typeSymbol in symbols)
@@ -168,7 +177,8 @@ namespace {nameSpace}
"Usage",
DiagnosticSeverity.Error,
true),
Location.Create(xamlFileName, new TextSpan(0,0), new LinePositionSpan(new LinePosition(0,0),new LinePosition(0,0)))));
Location.Create(xamlFileName, new TextSpan(0, 0),
new LinePositionSpan(new LinePosition(0, 0), new LinePosition(0, 0)))));
continue;
}
@@ -194,7 +204,8 @@ namespace {nameSpace}
}
}
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context, CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
private IReadOnlyList<INamedTypeSymbol> UnpackAnnotatedTypes(in GeneratorExecutionContext context,
CSharpCompilation comp, NameReferenceSyntaxReceiver receiver)
{
var options = (CSharpParseOptions) comp.SyntaxTrees[0].Options;
var compilation =

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.Audio.Midi;
using Robust.Client.Console;
using Robust.Client.Debugging;
@@ -10,6 +10,7 @@ using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Placement;
using Robust.Client.Player;
using Robust.Client.Prototypes;
using Robust.Client.Reflection;
using Robust.Client.ResourceManagement;
using Robust.Client.State;
@@ -17,6 +18,7 @@ using Robust.Client.UserInterface;
using Robust.Client.Utility;
using Robust.Client.ViewVariables;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -33,7 +35,7 @@ namespace Robust.Client
{
SharedIoC.RegisterIoC();
IoCManager.Register<IPrototypeManager, PrototypeManager>();
IoCManager.Register<IPrototypeManager, ClientPrototypeManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IComponentFactory, ClientComponentFactory>();
IoCManager.Register<ITileDefinitionManager, ClydeTileDefinitionManager>();
@@ -59,6 +61,7 @@ namespace Robust.Client
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IFontManager, FontManager>();
IoCManager.Register<IFontManagerInternal, FontManager>();
IoCManager.Register<IMidiManager, MidiManager>();

View File

@@ -26,7 +26,6 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -163,6 +162,7 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_consoleHost.Initialize();
_prototypeManager.Initialize();
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
_prototypeManager.Resync();
_mapManager.Initialize();

View File

@@ -1,11 +0,0 @@
using Robust.Shared.GameObjects;
namespace Robust.Client.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedIgnorePauseComponent))]
public sealed class IgnorePauseComponent : SharedIgnorePauseComponent
{
}
}

View File

@@ -40,7 +40,7 @@ namespace Robust.Client.GameObjects
/// <param name="message">Arguments for this event.</param>
/// <param name="replay">if true, current cmd state will not be checked or updated - use this for "replaying" an
/// old input that was saved or buffered until further processing could be done</param>
public bool HandleInputCommand(ICommonSession session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
public bool HandleInputCommand(ICommonSession? session, BoundKeyFunction function, FullInputCmdMessage message, bool replay = false)
{
#if DEBUG

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Robust.Client.GameObjects;
using Robust.Client.Input;
using Robust.Shared.GameStates;
@@ -352,7 +353,10 @@ namespace Robust.Client.GameStates
foreach (var component in _componentManager.GetNetComponents(createdEntity))
{
var state = component.GetComponentState();
Debug.Assert(_players.LocalPlayer != null, "_players.LocalPlayer != null");
var player = _players.LocalPlayer.Session;
var state = component.GetComponentState(player);
if (state.GetType() == typeof(ComponentState))
{

View File

@@ -55,6 +55,7 @@ namespace Robust.Client.Graphics.Clyde
private GLFWCallbacks.WindowSizeCallback _windowSizeCallback = default!;
private GLFWCallbacks.WindowContentScaleCallback _windowContentScaleCallback = default!;
private GLFWCallbacks.WindowIconifyCallback _windowIconifyCallback = default!;
private GLFWCallbacks.WindowFocusCallback _windowFocusCallback = default!;
private bool _glfwInitialized;
@@ -62,6 +63,7 @@ namespace Robust.Client.Graphics.Clyde
private Window* _glfwWindow;
private Vector2i _framebufferSize;
private bool _isFocused;
private Vector2i _windowSize;
private Vector2i _prevWindowSize;
private Vector2i _prevWindowPos;
@@ -74,6 +76,7 @@ namespace Robust.Client.Graphics.Clyde
// NOTE: in engine we pretend the framebuffer size is the screen size..
// For practical reasons like UI rendering.
public override Vector2i ScreenSize => _framebufferSize;
public override bool IsFocused => _isFocused;
public Vector2 DefaultWindowScale => _windowScale;
public Vector2 MouseScreenPosition => _lastMousePos;
@@ -231,6 +234,7 @@ namespace Robust.Client.Graphics.Clyde
GLFW.SetMouseButtonCallback(_glfwWindow, _mouseButtonCallback);
GLFW.SetWindowContentScaleCallback(_glfwWindow, _windowContentScaleCallback);
GLFW.SetWindowIconifyCallback(_glfwWindow, _windowIconifyCallback);
GLFW.SetWindowFocusCallback(_glfwWindow, _windowFocusCallback);
GLFW.MakeContextCurrent(_glfwWindow);
@@ -548,6 +552,19 @@ namespace Robust.Client.Graphics.Clyde
}
}
private void OnGlfwWindowFocus(Window* window, bool focused)
{
try
{
_isFocused = focused;
OnWindowFocused?.Invoke(new WindowFocusedEventArgs(focused));
}
catch (Exception e)
{
CatchCallbackException(e);
}
}
private void StoreCallbacks()
{
_errorCallback = OnGlfwError;
@@ -560,6 +577,7 @@ namespace Robust.Client.Graphics.Clyde
_windowSizeCallback = OnGlfwWindowSize;
_windowContentScaleCallback = OnGlfwWindownContentScale;
_windowIconifyCallback = OnGlfwWindowIconify;
_windowFocusCallback = OnGlfwWindowFocus;
}
public override void SetWindowTitle(string title)

View File

@@ -88,7 +88,7 @@ namespace Robust.Client.Graphics.Clyde
public override bool Initialize()
{
base.Initialize();
_configurationManager.OnValueChanged(CVars.DisplayOGLCheckErrors, b => _checkGLErrors = b, true);
if (!InitWindowing())
@@ -152,6 +152,8 @@ namespace Robust.Client.Graphics.Clyde
public override event Action<WindowResizedEventArgs>? OnWindowResized;
public override event Action<WindowFocusedEventArgs>? OnWindowFocused;
public void Screenshot(ScreenshotType type, Action<Image<Rgb24>> callback)
{
_queuedScreenshots.Add((type, callback));

View File

@@ -21,6 +21,7 @@ namespace Robust.Client.Graphics.Clyde
public IRenderWindow MainWindowRenderTarget { get; }
public override Vector2i ScreenSize { get; } = (1280, 720);
public Vector2 DefaultWindowScale => (1, 1);
public override bool IsFocused => true;
public ShaderInstance InstanceShader(ClydeHandle handle)
{
@@ -79,6 +80,12 @@ namespace Robust.Client.Graphics.Clyde
remove { }
}
public override event Action<WindowFocusedEventArgs> OnWindowFocused
{
add { }
remove { }
}
public void Render()
{
// Nada.

View File

@@ -25,6 +25,8 @@ namespace Robust.Client.Graphics
protected bool VSync { get; private set; } = true;
public abstract Vector2i ScreenSize { get; }
public abstract bool IsFocused { get; }
public abstract void SetWindowTitle(string title);
public virtual bool Initialize()
@@ -45,6 +47,8 @@ namespace Robust.Client.Graphics
public abstract event Action<WindowResizedEventArgs> OnWindowResized;
public abstract event Action<WindowFocusedEventArgs> OnWindowFocused;
protected virtual void ReadConfig()
{
WindowMode = (WindowMode) _configurationManager.GetCVar(CVars.DisplayWindowMode);

View File

@@ -13,6 +13,8 @@ namespace Robust.Client.Graphics
Vector2i ScreenSize { get; }
bool IsFocused { get; }
/// <summary>
/// The default scale ratio for window contents, given to us by the OS.
/// </summary>
@@ -27,6 +29,8 @@ namespace Robust.Client.Graphics
event Action<WindowResizedEventArgs> OnWindowResized;
event Action<WindowFocusedEventArgs> OnWindowFocused;
Texture LoadTextureFromPNGStream(Stream stream, string? name = null,
TextureLoadParameters? loadParams = null);

View File

@@ -12,7 +12,7 @@ using YamlDotNet.RepresentationModel;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
public sealed class ShaderPrototype : IPrototype, IIndexedPrototype
public sealed class ShaderPrototype : IPrototype
{
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;

View File

@@ -0,0 +1,14 @@
using System;
namespace Robust.Client.Graphics
{
public class WindowFocusedEventArgs : EventArgs
{
public WindowFocusedEventArgs(bool focused)
{
Focused = focused;
}
public bool Focused { get; }
}
}

View File

@@ -12,6 +12,11 @@ namespace Robust.Client.Player
LocalPlayer? LocalPlayer { get; }
/// <summary>
/// Invoked after LocalPlayer is changed
/// </summary>
event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
int PlayerCount { get; }
int MaxPlayers { get; }
event EventHandler PlayerListUpdated;
@@ -23,4 +28,15 @@ namespace Robust.Client.Player
void ApplyPlayerStates(IEnumerable<PlayerState>? list);
}
public class LocalPlayerChangedEventArgs : EventArgs
{
public readonly LocalPlayer? OldPlayer;
public readonly LocalPlayer? NewPlayer;
public LocalPlayerChangedEventArgs(LocalPlayer? oldPlayer, LocalPlayer? newPlayer)
{
OldPlayer = oldPlayer;
NewPlayer = newPlayer;
}
}
}

View File

@@ -37,7 +37,21 @@ namespace Robust.Client.Player
public int MaxPlayers => _client.GameInfo?.ServerMaxPlayers ?? 0;
/// <inheritdoc />
[ViewVariables] public LocalPlayer? LocalPlayer { get; private set; }
[ViewVariables]
public LocalPlayer? LocalPlayer
{
get => _localPlayer;
private set
{
if (_localPlayer == value) return;
var oldValue = _localPlayer;
_localPlayer = value;
LocalPlayerChanged?.Invoke(new LocalPlayerChangedEventArgs(oldValue, _localPlayer));
}
}
private LocalPlayer? _localPlayer;
public event Action<LocalPlayerChangedEventArgs>? LocalPlayerChanged;
/// <inheritdoc />
[ViewVariables] public IEnumerable<IPlayerSession> Sessions => _sessions.Values;

View File

@@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Robust.Client.Graphics;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.Prototypes
{
public sealed class ClientPrototypeManager : PrototypeManager
{
[Dependency] private readonly IClyde _clyde = default!;
private readonly List<FileSystemWatcher> _watchers = new();
private readonly TimeSpan _reloadDelay = TimeSpan.FromMilliseconds(10);
private CancellationTokenSource _reloadToken = new();
private readonly HashSet<ResourcePath> _reloadQueue = new();
public override void Initialize()
{
base.Initialize();
NetManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, accept: NetMessageAccept.Server);
_clyde.OnWindowFocused += WindowFocusedChanged;
WatchResources();
}
private void WindowFocusedChanged(WindowFocusedEventArgs args)
{
#if !FULL_RELEASE
if (args.Focused && _reloadQueue.Count > 0)
{
Timer.Spawn(_reloadDelay, ReloadPrototypeQueue, _reloadToken.Token);
}
else
{
_reloadToken.Cancel();
_reloadToken = new CancellationTokenSource();
}
#endif
}
private void ReloadPrototypeQueue()
{
#if !FULL_RELEASE
var then = DateTime.Now;
var msg = NetManager.CreateNetMessage<MsgReloadPrototypes>();
msg.Paths = _reloadQueue.ToArray();
NetManager.ClientSendMessage(msg);
foreach (var path in _reloadQueue)
{
ReloadPrototypes(path);
}
_reloadQueue.Clear();
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
#endif
}
private void WatchResources()
{
#if !FULL_RELEASE
foreach (var path in Resources.GetContentRoots().Select(r => r.ToString())
.Where(r => Directory.Exists(r + "/Prototypes")).Select(p => p + "/Prototypes"))
{
var watcher = new FileSystemWatcher(path, "*.yml")
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite
};
watcher.Changed += (_, args) =>
{
switch (args.ChangeType)
{
case WatcherChangeTypes.Renamed:
case WatcherChangeTypes.Deleted:
return;
case WatcherChangeTypes.Created:
// case WatcherChangeTypes.Deleted:
case WatcherChangeTypes.Changed:
case WatcherChangeTypes.All:
break;
default:
throw new ArgumentOutOfRangeException();
}
TaskManager.RunOnMainThread(() =>
{
var file = new ResourcePath(args.FullPath);
foreach (var root in IoCManager.Resolve<IResourceManager>().GetContentRoots())
{
if (!file.TryRelativeTo(root, out var relative))
{
continue;
}
_reloadQueue.Add(relative);
}
});
};
watcher.EnableRaisingEvents = true;
_watchers.Add(watcher);
}
#endif
}
}
}

View File

@@ -5,7 +5,7 @@ using System.Diagnostics.Contracts;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Maths;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.UserInterface.Controls
{

View File

@@ -15,15 +15,36 @@ namespace Robust.Client.UserInterface.Controls
private readonly Popup _popup;
private readonly VBoxContainer _popupVBox;
private readonly Label _label;
private readonly TextureRect _triangle;
public int ItemCount => _buttonData.Count;
/// <summary>
/// If true, hides the triangle that normally appears to the right of the button label
/// </summary>
public bool HideTriangle
{
get => _hideTriangle;
set
{
_hideTriangle = value;
_triangle.Visible = !_hideTriangle;
}
}
private bool _hideTriangle;
/// <summary>
/// StyleClasses to apply to the options that popup when clicking this button.
/// </summary>
public ICollection<string> OptionStyleClasses { get; }
public event Action<ItemSelectedEventArgs>? OnItemSelected;
public string Prefix { get; set; }
public OptionButton()
{
OptionStyleClasses = new List<string>();
AddStyleClass(StyleClassButton);
Prefix = "";
OnPressed += OnPressedInternal;
@@ -43,12 +64,13 @@ namespace Robust.Client.UserInterface.Controls
};
hBox.AddChild(_label);
var textureRect = new TextureRect
_triangle = new TextureRect
{
StyleClasses = { StyleClassOptionTriangle },
SizeFlagsVertical = SizeFlags.ShrinkCenter,
Visible = !HideTriangle
};
hBox.AddChild(textureRect);
hBox.AddChild(_triangle);
}
public void AddItem(Texture icon, string label, int? id = null)
@@ -73,6 +95,10 @@ namespace Robust.Client.UserInterface.Controls
Text = label,
ToggleMode = true
};
foreach (var styleClass in OptionStyleClasses)
{
button.AddStyleClass(styleClass);
}
button.OnPressed += ButtonOnPressed;
var data = new ButtonData(label, button)
{
@@ -170,6 +196,10 @@ namespace Robust.Client.UserInterface.Controls
}
}
/// <summary>
/// Select by index rather than id. Throws exception if item with that index
/// not in this control.
/// </summary>
public void Select(int idx)
{
if (_idMap.TryGetValue(SelectedId, out var prevIdx))
@@ -182,11 +212,29 @@ namespace Robust.Client.UserInterface.Controls
data.Button.Pressed = true;
}
/// <summary>
/// Select by index rather than id.
/// </summary>
/// <returns>false if item with that index not in this control</returns>
public bool TrySelect(int idx)
{
if (idx < 0 || idx >= _buttonData.Count) return false;
Select(idx);
return true;
}
/// throws exception if item with this ID is not in this control
public void SelectId(int id)
{
Select(GetIdx(id));
}
/// <returns>false if item with id not in this control</returns>
public bool TrySelectId(int id)
{
return _idMap.TryGetValue(id, out var idx) && TrySelect(idx);
}
public int GetIdx(int id)
{
return _idMap[id];

View File

@@ -1,15 +1,17 @@
<cc:SS14Window xmlns:cc="clr-namespace:Robust.Client.UserInterface.CustomControls"
xmlns:c="clr-namespace:Robust.Client.UserInterface.Controls">
<c:PanelContainer StyleClasses="windowPanel"/>
<c:VBoxContainer SeparationOverride="0">
<c:PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<c:HBoxContainer>
<c:MarginContainer MarginLeftOverride="5" SizeFlagsHorizontal="FillExpand">
<c:Label Name="TitleLabel" StyleIdentifier="foo" ClipText="True" Text="Exemplary Window Title Here" VAlign="Center" StyleClasses="windowTitle"/>
</c:MarginContainer>
<c:TextureButton Name="CloseButton" StyleClasses="windowCloseButton" SizeFlagsVertical="ShrinkCenter"></c:TextureButton>
</c:HBoxContainer>
</c:PanelContainer>
<c:MarginContainer Name="ContentsContainer" MarginBottomOverride="10" MarginLeftOverride="10" MarginRightOverride="10" MarginTopOverride="10" RectClipContent="True" SizeFlagsVertical="FillExpand" />
</c:VBoxContainer>
</cc:SS14Window>
<SS14Window xmlns="https://spacestation14.io">
<PanelContainer StyleClasses="windowPanel" />
<VBoxContainer SeparationOverride="0">
<PanelContainer Name="WindowHeader" StyleClasses="windowHeader">
<HBoxContainer>
<MarginContainer MarginLeftOverride="5" SizeFlagsHorizontal="FillExpand">
<Label Name="TitleLabel" StyleIdentifier="foo" ClipText="True"
Text="{Loc Exemplary Window Title Here}" VAlign="Center" StyleClasses="windowTitle" />
</MarginContainer>
<TextureButton Name="CloseButton" StyleClasses="windowCloseButton" SizeFlagsVertical="ShrinkCenter" />
</HBoxContainer>
</PanelContainer>
<MarginContainer Name="ContentsContainer" MarginBottomOverride="10" MarginLeftOverride="10"
MarginRightOverride="10" MarginTopOverride="10" RectClipContent="True"
SizeFlagsVertical="FillExpand" />
</VBoxContainer>
</SS14Window>

View File

@@ -0,0 +1,23 @@
using JetBrains.Annotations;
using Robust.Shared.Localization;
namespace Robust.Client.UserInterface
{
// TODO: Code a XAML compiler transformer to remove references to this type at compile time.
// And just replace them with the Loc.GetString() call.
[PublicAPI]
public class LocExtension
{
public string Key { get; }
public LocExtension(string key)
{
Key = key;
}
public object ProvideValue()
{
return Loc.GetString(Key);
}
}
}

View File

@@ -6,13 +6,6 @@ namespace Robust.Client.UserInterface.XAML
{
}
public class XmlnsDefinitionAttribute : Attribute
{
public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace)
{
}
}
public class UsableDuringInitializationAttribute : Attribute
{
public UsableDuringInitializationAttribute(bool usable)

View File

@@ -0,0 +1,16 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Avalonia.Data
{
[UsedImplicitly]
public class Binding
{
public Binding()
{
throw new InvalidOperationException(
"Data binding is not currently supported and this type exists only to make Rider work.");
}
}
}

View File

@@ -0,0 +1,6 @@
using Avalonia.Metadata;
[assembly: XmlnsDefinition("http://schemas.microsoft.com/winfx/2006/xaml", "Robust.Client.UserInterface.XAML.XNamespace")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface.Controls")]
[assembly: XmlnsDefinition("https://spacestation14.io", "Robust.Client.UserInterface.CustomControls")]

View File

@@ -0,0 +1,12 @@
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Null
[UsedImplicitly]
public sealed class NullExtension
{
}
}

View File

@@ -0,0 +1,28 @@
# XamlUI notes about Rider
XAML support in both Rider and VS is pretty hardcoded for all supported frameworks. Rider seems less bad about this but they still hardcode a lot of type paths and do other silly stuff. Luckily, they support Avalonia. So... let's pretend to be Avalonia!
**JetBrains, if you're reading this, please don't sue me or take away our licenses pls I only reverse engineered your program to avoid wasting time on your issue tracker.**
## Where to find this info
The primary XAML support in Rider appears to be in `lib/ReSharperHost/JetBrains.ReSharper.Psi.Xaml.dll`. I recommend you decompile it with DotPeek to a project for navigating around. You can then look around the decompiled code (it's not obfuscated at all) to figure out how Rider does all this stuff.
**JetBrains, if you're reading this, please don't sue me or take away our licenses pls I only reverse engineered your program to avoid wasting time on your issue tracker.**
## Attributes
Attributes like `XmlnsAttributionAttribute` are hardcoded for full name with namespace. That's also where it stops, so we just define our own `Avalonia.Metadata.XmlnsDefinitionAttribute` and this fools Rider into recognizing it and working. Hooray!
## `http://schemas.microsoft.com/winfx/2006/xaml` Namespace
As far as I can tell, at least in Avalonia, the things in this namespace are not backed by any real .NET symbols and are just made up by Rider. This is a problem because it only does this making up if it detects that you're using Avalonia.
The heuristic for "are we using Avalonia" appears to be finding an assembly or project reference for `Avalonia.Base`. You can see this in `XamlModulePlatformCache.cs` in the decompiled ReSharper source code and also the `XamlPlatform` flags enum.
For the time being I decided to hold off on actually fully pretending we're Avalonia so did *not* commit to a reference to `Avalonia.Base` yet. Instead we just manually created stuff like a dummy `StaticExtension` class (with a constructor that always throws) and put it into the `http://schemas.microsoft.com/winfx/2006/xaml` namespace. This seems to satisfy Rider without interfering with the actual XamlIL compilation.
## Markup Extensions
Markup extensions have to be classes with a method of signature either `object ProvideValue(IServiceProvider)` or `object ProvideValue()`.
It should be noted that Rider refuses to acknowledge markup extensions unless it finds Avalonia's `Avalonia.Data.Binding` type (or `Avalonia.Markup.Xaml.Data.Binding`). Yes, this is an actual requirement.

View File

@@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Static
[UsedImplicitly]
public sealed class StaticExtension
{
public StaticExtension(string _)
{
throw new InvalidOperationException(
"This type only exists to make Rider work and should never be instantiated.");
}
public static object ProvideValue()
{
throw new InvalidOperationException();
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Robust.Client.UserInterface.XAML.XNamespace
{
// x:Type
[UsedImplicitly]
public sealed class TypeExtension
{
public TypeExtension(string _)
{
throw new InvalidOperationException(
"This type only exists to make Rider work and should never be instantiated.");
}
public static object ProvideValue()
{
throw new InvalidOperationException();
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using JetBrains.Annotations;
// ReSharper disable once CheckNamespace
namespace Avalonia.Metadata
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
[PublicAPI]
public sealed class XmlnsDefinitionAttribute : Attribute
{
public XmlnsDefinitionAttribute(string xmlNamespace, string clrNamespace)
{
XmlNamespace = xmlNamespace;
ClrNamespace = clrNamespace;
}
public string XmlNamespace { get; }
public string ClrNamespace { get; }
}
}

View File

@@ -1,21 +1,27 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Threading;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables.Traits;
using Robust.Shared.Input;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
using Robust.Shared.Utility;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Client.ViewVariables.Instances
{
internal class ViewVariablesInstanceObject : ViewVariablesInstance
{
private TabContainer _tabs = default!;
private Button _refreshButton = default!;
private int _tabCount;
private readonly List<ViewVariablesTrait> _traits = new();
private CancellationTokenSource _refreshCancelToken = new();
public ViewVariablesRemoteSession? Session { get; private set; }
public object? Object { get; private set; }
@@ -72,9 +78,10 @@ namespace Robust.Client.ViewVariables.Instances
name.SizeFlagsHorizontal = Control.SizeFlags.FillExpand;
headBox.AddChild(name);
var button = new Button {Text = "Refresh"};
button.OnPressed += _ => _refresh();
headBox.AddChild(button);
_refreshButton = new Button {Text = "Refresh", ToolTip = "RMB to toggle auto-refresh."};
_refreshButton.OnPressed += _ => _refresh();
_refreshButton.OnKeyBindDown += OnButtonKeybindDown;
headBox.AddChild(_refreshButton);
vBoxContainer.AddChild(headBox);
}
@@ -82,10 +89,32 @@ namespace Robust.Client.ViewVariables.Instances
vBoxContainer.AddChild(_tabs);
}
private void OnButtonKeybindDown(GUIBoundKeyEventArgs eventArgs)
{
if (eventArgs.Function == EngineKeyFunctions.UIRightClick)
{
_refreshButton.ToggleMode = !_refreshButton.ToggleMode;
_refreshButton.Pressed = !_refreshButton.Pressed;
_refreshCancelToken.Cancel();
if (!_refreshButton.Pressed) return;
_refreshCancelToken = new CancellationTokenSource();
Timer.SpawnRepeating(500, _refresh, _refreshCancelToken.Token);
} else if (eventArgs.Function == EngineKeyFunctions.UIClick)
{
_refreshButton.ToggleMode = false;
}
}
public override void Close()
{
base.Close();
_refreshCancelToken.Cancel();
if (Session != null && !Session.Closed)
{
ViewVariablesManager.CloseSession(Session);

View File

@@ -31,7 +31,6 @@ using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Serilog.Debugging;
using Serilog.Sinks.Loki;
using Stopwatch = Robust.Shared.Timing.Stopwatch;
@@ -273,12 +272,17 @@ namespace Robust.Server
//IoCManager.Resolve<IMapLoader>().LoadedMapData +=
// IoCManager.Resolve<IRobustMappedStringSerializer>().AddStrings;
IoCManager.Resolve<IPrototypeManager>().LoadedData +=
(yaml, name) => _stringSerializer.AddStrings(yaml);
IoCManager.Resolve<IPrototypeManager>().LoadedData += (yaml, name) =>
{
if (!_stringSerializer.Locked)
{
_stringSerializer.AddStrings(yaml);
}
};
// Initialize Tier 2 services
IoCManager.Resolve<IGameTiming>().InSimulation = true;
IoCManager.Resolve<INetConfigurationManager>().SetupNetworking();
_stateManager.Initialize();
@@ -297,6 +301,7 @@ namespace Robust.Server
// because of 'reasons' this has to be called after the last assembly is loaded
// otherwise the prototypes will be cleared
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
prototypeManager.Initialize();
prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes"));
prototypeManager.Resync();

View File

@@ -1,64 +0,0 @@
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Console.Commands
{
[UsedImplicitly]
internal sealed class AddCompCommand : IConsoleCommand
{
public string Command => "addcomp";
public string Description => "Adds a component to an entity";
public string Help => "addcomp <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine("Wrong number of arguments");
return;
}
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var entityManager = IoCManager.Resolve<IEntityManager>();
var entity = entityManager.GetEntity(entityUid);
var component = (Component) compFactory.GetComponent(componentName);
component.Owner = entity;
compManager.AddComponent(entity, component);
}
}
[UsedImplicitly]
internal sealed class RemoveCompCommand : IConsoleCommand
{
public string Command => "rmcomp";
public string Description => "Removes a component from an entity.";
public string Help => "rmcomp <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine("Wrong number of arguments");
return;
}
var entityUid = EntityUid.Parse(args[0]);
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
var registration = compFactory.GetRegistration(componentName);
compManager.RemoveComponent(entityUid, registration.Type);
}
}
}

View File

@@ -0,0 +1,61 @@
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Console.Commands
{
[UsedImplicitly]
internal sealed class AddComponentCommand : IConsoleCommand
{
public string Command => "addcomp";
public string Description => "Adds a component to an entity";
public string Help => $"{Command} <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine($"Invalid amount of arguments.\n{Help}");
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
{
shell.WriteLine($"{uid} is not a valid entity uid.");
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(uid, out var entity))
{
shell.WriteLine($"No entity found with id {uid}.");
return;
}
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
if (!compFactory.TryGetRegistration(componentName, out var registration, true))
{
shell.WriteLine($"No component found with name {componentName}.");
return;
}
if (entity.HasComponent(registration.Type))
{
shell.WriteLine($"Entity {entity.Name} already has a {componentName} component.");
}
var component = (Component) compFactory.GetComponent(registration.Type);
component.Owner = entity;
compManager.AddComponent(entity, component);
shell.WriteLine($"Added {componentName} component to entity {entity.Name}.");
}
}
}

View File

@@ -1,15 +1,14 @@
using System.Globalization;
using System.Globalization;
using System.Linq;
using System.Text;
using Robust.Server.Maps;
using Robust.Server.Player;
using Robust.Server.Timing;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Server.Console.Commands
{
@@ -237,81 +236,6 @@ namespace Robust.Server.Console.Commands
}
}
class PauseMapCommand : IConsoleCommand
{
public string Command => "pausemap";
public string Description => "Pauses a map, pausing all simulation processing on it.";
public string Help => "Usage: pausemap <map ID>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Loc.GetString("Need to supply a valid MapId"));
return;
}
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var pauseManager = IoCManager.Resolve<IPauseManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
if (!mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
pauseManager.SetMapPaused(mapId, true);
}
}
class UnpauseMapCommand : IConsoleCommand
{
public string Command => "unpausemap";
public string Description => "unpauses a map, resuming all simulation processing on it.";
public string Help => "Usage: unpausemap <map ID>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 1)
{
shell.WriteLine(Loc.GetString("Need to supply a valid MapId"));
return;
}
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var pauseManager = IoCManager.Resolve<IPauseManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
if (!mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
pauseManager.SetMapPaused(mapId, false);
}
}
class QueryMapPausedCommand : IConsoleCommand
{
public string Command => "querymappaused";
public string Description => "Check whether a map is paused or not.";
public string Help => "Usage: querymappaused <map ID>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
var pauseManager = IoCManager.Resolve<IPauseManager>();
var mapManager = IoCManager.Resolve<IMapManager>();
if (!mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
shell.WriteLine(pauseManager.IsMapPaused(mapId).ToString());
}
}
class TpGridCommand : IConsoleCommand
{
public string Command => "tpgrid";

View File

@@ -0,0 +1,59 @@
using JetBrains.Annotations;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Robust.Server.Console.Commands
{
[UsedImplicitly]
internal sealed class RemoveComponentCommand : IConsoleCommand
{
public string Command => "rmcomp";
public string Description => "Removes a component from an entity.";
public string Help => $"{Command} <uid> <componentName>";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteLine($"Invalid amount of arguments.\n{Help}.");
return;
}
if (!EntityUid.TryParse(args[0], out var uid))
{
shell.WriteLine($"{uid} is not a valid entity uid.");
return;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetEntity(uid, out var entity))
{
shell.WriteLine($"No entity found with id {uid}.");
return;
}
var componentName = args[1];
var compManager = IoCManager.Resolve<IComponentManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
if (!compFactory.TryGetRegistration(componentName, out var registration, true))
{
shell.WriteLine($"No component found with name {componentName}.");
return;
}
if (!entity.HasComponent(registration.Type))
{
shell.WriteLine($"No {componentName} component found on entity {entity.Name}.");
return;
}
compManager.RemoveComponent(uid, registration.Type);
shell.WriteLine($"Removed {componentName} component from entity {entity.Name}.");
}
}
}

View File

@@ -30,5 +30,10 @@ namespace Robust.Server.Console
{
return Implementation?.CanAdminMenu(session) ?? false;
}
public bool CanAdminReloadPrototypes(IPlayerSession session)
{
return Implementation?.CanAdminReloadPrototypes(session) ?? false;
}
}
}

View File

@@ -9,5 +9,6 @@ namespace Robust.Server.Console
bool CanAdminPlace(IPlayerSession session);
bool CanScript(IPlayerSession session);
bool CanAdminMenu(IPlayerSession session);
bool CanAdminReloadPrototypes(IPlayerSession session);
}
}

View File

@@ -3,6 +3,7 @@ using Robust.Shared.ViewVariables;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Players;
namespace Robust.Server.GameObjects
{
@@ -61,7 +62,7 @@ namespace Robust.Server.GameObjects
return false;
}
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new AppearanceComponentState(data);
}

View File

@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -256,7 +257,7 @@ namespace Robust.Server.GameObjects
_entitiesWaitingResolve = null;
}
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new ContainerManagerComponentState(
_allContainers.ToDictionary(

View File

@@ -1,5 +1,6 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
namespace Robust.Server.GameObjects
@@ -63,7 +64,7 @@ namespace Robust.Server.GameObjects
}
}
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new EyeComponentState(DrawFov, Zoom, Offset, Rotation);
}

View File

@@ -1,5 +1,6 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -73,7 +74,7 @@ namespace Robust.Server.GameObjects
serializer.DataField(ref _offset, "offset", Vector2.Zero);
}
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new PointLightComponentState(Enabled, Color, Radius, Offset);
}

View File

@@ -4,6 +4,7 @@ using Robust.Shared.GameObjects;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -441,7 +442,7 @@ namespace Robust.Server.GameObjects
Layers = layerData;
}
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new SpriteComponentState(Visible, DrawDepth, Scale, Rotation, Offset, Color, Directional,
BaseRSIPath, Layers, RenderOrder);

View File

@@ -10,7 +10,7 @@ namespace Robust.Server.GameObjects
/// <summary>
/// Gets all entity states that have been modified after and including the provided tick.
/// </summary>
List<EntityState>? GetEntityStates(GameTick fromTick);
List<EntityState>? GetEntityStates(GameTick fromTick, IPlayerSession player);
/// <summary>
/// Gets all entity states within an AABB that have been modified after and including the provided tick.

View File

@@ -4,7 +4,6 @@ using System.Linq;
using System.Threading;
using Prometheus;
using Robust.Server.Player;
using Robust.Server.Timing;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -13,6 +12,7 @@ using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using static Robust.Shared.GameObjects.TransformComponent;
namespace Robust.Server.GameObjects
{
@@ -139,7 +139,7 @@ namespace Robust.Server.GameObjects
}
/// <inheritdoc />
public List<EntityState>? GetEntityStates(GameTick fromTick)
public List<EntityState>? GetEntityStates(GameTick fromTick, IPlayerSession player)
{
var stateEntities = new List<EntityState>();
foreach (var entity in AllEntities)
@@ -154,7 +154,7 @@ namespace Robust.Server.GameObjects
if (entity.LastModifiedTick <= fromTick)
continue;
stateEntities.Add(GetEntityState(ComponentManager, entity.Uid, fromTick));
stateEntities.Add(GetEntityState(ComponentManager, entity.Uid, fromTick, player));
}
// no point sending an empty collection
@@ -336,7 +336,7 @@ namespace Robust.Server.GameObjects
if (playerEnt == null)
{
// super-observer?
return GetEntityStates(fromTick);
return GetEntityStates(fromTick, player);
}
var playerUid = playerEnt.Uid;
@@ -390,7 +390,7 @@ namespace Robust.Server.GameObjects
}
}
var state = GetEntityState(ComponentManager, uid, fromTick);
var state = GetEntityState(ComponentManager, uid, fromTick, player);
if (checkedEnts.Add(uid))
{
@@ -404,14 +404,14 @@ namespace Robust.Server.GameObjects
{
// mover changed and can't be seen
var idx = Array.FindIndex(state.ComponentStates,
x => x is TransformComponent.TransformComponentState);
x => x is TransformComponentState);
if (idx != -1)
{
// mover changed positional data and can't be seen
var oldState =
(TransformComponent.TransformComponentState) state.ComponentStates[idx];
var newState = new TransformComponent.TransformComponentState(Vector2NaN,
(TransformComponentState) state.ComponentStates[idx];
var newState = new TransformComponentState(Vector2NaN,
oldState.Rotation, oldState.ParentID, oldState.NoLocalRotation);
state.ComponentStates[idx] = newState;
seenMovers.Remove(uid);
@@ -441,7 +441,7 @@ namespace Robust.Server.GameObjects
{
// mover can't be seen
var oldState =
(TransformComponent.TransformComponentState) entity.Transform.GetComponentState();
(TransformComponentState) entity.Transform.GetComponentState(player);
entityStates.Add(new EntityState(uid,
new ComponentChanged[]
{
@@ -449,7 +449,7 @@ namespace Robust.Server.GameObjects
},
new ComponentState[]
{
new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation,
new TransformComponentState(Vector2NaN, oldState.Rotation,
oldState.ParentID, oldState.NoLocalRotation)
}));
@@ -517,7 +517,7 @@ namespace Robust.Server.GameObjects
}
// should this be lastSeen or fromTick?
var entityState = GetEntityState(ComponentManager, uid, lastSeen);
var entityState = GetEntityState(ComponentManager, uid, lastSeen, player);
checkedEnts.Add(uid);
@@ -577,7 +577,7 @@ namespace Robust.Server.GameObjects
continue;
}
var state = GetEntityState(ComponentManager, uid, fromTick);
var state = GetEntityState(ComponentManager, uid, fromTick, player);
if (state.ComponentStates == null)
{
@@ -591,7 +591,7 @@ namespace Robust.Server.GameObjects
seenMovers.Remove(uid);
ClearLastSeenTick(lSeen, uid);
var idx = Array.FindIndex(state.ComponentStates, x => x is TransformComponent.TransformComponentState);
var idx = Array.FindIndex(state.ComponentStates, x => x is TransformComponentState);
if (idx == -1)
{
@@ -599,9 +599,9 @@ namespace Robust.Server.GameObjects
continue;
}
var oldState = (TransformComponent.TransformComponentState) state.ComponentStates[idx];
var oldState = (TransformComponentState) state.ComponentStates[idx];
var newState =
new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation, oldState.ParentID, oldState.NoLocalRotation);
new TransformComponentState(Vector2NaN, oldState.Rotation, oldState.ParentID, oldState.NoLocalRotation);
state.ComponentStates[idx] = newState;
@@ -630,7 +630,7 @@ namespace Robust.Server.GameObjects
}
var entity = GetEntity(uid);
var state = GetEntityState(ComponentManager, uid, fromTick);
var state = GetEntityState(ComponentManager, uid, fromTick, player);
if (state.ComponentStates == null || viewbox.Intersects(GetWorldAabbFromEntity(entity)))
{
@@ -643,7 +643,7 @@ namespace Robust.Server.GameObjects
entityStates.Add(state);
var idx = Array.FindIndex(state.ComponentStates,
x => x is TransformComponent.TransformComponentState);
x => x is TransformComponentState);
if (idx == -1)
{
@@ -651,9 +651,9 @@ namespace Robust.Server.GameObjects
continue;
}
var oldState = (TransformComponent.TransformComponentState) state.ComponentStates[idx];
var oldState = (TransformComponentState) state.ComponentStates[idx];
var newState =
new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation,
new TransformComponentState(Vector2NaN, oldState.Rotation,
oldState.ParentID, oldState.NoLocalRotation);
state.ComponentStates[idx] = newState;
seenMovers.Remove(uid);
@@ -881,8 +881,9 @@ namespace Robust.Server.GameObjects
/// <param name="compMan">ComponentManager that contains the components for the entity.</param>
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
/// <param name="fromTick">Only provide delta changes from this tick.</param>
/// <param name="player">The player to generate this state for.</param>
/// <returns>New entity State for the given entity.</returns>
private static EntityState GetEntityState(IComponentManager compMan, EntityUid entityUid, GameTick fromTick)
private static EntityState GetEntityState(IComponentManager compMan, EntityUid entityUid, GameTick fromTick, IPlayerSession player)
{
var compStates = new List<ComponentState>();
var changed = new List<ComponentChanged>();
@@ -899,7 +900,7 @@ namespace Robust.Server.GameObjects
// As such, we can avoid sending this data in this case since the client "already has it".
if (comp.NetSyncEnabled && comp.LastModifiedTick != GameTick.Zero && comp.LastModifiedTick >= fromTick)
compStates.Add(comp.GetComponentState());
compStates.Add(comp.GetComponentState(player));
if (comp.CreationTick != GameTick.Zero && comp.CreationTick >= fromTick && !comp.Deleted)
{

View File

@@ -135,7 +135,7 @@ namespace Robust.Server.GameStates
}
var entStates = lastAck == GameTick.Zero || !PvsEnabled
? _entityManager.GetEntityStates(lastAck)
? _entityManager.GetEntityStates(lastAck, session)
: _entityManager.UpdatePlayerSeenEntityStates(lastAck, session, _entityManager.MaxUpdateRange);
var playerStates = _playerManager.GetPlayerStates(lastAck);
var deletions = _entityManager.GetDeletedEntities(lastAck);

View File

@@ -12,9 +12,9 @@ using Robust.Shared.GameObjects;
using System.Globalization;
using System.Linq;
using Robust.Server.GameObjects;
using Robust.Server.Timing;
using Robust.Shared.ContentPack;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using YamlDotNet.Core;
namespace Robust.Server.Maps

View File

@@ -1,12 +1,50 @@
using System;
using Robust.Server.Console;
using Robust.Server.Player;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Network.Messages;
using Robust.Shared.Prototypes;
namespace Robust.Server.Prototypes
{
public sealed class ServerPrototypeManager : PrototypeManager
{
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IConGroupController _conGroups = default!;
public ServerPrototypeManager() : base()
{
RegisterIgnore("shader");
}
public override void Initialize()
{
base.Initialize();
NetManager.RegisterNetMessage<MsgReloadPrototypes>(MsgReloadPrototypes.NAME, HandleReloadPrototypes, NetMessageAccept.Server);
}
private void HandleReloadPrototypes(MsgReloadPrototypes msg)
{
#if !FULL_RELEASE
if (!_playerManager.TryGetSessionByChannel(msg.MsgChannel, out var player) ||
!_conGroups.CanAdminReloadPrototypes(player))
{
return;
}
var then = DateTime.Now;
foreach (var path in msg.Paths)
{
ReloadPrototypes(path);
}
Logger.Info($"Reloaded prototypes in {(int) (DateTime.Now - then).TotalMilliseconds} ms");
#endif
}
}
}

View File

@@ -10,9 +10,9 @@ using Robust.Server.Prototypes;
using Robust.Server.Reflection;
using Robust.Server.Scripting;
using Robust.Server.ServerStatus;
using Robust.Server.Timing;
using Robust.Server.ViewVariables;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
@@ -20,6 +20,7 @@ using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Timing;
namespace Robust.Server
{
@@ -37,11 +38,11 @@ namespace Robust.Server
IoCManager.Register<IComponentFactory, ServerComponentFactory>();
IoCManager.Register<IConGroupController, ConGroupController>();
IoCManager.Register<IServerConsoleHost, ServerConsoleHost>();
IoCManager.Register<IConsoleHost, ServerConsoleHost>();
IoCManager.Register<IEntityManager, ServerEntityManager>();
IoCManager.Register<IEntityNetworkManager, ServerEntityNetworkManager>();
IoCManager.Register<IServerEntityNetworkManager, ServerEntityNetworkManager>();
IoCManager.Register<IMapLoader, MapLoader>();
IoCManager.Register<IPauseManager, PauseManager>();
IoCManager.Register<IPlacementManager, PlacementManager>();
IoCManager.Register<IPlayerManager, PlayerManager>();
IoCManager.Register<IPrototypeManager, ServerPrototypeManager>();

View File

@@ -152,21 +152,21 @@ namespace Robust.Server.ServerStatus
private void RegisterCVars()
{
try
var path = PathHelpers.ExecutableRelativeFile("build.json");
if (!File.Exists(path))
{
var buildInfo = File.ReadAllText(PathHelpers.ExecutableRelativeFile("build.json"));
var info = JsonConvert.DeserializeObject<BuildInfo>(buildInfo);
return;
}
// 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)
{
}
var buildInfo = File.ReadAllText(path);
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 ?? "");
void SetCVarIfUnmodified(CVarDef<string> cvar, string val)
{

View File

@@ -1,98 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Robust.Server.Timing
{
internal sealed class PauseManager : IPauseManager, IPostInjectInit
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[ViewVariables] private readonly HashSet<MapId> _pausedMaps = new();
[ViewVariables] private readonly HashSet<MapId> _unInitializedMaps = new();
public void SetMapPaused(MapId mapId, bool paused)
{
if (paused)
{
_pausedMaps.Add(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = true;
}
}
else
{
_pausedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = false;
}
}
}
public void DoMapInitialize(MapId mapId)
{
if (IsMapInitialized(mapId))
{
throw new ArgumentException("That map is already initialized.");
}
_unInitializedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.RunMapInit();
entity.Paused = false;
}
}
public void DoGridMapInitialize(IMapGrid grid) => DoGridMapInitialize(grid.Index);
public void DoGridMapInitialize(GridId gridId)
{
var mapId = _mapManager.GetGrid(gridId).ParentMapId;
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
if (entity.Transform.GridID != gridId)
continue;
entity.RunMapInit();
entity.Paused = false;
}
}
public void AddUninitializedMap(MapId mapId)
{
_unInitializedMaps.Add(mapId);
}
public bool IsMapPaused(MapId mapId) => _pausedMaps.Contains(mapId) || _unInitializedMaps.Contains(mapId);
public bool IsGridPaused(IMapGrid grid) => IsMapPaused(grid.ParentMapId);
public bool IsGridPaused(GridId gridId)
{
var grid = _mapManager.GetGrid(gridId);
return IsGridPaused(grid);
}
public bool IsMapInitialized(MapId mapId)
{
return !_unInitializedMaps.Contains(mapId);
}
public void PostInject()
{
_mapManager.MapDestroyed += (sender, args) =>
{
_pausedMaps.Remove(args.Map);
_unInitializedMaps.Add(args.Map);
};
}
}
}

View File

@@ -1,16 +0,0 @@
using System;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Robust.Server.Timing
{
public static class PauseManagerExt
{
[Pure]
[Obsolete("Use IEntity.Paused directly")]
public static bool IsEntityPaused(this IPauseManager manager, IEntity entity)
{
return entity.Paused;
}
}
}

View File

@@ -32,7 +32,6 @@ namespace Robust.Shared.Scripting
"Robust.Shared.IoC",
"Robust.Shared.Maths",
"Robust.Shared.GameObjects",
"Robust.Shared.Interfaces.GameObjects",
"Robust.Shared.Map",
"Robust.Shared.Prototypes"
};

View File

@@ -767,14 +767,14 @@ namespace Robust.Shared.ContentPack
var dllName = $"{simpleName}.dll";
foreach (var diskLoadPath in _diskLoadPaths)
{
try
{
var path = Path.Combine(diskLoadPath, dllName);
return new PEReader(File.OpenRead(path));
}
catch (FileNotFoundException)
var path = Path.Combine(diskLoadPath, dllName);
if (!File.Exists(path))
{
continue;
}
return new PEReader(File.OpenRead(path));
}
var extraStream = _parent.ExtraRobustLoader?.Invoke(dllName);

View File

@@ -90,7 +90,7 @@ namespace Robust.Shared.ContentPack
{
var prevDir = new DirectoryInfo(prevPath);
var found = false;
foreach (var info in prevDir.GetFileSystemInfos())
foreach (var info in prevDir.EnumerateFileSystemInfos())
{
if (!string.Equals(info.Name, segment, StringComparison.InvariantCultureIgnoreCase))
{

View File

@@ -116,6 +116,12 @@ namespace Robust.Shared.ContentPack
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
IEnumerable<ResourcePath> ContentFindFiles(string path);
/// <summary>
/// Returns a list of paths to all top-level content directories
/// </summary>
/// <returns></returns>
IEnumerable<ResourcePath> GetContentRoots();
/// <summary>
/// Read a file from the mounted content paths to a string.
/// </summary>
@@ -168,4 +174,4 @@ namespace Robust.Shared.ContentPack
return new StreamReader(stream, encoding);
}
}
}
}

View File

@@ -305,6 +305,17 @@ namespace Robust.Shared.ContentPack
AddRoot(ResourcePath.Root, loader);
}
public IEnumerable<ResourcePath> GetContentRoots()
{
foreach (var (_, root) in _contentRoots)
{
if (root is DirLoader loader)
{
yield return new ResourcePath(loader.GetPath(new ResourcePath(@"/")));
}
}
}
internal static bool IsPathValid(ResourcePath path)
{
var asString = path.ToString();

View File

@@ -330,6 +330,7 @@ Types:
System.Globalization:
CompareOptions: { }
CultureInfo: { All: True }
DateTimeStyles: { All: True } # Enum
TextInfo:
Methods:
- "bool get_IsRightToLeft()"
@@ -894,6 +895,7 @@ Types:
MidpointRounding: { } # Enum
MulticastDelegate:
Inherit: Allow
NotImplementedException: { All: True }
NotSupportedException: { All: True }
Nullable: { All: True }
Nullable`1: { All: True }

View File

@@ -202,8 +202,9 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public virtual void HandleNetworkMessage(ComponentMessage message, INetChannel netChannel, ICommonSession? session = null) { }
/// <param name="player"></param>
/// <inheritdoc />
public virtual ComponentState GetComponentState()
public virtual ComponentState GetComponentState(ICommonSession player)
{
if (NetID == null)
throw new InvalidOperationException($"Cannot make state for component without Net ID: {GetType()}");

View File

@@ -5,6 +5,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -132,8 +133,9 @@ namespace Robust.Shared.GameObjects
serializer.DataField(ref _mass, "mass", 1.0f);
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new PhysicsComponentState(_canCollide, _status, _physShapes, _isHard, _mass, LinearVelocity, AngularVelocity, Anchored);
}

View File

@@ -1,5 +1,6 @@
using System;
using Robust.Shared.Localization.Macros;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -33,7 +34,7 @@ namespace Robust.Shared.GameObjects
serializer.DataField(this, x => x.Proper, "proper", false);
}
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new GrammarComponentState(Proper);
}

View File

@@ -1,13 +1,13 @@
using Robust.Server.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Robust.Server.GameObjects
namespace Robust.Shared.GameObjects
{
[RegisterComponent]
[ComponentReference(typeof(SharedIgnorePauseComponent))]
public sealed class IgnorePauseComponent : SharedIgnorePauseComponent
public class IgnorePauseComponent : Component
{
public override string Name => "IgnorePause";
public override void OnAdd()
{
base.OnAdd();

View File

@@ -1,6 +1,7 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -73,7 +74,7 @@ namespace Robust.Shared.GameObjects
}
}
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new OccluderComponentState(Enabled, BoundingBox);
}

View File

@@ -1,5 +1,6 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -39,8 +40,9 @@ namespace Robust.Shared.GameObjects
_mapIndex = MapId.Nullspace;
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new MapComponentState(_mapIndex);
}

View File

@@ -2,6 +2,7 @@
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -61,8 +62,9 @@ namespace Robust.Shared.GameObjects
base.OnRemove();
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new MapGridComponentState(_gridIndex, Grid.HasGravity);
}

View File

@@ -1,5 +1,6 @@
using System;
using Robust.Shared.IoC;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
@@ -137,8 +138,9 @@ namespace Robust.Shared.GameObjects
}
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new MetaDataComponentState(_entityName, _entityDescription, EntityPrototype?.ID);
}

View File

@@ -1,7 +0,0 @@
namespace Robust.Shared.GameObjects
{
public abstract class SharedIgnorePauseComponent : Component
{
public override string Name => "IgnorePause";
}
}

View File

@@ -4,7 +4,7 @@ using System.Threading;
using System.Threading.Tasks;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Shared.GameObjects
{

View File

@@ -1,7 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Timer = Robust.Shared.Timers.Timer;
using Timer = Robust.Shared.Timing.Timer;
namespace Robust.Shared.GameObjects
{

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Players;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
@@ -698,8 +699,9 @@ namespace Robust.Shared.GameObjects
serializer.DataField(ref _noLocalRotation, "noRot", false);
}
/// <param name="player"></param>
/// <inheritdoc />
public override ComponentState GetComponentState()
public override ComponentState GetComponentState(ICommonSession player)
{
return new TransformComponentState(_localPosition, LocalRotation, _parent, _noLocalRotation);
}

View File

@@ -79,7 +79,7 @@ namespace Robust.Shared.GameObjects
get => _paused;
set
{
if (_paused == value || value && HasComponent<SharedIgnorePauseComponent>())
if (_paused == value || value && HasComponent<IgnorePauseComponent>())
return;
_paused = value;

View File

@@ -32,7 +32,7 @@ namespace Robust.Shared.GameObjects
/// Whether the Owner has been paused.
/// </summary>
bool Paused { get; }
/// <summary>
/// Whether the client should synchronize component additions and removals.
/// If this is false and the component gets added or removed server side, the client will not do the same.
@@ -125,8 +125,9 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Get the component's state for replicating on the client.
/// </summary>
/// <param name="player"></param>
/// <returns>ComponentState object</returns>
ComponentState GetComponentState();
ComponentState GetComponentState(ICommonSession player);
/// <summary>
/// Handles an incoming component state from the server.

View File

@@ -1,11 +1,9 @@
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Defines a component that has "map initialization" behavior.
/// Basically irreversible behavior that moves the map from "map editor" to playable,
/// like spawning preset objects.
/// Defines a component that has "map initialization" behavior.
/// Basically irreversible behavior that moves the map from "map editor" to playable,
/// like spawning preset objects.
/// </summary>
public interface IMapInit
{

View File

@@ -0,0 +1,42 @@
using Lidgren.Network;
using Robust.Shared.Utility;
namespace Robust.Shared.Network.Messages
{
public class MsgReloadPrototypes : NetMessage
{
#region REQUIRED
public const MsgGroups GROUP = MsgGroups.Command;
public const string NAME = nameof(MsgReloadPrototypes);
public MsgReloadPrototypes(INetChannel channel) : base(NAME, GROUP)
{
}
#endregion
public ResourcePath[] Paths = default!;
public override void ReadFromBuffer(NetIncomingMessage buffer)
{
var count = buffer.ReadInt32();
Paths = new ResourcePath[count];
for (var i = 0; i < count; i++)
{
Paths[i] = new ResourcePath(buffer.ReadString());
}
}
public override void WriteToBuffer(NetOutgoingMessage buffer)
{
buffer.Write(Paths.Length);
foreach (var path in Paths)
{
buffer.Write(path.ToString());
}
}
}
}

View File

@@ -179,9 +179,33 @@ namespace Robust.Shared.Network
// Well they're in. Kick a connected client with the same GUID if we have to.
if (_assignedUserIds.TryGetValue(userId, out var existing))
{
existing.Disconnect("Another connection has been made with your account.");
// Have to wait until they're properly off the server to avoid any collisions.
await AwaitDisconnectAsync(existing);
if (_awaitingDisconnectToConnect.Contains(userId))
{
connection.Disconnect("Stop trying to connect multiple times at once.");
return;
}
_awaitingDisconnectToConnect.Add(userId);
try
{
existing.Disconnect("Another connection has been made with your account.");
// Have to wait until they're properly off the server to avoid any collisions.
await AwaitDisconnectAsync(existing);
}
finally
{
_awaitingDisconnectToConnect.Remove(userId);
}
}
if (connection.Status == NetConnectionStatus.Disconnecting ||
connection.Status == NetConnectionStatus.Disconnected)
{
Logger.InfoS("net",
"{ConnectionEndpoint} disconnected during handshake",
connection.RemoteEndPoint, userName, userId);
return;
}
var msg = peer.Peer.CreateMessage();
@@ -242,8 +266,12 @@ namespace Robust.Shared.Network
private Task AwaitDisconnectAsync(NetConnection connection)
{
var tcs = new TaskCompletionSource<object?>();
_awaitingDisconnect.Add(connection, tcs);
if (!_awaitingDisconnect.TryGetValue(connection, out var tcs))
{
tcs = new TaskCompletionSource<object?>();
_awaitingDisconnect.Add(connection, tcs);
}
return tcs.Task;
}

View File

@@ -135,6 +135,8 @@ namespace Robust.Shared.Network
private readonly Dictionary<NetConnection, TaskCompletionSource<object?>> _awaitingDisconnect
= new();
private readonly HashSet<NetUserId> _awaitingDisconnectToConnect = new HashSet<NetUserId>();
/// <inheritdoc />
public int Port => _config.GetCVar(CVars.NetPort);

View File

@@ -870,7 +870,7 @@ namespace Robust.Shared.Physics
public void Query<TState>(ref TState state, QueryCallback<TState> callback, in Box2 aabb)
{
using var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
stack.Push(_root);
ref var baseRef = ref _nodes[0];
@@ -915,7 +915,7 @@ namespace Robust.Shared.Physics
{
// NOTE: This is not Box2D's normal ray cast function, since our rays have infinite length.
using var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
stack.Push(_root);

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
@@ -6,6 +6,7 @@ using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
@@ -18,7 +19,7 @@ namespace Robust.Shared.Prototypes
/// Prototype that represents game entities.
/// </summary>
[Prototype("entity")]
public class EntityPrototype : IPrototype, IIndexedPrototype, ISyncingPrototype
public class EntityPrototype : IPrototype, ISyncingPrototype
{
[Dependency] private readonly IComponentFactory _componentFactory = default!;
@@ -257,6 +258,11 @@ namespace Robust.Shared.Prototypes
}
}
public void Reset()
{
Children.Clear();
}
// Resolve inheritance.
public bool Sync(IPrototypeManager manager, int stage)
{
@@ -424,6 +430,69 @@ namespace Robust.Shared.Prototypes
}
}
public void UpdateEntity(Entity entity)
{
bool HasBeenModified(string name, YamlMappingNode data, EntityPrototype prototype, IComponent currentComponent, IComponentFactory factory)
{
var component = factory.GetComponent(name);
prototype.CurrentDeserializingComponent = name;
ObjectSerializer ser = YamlObjectSerializer.NewReader(data, new PrototypeSerializationContext(prototype));
component.ExposeData(ser);
return component == (Component) currentComponent;
}
if (ID != entity.Prototype?.ID)
{
Logger.Error($"Reloaded prototype used to update entity did not match entity's existing prototype: Expected '{ID}', got '{entity.Prototype?.ID}'");
return;
}
var factory = IoCManager.Resolve<IComponentFactory>();
var componentManager = IoCManager.Resolve<IComponentManager>();
var oldPrototype = entity.Prototype;
var oldPrototypeComponents = oldPrototype.Components.Keys
.Where(n => n != "Transform" && n != "MetaData")
.Select(name => (name, factory.GetRegistration(name).Type))
.ToList();
var newPrototypeComponents = Components.Keys
.Where(n => n != "Transform" && n != "MetaData")
.Select(name => (name, factory.GetRegistration(name).Type))
.ToList();
var ignoredComponents = new List<string>();
// Find components to be removed, and remove them
foreach (var (name, type) in oldPrototypeComponents.Except(newPrototypeComponents))
{
if (!HasBeenModified(name, oldPrototype.Components[name], oldPrototype, entity.GetComponent(type),
factory) && Components.Keys.Contains(name))
{
ignoredComponents.Add(name);
continue;
}
componentManager.RemoveComponent(entity.Uid, type);
}
componentManager.CullRemovedComponents();
// Add new components
foreach (var (name, type) in newPrototypeComponents.Where(t => !ignoredComponents.Contains(t.name)).Except(oldPrototypeComponents))
{
var data = Components[name];
var component = (Component) factory.GetComponent(name);
ObjectSerializer ser = YamlObjectSerializer.NewReader(data, new PrototypeSerializationContext(this));
CurrentDeserializingComponent = name;
component.Owner = entity;
component.ExposeData(ser);
entity.AddComponent(component);
}
// Update entity metadata
entity.MetaData.EntityPrototype = this;
}
internal static void LoadEntity(EntityPrototype? prototype, Entity entity, IComponentFactory factory, IEntityLoadContext? context)
{
YamlObjectSerializer.Context? defaultContext = null;

View File

@@ -3,30 +3,24 @@
namespace Robust.Shared.Prototypes
{
/// <summary>
/// An IPrototype is a prototype that can be loaded from the global YAML prototypes.
/// An IPrototype is a prototype that can be loaded from the global YAML prototypes.
/// </summary>
/// <remarks>
/// To use this, the prototype must be accessible through IoC with <see cref="IoCTargetAttribute"/>
/// and it must have a <see cref="PrototypeAttribute"/> to give it a type string.
/// To use this, the prototype must be accessible through IoC with <see cref="IoCTargetAttribute"/>
/// and it must have a <see cref="PrototypeAttribute"/> to give it a type string.
/// </remarks>
public interface IPrototype
{
/// <summary>
/// Load data from the YAML mappings in the prototype files.
/// </summary>
void LoadFrom(YamlMappingNode mapping);
}
/// <summary>
/// Extension on <see cref="IPrototype"/> that allows it to be "indexed" by a string ID.
/// </summary>
public interface IIndexedPrototype
{
/// <summary>
/// An ID for this prototype instance.
/// If this is a duplicate, an error will be thrown.
/// </summary>
string ID { get; }
/// <summary>
/// Load data from the YAML mappings in the prototype files.
/// </summary>
void LoadFrom(YamlMappingNode mapping);
}
/// <summary>
@@ -35,6 +29,8 @@ namespace Robust.Shared.Prototypes
/// </summary>
public interface ISyncingPrototype
{
void Reset();
/// <summary>
/// Sync and update cross-referencing data.
/// Syncing works in stages, each time it will be called with the stage it's currently on.

View File

@@ -4,11 +4,16 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Robust.Shared.Asynchronous;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.IoC.Exceptions;
using Robust.Shared.Log;
using Robust.Shared.Network;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using YamlDotNet.Core;
@@ -21,6 +26,8 @@ namespace Robust.Shared.Prototypes
/// </summary>
public interface IPrototypeManager
{
void Initialize();
/// <summary>
/// Return an IEnumerable to iterate all prototypes of a certain type.
/// </summary>
@@ -28,6 +35,7 @@ namespace Robust.Shared.Prototypes
/// Thrown if the type of prototype is not registered.
/// </exception>
IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype;
/// <summary>
/// Return an IEnumerable to iterate all prototypes of a certain type.
/// </summary>
@@ -35,31 +43,44 @@ namespace Robust.Shared.Prototypes
/// Thrown if the type of prototype is not registered.
/// </exception>
IEnumerable<IPrototype> EnumeratePrototypes(Type type);
/// <summary>
/// Index for a <see cref="IIndexedPrototype"/> by ID.
/// Index for a <see cref="IPrototype"/> by ID.
/// </summary>
/// <exception cref="KeyNotFoundException">
/// Thrown if the type of prototype is not registered.
/// </exception>
T Index<T>(string id) where T : class, IIndexedPrototype;
T Index<T>(string id) where T : class, IPrototype;
/// <summary>
/// Index for a <see cref="IIndexedPrototype"/> by ID.
/// Index for a <see cref="IPrototype"/> by ID.
/// </summary>
/// <exception cref="KeyNotFoundException">
/// Thrown if the ID does not exist or the type of prototype is not registered.
/// </exception>
IIndexedPrototype Index(Type type, string id);
bool HasIndex<T>(string id) where T : IIndexedPrototype;
bool TryIndex<T>(string id, out T prototype) where T : IIndexedPrototype;
IPrototype Index(Type type, string id);
bool HasIndex<T>(string id) where T : IPrototype;
bool TryIndex<T>(string id, out T prototype) where T : IPrototype;
/// <summary>
/// Load prototypes from files in a directory, recursively.
/// </summary>
void LoadDirectory(ResourcePath path);
void LoadFromStream(TextReader stream);
Task<List<IPrototype>> LoadDirectory(ResourcePath path);
List<IPrototype> LoadFromStream(TextReader stream);
List<IPrototype> LoadString(string str);
/// <summary>
/// Clear out all prototypes and reset to a blank slate.
/// </summary>
void Clear();
/// <summary>
/// Performs a reload on all prototypes, updating the game state accordingly
/// </summary>
void ReloadPrototypes(ResourcePath file);
/// <summary>
/// Syncs all inter-prototype data. Call this when operations adding new prototypes are done.
/// </summary>
@@ -78,7 +99,6 @@ namespace Robust.Shared.Prototypes
void RegisterType(Type protoClass);
event Action<YamlStream, string>? LoadedData;
}
/// <summary>
@@ -102,25 +122,40 @@ namespace Robust.Shared.Prototypes
{
[Dependency] private readonly IReflectionManager ReflectionManager = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynamicTypeFactory = default!;
[Dependency] private readonly IResourceManager _resources = default!;
[Dependency] public readonly IResourceManager Resources = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] public readonly ITaskManager TaskManager = default!;
[Dependency] public readonly INetManager NetManager = default!;
private readonly Dictionary<string, Type> prototypeTypes = new();
private bool _initialized;
private bool _hasEverBeenReloaded;
private bool _hasEverResynced;
#region IPrototypeManager members
private readonly Dictionary<Type, List<IPrototype>> prototypes = new();
private readonly Dictionary<Type, Dictionary<string, IIndexedPrototype>> indexedPrototypes = new();
private readonly Dictionary<Type, Dictionary<string, IPrototype>> prototypes = new();
private readonly HashSet<string> IgnoredPrototypeTypes = new();
public virtual void Initialize()
{
if (_initialized)
{
throw new InvalidOperationException($"{nameof(PrototypeManager)} has already been initialized.");
}
_initialized = true;
}
public IEnumerable<T> EnumeratePrototypes<T>() where T : class, IPrototype
{
if (!_hasEverBeenReloaded)
{
throw new InvalidOperationException("No prototypes have been loaded yet.");
}
return prototypes[typeof(T)].Select((IPrototype p) => (T)p);
return prototypes[typeof(T)].Values.Select(p => (T) p);
}
public IEnumerable<IPrototype> EnumeratePrototypes(Type type)
@@ -129,10 +164,11 @@ namespace Robust.Shared.Prototypes
{
throw new InvalidOperationException("No prototypes have been loaded yet.");
}
return prototypes[type];
return prototypes[type].Values;
}
public T Index<T>(string id) where T : class, IIndexedPrototype
public T Index<T>(string id) where T : class, IPrototype
{
if (!_hasEverBeenReloaded)
{
@@ -140,7 +176,7 @@ namespace Robust.Shared.Prototypes
}
try
{
return (T)indexedPrototypes[typeof(T)][id];
return (T)prototypes[typeof(T)][id];
}
catch (KeyNotFoundException)
{
@@ -148,24 +184,60 @@ namespace Robust.Shared.Prototypes
}
}
public IIndexedPrototype Index(Type type, string id)
public IPrototype Index(Type type, string id)
{
if (!_hasEverBeenReloaded)
{
throw new InvalidOperationException("No prototypes have been loaded yet.");
}
return indexedPrototypes[type][id];
return prototypes[type][id];
}
public void Clear()
{
prototypes.Clear();
prototypeTypes.Clear();
indexedPrototypes.Clear();
prototypes.Clear();
}
public virtual async void ReloadPrototypes(ResourcePath file)
{
#if !FULL_RELEASE
var changed = await LoadFile(file.ToRootedPath(), true);
Resync();
foreach (var prototype in changed)
{
if (prototype is not EntityPrototype entityPrototype)
{
continue;
}
foreach (var entity in _entityManager.GetEntities(new PredicateEntityQuery(e => e.Prototype != null && e.Prototype.ID == entityPrototype.ID)))
{
entityPrototype.UpdateEntity((Entity) entity);
}
}
#endif
}
public void Resync()
{
// TODO Make this smarter and only resync changed prototypes
if (_hasEverResynced)
{
foreach (var prototypeList in prototypes.Values)
{
foreach (var prototype in prototypeList.Values)
{
if (prototype is ISyncingPrototype syncing)
{
syncing.Reset();
}
}
}
}
foreach (Type type in prototypeTypes.Values.Where(t => typeof(ISyncingPrototype).IsAssignableFrom(t)))
{
// This list is the list of prototypes we're syncing.
@@ -177,17 +249,18 @@ namespace Robust.Shared.Prototypes
// When we get to the end, do the whole thing again!
// Yes this is ridiculously overengineered BUT IT PERFORMS WELL.
// I hope.
List<ISyncingPrototype> currentRun = prototypes[type].Select(p => (ISyncingPrototype)p).ToList();
int stage = 0;
List<ISyncingPrototype> currentRun = prototypes[type].Values.Select(p => (ISyncingPrototype) p).ToList();
var stage = 0;
// Outer loop to iterate stages.
while (currentRun.Count > 0)
{
// Increase positions to iterate over list.
// If we need to stick, i gets reduced down below.
for (int i = 0; i < currentRun.Count; i++)
for (var i = 0; i < currentRun.Count; i++)
{
ISyncingPrototype prototype = currentRun[i];
bool result = prototype.Sync(this, stage);
var result = prototype.Sync(this, stage);
// Keep prototype and move on to next one if it returns true.
// Thus it stays in the list for next stage.
if (result)
@@ -204,80 +277,135 @@ namespace Robust.Shared.Prototypes
stage++;
}
}
_hasEverResynced = true;
}
/// <inheritdoc />
public void LoadDirectory(ResourcePath path)
public async Task<List<IPrototype>> LoadDirectory(ResourcePath path)
{
var sawmill = Logger.GetSawmill("eng");
var changedPrototypes = new List<IPrototype>();
_hasEverBeenReloaded = true;
var yamlStreams = _resources.ContentFindFiles(path).ToList().AsParallel()
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."))
.Select(filePath =>
{
try
{
using var reader = new StreamReader(_resources.ContentFileRead(filePath), EncodingHelpers.UTF8);
var yamlStream = new YamlStream();
yamlStream.Load(reader);
var streams = Resources.ContentFindFiles(path).ToList().AsParallel()
.Where(filePath => filePath.Extension == "yml" && !filePath.Filename.StartsWith("."));
var result = ((YamlStream? yamlStream, ResourcePath?))(yamlStream, filePath);
LoadedData?.Invoke(yamlStream, filePath.ToString());
return result;
}
catch (YamlException e)
{
sawmill.Error("YamlException whilst loading prototypes from {0}: {1}", filePath, e.Message);
return (null, null);
}
})
.Where(p => p.yamlStream != null) // Filter out loading errors.
.ToList();
foreach (var (stream, filePath) in yamlStreams)
foreach (var resourcePath in streams)
{
for (var i = 0; i < stream!.Documents.Count; i++)
var filePrototypes = await LoadFile(resourcePath);
changedPrototypes.AddRange(filePrototypes);
}
return changedPrototypes;
}
private Task<StreamReader?> ReadFile(ResourcePath file, bool @throw = true)
{
var retries = 0;
// This might be shit-code, but its pjb-responded-idk-when-asked shit-code.
while (true)
{
try
{
try
var reader = new StreamReader(Resources.ContentFileRead(file), EncodingHelpers.UTF8);
return Task.FromResult<StreamReader?>(reader);
}
catch (IOException e)
{
if (retries > 10)
{
LoadFromDocument(stream.Documents[i]);
}
catch (Exception e)
{
Logger.ErrorS("eng", $"Exception whilst loading prototypes from {filePath}#{i}:\n{e}");
if (@throw)
{
throw;
}
Logger.Error($"Error reloading prototypes in file {file}.", e);
return Task.FromResult<StreamReader?>(null);
}
retries++;
Thread.Sleep(10);
}
}
}
public void LoadFromStream(TextReader stream)
public async Task<List<IPrototype>> LoadFile(ResourcePath file, bool overwrite = false)
{
var changedPrototypes = new List<IPrototype>();
try
{
using var reader = await ReadFile(file, !overwrite);
if (reader == null)
{
return changedPrototypes;
}
var yamlStream = new YamlStream();
yamlStream.Load(reader);
LoadedData?.Invoke(yamlStream, file.ToString());
for (var i = 0; i < yamlStream.Documents.Count; i++)
{
try
{
var documentPrototypes = LoadFromDocument(yamlStream.Documents[i], overwrite);
changedPrototypes.AddRange(documentPrototypes);
}
catch (Exception e)
{
Logger.ErrorS("eng", $"Exception whilst loading prototypes from {file}#{i}:\n{e}");
}
}
}
catch (YamlException e)
{
var sawmill = Logger.GetSawmill("eng");
sawmill.Error("YamlException whilst loading prototypes from {0}: {1}", file, e.Message);
}
return changedPrototypes;
}
public List<IPrototype> LoadFromStream(TextReader stream)
{
var changedPrototypes = new List<IPrototype>();
_hasEverBeenReloaded = true;
var yaml = new YamlStream();
yaml.Load(stream);
for (int i = 0; i < yaml.Documents.Count; i++)
for (var i = 0; i < yaml.Documents.Count; i++)
{
try
{
LoadFromDocument(yaml.Documents[i]);
var documentPrototypes = LoadFromDocument(yaml.Documents[i]);
changedPrototypes.AddRange(documentPrototypes);
}
catch (Exception e)
{
throw new PrototypeLoadException(string.Format("Failed to load prototypes from document#{0}", i), e);
throw new PrototypeLoadException($"Failed to load prototypes from document#{i}", e);
}
}
LoadedData?.Invoke(yaml, "anonymous prototypes YAML stream");
return changedPrototypes;
}
public List<IPrototype> LoadString(string str)
{
return LoadFromStream(new StringReader(str));
}
#endregion IPrototypeManager members
public void PostInject()
{
ReflectionManager.OnAssemblyAdded += (_, __) => ReloadPrototypeTypes();
ReflectionManager.OnAssemblyAdded += (_, _) => ReloadPrototypeTypes();
ReloadPrototypeTypes();
}
@@ -290,9 +418,11 @@ namespace Robust.Shared.Prototypes
}
}
private void LoadFromDocument(YamlDocument document)
private List<IPrototype> LoadFromDocument(YamlDocument document, bool overwrite = false)
{
var rootNode = (YamlSequenceNode)document.RootNode;
var changedPrototypes = new List<IPrototype>();
var rootNode = (YamlSequenceNode) document.RootNode;
foreach (YamlMappingNode node in rootNode.Cast<YamlMappingNode>())
{
var type = node.GetNode("type").AsString();
@@ -302,38 +432,41 @@ namespace Robust.Shared.Prototypes
{
continue;
}
throw new PrototypeLoadException(string.Format("Unknown prototype type: '{0}'", type));
throw new PrototypeLoadException($"Unknown prototype type: '{type}'");
}
var prototypeType = prototypeTypes[type];
var prototype = _dynamicTypeFactory.CreateInstanceUnchecked<IPrototype>(prototypeType);
prototype.LoadFrom(node);
prototypes[prototypeType].Add(prototype);
var indexedPrototype = prototype as IIndexedPrototype;
if (indexedPrototype != null)
changedPrototypes.Add(prototype);
var id = prototype.ID;
if (!overwrite && prototypes[prototypeType].ContainsKey(id))
{
var id = indexedPrototype.ID;
if (indexedPrototypes[prototypeType].ContainsKey(id))
{
throw new PrototypeLoadException(string.Format("Duplicate ID: '{0}'", id));
}
indexedPrototypes[prototypeType][id] = (IIndexedPrototype)prototype;
throw new PrototypeLoadException($"Duplicate ID: '{id}'");
}
prototypes[prototypeType][id] = prototype;
}
return changedPrototypes;
}
public bool HasIndex<T>(string id) where T : IIndexedPrototype
public bool HasIndex<T>(string id) where T : IPrototype
{
if (!indexedPrototypes.TryGetValue(typeof(T), out var index))
if (!prototypes.TryGetValue(typeof(T), out var index))
{
throw new UnknownPrototypeException(id);
}
return index.ContainsKey(id);
}
public bool TryIndex<T>(string id, [MaybeNullWhen(false)] out T prototype) where T : IIndexedPrototype
public bool TryIndex<T>(string id, [MaybeNullWhen(false)] out T prototype) where T : IPrototype
{
if (!indexedPrototypes.TryGetValue(typeof(T), out var index))
if (!prototypes.TryGetValue(typeof(T), out var index))
{
throw new UnknownPrototypeException(id);
}
@@ -370,11 +503,10 @@ namespace Robust.Shared.Prototypes
}
prototypeTypes[attribute.Type] = type;
prototypes[type] = new List<IPrototype>();
if (typeof(IIndexedPrototype).IsAssignableFrom(type))
if (typeof(IPrototype).IsAssignableFrom(type))
{
indexedPrototypes[type] = new Dictionary<string, IIndexedPrototype>();
prototypes[type] = new Dictionary<string, IPrototype>();
}
}
@@ -398,11 +530,6 @@ namespace Robust.Shared.Prototypes
public PrototypeLoadException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
}
[Serializable]

View File

@@ -69,7 +69,7 @@ namespace Robust.Shared.Reflection
/// </summary>
/// <typeparam name="T">Attribute to search for.</typeparam>
/// <returns>Enumeration of all types with the specified attribute.</returns>
IEnumerable<Type> FindTypesWithAttribute<T>();
IEnumerable<Type> FindTypesWithAttribute<T>() where T : Attribute;
/// <summary>
/// Loads assemblies into the manager and get all the types.

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Robust.Shared.Log;
using Robust.Shared.Serialization;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Reflection
@@ -158,7 +159,7 @@ namespace Robust.Shared.Reflection
}
/// <inheritdoc />
public IEnumerable<Type> FindTypesWithAttribute<T>()
public IEnumerable<Type> FindTypesWithAttribute<T>() where T : Attribute
{
var types = new List<Type>();
@@ -217,7 +218,21 @@ namespace Robust.Shared.Reflection
Type? found = null;
foreach (var derivedType in GetAllChildren(baseType))
{
if (derivedType.Name == typeName && (derivedType.IsPublic))
if (!derivedType.IsPublic)
{
continue;
}
if (derivedType.Name == typeName)
{
found = derivedType;
break;
}
var serializedAttribute = derivedType.GetCustomAttribute<SerializedTypeAttribute>();
if (serializedAttribute != null &&
serializedAttribute.SerializeName == typeName)
{
found = derivedType;
break;

View File

@@ -14,6 +14,8 @@ namespace Robust.Shared.Serialization
[PublicAPI]
internal interface IRobustMappedStringSerializer
{
bool Locked { get; }
/// <summary>
/// The type serializer to register with NetSerializer.
/// </summary>

View File

@@ -133,6 +133,7 @@ namespace Robust.Shared.Serialization
| RegexOptions.IgnorePatternWhitespace
);
public bool Locked => _dict.Locked;
public ITypeSerializer TypeSerializer => this;

View File

@@ -0,0 +1,18 @@
using System;
namespace Robust.Shared.Serialization
{
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class SerializedTypeAttribute : Attribute
{
/// <summary>
/// Name of this type in serialized files.
/// </summary>
public string SerializeName { get; }
public SerializedTypeAttribute(string serializeName)
{
SerializeName = serializeName;
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Robust.Shared.Serialization
_formatter = formatter;
}
public override object NodeToType(Type _type, YamlNode node, YamlObjectSerializer serializer)
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)
{
return _formatter.FromCustomFormat(serializer.NodeToType(_formatter.Format, node));
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@@ -38,6 +39,7 @@ namespace Robust.Shared.Serialization
{
{ typeof(Color), new ColorSerializer() },
{ typeof(Vector2), new Vector2Serializer() },
{ typeof(Vector2i), new Vector2iSerializer() },
{ typeof(Vector3), new Vector3Serializer() },
{ typeof(Vector4), new Vector4Serializer() },
{ typeof(Angle), new AngleSerializer() },
@@ -510,6 +512,24 @@ namespace Robust.Shared.Serialization
return newList;
}
if (TryGenericImmutableListType(type, out var immutableListType))
{
var listNode = (YamlSequenceNode) node;
var elems = listNode.Children;
var newList = Array.CreateInstance(immutableListType, elems.Count);
for (var i = 0; i < elems.Count; i++)
{
newList.SetValue(NodeToType(immutableListType, elems[i]), i);
}
var list = typeof(ImmutableList);
var add = list.GetMethod("CreateRange")!.MakeGenericMethod(immutableListType);
return add.Invoke(null, new object?[] {newList})!;
}
// Dictionary<K,V>/IReadOnlyDictionary<K,V>
if (TryGenericReadDictType(type, out var keyType, out var valType, out var dictType))
{
@@ -584,12 +604,8 @@ namespace Robust.Shared.Serialization
// IExposeData.
if (typeof(IExposeData).IsAssignableFrom(type))
{
if (!(node is YamlMappingNode mapNode))
{
throw new InvalidOperationException($"Cannot read from IExposeData on non-mapping node. Type: '{type}'");
}
var concreteType = type;
if (type.IsAbstract || type.IsInterface)
{
var tag = node.Tag;
@@ -607,6 +623,12 @@ namespace Robust.Shared.Serialization
}
var instance = (IExposeData)Activator.CreateInstance(concreteType)!;
if (node is not YamlMappingNode mapNode)
{
return instance;
}
// TODO: Might be worth it to cut down on allocations here by using ourselves instead of creating a fork.
// Seems doable.
if (_context != null)
@@ -639,7 +661,7 @@ namespace Robust.Shared.Serialization
throw new ArgumentException($"Type {type.FullName} is not supported.", nameof(type));
}
public T NodeToType<T>(YamlNode node)
public T NodeToType<T>(YamlNode node, string name)
{
return (T) NodeToType(typeof(T), node);
}
@@ -723,6 +745,28 @@ namespace Robust.Shared.Serialization
return node;
}
if (TryGenericImmutableListType(type, out var immutableListType))
{
var node = new YamlSequenceNode {Tag = TagSkipTag};
foreach (var entry in (IEnumerable) obj)
{
if (entry == null)
{
throw new ArgumentException("Cannot serialize null value inside list.");
}
var entryNode = TypeToNode(entry);
// write the concrete type tag
AssignTag<object?>(immutableListType, entry, null, entryNode);
node.Add(entryNode);
}
return node;
}
// Dictionary<K,V>
if (TryGenericDictType(type, out var keyType, out var valType)
|| TryGenericReadOnlyDictType(type, out keyType, out valType))
@@ -861,7 +905,8 @@ namespace Robust.Shared.Serialization
return true;
}
if (TryGenericListType(type!, out _))
if (TryGenericListType(type!, out _) ||
TryGenericImmutableListType(type!, out _))
{
var listA = (IList) a;
var listB = (IList) b!;
@@ -1023,6 +1068,21 @@ namespace Robust.Shared.Serialization
return false;
}
private static bool TryGenericImmutableListType(Type type, [NotNullWhen(true)] out Type? listType)
{
var isImmutableList = type.GetTypeInfo().IsGenericType &&
type.GetGenericTypeDefinition() == typeof(ImmutableList<>);
if (isImmutableList)
{
listType = type.GetGenericArguments()[0];
return true;
}
listType = default;
return false;
}
private static bool TryGenericReadOnlyDictType(Type type, [NotNullWhen(true)] out Type? keyType, [NotNullWhen(true)] out Type? valType)
{
var isDict = type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>);
@@ -1287,6 +1347,20 @@ namespace Robust.Shared.Serialization
}
}
class Vector2iSerializer : TypeSerializer
{
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)
{
return node.AsVector2i();
}
public override YamlNode TypeToNode(object obj, YamlObjectSerializer serializer)
{
var vec = (Vector2i)obj;
return new YamlScalarNode($"{vec.X.ToString(CultureInfo.InvariantCulture)},{vec.Y.ToString(CultureInfo.InvariantCulture)}");
}
}
class Vector3Serializer : TypeSerializer
{
public override object NodeToType(Type type, YamlNode node, YamlObjectSerializer serializer)

View File

@@ -13,7 +13,6 @@ using Robust.Shared.Physics;
using Robust.Shared.Random;
using Robust.Shared.Sandboxing;
using Robust.Shared.Serialization;
using Robust.Shared.Timers;
using Robust.Shared.Timing;
namespace Robust.Shared
@@ -35,6 +34,7 @@ namespace Robust.Shared
IoCManager.Register<ILogManager, LogManager>();
IoCManager.Register<IMapManager, MapManager>();
IoCManager.Register<IMapManagerInternal, MapManager>();
IoCManager.Register<IPauseManager, PauseManager>();
IoCManager.Register<IModLoader, ModLoader>();
IoCManager.Register<IModLoaderInternal, ModLoader>();
IoCManager.Register<INetManager, NetManager>();

View File

@@ -123,8 +123,6 @@ namespace Robust.Shared.Timing
FrameEventArgs realFrameEvent;
FrameEventArgs simFrameEvent;
_timing.ResetRealTime();
while (Running)
{
// maximum number of ticks to queue before the loop slows down.

View File

@@ -92,18 +92,13 @@ namespace Robust.Shared.Timing
{
get
{
if (_netManager.IsServer)
{
return RealTime;
}
var clientNetManager = (IClientNetManager) _netManager;
if (clientNetManager.ServerChannel == null)
var offset = GetServerOffset();
if (offset == null)
{
return TimeSpan.Zero;
}
return clientNetManager.ServerChannel.RemoteTime;
return RealTime + offset.Value;
}
}
@@ -236,15 +231,6 @@ namespace Robust.Shared.Timing
_cachedCurTimeInfo = (newTime, CurTick);
}
/// <summary>
/// Resets the real uptime of the server.
/// </summary>
public void ResetRealTime()
{
_realTimer.Restart();
_lastRealTime = TimeSpan.Zero;
}
/// <summary>
/// Resets the simulation time.
/// </summary>
@@ -256,6 +242,35 @@ namespace Robust.Shared.Timing
Paused = true;
}
public TimeSpan RealLocalToServer(TimeSpan local)
{
var offset = GetServerOffset();
if (offset == null)
return TimeSpan.Zero;
return local + offset.Value;
}
public TimeSpan RealServerToLocal(TimeSpan server)
{
var offset = GetServerOffset();
if (offset == null)
return TimeSpan.Zero;
return server - offset.Value;
}
private TimeSpan? GetServerOffset()
{
if (_netManager.IsServer)
{
return TimeSpan.Zero;
}
var clientNetManager = (IClientNetManager) _netManager;
return clientNetManager.ServerChannel?.RemoteTimeOffset;
}
public bool IsFirstTimePredicted { get; private set; } = true;
/// <inheritdoc />

View File

@@ -123,11 +123,6 @@ namespace Robust.Shared.Timing
/// </summary>
void StartFrame();
/// <summary>
/// Resets the real uptime of the server.
/// </summary>
void ResetRealTime();
/// <summary>
/// Is this the first time CurTick has been predicted?
/// </summary>
@@ -162,5 +157,8 @@ namespace Robust.Shared.Timing
/// Resets the simulation time. This should be called on round restarts.
/// </summary>
void ResetSimTime();
TimeSpan RealLocalToServer(TimeSpan local);
TimeSpan RealServerToLocal(TimeSpan server);
}
}

View File

@@ -1,7 +1,7 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared.Map;
namespace Robust.Server.Timing
namespace Robust.Shared.Timing
{
public interface IPauseManager
{
@@ -26,4 +26,4 @@ namespace Robust.Server.Timing
[Pure]
bool IsMapInitialized(MapId mapId);
}
}
}

View File

@@ -1,7 +1,6 @@
using System.Threading;
using Robust.Shared.Timing;
namespace Robust.Shared.Timers
namespace Robust.Shared.Timing
{
public interface ITimerManager
{

View File

@@ -0,0 +1,179 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Map;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Timing
{
internal sealed class PauseManager : IPauseManager, IPostInjectInit
{
[Dependency] private readonly IConsoleHost _conhost = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[ViewVariables] private readonly HashSet<MapId> _pausedMaps = new();
[ViewVariables] private readonly HashSet<MapId> _unInitializedMaps = new();
public void SetMapPaused(MapId mapId, bool paused)
{
if (paused)
{
_pausedMaps.Add(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = true;
}
}
else
{
_pausedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.Paused = false;
}
}
}
public void DoMapInitialize(MapId mapId)
{
if (IsMapInitialized(mapId))
throw new ArgumentException("That map is already initialized.");
_unInitializedMaps.Remove(mapId);
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
entity.RunMapInit();
entity.Paused = false;
}
}
public void DoGridMapInitialize(IMapGrid grid)
{
DoGridMapInitialize(grid.Index);
}
public void DoGridMapInitialize(GridId gridId)
{
var mapId = _mapManager.GetGrid(gridId).ParentMapId;
foreach (var entity in _entityManager.GetEntitiesInMap(mapId))
{
if (entity.Transform.GridID != gridId)
continue;
entity.RunMapInit();
entity.Paused = false;
}
}
public void AddUninitializedMap(MapId mapId)
{
_unInitializedMaps.Add(mapId);
}
public bool IsMapPaused(MapId mapId)
{
return _pausedMaps.Contains(mapId) || _unInitializedMaps.Contains(mapId);
}
public bool IsGridPaused(IMapGrid grid)
{
return IsMapPaused(grid.ParentMapId);
}
public bool IsGridPaused(GridId gridId)
{
var grid = _mapManager.GetGrid(gridId);
return IsGridPaused(grid);
}
public bool IsMapInitialized(MapId mapId)
{
return !_unInitializedMaps.Contains(mapId);
}
/// <inheritdoc />
public void PostInject()
{
_mapManager.MapDestroyed += (_, args) =>
{
_pausedMaps.Remove(args.Map);
_unInitializedMaps.Add(args.Map);
};
if(_conhost.IsClient)
return;
_conhost.RegisterCommand("pausemap",
"Pauses a map, pausing all simulation processing on it.",
"pausemap <map ID>",
(shell, _, args) =>
{
if (args.Length != 1)
{
shell.WriteError("Need to supply a valid MapId");
return;
}
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
{
shell.WriteError("That map does not exist.");
return;
}
SetMapPaused(mapId, true);
});
_conhost.RegisterCommand("querymappaused",
"Check whether a map is paused or not.",
"querymappaused <map ID>",
(shell, _, args) =>
{
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
{
shell.WriteError("That map does not exist.");
return;
}
shell.WriteLine(IsMapPaused(mapId).ToString());
});
_conhost.RegisterCommand("unpausemap",
"unpauses a map, resuming all simulation processing on it.",
"Usage: unpausemap <map ID>",
(shell, _, args) =>
{
if (args.Length != 1)
{
shell.WriteLine(Loc.GetString("Need to supply a valid MapId"));
return;
}
string? arg = args[0];
var mapId = new MapId(int.Parse(arg, CultureInfo.InvariantCulture));
if (!_mapManager.MapExists(mapId))
{
shell.WriteLine("That map does not exist.");
return;
}
SetMapPaused(mapId, false);
});
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Threading.Tasks;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
namespace Robust.Shared.Timers
namespace Robust.Shared.Timing
{
public class Timer
{

View File

@@ -2,9 +2,8 @@
using System.Threading;
using Robust.Shared.Exceptions;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Robust.Shared.Timers
namespace Robust.Shared.Timing
{
internal sealed class TimerManager : ITimerManager
{

View File

@@ -15,7 +15,8 @@ namespace Robust.Shared.Utility
private static readonly Parser<char, char> ParseEscapeSequence =
Char('\\').Then(OneOf(
Char('\\'),
Char(TagBegin)));
Char(TagBegin),
Char(TagEnd)));
private static readonly Parser<char, TagText> ParseTagText =
ParseEscapeSequence.Or(Token(c => c != TagBegin && c != '\\'))

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