mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
42 Commits
fix/bui-st
...
v222.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec794ce4e4 | ||
|
|
6b13475842 | ||
|
|
b48ee22800 | ||
|
|
0b95a4edeb | ||
|
|
ed359481b4 | ||
|
|
1189613908 | ||
|
|
30907d8415 | ||
|
|
7f2da4d4f3 | ||
|
|
e30e963623 | ||
|
|
b056caeed7 | ||
|
|
fbc8086335 | ||
|
|
799702b814 | ||
|
|
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 }}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
We actually set ManagePackageVersionsCentrally manually in another import file.
|
||||
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
|
||||
https://github.com/NuGet/NuGet.Client/pull/5572
|
||||
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
|
||||
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
|
||||
-->
|
||||
<ManagePackageVersionsCentrally />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
|
||||
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,94 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 222.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`.
|
||||
|
||||
|
||||
## 222.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel.
|
||||
* Ordered event subscriptions now take child types into account, so ordering based on a shared type will work.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Cross-map BUI range checks now work.
|
||||
* Paused entities update on prototype reload.
|
||||
|
||||
### Other
|
||||
|
||||
* Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves.
|
||||
* Physics component has delta states to reduce network usage.
|
||||
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -130,7 +130,7 @@ public sealed class TileSpawningUIController : UIController
|
||||
|
||||
_window.TileList.Clear();
|
||||
|
||||
IEnumerable<ITileDefinition> tileDefs = _tiles;
|
||||
IEnumerable<ITileDefinition> tileDefs = _tiles.Where(def => !def.EditorHidden);
|
||||
|
||||
if (!string.IsNullOrEmpty(searchStr))
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -442,7 +442,14 @@ Types:
|
||||
AddressFamily: { }
|
||||
System.Numerics:
|
||||
BitOperations: { All: True }
|
||||
Complex: { All: True }
|
||||
Matrix3x2: { All: True }
|
||||
Matrix4x4: { All: True }
|
||||
Plane: { All: True }
|
||||
Quaternion: { All: True }
|
||||
Vector2: { All: True }
|
||||
Vector3: { All: True }
|
||||
Vector4: { All: True }
|
||||
System.Reflection:
|
||||
Assembly:
|
||||
Methods:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (eventHandler == null)
|
||||
throw new ArgumentNullException(nameof(eventHandler));
|
||||
|
||||
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
|
||||
var order = CreateOrderingData(orderType, before, after);
|
||||
|
||||
SubscribeEventCommon<T>(source, subscriber,
|
||||
(ref Unit ev) => eventHandler(Unsafe.As<Unit, T>(ref ev)), eventHandler, order, false);
|
||||
@@ -187,7 +187,7 @@ namespace Robust.Shared.GameObjects
|
||||
EntityEventRefHandler<T> eventHandler,
|
||||
Type orderType, Type[]? before = null, Type[]? after = null) where T : notnull
|
||||
{
|
||||
var order = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
|
||||
var order = CreateOrderingData(orderType, before, after);
|
||||
|
||||
SubscribeEventCommon<T>(source, subscriber, (ref Unit ev) =>
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
@@ -13,6 +14,7 @@ internal sealed partial class EntityEventBus : IEventBus
|
||||
{
|
||||
private IEntityManager _entMan;
|
||||
private IComponentFactory _comFac;
|
||||
private IReflectionManager _reflection;
|
||||
|
||||
// Data on individual events. Used to check ordering info and fire broadcast events.
|
||||
private FrozenDictionary<Type, EventData> _eventData = FrozenDictionary<Type, EventData>.Empty;
|
||||
@@ -29,26 +31,36 @@ internal sealed partial class EntityEventBus : IEventBus
|
||||
// See EventTable declaration for layout details
|
||||
internal Dictionary<EntityUid, EventTable> _entEventTables = new();
|
||||
|
||||
// CompType -> EventType -> Handler
|
||||
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptions = default!;
|
||||
/// <summary>
|
||||
/// Array of component events and their handlers. The array is indexed by a component's
|
||||
/// <see cref="CompIdx.Value"/>, while the dictionary is indexed by the event type. This does not include events
|
||||
/// with the <see cref="ComponentEventAttribute"/>
|
||||
/// </summary>
|
||||
internal FrozenDictionary<Type, DirectedRegistration>[] _eventSubs = default!;
|
||||
|
||||
// Variant of _entSubscriptions that omits any events with the ComponentEventAttribute
|
||||
internal FrozenDictionary<Type, DirectedRegistration>?[] _entSubscriptionsNoCompEv = default!;
|
||||
/// <summary>
|
||||
/// Variant of <see cref="_eventSubs"/> that also includes events with the <see cref="ComponentEventAttribute"/>
|
||||
/// </summary>
|
||||
internal FrozenDictionary<Type, DirectedRegistration>[] _compEventSubs = default!;
|
||||
|
||||
// pre-freeze _entSubscriptions data
|
||||
internal Dictionary<Type, DirectedRegistration>?[] _entSubscriptionsUnfrozen =
|
||||
Array.Empty<Dictionary<Type, DirectedRegistration>?>();
|
||||
// pre-freeze event subscription data
|
||||
internal Dictionary<Type, DirectedRegistration>?[] _eventSubsUnfrozen =
|
||||
Array.Empty<Dictionary<Type, DirectedRegistration>>();
|
||||
|
||||
// EventType -> { CompType1, ... CompType N }
|
||||
/// <summary>
|
||||
/// Inverse of <see cref="_eventSubs"/>, mapping event types to sets of components.
|
||||
/// </summary>
|
||||
private Dictionary<Type, HashSet<CompIdx>> _eventSubsInv = new();
|
||||
// Only required to sort ordered subscriptions, which only happens during initialization
|
||||
// so doesn't need to be a frozen dictionary.
|
||||
private Dictionary<Type, HashSet<CompIdx>> _entSubscriptionsInv = new();
|
||||
|
||||
// prevents shitcode, get your subscriptions figured out before you start spawning entities
|
||||
private bool _subscriptionLock;
|
||||
|
||||
public bool IgnoreUnregisteredComponents;
|
||||
|
||||
private readonly List<Type> _childrenTypesTemp = [];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ref Unit ExtractUnitRef(ref object obj, Type objType)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
@@ -117,10 +118,12 @@ namespace Robust.Shared.GameObjects
|
||||
/// Constructs a new instance of <see cref="EntityEventBus"/>.
|
||||
/// </summary>
|
||||
/// <param name="entMan">The entity manager to watch for entity/component events.</param>
|
||||
public EntityEventBus(IEntityManager entMan)
|
||||
/// <param name="reflection">The reflection manager to use when finding derived types.</param>
|
||||
public EntityEventBus(IEntityManager entMan, IReflectionManager reflection)
|
||||
{
|
||||
_entMan = entMan;
|
||||
_comFac = entMan.ComponentFactory;
|
||||
_reflection = reflection;
|
||||
|
||||
// Dynamic handling of components is only for RobustUnitTest compatibility spaghetti.
|
||||
_comFac.ComponentsAdded += ComFacOnComponentsAdded;
|
||||
@@ -248,7 +251,7 @@ namespace Robust.Shared.GameObjects
|
||||
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
|
||||
=> handler(uid, (TComp)comp, args);
|
||||
|
||||
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
|
||||
var orderData = CreateOrderingData(orderType, before, after);
|
||||
|
||||
EntSubscribe<TEvent>(
|
||||
CompIdx.Index<TComp>(),
|
||||
@@ -281,7 +284,7 @@ namespace Robust.Shared.GameObjects
|
||||
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
|
||||
=> handler(uid, (TComp)comp, ref args);
|
||||
|
||||
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
|
||||
var orderData = CreateOrderingData(orderType, before, after);
|
||||
|
||||
EntSubscribe<TEvent>(
|
||||
CompIdx.Index<TComp>(),
|
||||
@@ -300,7 +303,7 @@ namespace Robust.Shared.GameObjects
|
||||
void EventHandler(EntityUid uid, IComponent comp, ref TEvent args)
|
||||
=> handler(new Entity<TComp>(uid, (TComp) comp), ref args);
|
||||
|
||||
var orderData = new OrderingData(orderType, before ?? Array.Empty<Type>(), after ?? Array.Empty<Type>());
|
||||
var orderData = CreateOrderingData(orderType, before, after);
|
||||
|
||||
EntSubscribe<TEvent>(
|
||||
CompIdx.Index<TComp>(),
|
||||
@@ -327,7 +330,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
foreach (var reg in regs)
|
||||
{
|
||||
CompIdx.RefArray(ref _entSubscriptionsUnfrozen, reg.Idx) ??= new();
|
||||
CompIdx.RefArray(ref _eventSubsUnfrozen, reg.Idx) ??= new();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,29 +354,33 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptionLock = true;
|
||||
_eventData = _eventDataUnfrozen.ToFrozenDictionary();
|
||||
|
||||
_entSubscriptions = _entSubscriptionsUnfrozen
|
||||
.Select(x => x?.ToFrozenDictionary())
|
||||
// Find last non-null entry.
|
||||
var last = 0;
|
||||
for (var i = 0; i < _eventSubsUnfrozen.Length; i++)
|
||||
{
|
||||
var entry = _eventSubsUnfrozen[i];
|
||||
if (entry != null)
|
||||
last = i;
|
||||
}
|
||||
|
||||
// TODO PERFORMANCE
|
||||
// make this only contain events that actually use comp-events
|
||||
// Assuming it makes the frozen dictionaries more specialized and thus faster.
|
||||
// AFAIK currently only MapInit is both a comp-event and a general event.
|
||||
// It should probably be changed to just be a comp event.
|
||||
_compEventSubs = _eventSubsUnfrozen
|
||||
.Take(last+1)
|
||||
.Select(dict => dict?.ToFrozenDictionary()!)
|
||||
.ToArray();
|
||||
|
||||
_entSubscriptionsNoCompEv = _entSubscriptionsUnfrozen.Select(FreezeWithoutComponentEvent).ToArray();
|
||||
_eventSubs = _eventSubsUnfrozen
|
||||
.Take(last+1)
|
||||
.Select(dict => dict?.Where(x => !IsComponentEvent(x.Key)).ToFrozenDictionary()!)
|
||||
.ToArray();
|
||||
|
||||
CalcOrdering();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Freezes a dictionary while committing events with the <see cref="ComponentEventAttribute"/>.
|
||||
/// This avoids unnecessarily adding one-off events to the list of subscriptions.
|
||||
/// </summary>
|
||||
private FrozenDictionary<Type, DirectedRegistration>? FreezeWithoutComponentEvent(
|
||||
Dictionary<Type, DirectedRegistration>? input)
|
||||
{
|
||||
if (input == null)
|
||||
return null;
|
||||
|
||||
return input.Where(x => !IsComponentEvent(x.Key))
|
||||
.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
private bool IsComponentEvent(Type t)
|
||||
{
|
||||
var isCompEv = _eventData[t].ComponentEvent;
|
||||
@@ -395,8 +402,8 @@ namespace Robust.Shared.GameObjects
|
||||
if (_subscriptionLock)
|
||||
throw new InvalidOperationException("Subscription locked.");
|
||||
|
||||
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|
||||
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
|
||||
if (compType.Value >= _eventSubsUnfrozen.Length
|
||||
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
|
||||
{
|
||||
if (IgnoreUnregisteredComponents)
|
||||
return;
|
||||
@@ -411,10 +418,11 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
compSubs.Add(eventType, registration);
|
||||
_entSubscriptionsInv.GetOrNew(eventType).Add(compType);
|
||||
|
||||
RegisterCommon(eventType, registration.Ordering, out var data);
|
||||
data.ComponentEvent = eventType.HasCustomAttribute<ComponentEventAttribute>();
|
||||
if (!data.ComponentEvent)
|
||||
_eventSubsInv.GetOrNew(eventType).Add(compType);
|
||||
}
|
||||
|
||||
private void EntSubscribe<TEvent>(
|
||||
@@ -438,8 +446,8 @@ namespace Robust.Shared.GameObjects
|
||||
if (_subscriptionLock)
|
||||
throw new InvalidOperationException("Subscription locked.");
|
||||
|
||||
if (compType.Value >= _entSubscriptionsUnfrozen.Length
|
||||
|| _entSubscriptionsUnfrozen[compType.Value] is not { } compSubs)
|
||||
if (compType.Value >= _eventSubsUnfrozen.Length
|
||||
|| _eventSubsUnfrozen[compType.Value] is not { } compSubs)
|
||||
{
|
||||
if (IgnoreUnregisteredComponents)
|
||||
return;
|
||||
@@ -449,7 +457,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
var removed = compSubs.Remove(eventType);
|
||||
if (removed)
|
||||
_entSubscriptionsInv[eventType].Remove(compType);
|
||||
_eventSubsInv[eventType].Remove(compType);
|
||||
}
|
||||
|
||||
private void EntAddEntity(EntityUid euid)
|
||||
@@ -469,7 +477,7 @@ namespace Robust.Shared.GameObjects
|
||||
DebugTools.Assert(_subscriptionLock);
|
||||
|
||||
var eventTable = _entEventTables[euid];
|
||||
var compSubs = _entSubscriptionsNoCompEv[compType.Value]!;
|
||||
var compSubs = _eventSubs[compType.Value];
|
||||
|
||||
foreach (var evType in compSubs.Keys)
|
||||
{
|
||||
@@ -528,13 +536,17 @@ namespace Robust.Shared.GameObjects
|
||||
private void EntRemoveComponent(EntityUid euid, CompIdx compType)
|
||||
{
|
||||
var eventTable = _entEventTables[euid];
|
||||
var compSubs = _entSubscriptions[compType.Value]!;
|
||||
var compSubs = _eventSubs[compType.Value];
|
||||
|
||||
foreach (var evType in compSubs.Keys)
|
||||
{
|
||||
DebugTools.Assert(!_eventData[evType].ComponentEvent);
|
||||
ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
|
||||
if (Unsafe.IsNullRef(ref dictIdx))
|
||||
{
|
||||
DebugTools.Assert("This should not be possible. Were the events for this component never added?");
|
||||
continue;
|
||||
}
|
||||
|
||||
ref var updateNext = ref dictIdx;
|
||||
|
||||
@@ -610,9 +622,7 @@ namespace Robust.Shared.GameObjects
|
||||
ref Unit args)
|
||||
where TEvent : notnull
|
||||
{
|
||||
var compSubs = _entSubscriptions[baseType.Value]!;
|
||||
|
||||
if (compSubs.TryGetValue(typeof(TEvent), out var reg))
|
||||
if (_compEventSubs[baseType.Value].TryGetValue(typeof(TEvent), out var reg))
|
||||
reg.Handler(euid, component, ref args);
|
||||
}
|
||||
|
||||
@@ -634,7 +644,7 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _entSubscriptions, euid, _entMan);
|
||||
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _eventSubs, euid, _entMan);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -644,10 +654,10 @@ namespace Robust.Shared.GameObjects
|
||||
_eventDataUnfrozen.Clear();
|
||||
_entEventTables.Clear();
|
||||
_inverseEventSubscriptions.Clear();
|
||||
_entSubscriptions = default!;
|
||||
_entSubscriptionsNoCompEv = default!;
|
||||
_compEventSubs = default!;
|
||||
_eventSubs = default!;
|
||||
_eventData = FrozenDictionary<Type, EventData>.Empty;
|
||||
foreach (var sub in _entSubscriptionsUnfrozen)
|
||||
foreach (var sub in _eventSubsUnfrozen)
|
||||
{
|
||||
sub?.Clear();
|
||||
}
|
||||
@@ -660,18 +670,19 @@ namespace Robust.Shared.GameObjects
|
||||
// punishment for use-after-free
|
||||
_entMan = null!;
|
||||
_comFac = null!;
|
||||
_reflection = null!;
|
||||
_entEventTables = null!;
|
||||
_entSubscriptions = null!;
|
||||
_entSubscriptionsNoCompEv = null!;
|
||||
_entSubscriptionsUnfrozen = null!;
|
||||
_entSubscriptionsInv = null!;
|
||||
_compEventSubs = null!;
|
||||
_eventSubs = null!;
|
||||
_eventSubsUnfrozen = null!;
|
||||
_eventSubsInv = null!;
|
||||
}
|
||||
|
||||
private struct SubscriptionsEnumerator
|
||||
{
|
||||
private readonly Type _eventType;
|
||||
private readonly EntityUid _uid;
|
||||
private readonly FrozenDictionary<Type, DirectedRegistration>?[] _subscriptions;
|
||||
private readonly FrozenDictionary<Type, DirectedRegistration>[] _subscriptions;
|
||||
private readonly IEntityManager _entityManager;
|
||||
private readonly EventTableListEntry[] _list;
|
||||
private int _idx;
|
||||
@@ -680,7 +691,7 @@ namespace Robust.Shared.GameObjects
|
||||
Type eventType,
|
||||
int startEntry,
|
||||
EventTableListEntry[] list,
|
||||
FrozenDictionary<Type, DirectedRegistration>?[] subscriptions,
|
||||
FrozenDictionary<Type, DirectedRegistration>[] subscriptions,
|
||||
EntityUid uid,
|
||||
IEntityManager entityManager)
|
||||
{
|
||||
@@ -707,7 +718,7 @@ namespace Robust.Shared.GameObjects
|
||||
_idx = entry.Next;
|
||||
|
||||
var compType = entry.Component;
|
||||
var compSubs = _subscriptions[compType.Value]!;
|
||||
var compSubs = _subscriptions[compType.Value];
|
||||
|
||||
if (!compSubs.TryGetValue(_eventType, out registration))
|
||||
{
|
||||
|
||||
@@ -59,10 +59,10 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// Collect all subscriptions, broadcast and ordered.
|
||||
IEnumerable<OrderedRegistration> regs = sub.BroadcastRegistrations;
|
||||
if (_entSubscriptionsInv.TryGetValue(eventType, out var comps))
|
||||
if (_eventSubsInv.TryGetValue(eventType, out var comps))
|
||||
{
|
||||
regs = regs.Concat(comps
|
||||
.Select(c => _entSubscriptions[c.Value])
|
||||
.Select(c => _eventSubs[c.Value])
|
||||
.Where(c => c != null)
|
||||
.Select(c => c![eventType]));
|
||||
}
|
||||
@@ -200,5 +200,33 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private OrderingData CreateOrderingData(Type orderType, Type[]? before, Type[]? after)
|
||||
{
|
||||
AddChildrenTypes(ref before);
|
||||
AddChildrenTypes(ref after);
|
||||
return new OrderingData(orderType, before ?? [], after ?? []);
|
||||
}
|
||||
|
||||
private void AddChildrenTypes(ref Type[]? original)
|
||||
{
|
||||
if (original == null || original.Length == 0)
|
||||
return;
|
||||
|
||||
_childrenTypesTemp.Clear();
|
||||
foreach (var beforeType in original)
|
||||
{
|
||||
foreach (var child in _reflection.GetAllChildren(beforeType))
|
||||
{
|
||||
_childrenTypesTemp.Add(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (_childrenTypesTemp.Count > 0)
|
||||
{
|
||||
Array.Resize(ref original, original.Length + _childrenTypesTemp.Count);
|
||||
_childrenTypesTemp.CopyTo(original, original.Length - _childrenTypesTemp.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -15,6 +15,7 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -40,6 +41,7 @@ namespace Robust.Shared.GameObjects
|
||||
[IoC.Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[IoC.Dependency] private readonly ProfManager _prof = default!;
|
||||
[IoC.Dependency] private readonly INetManager _netMan = default!;
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
|
||||
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
|
||||
// positions on spawn....
|
||||
@@ -125,7 +127,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (Initialized)
|
||||
throw new InvalidOperationException("Initialize() called multiple times");
|
||||
|
||||
_eventBus = new EntityEventBus(this);
|
||||
_eventBus = new EntityEventBus(this, _reflection);
|
||||
|
||||
InitializeComponents();
|
||||
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
|
||||
@@ -281,6 +283,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#region Entity Management
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
|
||||
{
|
||||
return CreateEntity(prototypeName, out _, overrides);
|
||||
|
||||
@@ -109,6 +109,17 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
EntityEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
/// <seealso cref="SubscribeLocalEvent{TComp, TEvent}(ComponentEventRefHandler{TComp, TEvent}, Type[], Type[])"/>
|
||||
// [Obsolete("Subscribe to the event by ref instead (ComponentEventRefHandler)")]
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
@@ -133,17 +144,6 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
EntityEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
private void ShutdownSubscriptions()
|
||||
{
|
||||
foreach (var sub in _subscriptions)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed class PrototypeReloadSystem : EntitySystem
|
||||
if (!eventArgs.ByType.TryGetValue(typeof(EntityPrototype), out var set))
|
||||
return;
|
||||
|
||||
var query = EntityQueryEnumerator<MetaDataComponent>();
|
||||
var query = AllEntityQuery<MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var metadata))
|
||||
{
|
||||
var id = metadata.EntityPrototype?.ID;
|
||||
|
||||
@@ -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>
|
||||
@@ -923,12 +944,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
EntityCoordinates uiCoordinates,
|
||||
MapId uiMap)
|
||||
{
|
||||
if (!_xformQuery.TryGetComponent(actor, out var actorXform) || actorXform.MapID != uiMap)
|
||||
return false;
|
||||
|
||||
if (_ignoreUIRangeQuery.HasComponent(actor))
|
||||
return true;
|
||||
|
||||
if (!_xformQuery.TryGetComponent(actor, out var actorXform))
|
||||
return false;
|
||||
|
||||
// Handle pluggable BoundUserInterfaceCheckRangeEvent
|
||||
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, key, data, actor);
|
||||
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
|
||||
@@ -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;
|
||||
|
||||
@@ -55,5 +55,10 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="id">The new tile ID for this tile definition.</param>
|
||||
void AssignTileId(ushort id);
|
||||
|
||||
/// <summary>
|
||||
/// Allows you to hide tiles from the tile spawn menu.
|
||||
/// </summary>
|
||||
bool EditorHidden => false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Shared.Reflection;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
@@ -21,6 +21,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
var compInstance = new MetaDataComponent();
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
compFactory.RegisterClass<MetaDataComponent>();
|
||||
entManMock.Setup(m => m.ComponentFactory).Returns(compFactory);
|
||||
@@ -35,7 +36,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
entManMock.Setup(m => m.GetComponentInternal(entUid, CompIdx.Index<MetaDataComponent>()))
|
||||
.Returns(compInstance);
|
||||
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
@@ -80,6 +81,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
CompIdx.Index<MetaDataComponent>());
|
||||
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
compFacMock.Setup(m => m.GetRegistration(CompIdx.Index<MetaDataComponent>())).Returns(compRegistration);
|
||||
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration });
|
||||
@@ -92,7 +94,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
|
||||
.Returns(compInstance);
|
||||
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
@@ -137,6 +139,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
CompIdx.Index<MetaDataComponent>());
|
||||
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
compFacMock.Setup(m => m.GetRegistration(CompIdx.Index<MetaDataComponent>())).Returns(compRegistration);
|
||||
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration });
|
||||
@@ -149,7 +152,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
|
||||
.Returns(compInstance);
|
||||
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
@@ -184,6 +187,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
List<ComponentRegistration> allRefTypes = new();
|
||||
void Setup<T>(out T instance) where T : IComponent, new()
|
||||
@@ -208,7 +212,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(allRefTypes.ToArray());
|
||||
|
||||
entManMock.Setup(m => m.ComponentFactory).Returns(compFacMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
@@ -12,8 +13,9 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
entManMock.SetupGet(e => e.ComponentFactory).Returns(compFacMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
return bus;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user