mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab050ceb7a | ||
|
|
279b3b7c5d | ||
|
|
328303b83a | ||
|
|
ff5c89a7d6 | ||
|
|
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 | ||
|
|
7cb3aeccc2 | ||
|
|
ae83e606d6 | ||
|
|
d9d5ef7471 | ||
|
|
0f97f366a6 | ||
|
|
35ab0b8cc8 | ||
|
|
5a14e939bf | ||
|
|
ccba6b5d1c | ||
|
|
254a5987c7 | ||
|
|
8550056e68 | ||
|
|
25211e3781 | ||
|
|
3500abfd47 | ||
|
|
7d1915096a | ||
|
|
4504731588 | ||
|
|
701fa95a82 | ||
|
|
40a9048704 | ||
|
|
cee8d42776 | ||
|
|
3330d96177 | ||
|
|
4033d96327 | ||
|
|
6e0205d1a8 | ||
|
|
7cd95351c3 | ||
|
|
2a102f048f | ||
|
|
16bab1bc03 | ||
|
|
123d0ae6ac | ||
|
|
d72de032fa | ||
|
|
0fdba836ee |
@@ -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
|
||||
|
||||
|
||||
2
.github/workflows/build-test.yml
vendored
2
.github/workflows/build-test.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
6
.github/workflows/publish-client.yml
vendored
6
.github/workflows/publish-client.yml
vendored
@@ -33,10 +33,10 @@ jobs:
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
|
||||
- name: Upload files to centcomm
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
We actually set ManagePackageVersionsCentrally manually in another import file.
|
||||
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
|
||||
https://github.com/NuGet/NuGet.Client/pull/5572
|
||||
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
|
||||
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
|
||||
-->
|
||||
<ManagePackageVersionsCentrally />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
@@ -45,7 +55,7 @@
|
||||
<PackageVersion Include="Serilog" Version="3.1.1" />
|
||||
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
|
||||
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
|
||||
|
||||
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 -->
|
||||
|
||||
|
||||
167
RELEASE-NOTES.md
167
RELEASE-NOTES.md
@@ -54,6 +54,173 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 222.4.2
|
||||
|
||||
|
||||
## 222.4.1
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
* Add SetMapAudio helper to SharedAudioSystem to setup map-wide audio entities.
|
||||
* Add SetWorldRotNoLerp method to SharedTransformSystem to avoid client lerping.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration.
|
||||
|
||||
|
||||
## 221.1.0
|
||||
|
||||
|
||||
## 221.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* `EntParentChangedMessage.OldMapId` is now an `EntityUid` instead of `MapId`
|
||||
* `TransformSystem.DetachParentToNull()` is being renamed to `DetachEntity`
|
||||
* The order in which `MoveEvent` handlers are invoked has been changed to prioritise engine subscriptions
|
||||
|
||||
### New features
|
||||
|
||||
* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control.
|
||||
* Add SwapPositions to TransformSystem to swap two entity's transforms.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Improve client gamestate exception tolerance.
|
||||
|
||||
### Other
|
||||
|
||||
* If the currently hovered control is disposed, `UserInterfaceManager` will now look for a new control, rather than just setting the hovered control to null.
|
||||
|
||||
### Internal
|
||||
|
||||
* Use more `EntityQuery<T>` internally in EntityManager and PhysicsSystem.
|
||||
|
||||
|
||||
## 220.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported.
|
||||
* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad.
|
||||
* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers.
|
||||
|
||||
|
||||
## 220.1.0
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix client-side replay exceptions due to dropped states when recording.
|
||||
|
||||
### Other
|
||||
|
||||
* Remove IP + HWId from ViewVariables.
|
||||
* Close BUIs upon disconnect.
|
||||
|
||||
|
||||
## 220.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Refactor UserInterfaceSystem.
|
||||
- The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate.
|
||||
- Interface data is now stored via key rather than as a flat list which is a breaking change for YAML.
|
||||
- BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before.
|
||||
- BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly.
|
||||
|
||||
|
||||
## 219.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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var type = Type.GetType(args[0]);
|
||||
var type = GetType(args[0]);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
|
||||
shell.WriteLine(sig);
|
||||
}
|
||||
}
|
||||
|
||||
private Type? GetType(string name)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
if (assembly.GetType(name) is { } type)
|
||||
return type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Graphics.Clyde;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Client.Utility;
|
||||
using Robust.Shared.Animations;
|
||||
@@ -28,6 +29,7 @@ using static Robust.Client.ComponentTrees.SpriteTreeSystem;
|
||||
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -770,15 +772,7 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
foreach (var keyString in layerDatum.MapKeys)
|
||||
{
|
||||
object key;
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
{
|
||||
key = @enum;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = keyString;
|
||||
}
|
||||
var key = ParseKey(keyString);
|
||||
|
||||
if (LayerMap.TryGetValue(key, out var mappedIndex))
|
||||
{
|
||||
@@ -804,9 +798,30 @@ namespace Robust.Client.GameObjects
|
||||
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
|
||||
layer.Visible = layerDatum.Visible ?? layer.Visible;
|
||||
|
||||
if (layerDatum.CopyToShaderParameters is { } copyParameters)
|
||||
{
|
||||
layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey))
|
||||
{
|
||||
ParameterTexture = copyParameters.ParameterTexture,
|
||||
ParameterUV = copyParameters.ParameterUV
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
layer.CopyToShaderParameters = null;
|
||||
}
|
||||
|
||||
RebuildBounds();
|
||||
}
|
||||
|
||||
private object ParseKey(string keyString)
|
||||
{
|
||||
if (reflection.TryParseEnumReference(keyString, out var @enum))
|
||||
return @enum;
|
||||
|
||||
return keyString;
|
||||
}
|
||||
|
||||
public void LayerSetData(object layerKey, PrototypeLayerData data)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer, true))
|
||||
@@ -1635,6 +1650,9 @@ namespace Robust.Client.GameObjects
|
||||
[ViewVariables]
|
||||
public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy;
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public CopyToShaderParameters? CopyToShaderParameters;
|
||||
|
||||
public Layer(SpriteComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
@@ -1663,6 +1681,8 @@ namespace Robust.Client.GameObjects
|
||||
DirOffset = toClone.DirOffset;
|
||||
_autoAnimated = toClone._autoAnimated;
|
||||
RenderingStrategy = toClone.RenderingStrategy;
|
||||
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
|
||||
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
|
||||
}
|
||||
|
||||
void ISerializationHooks.AfterDeserialization()
|
||||
@@ -2007,8 +2027,6 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// Set the drawing transform for this layer
|
||||
GetLayerDrawMatrix(dir, out var layerMatrix);
|
||||
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
|
||||
// due to direction overrides or offsets.
|
||||
@@ -2018,7 +2036,41 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
// Get the correct directional texture from the state, and draw it!
|
||||
var texture = GetRenderTexture(_actualState, dir);
|
||||
RenderTexture(drawingHandle, texture);
|
||||
|
||||
if (CopyToShaderParameters == null)
|
||||
{
|
||||
// Set the drawing transform for this layer
|
||||
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
|
||||
drawingHandle.SetTransform(in transformMatrix);
|
||||
|
||||
RenderTexture(drawingHandle, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multiple atrocities to god being committed right here.
|
||||
var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!];
|
||||
var otherLayer = _parent.Layers[otherLayerIdx];
|
||||
if (otherLayer.Shader is not { } shader)
|
||||
{
|
||||
// No shader set apparently..?
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shader.Mutable)
|
||||
otherLayer.Shader = shader = shader.Duplicate();
|
||||
|
||||
var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr);
|
||||
var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr);
|
||||
|
||||
if (CopyToShaderParameters.ParameterTexture is { } paramTexture)
|
||||
shader.SetParameter(paramTexture, clydeTexture);
|
||||
|
||||
if (CopyToShaderParameters.ParameterUV is { } paramUV)
|
||||
{
|
||||
var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top);
|
||||
shader.SetParameter(paramUV, uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
|
||||
@@ -2096,6 +2148,23 @@ namespace Robust.Client.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiated version of <see cref="PrototypeCopyToShaderParameters"/>.
|
||||
/// Has <see cref="LayerKey"/> actually resolved to a a real key.
|
||||
/// </summary>
|
||||
public sealed class CopyToShaderParameters(object layerKey)
|
||||
{
|
||||
public object LayerKey = layerKey;
|
||||
public string? ParameterTexture;
|
||||
public string? ParameterUV;
|
||||
|
||||
public CopyToShaderParameters(CopyToShaderParameters toClone) : this(toClone.LayerKey)
|
||||
{
|
||||
ParameterTexture = toClone.ParameterTexture;
|
||||
ParameterUV = toClone.ParameterUV;
|
||||
}
|
||||
}
|
||||
|
||||
void IAnimationProperties.SetAnimatableProperty(string name, object value)
|
||||
{
|
||||
if (!name.StartsWith("layer/"))
|
||||
|
||||
@@ -1,84 +1,8 @@
|
||||
using Robust.Client.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Reflection;
|
||||
using System;
|
||||
using UserInterfaceComponent = Robust.Shared.GameObjects.UserInterfaceComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
|
||||
}
|
||||
|
||||
private void MessageReceived(BoundUIWrapMessage ev)
|
||||
{
|
||||
var uid = GetEntity(ev.Entity);
|
||||
|
||||
if (!TryComp<UserInterfaceComponent>(uid, out var cmp))
|
||||
return;
|
||||
|
||||
var uiKey = ev.UiKey;
|
||||
var message = ev.Message;
|
||||
message.Session = _playerManager.LocalSession!;
|
||||
message.Entity = GetNetEntity(uid);
|
||||
message.UiKey = uiKey;
|
||||
|
||||
// Raise as object so the correct type is used.
|
||||
RaiseLocalEvent(uid, (object)message, true);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case OpenBoundInterfaceMessage _:
|
||||
TryOpenUi(uid, uiKey, cmp);
|
||||
break;
|
||||
|
||||
case CloseBoundInterfaceMessage _:
|
||||
TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (cmp.OpenInterfaces.TryGetValue(uiKey, out var bui))
|
||||
bui.InternalReceiveMessage(message);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null)
|
||||
{
|
||||
if (!Resolve(uid, ref uiComp))
|
||||
return false;
|
||||
|
||||
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
|
||||
return false;
|
||||
|
||||
var data = uiComp.MappedInterfaceData[uiKey];
|
||||
|
||||
// TODO: This type should be cached, but I'm too lazy.
|
||||
var type = _reflectionManager.LooseGetType(data.ClientType);
|
||||
var boundInterface =
|
||||
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {uid, uiKey});
|
||||
|
||||
boundInterface.Open();
|
||||
uiComp.OpenInterfaces[uiKey] = boundInterface;
|
||||
|
||||
if (_playerManager.LocalSession is { } playerSession)
|
||||
{
|
||||
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
|
||||
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ namespace Robust.Client.GameStates
|
||||
#endif
|
||||
|
||||
private bool _resettingPredictedEntities;
|
||||
private readonly List<EntityUid> _brokenEnts = new();
|
||||
private readonly List<(EntityUid, NetEntity)> _toStart = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -667,7 +669,16 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var netEntity in createdEntities)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
|
||||
{
|
||||
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
var (_, meta) = _entityManager.GetEntityData(netEntity);
|
||||
#endif
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
_outputData.Add(netEntity, compData);
|
||||
|
||||
@@ -876,9 +887,22 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
foreach (var (entity, data) in _toApply)
|
||||
{
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
|
||||
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
|
||||
_entityManager.DeleteEntity(entity);
|
||||
RequestFullState();
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (!data.EnteringPvs)
|
||||
continue;
|
||||
|
||||
@@ -917,7 +941,7 @@ namespace Robust.Client.GameStates
|
||||
{
|
||||
try
|
||||
{
|
||||
ProcessDeletions(delSpan, xforms, xformSys);
|
||||
ProcessDeletions(delSpan, xforms, metas, xformSys);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -962,6 +986,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
var xforms = _entities.GetEntityQuery<TransformComponent>();
|
||||
var metas = _entities.GetEntityQuery<MetaDataComponent>();
|
||||
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
|
||||
|
||||
_toDelete.Clear();
|
||||
@@ -990,12 +1015,12 @@ namespace Robust.Client.GameStates
|
||||
|
||||
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
|
||||
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
|
||||
xformSys.DetachParentToNull(ent, xform);
|
||||
xformSys.DetachEntity(ent, xform);
|
||||
|
||||
// Then detach all children.
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
|
||||
|
||||
if (deleteClientChildren
|
||||
&& !deleteClientEntities // don't add duplicates
|
||||
@@ -1014,9 +1039,9 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessDeletions(
|
||||
ReadOnlySpan<NetEntity> delSpan,
|
||||
private void ProcessDeletions(ReadOnlySpan<NetEntity> delSpan,
|
||||
EntityQuery<TransformComponent> xforms,
|
||||
EntityQuery<MetaDataComponent> metas,
|
||||
SharedTransformSystem xformSys)
|
||||
{
|
||||
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
|
||||
@@ -1043,13 +1068,13 @@ namespace Robust.Client.GameStates
|
||||
continue; // Already deleted? or never sent to us?
|
||||
|
||||
// First, a single recursive map change
|
||||
xformSys.DetachParentToNull(id.Value, xform);
|
||||
xformSys.DetachEntity(id.Value, xform);
|
||||
|
||||
// Then detach all children.
|
||||
var childEnumerator = xform.ChildEnumerator;
|
||||
while (childEnumerator.MoveNext(out var child))
|
||||
{
|
||||
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
|
||||
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
|
||||
}
|
||||
|
||||
// Finally, delete the entity.
|
||||
@@ -1144,7 +1169,7 @@ namespace Robust.Client.GameStates
|
||||
}
|
||||
|
||||
meta._flags |= MetaDataFlags.Detached;
|
||||
xformSys.DetachParentToNull(ent.Value, xform);
|
||||
xformSys.DetachEntity(ent.Value, xform);
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
|
||||
|
||||
if (container != null)
|
||||
@@ -1157,63 +1182,58 @@ namespace Robust.Client.GameStates
|
||||
|
||||
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
|
||||
{
|
||||
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
|
||||
_toStart.Clear();
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
var brokenEnts = new List<EntityUid>();
|
||||
#endif
|
||||
using (_prof.Group("Initialize Entity"))
|
||||
{
|
||||
EntityUid entity = default;
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(netEntity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
|
||||
#if EXCEPTION_TOLERANCE
|
||||
(entity, var meta) = _entityManager.GetEntityData(netEntity);
|
||||
_entities.InitializeEntity(entity, meta);
|
||||
_toStart.Add((entity, netEntity));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
|
||||
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
brokenEnts.Add(entity);
|
||||
toCreate.Remove(netEntity);
|
||||
}
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (_prof.Group("Start Entity"))
|
||||
{
|
||||
foreach (var netEntity in toCreate.Keys)
|
||||
foreach (var (entity, netEntity) in _toStart)
|
||||
{
|
||||
var entity = _entityManager.GetEntity(netEntity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
try
|
||||
{
|
||||
#endif
|
||||
_entities.StartEntity(entity);
|
||||
#if EXCEPTION_TOLERANCE
|
||||
_entities.StartEntity(entity);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
|
||||
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
|
||||
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
|
||||
brokenEnts.Add(entity);
|
||||
toCreate.Remove(netEntity);
|
||||
}
|
||||
_toCreate.Remove(netEntity);
|
||||
_brokenEnts.Add(entity);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if EXCEPTION_TOLERANCE
|
||||
foreach (var entity in brokenEnts)
|
||||
foreach (var entity in _brokenEnts)
|
||||
{
|
||||
_entityManager.DeleteEntity(entity);
|
||||
}
|
||||
#endif
|
||||
_brokenEnts.Clear();
|
||||
}
|
||||
|
||||
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
|
||||
@@ -1402,7 +1422,7 @@ namespace Robust.Client.GameStates
|
||||
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
|
||||
}
|
||||
|
||||
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
|
||||
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachEntity(uid, xform);
|
||||
|
||||
if (container != null)
|
||||
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);
|
||||
|
||||
@@ -6,7 +6,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
("aPos", 0),
|
||||
("tCoord", 1),
|
||||
("modulate", 2)
|
||||
("tCoord2", 2),
|
||||
("modulate", 3)
|
||||
};
|
||||
|
||||
private const int UniIModUV = 0;
|
||||
|
||||
@@ -23,9 +23,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Texture Coords.
|
||||
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(1);
|
||||
// Colour Modulation.
|
||||
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
|
||||
// Texture Coords (2).
|
||||
GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(2);
|
||||
// Colour Modulation.
|
||||
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float));
|
||||
GL.EnableVertexAttribArray(3);
|
||||
}
|
||||
|
||||
// NOTE: This is:
|
||||
@@ -37,6 +40,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
public readonly Vector2 Position;
|
||||
public readonly Vector2 TextureCoordinates;
|
||||
public readonly Vector2 TextureCoordinates2;
|
||||
// Note that this color is in linear space.
|
||||
public readonly Color Modulate;
|
||||
|
||||
@@ -48,6 +52,15 @@ namespace Robust.Client.Graphics.Clyde
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate)
|
||||
{
|
||||
Position = position;
|
||||
TextureCoordinates = textureCoordinates;
|
||||
TextureCoordinates2 = textureCoordinates2;
|
||||
Modulate = modulate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a)
|
||||
: this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a))
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
private RenderHandle _renderHandle = default!;
|
||||
|
||||
private sealed class RenderHandle : IRenderHandle
|
||||
internal sealed class RenderHandle : IRenderHandle
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
private readonly IEntityManager _entities;
|
||||
@@ -88,16 +88,21 @@ namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
|
||||
|
||||
var (w, h) = clydeTexture.Size;
|
||||
var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
|
||||
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
|
||||
|
||||
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
|
||||
}
|
||||
|
||||
internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr)
|
||||
{
|
||||
var (w, h) = texture.Size;
|
||||
return new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas).
|
||||
/// </summary>
|
||||
private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
|
||||
internal static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
|
||||
{
|
||||
if (texture is AtlasTexture atlas)
|
||||
{
|
||||
|
||||
@@ -578,10 +578,10 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
// TODO: split batch if necessary.
|
||||
var vIdx = BatchVertexIndex;
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, modulate);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, modulate);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, modulate);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, modulate);
|
||||
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, new Vector2(0, 0), modulate);
|
||||
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, new Vector2(1, 0), modulate);
|
||||
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, new Vector2(1, 1), modulate);
|
||||
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, new Vector2(0, 1), modulate);
|
||||
BatchVertexIndex += 4;
|
||||
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);
|
||||
|
||||
|
||||
@@ -601,7 +601,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ClydeTexture : OwnedTexture
|
||||
internal sealed class ClydeTexture : OwnedTexture
|
||||
{
|
||||
private readonly Clyde _clyde;
|
||||
public readonly bool IsSrgb;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
varying highp vec2 Pos;
|
||||
varying highp vec4 VtxModulate;
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
/*layout (location = 2)*/ attribute vec2 tCoord2;
|
||||
// Colour modulation.
|
||||
/*layout (location = 2)*/ attribute vec4 modulate;
|
||||
/*layout (location = 3)*/ attribute vec4 modulate;
|
||||
|
||||
varying vec2 UV;
|
||||
varying vec2 UV2;
|
||||
varying vec2 Pos;
|
||||
varying vec4 VtxModulate;
|
||||
|
||||
@@ -36,5 +38,6 @@ void main()
|
||||
gl_Position = vec4(VERTEX, 0.0, 1.0);
|
||||
Pos = (VERTEX + 1.0) / 2.0;
|
||||
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
|
||||
UV2 = tCoord2;
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
|
||||
uniform sampler2D lightMap;
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
/*layout (location = 0)*/ attribute vec2 aPos;
|
||||
// Texture coordinates.
|
||||
/*layout (location = 1)*/ attribute vec2 tCoord;
|
||||
/*layout (location = 2)*/ attribute vec2 tCoord2;
|
||||
// Colour modulation.
|
||||
/*layout (location = 2)*/ attribute vec4 modulate;
|
||||
/*layout (location = 3)*/ attribute vec4 modulate;
|
||||
|
||||
varying vec2 UV;
|
||||
varying vec2 UV2;
|
||||
|
||||
// Maybe we should merge these CPU side.
|
||||
// idk yet.
|
||||
@@ -40,6 +42,7 @@ void main()
|
||||
vec2 VERTEX = aPos;
|
||||
|
||||
UV = tCoord;
|
||||
UV2 = tCoord2;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
|
||||
@@ -114,43 +114,12 @@ namespace Robust.Client.Graphics
|
||||
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
|
||||
}
|
||||
|
||||
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
|
||||
{
|
||||
if (input.Length == 0)
|
||||
return;
|
||||
|
||||
if (input.Length != output.Length)
|
||||
Color colorLinear = Color.FromSrgb(color);
|
||||
for (var i = 0; i < output.Length; i++)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid lengths!");
|
||||
}
|
||||
|
||||
var colorLinear = Color.FromSrgb(color);
|
||||
var colorVec = Unsafe.As<Color, Vector128<float>>(ref colorLinear);
|
||||
var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f);
|
||||
var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle();
|
||||
|
||||
var simdVectors = (nuint)(input.Length / 2);
|
||||
ref readonly var srcBase = ref Unsafe.As<Vector2, float>(ref Unsafe.AsRef(in input[0]));
|
||||
ref var dstBase = ref Unsafe.As<DrawVertexUV2DColor, float>(ref output[0]);
|
||||
|
||||
for (nuint i = 0; i < simdVectors; i++)
|
||||
{
|
||||
var positions = Vector128.LoadUnsafe(in srcBase, i * 4);
|
||||
|
||||
var posColorLower = (positions & maskVec) | uvVec;
|
||||
var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec;
|
||||
|
||||
posColorLower.StoreUnsafe(ref dstBase, i * 16);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 4);
|
||||
posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8);
|
||||
colorVec.StoreUnsafe(ref dstBase, i * 16 + 12);
|
||||
}
|
||||
|
||||
var lastPos = (int)simdVectors * 2;
|
||||
if (lastPos != output.Length)
|
||||
{
|
||||
// Odd number of vertices. Handle the last manually.
|
||||
output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +237,8 @@ namespace Robust.Client.Graphics
|
||||
{
|
||||
public Vector2 Position;
|
||||
public Vector2 UV;
|
||||
public Vector2 UV2;
|
||||
|
||||
/// <summary>
|
||||
/// Modulation colour for this vertex.
|
||||
/// Note that this color is in linear space.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -88,7 +88,6 @@ public sealed partial class ReplayLoadManager
|
||||
if (initMessages != null)
|
||||
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
|
||||
UpdateMessages(messages[0], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
|
||||
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
|
||||
|
||||
var entSpan = state0.EntityStates.Value;
|
||||
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
|
||||
@@ -98,6 +97,8 @@ public sealed partial class ReplayLoadManager
|
||||
entStates.Add(entState.NetEntity, modifiedState);
|
||||
}
|
||||
|
||||
ProcessQueue(GameTick.MaxValue, detachQueue, detached, entStates);
|
||||
|
||||
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
|
||||
var playerSpan = state0.PlayerStates.Value;
|
||||
Dictionary<NetUserId, SessionState> playerStates = new(playerSpan.Count);
|
||||
@@ -144,7 +145,7 @@ public sealed partial class ReplayLoadManager
|
||||
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
|
||||
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
|
||||
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
|
||||
ProcessQueue(curState.ToSequence, detachQueue, detached);
|
||||
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
|
||||
UpdateDeletions(curState.EntityDeletions, entStates, detached);
|
||||
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
|
||||
ticksSinceLastCheckpoint++;
|
||||
@@ -176,14 +177,28 @@ public sealed partial class ReplayLoadManager
|
||||
private void ProcessQueue(
|
||||
GameTick curTick,
|
||||
Dictionary<GameTick, List<NetEntity>> detachQueue,
|
||||
HashSet<NetEntity> detached)
|
||||
HashSet<NetEntity> detached,
|
||||
Dictionary<NetEntity, EntityState> entStates)
|
||||
{
|
||||
foreach (var (tick, ents) in detachQueue)
|
||||
{
|
||||
if (tick > curTick)
|
||||
continue;
|
||||
detachQueue.Remove(tick);
|
||||
detached.UnionWith(ents);
|
||||
|
||||
foreach (var e in ents)
|
||||
{
|
||||
if (entStates.ContainsKey(e))
|
||||
detached.Add(e);
|
||||
else
|
||||
{
|
||||
// AFAIK this should only happen if the client skipped over some ticks, probably due to packet loss
|
||||
// I.e., entity was created on tick n, then leaves PVS range on the tick n+1
|
||||
// If the n-th tick gets dropped, the client only ever receives the pvs-leave message.
|
||||
// In that case we should just ignore it.
|
||||
_sawmill.Debug($"Received a PVS detach msg for entity {e} before it was received?");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -79,13 +79,14 @@ internal sealed partial class ReplayPlaybackManager
|
||||
if (checkpoint.DetachedStates == null)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); ;
|
||||
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
|
||||
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length);
|
||||
foreach (var es in checkpoint.DetachedStates)
|
||||
{
|
||||
var uid = _entMan.GetEntity(es.NetEntity);
|
||||
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
|
||||
if (_entMan.TryGetEntityData(es.NetEntity, out var uid, out var meta))
|
||||
{
|
||||
DebugTools.Assert(!meta.EntityDeleted);
|
||||
continue;
|
||||
}
|
||||
|
||||
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
|
||||
.FirstOrDefault(c => c.NetID == _metaId).State;
|
||||
@@ -93,18 +94,16 @@ internal sealed partial class ReplayPlaybackManager
|
||||
if (metaState == null)
|
||||
throw new MissingMetadataException(es.NetEntity);
|
||||
|
||||
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
|
||||
meta = metas.GetComponent(uid);
|
||||
uid = _entMan.CreateEntity(metaState.PrototypeId, out meta);
|
||||
|
||||
// Client creates a client-side net entity for the newly created entity.
|
||||
// We need to clear this mapping before assigning the real net id.
|
||||
// TODO NetEntity Jank: prevent the client from creating this in the first place.
|
||||
_entMan.ClearNetEntity(meta.NetEntity);
|
||||
_entMan.SetNetEntity(uid.Value, es.NetEntity, meta);
|
||||
|
||||
_entMan.SetNetEntity(uid, es.NetEntity, meta);
|
||||
|
||||
_entMan.InitializeEntity(uid, meta);
|
||||
_entMan.StartEntity(uid);
|
||||
_entMan.InitializeEntity(uid.Value, meta);
|
||||
_entMan.StartEntity(uid.Value);
|
||||
meta.LastStateApplied = checkpoint.Tick;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -142,6 +143,26 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
});
|
||||
|
||||
// Do not meta-atlas RSIs with custom load parameters.
|
||||
var atlasList = rsiList.Where(x => x.LoadParameters == TextureLoadParameters.Default).ToArray();
|
||||
var nonAtlasList = rsiList.Where(x => x.LoadParameters != TextureLoadParameters.Default).ToArray();
|
||||
|
||||
foreach (var data in nonAtlasList)
|
||||
{
|
||||
if (data.Bad)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
RSIResource.LoadTexture(Clyde, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
|
||||
data.Bad = true;
|
||||
}
|
||||
}
|
||||
|
||||
// This combines individual RSI atlases into larger atlases to reduce draw batches. currently this is a VERY
|
||||
// lazy bundling and is not at all compact, its basically an atlas of RSI atlases. Really what this should
|
||||
// try to do is to have each RSI write directly to the atlas, rather than having each RSI write to its own
|
||||
@@ -155,7 +176,7 @@ namespace Robust.Client.ResourceManagement
|
||||
// TODO allow RSIs to opt out (useful for very big & rare RSIs)
|
||||
// TODO combine with (non-rsi) texture atlas?
|
||||
|
||||
Array.Sort(rsiList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
|
||||
Array.Sort(atlasList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
|
||||
|
||||
// Each RSI sub atlas has a different size.
|
||||
// Even if we iterate through them once to estimate total area, I have NFI how to sanely estimate an optimal square-texture size.
|
||||
@@ -167,9 +188,9 @@ namespace Robust.Client.ResourceManagement
|
||||
Vector2i offset = default;
|
||||
int finalized = -1;
|
||||
int atlasCount = 0;
|
||||
for (int i = 0; i < rsiList.Length; i++)
|
||||
for (int i = 0; i < atlasList.Length; i++)
|
||||
{
|
||||
var rsi = rsiList[i];
|
||||
var rsi = atlasList[i];
|
||||
if (rsi.Bad)
|
||||
continue;
|
||||
|
||||
@@ -200,14 +221,14 @@ namespace Robust.Client.ResourceManagement
|
||||
var height = offset.Y + deltaY;
|
||||
var croppedSheet = new Image<Rgba32>(maxSize, height);
|
||||
sheet.Blit(new UIBox2i(0, 0, maxSize, height), croppedSheet, default);
|
||||
FinalizeMetaAtlas(rsiList.Length - 1, croppedSheet);
|
||||
FinalizeMetaAtlas(atlasList.Length - 1, croppedSheet);
|
||||
|
||||
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
|
||||
{
|
||||
var atlas = Clyde.LoadTextureFromImage(sheet);
|
||||
for (int i = finalized + 1; i <= toIndex; i++)
|
||||
{
|
||||
var rsi = rsiList[i];
|
||||
var rsi = atlasList[i];
|
||||
rsi.AtlasTexture = atlas;
|
||||
}
|
||||
|
||||
@@ -255,9 +276,10 @@ namespace Robust.Client.ResourceManagement
|
||||
}
|
||||
|
||||
sawmill.Debug(
|
||||
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountErrored} errored) in {LoadTime}",
|
||||
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountNotAtlas} not atlassed, {CountErrored} errored) in {LoadTime}",
|
||||
rsiList.Length,
|
||||
atlasCount,
|
||||
nonAtlasList.Length,
|
||||
errors,
|
||||
sw.Elapsed);
|
||||
|
||||
|
||||
@@ -40,17 +40,21 @@ namespace Robust.Client.ResourceManagement
|
||||
var loadStepData = new LoadStepData {Path = path};
|
||||
var manager = dependencies.Resolve<IResourceManager>();
|
||||
LoadPreTexture(manager, loadStepData);
|
||||
|
||||
loadStepData.AtlasTexture = dependencies.Resolve<IClyde>().LoadTextureFromImage(
|
||||
loadStepData.AtlasSheet,
|
||||
loadStepData.Path.ToString());
|
||||
|
||||
LoadTexture(dependencies.Resolve<IClyde>(), loadStepData);
|
||||
LoadPostTexture(loadStepData);
|
||||
LoadFinish(dependencies.Resolve<IResourceCacheInternal>(), loadStepData);
|
||||
|
||||
loadStepData.AtlasSheet.Dispose();
|
||||
}
|
||||
|
||||
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
|
||||
{
|
||||
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
|
||||
loadStepData.AtlasSheet,
|
||||
loadStepData.Path.ToString(),
|
||||
loadStepData.LoadParameters);
|
||||
}
|
||||
|
||||
internal static void LoadPreTexture(IResourceManager manager, LoadStepData data)
|
||||
{
|
||||
var manifestPath = data.Path / "meta.json";
|
||||
@@ -178,6 +182,7 @@ namespace Robust.Client.ResourceManagement
|
||||
data.FrameSize = frameSize;
|
||||
data.DimX = dimensionX;
|
||||
data.CallbackOffsets = callbackOffsets;
|
||||
data.LoadParameters = metadata.LoadParameters;
|
||||
}
|
||||
|
||||
internal static void LoadPostTexture(LoadStepData data)
|
||||
@@ -380,6 +385,7 @@ namespace Robust.Client.ResourceManagement
|
||||
public Texture AtlasTexture = default!;
|
||||
public Vector2i AtlasOffset;
|
||||
public RSI Rsi = default!;
|
||||
public TextureLoadParameters LoadParameters;
|
||||
}
|
||||
|
||||
internal struct StateReg
|
||||
|
||||
@@ -52,6 +52,10 @@ namespace Robust.Client.UserInterface
|
||||
[ViewVariables] public bool IsMeasureValid { get; private set; }
|
||||
[ViewVariables] public bool IsArrangeValid { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controls the amount of empty space in virtual pixels around the control.
|
||||
/// </summary>
|
||||
/// <remarks>Values can be provided as "All" or "Horizontal, Vertical" or "Left, Top, Right, Bottom"</remarks>
|
||||
[ViewVariables]
|
||||
public Thickness Margin
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,17 @@ namespace Robust.Client.UserInterface
|
||||
/// Plays the UI hover sound if relevant.
|
||||
/// </summary>
|
||||
void HoverSound();
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="CurrentlyHovered"/> to the given control.
|
||||
/// </summary>
|
||||
void SetHovered(Control? control);
|
||||
|
||||
/// <summary>
|
||||
/// Forces <see cref="CurrentlyHovered"/> to get updated. This is done automatically when the mouse is moved,
|
||||
/// but not necessarily a new or existing control is rearranged.
|
||||
/// </summary>
|
||||
void UpdateHovered();
|
||||
}
|
||||
|
||||
public readonly struct PostDrawUIRootEventArgs
|
||||
|
||||
@@ -9,6 +9,7 @@ using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
@@ -20,9 +21,10 @@ internal partial class UserInterfaceManager
|
||||
private bool _needUpdateActiveCursor;
|
||||
[ViewVariables] public Control? KeyboardFocused { get; private set; }
|
||||
|
||||
[ViewVariables] public Control? CurrentlyHovered { get; private set; } = default!;
|
||||
[ViewVariables] public Control? CurrentlyHovered { get; private set; }
|
||||
|
||||
private Control? _controlFocused;
|
||||
|
||||
[ViewVariables]
|
||||
public Control? ControlFocused
|
||||
{
|
||||
@@ -100,6 +102,7 @@ internal partial class UserInterfaceManager
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
|
||||
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
|
||||
args.PointerLocation.Position - control.GlobalPixelPosition);
|
||||
@@ -111,16 +114,20 @@ internal partial class UserInterfaceManager
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public void KeyBindUp(BoundKeyEventArgs args)
|
||||
{
|
||||
if (!_focusedControls.TryGetValue(args.Function, out var control))
|
||||
{
|
||||
// Only raise keybind-up for the control on which we previously raised keybind-down
|
||||
if (!_focusedControls.Remove(args.Function, out var control) || !control.VisibleInTree)
|
||||
return;
|
||||
}
|
||||
|
||||
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
|
||||
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
|
||||
@@ -131,7 +138,6 @@ internal partial class UserInterfaceManager
|
||||
// Always mark this as handled.
|
||||
// The only case it should not be is if we do not have a control to click on,
|
||||
// in which case we never reach this.
|
||||
_focusedControls.Remove(args.Function);
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
@@ -140,23 +146,7 @@ internal partial class UserInterfaceManager
|
||||
_resetTooltipTimer();
|
||||
// Update which control is considered hovered.
|
||||
var newHovered = MouseGetControl(mouseMoveEventArgs.Position);
|
||||
if (newHovered != CurrentlyHovered)
|
||||
{
|
||||
_clearTooltip();
|
||||
CurrentlyHovered?.MouseExited();
|
||||
CurrentlyHovered = newHovered;
|
||||
CurrentlyHovered?.MouseEntered();
|
||||
if (CurrentlyHovered != null)
|
||||
{
|
||||
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tooltipDelay = null;
|
||||
}
|
||||
|
||||
_needUpdateActiveCursor = true;
|
||||
}
|
||||
SetHovered(newHovered);
|
||||
|
||||
var target = ControlFocused ?? newHovered;
|
||||
if (target != null)
|
||||
@@ -172,6 +162,33 @@ internal partial class UserInterfaceManager
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateHovered()
|
||||
{
|
||||
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
|
||||
SetHovered(ctrl);
|
||||
}
|
||||
|
||||
public void SetHovered(Control? control)
|
||||
{
|
||||
if (control == CurrentlyHovered)
|
||||
return;
|
||||
|
||||
_clearTooltip();
|
||||
CurrentlyHovered?.MouseExited();
|
||||
CurrentlyHovered = control;
|
||||
CurrentlyHovered?.MouseEntered();
|
||||
if (CurrentlyHovered != null)
|
||||
{
|
||||
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tooltipDelay = null;
|
||||
}
|
||||
|
||||
_needUpdateActiveCursor = true;
|
||||
}
|
||||
|
||||
private void UpdateActiveCursor()
|
||||
{
|
||||
// Consider mouse input focus first so that dragging windows don't act up etc.
|
||||
|
||||
@@ -77,15 +77,12 @@ internal sealed partial class UserInterfaceManager
|
||||
|
||||
ReleaseKeyboardFocus(control);
|
||||
RemoveModal(control);
|
||||
if (control == CurrentlyHovered)
|
||||
{
|
||||
control.MouseExited();
|
||||
CurrentlyHovered = null;
|
||||
_clearTooltip();
|
||||
}
|
||||
|
||||
if (control != ControlFocused) return;
|
||||
ControlFocused = null;
|
||||
if (control == ControlFocused)
|
||||
ControlFocused = null;
|
||||
|
||||
if (control == CurrentlyHovered)
|
||||
UpdateHovered();
|
||||
}
|
||||
|
||||
public void PushModal(Control modal)
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -42,6 +42,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
|
||||
component.Source = new DummyAudioSource();
|
||||
}
|
||||
|
||||
public override void SetMapAudio(Entity<AudioComponent>? audio)
|
||||
{
|
||||
if (audio == null)
|
||||
return;
|
||||
|
||||
base.SetMapAudio(audio);
|
||||
|
||||
// Also need a global override because clients not near 0,0 won't get the audio.
|
||||
_pvs.AddGlobalOverride(audio.Value);
|
||||
}
|
||||
|
||||
private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter)
|
||||
{
|
||||
var count = filter.Count;
|
||||
|
||||
@@ -1,416 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using System.Collections;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameObjects
|
||||
namespace Robust.Server.GameObjects;
|
||||
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerMan = default!;
|
||||
[Dependency] private readonly TransformSystem _xformSys = default!;
|
||||
|
||||
private EntityQuery<IgnoreUIRangeComponent> _ignoreUIRangeQuery;
|
||||
|
||||
private readonly List<ICommonSession> _sessionCache = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeNetworkEvent<BoundUIWrapMessage>(OnMessageReceived);
|
||||
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_playerMan.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)
|
||||
{
|
||||
if (args.NewStatus != SessionStatus.Disconnected)
|
||||
return;
|
||||
|
||||
if (!OpenInterfaces.TryGetValue(args.Session, out var buis))
|
||||
return;
|
||||
|
||||
foreach (var bui in buis.ToArray())
|
||||
{
|
||||
CloseShared(bui, args.Session);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var query = AllEntityQuery<ActiveUserInterfaceComponent, TransformComponent>();
|
||||
|
||||
while (query.MoveNext(out var uid, out var activeUis, out var xform))
|
||||
{
|
||||
foreach (var ui in activeUis.Interfaces)
|
||||
{
|
||||
CheckRange(uid, activeUis, ui, xform, xformQuery);
|
||||
|
||||
if (!ui.StateDirty)
|
||||
continue;
|
||||
|
||||
ui.StateDirty = false;
|
||||
|
||||
foreach (var (player, state) in ui.PlayerStateOverrides)
|
||||
{
|
||||
RaiseNetworkEvent(state, player.Channel);
|
||||
}
|
||||
|
||||
if (ui.LastStateMsg == null)
|
||||
continue;
|
||||
|
||||
foreach (var session in ui.SubscribedSessions)
|
||||
{
|
||||
if (!ui.PlayerStateOverrides.ContainsKey(session))
|
||||
RaiseNetworkEvent(ui.LastStateMsg, session.Channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the subscribed clients are still in range of the interface.
|
||||
/// </summary>
|
||||
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
|
||||
{
|
||||
if (ui.InteractionRange <= 0)
|
||||
return;
|
||||
|
||||
// We have to cache the set of sessions because Unsubscribe modifies the original.
|
||||
_sessionCache.Clear();
|
||||
_sessionCache.AddRange(ui.SubscribedSessions);
|
||||
|
||||
var uiPos = _xformSys.GetWorldPosition(transform, query);
|
||||
var uiMap = transform.MapID;
|
||||
|
||||
foreach (var session in _sessionCache)
|
||||
{
|
||||
// The component manages the set of sessions, so this invalid session should be removed soon.
|
||||
if (!query.TryGetComponent(session.AttachedEntity, out var xform))
|
||||
continue;
|
||||
|
||||
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
|
||||
continue;
|
||||
|
||||
// Handle pluggable BoundUserInterfaceCheckRangeEvent
|
||||
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, ui, session);
|
||||
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
|
||||
continue;
|
||||
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
|
||||
|
||||
if (uiMap != xform.MapID)
|
||||
{
|
||||
CloseUi(ui, session, activeUis);
|
||||
continue;
|
||||
}
|
||||
|
||||
var distanceSquared = (uiPos - _xformSys.GetWorldPosition(xform, query)).LengthSquared();
|
||||
if (distanceSquared > ui.InteractionRangeSqrd)
|
||||
CloseUi(ui, session, activeUis);
|
||||
}
|
||||
}
|
||||
|
||||
#region Get BUI
|
||||
|
||||
public bool HasUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref ui))
|
||||
return false;
|
||||
|
||||
return ui.Interfaces.ContainsKey(uiKey);
|
||||
}
|
||||
|
||||
public PlayerBoundUserInterface GetUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref ui))
|
||||
throw new InvalidOperationException($"Cannot get {typeof(PlayerBoundUserInterface)} from an entity without {typeof(UserInterfaceComponent)}!");
|
||||
|
||||
return ui.Interfaces[uiKey];
|
||||
}
|
||||
|
||||
public PlayerBoundUserInterface? GetUiOrNull(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
return TryGetUi(uid, uiKey, out var bui, ui)
|
||||
? bui
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return UIs a session has open.
|
||||
/// Null if empty.
|
||||
/// </summary>
|
||||
public List<PlayerBoundUserInterface>? GetAllUIsForSession(ICommonSession session)
|
||||
{
|
||||
OpenInterfaces.TryGetValue(session, out var value);
|
||||
return value;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public bool IsUiOpen(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return bui.SubscribedSessions.Count > 0;
|
||||
}
|
||||
|
||||
public bool SessionHasOpenUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return bui.SubscribedSessions.Contains(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state. This can be used for stateful UI updating.
|
||||
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
|
||||
/// Pretty much how NanoUI did it back in ye olde BYOND.
|
||||
/// </summary>
|
||||
/// <param name="state">
|
||||
/// The state object that will be sent to all current and future client.
|
||||
/// This can be null.
|
||||
/// </param>
|
||||
/// <param name="session">
|
||||
/// The player session to send this new state to.
|
||||
/// Set to null for sending it to every subscribed player session.
|
||||
/// </param>
|
||||
public bool TrySetUiState(EntityUid uid,
|
||||
Enum uiKey,
|
||||
BoundUserInterfaceState state,
|
||||
ICommonSession? session = null,
|
||||
UserInterfaceComponent? ui = null,
|
||||
bool clearOverrides = true)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
SetUiState(bui, state, session, clearOverrides);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a state. This can be used for stateful UI updating.
|
||||
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
|
||||
/// Pretty much how NanoUI did it back in ye olde BYOND.
|
||||
/// </summary>
|
||||
/// <param name="state">
|
||||
/// The state object that will be sent to all current and future client.
|
||||
/// This can be null.
|
||||
/// </param>
|
||||
/// <param name="session">
|
||||
/// The player session to send this new state to.
|
||||
/// Set to null for sending it to every subscribed player session.
|
||||
/// </param>
|
||||
public void SetUiState(PlayerBoundUserInterface bui, BoundUserInterfaceState state, ICommonSession? session = null, bool clearOverrides = true)
|
||||
{
|
||||
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), new UpdateBoundStateMessage(state), bui.UiKey);
|
||||
if (session == null)
|
||||
{
|
||||
bui.LastStateMsg = msg;
|
||||
if (clearOverrides)
|
||||
bui.PlayerStateOverrides.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
bui.PlayerStateOverrides[session] = msg;
|
||||
}
|
||||
|
||||
bui.StateDirty = true;
|
||||
}
|
||||
|
||||
#region Close
|
||||
protected override void CloseShared(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null)
|
||||
{
|
||||
var owner = bui.Owner;
|
||||
bui._subscribedSessions.Remove(session);
|
||||
bui.PlayerStateOverrides.Remove(session);
|
||||
|
||||
if (OpenInterfaces.TryGetValue(session, out var buis))
|
||||
buis.Remove(bui);
|
||||
|
||||
RaiseLocalEvent(owner, new BoundUIClosedEvent(bui.UiKey, owner, session));
|
||||
|
||||
if (bui._subscribedSessions.Count == 0)
|
||||
DeactivateInterface(bui.Owner, bui, activeUis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes this all interface for any clients that have any open.
|
||||
/// </summary>
|
||||
public bool TryCloseAll(EntityUid uid, Shared.GameObjects.ActiveUserInterfaceComponent? aui = null)
|
||||
{
|
||||
if (!Resolve(uid, ref aui, false))
|
||||
return false;
|
||||
|
||||
foreach (var ui in aui.Interfaces)
|
||||
{
|
||||
CloseAll(ui);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes this specific interface for any clients that have it open.
|
||||
/// </summary>
|
||||
public bool TryCloseAll(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
CloseAll(bui);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes this interface for any clients that have it open.
|
||||
/// </summary>
|
||||
public void CloseAll(PlayerBoundUserInterface bui)
|
||||
{
|
||||
foreach (var session in bui.SubscribedSessions.ToArray())
|
||||
{
|
||||
CloseUi(bui, session);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region SendMessage
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to all connected player sessions.
|
||||
/// </summary>
|
||||
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
SendUiMessage(bui, message);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to all connected player sessions.
|
||||
/// </summary>
|
||||
public void SendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message)
|
||||
{
|
||||
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey);
|
||||
foreach (var session in bui.SubscribedSessions)
|
||||
{
|
||||
RaiseNetworkEvent(msg, session.Channel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to a specific player session.
|
||||
/// </summary>
|
||||
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, ICommonSession session, UserInterfaceComponent? ui = null)
|
||||
{
|
||||
if (!TryGetUi(uid, uiKey, out var bui, ui))
|
||||
return false;
|
||||
|
||||
return TrySendUiMessage(bui, message, session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a BUI message to a specific player session.
|
||||
/// </summary>
|
||||
public bool TrySendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message, ICommonSession session)
|
||||
{
|
||||
if (!bui.SubscribedSessions.Contains(session))
|
||||
return false;
|
||||
|
||||
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.Channel);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
[PublicAPI]
|
||||
public struct BoundUserInterfaceCheckRangeEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity owning the UI being checked for.
|
||||
/// </summary>
|
||||
public readonly EntityUid Target;
|
||||
|
||||
/// <summary>
|
||||
/// The UI itself.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public readonly PlayerBoundUserInterface UserInterface;
|
||||
|
||||
/// <summary>
|
||||
/// The player for which the UI is being checked.
|
||||
/// </summary>
|
||||
public readonly ICommonSession Player;
|
||||
|
||||
/// <summary>
|
||||
/// The result of the range check.
|
||||
/// </summary>
|
||||
public BoundUserInterfaceRangeResult Result;
|
||||
|
||||
public BoundUserInterfaceCheckRangeEvent(
|
||||
EntityUid target,
|
||||
PlayerBoundUserInterface userInterface,
|
||||
ICommonSession player)
|
||||
{
|
||||
Target = target;
|
||||
UserInterface = userInterface;
|
||||
Player = player;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible results for a <see cref="BoundUserInterfaceCheckRangeEvent"/>.
|
||||
/// </summary>
|
||||
public enum BoundUserInterfaceRangeResult : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Run built-in range check.
|
||||
/// </summary>
|
||||
Default,
|
||||
|
||||
/// <summary>
|
||||
/// Range check passed, UI is accessible.
|
||||
/// </summary>
|
||||
Pass,
|
||||
|
||||
/// <summary>
|
||||
/// Range check failed, UI is inaccessible.
|
||||
/// </summary>
|
||||
Fail
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Robust.Server.GameObjects
|
||||
StartEntity(entity);
|
||||
}
|
||||
|
||||
private protected override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
internal override EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
{
|
||||
if (prototypeName == null)
|
||||
return base.CreateEntity(prototypeName, out metadata, context);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -55,13 +55,10 @@ internal sealed partial class PvsSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var root = (xform.GridUid ?? xform.MapUid);
|
||||
DebugTools.AssertNotNull(root);
|
||||
|
||||
if (xform.ParentUid != root)
|
||||
if (xform.ParentUid != xform.GridUid && xform.ParentUid != xform.MapUid)
|
||||
return;
|
||||
|
||||
var location = new PvsChunkLocation(root.Value, GetChunkIndices(xform._localPosition));
|
||||
var location = new PvsChunkLocation(xform.ParentUid, GetChunkIndices(xform._localPosition));
|
||||
if (meta.LastPvsLocation == location)
|
||||
return;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -134,7 +134,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
_transform.OnGlobalMoveEvent += OnEntityMove;
|
||||
_transform.OnBeforeMoveEvent += OnEntityMove;
|
||||
EntityManager.EntityAdded += OnEntityAdded;
|
||||
EntityManager.EntityDeleted += OnEntityDeleted;
|
||||
EntityManager.AfterEntityFlush += AfterEntityFlush;
|
||||
@@ -159,7 +159,7 @@ internal sealed partial class PvsSystem : EntitySystem
|
||||
base.Shutdown();
|
||||
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
_transform.OnGlobalMoveEvent -= OnEntityMove;
|
||||
_transform.OnBeforeMoveEvent -= OnEntityMove;
|
||||
EntityManager.EntityAdded -= OnEntityAdded;
|
||||
EntityManager.EntityDeleted -= OnEntityDeleted;
|
||||
EntityManager.AfterEntityFlush -= AfterEntityFlush;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -32,11 +33,12 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] protected readonly IPrototypeManager ProtoMan = default!;
|
||||
[Dependency] protected readonly IRobustRandom RandMan = default!;
|
||||
[Dependency] protected readonly MetaDataSystem MetadataSys = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Default max range at which the sound can be heard.
|
||||
/// </summary>
|
||||
public const float DefaultSoundRange = 20;
|
||||
public const float DefaultSoundRange = 15;
|
||||
|
||||
/// <summary>
|
||||
/// Used in the PAS to designate the physics collision mask of occluders.
|
||||
@@ -131,6 +133,18 @@ public abstract partial class SharedAudioSystem : EntitySystem
|
||||
return (float) (Timing.CurTime - (component.PauseTime ?? TimeSpan.Zero) - component.AudioStart).TotalSeconds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks this audio as being map-based.
|
||||
/// </summary>
|
||||
public virtual void SetMapAudio(Entity<AudioComponent>? audio)
|
||||
{
|
||||
if (audio == null)
|
||||
return;
|
||||
|
||||
audio.Value.Comp.Global = true;
|
||||
MetadataSys.AddFlag(audio.Value.Owner, MetaDataFlags.Undetachable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the shared state for an audio entity.
|
||||
/// </summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,8 @@ public abstract class ComponentTreeSystem<TTreeComp, TComp> : EntitySystem
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO EXCEPTION TOLERANCE
|
||||
// Ensure lookup trees update before content code handles move events.
|
||||
SubscribeLocalEvent<TComp, MoveEvent>(HandleMove);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ internal sealed class RecursiveMoveSystem : EntitySystem
|
||||
public override void Shutdown()
|
||||
{
|
||||
if (_subscribed)
|
||||
_transform.OnGlobalMoveEvent -= AnythingMoved;
|
||||
_transform.OnBeforeMoveEvent -= AnythingMoved;
|
||||
|
||||
_subscribed = false;
|
||||
}
|
||||
@@ -44,7 +44,7 @@ internal sealed class RecursiveMoveSystem : EntitySystem
|
||||
return;
|
||||
|
||||
_subscribed = true;
|
||||
_transform.OnGlobalMoveEvent += AnythingMoved;
|
||||
_transform.OnBeforeMoveEvent += AnythingMoved;
|
||||
}
|
||||
|
||||
private void AnythingMoved(ref MoveEvent args)
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
|
||||
String("short").ThenReturn(PrimitiveTypeCode.Int16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
|
||||
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
|
||||
|
||||
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
|
||||
String("int").ThenReturn(PrimitiveTypeCode.Int32);
|
||||
|
||||
@@ -84,12 +84,146 @@ Types:
|
||||
- "bool get_HasContents()"
|
||||
Lidgren.Network:
|
||||
NetBuffer:
|
||||
All: True
|
||||
Methods:
|
||||
- "byte[] get_Data()"
|
||||
- "void set_Data(byte[])"
|
||||
- "int get_LengthBytes()"
|
||||
- "void set_LengthBytes(int)"
|
||||
- "int get_LengthBits()"
|
||||
- "void set_LengthBits(int)"
|
||||
- "long get_Position()"
|
||||
- "void set_Position(long)"
|
||||
- "int get_PositionInBytes()"
|
||||
- "byte[] PeekDataBuffer()"
|
||||
- "bool PeekBoolean()"
|
||||
- "byte PeekByte()"
|
||||
- "sbyte PeekSByte()"
|
||||
- "byte PeekByte(int)"
|
||||
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
|
||||
- "byte[] PeekBytes(int)"
|
||||
- "void PeekBytes(byte[], int, int)"
|
||||
- "short PeekInt16()"
|
||||
- "ushort PeekUInt16()"
|
||||
- "int PeekInt32()"
|
||||
- "int PeekInt32(int)"
|
||||
- "uint PeekUInt32()"
|
||||
- "uint PeekUInt32(int)"
|
||||
- "ulong PeekUInt64()"
|
||||
- "long PeekInt64()"
|
||||
- "ulong PeekUInt64(int)"
|
||||
- "long PeekInt64(int)"
|
||||
- "float PeekFloat()"
|
||||
- "System.Half PeekHalf()"
|
||||
- "float PeekSingle()"
|
||||
- "double PeekDouble()"
|
||||
- "string PeekString()"
|
||||
- "int PeekStringSize()"
|
||||
- "bool ReadBoolean()"
|
||||
- "byte ReadByte()"
|
||||
- "bool ReadByte(ref byte)"
|
||||
- "sbyte ReadSByte()"
|
||||
- "byte ReadByte(int)"
|
||||
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
|
||||
- "byte[] ReadBytes(int)"
|
||||
- "bool ReadBytes(int, ref byte[])"
|
||||
- "bool TryReadBytes(System.Span`1<byte>)"
|
||||
- "void ReadBytes(byte[], int, int)"
|
||||
- "void ReadBits(System.Span`1<byte>, int)"
|
||||
- "void ReadBits(byte[], int, int)"
|
||||
- "short ReadInt16()"
|
||||
- "ushort ReadUInt16()"
|
||||
- "int ReadInt32()"
|
||||
- "bool ReadInt32(ref int)"
|
||||
- "int ReadInt32(int)"
|
||||
- "uint ReadUInt32()"
|
||||
- "bool ReadUInt32(ref uint)"
|
||||
- "uint ReadUInt32(int)"
|
||||
- "ulong ReadUInt64()"
|
||||
- "long ReadInt64()"
|
||||
- "ulong ReadUInt64(int)"
|
||||
- "long ReadInt64(int)"
|
||||
- "float ReadFloat()"
|
||||
- "System.Half ReadHalf()"
|
||||
- "float ReadSingle()"
|
||||
- "bool ReadSingle(ref float)"
|
||||
- "double ReadDouble()"
|
||||
- "uint ReadVariableUInt32()"
|
||||
- "bool ReadVariableUInt32(ref uint)"
|
||||
- "int ReadVariableInt32()"
|
||||
- "long ReadVariableInt64()"
|
||||
- "ulong ReadVariableUInt64()"
|
||||
- "float ReadSignedSingle(int)"
|
||||
- "float ReadUnitSingle(int)"
|
||||
- "float ReadRangedSingle(float, float, int)"
|
||||
- "int ReadRangedInteger(int, int)"
|
||||
- "long ReadRangedInteger(long, long)"
|
||||
- "string ReadString()"
|
||||
- "bool ReadString(ref string)"
|
||||
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
|
||||
- "System.Net.IPEndPoint ReadIPEndPoint()"
|
||||
- "void SkipPadBits()"
|
||||
- "void ReadPadBits()"
|
||||
- "void SkipPadBits(int)"
|
||||
- "void EnsureBufferSize(int)"
|
||||
- "void Write(bool)"
|
||||
- "void Write(byte)"
|
||||
- "void WriteAt(int, byte)"
|
||||
- "void Write(sbyte)"
|
||||
- "void Write(byte, int)"
|
||||
- "void Write(byte[])"
|
||||
- "void Write(System.ReadOnlySpan`1<byte>)"
|
||||
- "void Write(byte[], int, int)"
|
||||
- "void Write(ushort)"
|
||||
- "void WriteAt(int, ushort)"
|
||||
- "void Write(ushort, int)"
|
||||
- "void Write(short)"
|
||||
- "void WriteAt(int, short)"
|
||||
- "void Write(int)"
|
||||
- "void WriteAt(int, int)"
|
||||
- "void Write(uint)"
|
||||
- "void WriteAt(int, uint)"
|
||||
- "void Write(uint, int)"
|
||||
- "void Write(int, int)"
|
||||
- "void Write(ulong)"
|
||||
- "void WriteAt(int, ulong)"
|
||||
- "void Write(ulong, int)"
|
||||
- "void Write(long)"
|
||||
- "void Write(long, int)"
|
||||
- "void Write(System.Half)"
|
||||
- "void Write(float)"
|
||||
- "void Write(double)"
|
||||
- "int WriteVariableUInt32(uint)"
|
||||
- "int WriteVariableInt32(int)"
|
||||
- "int WriteVariableInt64(long)"
|
||||
- "int WriteVariableUInt64(ulong)"
|
||||
- "void WriteSignedSingle(float, int)"
|
||||
- "void WriteUnitSingle(float, int)"
|
||||
- "void WriteRangedSingle(float, float, float, int)"
|
||||
- "int WriteRangedInteger(int, int, int)"
|
||||
- "int WriteRangedInteger(long, long, long)"
|
||||
- "void Write(string)"
|
||||
- "void Write(System.Net.IPEndPoint)"
|
||||
- "void WriteTime(bool)"
|
||||
- "void WriteTime(double, bool)"
|
||||
- "void WritePadBits()"
|
||||
- "void WritePadBits(int)"
|
||||
- "void Write(Lidgren.Network.NetBuffer)"
|
||||
- "void Zero(int)"
|
||||
- "void .ctor()"
|
||||
NetDeliveryMethod: { }
|
||||
NetIncomingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
|
||||
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
|
||||
- "int get_SequenceChannel()"
|
||||
- "System.Net.IPEndPoint get_SenderEndPoint()"
|
||||
- "Lidgren.Network.NetConnection get_SenderConnection()"
|
||||
- "double get_ReceiveTime()"
|
||||
- "double ReadTime(bool)"
|
||||
- "string ToString()"
|
||||
NetOutgoingMessage:
|
||||
All: True
|
||||
Methods:
|
||||
- "string ToString()"
|
||||
Nett:
|
||||
CommentLocation: { } # Enum
|
||||
Toml:
|
||||
@@ -442,7 +576,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:
|
||||
@@ -770,7 +911,7 @@ Types:
|
||||
Array:
|
||||
Methods:
|
||||
- "!!0 Find<>(!!0[], System.Predicate`1<!!0>)"
|
||||
- "!!0 Resize<>(!!0[], int)"
|
||||
- "void Resize<>(ref !!0[], int)"
|
||||
- "!!1 ConvertAll<,>(!!0[], System.Converter`2<!!0, !!1>)"
|
||||
- "!!0[] Empty<>()"
|
||||
- "!!0[] FindAll<>(!!0[], System.Predicate`1<!!0>)"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
|
||||
path = path.Directory;
|
||||
|
||||
var fullPath = GetFullPath(path);
|
||||
Process.Start(new ProcessStartInfo
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
UseShellExecute = true,
|
||||
FileName = fullPath,
|
||||
});
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = $"{Environment.GetEnvironmentVariable("SystemRoot")}\\explorer.exe",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
|
||||
{
|
||||
Process.Start(new ProcessStartInfo
|
||||
{
|
||||
FileName = "xdg-open",
|
||||
Arguments = ".",
|
||||
WorkingDirectory = fullPath,
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException("Opening OS windows not supported on this OS");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,50 @@ public sealed partial class PrototypeLayerData
|
||||
[DataField("map")] public HashSet<string>? MapKeys;
|
||||
[DataField("renderingStrategy")] public LayerRenderingStrategy? RenderingStrategy;
|
||||
|
||||
/// <summary>
|
||||
/// If set, indicates that this sprite layer should instead be used to copy into shader parameters on another layer.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// If set, this sprite layer is not rendered. Instead, the "result" of rendering it (exact sprite layer and such)
|
||||
/// are copied into the shader parameters of another object,
|
||||
/// specified by the <see cref="PrototypeCopyToShaderParameters"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The specified layer must have a shader set. When it does, the shader's
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Note that sprite layers are processed in-order, so to avoid 1-frame delays,
|
||||
/// the layer doing the copying should occur BEFORE the layer being copied into.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[DataField] public PrototypeCopyToShaderParameters? CopyToShaderParameters;
|
||||
|
||||
[DataField] public bool Cycle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stores parameters for <see cref="PrototypeLayerData.CopyToShaderParameters"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable, DataDefinition]
|
||||
public sealed partial class PrototypeCopyToShaderParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The map key of the layer that will have its shader modified.
|
||||
/// </summary>
|
||||
[DataField(required: true)] public string LayerKey;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the shader parameter that will receive the actual selected texture.
|
||||
/// </summary>
|
||||
[DataField] public string? ParameterTexture;
|
||||
|
||||
/// <summary>
|
||||
/// The name of the shader parameter that will receive UVs to select the sprite in <see cref="ParameterTexture"/>.
|
||||
/// </summary>
|
||||
[DataField] public string? ParameterUV;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum LayerRenderingStrategy
|
||||
{
|
||||
|
||||
@@ -157,9 +157,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
var moveEvent = new MoveEvent((Owner, this, meta), Coordinates, Coordinates, oldRotation, _localRotation, _gameTiming.ApplyingState);
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
|
||||
_entMan.System<SharedTransformSystem>().InvokeGlobalMoveEvent(ref moveEvent);
|
||||
_entMan.System<SharedTransformSystem>().RaiseMoveEvent((Owner, this, meta), _parent, _localPosition, oldRotation, MapUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,7 +332,9 @@ namespace Robust.Shared.GameObjects
|
||||
if (_localPosition.EqualsApprox(value))
|
||||
return;
|
||||
|
||||
var oldGridPos = Coordinates;
|
||||
var oldParent = _parent;
|
||||
var oldPos = _localPosition;
|
||||
|
||||
_localPosition = value;
|
||||
var meta = _entMan.GetComponent<MetaDataComponent>(Owner);
|
||||
_entMan.Dirty(Owner, this, meta);
|
||||
@@ -343,9 +343,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (!Initialized)
|
||||
return;
|
||||
|
||||
var moveEvent = new MoveEvent((Owner, this, meta), oldGridPos, Coordinates, _localRotation, _localRotation, _gameTiming.ApplyingState);
|
||||
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
|
||||
_entMan.System<SharedTransformSystem>().InvokeGlobalMoveEvent(ref moveEvent);
|
||||
_entMan.System<SharedTransformSystem>().RaiseMoveEvent((Owner, this, meta), oldParent, oldPos, _localRotation, MapUid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -602,8 +600,12 @@ namespace Robust.Shared.GameObjects
|
||||
/// move events, subscribe to the <see cref="SharedTransformSystem.OnGlobalMoveEvent"/>.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public readonly struct MoveEvent(Entity<TransformComponent, MetaDataComponent> entity, EntityCoordinates oldPos,
|
||||
EntityCoordinates newPos, Angle oldRotation, Angle newRotation, bool stateHandling = false)
|
||||
public readonly struct MoveEvent(
|
||||
Entity<TransformComponent, MetaDataComponent> entity,
|
||||
EntityCoordinates oldPos,
|
||||
EntityCoordinates newPos,
|
||||
Angle oldRotation,
|
||||
Angle newRotation)
|
||||
{
|
||||
public readonly Entity<TransformComponent, MetaDataComponent> Entity = entity;
|
||||
public readonly EntityCoordinates OldPosition = oldPos;
|
||||
@@ -615,15 +617,6 @@ namespace Robust.Shared.GameObjects
|
||||
public TransformComponent Component => Entity.Comp1;
|
||||
|
||||
public bool ParentChanged => NewPosition.EntityId != OldPosition.EntityId;
|
||||
|
||||
[Obsolete("Check IGameTiming.ApplyingState")]
|
||||
public readonly bool FromStateHandling = stateHandling;
|
||||
|
||||
[Obsolete]
|
||||
public MoveEvent(EntityUid uid, EntityCoordinates oldPos, EntityCoordinates newPos, Angle oldRot, Angle newRot, TransformComponent xform, bool state)
|
||||
: this((uid, xform, default!), oldPos, newPos, oldRot, newRot)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public struct TransformChildrenEnumerator : IDisposable
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// The last received state object sent from the server.
|
||||
/// </summary>
|
||||
protected BoundUserInterfaceState? State { get; private set; }
|
||||
protected internal BoundUserInterfaceState? State { get; internal set; }
|
||||
|
||||
protected BoundUserInterface(EntityUid owner, Enum uiKey)
|
||||
{
|
||||
@@ -41,14 +41,14 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Invoked when the server uses <c>SetState</c>.
|
||||
/// </summary>
|
||||
protected virtual void UpdateState(BoundUserInterfaceState state)
|
||||
protected internal virtual void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the server sends an arbitrary message.
|
||||
/// </summary>
|
||||
protected virtual void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
protected internal virtual void ReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void Close()
|
||||
{
|
||||
UiSystem.TryCloseUi(_playerManager.LocalSession, Owner, UiKey);
|
||||
UiSystem.CloseUi(Owner, UiKey, _playerManager.LocalEntity, predicted: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -65,7 +65,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public void SendMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
UiSystem.SendUiMessage(this, message);
|
||||
UiSystem.ClientSendUiMessage(Owner, UiKey, message);
|
||||
}
|
||||
|
||||
public void SendPredictedMessage(BoundUserInterfaceMessage message)
|
||||
@@ -73,20 +73,6 @@ namespace Robust.Shared.GameObjects
|
||||
UiSystem.SendPredictedUiMessage(this, message);
|
||||
}
|
||||
|
||||
internal void InternalReceiveMessage(BoundUserInterfaceMessage message)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case UpdateBoundStateMessage updateBoundStateMessage:
|
||||
State = updateBoundStateMessage.State;
|
||||
UpdateState(State);
|
||||
break;
|
||||
default:
|
||||
ReceiveMessage(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
~BoundUserInterface()
|
||||
{
|
||||
Dispose(false);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Robust.Server.GameObjects;
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Lets any entities with this component ignore user interface range checks that would normally
|
||||
/// close the UI automatically.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class IgnoreUIRangeComponent : Component
|
||||
{
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an entity-bound interface that can be opened by multiple players at once.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public sealed class PlayerBoundUserInterface
|
||||
{
|
||||
[ViewVariables]
|
||||
public float InteractionRange;
|
||||
|
||||
[ViewVariables]
|
||||
public float InteractionRangeSqrd => InteractionRange * InteractionRange;
|
||||
|
||||
[ViewVariables]
|
||||
public Enum UiKey { get; }
|
||||
[ViewVariables]
|
||||
public EntityUid Owner { get; }
|
||||
|
||||
internal readonly HashSet<ICommonSession> _subscribedSessions = new();
|
||||
[ViewVariables]
|
||||
internal BoundUIWrapMessage? LastStateMsg;
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool RequireInputValidation;
|
||||
|
||||
[ViewVariables]
|
||||
internal bool StateDirty;
|
||||
|
||||
[ViewVariables]
|
||||
internal readonly Dictionary<ICommonSession, BoundUIWrapMessage> PlayerStateOverrides =
|
||||
new();
|
||||
|
||||
/// <summary>
|
||||
/// All of the sessions currently subscribed to this UserInterface.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IReadOnlySet<ICommonSession> SubscribedSessions => _subscribedSessions;
|
||||
|
||||
public PlayerBoundUserInterface(PrototypeData data, EntityUid owner)
|
||||
{
|
||||
RequireInputValidation = data.RequireInputValidation;
|
||||
UiKey = data.UiKey;
|
||||
Owner = owner;
|
||||
|
||||
InteractionRange = data.InteractionRange;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class ActiveUserInterfaceComponent : Component
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed partial class ActiveUserInterfaceComponent : Component
|
||||
{
|
||||
[ViewVariables]
|
||||
public HashSet<PlayerBoundUserInterface> Interfaces = new();
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public sealed class ServerBoundUserInterfaceMessage
|
||||
{
|
||||
[ViewVariables]
|
||||
public BoundUserInterfaceMessage Message { get; }
|
||||
[ViewVariables]
|
||||
public ICommonSession Session { get; }
|
||||
[PublicAPI]
|
||||
public sealed class ServerBoundUserInterfaceMessage
|
||||
{
|
||||
[ViewVariables]
|
||||
public BoundUserInterfaceMessage Message { get; }
|
||||
[ViewVariables]
|
||||
public ICommonSession Session { get; }
|
||||
|
||||
public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session)
|
||||
{
|
||||
Message = message;
|
||||
Session = session;
|
||||
}
|
||||
public ServerBoundUserInterfaceMessage(BoundUserInterfaceMessage message, ICommonSession session)
|
||||
{
|
||||
Message = message;
|
||||
Session = session;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,36 +8,51 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
[RegisterComponent, NetworkedComponent, Access(typeof(SharedUserInterfaceSystem))]
|
||||
public sealed partial class UserInterfaceComponent : Component
|
||||
{
|
||||
// TODO: Obviously clean this shit up, I just moved it into shared.
|
||||
/// <summary>
|
||||
/// The currently open interfaces. Used clientside to store the UI.
|
||||
/// </summary>
|
||||
[ViewVariables, Access(Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.ReadWriteExecute)]
|
||||
public readonly Dictionary<Enum, BoundUserInterface> ClientOpenInterfaces = new();
|
||||
|
||||
[ViewVariables] public readonly Dictionary<Enum, BoundUserInterface> OpenInterfaces = new();
|
||||
|
||||
[ViewVariables] public readonly Dictionary<Enum, PlayerBoundUserInterface> Interfaces = new();
|
||||
|
||||
public Dictionary<Enum, PrototypeData> MappedInterfaceData = new();
|
||||
[DataField]
|
||||
internal Dictionary<Enum, InterfaceData> Interfaces = new();
|
||||
|
||||
/// <summary>
|
||||
/// Loaded on Init from serialized data.
|
||||
/// Actors that currently have interfaces open.
|
||||
/// </summary>
|
||||
[DataField("interfaces")] internal List<PrototypeData> InterfaceData = new();
|
||||
[DataField]
|
||||
public Dictionary<Enum, List<EntityUid>> Actors = new();
|
||||
|
||||
/// <summary>
|
||||
/// Legacy data, new BUIs should be using comp states.
|
||||
/// </summary>
|
||||
public Dictionary<Enum, BoundUserInterfaceState> States = new();
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class UserInterfaceComponentState(
|
||||
Dictionary<Enum, List<NetEntity>> actors,
|
||||
Dictionary<Enum, BoundUserInterfaceState> states)
|
||||
: IComponentState
|
||||
{
|
||||
public Dictionary<Enum, List<NetEntity>> Actors = actors;
|
||||
|
||||
public Dictionary<Enum, BoundUserInterfaceState> States = states;
|
||||
}
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class PrototypeData
|
||||
public sealed partial class InterfaceData
|
||||
{
|
||||
[DataField("key", required: true)]
|
||||
public Enum UiKey { get; private set; } = default!;
|
||||
|
||||
[DataField("type", required: true)]
|
||||
public string ClientType { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum range before a BUI auto-closes. A non-positive number means there is no limit.
|
||||
/// </summary>
|
||||
[DataField("range")]
|
||||
[DataField]
|
||||
public float InteractionRange = 2f;
|
||||
|
||||
// TODO BUI move to content?
|
||||
@@ -48,7 +63,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <remarks>
|
||||
/// Avoids requiring each system to individually validate client inputs. However, perhaps some BUIs are supposed to be bypass accessibility checks
|
||||
/// </remarks>
|
||||
[DataField("requireInputValidation")]
|
||||
[DataField]
|
||||
public bool RequireInputValidation = true;
|
||||
}
|
||||
|
||||
@@ -56,18 +71,12 @@ namespace Robust.Shared.GameObjects
|
||||
/// Raised whenever the server receives a BUI message from a client relating to a UI that requires input
|
||||
/// validation.
|
||||
/// </summary>
|
||||
public sealed class BoundUserInterfaceMessageAttempt : CancellableEntityEventArgs
|
||||
public sealed class BoundUserInterfaceMessageAttempt(EntityUid actor, EntityUid target, Enum uiKey)
|
||||
: CancellableEntityEventArgs
|
||||
{
|
||||
public readonly ICommonSession Sender;
|
||||
public readonly EntityUid Target;
|
||||
public readonly Enum UiKey;
|
||||
|
||||
public BoundUserInterfaceMessageAttempt(ICommonSession sender, EntityUid target, Enum uiKey)
|
||||
{
|
||||
Sender = sender;
|
||||
Target = target;
|
||||
UiKey = uiKey;
|
||||
}
|
||||
public readonly EntityUid Actor = actor;
|
||||
public readonly EntityUid Target = target;
|
||||
public readonly Enum UiKey = uiKey;
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
@@ -104,7 +113,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// Only set when the message is raised as a directed event.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
public ICommonSession Session = default!;
|
||||
public EntityUid Actor = default!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -120,17 +129,6 @@ namespace Robust.Shared.GameObjects
|
||||
public NetEntity Entity { get; set; } = NetEntity.Invalid;
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
internal sealed class UpdateBoundStateMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
public readonly BoundUserInterfaceState State;
|
||||
|
||||
public UpdateBoundStateMessage(BoundUserInterfaceState state)
|
||||
{
|
||||
State = state;
|
||||
}
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
internal sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
@@ -142,59 +140,38 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal abstract class BaseBoundUIWrapMessage : EntityEventArgs
|
||||
internal abstract class BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
|
||||
: EntityEventArgs
|
||||
{
|
||||
public readonly NetEntity Entity;
|
||||
public readonly BoundUserInterfaceMessage Message;
|
||||
public readonly Enum UiKey;
|
||||
|
||||
public BaseBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
|
||||
{
|
||||
Message = message;
|
||||
UiKey = uiKey;
|
||||
Entity = entity;
|
||||
}
|
||||
public readonly NetEntity Entity = entity;
|
||||
public readonly BoundUserInterfaceMessage Message = message;
|
||||
public readonly Enum UiKey = uiKey;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper message raised from client to server.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class BoundUIWrapMessage : BaseBoundUIWrapMessage
|
||||
{
|
||||
public BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper message raised from client to server.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class PredictedBoundUIWrapMessage : BaseBoundUIWrapMessage
|
||||
{
|
||||
public PredictedBoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey) : base(entity, message, uiKey)
|
||||
{
|
||||
}
|
||||
}
|
||||
internal sealed class BoundUIWrapMessage(NetEntity entity, BoundUserInterfaceMessage message, Enum uiKey)
|
||||
: BaseBoundUIWrapMessage(entity, message, uiKey);
|
||||
|
||||
public sealed class BoundUIOpenedEvent : BaseLocalBoundUserInterfaceEvent
|
||||
{
|
||||
public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, ICommonSession session)
|
||||
public BoundUIOpenedEvent(Enum uiKey, EntityUid uid, EntityUid actor)
|
||||
{
|
||||
UiKey = uiKey;
|
||||
Entity = uid;
|
||||
Session = session;
|
||||
Actor = actor;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BoundUIClosedEvent : BaseLocalBoundUserInterfaceEvent
|
||||
{
|
||||
public BoundUIClosedEvent(Enum uiKey, EntityUid uid, ICommonSession session)
|
||||
public BoundUIClosedEvent(Enum uiKey, EntityUid uid, EntityUid actor)
|
||||
{
|
||||
UiKey = uiKey;
|
||||
Entity = uid;
|
||||
Session = session;
|
||||
Actor = actor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
/// <summary>
|
||||
/// Stores data about this entity and what BUIs they have open.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class UserInterfaceUserComponent : Component
|
||||
{
|
||||
public override bool SessionSpecific => true;
|
||||
|
||||
[DataField]
|
||||
public Dictionary<EntityUid, List<Enum>> OpenInterfaces = new();
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class UserInterfaceUserComponentState : IComponentState
|
||||
{
|
||||
public Dictionary<NetEntity, List<Enum>> OpenInterfaces = new();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ namespace Robust.Shared.GameObjects
|
||||
public void InitializeComponents(EntityUid uid, MetaDataComponent? metadata = null)
|
||||
{
|
||||
DebugTools.AssertOwner(uid, metadata);
|
||||
metadata ??= GetComponent<MetaDataComponent>(uid);
|
||||
metadata ??= MetaQuery.GetComponent(uid);
|
||||
DebugTools.Assert(metadata.EntityLifeStage == EntityLifeStage.PreInit);
|
||||
SetLifeStage(metadata, EntityLifeStage.Initializing);
|
||||
|
||||
@@ -158,13 +158,12 @@ namespace Robust.Shared.GameObjects
|
||||
// TODO: please for the love of god remove these initialization order hacks.
|
||||
|
||||
// Init transform first, we always have it.
|
||||
var transform = GetComponent<TransformComponent>(uid);
|
||||
var transform = TransformQuery.GetComponent(uid);
|
||||
if (transform.LifeStage == ComponentLifeStage.Initialized)
|
||||
LifeStartup(transform);
|
||||
|
||||
// Init physics second if it exists.
|
||||
if (TryGetComponent<PhysicsComponent>(uid, out var phys)
|
||||
&& phys.LifeStage == ComponentLifeStage.Initialized)
|
||||
if (_physicsQuery.TryComp(uid, out var phys) && phys.LifeStage == ComponentLifeStage.Initialized)
|
||||
{
|
||||
LifeStartup(phys);
|
||||
}
|
||||
@@ -294,7 +293,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (!uid.IsValid() || !EntityExists(uid))
|
||||
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
|
||||
|
||||
AddComponentInternal(uid, newComponent, false, true);
|
||||
AddComponentInternal(uid, newComponent, false, true, null);
|
||||
|
||||
return new CompInitializeHandle<T>(this, uid, newComponent, reg.Idx);
|
||||
}
|
||||
@@ -302,10 +301,11 @@ namespace Robust.Shared.GameObjects
|
||||
/// <inheritdoc />
|
||||
public void AddComponent<T>(EntityUid uid, T component, bool overwrite = false, MetaDataComponent? metadata = null) where T : IComponent
|
||||
{
|
||||
if (!uid.IsValid() || !EntityExists(uid))
|
||||
if (!MetaQuery.Resolve(uid, ref metadata, false))
|
||||
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
|
||||
|
||||
if (component == null) throw new ArgumentNullException(nameof(component));
|
||||
if (component == null)
|
||||
throw new ArgumentNullException(nameof(component));
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (component.Owner == default)
|
||||
@@ -321,14 +321,17 @@ namespace Robust.Shared.GameObjects
|
||||
AddComponentInternal(uid, component, overwrite, false, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, bool overwrite, bool skipInit, MetaDataComponent? metadata) where T : IComponent
|
||||
{
|
||||
if (!MetaQuery.ResolveInternal(uid, ref metadata, false))
|
||||
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
|
||||
|
||||
// get interface aliases for mapping
|
||||
var reg = _componentFactory.GetRegistration(component);
|
||||
AddComponentInternal(uid, component, reg, overwrite, skipInit, metadata);
|
||||
}
|
||||
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent? metadata = null) where T : IComponent
|
||||
private void AddComponentInternal<T>(EntityUid uid, T component, ComponentRegistration reg, bool overwrite, bool skipInit, MetaDataComponent metadata) where T : IComponent
|
||||
{
|
||||
// We can't use typeof(T) here in case T is just Component
|
||||
DebugTools.Assert(component is MetaDataComponent ||
|
||||
@@ -642,13 +645,14 @@ namespace Robust.Shared.GameObjects
|
||||
_runtimeLog.LogException(e, nameof(CullRemovedComponents));
|
||||
}
|
||||
#endif
|
||||
DeleteComponent(uid, component, false);
|
||||
var meta = MetaQuery.GetComponent(uid);
|
||||
DeleteComponent(uid, component, false, meta);
|
||||
}
|
||||
|
||||
_deleteSet.Clear();
|
||||
}
|
||||
|
||||
private void DeleteComponent(EntityUid entityUid, IComponent component, bool terminating, MetaDataComponent? metadata = null)
|
||||
private void DeleteComponent(EntityUid entityUid, IComponent component, bool terminating, MetaDataComponent? metadata)
|
||||
{
|
||||
if (!MetaQuery.ResolveInternal(entityUid, ref metadata))
|
||||
return;
|
||||
@@ -1519,7 +1523,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public bool TryComp(EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
|
||||
public bool TryComp([NotNullWhen(true)] EntityUid? uid, [NotNullWhen(true)] out TComp1? component)
|
||||
=> TryGetComponent(uid, out component);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -1582,6 +1586,13 @@ namespace Robust.Shared.GameObjects
|
||||
return default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public TComp1 Comp(EntityUid uid)
|
||||
{
|
||||
return GetComponent(uid);
|
||||
}
|
||||
|
||||
#region Internal
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,9 +11,11 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Network;
|
||||
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;
|
||||
@@ -39,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....
|
||||
@@ -47,6 +50,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public EntityQuery<MetaDataComponent> MetaQuery;
|
||||
public EntityQuery<TransformComponent> TransformQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<ActorComponent> _actorQuery;
|
||||
|
||||
#endregion Dependencies
|
||||
@@ -123,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));
|
||||
@@ -210,6 +214,7 @@ namespace Robust.Shared.GameObjects
|
||||
_containers = System<SharedContainerSystem>();
|
||||
MetaQuery = GetEntityQuery<MetaDataComponent>();
|
||||
TransformQuery = GetEntityQuery<TransformComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
_actorQuery = GetEntityQuery<ActorComponent>();
|
||||
}
|
||||
|
||||
@@ -278,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);
|
||||
@@ -551,17 +557,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// Detach the base entity to null before iterating over children
|
||||
// This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy).
|
||||
if (transform.ParentUid != EntityUid.Invalid)
|
||||
{
|
||||
try
|
||||
{
|
||||
_xforms.DetachParentToNull((uid, transform, metadata), parentXform, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_sawmill.Error($"Caught exception while trying to detach parent of entity '{ToPrettyString(uid, metadata)}' to null.\n{e}");
|
||||
}
|
||||
}
|
||||
_xforms.DetachEntity(uid, transform, metadata, parentXform, true);
|
||||
|
||||
foreach (var child in transform._children)
|
||||
{
|
||||
@@ -776,7 +772,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <summary>
|
||||
/// Allocates an entity and loads components but does not do initialization.
|
||||
/// </summary>
|
||||
private protected virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
internal virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
||||
{
|
||||
if (prototypeName == null)
|
||||
return AllocEntity(out metadata);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,26 +19,26 @@ namespace Robust.Shared.GameObjects
|
||||
public EntityUid? OldParent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The map Id that the entity was on before its parent changed.
|
||||
/// The map that the entity was on before its parent changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the old parent was detached to null without manually updating the map ID of its children, then this
|
||||
/// is required as we cannot simply use the old parent's map ID. Also avoids having to fetch the old
|
||||
/// parent's transform component.
|
||||
/// </remarks>
|
||||
public MapId OldMapId { get; }
|
||||
public readonly EntityUid? OldMapId;
|
||||
|
||||
public TransformComponent Transform { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="EntParentChangedMessage"/>.
|
||||
/// </summary>
|
||||
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent, MapId oldMapId, TransformComponent xform)
|
||||
public EntParentChangedMessage(EntityUid entity, EntityUid? oldParent, EntityUid? oldMapId, TransformComponent xform)
|
||||
{
|
||||
Entity = entity;
|
||||
OldParent = oldParent;
|
||||
OldMapId = oldMapId;
|
||||
Transform = xform;
|
||||
OldMapId = oldMapId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -125,7 +125,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
|
||||
SubscribeLocalEvent<MapChangedEvent>(OnMapChange);
|
||||
|
||||
_transform.OnGlobalMoveEvent += OnMove;
|
||||
_transform.OnBeforeMoveEvent += OnMove;
|
||||
EntityManager.EntityInitialized += OnEntityInit;
|
||||
|
||||
SubscribeLocalEvent<TransformComponent, PhysicsBodyTypeChangedEvent>(OnBodyTypeChange);
|
||||
@@ -142,7 +142,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
|
||||
{
|
||||
base.Shutdown();
|
||||
EntityManager.EntityInitialized -= OnEntityInit;
|
||||
_transform.OnGlobalMoveEvent -= OnMove;
|
||||
_transform.OnBeforeMoveEvent -= OnMove;
|
||||
}
|
||||
|
||||
#region DynamicTree
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -26,7 +26,6 @@ internal sealed class SharedGridTraversalSystem : EntitySystem
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<TransformStartupEvent>(OnStartup);
|
||||
_transform.OnGlobalMoveEvent += OnMove;
|
||||
}
|
||||
|
||||
private void OnStartup(ref TransformStartupEvent ev)
|
||||
@@ -34,17 +33,6 @@ internal sealed class SharedGridTraversalSystem : EntitySystem
|
||||
CheckTraverse(ev.Entity.Owner, ev.Entity.Comp);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
_transform.OnGlobalMoveEvent -= OnMove;
|
||||
}
|
||||
|
||||
private void OnMove(ref MoveEvent moveEv)
|
||||
{
|
||||
CheckTraverse(moveEv.Sender, moveEv.Component);
|
||||
}
|
||||
|
||||
|
||||
internal void CheckTraverse(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
if (!Enabled || _timing.ApplyingState)
|
||||
|
||||
@@ -145,6 +145,11 @@ namespace Robust.Shared.GameObjects
|
||||
ChunkIndex = chunkIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Was the tile previously empty or is it now empty.
|
||||
/// </summary>
|
||||
public bool EmptyChanged => OldTile.IsEmpty != NewTile.Tile.IsEmpty;
|
||||
|
||||
/// <summary>
|
||||
/// EntityUid of the grid with the tile-change. TileRef stores the GridId.
|
||||
/// </summary>
|
||||
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Map.Components;
|
||||
@@ -43,21 +44,13 @@ public abstract partial class SharedTransformSystem
|
||||
xform._anchored = true;
|
||||
var oldPos = xform._localPosition;
|
||||
var oldRot = xform._localRotation;
|
||||
var oldMap = xform.MapUid;
|
||||
xform._localPosition = tilePos + newGrid.TileSizeHalfVector;
|
||||
xform._localRotation += rotation;
|
||||
|
||||
SetGridId(uid, xform, newGridUid, XformQuery);
|
||||
var reParent = new EntParentChangedMessage(uid, oldGridUid, xform.MapID, xform);
|
||||
RaiseLocalEvent(uid, ref reParent, true);
|
||||
var meta = MetaData(uid);
|
||||
var movEevee = new MoveEvent((uid, xform, meta),
|
||||
new EntityCoordinates(oldGridUid, oldPos),
|
||||
new EntityCoordinates(newGridUid, xform._localPosition),
|
||||
oldRot,
|
||||
xform.LocalRotation,
|
||||
_gameTiming.ApplyingState);
|
||||
RaiseLocalEvent(uid, ref movEevee);
|
||||
InvokeGlobalMoveEvent(ref movEevee);
|
||||
RaiseMoveEvent((uid, xform, meta), oldGridUid, oldPos, oldRot, oldMap);
|
||||
|
||||
DebugTools.Assert(XformQuery.GetComponent(oldGridUid).MapID == XformQuery.GetComponent(newGridUid).MapID);
|
||||
DebugTools.Assert(xform._anchored);
|
||||
@@ -321,7 +314,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
// I hate this too. Once again, required for shit like containers because they CBF doing their own init logic
|
||||
// and rely on parent changed messages instead. Might also be used by broadphase stuff?
|
||||
var parentEv = new EntParentChangedMessage(uid, null, MapId.Nullspace, xform);
|
||||
var parentEv = new EntParentChangedMessage(uid, null, null, xform);
|
||||
RaiseLocalEvent(uid, ref parentEv, true);
|
||||
|
||||
var ev = new TransformStartupEvent((uid, xform));
|
||||
@@ -449,9 +442,6 @@ public abstract partial class SharedTransformSystem
|
||||
return;
|
||||
}
|
||||
|
||||
var oldPosition = xform._parent.IsValid() ? new EntityCoordinates(xform._parent, xform._localPosition) : default;
|
||||
var oldRotation = xform._localRotation;
|
||||
|
||||
if (xform.Anchored && unanchor)
|
||||
Unanchor(uid, xform);
|
||||
|
||||
@@ -470,6 +460,11 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
}
|
||||
|
||||
var oldParentUid = xform._parent;
|
||||
var oldPosition = xform._localPosition;
|
||||
var oldRotation = xform._localRotation;
|
||||
var oldMap = xform.MapUid;
|
||||
|
||||
// Set new values
|
||||
Dirty(uid, xform, meta);
|
||||
xform.MatricesDirty = true;
|
||||
@@ -485,7 +480,7 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (value.EntityId == uid)
|
||||
{
|
||||
DetachParentToNull(uid, xform);
|
||||
DetachEntity(uid, xform);
|
||||
if (_netMan.IsServer || IsClientSide(uid))
|
||||
QueueDel(uid);
|
||||
throw new InvalidOperationException($"Attempted to parent an entity to itself: {ToPrettyString(uid)}");
|
||||
@@ -495,7 +490,7 @@ public abstract partial class SharedTransformSystem
|
||||
{
|
||||
if (!XformQuery.Resolve(value.EntityId, ref newParent, false))
|
||||
{
|
||||
DetachParentToNull(uid, xform);
|
||||
DetachEntity(uid, xform);
|
||||
if (_netMan.IsServer || IsClientSide(uid))
|
||||
QueueDel(uid);
|
||||
throw new InvalidOperationException($"Attempted to parent entity {ToPrettyString(uid)} to non-existent entity {value.EntityId}");
|
||||
@@ -503,7 +498,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
if (newParent.LifeStage >= ComponentLifeStage.Stopping || LifeStage(value.EntityId) >= EntityLifeStage.Terminating)
|
||||
{
|
||||
DetachParentToNull(uid, xform);
|
||||
DetachEntity(uid, xform);
|
||||
if (_netMan.IsServer || IsClientSide(uid))
|
||||
QueueDel(uid);
|
||||
throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}");
|
||||
@@ -528,7 +523,7 @@ public abstract partial class SharedTransformSystem
|
||||
// Even though its temporary, this can still cause the client to get stuck in infinite loops while applying the game state.
|
||||
// So we will just break the loop by detaching to null and just trusting that the loop wasn't actually a real feature of the server state.
|
||||
Log.Warning($"Encountered circular transform hierarchy while applying state for entity: {ToPrettyString(uid)}. Detaching child to null: {ToPrettyString(recursiveUid)}");
|
||||
DetachParentToNull(recursiveUid, recursiveXform);
|
||||
DetachEntity(recursiveUid, recursiveXform);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -545,7 +540,6 @@ public abstract partial class SharedTransformSystem
|
||||
newParent?._children.Add(uid);
|
||||
|
||||
xform._parent = value.EntityId;
|
||||
var oldMapId = xform.MapID;
|
||||
|
||||
if (newParent != null)
|
||||
{
|
||||
@@ -576,24 +570,18 @@ public abstract partial class SharedTransformSystem
|
||||
xform._localRotation += GetWorldRotation(oldParent) - GetWorldRotation(newParent);
|
||||
|
||||
DebugTools.Assert(!xform.NoLocalRotation || xform.LocalRotation == 0);
|
||||
|
||||
var entParentChangedMessage = new EntParentChangedMessage(uid, oldParent?.Owner, oldMapId, xform);
|
||||
RaiseLocalEvent(uid, ref entParentChangedMessage, true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!xform.Initialized)
|
||||
return;
|
||||
|
||||
var newPosition = xform._parent.IsValid() ? new EntityCoordinates(xform._parent, xform._localPosition) : default;
|
||||
#if DEBUG
|
||||
// If an entity is parented to the map, its grid uid should be null (unless it is itself a grid or we have a map-grid)
|
||||
if (xform.ParentUid == xform.MapUid)
|
||||
DebugTools.Assert(xform.GridUid == null || xform.GridUid == uid || xform.GridUid == xform.MapUid);
|
||||
#endif
|
||||
var moveEvent = new MoveEvent((uid, xform, meta), oldPosition, newPosition, oldRotation, xform._localRotation, _gameTiming.ApplyingState);
|
||||
RaiseLocalEvent(uid, ref moveEvent);
|
||||
InvokeGlobalMoveEvent(ref moveEvent);
|
||||
RaiseMoveEvent(entity, oldParentUid, oldPosition, oldRotation, oldMap);
|
||||
}
|
||||
|
||||
public void SetCoordinates(
|
||||
@@ -668,13 +656,13 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public void SetParent(EntityUid uid, TransformComponent xform, EntityUid parent, EntityQuery<TransformComponent> xformQuery, TransformComponent? parentXform = null)
|
||||
{
|
||||
DebugTools.Assert(uid == xform.Owner);
|
||||
DebugTools.AssertOwner(uid, xform);
|
||||
if (xform.ParentUid == parent)
|
||||
return;
|
||||
|
||||
if (!parent.IsValid())
|
||||
{
|
||||
DetachParentToNull(uid, xform);
|
||||
DetachEntity(uid, xform);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -976,7 +964,7 @@ public abstract partial class SharedTransformSystem
|
||||
// Entity was not actually in the transform hierarchy. This is probably a sign that something is wrong, or that the function is being misused.
|
||||
Log.Warning($"Target entity ({ToPrettyString(relative)}) not in transform hierarchy while calling {nameof(GetRelativePositionRotation)}.");
|
||||
var relXform = query.GetComponent(relative);
|
||||
pos = relXform.InvWorldMatrix.Transform(pos);
|
||||
pos = GetInvWorldMatrix(relXform).Transform(pos);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -990,7 +978,6 @@ public abstract partial class SharedTransformSystem
|
||||
SetWorldPosition(xform, worldPos);
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetWorldPosition(TransformComponent component, Vector2 worldPos)
|
||||
{
|
||||
@@ -1048,6 +1035,16 @@ public abstract partial class SharedTransformSystem
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public void SetWorldRotationNoLerp(Entity<TransformComponent?> entity, Angle angle)
|
||||
{
|
||||
if (!XformQuery.Resolve(entity.Owner, ref entity.Comp))
|
||||
return;
|
||||
|
||||
var current = GetWorldRotation(entity.Comp);
|
||||
var diff = angle - current;
|
||||
SetLocalRotationNoLerp(entity, entity.Comp.LocalRotation + diff);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetWorldRotation(EntityUid uid, Angle angle)
|
||||
{
|
||||
@@ -1143,7 +1140,8 @@ public abstract partial class SharedTransformSystem
|
||||
if (xform._localPosition.EqualsApprox(pos) && xform.LocalRotation.EqualsApprox(rot))
|
||||
return;
|
||||
|
||||
var oldPosition = xform.Coordinates;
|
||||
var oldParent = xform._parent;
|
||||
var oldPosition = xform._localPosition;
|
||||
var oldRotation = xform.LocalRotation;
|
||||
|
||||
if (!xform.Anchored)
|
||||
@@ -1161,9 +1159,7 @@ public abstract partial class SharedTransformSystem
|
||||
if (!xform.Initialized)
|
||||
return;
|
||||
|
||||
var moveEvent = new MoveEvent((uid, xform, meta), oldPosition, xform.Coordinates, oldRotation, rot, _gameTiming.ApplyingState);
|
||||
RaiseLocalEvent(uid, ref moveEvent);
|
||||
InvokeGlobalMoveEvent(ref moveEvent);
|
||||
RaiseMoveEvent((uid, xform, meta), oldParent, oldPosition, oldRotation, xform.MapUid);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1326,7 +1322,7 @@ public abstract partial class SharedTransformSystem
|
||||
if (!_mapManager.IsMap(uid))
|
||||
Log.Warning($"Failed to attach entity to map or grid. Entity: ({ToPrettyString(uid)}). Trace: {Environment.StackTrace}");
|
||||
|
||||
DetachParentToNull(uid, xform);
|
||||
DetachEntity(uid, xform);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1362,21 +1358,50 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
#region State Handling
|
||||
|
||||
[Obsolete("Use DetachEntity")]
|
||||
public void DetachParentToNull(EntityUid uid, TransformComponent xform)
|
||||
=> DetachEntity(uid, xform);
|
||||
|
||||
/// <inheritdoc cref="DetachEntityInternal"/>
|
||||
public void DetachEntity(EntityUid uid, TransformComponent xform)
|
||||
{
|
||||
XformQuery.TryGetComponent(xform.ParentUid, out var oldXform);
|
||||
DetachParentToNull(uid, xform, oldXform);
|
||||
DetachEntity(uid, xform, MetaData(uid), oldXform);
|
||||
}
|
||||
|
||||
public void DetachParentToNull(EntityUid uid, TransformComponent xform, TransformComponent? oldXform)
|
||||
/// <inheritdoc cref="DetachEntityInternal"/>
|
||||
public void DetachEntity(
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
MetaDataComponent meta,
|
||||
TransformComponent? oldXform,
|
||||
bool terminating = false)
|
||||
{
|
||||
DetachParentToNull((uid, xform, MetaData(uid)), oldXform);
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
DetachEntityInternal(uid, xform, meta, oldXform, terminating);
|
||||
#else
|
||||
try
|
||||
{
|
||||
DetachEntityInternal(uid, xform, meta, oldXform, terminating);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error($"Caught exception while attempting to detach an entity to nullspace. Entity: {ToPrettyString(uid, meta)}. Exception: {e}");
|
||||
// TODO detach without content event handling.
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void DetachParentToNull(Entity<TransformComponent,MetaDataComponent> entity, TransformComponent? oldXform, bool terminating = false)
|
||||
/// <summary>
|
||||
/// Remove an entity from the transform hierarchy and send it to null space
|
||||
/// </summary>
|
||||
internal void DetachEntityInternal(
|
||||
EntityUid uid,
|
||||
TransformComponent xform,
|
||||
MetaDataComponent meta,
|
||||
TransformComponent? oldXform,
|
||||
bool terminating = false)
|
||||
{
|
||||
var (uid, xform, meta) = entity;
|
||||
|
||||
if (!terminating && meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
||||
{
|
||||
// Something is attempting to remove the entity from this entity's parent while it is in the process of being deleted.
|
||||
@@ -1393,15 +1418,14 @@ public abstract partial class SharedTransformSystem
|
||||
DebugTools.Assert((MetaData(uid).Flags & MetaDataFlags.InContainer) == 0x0,
|
||||
$"Entity is in a container but has no parent? Entity: {ToPrettyString(uid)}");
|
||||
|
||||
if (xform.Broadphase != null)
|
||||
{
|
||||
DebugTools.Assert(
|
||||
xform.Broadphase == BroadphaseData.Invalid
|
||||
|| xform.Broadphase.Value.Uid == uid
|
||||
|| Deleted(xform.Broadphase.Value.Uid)
|
||||
|| Terminating(xform.Broadphase.Value.Uid),
|
||||
$"Entity has no parent but is on some broadphase? Entity: {ToPrettyString(uid)}. Broadphase: {ToPrettyString(xform.Broadphase.Value.Uid)}");
|
||||
}
|
||||
DebugTools.Assert(
|
||||
xform.Broadphase == null
|
||||
|| xform.Broadphase == BroadphaseData.Invalid
|
||||
|| xform.Broadphase.Value.Uid == uid
|
||||
|| Deleted(xform.Broadphase.Value.Uid)
|
||||
|| Terminating(xform.Broadphase.Value.Uid),
|
||||
$"Entity has no parent but is on some broadphase? Entity: {ToPrettyString(uid)}. Broadphase: {ToPrettyString(xform.Broadphase!.Value.Uid)}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1425,7 +1449,7 @@ public abstract partial class SharedTransformSystem
|
||||
RaiseLocalEvent(uid, ref anchorStateChangedEvent, true);
|
||||
}
|
||||
|
||||
SetCoordinates(entity, default, Angle.Zero, oldParent: oldXform);
|
||||
SetCoordinates((uid, xform, meta), default, Angle.Zero, oldParent: oldXform);
|
||||
|
||||
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0x0,
|
||||
$"Entity is in a container after having been detached to null-space? Entity: {ToPrettyString(uid)}");
|
||||
@@ -1460,7 +1484,7 @@ public abstract partial class SharedTransformSystem
|
||||
var targetXform = target.Comp;
|
||||
if (!XformQuery.Resolve(target, ref targetXform) || !targetXform.ParentUid.IsValid())
|
||||
{
|
||||
DetachParentToNull(entity, xform);
|
||||
DetachEntity(entity, xform);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1498,7 +1522,7 @@ public abstract partial class SharedTransformSystem
|
||||
var targetXform = target.Comp;
|
||||
if (!XformQuery.Resolve(target, ref targetXform) || !targetXform.ParentUid.IsValid())
|
||||
{
|
||||
DetachParentToNull(entity, xform);
|
||||
DetachEntity(entity, xform);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1518,4 +1542,90 @@ public abstract partial class SharedTransformSystem
|
||||
PlaceNextTo((entity, xform), targetXform.ParentUid);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swaps the position of two entities, placing them inside of containers when applicable.
|
||||
/// </summary>
|
||||
/// <returns>Returns if the entities can have their positions swapped. Fails if the entities are parented to one another</returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public bool SwapPositions(Entity<TransformComponent?> entity1, Entity<TransformComponent?> entity2)
|
||||
{
|
||||
if (!XformQuery.Resolve(entity1, ref entity1.Comp) || !XformQuery.Resolve(entity2, ref entity2.Comp))
|
||||
return false;
|
||||
|
||||
// save ourselves the hassle and just don't move anything.
|
||||
if (entity1 == entity2)
|
||||
return true;
|
||||
|
||||
// don't parent things to each other by accident
|
||||
if (IsParentOf(entity1.Comp, entity2) || IsParentOf(entity2.Comp, entity1))
|
||||
return false;
|
||||
|
||||
MapCoordinates? pos1 = null;
|
||||
MapCoordinates? pos2 = null;
|
||||
|
||||
if (_container.TryGetContainingContainer(entity1, out var container1))
|
||||
_container.Remove(entity1, container1, force: true);
|
||||
else
|
||||
pos1 = GetMapCoordinates(entity1.Comp);
|
||||
|
||||
if (_container.TryGetContainingContainer(entity2, out var container2))
|
||||
_container.Remove(entity2, container2, force: true);
|
||||
else
|
||||
pos2 = GetMapCoordinates(entity2.Comp);
|
||||
|
||||
// making sure we don't accidentally place something inside of itself
|
||||
if (container1 != null && container1.Owner == entity2.Owner)
|
||||
return false;
|
||||
if (container2 != null && container2.Owner == entity1.Owner)
|
||||
return false;
|
||||
|
||||
if (container2 != null)
|
||||
{
|
||||
_container.Insert(entity1, container2);
|
||||
}
|
||||
else if (pos2 != null)
|
||||
{
|
||||
var mapUid = _mapManager.GetMapEntityId(pos2.Value.MapId);
|
||||
|
||||
if (!_gridQuery.HasComponent(entity1) && _mapManager.TryFindGridAt(mapUid, pos2.Value.Position, out var targetGrid, out _))
|
||||
{
|
||||
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
|
||||
SetCoordinates(entity1, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(pos2.Value.Position)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCoordinates(entity1, new EntityCoordinates(mapUid, pos2.Value.Position));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
if (container1 != null)
|
||||
{
|
||||
_container.Insert(entity2, container1);
|
||||
}
|
||||
else if (pos1 != null)
|
||||
{
|
||||
var mapUid = _mapManager.GetMapEntityId(pos1.Value.MapId);
|
||||
|
||||
if (!_gridQuery.HasComponent(entity1) && _mapManager.TryFindGridAt(mapUid, pos1.Value.Position, out var targetGrid, out _))
|
||||
{
|
||||
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
|
||||
SetCoordinates(entity2, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(pos1.Value.Position)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCoordinates(entity2, new EntityCoordinates(mapUid, pos1.Value.Position));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Robust.Shared.GameObjects
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly INetManager _netMan = default!;
|
||||
[Dependency] private readonly SharedContainerSystem _container = default!;
|
||||
[Dependency] private readonly SharedGridTraversalSystem _traversal = default!;
|
||||
|
||||
private EntityQuery<MapComponent> _mapQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
@@ -40,10 +41,12 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public event MoveEventHandler? OnGlobalMoveEvent;
|
||||
|
||||
public void InvokeGlobalMoveEvent(ref MoveEvent ev)
|
||||
{
|
||||
OnGlobalMoveEvent?.Invoke(ref ev);
|
||||
}
|
||||
/// <summary>
|
||||
/// Internal move event handlers. This gets invoked before the global & directed move events. This is mainly
|
||||
/// for exception tolerance, we want to ensure that PVS, physics & entity lookups get updated before some
|
||||
/// content code throws an exception.
|
||||
/// </summary>
|
||||
internal event MoveEventHandler? OnBeforeMoveEvent;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -104,7 +107,7 @@ namespace Robust.Shared.GameObjects
|
||||
// If a tile is being removed due to an explosion or somesuch, some entities are likely being deleted.
|
||||
// Avoid unnecessary entity updates.
|
||||
if (EntityManager.IsQueuedForDeletion(entity))
|
||||
DetachParentToNull(entity, xform, gridXform);
|
||||
DetachEntity(entity, xform, MetaData(entity), gridXform);
|
||||
else
|
||||
SetParent(entity, xform, gridXform.MapUid.Value, mapTransform);
|
||||
}
|
||||
@@ -255,6 +258,44 @@ namespace Robust.Shared.GameObjects
|
||||
indices = _map.CoordinatesToTile(xform.GridUid.Value, grid, xform.Coordinates);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RaiseMoveEvent(
|
||||
Entity<TransformComponent, MetaDataComponent> ent,
|
||||
EntityUid oldParent,
|
||||
Vector2 oldPosition,
|
||||
Angle oldRotation,
|
||||
EntityUid? oldMap)
|
||||
{
|
||||
var pos = ent.Comp1._parent == EntityUid.Invalid
|
||||
? default
|
||||
: new EntityCoordinates(ent.Comp1._parent, ent.Comp1._localPosition);
|
||||
|
||||
var oldPos = oldParent == EntityUid.Invalid
|
||||
? default
|
||||
: new EntityCoordinates(oldParent, oldPosition);
|
||||
|
||||
var ev = new MoveEvent(ent, oldPos, pos, oldRotation, ent.Comp1._localRotation);
|
||||
|
||||
if (oldParent != ent.Comp1._parent)
|
||||
{
|
||||
_physics.OnParentChange(ent, oldParent, oldMap);
|
||||
OnBeforeMoveEvent?.Invoke(ref ev);
|
||||
var entParentChangedMessage = new EntParentChangedMessage(ev.Sender, oldParent, oldMap, ev.Component);
|
||||
RaiseLocalEvent(ev.Sender, ref entParentChangedMessage, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnBeforeMoveEvent?.Invoke(ref ev);
|
||||
}
|
||||
|
||||
RaiseLocalEvent(ev.Sender, ref ev);
|
||||
OnGlobalMoveEvent?.Invoke(ref ev);
|
||||
|
||||
// Finally, handle grid traversal. This is handled separately to avoid out-of-order move events.
|
||||
// I.e., if the traversal raises its own move event, this ensures that all the old move event handlers
|
||||
// have finished running first. Ideally this shouldn't be required, but this is here just in case
|
||||
_traversal.CheckTraverse(ent.Owner, ent.Comp1);
|
||||
}
|
||||
}
|
||||
|
||||
[ByRefEvent]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
@@ -8,7 +9,7 @@ namespace Robust.Shared.Graphics;
|
||||
/// Flags for loading of textures.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public struct TextureLoadParameters
|
||||
public struct TextureLoadParameters : IEquatable<TextureLoadParameters>
|
||||
{
|
||||
/// <summary>
|
||||
/// The default sampling parameters for the texture.
|
||||
@@ -41,4 +42,29 @@ public struct TextureLoadParameters
|
||||
SampleParameters = TextureSampleParameters.Default,
|
||||
Srgb = true
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(TextureLoadParameters other)
|
||||
{
|
||||
return SampleParameters.Equals(other.SampleParameters) && Srgb == other.Srgb;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TextureLoadParameters other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(SampleParameters, Srgb);
|
||||
}
|
||||
|
||||
public static bool operator ==(TextureLoadParameters left, TextureLoadParameters right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TextureLoadParameters left, TextureLoadParameters right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Robust.Shared.Graphics;
|
||||
/// with different sampling parameters than the base texture.
|
||||
/// </summary>
|
||||
[PublicAPI]
|
||||
public struct TextureSampleParameters
|
||||
public struct TextureSampleParameters : IEquatable<TextureSampleParameters>
|
||||
{
|
||||
// NOTE: If somebody is gonna add support for 3D/1D textures, change this doc comment.
|
||||
// See the note on this page for why: https://www.khronos.org/opengl/wiki/Sampler_Object#Filtering
|
||||
@@ -62,4 +62,29 @@ public struct TextureSampleParameters
|
||||
Filter = false,
|
||||
WrapMode = TextureWrapMode.None
|
||||
};
|
||||
}
|
||||
|
||||
public bool Equals(TextureSampleParameters other)
|
||||
{
|
||||
return Filter == other.Filter && WrapMode == other.WrapMode;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TextureSampleParameters other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Filter, (int) WrapMode);
|
||||
}
|
||||
|
||||
public static bool operator ==(TextureSampleParameters left, TextureSampleParameters right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TextureSampleParameters left, TextureSampleParameters right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -344,6 +344,9 @@ namespace Robust.Shared.Map
|
||||
var mapCoordinates = ToMap(entityManager, transformSystem);
|
||||
var otherMapCoordinates = otherCoordinates.ToMap(entityManager, transformSystem);
|
||||
|
||||
if (mapCoordinates.MapId != otherMapCoordinates.MapId)
|
||||
return false;
|
||||
|
||||
return mapCoordinates.InRange(otherMapCoordinates, range);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace Robust.Shared.Network
|
||||
public bool IsConnected => _connection.Status == NetConnectionStatus.Connected;
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables]
|
||||
public IPEndPoint RemoteEndPoint => _connection.RemoteEndPoint;
|
||||
|
||||
/// <summary>
|
||||
@@ -100,7 +99,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{RemoteEndPoint}/{UserId}";
|
||||
return $"{ConnectionId}/{UserId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Network
|
||||
@@ -10,11 +11,13 @@ namespace Robust.Shared.Network
|
||||
{
|
||||
[ViewVariables]
|
||||
public NetUserId UserId { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public string UserName { get; }
|
||||
|
||||
[ViewVariables]
|
||||
public string? PatronTier { get; init; }
|
||||
[ViewVariables]
|
||||
|
||||
public ImmutableArray<byte> HWId { get; init; }
|
||||
|
||||
public NetUserData(NetUserId userId, string userName)
|
||||
@@ -22,5 +25,18 @@ namespace Robust.Shared.Network
|
||||
UserId = userId;
|
||||
UserName = userName;
|
||||
}
|
||||
|
||||
public sealed override string ToString()
|
||||
{
|
||||
var stringBuilder = new StringBuilder();
|
||||
stringBuilder.Append("NetUserData"); // type name
|
||||
stringBuilder.Append(" { ");
|
||||
if ((this with { HWId = default }).PrintMembers(stringBuilder))
|
||||
{
|
||||
stringBuilder.Append(' ');
|
||||
}
|
||||
stringBuilder.Append('}');
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,9 +181,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
if (other == null) return false;
|
||||
|
||||
// Owner field shouldn't be required, fixtures on other entities shouldn't be getting compared to each other.
|
||||
// This is mainly here because it might've intruded some physics bugs, so this is here just in case.
|
||||
DebugTools.Assert(Owner == other.Owner);
|
||||
return Equivalent(other) && Owner == other.Owner;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -21,6 +21,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private EntityQuery<JointComponent> _jointsQuery;
|
||||
private EntityQuery<PhysicsComponent> _physicsQuery;
|
||||
private EntityQuery<JointRelayTargetComponent> _relayQuery;
|
||||
private EntityQuery<TransformComponent> _xformQuery;
|
||||
|
||||
@@ -37,6 +38,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
_jointsQuery = GetEntityQuery<JointComponent>();
|
||||
_relayQuery = GetEntityQuery<JointRelayTargetComponent>();
|
||||
_xformQuery = GetEntityQuery<TransformComponent>();
|
||||
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
UpdatesOutsidePrediction = true;
|
||||
|
||||
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
|
||||
@@ -136,7 +138,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
var aUid = joint.BodyAUid;
|
||||
var bUid = joint.BodyBUid;
|
||||
|
||||
if (!Resolve(aUid, ref bodyA, false) || !Resolve(bUid, ref bodyB, false))
|
||||
if (!_physicsQuery.Resolve(aUid, ref bodyA, false) || !_physicsQuery.Resolve(bUid, ref bodyB, false))
|
||||
return;
|
||||
|
||||
DebugTools.Assert(Transform(aUid).MapID == Transform(bUid).MapID, "Attempted to initialize cross-map joint");
|
||||
@@ -311,7 +313,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
public WeldJoint GetOrCreateWeldJoint(EntityUid bodyA, EntityUid bodyB, string? id = null)
|
||||
{
|
||||
if (id != null &&
|
||||
EntityManager.TryGetComponent(bodyA, out JointComponent? jointComponent) &&
|
||||
_jointsQuery.TryComp(bodyA, out JointComponent? jointComponent) &&
|
||||
jointComponent.Joints.TryGetValue(id, out var weldJoint))
|
||||
{
|
||||
return (WeldJoint) weldJoint;
|
||||
@@ -404,17 +406,17 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
|
||||
protected void AddJoint(Joint joint, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null)
|
||||
{
|
||||
if (!Resolve(joint.BodyAUid, ref bodyA) || !Resolve(joint.BodyBUid, ref bodyB))
|
||||
if (!_physicsQuery.Resolve(joint.BodyAUid, ref bodyA) || !_physicsQuery.Resolve(joint.BodyBUid, ref bodyB))
|
||||
return;
|
||||
|
||||
if (!joint.CollideConnected)
|
||||
FilterContactsForJoint(joint, bodyA, bodyB);
|
||||
|
||||
// Maybe make this method AddOrUpdate so we can have an Add one that explicitly throws if present?
|
||||
var mapidA = EntityManager.GetComponent<TransformComponent>(joint.BodyAUid).MapID;
|
||||
var mapidA = Transform(joint.BodyAUid).MapID;
|
||||
|
||||
if (mapidA == MapId.Nullspace ||
|
||||
mapidA != EntityManager.GetComponent<TransformComponent>(joint.BodyBUid).MapID)
|
||||
mapidA != Transform(joint.BodyBUid).MapID)
|
||||
{
|
||||
Log.Error($"Tried to add joint to ineligible bodies");
|
||||
return;
|
||||
@@ -447,7 +449,8 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
if (!Resolve(uid, ref xform))
|
||||
return;
|
||||
|
||||
Resolve(uid, ref component, ref relay, false);
|
||||
_jointsQuery.Resolve(uid, ref component, false);
|
||||
_relayQuery.Resolve(uid, ref relay, false);
|
||||
|
||||
if (relay != null)
|
||||
{
|
||||
@@ -471,7 +474,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
/// </summary>
|
||||
public void ClearJoints(EntityUid uid, JointComponent? component = null)
|
||||
{
|
||||
if (!Resolve(uid, ref component, false))
|
||||
if (!_jointsQuery.Resolve(uid, ref component, false))
|
||||
return;
|
||||
|
||||
// TODO PERFORMANCE
|
||||
@@ -497,15 +500,9 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use the other ClearJoints overload")]
|
||||
public void ClearJoints(JointComponent joint)
|
||||
{
|
||||
ClearJoints(joint.Owner, joint);
|
||||
}
|
||||
|
||||
public void RemoveJoint(EntityUid uid, string id)
|
||||
{
|
||||
if (!TryComp<JointComponent>(uid, out var jointComp))
|
||||
if (!_jointsQuery.TryComp(uid, out var jointComp))
|
||||
return;
|
||||
|
||||
if (!jointComp.Joints.TryGetValue(id, out var joint))
|
||||
@@ -522,12 +519,12 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
|
||||
// Originally I logged these but because of prediction the client can just nuke them multiple times in a row
|
||||
// because each body has its own JointComponent, bleh.
|
||||
if (!TryComp<JointComponent>(bodyAUid, out var jointComponentA))
|
||||
if (!_jointsQuery.TryComp(bodyAUid, out var jointComponentA))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TryComp<JointComponent>(bodyBUid, out var jointComponentB))
|
||||
if (!_jointsQuery.TryComp(bodyBUid, out var jointComponentB))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -543,7 +540,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
}
|
||||
|
||||
// Wake up connected bodies.
|
||||
if (EntityManager.TryGetComponent<PhysicsComponent>(bodyAUid, out var bodyA) &&
|
||||
if (_physicsQuery.TryComp(bodyAUid, out var bodyA) &&
|
||||
MetaData(bodyAUid).EntityLifeStage < EntityLifeStage.Terminating)
|
||||
{
|
||||
var uidA = jointComponentA.Relay ?? bodyAUid;
|
||||
@@ -607,7 +604,7 @@ public abstract partial class SharedJointSystem : EntitySystem
|
||||
|
||||
internal void FilterContactsForJoint(Joint joint, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null)
|
||||
{
|
||||
if (!Resolve(joint.BodyBUid, ref bodyB))
|
||||
if (!_physicsQuery.Resolve(joint.BodyBUid, ref bodyB))
|
||||
return;
|
||||
|
||||
var node = bodyB.Contacts.First;
|
||||
|
||||
@@ -115,7 +115,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void ApplyAngularImpulse(EntityUid uid, float impulse, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void ApplyForce(EntityUid uid, Vector2 force, Vector2 point, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -137,7 +137,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void ApplyForce(EntityUid uid, Vector2 force, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void ApplyTorque(EntityUid uid, float torque, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void ApplyLinearImpulse(EntityUid uid, Vector2 impulse, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -169,7 +169,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void ApplyLinearImpulse(EntityUid uid, Vector2 impulse, Vector2 point, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body) || !IsMoveable(body) || !WakeBody(uid, manager: manager, body: body))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -250,7 +250,10 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void ResetMassData(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref manager, ref body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body))
|
||||
return;
|
||||
|
||||
if (!_fixturesQuery.Resolve(uid, ref manager))
|
||||
return;
|
||||
|
||||
body._mass = 0.0f;
|
||||
@@ -315,7 +318,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public bool SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body))
|
||||
return false;
|
||||
|
||||
if (body.BodyType == BodyType.Static)
|
||||
@@ -346,7 +349,7 @@ public partial class SharedPhysicsSystem
|
||||
/// </summary>
|
||||
public bool SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body))
|
||||
return false;
|
||||
|
||||
if (body.BodyType == BodyType.Static)
|
||||
@@ -467,7 +470,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void SetBodyType(EntityUid uid, BodyType value, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body))
|
||||
return;
|
||||
|
||||
if (body.BodyType == value)
|
||||
@@ -531,7 +534,7 @@ public partial class SharedPhysicsSystem
|
||||
FixturesComponent? manager = null,
|
||||
PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body))
|
||||
return false;
|
||||
|
||||
if (body.CanCollide == value)
|
||||
@@ -545,7 +548,7 @@ public partial class SharedPhysicsSystem
|
||||
if (_containerSystem.IsEntityOrParentInContainer(uid))
|
||||
return false;
|
||||
|
||||
if (!Resolve(uid, ref manager) || manager.FixtureCount == 0 && !_mapManager.IsGrid(uid))
|
||||
if (!_fixturesQuery.Resolve(uid, ref manager) || manager.FixtureCount == 0 && !_mapManager.IsGrid(uid))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -575,7 +578,7 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public void SetFixedRotation(EntityUid uid, bool value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body) || body.FixedRotation == value)
|
||||
if (!PhysicsQuery.Resolve(uid, ref body) || body.FixedRotation == value)
|
||||
return;
|
||||
|
||||
body.FixedRotation = value;
|
||||
@@ -668,10 +671,13 @@ public partial class SharedPhysicsSystem
|
||||
/// <returns>true if the body is collidable and awake</returns>
|
||||
public bool WakeBody(EntityUid uid, bool force = false, FixturesComponent? manager = null, PhysicsComponent? body = null)
|
||||
{
|
||||
if (!SetCanCollide(uid, true, manager: manager, body: body, force: force) || !Resolve(uid, ref body))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body))
|
||||
return false;
|
||||
|
||||
SetAwake(uid, body, true);
|
||||
if (!SetCanCollide(uid, true, manager: manager, body: body, force: force))
|
||||
return false;
|
||||
|
||||
SetAwake((uid, body), true);
|
||||
return body.Awake;
|
||||
}
|
||||
|
||||
@@ -715,7 +721,9 @@ public partial class SharedPhysicsSystem
|
||||
|
||||
public Box2 GetHardAABB(EntityUid uid, FixturesComponent? manager = null, PhysicsComponent? body = null, TransformComponent? xform = null)
|
||||
{
|
||||
if (!Resolve(uid, ref body, ref xform, ref manager))
|
||||
if (!PhysicsQuery.Resolve(uid, ref body)
|
||||
|| !_fixturesQuery.Resolve(uid, ref manager)
|
||||
|| !Resolve(uid, ref xform))
|
||||
{
|
||||
return Box2.Empty;
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
return (linearVelocity + linearVelocityAngularContribution, angularVelocity);
|
||||
}
|
||||
|
||||
private void HandleParentChangeVelocity(EntityUid uid, PhysicsComponent physics, ref EntParentChangedMessage args, TransformComponent xform)
|
||||
private void HandleParentChangeVelocity(EntityUid uid, PhysicsComponent physics, EntityUid oldParent, TransformComponent xform)
|
||||
{
|
||||
// If parent changed due to state handling, don't modify velocities. The physics comp state will take care of itself..
|
||||
if (_gameTiming.ApplyingState)
|
||||
@@ -217,15 +217,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
// I guess the question becomes, what do you do with conservation of momentum in that case. I guess its the job
|
||||
// of the teleporter to select a velocity at the after the parent has changed.
|
||||
|
||||
var xformQuery = GetEntityQuery<TransformComponent>();
|
||||
var physicsQuery = GetEntityQuery<PhysicsComponent>();
|
||||
FixturesComponent? manager = null;
|
||||
|
||||
// for the new velocities (that need to be updated), we can just use the existing function:
|
||||
var (newLinear, newAngular) = GetMapVelocities(uid, physics, xform);
|
||||
|
||||
// for the old velocities, we need to re-implement this function while using the old parent and old local position:
|
||||
if (args.OldParent is not { Valid: true } parent)
|
||||
if (oldParent == EntityUid.Invalid)
|
||||
{
|
||||
// no previous parent --> simple
|
||||
// Old velocity + (old velocity - new velocity)
|
||||
@@ -234,7 +232,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
return;
|
||||
}
|
||||
|
||||
TransformComponent? parentXform = xformQuery.GetComponent(parent);
|
||||
var parent = oldParent;
|
||||
TransformComponent? parentXform = _xformQuery.GetComponent(parent);
|
||||
var localPos = _transform.GetInvWorldMatrix(parentXform).Transform(_transform.GetWorldPosition(xform));
|
||||
|
||||
var oldLinear = physics.LinearVelocity;
|
||||
@@ -243,7 +242,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
do
|
||||
{
|
||||
if (physicsQuery.TryGetComponent(parent, out var body))
|
||||
if (PhysicsQuery.TryGetComponent(parent, out var body))
|
||||
{
|
||||
oldAngular += body.AngularVelocity;
|
||||
|
||||
@@ -259,7 +258,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
localPos = parentXform.LocalPosition + parentXform.LocalRotation.RotateVec(localPos);
|
||||
parent = parentXform.ParentUid;
|
||||
|
||||
} while (parent.IsValid() && xformQuery.TryGetComponent(parent, out parentXform));
|
||||
} while (parent.IsValid() && _xformQuery.TryGetComponent(parent, out parentXform));
|
||||
|
||||
oldLinear += linearAngularContribution;
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ namespace Robust.Shared.Physics.Systems
|
||||
SubscribeLocalEvent<GridAddEvent>(OnGridAdd);
|
||||
SubscribeLocalEvent<CollisionChangeEvent>(OnCollisionChange);
|
||||
SubscribeLocalEvent<PhysicsComponent, EntGotRemovedFromContainerMessage>(HandleContainerRemoved);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
|
||||
SubscribeLocalEvent<PhysicsMapComponent, ComponentInit>(HandlePhysicsMapInit);
|
||||
SubscribeLocalEvent<PhysicsComponent, ComponentInit>(OnPhysicsInit);
|
||||
SubscribeLocalEvent<PhysicsComponent, ComponentShutdown>(OnPhysicsShutdown);
|
||||
@@ -150,51 +149,45 @@ namespace Robust.Shared.Physics.Systems
|
||||
_substeps = (int)Math.Ceiling(targetMinTickrate / serverTickrate);
|
||||
}
|
||||
|
||||
private void OnParentChange(ref EntParentChangedMessage args)
|
||||
internal void OnParentChange(Entity<TransformComponent, MetaDataComponent> ent, EntityUid oldParent, EntityUid? oldMap)
|
||||
{
|
||||
// We do not have a directed/body subscription, because the entity changing parents may not have a physics component, but one of its children might.
|
||||
var uid = args.Entity;
|
||||
var xform = args.Transform;
|
||||
var (uid, xform, meta) = ent;
|
||||
|
||||
// If this entity has yet to be initialized, then we can skip this as equivalent code will get run during
|
||||
// init anyways. HOWEVER: it is possible that one of the children of this entity are already post-init, in
|
||||
// which case they still need to handle map changes. This frequently happens when clients receives a server
|
||||
// state where a known/old entity gets attached to a new, previously unknown, entity. The new entity will be
|
||||
// uninitialized but have an initialized child.
|
||||
if (xform.ChildCount == 0 && LifeStage(uid) < EntityLifeStage.Initialized)
|
||||
if (xform.ChildCount == 0 && meta.EntityLifeStage < EntityLifeStage.Initialized)
|
||||
return;
|
||||
|
||||
// Is this entity getting recursively detached after it's parent was already detached to null?
|
||||
if (args.OldMapId == MapId.Nullspace && xform.MapID == MapId.Nullspace)
|
||||
if (oldMap == null && xform.MapUid == null)
|
||||
return;
|
||||
|
||||
var body = CompOrNull<PhysicsComponent>(uid);
|
||||
var body = PhysicsQuery.CompOrNull(uid);
|
||||
|
||||
// Handle map changes
|
||||
if (args.OldMapId != xform.MapID)
|
||||
if (oldMap != xform.MapUid)
|
||||
{
|
||||
// This will also handle broadphase updating & joint clearing.
|
||||
HandleMapChange(uid, xform, body, args.OldMapId, xform.MapID);
|
||||
HandleMapChange(uid, xform, body, oldMap, xform.MapUid);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.OldMapId != xform.MapID)
|
||||
return;
|
||||
|
||||
if (body != null)
|
||||
HandleParentChangeVelocity(uid, body, ref args, xform);
|
||||
HandleParentChangeVelocity(uid, body, oldParent, xform);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recursively add/remove from awake bodies, clear joints, remove from move buffer, and update broadphase.
|
||||
/// </summary>
|
||||
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body, MapId oldMapId, MapId newMapId)
|
||||
private void HandleMapChange(EntityUid uid, TransformComponent xform, PhysicsComponent? body, EntityUid? oldMapId, EntityUid? newMapId)
|
||||
{
|
||||
var jointQuery = GetEntityQuery<JointComponent>();
|
||||
|
||||
PhysMapQuery.TryGetComponent(_mapManager.GetMapEntityId(oldMapId), out var oldMap);
|
||||
PhysMapQuery.TryGetComponent(_mapManager.GetMapEntityId(newMapId), out var newMap);
|
||||
|
||||
RecursiveMapUpdate(uid, xform, body, newMap, oldMap, jointQuery);
|
||||
PhysMapQuery.TryGetComponent(oldMapId, out var oldMap);
|
||||
PhysMapQuery.TryGetComponent(newMapId, out var newMap);
|
||||
RecursiveMapUpdate(uid, xform, body, newMap, oldMap);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -205,8 +198,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
TransformComponent xform,
|
||||
PhysicsComponent? body,
|
||||
PhysicsMapComponent? newMap,
|
||||
PhysicsMapComponent? oldMap,
|
||||
EntityQuery<JointComponent> jointQuery)
|
||||
PhysicsMapComponent? oldMap)
|
||||
{
|
||||
DebugTools.Assert(!Deleted(uid));
|
||||
|
||||
@@ -223,16 +215,14 @@ namespace Robust.Shared.Physics.Systems
|
||||
DebugTools.Assert(oldMap?.AwakeBodies.Contains(body) != true);
|
||||
}
|
||||
|
||||
if (jointQuery.TryGetComponent(uid, out var joint))
|
||||
_joints.ClearJoints(uid, joint);
|
||||
|
||||
_joints.ClearJoints(uid);
|
||||
|
||||
foreach (var child in xform._children)
|
||||
{
|
||||
if (_xformQuery.TryGetComponent(child, out var childXform))
|
||||
{
|
||||
PhysicsQuery.TryGetComponent(child, out var childBody);
|
||||
RecursiveMapUpdate(child, childXform, childBody, newMap, oldMap, jointQuery);
|
||||
RecursiveMapUpdate(child, childXform, childBody, newMap, oldMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,12 +200,13 @@ internal abstract partial class SharedPlayerManager
|
||||
if (EntManager.EnsureComponent<ActorComponent>(uid, out var actor))
|
||||
{
|
||||
// component already existed.
|
||||
DebugTools.AssertNotNull(actor.PlayerSession);
|
||||
if (!force)
|
||||
return false;
|
||||
|
||||
kicked = actor.PlayerSession;
|
||||
Detach(kicked);
|
||||
|
||||
if (kicked != null)
|
||||
Detach(kicked);
|
||||
}
|
||||
|
||||
if (_netMan.IsServer)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user