mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
15 Commits
fix/bui-st
...
v222.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
702dfef5fc | ||
|
|
a0c1ad246f | ||
|
|
1153888bd1 | ||
|
|
ccbb6ddec7 | ||
|
|
970da5f717 | ||
|
|
4d528dd577 | ||
|
|
c83720b163 | ||
|
|
bd87a805d4 | ||
|
|
fff42fb2b4 | ||
|
|
4500669f65 | ||
|
|
7d19ea9338 | ||
|
|
2dc610907d | ||
|
|
beb1c4b1fb | ||
|
|
7e331eaa75 | ||
|
|
caf9e45ad9 |
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,53 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 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()));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -48,6 +48,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 +56,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 +118,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)
|
||||
@@ -233,10 +221,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 +268,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 +394,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 +409,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 +432,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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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?.BackingField.Name==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