mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
30 Commits
fix/bui-st
...
v222.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63df90f86f | ||
|
|
51f0c60bd3 | ||
|
|
a9ed53f47b | ||
|
|
41c40f1a94 | ||
|
|
6e61c35d35 | ||
|
|
aae0a8bc51 | ||
|
|
cb543240c6 | ||
|
|
1654ab06f5 | ||
|
|
211245215e | ||
|
|
10aaaa65c5 | ||
|
|
d2a2afe82e | ||
|
|
025d90d281 | ||
|
|
c229f2e312 | ||
|
|
fe051a3577 | ||
|
|
51a0ef1e60 | ||
|
|
702dfef5fc | ||
|
|
a0c1ad246f | ||
|
|
1153888bd1 | ||
|
|
ccbb6ddec7 | ||
|
|
970da5f717 | ||
|
|
4d528dd577 | ||
|
|
c83720b163 | ||
|
|
bd87a805d4 | ||
|
|
fff42fb2b4 | ||
|
|
4500669f65 | ||
|
|
7d19ea9338 | ||
|
|
2dc610907d | ||
|
|
beb1c4b1fb | ||
|
|
7e331eaa75 | ||
|
|
caf9e45ad9 |
@@ -7,6 +7,18 @@ indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
max_line_length = 120
|
||||
|
||||
# ReSharper properties
|
||||
resharper_csharp_max_line_length = 120
|
||||
resharper_csharp_wrap_after_declaration_lpar = true
|
||||
resharper_csharp_wrap_arguments_style = chop_if_long
|
||||
resharper_csharp_wrap_parameters_style = chop_if_long
|
||||
resharper_keep_existing_attribute_arrangement = true
|
||||
resharper_place_field_attribute_on_same_line = if_owner_is_single_line
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
|
||||
[*.{csproj,xml,yml,dll.config,targets,props}]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
6
.github/workflows/publish-client.yml
vendored
6
.github/workflows/publish-client.yml
vendored
@@ -33,10 +33,10 @@ jobs:
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
|
||||
- name: Upload files to centcomm
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 61a56c60bd...1d85b82e05
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,69 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 222.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `EntityQuery.Comp()` (abbreviation of `GetComponent()`)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `SerializationManager.TryGetVariableType` checking the wrong property.
|
||||
* Fixed GrammarSystem mispredicting a character's gender
|
||||
|
||||
### Other
|
||||
|
||||
* User interface system now performs range checks in parallel
|
||||
|
||||
|
||||
## 222.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed never setting BoundUserInterface.State.
|
||||
|
||||
### Other
|
||||
|
||||
* Add truncate for filesaving.
|
||||
* Add method for getting the type of a data field by name from ISerializationManager.
|
||||
|
||||
|
||||
## 222.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `BoundKeyEventArgs.IsRepeat`.
|
||||
* Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix assert trip when holding repeatable keybinds.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled.
|
||||
|
||||
|
||||
## 222.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Mark IComponentFactory argument in EntityPrototype as mandatory.
|
||||
|
||||
### New features
|
||||
|
||||
* Add `EntProtoId<T>` to check for components on the attached entity as well.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix PVS iterating duplicate chunks for multiple viewsubscriptions.
|
||||
|
||||
### Other
|
||||
|
||||
* Defer clientside BUI opens if it's the first state that comes in.
|
||||
|
||||
|
||||
## 221.2.0
|
||||
|
||||
### New features
|
||||
|
||||
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class NoUncachedRegexAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public static class Foo
|
||||
{
|
||||
public static void Bad()
|
||||
{
|
||||
Regex.Replace("foo", "bar", "baz");
|
||||
}
|
||||
|
||||
public static void Good()
|
||||
{
|
||||
var r = new Regex("bar");
|
||||
r.Replace("foo", "baz");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.
|
||||
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43)
|
||||
);
|
||||
}
|
||||
}
|
||||
66
Robust.Analyzers/NoUncachedRegexAnalyzer.cs
Normal file
66
Robust.Analyzers/NoUncachedRegexAnalyzer.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class NoUncachedRegexAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string RegexTypeName = "Regex";
|
||||
private const string RegexType = $"System.Text.RegularExpressions.{RegexTypeName}";
|
||||
|
||||
private static readonly DiagnosticDescriptor Rule = new (
|
||||
Diagnostics.IdUncachedRegex,
|
||||
"Use of uncached static Regex function",
|
||||
"Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
|
||||
|
||||
public static readonly HashSet<string> BadFunctions =
|
||||
[
|
||||
"Count",
|
||||
"EnumerateMatches",
|
||||
"IsMatch",
|
||||
"Match",
|
||||
"Matches",
|
||||
"Replace",
|
||||
"Split"
|
||||
];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.EnableConcurrentExecution();
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
||||
context.RegisterOperationAction(CheckInvocation, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private static void CheckInvocation(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation invocation)
|
||||
return;
|
||||
|
||||
// All Regex functions we care about are static.
|
||||
var targetMethod = invocation.TargetMethod;
|
||||
if (!targetMethod.IsStatic)
|
||||
return;
|
||||
|
||||
// Bail early.
|
||||
if (targetMethod.ContainingType.Name != "Regex")
|
||||
return;
|
||||
|
||||
var regexType = context.Compilation.GetTypeByMetadataName(RegexType);
|
||||
if (!SymbolEqualityComparer.Default.Equals(regexType, targetMethod.ContainingType))
|
||||
return;
|
||||
|
||||
if (!BadFunctions.Contains(targetMethod.Name))
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(Rule, invocation.Syntax.GetLocation()));
|
||||
}
|
||||
}
|
||||
@@ -239,6 +239,7 @@ namespace Robust.Build.Tasks
|
||||
engine.LogErrorEvent(new BuildErrorEventArgs("XAMLIL", "", res.FilePath, 0, 0, 0, 0,
|
||||
$"{res.FilePath}: {e.Message}", "", "CompileRobustXaml"));
|
||||
}
|
||||
res.Remove();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ namespace Robust.Client.Input
|
||||
{
|
||||
if (binding.CanRepeat)
|
||||
{
|
||||
return SetBindState(binding, BoundKeyState.Down, uiOnly);
|
||||
return SetBindState(binding, BoundKeyState.Down, uiOnly, isRepeat);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -375,7 +375,7 @@ namespace Robust.Client.Input
|
||||
SetBindState(binding, BoundKeyState.Up);
|
||||
}
|
||||
|
||||
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false)
|
||||
private bool SetBindState(KeyBinding binding, BoundKeyState state, bool uiOnly = false, bool isRepeat = false)
|
||||
{
|
||||
if (binding.BindingType == KeyBindingType.Command && state == BoundKeyState.Down)
|
||||
{
|
||||
@@ -387,6 +387,7 @@ namespace Robust.Client.Input
|
||||
// I honestly have no idea what the best solution here is.
|
||||
// note from the future: context switches won't cause re-entrancy anymore because InputContextContainer defers context switches
|
||||
DebugTools.Assert(!_currentlyFindingViewport, "Re-entrant key events??");
|
||||
DebugTools.Assert(!isRepeat || binding.CanRepeat);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -399,7 +400,7 @@ namespace Robust.Client.Input
|
||||
binding.State = state;
|
||||
|
||||
var eventArgs = new BoundKeyEventArgs(binding.Function, binding.State,
|
||||
MouseScreenPosition, binding.CanFocus);
|
||||
MouseScreenPosition, binding.CanFocus, isRepeat);
|
||||
|
||||
// UI returns true here into blockPass if it wants to prevent us from giving input events
|
||||
// to the viewport, but doesn't want it hard-handled so we keep processing possible key actions.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface
|
||||
return Task.FromResult<Stream?>(null);
|
||||
}
|
||||
|
||||
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null)
|
||||
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
|
||||
{
|
||||
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Robust.Client.UserInterface
|
||||
return await OpenFileNfd(filters);
|
||||
}
|
||||
|
||||
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters)
|
||||
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
|
||||
{
|
||||
var name = await GetSaveFileName(filters);
|
||||
if (name == null)
|
||||
@@ -61,7 +61,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
try
|
||||
{
|
||||
return (File.Open(name, FileMode.Open), true);
|
||||
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Robust.Client.UserInterface
|
||||
/// The file stream the user chose to save to, and whether the file already existed.
|
||||
/// Null if the user cancelled the action.
|
||||
/// </returns>
|
||||
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null);
|
||||
/// <param name="truncate">Should we truncate an existing file to 0-size then write or append.</param>
|
||||
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,10 @@ internal partial class UserInterfaceManager
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
// Attempt to ensure that keybind-up events only get raised after a single keybind-down.
|
||||
DebugTools.Assert(!_focusedControls.ContainsKey(args.Function));
|
||||
// Attempt to ensure that keybind-up events get raised after a keybind-down.
|
||||
DebugTools.Assert(!_focusedControls.TryGetValue(args.Function, out var existing)
|
||||
|| !existing.VisibleInTree
|
||||
|| args.IsRepeat && existing == control);
|
||||
_focusedControls[args.Function] = control;
|
||||
|
||||
OnKeyBindDown?.Invoke(control);
|
||||
@@ -124,7 +126,7 @@ internal partial class UserInterfaceManager
|
||||
public void KeyBindUp(BoundKeyEventArgs args)
|
||||
{
|
||||
// Only raise keybind-up for the control on which we previously raised keybind-down
|
||||
if (!_focusedControls.Remove(args.Function, out var control) || control.Disposed)
|
||||
if (!_focusedControls.Remove(args.Function, out var control) || !control.VisibleInTree)
|
||||
return;
|
||||
|
||||
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
|
||||
|
||||
@@ -29,6 +29,7 @@ public static class Diagnostics
|
||||
public const string IdComponentPauseNoParentAttribute = "RA0023";
|
||||
public const string IdComponentPauseWrongTypeAttribute = "RA0024";
|
||||
public const string IdDependencyFieldAssigned = "RA0025";
|
||||
public const string IdUncachedRegex = "RA0026";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -51,10 +51,15 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
|
||||
public (GameTick ToTick, List<PvsIndex> PreviouslySent)? LastSent;
|
||||
|
||||
/// <summary>
|
||||
/// Visible chunks, sorted by proximity to the clients's viewers;
|
||||
/// Visible chunks, sorted by proximity to the client's viewers.
|
||||
/// </summary>
|
||||
public readonly List<(PvsChunk Chunk, float ChebyshevDistance)> Chunks = new();
|
||||
|
||||
/// <summary>
|
||||
/// Unsorted set of visible chunks. Used to construct the <see cref="Chunks"/> list.
|
||||
/// </summary>
|
||||
public readonly HashSet<PvsChunk> ChunkSet = new();
|
||||
|
||||
/// <summary>
|
||||
/// Squared distance ta all of the visible chunks.
|
||||
/// </summary>
|
||||
@@ -117,6 +122,7 @@ internal sealed class PvsSession(ICommonSession session, ResizableMemoryRegion<P
|
||||
{
|
||||
PlayerStates.Clear();
|
||||
Chunks.Clear();
|
||||
ChunkSet.Clear();
|
||||
States.Clear();
|
||||
State = null;
|
||||
}
|
||||
|
||||
@@ -90,15 +90,13 @@ internal sealed partial class PvsSystem
|
||||
foreach (var session in _sessions)
|
||||
{
|
||||
session.Chunks.Clear();
|
||||
session.ChunkSet.Clear();
|
||||
GetSessionViewers(session);
|
||||
|
||||
foreach (var eye in session.Viewers)
|
||||
{
|
||||
GetVisibleChunks(eye, session.Chunks);
|
||||
GetVisibleChunks(eye, session.ChunkSet);
|
||||
}
|
||||
|
||||
// The list of visible chunks should be unique.
|
||||
DebugTools.Assert(session.Chunks.Select(x => x.Chunk).ToHashSet().Count == session.Chunks.Count);
|
||||
}
|
||||
DebugTools.Assert(_dirtyChunks.ToHashSet().Count == _dirtyChunks.Count);
|
||||
DebugTools.Assert(_cleanChunks.ToHashSet().Count == _cleanChunks.Count);
|
||||
@@ -108,7 +106,7 @@ internal sealed partial class PvsSystem
|
||||
/// Get the chunks visible to a single entity and add them to a player's set of visible chunks.
|
||||
/// </summary>
|
||||
private void GetVisibleChunks(Entity<TransformComponent, EyeComponent?> eye,
|
||||
List<(PvsChunk Chunk, float ChebyshevDistance)> playerChunks)
|
||||
HashSet<PvsChunk> chunks)
|
||||
{
|
||||
var (viewPos, range, mapUid) = CalcViewBounds(eye);
|
||||
if (mapUid is not {} map)
|
||||
@@ -121,7 +119,7 @@ internal sealed partial class PvsSystem
|
||||
if (!_chunks.TryGetValue(loc, out var chunk))
|
||||
continue;
|
||||
|
||||
playerChunks.Add((chunk, default));
|
||||
chunks.Add(chunk);
|
||||
if (chunk.UpdateQueued)
|
||||
continue;
|
||||
|
||||
@@ -147,7 +145,7 @@ internal sealed partial class PvsSystem
|
||||
if (!_chunks.TryGetValue(loc, out var chunk))
|
||||
continue;
|
||||
|
||||
playerChunks.Add((chunk, default));
|
||||
chunks.Add(chunk);
|
||||
if (chunk.UpdateQueued)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -137,15 +137,19 @@ internal sealed partial class PvsSystem
|
||||
if (!CullingEnabled || session.DisableCulling)
|
||||
return;
|
||||
|
||||
var chunkSet = session.ChunkSet;
|
||||
var chunks = session.Chunks;
|
||||
var distances = session.ChunkDistanceSq;
|
||||
|
||||
DebugTools.AssertEqual(chunks.Count, 0);
|
||||
|
||||
distances.Clear();
|
||||
distances.EnsureCapacity(chunks.Count);
|
||||
distances.EnsureCapacity(chunkSet.Count);
|
||||
chunks.EnsureCapacity(chunkSet.Count);
|
||||
|
||||
// Assemble list of chunks and their distances to the nearest eye.
|
||||
foreach (ref var tuple in CollectionsMarshal.AsSpan(chunks))
|
||||
foreach(var chunk in chunkSet)
|
||||
{
|
||||
var chunk = tuple.Chunk;
|
||||
var dist = float.MaxValue;
|
||||
var chebDist = float.MaxValue;
|
||||
|
||||
@@ -165,7 +169,7 @@ internal sealed partial class PvsSystem
|
||||
}
|
||||
|
||||
distances.Add(dist);
|
||||
tuple.ChebyshevDistance = chebDist;
|
||||
chunks.Add((chunk, chebDist));
|
||||
}
|
||||
|
||||
// Sort chunks based on distances
|
||||
|
||||
@@ -68,7 +68,7 @@ namespace Robust.Server.ServerStatus
|
||||
if (auth != _watchdogToken)
|
||||
{
|
||||
// Holy shit nobody read these logs please.
|
||||
_sawmill.Info(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
|
||||
_sawmill.Verbose(@"Failed auth: ""{0}"" vs ""{1}""", auth, _watchdogToken);
|
||||
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -368,6 +368,21 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> NetHappyEyeballsDelay =
|
||||
CVarDef.Create("net.happy_eyeballs_delay", 0.025f, CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the networking library will log warning messages.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Disabling this should make the networking layer more resilient against some DDoS attacks.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<bool> NetLidgrenLogWarning =
|
||||
CVarDef.Create("net.lidgren_log_warning", true);
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether the networking library will log error messages.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> NetLidgrenLogError =
|
||||
CVarDef.Create("net.lidgren_log_error", true);
|
||||
|
||||
/**
|
||||
* SUS
|
||||
*/
|
||||
|
||||
@@ -294,6 +294,9 @@ public struct ValueList<T> : IEnumerable<T>
|
||||
if (Capacity < capacity)
|
||||
Grow(capacity);
|
||||
|
||||
if (capacity == 0)
|
||||
return capacity;
|
||||
|
||||
return _items!.Length;
|
||||
}
|
||||
|
||||
|
||||
@@ -289,6 +289,12 @@ namespace Robust.Shared.GameObjects
|
||||
return GetRegistration(componentType).Name;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public string GetComponentName<T>() where T : IComponent, new()
|
||||
{
|
||||
return GetRegistration<T>().Name;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public string GetComponentName(ushort netID)
|
||||
{
|
||||
@@ -324,7 +330,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public ComponentRegistration GetRegistration<T>() where T : IComponent, new()
|
||||
{
|
||||
return GetRegistration(typeof(T));
|
||||
return GetRegistration(CompIdx.Index<T>());
|
||||
}
|
||||
|
||||
public ComponentRegistration GetRegistration(IComponent component)
|
||||
|
||||
@@ -2,45 +2,35 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects.Components.Localization
|
||||
namespace Robust.Shared.GameObjects.Components.Localization;
|
||||
|
||||
/// <summary>
|
||||
/// Overrides grammar attributes specified in prototypes or localization files.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
// [Access(typeof(GrammarSystem))] TODO access
|
||||
public sealed partial class GrammarComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Overrides grammar attributes specified in prototypes or localization files.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[NetworkedComponent()]
|
||||
public sealed partial class GrammarComponent : Component
|
||||
[DataField, AutoNetworkedField]
|
||||
public Dictionary<string, string> Attributes = new();
|
||||
|
||||
[ViewVariables]
|
||||
public Gender? Gender
|
||||
{
|
||||
[DataField("attributes")]
|
||||
public Dictionary<string, string> Attributes { get; private set; } = new();
|
||||
get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse<Gender>(g, true) : null;
|
||||
[Obsolete("Use GrammarSystem.SetGender instead")]
|
||||
set => IoCManager.Resolve<IEntityManager>().System<GrammarSystem>().SetGender((Owner, this), value);
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public Gender? Gender
|
||||
{
|
||||
get => Attributes.TryGetValue("gender", out var g) ? Enum.Parse<Gender>(g, true) : null;
|
||||
set
|
||||
{
|
||||
if (value.HasValue)
|
||||
Attributes["gender"] = value.Value.ToString();
|
||||
else
|
||||
Attributes.Remove("gender");
|
||||
}
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
public bool? ProperNoun
|
||||
{
|
||||
get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null;
|
||||
set
|
||||
{
|
||||
if (value.HasValue)
|
||||
Attributes["proper"] = value.Value.ToString();
|
||||
else
|
||||
Attributes.Remove("proper");
|
||||
}
|
||||
}
|
||||
[ViewVariables]
|
||||
public bool? ProperNoun
|
||||
{
|
||||
get => Attributes.TryGetValue("proper", out var g) ? bool.Parse(g) : null;
|
||||
[Obsolete("Use GrammarSystem.SetProperNoun instead")]
|
||||
set => IoCManager.Resolve<IEntityManager>().System<GrammarSystem>().SetProperNoun((Owner, this), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
public sealed class GrammarSystem : EntitySystem
|
||||
{
|
||||
public void Clear(Entity<GrammarComponent> grammar)
|
||||
{
|
||||
grammar.Comp.Attributes.Clear();
|
||||
Dirty(grammar);
|
||||
}
|
||||
|
||||
public bool TryGet(Entity<GrammarComponent> grammar, string key, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
return grammar.Comp.Attributes.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public void Set(Entity<GrammarComponent> grammar, string key, string? value)
|
||||
{
|
||||
if (value == null)
|
||||
grammar.Comp.Attributes.Remove(key);
|
||||
else
|
||||
grammar.Comp.Attributes[key] = value;
|
||||
|
||||
Dirty(grammar);
|
||||
}
|
||||
|
||||
public void SetGender(Entity<GrammarComponent> grammar, Gender? gender)
|
||||
{
|
||||
Set(grammar, "gender", gender?.ToString());
|
||||
}
|
||||
|
||||
public void SetProperNoun(Entity<GrammarComponent> grammar, bool? proper)
|
||||
{
|
||||
Set(grammar, "proper", proper?.ToString());
|
||||
}
|
||||
}
|
||||
@@ -1586,6 +1586,13 @@ namespace Robust.Shared.GameObjects
|
||||
return default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public TComp1 Comp(EntityUid uid)
|
||||
{
|
||||
return GetComponent(uid);
|
||||
}
|
||||
|
||||
#region Internal
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -281,6 +281,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#region Entity Management
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
|
||||
{
|
||||
return CreateEntity(prototypeName, out _, overrides);
|
||||
|
||||
@@ -160,6 +160,9 @@ namespace Robust.Shared.GameObjects
|
||||
[Pure]
|
||||
string GetComponentName(Type componentType);
|
||||
|
||||
[Pure]
|
||||
string GetComponentName<T>() where T : IComponent, new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of a component, throwing an exception if it does not exist.
|
||||
/// </summary>
|
||||
|
||||
@@ -71,12 +71,43 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public event Action? AfterEntityFlush;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <param name="euid">Does nothing. Used to be the forced EntityUid of the new entity.</param>
|
||||
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
|
||||
[Obsolete($"Use one of the other {nameof(CreateEntityUninitialized)} overloads. euid no longer does anything.")]
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity and sets its position to the EntityCoordinates provided.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <param name="coordinates">Coordinates to set position and parent of the newly spawned entity to.</param>
|
||||
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity and puts it on the grid or map at the MapCoordinates provided.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName">Name of the <see cref="EntityPrototype"/> to spawn.</param>
|
||||
/// <param name="coordinates">Coordinates to place the newly spawned entity.</param>
|
||||
/// <param name="overrides">Overrides to add or remove components that differ from the prototype.</param>
|
||||
/// <param name="rotation">Map rotation to set the newly spawned entity to.</param>
|
||||
/// <returns>A new uninitialized entity.</returns>
|
||||
/// <remarks>If there is a grid at the <paramref name="coordinates"/>, the entity will be parented to the grid.
|
||||
/// Otherwise, it will be parented to the map.</remarks>
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
|
||||
|
||||
void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null);
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -20,6 +21,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
[Dependency] private readonly IDynamicTypeFactory _factory = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transforms = default!;
|
||||
@@ -29,6 +31,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
private EntityQuery<UserInterfaceComponent> _uiQuery;
|
||||
private EntityQuery<UserInterfaceUserComponent> _userQuery;
|
||||
|
||||
private ActorRangeCheckJob _rangeJob;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -38,6 +42,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
_uiQuery = GetEntityQuery<UserInterfaceComponent>();
|
||||
_userQuery = GetEntityQuery<UserInterfaceUserComponent>();
|
||||
|
||||
_rangeJob = new()
|
||||
{
|
||||
System = this,
|
||||
XformQuery = _xformQuery,
|
||||
};
|
||||
|
||||
SubscribeAllEvent<BoundUIWrapMessage>((msg, args) =>
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity is not { } player)
|
||||
@@ -48,6 +58,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<UserInterfaceComponent, OpenBoundInterfaceMessage>(OnUserInterfaceOpen);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, CloseBoundInterfaceMessage>(OnUserInterfaceClosed);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentStartup>(OnUserInterfaceStartup);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentGetState>(OnUserInterfaceGetState);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentHandleState>(OnUserInterfaceHandleState);
|
||||
@@ -55,28 +66,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentShutdown>(OnActorShutdown);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetStateAttemptEvent>(OnGetStateAttempt);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetState>(OnActorGetState);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentHandleState>(OnActorHandleState);
|
||||
|
||||
_player.PlayerStatusChanged += OnStatusChange;
|
||||
}
|
||||
|
||||
private void OnStatusChange(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
var attachedEnt = e.Session.AttachedEntity;
|
||||
|
||||
if (attachedEnt == null)
|
||||
return;
|
||||
|
||||
// Content can't handle it yet sadly :(
|
||||
CloseUserUis(attachedEnt.Value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_player.PlayerStatusChanged -= OnStatusChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -135,6 +128,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
#region User
|
||||
|
||||
private void OnActorShutdown(Entity<UserInterfaceUserComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
CloseUserUis((ent.Owner, ent.Comp));
|
||||
}
|
||||
|
||||
private void OnGetStateAttempt(Entity<UserInterfaceUserComponent> ent, ref ComponentGetStateAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Player?.AttachedEntity != ent.Owner)
|
||||
@@ -207,11 +205,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!uiComp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
|
||||
if (!uiComp.ClientOpenInterfaces.Remove(key, out var cBui))
|
||||
continue;
|
||||
|
||||
cBui.Dispose();
|
||||
uiComp.ClientOpenInterfaces.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,10 +230,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
Dirty(ent);
|
||||
|
||||
// If the actor is also deleting then don't worry about updating what they have open.
|
||||
if (!TerminatingOrDeleted(actor))
|
||||
if (!TerminatingOrDeleted(actor) && _userQuery.TryComp(actor, out var actorComp))
|
||||
{
|
||||
var actorComp = EnsureComp<UserInterfaceUserComponent>(actor);
|
||||
|
||||
if (actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys))
|
||||
{
|
||||
keys.Remove(args.UiKey);
|
||||
@@ -282,6 +277,20 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
EnsureClientBui(ent, args.UiKey, ent.Comp.Interfaces[args.UiKey]);
|
||||
}
|
||||
|
||||
private void OnUserInterfaceStartup(Entity<UserInterfaceComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
// PlayerAttachedEvent will catch some of these.
|
||||
foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces)
|
||||
{
|
||||
bui.Open();
|
||||
|
||||
if (ent.Comp.States.TryGetValue(key, out var state))
|
||||
{
|
||||
bui.UpdateState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUserInterfaceShutdown(EntityUid uid, UserInterfaceComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var bui in component.ClientOpenInterfaces.Values)
|
||||
@@ -394,11 +403,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
// If UI not open then open it
|
||||
var attachedEnt = _player.LocalEntity;
|
||||
|
||||
// If we get the first state for an ent coming in then don't open BUIs yet, just defer it until later.
|
||||
var open = ent.Comp.LifeStage > ComponentLifeStage.Added;
|
||||
|
||||
if (attachedEnt != null)
|
||||
{
|
||||
foreach (var (key, value) in ent.Comp.Interfaces)
|
||||
{
|
||||
EnsureClientBui(ent, key, value);
|
||||
EnsureClientBui(ent, key, value, open);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -406,7 +418,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Opens a client's BUI if not already open and applies the state to it.
|
||||
/// </summary>
|
||||
private void EnsureClientBui(Entity<UserInterfaceComponent> entity, Enum key, InterfaceData data)
|
||||
private void EnsureClientBui(Entity<UserInterfaceComponent> entity, Enum key, InterfaceData data, bool open = true)
|
||||
{
|
||||
// If it's out BUI open it up and apply the state, otherwise do nothing.
|
||||
var player = _player.LocalEntity;
|
||||
@@ -429,6 +441,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
var boundUserInterface = (BoundUserInterface) _factory.CreateInstance(type, [entity.Owner, key]);
|
||||
|
||||
entity.Comp.ClientOpenInterfaces[key] = boundUserInterface;
|
||||
|
||||
// This is just so we don't open while applying UI states.
|
||||
if (!open)
|
||||
return;
|
||||
|
||||
boundUserInterface.Open();
|
||||
|
||||
if (entity.Comp.States.TryGetValue(key, out var buiState))
|
||||
@@ -879,6 +896,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = AllEntityQuery<ActiveUserInterfaceComponent, UserInterfaceComponent>();
|
||||
// Run these in parallel because it's expensive.
|
||||
_rangeJob.ActorRanges.Clear();
|
||||
|
||||
// Handles closing the BUI if actors move out of range of them.
|
||||
while (query.MoveNext(out var uid, out _, out var uiComp))
|
||||
@@ -892,24 +911,26 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (data.InteractionRange <= 0f || actors.Count == 0)
|
||||
continue;
|
||||
|
||||
// Okay so somehow UISystem is high up on the server profile
|
||||
// If that's actually still a problem turn this into an IParallelRobustJob and slam all the UIs in parallel.
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
var coordinates = xform.Coordinates;
|
||||
var mapId = xform.MapID;
|
||||
|
||||
for (var i = actors.Count - 1; i >= 0; i--)
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
var actor = actors[i];
|
||||
|
||||
if (CheckRange(uid, key, data, actor, coordinates, mapId))
|
||||
continue;
|
||||
|
||||
// Using the non-predicted one here seems fine?
|
||||
CloseUi((uid, uiComp), key, actor);
|
||||
_rangeJob.ActorRanges.Add((uid, key, data, actor, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_parallel.ProcessNow(_rangeJob, _rangeJob.ActorRanges.Count);
|
||||
|
||||
foreach (var data in _rangeJob.ActorRanges)
|
||||
{
|
||||
var uid = data.Ui;
|
||||
var actor = data.Actor;
|
||||
var key = data.Key;
|
||||
|
||||
if (data.Result || Deleted(uid) || Deleted(actor) || !_uiQuery.TryComp(uid, out var uiComp))
|
||||
continue;
|
||||
|
||||
CloseUi((uid, uiComp), key, actor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -946,6 +967,32 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
return uiCoordinates.InRange(EntityManager, _transforms, actorXform.Coordinates, data.InteractionRange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for running UI raycast checks in parallel.
|
||||
/// </summary>
|
||||
private record struct ActorRangeCheckJob() : IParallelRobustJob
|
||||
{
|
||||
public EntityQuery<TransformComponent> XformQuery;
|
||||
public SharedUserInterfaceSystem System;
|
||||
public readonly List<(EntityUid Ui, Enum Key, InterfaceData Data, EntityUid Actor, bool Result)> ActorRanges = new();
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var data = ActorRanges[index];
|
||||
|
||||
if (!XformQuery.TryComp(data.Ui, out var uiXform))
|
||||
{
|
||||
data.Result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Result = System.CheckRange(data.Ui, data.Key, data.Data, data.Actor, uiXform.Coordinates, uiXform.MapID);
|
||||
}
|
||||
|
||||
ActorRanges[index] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -33,6 +33,11 @@ namespace Robust.Shared.Input
|
||||
|
||||
public bool Handled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this a repeated keypress (i.e., are they holding down the key)?
|
||||
/// </summary>
|
||||
public readonly bool IsRepeat;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="BoundKeyEventArgs"/>.
|
||||
/// </summary>
|
||||
@@ -47,6 +52,23 @@ namespace Robust.Shared.Input
|
||||
CanFocus = canFocus;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new instance of <see cref="BoundKeyEventArgs"/>.
|
||||
/// </summary>
|
||||
/// <param name="function">Bound key that that is changing.</param>
|
||||
/// <param name="state">New state of the function.</param>
|
||||
/// <param name="pointerLocation">Current Pointer location in screen coordinates.</param>
|
||||
/// <param name="isRepeat"></param>
|
||||
public BoundKeyEventArgs(
|
||||
BoundKeyFunction function,
|
||||
BoundKeyState state,
|
||||
ScreenCoordinates pointerLocation,
|
||||
bool canFocus,
|
||||
bool isRepeat = false) : this(function, state, pointerLocation, canFocus)
|
||||
{
|
||||
IsRepeat = isRepeat;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark this event as handled.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,6 +17,8 @@ namespace Robust.Shared.Localization
|
||||
{
|
||||
internal sealed partial class LocalizationManager
|
||||
{
|
||||
private static readonly Regex RegexWordMatch = new Regex(@"\w+");
|
||||
|
||||
private void AddBuiltInFunctions(FluentBundle bundle)
|
||||
{
|
||||
// Grammatical gender / pronouns
|
||||
@@ -108,7 +110,7 @@ namespace Robust.Shared.Localization
|
||||
var a = new LocValueString("a");
|
||||
var an = new LocValueString("an");
|
||||
|
||||
var m = Regex.Match(input, @"\w+");
|
||||
var m = RegexWordMatch.Match(input);
|
||||
if (m.Success)
|
||||
{
|
||||
word = m.Groups[0].Value;
|
||||
|
||||
@@ -249,6 +249,9 @@ namespace Robust.Shared.Network
|
||||
|
||||
IsServer = isServer;
|
||||
|
||||
_config.OnValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged);
|
||||
_config.OnValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged);
|
||||
|
||||
_config.OnValueChanged(CVars.NetVerbose, NetVerboseChanged);
|
||||
if (isServer)
|
||||
{
|
||||
@@ -272,6 +275,22 @@ namespace Robust.Shared.Network
|
||||
}
|
||||
}
|
||||
|
||||
private void LidgrenLogWarningChanged(bool newValue)
|
||||
{
|
||||
foreach (var netPeer in _netPeers)
|
||||
{
|
||||
netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.WarningMessage, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void LidgrenLogErrorChanged(bool newValue)
|
||||
{
|
||||
foreach (var netPeer in _netPeers)
|
||||
{
|
||||
netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.ErrorMessage, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAuthModeChanged(int mode)
|
||||
{
|
||||
Auth = (AuthMode)mode;
|
||||
@@ -422,6 +441,8 @@ namespace Robust.Shared.Network
|
||||
_config.UnsubValueChanged(CVars.NetFakeLagMin, _fakeLagMinChanged);
|
||||
_config.UnsubValueChanged(CVars.NetFakeLagRand, _fakeLagRandomChanged);
|
||||
_config.UnsubValueChanged(CVars.NetFakeDuplicates, FakeDuplicatesChanged);
|
||||
_config.UnsubValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged);
|
||||
_config.UnsubValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged);
|
||||
|
||||
_serializer.ClientHandshakeComplete -= OnSerializerOnClientHandshakeComplete;
|
||||
|
||||
@@ -576,6 +597,14 @@ namespace Robust.Shared.Network
|
||||
// ping the client once per second.
|
||||
netConfig.PingInterval = 1f;
|
||||
|
||||
netConfig.SetMessageTypeEnabled(
|
||||
NetIncomingMessageType.WarningMessage,
|
||||
_config.GetCVar(CVars.NetLidgrenLogWarning));
|
||||
|
||||
netConfig.SetMessageTypeEnabled(
|
||||
NetIncomingMessageType.ErrorMessage,
|
||||
_config.GetCVar(CVars.NetLidgrenLogError));
|
||||
|
||||
var poolSize = _config.GetCVar(CVars.NetPoolSize);
|
||||
|
||||
if (poolSize <= 0)
|
||||
|
||||
@@ -48,6 +48,9 @@ public abstract partial class SharedJointSystem
|
||||
|
||||
private void OnRelayShutdown(EntityUid uid, JointRelayTargetComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
foreach (var relay in component.Relayed)
|
||||
{
|
||||
if (TerminatingOrDeleted(relay) || !_jointsQuery.TryGetComponent(relay, out var joint))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
@@ -42,3 +45,59 @@ public readonly record struct EntProtoId(string Id) : IEquatable<string>, ICompa
|
||||
|
||||
public override string ToString() => Id ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntProtoId"/>
|
||||
[Serializable]
|
||||
public readonly record struct EntProtoId<T>(string Id) : IEquatable<string>, IComparable<EntProtoId> where T : IComponent, new()
|
||||
{
|
||||
public static implicit operator string(EntProtoId<T> protoId)
|
||||
{
|
||||
return protoId.Id;
|
||||
}
|
||||
|
||||
public static implicit operator EntProtoId(EntProtoId<T> protoId)
|
||||
{
|
||||
return new EntProtoId(protoId.Id);
|
||||
}
|
||||
|
||||
public static implicit operator EntProtoId<T>(string id)
|
||||
{
|
||||
return new EntProtoId<T>(id);
|
||||
}
|
||||
|
||||
public static implicit operator EntProtoId<T>?(string? id)
|
||||
{
|
||||
return id == null ? default(EntProtoId<T>?) : new EntProtoId<T>(id);
|
||||
}
|
||||
|
||||
public bool Equals(string? other)
|
||||
{
|
||||
return Id == other;
|
||||
}
|
||||
|
||||
public int CompareTo(EntProtoId other)
|
||||
{
|
||||
return string.Compare(Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override string ToString() => Id ?? string.Empty;
|
||||
|
||||
public T Get(IPrototypeManager? prototypes, IComponentFactory compFactory)
|
||||
{
|
||||
prototypes ??= IoCManager.Resolve<IPrototypeManager>();
|
||||
var proto = prototypes.Index(this);
|
||||
if (!proto.TryGetComponent(out T? comp, compFactory))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(EntityPrototype)} {proto.ID} has no {nameof(T)}");
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
public bool TryGet([NotNullWhen(true)] out T? comp, IPrototypeManager? prototypes, IComponentFactory compFactory)
|
||||
{
|
||||
prototypes ??= IoCManager.Resolve<IPrototypeManager>();
|
||||
var proto = prototypes.Index(this);
|
||||
return proto.TryGetComponent(out comp, compFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
@@ -168,28 +169,37 @@ namespace Robust.Shared.Prototypes
|
||||
_loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory? factory = null) where T : IComponent
|
||||
[Obsolete("Pass in IComponentFactory")]
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component)
|
||||
where T : IComponent
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
factory = IoCManager.Resolve<IComponentFactory>();
|
||||
}
|
||||
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T));
|
||||
return TryGetComponent(compName, out component);
|
||||
}
|
||||
|
||||
var compName = factory.GetComponentName(typeof(T));
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory factory) where T : IComponent, new()
|
||||
{
|
||||
var compName = factory.GetComponentName<T>();
|
||||
return TryGetComponent(compName, out component);
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent
|
||||
{
|
||||
DebugTools.AssertEqual(IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T)), name);
|
||||
|
||||
if (!Components.TryGetValue(name, out var componentUnCast))
|
||||
{
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// There are no duplicate component names
|
||||
// TODO Sanity check with names being in an attribute of the type instead
|
||||
component = (T) componentUnCast.Component;
|
||||
if (componentUnCast.Component is not T cast)
|
||||
{
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
component = cast;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
@@ -440,5 +441,7 @@ namespace Robust.Shared.Serialization.Manager
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +264,26 @@ namespace Robust.Shared.Serialization.Manager
|
||||
return dataDefinition != null;
|
||||
}
|
||||
|
||||
public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType)
|
||||
{
|
||||
if (!TryGetDefinition(type, out var definition))
|
||||
{
|
||||
variableType = null;
|
||||
return false;
|
||||
}
|
||||
var foundFieldDef = definition.BaseFieldDefinitions.FirstOrDefault(fieldDef => fieldDef?.Attribute is DataFieldAttribute attr && attr.Tag==variableName, null);
|
||||
if(foundFieldDef != null)
|
||||
{
|
||||
variableType = foundFieldDef.BackingField.FieldType;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
variableType = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Type ResolveConcreteType(Type baseType, string typeName)
|
||||
{
|
||||
var type = ReflectionManager.YamlTypeTagLookup(baseType, typeName);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
@@ -40,3 +43,49 @@ public sealed class EntProtoIdSerializer : ITypeSerializer<EntProtoId, ValueData
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer used automatically for <see cref="EntProtoId"/> types.
|
||||
/// </summary>
|
||||
[TypeSerializer]
|
||||
public sealed class EntProtoIdSerializer<T> : ITypeSerializer<EntProtoId<T>, ValueDataNode>, ITypeCopyCreator<EntProtoId<T>> where T : IComponent, new()
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
var prototypes = dependencies.Resolve<IPrototypeManager>();
|
||||
if (!prototypes.TryGetMapping(typeof(EntityPrototype), node.Value, out var mapping))
|
||||
return new ErrorNode(node, $"No {nameof(EntityPrototype)} found with id {node.Value} that has a {typeof(T).Name}");
|
||||
|
||||
if (!mapping.TryGet("components", out SequenceDataNode? components))
|
||||
return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}.");
|
||||
|
||||
var compFactory = dependencies.Resolve<IComponentFactory>();
|
||||
var registration = compFactory.GetRegistration<T>();
|
||||
foreach (var componentNode in components)
|
||||
{
|
||||
if (componentNode is MappingDataNode component &&
|
||||
component.TryGet("type", out ValueDataNode? compName) &&
|
||||
compName.Value == registration.Name)
|
||||
{
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}.");
|
||||
}
|
||||
|
||||
public EntProtoId<T> Read(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, InstantiationDelegate<EntProtoId<T>>? instanceProvider = null)
|
||||
{
|
||||
return new EntProtoId<T>(node.Value);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serialization, EntProtoId<T> value, IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null)
|
||||
{
|
||||
return new ValueDataNode(value.Id);
|
||||
}
|
||||
|
||||
public EntProtoId<T> CreateCopy(ISerializationManager serializationManager, EntProtoId<T> source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user