mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
61 Commits
fix/bui-st
...
v223.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
796abe1230 | ||
|
|
6c2cf26250 | ||
|
|
ce262d5ff8 | ||
|
|
c250010dad | ||
|
|
709c7bc808 | ||
|
|
37918da73c | ||
|
|
6cc2083b09 | ||
|
|
6a6bfe33ca | ||
|
|
9737a4249c | ||
|
|
a48a353939 | ||
|
|
f0b45d95cb | ||
|
|
d69c5500f2 | ||
|
|
cf133ca341 | ||
|
|
b0922b8e0e | ||
|
|
512ebd8422 | ||
|
|
85f74c3ba3 | ||
|
|
da7abc6580 | ||
|
|
b1329d30bf | ||
|
|
12808d073e | ||
|
|
ec794ce4e4 | ||
|
|
6b13475842 | ||
|
|
b48ee22800 | ||
|
|
0b95a4edeb | ||
|
|
ed359481b4 | ||
|
|
1189613908 | ||
|
|
30907d8415 | ||
|
|
7f2da4d4f3 | ||
|
|
e30e963623 | ||
|
|
b056caeed7 | ||
|
|
fbc8086335 | ||
|
|
799702b814 | ||
|
|
63df90f86f | ||
|
|
51f0c60bd3 | ||
|
|
a9ed53f47b | ||
|
|
41c40f1a94 | ||
|
|
6e61c35d35 | ||
|
|
aae0a8bc51 | ||
|
|
cb543240c6 | ||
|
|
1654ab06f5 | ||
|
|
211245215e | ||
|
|
10aaaa65c5 | ||
|
|
d2a2afe82e | ||
|
|
025d90d281 | ||
|
|
c229f2e312 | ||
|
|
fe051a3577 | ||
|
|
51a0ef1e60 | ||
|
|
702dfef5fc | ||
|
|
a0c1ad246f | ||
|
|
1153888bd1 | ||
|
|
ccbb6ddec7 | ||
|
|
970da5f717 | ||
|
|
4d528dd577 | ||
|
|
c83720b163 | ||
|
|
bd87a805d4 | ||
|
|
fff42fb2b4 | ||
|
|
4500669f65 | ||
|
|
7d19ea9338 | ||
|
|
2dc610907d | ||
|
|
beb1c4b1fb | ||
|
|
7e331eaa75 | ||
|
|
caf9e45ad9 |
@@ -7,6 +7,18 @@ indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
max_line_length = 120
|
||||
|
||||
# ReSharper properties
|
||||
resharper_csharp_max_line_length = 120
|
||||
resharper_csharp_wrap_after_declaration_lpar = true
|
||||
resharper_csharp_wrap_arguments_style = chop_if_long
|
||||
resharper_csharp_wrap_parameters_style = chop_if_long
|
||||
resharper_keep_existing_attribute_arrangement = true
|
||||
resharper_place_field_attribute_on_same_line = if_owner_is_single_line
|
||||
resharper_wrap_chained_binary_patterns = chop_if_long
|
||||
resharper_wrap_chained_method_calls = chop_if_long
|
||||
|
||||
[*.{csproj,xml,yml,dll.config,targets,props}]
|
||||
indent_size = 2
|
||||
|
||||
|
||||
6
.github/workflows/publish-client.yml
vendored
6
.github/workflows/publish-client.yml
vendored
@@ -33,10 +33,10 @@ jobs:
|
||||
mkdir "release/${{ steps.parse_version.outputs.version }}"
|
||||
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
|
||||
|
||||
- name: Upload files to centcomm
|
||||
- name: Upload files to Suns
|
||||
uses: appleboy/scp-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
source: "release/${{ steps.parse_version.outputs.version }}"
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Update manifest JSON
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: centcomm.spacestation14.io
|
||||
host: suns.spacestation14.com
|
||||
username: robust-build-push
|
||||
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
|
||||
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
We actually set ManagePackageVersionsCentrally manually in another import file.
|
||||
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
|
||||
https://github.com/NuGet/NuGet.Client/pull/5572
|
||||
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
|
||||
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
|
||||
-->
|
||||
<ManagePackageVersionsCentrally />
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
|
||||
Submodule Lidgren.Network/Lidgren.Network updated: 61a56c60bd...1d85b82e05
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
131
RELEASE-NOTES.md
131
RELEASE-NOTES.md
@@ -54,6 +54,137 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 223.1.2
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `MapGridComponent.LastTileModifiedTick` is now actually networked to clients.
|
||||
|
||||
|
||||
## 223.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed an exception caused by enum cvars using integer type values instead of enum values
|
||||
|
||||
|
||||
## 223.1.0
|
||||
|
||||
### Other
|
||||
|
||||
* Various `ContainerSystem` methods have been obsoleted in favour of overrides that take in an `Entity` struct instead of `EntityUid`
|
||||
* Various `EntityCoordinates` methods have been obsoleted with replacements added to `SharedTransformSystem`
|
||||
|
||||
|
||||
## 223.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* The `ComponentState` class is now abstract. Networked components that don't have state information now just return a null state.
|
||||
* The way that delta component states work has changed. It now expects there to be two different state classes, only one of which should implement `IComponentDeltaState<TFullState>`
|
||||
|
||||
### New features
|
||||
|
||||
* A new `replay.checkpoint_min_interval` cvar has been added. It can be used to limit the frequency at which checkpoints are generated when loading a replay.
|
||||
* Added `IConfigurationManager.OnCVarValueChanged`. This is a c# event that gets invoked whenever any cvar value changes.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* `IEyeManager.GetWorldViewbounds()` and `IEyeManager.GetWorldViewbounds()` should now return the correct bounds if the main viewport does not take up the whole screen.
|
||||
|
||||
### Other
|
||||
|
||||
* The default values of various replay related cvars have been changed to try and reduce memory usage.
|
||||
|
||||
|
||||
## 222.4.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added the following types from `System.Numerics` to the sandbox: `Complex`, `Matrix3x2`, `Matrix4x4`, `Plane`, `Quaternion`, `Vector3`, `Vector4`.
|
||||
|
||||
|
||||
## 222.3.0
|
||||
|
||||
### New features
|
||||
|
||||
* `ITileDefinition.EditorHidden` allows hiding a tile from the tile spawn panel.
|
||||
* Ordered event subscriptions now take child types into account, so ordering based on a shared type will work.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Cross-map BUI range checks now work.
|
||||
* Paused entities update on prototype reload.
|
||||
|
||||
### Other
|
||||
|
||||
* Fixed build compatibility with .NET 8.0.300 SDK, due to changes in how Central Package Management behaves.
|
||||
* Physics component has delta states to reduce network usage.
|
||||
|
||||
|
||||
## 222.2.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `EntityQuery.Comp()` (abbreviation of `GetComponent()`)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix `SerializationManager.TryGetVariableType` checking the wrong property.
|
||||
* Fixed GrammarSystem mispredicting a character's gender
|
||||
|
||||
### Other
|
||||
|
||||
* User interface system now performs range checks in parallel
|
||||
|
||||
|
||||
## 222.1.1
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fixed never setting BoundUserInterface.State.
|
||||
|
||||
### Other
|
||||
|
||||
* Add truncate for filesaving.
|
||||
* Add method for getting the type of a data field by name from ISerializationManager.
|
||||
|
||||
|
||||
## 222.1.0
|
||||
|
||||
### New features
|
||||
|
||||
* Added `BoundKeyEventArgs.IsRepeat`.
|
||||
* Added `net.lidgren_log_warning` and `net.lidgren_log_error` CVars.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix assert trip when holding repeatable keybinds.
|
||||
|
||||
### Other
|
||||
|
||||
* Updated Lidgren to v0.3.1. This should provide performance improvements if warning/error logs are disabled.
|
||||
|
||||
|
||||
## 222.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Mark IComponentFactory argument in EntityPrototype as mandatory.
|
||||
|
||||
### New features
|
||||
|
||||
* Add `EntProtoId<T>` to check for components on the attached entity as well.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix PVS iterating duplicate chunks for multiple viewsubscriptions.
|
||||
|
||||
### Other
|
||||
|
||||
* Defer clientside BUI opens if it's the first state that comes in.
|
||||
|
||||
|
||||
## 221.2.0
|
||||
|
||||
### New features
|
||||
|
||||
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
57
Robust.Analyzers.Tests/NoUncachedRegexAnalyzerTest.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.NoUncachedRegexAnalyzer>;
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class NoUncachedRegexAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<NoUncachedRegexAnalyzer, NUnitVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code }
|
||||
},
|
||||
};
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Test()
|
||||
{
|
||||
const string code = """
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public static class Foo
|
||||
{
|
||||
public static void Bad()
|
||||
{
|
||||
Regex.Replace("foo", "bar", "baz");
|
||||
}
|
||||
|
||||
public static void Good()
|
||||
{
|
||||
var r = new Regex("bar");
|
||||
r.Replace("foo", "baz");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(7,9): warning RA0026: Usage of a static Regex function that takes in a pattern string. This can cause constant re-parsing of the pattern.
|
||||
VerifyCS.Diagnostic().WithSpan(7, 9, 7, 43)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Robust.Roslyn.Shared;
|
||||
using Robust.Shared.Serialization.Manager.Definition;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
@@ -56,8 +57,18 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to add a setter."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
|
||||
Diagnostics.IdDataFieldRedundantTag,
|
||||
"Data field has redundant tag specified",
|
||||
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Info,
|
||||
true,
|
||||
"Make sure to remove the tag string from the data field attribute."
|
||||
);
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(
|
||||
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule
|
||||
DataDefinitionPartialRule, NestedDataDefinitionPartialRule, DataFieldWritableRule, DataFieldPropertyWritableRule,
|
||||
DataFieldRedundantTagRule
|
||||
);
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
@@ -125,6 +136,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +165,11 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
|
||||
@@ -248,6 +269,29 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool HasRedundantTag(ISymbol symbol)
|
||||
{
|
||||
if (!IsDataField(symbol, out var _, out var attribute))
|
||||
return false;
|
||||
|
||||
// No args, no problem
|
||||
if (attribute.ConstructorArguments.Length == 0)
|
||||
return false;
|
||||
|
||||
// If a tag is explicitly specified, it will be the first argument...
|
||||
var tagArgument = attribute.ConstructorArguments[0];
|
||||
// ...but the first arg could also something else, since tag is optional
|
||||
// so we make sure that it's a string
|
||||
if (tagArgument.Value is not string explicitName)
|
||||
return false;
|
||||
|
||||
// Get the name that sourcegen would provide
|
||||
var automaticName = DataDefinitionUtility.AutoGenerateTag(symbol.Name);
|
||||
|
||||
// If the explicit name matches the sourcegen name, we have a redundancy
|
||||
return explicitName == automaticName;
|
||||
}
|
||||
|
||||
private static bool IsImplicitDataDefinition(ITypeSymbol type)
|
||||
{
|
||||
if (HasAttribute(type, ImplicitDataDefinitionNamespace))
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
using Microsoft.CodeAnalysis.CodeFixes;
|
||||
@@ -16,8 +13,11 @@ namespace Robust.Analyzers;
|
||||
[ExportCodeFixProvider(LanguageNames.CSharp)]
|
||||
public sealed class DefinitionFixer : CodeFixProvider
|
||||
{
|
||||
private const string DataFieldAttributeName = "DataField";
|
||||
|
||||
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
|
||||
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable
|
||||
IdDataDefinitionPartial, IdNestedDataDefinitionPartial, IdDataFieldWritable, IdDataFieldPropertyWritable,
|
||||
IdDataFieldRedundantTag
|
||||
);
|
||||
|
||||
public override Task RegisterCodeFixesAsync(CodeFixContext context)
|
||||
@@ -34,6 +34,8 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
return RegisterDataFieldFix(context, diagnostic);
|
||||
case IdDataFieldPropertyWritable:
|
||||
return RegisterDataFieldPropertyFix(context, diagnostic);
|
||||
case IdDataFieldRedundantTag:
|
||||
return RegisterRedundantTagFix(context, diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +74,68 @@ public sealed class DefinitionFixer : CodeFixProvider
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterRedundantTagFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
var span = diagnostic.Location.SourceSpan;
|
||||
var token = root?.FindToken(span.Start).Parent?.AncestorsAndSelf().OfType<MemberDeclarationSyntax>().First();
|
||||
|
||||
if (token == null)
|
||||
return;
|
||||
|
||||
// Find the DataField attribute
|
||||
AttributeSyntax? dataFieldAttribute = null;
|
||||
foreach (var attributeList in token.AttributeLists)
|
||||
{
|
||||
foreach (var attribute in attributeList.Attributes)
|
||||
{
|
||||
if (attribute.Name.ToString() == DataFieldAttributeName)
|
||||
{
|
||||
dataFieldAttribute = attribute;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dataFieldAttribute != null)
|
||||
break;
|
||||
}
|
||||
|
||||
if (dataFieldAttribute == null)
|
||||
return;
|
||||
|
||||
context.RegisterCodeFix(CodeAction.Create(
|
||||
"Remove explicitly set tag",
|
||||
c => RemoveRedundantTag(context.Document, dataFieldAttribute, c),
|
||||
"Remove explicitly set tag"
|
||||
), diagnostic);
|
||||
}
|
||||
|
||||
private static async Task<Document> RemoveRedundantTag(Document document, AttributeSyntax syntax, CancellationToken cancellation)
|
||||
{
|
||||
var root = (CompilationUnitSyntax?) await document.GetSyntaxRootAsync(cancellation);
|
||||
|
||||
if (syntax.ArgumentList == null)
|
||||
return document;
|
||||
|
||||
AttributeSyntax? newSyntax;
|
||||
if (syntax.ArgumentList.Arguments.Count == 1)
|
||||
{
|
||||
// If this is the only argument, delete the ArgumentList so we don't leave empty parentheses
|
||||
newSyntax = syntax.RemoveNode(syntax.ArgumentList, SyntaxRemoveOptions.KeepNoTrivia);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove the first argument, which is the tag
|
||||
var newArgs = syntax.ArgumentList.Arguments.RemoveAt(0);
|
||||
var newArgList = syntax.ArgumentList.WithArguments(newArgs);
|
||||
// Construct a new attribute with the tag removed
|
||||
newSyntax = syntax.WithArgumentList(newArgList);
|
||||
}
|
||||
|
||||
root = root!.ReplaceNode(syntax, newSyntax!);
|
||||
|
||||
return document.WithSyntaxRoot(root);
|
||||
}
|
||||
|
||||
private static async Task RegisterDataFieldFix(CodeFixContext context, Diagnostic diagnostic)
|
||||
{
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,11 @@
|
||||
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Needed for DataDefinitionAnalyzer. -->
|
||||
<Compile Include="..\Robust.Shared\Serialization\Manager\Definition\DataDefinitionUtility.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
var viewBounds = args.WorldBounds;
|
||||
var viewAABB = args.WorldAABB;
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = args.MapId;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
@@ -373,7 +373,7 @@ namespace Robust.Client.Debugging
|
||||
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mapId = args.MapId;
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.ShapeInfo) != 0x0)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var map = _eyeManager.CurrentMap;
|
||||
var map = args.MapId;
|
||||
if (map == MapId.Nullspace) return;
|
||||
|
||||
foreach (var (_, treeComp) in _trees.GetIntersectingTrees(map, args.WorldBounds))
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
var currentMap = _eyeManager.CurrentMap;
|
||||
var currentMap = args.MapId;
|
||||
var viewport = args.WorldBounds;
|
||||
var worldHandle = args.WorldHandle;
|
||||
|
||||
|
||||
@@ -54,11 +54,11 @@ namespace Robust.Client.GameStates
|
||||
private readonly HashSet<NetEntity> _stateEnts = new();
|
||||
private readonly List<EntityUid> _toDelete = new();
|
||||
private readonly List<IComponent> _toRemove = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _outputData = new();
|
||||
private readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _outputData = new();
|
||||
private readonly List<(EntityUid, TransformComponent)> _queuedBroadphaseUpdates = new();
|
||||
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState>>(new DictPolicy<ushort, IComponentState>(), 256);
|
||||
private readonly ObjectPool<Dictionary<ushort, IComponentState?>> _compDataPool =
|
||||
new DefaultObjectPool<Dictionary<ushort, IComponentState?>>(new DictPolicy<ushort, IComponentState?>(), 256);
|
||||
|
||||
private uint _metaCompNetId;
|
||||
|
||||
@@ -259,7 +259,7 @@ namespace Robust.Client.GameStates
|
||||
public void UpdateFullRep(GameState state, bool cloneDelta = false)
|
||||
=> _processor.UpdateFullRep(state, cloneDelta);
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
|
||||
=> _processor.GetFullRep();
|
||||
|
||||
private void HandlePvsLeaveMessage(MsgStateLeavePvs message)
|
||||
@@ -600,8 +600,12 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
|
||||
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
if (compState != null)
|
||||
{
|
||||
var handleState = new ComponentHandleState(compState, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
}
|
||||
@@ -633,8 +637,12 @@ namespace Robust.Client.GameStates
|
||||
if (_sawmill.Level <= LogLevel.Debug)
|
||||
_sawmill.Debug($" A component was removed: {comp.GetType()}");
|
||||
|
||||
var stateEv = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
|
||||
if (state != null)
|
||||
{
|
||||
var stateEv = new ComponentHandleState(state, null);
|
||||
_entities.EventBus.RaiseComponentEvent(comp, ref stateEv);
|
||||
}
|
||||
|
||||
comp.ClearCreationTick(); // don't undo the re-adding.
|
||||
comp.LastModifiedTick = _timing.LastRealTick;
|
||||
}
|
||||
@@ -687,7 +695,7 @@ namespace Robust.Client.GameStates
|
||||
DebugTools.Assert(component.NetSyncEnabled);
|
||||
|
||||
var state = _entityManager.GetComponentState(bus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
compData.Add(netId, state);
|
||||
}
|
||||
}
|
||||
@@ -1349,6 +1357,9 @@ namespace Robust.Client.GameStates
|
||||
|
||||
foreach (var (comp, cur, next) in _compStateWork.Values)
|
||||
{
|
||||
if (cur == null && next == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(cur, next);
|
||||
bus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
@@ -1501,6 +1512,9 @@ namespace Robust.Client.GameStates
|
||||
_entityManager.AddComponent(uid, comp, true, meta);
|
||||
}
|
||||
|
||||
if (state == null)
|
||||
continue;
|
||||
|
||||
var handleState = new ComponentHandleState(state, null);
|
||||
_entityManager.EventBus.RaiseComponentEvent(comp, ref handleState);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// This dictionary stores the full most recently received server state of any entity. This is used whenever predicted entities get reset.
|
||||
/// </summary>
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState>> _lastStateFullRep
|
||||
internal readonly Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> _lastStateFullRep
|
||||
= new();
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -212,7 +212,7 @@ Had full state: {LastFullState != null}"
|
||||
{
|
||||
if (!_lastStateFullRep.TryGetValue(entityState.NetEntity, out var compData))
|
||||
{
|
||||
compData = new Dictionary<ushort, IComponentState>();
|
||||
compData = new();
|
||||
_lastStateFullRep.Add(entityState.NetEntity, compData);
|
||||
}
|
||||
|
||||
@@ -221,21 +221,20 @@ Had full state: {LastFullState != null}"
|
||||
var compState = change.State;
|
||||
|
||||
if (compState is IComponentDeltaState delta
|
||||
&& !delta.FullState
|
||||
&& compData.TryGetValue(change.NetID, out var old)) // May fail if relying on implicit data
|
||||
{
|
||||
DebugTools.Assert(old is IComponentDeltaState oldDelta && oldDelta.FullState, "last state is not a full state");
|
||||
DebugTools.Assert(old is not IComponentDeltaState, "last state is not a full state");
|
||||
|
||||
if (cloneDelta)
|
||||
{
|
||||
compState = delta.CreateNewFullState(old);
|
||||
compState = delta.CreateNewFullState(old!);
|
||||
}
|
||||
else
|
||||
{
|
||||
delta.ApplyToFullState(old);
|
||||
delta.ApplyToFullState(old!);
|
||||
compState = old;
|
||||
}
|
||||
DebugTools.Assert(compState is IComponentDeltaState newState && newState.FullState, "newly constructed state is not a full state");
|
||||
DebugTools.Assert(compState is not IComponentDeltaState, "newly constructed state is not a full state");
|
||||
}
|
||||
|
||||
compData[change.NetID] = compState;
|
||||
@@ -391,7 +390,7 @@ Had full state: {LastFullState != null}"
|
||||
LastFullStateRequested = null;
|
||||
}
|
||||
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> implicitData)
|
||||
public void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> implicitData)
|
||||
{
|
||||
foreach (var (netEntity, implicitEntState) in implicitData)
|
||||
{
|
||||
@@ -399,6 +398,7 @@ Had full state: {LastFullState != null}"
|
||||
|
||||
foreach (var (netId, implicitCompState) in implicitEntState)
|
||||
{
|
||||
DebugTools.Assert(implicitCompState is not IComponentDeltaState);
|
||||
ref var serverState = ref CollectionsMarshal.GetValueRefOrAddDefault(fullRep, netId, out var exists);
|
||||
|
||||
if (!exists)
|
||||
@@ -407,36 +407,32 @@ Had full state: {LastFullState != null}"
|
||||
continue;
|
||||
}
|
||||
|
||||
if (serverState is not IComponentDeltaState serverDelta || serverDelta.FullState)
|
||||
if (serverState is not IComponentDeltaState serverDelta)
|
||||
continue;
|
||||
|
||||
DebugTools.AssertNotNull(implicitCompState);
|
||||
|
||||
// Server sent an initial delta state. This is fine as long as the client can infer an initial full
|
||||
// state from the entity prototype.
|
||||
if (implicitCompState is not IComponentDeltaState implicitDelta || !implicitDelta.FullState)
|
||||
{
|
||||
_logger.Error($"Server sent delta state and client failed to construct an implicit full state for entity {netEntity}");
|
||||
continue;
|
||||
}
|
||||
|
||||
serverDelta.ApplyToFullState(implicitCompState);
|
||||
serverDelta.ApplyToFullState(implicitCompState!);
|
||||
serverState = implicitCompState;
|
||||
DebugTools.Assert(implicitCompState is IComponentDeltaState d && d.FullState);
|
||||
DebugTools.Assert(serverState is not IComponentDeltaState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity netEntity)
|
||||
public Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity netEntity)
|
||||
{
|
||||
return _lastStateFullRep[netEntity];
|
||||
}
|
||||
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep()
|
||||
public Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep()
|
||||
{
|
||||
return _lastStateFullRep;
|
||||
}
|
||||
|
||||
public bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary)
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary)
|
||||
{
|
||||
return _lastStateFullRep.TryGetValue(entity, out dictionary);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace Robust.Client.GameStates
|
||||
/// <summary>
|
||||
/// Returns the full collection of cached game states that are used to reset predicted entities.
|
||||
/// </summary>
|
||||
Dictionary<NetEntity, Dictionary<ushort, IComponentState>> GetFullRep();
|
||||
Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> GetFullRep();
|
||||
|
||||
/// <summary>
|
||||
/// This will perform some setup in order to reset the game to an earlier state. To fully reset the state
|
||||
|
||||
@@ -83,13 +83,13 @@ namespace Robust.Client.GameStates
|
||||
/// The data to merge.
|
||||
/// It's a dictionary of entity ID -> (component net ID -> ComponentState)
|
||||
/// </param>
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState>> data);
|
||||
void MergeImplicitData(Dictionary<NetEntity, Dictionary<ushort, IComponentState?>> data);
|
||||
|
||||
/// <summary>
|
||||
/// Get the last state data from the server for an entity.
|
||||
/// </summary>
|
||||
/// <returns>Dictionary (net ID -> ComponentState)</returns>
|
||||
Dictionary<ushort, IComponentState> GetLastServerStates(NetEntity entity);
|
||||
Dictionary<ushort, IComponentState?> GetLastServerStates(NetEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the number of applicable states in the game state buffer from a given tick.
|
||||
@@ -99,6 +99,6 @@ namespace Robust.Client.GameStates
|
||||
int GetApplicableStateCount(GameTick? fromTick);
|
||||
|
||||
bool TryGetLastServerStates(NetEntity entity,
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState>? dictionary);
|
||||
[NotNullWhen(true)] out Dictionary<ushort, IComponentState?>? dictionary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Robust.Client.GameStates
|
||||
while (query.MoveNext(out var uid, out var transform))
|
||||
{
|
||||
// if not on the same map, continue
|
||||
if (transform.MapID != _eyeManager.CurrentMap || _container.IsEntityInContainer(uid))
|
||||
if (transform.MapID != args.MapId || _container.IsEntityInContainer(uid))
|
||||
continue;
|
||||
|
||||
if (transform.GridUid == uid)
|
||||
|
||||
@@ -64,29 +64,22 @@ namespace Robust.Client.Graphics
|
||||
/// <inheritdoc />
|
||||
public Box2 GetWorldViewport()
|
||||
{
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
|
||||
var topLeft = ScreenToMap(Vector2.Zero);
|
||||
var topRight = ScreenToMap(new Vector2(vpSize.X, 0));
|
||||
var bottomRight = ScreenToMap(vpSize);
|
||||
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y));
|
||||
|
||||
var left = MathHelper.Min(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
|
||||
var bottom = MathHelper.Min(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
|
||||
var right = MathHelper.Max(topLeft.X, topRight.X, bottomRight.X, bottomLeft.X);
|
||||
var top = MathHelper.Max(topLeft.Y, topRight.Y, bottomRight.Y, bottomLeft.Y);
|
||||
|
||||
return new Box2(left, bottom, right, top);
|
||||
return GetWorldViewbounds().CalcBoundingBox();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2Rotated GetWorldViewbounds()
|
||||
{
|
||||
var vpSize = _displayManager.ScreenSize;
|
||||
// This is an inefficient and roundabout way of geting the viewport.
|
||||
// But its a method that shouldn't get used much.
|
||||
|
||||
var vp = MainViewport as Control;
|
||||
var vpSize = vp?.PixelSize ?? _displayManager.ScreenSize;
|
||||
|
||||
var topRight = ScreenToMap(new Vector2(vpSize.X, 0)).Position;
|
||||
var bottomLeft = ScreenToMap(new Vector2(0, vpSize.Y)).Position;
|
||||
|
||||
// This assumes the main viewports eye and the main eye are the same.
|
||||
var rotation = new Angle(CurrentEye.Rotation);
|
||||
var center = (bottomLeft + topRight) / 2;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Map;
|
||||
@@ -13,26 +14,29 @@ namespace Robust.Client.Graphics
|
||||
public interface IEyeManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The current eye that is being used to render the game.
|
||||
/// The primary eye, which is usually the eye associated with the main viewport.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Generally, you should avoid using this whenever possible. E.g., when rendering overlays should use the
|
||||
/// eye & viewbounds that gets passed to the draw method.
|
||||
/// Setting this property to null will use the default eye.
|
||||
/// </remarks>
|
||||
IEye CurrentEye { get; set; }
|
||||
|
||||
IViewportControl MainViewport { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the map on which the current eye is "placed".
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
MapId CurrentMap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A world-space box that is at LEAST the area covered by the viewport.
|
||||
/// A world-space box that is at LEAST the area covered by the main viewport.
|
||||
/// May be larger due to say rotation.
|
||||
/// </summary>
|
||||
Box2 GetWorldViewport();
|
||||
|
||||
/// <summary>
|
||||
/// A world-space box of the area visible in the main viewport.
|
||||
/// </summary>
|
||||
Box2Rotated GetWorldViewbounds();
|
||||
|
||||
/// <summary>
|
||||
@@ -43,7 +47,7 @@ namespace Robust.Client.Graphics
|
||||
void GetScreenProjectionMatrix(out Matrix3 projMatrix);
|
||||
|
||||
/// <summary>
|
||||
/// Projects a point from world space to UI screen space using the current camera.
|
||||
/// Projects a point from world space to UI screen space using the main viewport.
|
||||
/// </summary>
|
||||
/// <param name="point">Point in world to transform.</param>
|
||||
/// <returns>Corresponding point in UI screen space.</returns>
|
||||
|
||||
@@ -480,7 +480,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
var worldBounds = CalcWorldBounds(viewport);
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
|
||||
if (_eyeManager.CurrentMap != MapId.Nullspace)
|
||||
if (eye.Position.MapId != MapId.Nullspace)
|
||||
{
|
||||
using (DebugGroup("Lights"))
|
||||
using (_prof.Group("Lights"))
|
||||
@@ -544,9 +544,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
UIBox2.FromDimensions(Vector2.Zero, viewport.Size), new Color(1, 1, 1, 0.5f));
|
||||
}
|
||||
|
||||
using (_prof.Group("Overlays WS"))
|
||||
if (eye.Position.MapId != MapId.Nullspace)
|
||||
{
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
|
||||
using (_prof.Group("Overlays WS"))
|
||||
{
|
||||
RenderOverlays(viewport, OverlaySpace.WorldSpace, worldAABB, worldBounds);
|
||||
}
|
||||
}
|
||||
|
||||
_currentViewport = oldVp;
|
||||
|
||||
@@ -664,7 +664,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
fixed (byte* p = buffer)
|
||||
{
|
||||
GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p);
|
||||
GL.GetTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, (IntPtr) p);
|
||||
}
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Placement.Modes
|
||||
|
||||
public SnapgridCenter(PlacementManager pMan) : base(pMan) { }
|
||||
|
||||
public override void Render(DrawingHandleWorld handle)
|
||||
public override void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
if (Grid != null)
|
||||
{
|
||||
@@ -34,18 +34,18 @@ namespace Robust.Client.Placement.Modes
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(a, 0));
|
||||
var to = ScreenToWorld(new Vector2(a, viewportSize.Y));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
}
|
||||
for (var a = gridstart.Y; a < viewportSize.Y; a += SnapSize * EyeManager.PixelsPerMeter)
|
||||
{
|
||||
var from = ScreenToWorld(new Vector2(0, a));
|
||||
var to = ScreenToWorld(new Vector2(viewportSize.X, a));
|
||||
handle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
args.WorldHandle.DrawLine(from, to, new Color(0, 0, 1f));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw grid BELOW the ghost thing.
|
||||
base.Render(handle);
|
||||
base.Render(args);
|
||||
}
|
||||
|
||||
public override void AlignPlacementMode(ScreenCoordinates mouseScreen)
|
||||
|
||||
@@ -628,20 +628,20 @@ namespace Robust.Client.Placement
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Render(DrawingHandleWorld handle)
|
||||
private void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
if (CurrentMode == null || !IsActive)
|
||||
{
|
||||
if (EraserRect.HasValue)
|
||||
{
|
||||
handle.UseShader(_drawingShader);
|
||||
handle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
|
||||
handle.UseShader(null);
|
||||
args.WorldHandle.UseShader(_drawingShader);
|
||||
args.WorldHandle.DrawRect(EraserRect.Value, new Color(255, 0, 0, 50));
|
||||
args.WorldHandle.UseShader(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentMode.Render(handle);
|
||||
CurrentMode.Render(args);
|
||||
|
||||
if (CurrentPermission is not {Range: > 0} ||
|
||||
!CurrentMode.RangeRequired ||
|
||||
@@ -650,7 +650,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
var worldPos = EntityManager.GetComponent<TransformComponent>(controlled).WorldPosition;
|
||||
|
||||
handle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
|
||||
args.WorldHandle.DrawCircle(worldPos, CurrentPermission.Range, new Color(1, 1, 1, 0.25f));
|
||||
}
|
||||
|
||||
private void HandleStartPlacement(MsgPlacement msg)
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace Robust.Client.Placement
|
||||
/// <returns></returns>
|
||||
public abstract bool IsValidPosition(EntityCoordinates position);
|
||||
|
||||
public virtual void Render(DrawingHandleWorld handle)
|
||||
public virtual void Render(in OverlayDrawArgs args)
|
||||
{
|
||||
var uid = pManager.CurrentPlacementOverlayEntity;
|
||||
if (!pManager.EntityManager.TryGetComponent(uid, out SpriteComponent? sprite) || !sprite.Visible)
|
||||
@@ -125,7 +125,8 @@ namespace Robust.Client.Placement
|
||||
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
|
||||
|
||||
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
|
||||
spriteSys.Render(uid.Value, sprite, handle, pManager.EyeManager.CurrentEye.Rotation, worldRot, worldPos);
|
||||
var rot = args.Viewport.Eye?.Rotation ?? default;
|
||||
spriteSys.Render(uid.Value, sprite, args.WorldHandle, rot, worldRot, worldPos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Robust.Client.Placement
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
_manager.Render(args.WorldHandle);
|
||||
_manager.Render(args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,11 @@ public sealed partial class ReplayLoadManager
|
||||
var spawnedTracker = 0;
|
||||
var stateTracker = 0;
|
||||
var curState = state0;
|
||||
|
||||
var stats_due_ticks = 0;
|
||||
var stats_due_spawned = 0;
|
||||
var stats_due_state = 0;
|
||||
|
||||
for (var i = 1; i < states.Count; i++)
|
||||
{
|
||||
if (i % 10 == 0)
|
||||
@@ -150,11 +155,28 @@ public sealed partial class ReplayLoadManager
|
||||
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
|
||||
ticksSinceLastCheckpoint++;
|
||||
|
||||
// Don't create checkpoints too frequently no matter the circumstance
|
||||
if (ticksSinceLastCheckpoint < _checkpointMinInterval)
|
||||
continue;
|
||||
|
||||
// Check if enough time, spawned entities or changed states have occurred.
|
||||
if (ticksSinceLastCheckpoint < _checkpointInterval
|
||||
&& spawnedTracker < _checkpointEntitySpawnThreshold
|
||||
&& stateTracker < _checkpointEntityStateThreshold)
|
||||
{
|
||||
continue;
|
||||
|
||||
// Track and update statistics about why checkpoints are getting created:
|
||||
if (ticksSinceLastCheckpoint >= _checkpointInterval)
|
||||
{
|
||||
stats_due_ticks += 1;
|
||||
}
|
||||
else if (spawnedTracker >= _checkpointEntitySpawnThreshold)
|
||||
{
|
||||
stats_due_spawned += 1;
|
||||
}
|
||||
else if (stateTracker >= _checkpointEntityStateThreshold)
|
||||
{
|
||||
stats_due_state += 1;
|
||||
}
|
||||
|
||||
ticksSinceLastCheckpoint = 0;
|
||||
@@ -169,7 +191,8 @@ public sealed partial class ReplayLoadManager
|
||||
checkPoints.Add(new CheckpointState(newState, timeBase, cvars, i, detached));
|
||||
}
|
||||
|
||||
_sawmill.Info($"Finished generating checkpoints. Elapsed time: {st.Elapsed}");
|
||||
_sawmill.Info($"Finished generating {checkPoints.Count} checkpoints. Elapsed time: {st.Elapsed}. Checkpoint every {(float)states.Count / checkPoints.Count} ticks on average");
|
||||
_sawmill.Info($"Checkpoint stats - Spawning: {stats_due_spawned} StateChanges: {stats_due_state} Ticks: {stats_due_ticks}. ");
|
||||
await callback(states.Count, states.Count, LoadingState.ProcessingFiles, false);
|
||||
return (checkPoints.ToArray(), serverTime);
|
||||
}
|
||||
@@ -340,7 +363,7 @@ public sealed partial class ReplayLoadManager
|
||||
#if DEBUG
|
||||
foreach (var state in modifiedState.ComponentChanges.Value)
|
||||
{
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta);
|
||||
}
|
||||
#endif
|
||||
continue;
|
||||
@@ -353,7 +376,7 @@ public sealed partial class ReplayLoadManager
|
||||
#if DEBUG
|
||||
foreach (var state in entStates[entState.NetEntity].ComponentChanges.Span)
|
||||
{
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state.State is not IComponentDeltaState delta);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -384,20 +407,20 @@ public sealed partial class ReplayLoadManager
|
||||
if (!newCompStates.Remove(existing.NetID, out var newCompState))
|
||||
continue;
|
||||
|
||||
if (newCompState.State is not IComponentDeltaState delta || delta.FullState)
|
||||
if (newCompState.State is not IComponentDeltaState delta)
|
||||
{
|
||||
combined[index] = newCompState;
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(existing.State is IComponentDeltaState fullDelta && fullDelta.FullState);
|
||||
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State), newCompState.LastModifiedTick);
|
||||
DebugTools.Assert(existing.State != null && existing.State is not IComponentDeltaState);
|
||||
combined[index] = new ComponentChange(existing.NetID, delta.CreateNewFullState(existing.State!), newCompState.LastModifiedTick);
|
||||
}
|
||||
|
||||
foreach (var compChange in newCompStates.Values)
|
||||
{
|
||||
// I'm not 100% sure about this, but I think delta states should always be full states here?
|
||||
DebugTools.Assert(compChange.State is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(compChange.State is not IComponentDeltaState delta);
|
||||
combined.Add(compChange);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ public sealed partial class ReplayLoadManager
|
||||
continue;
|
||||
|
||||
var state = _entMan.GetComponentState(_entMan.EventBus, component, null, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
list.Add(new ComponentChange(netId, state, GameTick.Zero));
|
||||
set.Add(netId);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ public sealed partial class ReplayLoadManager
|
||||
{
|
||||
if (comp.NetID == _metaId)
|
||||
{
|
||||
var state = (MetaDataComponentState) comp.State;
|
||||
var state = (MetaDataComponentState) comp.State!;
|
||||
return state.PrototypeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
|
||||
private ushort _metaId;
|
||||
private bool _initialized;
|
||||
private int _checkpointInterval;
|
||||
private int _checkpointMinInterval;
|
||||
private int _checkpointEntitySpawnThreshold;
|
||||
private int _checkpointEntityStateThreshold;
|
||||
private ISawmill _sawmill = default!;
|
||||
@@ -45,6 +46,7 @@ public sealed partial class ReplayLoadManager : IReplayLoadManager
|
||||
|
||||
_initialized = true;
|
||||
_confMan.OnValueChanged(CVars.CheckpointInterval, value => _checkpointInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointMinInterval, value => _checkpointMinInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointEntitySpawnThreshold, value => _checkpointEntitySpawnThreshold = value,
|
||||
true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointEntityStateThreshold, value => _checkpointEntityStateThreshold = value,
|
||||
|
||||
@@ -41,12 +41,12 @@ internal sealed partial class ReplayPlaybackManager
|
||||
skipEffectEvents = true;
|
||||
ResetToNearestCheckpoint(value, false);
|
||||
}
|
||||
else if (value > Replay.CurrentIndex + _checkpointInterval)
|
||||
else if (value > Replay.CurrentIndex + _checkpointMinInterval)
|
||||
{
|
||||
// If we are skipping many ticks into the future, we try to skip directly to a checkpoint instead of
|
||||
// applying every tick.
|
||||
var nextCheckpoint = GetNextCheckpoint(Replay, Replay.CurrentIndex);
|
||||
if (nextCheckpoint.Index < value)
|
||||
if (nextCheckpoint.Index < value && nextCheckpoint.Index > Replay.CurrentIndex)
|
||||
ResetToNearestCheckpoint(value, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
|
||||
|
||||
public ReplayData? Replay { get; private set; }
|
||||
public NetUserId? Recorder => Replay?.Recorder;
|
||||
private int _checkpointInterval;
|
||||
private int _checkpointMinInterval;
|
||||
private int _visualEventThreshold;
|
||||
public uint? AutoPauseCountdown { get; set; }
|
||||
public int? ScrubbingTarget { get; set; }
|
||||
@@ -93,7 +93,7 @@ internal sealed partial class ReplayPlaybackManager : IReplayPlaybackManager
|
||||
_initialized = true;
|
||||
_sawmill = _logMan.GetSawmill("replay");
|
||||
_metaId = _factory.GetRegistration(typeof(MetaDataComponent)).NetID!.Value;
|
||||
_confMan.OnValueChanged(CVars.CheckpointInterval, (value) => _checkpointInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.CheckpointMinInterval, (value) => _checkpointMinInterval = value, true);
|
||||
_confMan.OnValueChanged(CVars.ReplaySkipThreshold, (value) => _visualEventThreshold = value, true);
|
||||
_client.RunLevelChanged += OnRunLevelChanged;
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private TimeSpan? _lastClickTime;
|
||||
private Vector2? _lastClickPosition;
|
||||
|
||||
private bool IsPlaceHolderVisible => string.IsNullOrEmpty(_text) && _placeHolder != null;
|
||||
private bool IsPlaceHolderVisible => !(HidePlaceHolderOnFocus && HasKeyboardFocus()) && string.IsNullOrEmpty(_text) && _placeHolder != null;
|
||||
|
||||
public event Action<LineEditEventArgs>? OnTextChanged;
|
||||
public event Action<LineEditEventArgs>? OnTextEntered;
|
||||
@@ -186,6 +186,8 @@ namespace Robust.Client.UserInterface.Controls
|
||||
public int SelectionLower => Math.Min(_selectionStart, _cursorPosition);
|
||||
public int SelectionUpper => Math.Max(_selectionStart, _cursorPosition);
|
||||
|
||||
public bool HidePlaceHolderOnFocus { get; set; }
|
||||
|
||||
public bool IgnoreNext { get; set; }
|
||||
|
||||
private (int start, int length)? _imeData;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Client.UserInterface
|
||||
return Task.FromResult<Stream?>(null);
|
||||
}
|
||||
|
||||
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null)
|
||||
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
|
||||
{
|
||||
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Robust.Client.UserInterface
|
||||
return await OpenFileNfd(filters);
|
||||
}
|
||||
|
||||
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters)
|
||||
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
|
||||
{
|
||||
var name = await GetSaveFileName(filters);
|
||||
if (name == null)
|
||||
@@ -61,7 +61,7 @@ namespace Robust.Client.UserInterface
|
||||
|
||||
try
|
||||
{
|
||||
return (File.Open(name, FileMode.Open), true);
|
||||
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace Robust.Client.UserInterface
|
||||
/// The file stream the user chose to save to, and whether the file already existed.
|
||||
/// Null if the user cancelled the action.
|
||||
/// </returns>
|
||||
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null);
|
||||
/// <param name="truncate">Should we truncate an existing file to 0-size then write or append.</param>
|
||||
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +114,10 @@ internal partial class UserInterfaceManager
|
||||
args.Handle();
|
||||
}
|
||||
|
||||
// Attempt to ensure that keybind-up events only get raised after a single keybind-down.
|
||||
DebugTools.Assert(!_focusedControls.ContainsKey(args.Function));
|
||||
// Attempt to ensure that keybind-up events get raised after a keybind-down.
|
||||
DebugTools.Assert(!_focusedControls.TryGetValue(args.Function, out var existing)
|
||||
|| !existing.VisibleInTree
|
||||
|| args.IsRepeat && existing == control);
|
||||
_focusedControls[args.Function] = control;
|
||||
|
||||
OnKeyBindDown?.Invoke(control);
|
||||
@@ -124,7 +126,7 @@ internal partial class UserInterfaceManager
|
||||
public void KeyBindUp(BoundKeyEventArgs args)
|
||||
{
|
||||
// Only raise keybind-up for the control on which we previously raised keybind-down
|
||||
if (!_focusedControls.Remove(args.Function, out var control) || control.Disposed)
|
||||
if (!_focusedControls.Remove(args.Function, out var control) || !control.VisibleInTree)
|
||||
return;
|
||||
|
||||
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
|
||||
|
||||
@@ -29,6 +29,8 @@ 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 const string IdDataFieldRedundantTag = "RA0027";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
@@ -36,6 +34,7 @@ public class Generator : IIncrementalGenerator
|
||||
var (compilation, declarations) = source;
|
||||
var builder = new StringBuilder();
|
||||
var containingTypes = new Stack<INamedTypeSymbol>();
|
||||
var declarationsGenerated = new HashSet<string>();
|
||||
|
||||
foreach (var declaration in declarations)
|
||||
{
|
||||
@@ -44,6 +43,14 @@ public class Generator : IIncrementalGenerator
|
||||
|
||||
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
|
||||
|
||||
var symbolName = type
|
||||
.ToDisplayString()
|
||||
.Replace('<', '{')
|
||||
.Replace('>', '}');
|
||||
|
||||
if (!declarationsGenerated.Add(symbolName))
|
||||
continue;
|
||||
|
||||
var nonPartial = !IsPartial(declaration);
|
||||
|
||||
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
|
||||
@@ -107,11 +114,6 @@ using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
{{containingTypesEnd}}
|
||||
""");
|
||||
|
||||
var symbolName = type
|
||||
.ToDisplayString()
|
||||
.Replace('<', '{')
|
||||
.Replace('>', '}');
|
||||
|
||||
var sourceText = CSharpSyntaxTree
|
||||
.ParseText(builder.ToString())
|
||||
.GetRoot()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -50,9 +50,11 @@ internal sealed partial class PvsSystem
|
||||
continue;
|
||||
|
||||
var state = EntityManager.GetComponentState(bus, component, player, fromTick);
|
||||
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState delta || delta.FullState);
|
||||
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
|
||||
|
||||
if (state != null)
|
||||
DebugTools.Assert(fromTick > component.CreationTick || state is not IComponentDeltaState);
|
||||
|
||||
if (sendCompList)
|
||||
netComps!.Add(netId);
|
||||
}
|
||||
@@ -85,7 +87,7 @@ internal sealed partial class PvsSystem
|
||||
continue;
|
||||
|
||||
var state = EntityManager.GetComponentState(bus, component, player, GameTick.Zero);
|
||||
DebugTools.Assert(state is not IComponentDeltaState delta || delta.FullState);
|
||||
DebugTools.Assert(state is not IComponentDeltaState);
|
||||
changed.Add(new ComponentChange(netId, state, component.LastModifiedTick));
|
||||
netComps.Add(netId);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -43,6 +43,9 @@
|
||||
<Content Include="server_config.toml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="run_server.bat">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<EmbeddedResource Include="ExtraMappedSerializerStrings.txt">
|
||||
<LogicalName>Robust.Server.ExtraMappedSerializerStrings.txt</LogicalName>
|
||||
</EmbeddedResource>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
if (auth != _watchdogToken)
|
||||
{
|
||||
_sawmill.Warning(
|
||||
_sawmill.Verbose(
|
||||
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", auth,
|
||||
_watchdogToken);
|
||||
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
|
||||
|
||||
2
Robust.Server/run_server.bat
Normal file
2
Robust.Server/run_server.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
Robust.Server.exe
|
||||
pause
|
||||
@@ -52,7 +52,7 @@ tags = ""
|
||||
# Must be in the form of an ss14:// or ss14s:// URI pointing to the status API.
|
||||
server_url = ""
|
||||
# Comma-separated list of URLs of hub servers to advertise to.
|
||||
hub_urls = "https://central.spacestation14.io/hub/"
|
||||
hub_urls = "https://hub.spacestation14.com/"
|
||||
|
||||
[build]
|
||||
# *Absolutely all of these can be supplied using a "build.json" file*
|
||||
@@ -98,5 +98,5 @@ hub_urls = "https://central.spacestation14.io/hub/"
|
||||
|
||||
# You should probably never EVER need to touch this, but if you need a custom auth server,
|
||||
# (the auth server being the one which manages Space Station 14 accounts), you change it here.
|
||||
# server = https://central.spacestation14.io/auth/
|
||||
# server = https://auth.spacestation14.com/
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -884,7 +899,7 @@ namespace Robust.Shared
|
||||
CVarDef.Create("render.sprite_direction_bias", -0.05, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
public static readonly CVarDef<string> RenderFOVColor =
|
||||
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.REPLICATED | CVar.SERVER);
|
||||
|
||||
/*
|
||||
* CONTROLS
|
||||
@@ -1632,15 +1647,20 @@ namespace Robust.Shared
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ReplaySkipThreshold = CVarDef.Create("replay.skip_threshold", 30);
|
||||
|
||||
/// <summary>
|
||||
/// Minimum number of ticks before a new checkpoint tick is generated (overrides SpawnThreshold and StateThreshold)
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CheckpointMinInterval = CVarDef.Create("replay.checkpoint_min_interval", 60);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of ticks before a new checkpoint tick is generated.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 200);
|
||||
public static readonly CVarDef<int> CheckpointInterval = CVarDef.Create("replay.checkpoint_interval", 500);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of entities that can be spawned before a new checkpoint tick is generated.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 100);
|
||||
public static readonly CVarDef<int> CheckpointEntitySpawnThreshold = CVarDef.Create("replay.checkpoint_entity_spawn_threshold", 1000);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of entity states that can be applied before a new checkpoint tick is generated.
|
||||
|
||||
@@ -294,6 +294,9 @@ public struct ValueList<T> : IEnumerable<T>
|
||||
if (Capacity < capacity)
|
||||
Grow(capacity);
|
||||
|
||||
if (capacity == 0)
|
||||
return capacity;
|
||||
|
||||
return _items!.Length;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public event Action<CVarChangeInfo>? OnCVarValueChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new ConfigurationManager.
|
||||
/// </summary>
|
||||
@@ -94,15 +96,15 @@ namespace Robust.Shared.Configuration
|
||||
if (_configVars.TryGetValue(cvar, out var cfgVar))
|
||||
{
|
||||
// overwrite the value with the saved one
|
||||
var oldValue = GetConfigVarValue(cfgVar);
|
||||
changedInvokes.Add(SetupInvokeValueChanged(cfgVar, value, oldValue));
|
||||
cfgVar.Value = value;
|
||||
if (SetupInvokeValueChanged(cfgVar, value) is { } invoke)
|
||||
changedInvokes.Add(invoke);
|
||||
}
|
||||
else
|
||||
{
|
||||
//or add another unregistered CVar
|
||||
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
|
||||
cfgVar = new ConfigVar(cvar, 0, CVar.NONE) { Value = value };
|
||||
//Note: the initial defaultValue is null, but it will get overwritten when the cvar is registered.
|
||||
cfgVar = new ConfigVar(cvar, null!, CVar.NONE) { Value = value };
|
||||
_configVars.Add(cvar, cfgVar);
|
||||
}
|
||||
|
||||
@@ -127,26 +129,26 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
|
||||
var convertedValue = value;
|
||||
if (!cVar.DefaultValue.GetType().IsEnum && cVar.DefaultValue.GetType() != value.GetType())
|
||||
if (cVar.Type != value.GetType())
|
||||
{
|
||||
try
|
||||
{
|
||||
convertedValue = ConvertToCVarType(value, cVar.DefaultValue.GetType());
|
||||
convertedValue = ConvertToCVarType(value, cVar.Type!);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.DefaultValue.GetType()}. Toml type: {value.GetType()}");
|
||||
_sawmill.Error($"Override TOML parsed cvar does not match registered cvar type. Name: {cVarName}. Code Type: {cVar.Type}. Toml type: {value.GetType()}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cVar.DefaultValue = value;
|
||||
|
||||
if (cVar.OverrideValue == null && cVar.Value == null)
|
||||
{
|
||||
if (SetupInvokeValueChanged(cVar, convertedValue) is { } invoke)
|
||||
callbackEvents.Add(invoke);
|
||||
var oldValue = GetConfigVarValue(cVar);
|
||||
callbackEvents.Add(SetupInvokeValueChanged(cVar, convertedValue, oldValue));
|
||||
}
|
||||
|
||||
cVar.DefaultValue = convertedValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,6 +319,7 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
private void RegisterCVar(string name, Type type, object defaultValue, CVar flags)
|
||||
{
|
||||
DebugTools.AssertEqual(defaultValue.GetType(), type);
|
||||
DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int),
|
||||
$"{name}: Enum cvars must have int as underlying type.");
|
||||
|
||||
@@ -335,7 +338,7 @@ namespace Robust.Shared.Configuration
|
||||
if (cVar.Registered)
|
||||
_sawmill.Error($"The variable '{name}' has already been registered.");
|
||||
|
||||
if (!type.IsEnum && cVar.Value != null && !type.IsAssignableFrom(cVar.Value.GetType()))
|
||||
if (cVar.Value != null && type != cVar.Value.GetType())
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -350,7 +353,7 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
cVar.DefaultValue = defaultValue;
|
||||
cVar.Flags = flags;
|
||||
cVar.Registered = true;
|
||||
cVar.Register();
|
||||
|
||||
if (cVar.OverrideValue != null)
|
||||
{
|
||||
@@ -360,10 +363,9 @@ namespace Robust.Shared.Configuration
|
||||
return;
|
||||
}
|
||||
|
||||
_configVars.Add(name, new ConfigVar(name, defaultValue, flags)
|
||||
{
|
||||
Registered = true
|
||||
});
|
||||
var cvar = new ConfigVar(name, defaultValue, flags);
|
||||
cvar.Register();
|
||||
_configVars.Add(name, cvar);
|
||||
}
|
||||
|
||||
public void OnValueChanged<T>(CVarDef<T> cVar, Action<T> onValueChanged, bool invokeImmediately = false)
|
||||
@@ -412,10 +414,11 @@ namespace Robust.Shared.Configuration
|
||||
public void OnValueChanged<T>(string name, CVarChanged<T> onValueChanged, bool invokeImmediately = false)
|
||||
where T : notnull
|
||||
{
|
||||
object value;
|
||||
using (Lock.WriteGuard())
|
||||
{
|
||||
var reg = _configVars[name];
|
||||
|
||||
value = GetConfigVarValue(reg);
|
||||
reg.ValueChanged.AddInPlace(
|
||||
(object value, in CVarChangeInfo info) => onValueChanged((T)value, info),
|
||||
onValueChanged);
|
||||
@@ -423,7 +426,7 @@ namespace Robust.Shared.Configuration
|
||||
|
||||
if (invokeImmediately)
|
||||
{
|
||||
onValueChanged(GetCVar<T>(name), new CVarChangeInfo(_gameTiming.CurTick));
|
||||
onValueChanged(GetCVar<T>(name), new CVarChangeInfo(name, _gameTiming.CurTick, value, value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,12 +515,13 @@ namespace Robust.Shared.Configuration
|
||||
{
|
||||
if (!Equals(cVar.OverrideValueParsed ?? cVar.Value, value))
|
||||
{
|
||||
var oldValue = GetConfigVarValue(cVar);
|
||||
invoke = SetupInvokeValueChanged(cVar, value, oldValue, intendedTick);
|
||||
|
||||
// Setting an overriden var just turns off the override, basically.
|
||||
cVar.OverrideValue = null;
|
||||
cVar.OverrideValueParsed = null;
|
||||
|
||||
cVar.Value = value;
|
||||
invoke = SetupInvokeValueChanged(cVar, value, intendedTick);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -543,10 +547,14 @@ namespace Robust.Shared.Configuration
|
||||
if (!_configVars.TryGetValue(name, out var cVar) || !cVar.Registered)
|
||||
throw new InvalidConfigurationException($"Trying to set unregistered variable '{name}'");
|
||||
|
||||
if (cVar.OverrideValue == null && cVar.Value == null)
|
||||
{
|
||||
var oldValue = GetConfigVarValue(cVar);
|
||||
invoke = SetupInvokeValueChanged(cVar, value, oldValue);
|
||||
}
|
||||
|
||||
cVar.DefaultValue = value;
|
||||
|
||||
if (cVar.OverrideValue == null && cVar.Value == null)
|
||||
invoke = SetupInvokeValueChanged(cVar, value);
|
||||
}
|
||||
|
||||
if (invoke != null)
|
||||
@@ -587,7 +595,7 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
|
||||
// If it's null it's a string, since the rest is primitives which aren't null.
|
||||
return cVar.DefaultValue.GetType();
|
||||
return cVar.Type!;
|
||||
}
|
||||
|
||||
protected static object GetConfigVarValue(ConfigVar cVar)
|
||||
@@ -606,18 +614,19 @@ namespace Robust.Shared.Configuration
|
||||
if (_configVars.TryGetValue(key, out var cfgVar))
|
||||
{
|
||||
cfgVar.OverrideValue = value;
|
||||
if (cfgVar.Registered)
|
||||
{
|
||||
cfgVar.OverrideValueParsed = ParseOverrideValue(value, cfgVar.DefaultValue?.GetType());
|
||||
if (SetupInvokeValueChanged(cfgVar, cfgVar.OverrideValueParsed) is { } invoke)
|
||||
invokes.Add(invoke);
|
||||
}
|
||||
if (!cfgVar.Registered)
|
||||
continue;
|
||||
|
||||
var newValue = ParseOverrideValue(value, cfgVar.Type!);
|
||||
var oldValue = GetConfigVarValue(cfgVar);
|
||||
invokes.Add(SetupInvokeValueChanged(cfgVar, newValue, oldValue));
|
||||
cfgVar.OverrideValueParsed = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//or add another unregistered CVar
|
||||
//Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered.
|
||||
var cVar = new ConfigVar(key, 0, CVar.NONE) { OverrideValue = value };
|
||||
//Note: the initial defaultValue is null, but it will get overwritten when the cvar is registered.
|
||||
var cVar = new ConfigVar(key, null!, CVar.NONE) { OverrideValue = value };
|
||||
_configVars.Add(key, cVar);
|
||||
}
|
||||
}
|
||||
@@ -696,25 +705,20 @@ namespace Robust.Shared.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
private static void InvokeValueChanged(in ValueChangedInvoke invoke)
|
||||
private void InvokeValueChanged(in ValueChangedInvoke invoke)
|
||||
{
|
||||
OnCVarValueChanged?.Invoke(invoke.Info);
|
||||
foreach (var entry in invoke.Invoke.Entries)
|
||||
{
|
||||
entry.Value!.Invoke(invoke.Value, in invoke.Info);
|
||||
}
|
||||
}
|
||||
|
||||
private ValueChangedInvoke? SetupInvokeValueChanged(ConfigVar var, object value, GameTick? tick = null)
|
||||
private ValueChangedInvoke SetupInvokeValueChanged(ConfigVar var, object newValue, object oldValue, GameTick? tick = null)
|
||||
{
|
||||
if (var.ValueChanged.Count == 0)
|
||||
return null;
|
||||
|
||||
return new ValueChangedInvoke
|
||||
{
|
||||
Info = new CVarChangeInfo(tick ?? _gameTiming.CurTick),
|
||||
Invoke = var.ValueChanged,
|
||||
Value = value
|
||||
};
|
||||
tick ??= _gameTiming.CurTick;
|
||||
var info = new CVarChangeInfo(var.Name, tick.Value, newValue, oldValue);
|
||||
return new ValueChangedInvoke(info, var.ValueChanged);
|
||||
}
|
||||
|
||||
private IEnumerable<(string cvar, object value)> ParseCVarValuesFromToml(Stream stream)
|
||||
@@ -767,6 +771,9 @@ namespace Robust.Shared.Configuration
|
||||
/// <returns></returns>
|
||||
private static object ConvertToCVarType(object value, Type cVar)
|
||||
{
|
||||
if (cVar.IsEnum)
|
||||
return Enum.Parse(cVar, value.ToString() ?? string.Empty);
|
||||
|
||||
return Convert.ChangeType(value, cVar);
|
||||
}
|
||||
|
||||
@@ -786,10 +793,15 @@ namespace Robust.Shared.Configuration
|
||||
public ConfigVar(string name, object defaultValue, CVar flags)
|
||||
{
|
||||
Name = name;
|
||||
DefaultValue = defaultValue;
|
||||
Flags = flags;
|
||||
_defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the cvar's value. This may be null until the cvar is registered.
|
||||
/// </summary>
|
||||
public Type? Type { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the CVar.
|
||||
/// </summary>
|
||||
@@ -798,7 +810,16 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// The default value of this CVar.
|
||||
/// </summary>
|
||||
public object DefaultValue { get; set; }
|
||||
public object DefaultValue
|
||||
{
|
||||
get => _defaultValue;
|
||||
set
|
||||
{
|
||||
if (Registered)
|
||||
DebugTools.AssertEqual(value.GetType(), Type);
|
||||
_defaultValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optional flags to modify the behavior of this CVar.
|
||||
@@ -808,12 +829,45 @@ namespace Robust.Shared.Configuration
|
||||
/// <summary>
|
||||
/// The current value of this CVar.
|
||||
/// </summary>
|
||||
public object? Value { get; set; }
|
||||
public object? Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
if (value != null && Registered)
|
||||
DebugTools.AssertEqual(value.GetType(), Type);
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Has this CVar been registered in code?
|
||||
/// </summary>
|
||||
public bool Registered { get; set; }
|
||||
public bool Registered { get; private set; }
|
||||
|
||||
public void Register()
|
||||
{
|
||||
if (Registered)
|
||||
{
|
||||
DebugTools.AssertNotNull(DefaultValue);
|
||||
DebugTools.AssertEqual(DefaultValue.GetType(), Type);
|
||||
DebugTools.Assert(Value == null || Value.GetType() == Type);
|
||||
DebugTools.Assert(OverrideValueParsed == null || OverrideValueParsed.GetType() == Type);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_defaultValue == null)
|
||||
throw new NullReferenceException("Must specify default value before registering");
|
||||
|
||||
if (Value != null && DefaultValue.GetType() != Value.GetType())
|
||||
throw new Exception($"The cvar value & default value must be of the same type");
|
||||
|
||||
if (OverrideValueParsed != null && DefaultValue.GetType() != OverrideValueParsed.GetType())
|
||||
throw new Exception($"The cvar override value & default value must be of the same type");
|
||||
|
||||
Type = DefaultValue.GetType();
|
||||
Registered = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Was the CVar present in the config file?
|
||||
@@ -822,12 +876,25 @@ namespace Robust.Shared.Configuration
|
||||
public bool ConfigModified;
|
||||
|
||||
public InvokeList<ValueChangedDelegate> ValueChanged;
|
||||
private object _defaultValue;
|
||||
private object? _value;
|
||||
private object? _overrideValueParsed;
|
||||
|
||||
// We don't know what the type of the var is until it's registered.
|
||||
// So we can't actually parse them until then.
|
||||
// So we keep the raw string around.
|
||||
public string? OverrideValue { get; set; }
|
||||
public object? OverrideValueParsed { get; set; }
|
||||
|
||||
public object? OverrideValueParsed
|
||||
{
|
||||
get => _overrideValueParsed;
|
||||
set
|
||||
{
|
||||
if (value != null && Registered)
|
||||
DebugTools.AssertEqual(value.GetType(), Type);
|
||||
_overrideValueParsed = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -836,8 +903,14 @@ namespace Robust.Shared.Configuration
|
||||
private struct ValueChangedInvoke
|
||||
{
|
||||
public InvokeList<ValueChangedDelegate> Invoke;
|
||||
public object Value;
|
||||
public object Value => Info.NewValue;
|
||||
public CVarChangeInfo Info;
|
||||
|
||||
public ValueChangedInvoke(CVarChangeInfo info, InvokeList<ValueChangedDelegate> invoke) : this()
|
||||
{
|
||||
Info = info;
|
||||
Invoke = invoke;
|
||||
}
|
||||
}
|
||||
|
||||
protected delegate void ValueChangedDelegate(object value, in CVarChangeInfo info);
|
||||
|
||||
@@ -10,6 +10,11 @@ namespace Robust.Shared.Configuration
|
||||
/// </summary>
|
||||
public readonly struct CVarChangeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the cvar that was changed.
|
||||
/// </summary>
|
||||
public readonly string Name;
|
||||
|
||||
/// <summary>
|
||||
/// The tick this CVar changed at.
|
||||
/// </summary>
|
||||
@@ -20,9 +25,22 @@ namespace Robust.Shared.Configuration
|
||||
/// </remarks>
|
||||
public readonly GameTick TickChanged;
|
||||
|
||||
internal CVarChangeInfo(GameTick tickChanged)
|
||||
/// <summary>
|
||||
/// The new value.
|
||||
/// </summary>
|
||||
public readonly object NewValue;
|
||||
|
||||
/// <summary>
|
||||
/// The previous value.
|
||||
/// </summary>
|
||||
public readonly object OldValue;
|
||||
|
||||
internal CVarChangeInfo(string name, GameTick tickChanged, object newValue, object oldValue)
|
||||
{
|
||||
Name = name;
|
||||
TickChanged = tickChanged;
|
||||
NewValue = newValue;
|
||||
OldValue = oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,5 +270,7 @@ namespace Robust.Shared.Configuration
|
||||
/// <typeparam name="T">The type of value contained in this CVar.</typeparam>
|
||||
void UnsubValueChanged<T>(string name, CVarChanged<T> onValueChanged)
|
||||
where T : notnull;
|
||||
|
||||
public event Action<CVarChangeInfo>? OnCVarValueChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@ namespace Robust.Shared.Containers
|
||||
|
||||
private void OnContainerGetState(EntityUid uid, ContainerManagerComponent component, ref ComponentGetState args)
|
||||
{
|
||||
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet = new(component.Containers.Count);
|
||||
Dictionary<string, ContainerManagerComponent.ContainerManagerComponentState.ContainerData> containerSet =
|
||||
new(component.Containers.Count);
|
||||
|
||||
foreach (var container in component.Containers.Values)
|
||||
{
|
||||
@@ -65,7 +66,11 @@ namespace Robust.Shared.Containers
|
||||
uidArr[index] = GetNetEntity(container.ContainedEntities[index]);
|
||||
}
|
||||
|
||||
var sContainer = new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name, container.ShowContents, container.OccludesLight, uidArr);
|
||||
var sContainer =
|
||||
new ContainerManagerComponent.ContainerManagerComponentState.ContainerData(container.GetType().Name,
|
||||
container.ShowContents,
|
||||
container.OccludesLight,
|
||||
uidArr);
|
||||
containerSet.Add(container.ID, sContainer);
|
||||
}
|
||||
|
||||
@@ -115,7 +120,11 @@ namespace Robust.Shared.Containers
|
||||
container.ExpectedEntities.Clear();
|
||||
}
|
||||
|
||||
public T EnsureContainer<T>(EntityUid uid, string id, out bool alreadyExisted, ContainerManagerComponent? containerManager = null)
|
||||
public T EnsureContainer<T>(
|
||||
EntityUid uid,
|
||||
string id,
|
||||
out bool alreadyExisted,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
where T : BaseContainer
|
||||
{
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
@@ -136,7 +145,7 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
|
||||
public T EnsureContainer<T>(EntityUid uid, string id, ContainerManagerComponent? containerManager = null)
|
||||
where T : BaseContainer
|
||||
where T : BaseContainer
|
||||
{
|
||||
return EnsureContainer<T>(uid, id, out _, containerManager);
|
||||
}
|
||||
@@ -157,7 +166,11 @@ namespace Robust.Shared.Containers
|
||||
return containerManager.Containers.ContainsKey(id);
|
||||
}
|
||||
|
||||
public bool TryGetContainer(EntityUid uid, string id, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
|
||||
public bool TryGetContainer(
|
||||
EntityUid uid,
|
||||
string id,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
{
|
||||
@@ -169,12 +182,20 @@ namespace Robust.Shared.Containers
|
||||
}
|
||||
|
||||
[Obsolete("Use variant without skipExistCheck argument")]
|
||||
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, bool skipExistCheck)
|
||||
public bool TryGetContainingContainer(
|
||||
EntityUid uid,
|
||||
EntityUid containedUid,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
bool skipExistCheck)
|
||||
{
|
||||
return TryGetContainingContainer(uid, containedUid, out container);
|
||||
}
|
||||
|
||||
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
|
||||
public bool TryGetContainingContainer(
|
||||
EntityUid uid,
|
||||
EntityUid containedUid,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
DebugTools.Assert(Exists(containedUid));
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
@@ -196,7 +217,10 @@ namespace Robust.Shared.Containers
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ContainsEntity(EntityUid uid, EntityUid containedUid, ContainerManagerComponent? containerManager = null)
|
||||
public bool ContainsEntity(
|
||||
EntityUid uid,
|
||||
EntityUid containedUid,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
DebugTools.Assert(Exists(containedUid));
|
||||
if (!Resolve(uid, ref containerManager, false))
|
||||
@@ -228,13 +252,20 @@ namespace Robust.Shared.Containers
|
||||
foreach (var containers in containerManager.Containers.Values)
|
||||
{
|
||||
if (containers.Contains(toremove))
|
||||
return Remove((toremove, containedXform, containedMeta), containers, reparent, force, destination, localRotation);
|
||||
return Remove((toremove, containedXform, containedMeta),
|
||||
containers,
|
||||
reparent,
|
||||
force,
|
||||
destination,
|
||||
localRotation);
|
||||
}
|
||||
|
||||
return true; // If we don't contain the entity, it will always be removed
|
||||
}
|
||||
|
||||
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(EntityUid uid, ContainerManagerComponent? containerManager = null)
|
||||
public ContainerManagerComponent.AllContainersEnumerable GetAllContainers(
|
||||
EntityUid uid,
|
||||
ContainerManagerComponent? containerManager = null)
|
||||
{
|
||||
if (!Resolve(uid, ref containerManager))
|
||||
return new ContainerManagerComponent.AllContainersEnumerable();
|
||||
@@ -246,20 +277,32 @@ namespace Robust.Shared.Containers
|
||||
|
||||
#region Container Helpers
|
||||
|
||||
public bool TryGetContainingContainer(EntityUid uid, [NotNullWhen(true)] out BaseContainer? container, MetaDataComponent? meta = null, TransformComponent? transform = null)
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public bool TryGetContainingContainer(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] out BaseContainer? container,
|
||||
MetaDataComponent? meta = null,
|
||||
TransformComponent? transform = null)
|
||||
{
|
||||
return TryGetContainingContainer((uid, transform, meta), out container);
|
||||
}
|
||||
|
||||
public bool TryGetContainingContainer(
|
||||
Entity<TransformComponent?, MetaDataComponent?> ent,
|
||||
[NotNullWhen(true)] out BaseContainer? container)
|
||||
{
|
||||
container = null;
|
||||
|
||||
if (!Resolve(uid, ref meta, false))
|
||||
if (!Resolve(ent, ref ent.Comp2, false))
|
||||
return false;
|
||||
|
||||
if ((meta.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
|
||||
if ((ent.Comp2.Flags & MetaDataFlags.InContainer) == MetaDataFlags.None)
|
||||
return false;
|
||||
|
||||
if (!Resolve(uid, ref transform, false))
|
||||
if (!Resolve(ent, ref ent.Comp1, false))
|
||||
return false;
|
||||
|
||||
return TryGetContainingContainer(transform.ParentUid, uid, out container);
|
||||
return TryGetContainingContainer(ent.Comp1.ParentUid, ent, out container);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -356,10 +399,19 @@ namespace Robust.Shared.Containers
|
||||
return TryFindComponentsOnEntityContainerOrParent(xform.ParentUid, entityQuery, foundComponents);
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public bool IsInSameOrNoContainer(EntityUid user, EntityUid other)
|
||||
{
|
||||
return IsInSameOrNoContainer((user, null, null), (other, null, null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the two entities are not contained, or are contained in the same container.
|
||||
/// </summary>
|
||||
public bool IsInSameOrNoContainer(EntityUid user, EntityUid other)
|
||||
public bool IsInSameOrNoContainer(
|
||||
Entity<TransformComponent?, MetaDataComponent?> user,
|
||||
Entity<TransformComponent?, MetaDataComponent?> other)
|
||||
{
|
||||
var isUserContained = TryGetContainingContainer(user, out var userContainer);
|
||||
var isOtherContained = TryGetContainingContainer(other, out var otherContainer);
|
||||
@@ -374,14 +426,33 @@ namespace Robust.Shared.Containers
|
||||
return userContainer == otherContainer;
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public bool IsInSameOrParentContainer(EntityUid user, EntityUid other)
|
||||
{
|
||||
return IsInSameOrParentContainer((user, null), other);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the two entities are not contained, or are contained in the same container, or if one
|
||||
/// entity contains the other (i.e., is the parent).
|
||||
/// </summary>
|
||||
public bool IsInSameOrParentContainer(EntityUid user, EntityUid other)
|
||||
public bool IsInSameOrParentContainer(
|
||||
Entity<TransformComponent?, MetaDataComponent?> user,
|
||||
Entity<TransformComponent?, MetaDataComponent?> other)
|
||||
{
|
||||
var isUserContained = TryGetContainingContainer(user, out var userContainer);
|
||||
var isOtherContained = TryGetContainingContainer(other, out var otherContainer);
|
||||
return IsInSameOrParentContainer(user, other, out _, out _);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsInSameOrParentContainer(Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?},Robust.Shared.GameObjects.Entity{Robust.Shared.GameObjects.TransformComponent?})"/>
|
||||
public bool IsInSameOrParentContainer(
|
||||
Entity<TransformComponent?, MetaDataComponent?> user,
|
||||
Entity<TransformComponent?, MetaDataComponent?> other,
|
||||
out BaseContainer? userContainer,
|
||||
out BaseContainer? otherContainer)
|
||||
{
|
||||
var isUserContained = TryGetContainingContainer(user, out userContainer);
|
||||
var isOtherContained = TryGetContainingContainer(other, out otherContainer);
|
||||
|
||||
// Both entities are not in a container
|
||||
if (!isUserContained && !isOtherContained) return true;
|
||||
@@ -396,6 +467,21 @@ namespace Robust.Shared.Containers
|
||||
return userContainer == otherContainer;
|
||||
}
|
||||
|
||||
[Obsolete("Use Entity<T> variant")]
|
||||
public bool IsInSameOrTransparentContainer(
|
||||
EntityUid user,
|
||||
EntityUid other,
|
||||
BaseContainer? userContainer = null,
|
||||
BaseContainer? otherContainer = null,
|
||||
bool userSeeInsideSelf = false)
|
||||
{
|
||||
return IsInSameOrTransparentContainer((user, null),
|
||||
other,
|
||||
userContainer,
|
||||
otherContainer,
|
||||
userSeeInsideSelf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a given entity can see another entity despite whatever containers they may be in.
|
||||
/// </summary>
|
||||
@@ -407,8 +493,8 @@ namespace Robust.Shared.Containers
|
||||
/// this means that the two entity arguments are NOT interchangeable.
|
||||
/// </remarks>
|
||||
public bool IsInSameOrTransparentContainer(
|
||||
EntityUid user,
|
||||
EntityUid other,
|
||||
Entity<TransformComponent?, MetaDataComponent?> user,
|
||||
Entity<TransformComponent?, MetaDataComponent?> other,
|
||||
BaseContainer? userContainer = null,
|
||||
BaseContainer? otherContainer = null,
|
||||
bool userSeeInsideSelf = false)
|
||||
@@ -433,11 +519,11 @@ namespace Robust.Shared.Containers
|
||||
|
||||
// Is the user in a see-through container?
|
||||
if (userContainer?.ShowContents ?? false)
|
||||
return IsInSameOrTransparentContainer(userContainer.Owner, other, otherContainer: otherContainer);
|
||||
return IsInSameOrTransparentContainer((userContainer.Owner, null, null), other, otherContainer: otherContainer);
|
||||
|
||||
// Is the other entity in a see-through container?
|
||||
if (otherContainer?.ShowContents ?? false)
|
||||
return IsInSameOrTransparentContainer(user, otherContainer.Owner, userContainer: userContainer, userSeeInsideSelf: userSeeInsideSelf);
|
||||
return IsInSameOrTransparentContainer(user, (otherContainer.Owner, null, null), userContainer: userContainer, userSeeInsideSelf: userSeeInsideSelf);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -442,7 +442,14 @@ Types:
|
||||
AddressFamily: { }
|
||||
System.Numerics:
|
||||
BitOperations: { All: True }
|
||||
Complex: { All: True }
|
||||
Matrix3x2: { All: True }
|
||||
Matrix4x4: { All: True }
|
||||
Plane: { All: True }
|
||||
Quaternion: { All: True }
|
||||
Vector2: { All: True }
|
||||
Vector3: { All: True }
|
||||
Vector4: { All: True }
|
||||
System.Reflection:
|
||||
Assembly:
|
||||
Methods:
|
||||
|
||||
@@ -289,6 +289,12 @@ namespace Robust.Shared.GameObjects
|
||||
return GetRegistration(componentType).Name;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public string GetComponentName<T>() where T : IComponent, new()
|
||||
{
|
||||
return GetRegistration<T>().Name;
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public string GetComponentName(ushort netID)
|
||||
{
|
||||
@@ -324,7 +330,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public ComponentRegistration GetRegistration<T>() where T : IComponent, new()
|
||||
{
|
||||
return GetRegistration(typeof(T));
|
||||
return GetRegistration(CompIdx.Index<T>());
|
||||
}
|
||||
|
||||
public ComponentRegistration GetRegistration(IComponent component)
|
||||
|
||||
@@ -1,43 +1,56 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
namespace Robust.Shared.GameObjects;
|
||||
|
||||
[RequiresSerializable]
|
||||
[Serializable, NetSerializable]
|
||||
[Virtual]
|
||||
public abstract class ComponentState : IComponentState;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the state of a component for networking purposes.
|
||||
/// </summary>
|
||||
public interface IComponentState;
|
||||
|
||||
public interface IComponentDeltaState : IComponentState
|
||||
{
|
||||
[RequiresSerializable]
|
||||
[Serializable, NetSerializable]
|
||||
[Virtual]
|
||||
public class ComponentState : IComponentState
|
||||
{
|
||||
public void ApplyToFullState(IComponentState fullState);
|
||||
|
||||
}
|
||||
public IComponentState CreateNewFullState(IComponentState fullState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for component states that only contain partial state data. The actual delta state class should be a
|
||||
/// separate class from the full component states.
|
||||
/// </summary>
|
||||
/// <typeparam name="TState">The full-state class associated with this partial state</typeparam>
|
||||
public interface IComponentDeltaState<TState> : IComponentDeltaState where TState: IComponentState
|
||||
{
|
||||
/// <summary>
|
||||
/// This function will apply the current delta state to the provided full state, modifying it in the process.
|
||||
/// </summary>
|
||||
public void ApplyToFullState(TState fullState);
|
||||
|
||||
/// <summary>
|
||||
/// Represents the state of a component for networking purposes.
|
||||
/// This function should take in a full state and return a new full state with the current delta applied, WITHOUT
|
||||
/// modifying the original input state.
|
||||
/// </summary>
|
||||
public interface IComponentState
|
||||
{
|
||||
public TState CreateNewFullState(TState fullState);
|
||||
|
||||
void IComponentDeltaState.ApplyToFullState(IComponentState fullState)
|
||||
{
|
||||
if (fullState is TState state)
|
||||
ApplyToFullState(state);
|
||||
else
|
||||
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for components that support delta-states.
|
||||
/// </summary>
|
||||
public interface IComponentDeltaState
|
||||
IComponentState IComponentDeltaState.CreateNewFullState(IComponentState fullState)
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this state is a delta or full state.
|
||||
/// </summary>
|
||||
bool FullState { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This function will apply the current delta state to the provided full state, modifying it in the process.
|
||||
/// </summary>
|
||||
public void ApplyToFullState(IComponentState fullState);
|
||||
|
||||
/// <summary>
|
||||
/// This function should take in a full state and return a new full state with the current delta applied,
|
||||
/// WITHOUT modifying the original input state.
|
||||
/// </summary>
|
||||
public IComponentState CreateNewFullState(IComponentState fullState);
|
||||
if (fullState is TState state)
|
||||
return CreateNewFullState(state);
|
||||
else
|
||||
throw new Exception($"Unexpected type. Expected {nameof(TState)} but got {fullState.GetType().Name}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -71,12 +71,17 @@ 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(EntityUid actor, EntityUid target, Enum uiKey)
|
||||
public sealed class BoundUserInterfaceMessageAttempt(
|
||||
EntityUid actor,
|
||||
EntityUid target,
|
||||
Enum uiKey,
|
||||
BoundUserInterfaceMessage message)
|
||||
: CancellableEntityEventArgs
|
||||
{
|
||||
public readonly EntityUid Actor = actor;
|
||||
public readonly EntityUid Target = target;
|
||||
public readonly Enum UiKey = uiKey;
|
||||
public readonly BoundUserInterfaceMessage Message = message;
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
@@ -130,12 +135,12 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
internal sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
|
||||
public sealed class OpenBoundInterfaceMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
[NetSerializable, Serializable]
|
||||
internal sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage
|
||||
public sealed class CloseBoundInterfaceMessage : BoundUserInterfaceMessage
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ namespace Robust.Shared.GameObjects
|
||||
private const int EntityCapacity = 1024;
|
||||
private const int NetComponentCapacity = 8;
|
||||
|
||||
private static readonly IComponentState DefaultComponentState = new ComponentState();
|
||||
|
||||
private FrozenDictionary<Type, Dictionary<EntityUid, IComponent>> _entTraitDict
|
||||
= FrozenDictionary<Type, Dictionary<EntityUid, IComponent>>.Empty;
|
||||
|
||||
@@ -1404,13 +1402,13 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session, GameTick fromTick)
|
||||
public IComponentState? GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? session, GameTick fromTick)
|
||||
{
|
||||
DebugTools.Assert(component.NetSyncEnabled, $"Attempting to get component state for an un-synced component: {component.GetType()}");
|
||||
var getState = new ComponentGetState(session, fromTick);
|
||||
eventBus.RaiseComponentEvent(component, ref getState);
|
||||
|
||||
return getState.State ?? DefaultComponentState;
|
||||
return getState.State;
|
||||
}
|
||||
|
||||
public bool CanGetComponentState(IEventBus eventBus, IComponent component, ICommonSession player)
|
||||
@@ -1586,6 +1584,13 @@ namespace Robust.Shared.GameObjects
|
||||
return default;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[Pure]
|
||||
public TComp1 Comp(EntityUid uid)
|
||||
{
|
||||
return GetComponent(uid);
|
||||
}
|
||||
|
||||
#region Internal
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,6 +15,7 @@ using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -40,6 +41,7 @@ namespace Robust.Shared.GameObjects
|
||||
[IoC.Dependency] private readonly ISerializationManager _serManager = default!;
|
||||
[IoC.Dependency] private readonly ProfManager _prof = default!;
|
||||
[IoC.Dependency] private readonly INetManager _netMan = default!;
|
||||
[IoC.Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
|
||||
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
|
||||
// positions on spawn....
|
||||
@@ -125,7 +127,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (Initialized)
|
||||
throw new InvalidOperationException("Initialize() called multiple times");
|
||||
|
||||
_eventBus = new EntityEventBus(this);
|
||||
_eventBus = new EntityEventBus(this, _reflection);
|
||||
|
||||
InitializeComponents();
|
||||
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
|
||||
@@ -281,6 +283,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
#region Entity Management
|
||||
|
||||
/// <inheritdoc />
|
||||
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
|
||||
{
|
||||
return CreateEntity(prototypeName, out _, overrides);
|
||||
|
||||
@@ -38,11 +38,10 @@ namespace Robust.Shared.GameObjects
|
||||
[Serializable, NetSerializable]
|
||||
public readonly struct ComponentChange
|
||||
{
|
||||
// 15ish bytes to create a component (strings are big), 5 bytes to remove one
|
||||
|
||||
/// <summary>
|
||||
/// State data for the created/modified component, if any.
|
||||
/// </summary>
|
||||
public readonly IComponentState State;
|
||||
public readonly IComponentState? State;
|
||||
|
||||
/// <summary>
|
||||
/// The Network ID of the component to remove.
|
||||
@@ -51,7 +50,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
public readonly GameTick LastModifiedTick;
|
||||
|
||||
public ComponentChange(ushort netId, IComponentState state, GameTick lastModifiedTick)
|
||||
public ComponentChange(ushort netId, IComponentState? state, GameTick lastModifiedTick)
|
||||
{
|
||||
State = state;
|
||||
NetID = netId;
|
||||
|
||||
@@ -109,6 +109,17 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptions.Add(new SubBroadcast<EntitySessionMessage<T>>(src));
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
EntityEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
/// <seealso cref="SubscribeLocalEvent{TComp, TEvent}(ComponentEventRefHandler{TComp, TEvent}, Type[], Type[])"/>
|
||||
// [Obsolete("Subscribe to the event by ref instead (ComponentEventRefHandler)")]
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
@@ -133,17 +144,6 @@ namespace Robust.Shared.GameObjects
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
protected void SubscribeLocalEvent<TComp, TEvent>(
|
||||
EntityEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : notnull
|
||||
{
|
||||
EntityManager.EventBus.SubscribeLocalEvent(handler, GetType(), before, after);
|
||||
|
||||
_subscriptions.Add(new SubLocal<TComp, TEvent>());
|
||||
}
|
||||
|
||||
private void ShutdownSubscriptions()
|
||||
{
|
||||
foreach (var sub in _subscriptions)
|
||||
|
||||
@@ -160,6 +160,9 @@ namespace Robust.Shared.GameObjects
|
||||
[Pure]
|
||||
string GetComponentName(Type componentType);
|
||||
|
||||
[Pure]
|
||||
string GetComponentName<T>() where T : IComponent, new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of a component, throwing an exception if it does not exist.
|
||||
/// </summary>
|
||||
|
||||
@@ -404,7 +404,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// <param name="player">The player that is going to receive this state. Null implies that this state is for a replay.</param>
|
||||
/// <param name="fromTick">The from tick, which indicates the range of data that must be included for delta-states.</param>
|
||||
/// <returns>The component state of the component.</returns>
|
||||
IComponentState GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player, GameTick fromTick);
|
||||
IComponentState? GetComponentState(IEventBus eventBus, IComponent component, ICommonSession? player, GameTick fromTick);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a certain player should get a component state.
|
||||
|
||||
@@ -71,12 +71,43 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
public event Action? AfterEntityFlush;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <param name="euid">Does nothing. Used to be the forced EntityUid of the new entity.</param>
|
||||
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
|
||||
[Obsolete($"Use one of the other {nameof(CreateEntityUninitialized)} overloads. euid no longer does anything.")]
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity and sets its position to the EntityCoordinates provided.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <param name="coordinates">Coordinates to set position and parent of the newly spawned entity to.</param>
|
||||
/// <param name="overrides"><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></param>
|
||||
/// <returns><inheritdoc cref="CreateEntityUninitialized(string?, MapCoordinates , ComponentRegistry?, Angle)"/></returns>
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an uninitialized entity and puts it on the grid or map at the MapCoordinates provided.
|
||||
/// </summary>
|
||||
/// <param name="prototypeName">Name of the <see cref="EntityPrototype"/> to spawn.</param>
|
||||
/// <param name="coordinates">Coordinates to place the newly spawned entity.</param>
|
||||
/// <param name="overrides">Overrides to add or remove components that differ from the prototype.</param>
|
||||
/// <param name="rotation">Map rotation to set the newly spawned entity to.</param>
|
||||
/// <returns>A new uninitialized entity.</returns>
|
||||
/// <remarks>If there is a grid at the <paramref name="coordinates"/>, the entity will be parented to the grid.
|
||||
/// Otherwise, it will be parented to the map.</remarks>
|
||||
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
|
||||
|
||||
void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null);
|
||||
|
||||
@@ -24,7 +24,7 @@ internal sealed class PrototypeReloadSystem : EntitySystem
|
||||
if (!eventArgs.ByType.TryGetValue(typeof(EntityPrototype), out var set))
|
||||
return;
|
||||
|
||||
var query = EntityQueryEnumerator<MetaDataComponent>();
|
||||
var query = AllEntityQuery<MetaDataComponent>();
|
||||
while (query.MoveNext(out var uid, out var metadata))
|
||||
{
|
||||
var id = metadata.EntityPrototype?.ID;
|
||||
|
||||
@@ -244,40 +244,51 @@ public abstract partial class SharedMapSystem
|
||||
|
||||
private void OnGridHandleState(EntityUid uid, MapGridComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not MapGridComponentState state)
|
||||
return;
|
||||
|
||||
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
|
||||
"Can't modify chunk size of an existing grid.");
|
||||
|
||||
component.ChunkSize = state.ChunkSize;
|
||||
if (state.ChunkData == null && state.FullGridData == null)
|
||||
return;
|
||||
|
||||
var modifiedChunks = new HashSet<MapChunk>();
|
||||
|
||||
// delta state
|
||||
if (state.ChunkData != null)
|
||||
HashSet<MapChunk> modifiedChunks;
|
||||
switch (args.Current)
|
||||
{
|
||||
foreach (var chunkData in state.ChunkData)
|
||||
case MapGridComponentDeltaState delta:
|
||||
{
|
||||
ApplyChunkData(uid, component, chunkData, modifiedChunks);
|
||||
}
|
||||
}
|
||||
modifiedChunks = new();
|
||||
DebugTools.Assert(component.ChunkSize == delta.ChunkSize || component.Chunks.Count == 0,
|
||||
"Can't modify chunk size of an existing grid.");
|
||||
|
||||
// full state
|
||||
if (state.FullGridData != null)
|
||||
{
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.FullGridData.ContainsKey(index))
|
||||
ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
|
||||
}
|
||||
component.ChunkSize = delta.ChunkSize;
|
||||
if (delta.ChunkData == null)
|
||||
return;
|
||||
|
||||
foreach (var (index, tiles) in state.FullGridData)
|
||||
{
|
||||
ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
|
||||
foreach (var chunkData in delta.ChunkData)
|
||||
{
|
||||
ApplyChunkData(uid, component, chunkData, modifiedChunks);
|
||||
}
|
||||
|
||||
component.LastTileModifiedTick = delta.LastTileModifiedTick;
|
||||
break;
|
||||
}
|
||||
case MapGridComponentState state:
|
||||
{
|
||||
modifiedChunks = new();
|
||||
DebugTools.Assert(component.ChunkSize == state.ChunkSize || component.Chunks.Count == 0,
|
||||
"Can't modify chunk size of an existing grid.");
|
||||
|
||||
component.LastTileModifiedTick = state.LastTileModifiedTick;
|
||||
component.ChunkSize = state.ChunkSize;
|
||||
|
||||
foreach (var index in component.Chunks.Keys)
|
||||
{
|
||||
if (!state.FullGridData.ContainsKey(index))
|
||||
ApplyChunkData(uid, component, ChunkDatum.CreateDeleted(index), modifiedChunks);
|
||||
}
|
||||
|
||||
foreach (var (index, tiles) in state.FullGridData)
|
||||
{
|
||||
ApplyChunkData(uid, component, ChunkDatum.CreateModified(index, tiles), modifiedChunks);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
var count = component.Chunks.Count;
|
||||
@@ -409,7 +420,7 @@ public abstract partial class SharedMapSystem
|
||||
}
|
||||
}
|
||||
|
||||
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
|
||||
args.State = new MapGridComponentDeltaState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
|
||||
|
||||
#if DEBUG
|
||||
if (chunkData == null)
|
||||
@@ -445,7 +456,7 @@ public abstract partial class SharedMapSystem
|
||||
chunkData.Add(index, tileBuffer);
|
||||
}
|
||||
|
||||
args.State = new MapGridComponentState(component.ChunkSize, chunkData);
|
||||
args.State = new MapGridComponentState(component.ChunkSize, chunkData, component.LastTileModifiedTick);
|
||||
|
||||
#if DEBUG
|
||||
foreach (var chunk in chunkData.Values)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Robust.Shared.Map;
|
||||
|
||||
@@ -36,34 +34,23 @@ public abstract partial class SharedTransformSystem
|
||||
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
|
||||
public EntityCoordinates WithEntityId(EntityCoordinates coordinates, EntityUid entity)
|
||||
{
|
||||
var mapPos = ToMapCoordinates(coordinates);
|
||||
|
||||
// You'd think this would throw like ToCoordinates does but TODO check that.
|
||||
if (mapPos.MapId == MapId.Nullspace)
|
||||
{
|
||||
return new EntityCoordinates(entity, Vector2.Zero);
|
||||
}
|
||||
|
||||
var xform = XformQuery.GetComponent(entity);
|
||||
|
||||
if (xform.MapID != mapPos.MapId)
|
||||
{
|
||||
return new EntityCoordinates(entity, Vector2.Zero);
|
||||
}
|
||||
|
||||
var localPos = GetInvWorldMatrix(xform).Transform(mapPos.Position);
|
||||
return new EntityCoordinates(entity, localPos);
|
||||
return entity == coordinates.EntityId
|
||||
? coordinates
|
||||
: ToCoordinates(entity, ToMapCoordinates(coordinates));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts entity-local coordinates into map terms.
|
||||
/// </summary>
|
||||
public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates)
|
||||
public MapCoordinates ToMapCoordinates(EntityCoordinates coordinates, bool logError = true)
|
||||
{
|
||||
if (!IsValid(coordinates))
|
||||
if (!TryComp(coordinates.EntityId, out TransformComponent? xform))
|
||||
{
|
||||
if (logError)
|
||||
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}");
|
||||
return MapCoordinates.Nullspace;
|
||||
}
|
||||
|
||||
var xform = XformQuery.GetComponent(coordinates.EntityId);
|
||||
var worldPos = GetWorldMatrix(xform).Transform(coordinates.Position);
|
||||
return new MapCoordinates(worldPos, xform.MapID);
|
||||
}
|
||||
@@ -79,17 +66,129 @@ public abstract partial class SharedTransformSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates EntityCoordinates given an entity and some MapCoordinates.
|
||||
/// Creates EntityCoordinates given an entity and some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
|
||||
public EntityCoordinates ToCoordinates(EntityUid entity, MapCoordinates coordinates)
|
||||
public EntityCoordinates ToCoordinates(Entity<TransformComponent?> entity, MapCoordinates coordinates)
|
||||
{
|
||||
var xform = XformQuery.GetComponent(entity);
|
||||
if (!Resolve(entity, ref entity.Comp, false))
|
||||
{
|
||||
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}");
|
||||
return default;
|
||||
}
|
||||
|
||||
if (xform.MapID != coordinates.MapId)
|
||||
throw new InvalidOperationException("Entity is not on the same map!");
|
||||
if (entity.Comp.MapID != coordinates.MapId)
|
||||
{
|
||||
Log.Error($"Attempted to convert map coordinates {coordinates} to entity coordinates on a different map. Entity: {ToPrettyString(entity)}");
|
||||
return default;
|
||||
}
|
||||
|
||||
var localPos = GetInvWorldMatrix(xform).Transform(coordinates.Position);
|
||||
var localPos = GetInvWorldMatrix(entity.Comp).Transform(coordinates.Position);
|
||||
return new EntityCoordinates(entity, localPos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates map-relative <see cref="EntityCoordinates"/> given some <see cref="MapCoordinates"/>.
|
||||
/// </summary>
|
||||
public EntityCoordinates ToCoordinates(MapCoordinates coordinates)
|
||||
{
|
||||
if (_map.TryGetMap(coordinates.MapId, out var uid))
|
||||
return ToCoordinates(uid.Value, coordinates);
|
||||
|
||||
Log.Error($"Attempted to convert map coordinates with unknown map id: {coordinates}");
|
||||
return default;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the grid that the entity whose position the coordinates are relative to is on.
|
||||
/// </summary>
|
||||
public EntityUid? GetGrid(EntityCoordinates coordinates)
|
||||
{
|
||||
return GetGrid(coordinates.EntityId);
|
||||
}
|
||||
|
||||
public EntityUid? GetGrid(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.GridUid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Map Id these coordinates are on.
|
||||
/// </summary>
|
||||
public MapId GetMapId(EntityCoordinates coordinates)
|
||||
{
|
||||
return GetMapId(coordinates.EntityId);
|
||||
}
|
||||
|
||||
public MapId GetMapId(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? MapId.Nullspace : entity.Comp.MapID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the Map that these coordinates are on.
|
||||
/// </summary>
|
||||
public EntityUid? GetMap(EntityCoordinates coordinates)
|
||||
{
|
||||
return GetMap(coordinates.EntityId);
|
||||
}
|
||||
|
||||
public EntityUid? GetMap(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.MapUid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two sets of coordinates to see if they are in range of each other.
|
||||
/// </summary>
|
||||
/// <param name="range">maximum distance between the two sets of coordinates.</param>
|
||||
/// <returns>True if the two points are within a given range.</returns>
|
||||
public bool InRange(EntityCoordinates coordA, EntityCoordinates coordB, float range)
|
||||
{
|
||||
if (!coordA.EntityId.IsValid() || !coordB.EntityId.IsValid())
|
||||
return false;
|
||||
|
||||
if (coordA.EntityId == coordB.EntityId)
|
||||
return (coordA.Position - coordB.Position).LengthSquared() < range * range;
|
||||
|
||||
var mapA = ToMapCoordinates(coordA, logError:false);
|
||||
var mapB = ToMapCoordinates(coordB, logError:false);
|
||||
|
||||
if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
return mapA.InRange(mapB, range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares the positions of two entities to see if they are within some specified distance of each other.
|
||||
/// </summary>
|
||||
public bool InRange(Entity<TransformComponent?> entA, Entity<TransformComponent?> entB, float range)
|
||||
{
|
||||
if (!Resolve(entA, ref entA.Comp))
|
||||
return false;
|
||||
|
||||
if (!Resolve(entB, ref entB.Comp))
|
||||
return false;
|
||||
|
||||
if (!entA.Comp.ParentUid.IsValid() || !entB.Comp.ParentUid.IsValid())
|
||||
return false;
|
||||
|
||||
if (entA.Comp.ParentUid == entB.Comp.ParentUid)
|
||||
return (entA.Comp.LocalPosition - entB.Comp.LocalPosition).LengthSquared() < range * range;
|
||||
|
||||
if (entA.Comp.ParentUid == entB.Owner)
|
||||
return entA.Comp.LocalPosition.LengthSquared() < range * range;
|
||||
|
||||
if (entB.Comp.ParentUid == entA.Owner)
|
||||
return entB.Comp.LocalPosition.LengthSquared() < range * range;
|
||||
|
||||
var mapA = GetMapCoordinates(entA!);
|
||||
var mapB = GetMapCoordinates(entB!);
|
||||
|
||||
if (mapA.MapId != mapB.MapId || mapA.MapId == MapId.Nullspace)
|
||||
return false;
|
||||
|
||||
return mapA.InRange(mapB, range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Threading;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -20,6 +21,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
[Dependency] private readonly IDynamicTypeFactory _factory = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly INetManager _netManager = default!;
|
||||
[Dependency] private readonly IParallelManager _parallel = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflection = default!;
|
||||
[Dependency] private readonly ISharedPlayerManager _player = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transforms = default!;
|
||||
@@ -29,6 +31,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
private EntityQuery<UserInterfaceComponent> _uiQuery;
|
||||
private EntityQuery<UserInterfaceUserComponent> _userQuery;
|
||||
|
||||
private ActorRangeCheckJob _rangeJob;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
@@ -38,6 +42,12 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
_uiQuery = GetEntityQuery<UserInterfaceComponent>();
|
||||
_userQuery = GetEntityQuery<UserInterfaceUserComponent>();
|
||||
|
||||
_rangeJob = new()
|
||||
{
|
||||
System = this,
|
||||
XformQuery = _xformQuery,
|
||||
};
|
||||
|
||||
SubscribeAllEvent<BoundUIWrapMessage>((msg, args) =>
|
||||
{
|
||||
if (args.SenderSession.AttachedEntity is not { } player)
|
||||
@@ -48,6 +58,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
SubscribeLocalEvent<UserInterfaceComponent, OpenBoundInterfaceMessage>(OnUserInterfaceOpen);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, CloseBoundInterfaceMessage>(OnUserInterfaceClosed);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentStartup>(OnUserInterfaceStartup);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentShutdown>(OnUserInterfaceShutdown);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentGetState>(OnUserInterfaceGetState);
|
||||
SubscribeLocalEvent<UserInterfaceComponent, ComponentHandleState>(OnUserInterfaceHandleState);
|
||||
@@ -55,28 +66,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentShutdown>(OnActorShutdown);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetStateAttemptEvent>(OnGetStateAttempt);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentGetState>(OnActorGetState);
|
||||
SubscribeLocalEvent<UserInterfaceUserComponent, ComponentHandleState>(OnActorHandleState);
|
||||
|
||||
_player.PlayerStatusChanged += OnStatusChange;
|
||||
}
|
||||
|
||||
private void OnStatusChange(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
var attachedEnt = e.Session.AttachedEntity;
|
||||
|
||||
if (attachedEnt == null)
|
||||
return;
|
||||
|
||||
// Content can't handle it yet sadly :(
|
||||
CloseUserUis(attachedEnt.Value);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_player.PlayerStatusChanged -= OnStatusChange;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,7 +105,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
// If it's a close message something else might try to cancel it but we want to force it.
|
||||
if (msg.Message is not CloseBoundInterfaceMessage && ui.RequireInputValidation)
|
||||
{
|
||||
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey);
|
||||
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey, msg.Message);
|
||||
RaiseLocalEvent(attempt);
|
||||
if (attempt.Cancelled)
|
||||
return;
|
||||
@@ -135,6 +128,11 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
#region User
|
||||
|
||||
private void OnActorShutdown(Entity<UserInterfaceUserComponent> ent, ref ComponentShutdown args)
|
||||
{
|
||||
CloseUserUis((ent.Owner, ent.Comp));
|
||||
}
|
||||
|
||||
private void OnGetStateAttempt(Entity<UserInterfaceUserComponent> ent, ref ComponentGetStateAttemptEvent args)
|
||||
{
|
||||
if (args.Cancelled || args.Player?.AttachedEntity != ent.Owner)
|
||||
@@ -207,11 +205,10 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (!uiComp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
|
||||
if (!uiComp.ClientOpenInterfaces.Remove(key, out var cBui))
|
||||
continue;
|
||||
|
||||
cBui.Dispose();
|
||||
uiComp.ClientOpenInterfaces.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -233,10 +230,8 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
Dirty(ent);
|
||||
|
||||
// If the actor is also deleting then don't worry about updating what they have open.
|
||||
if (!TerminatingOrDeleted(actor))
|
||||
if (!TerminatingOrDeleted(actor) && _userQuery.TryComp(actor, out var actorComp))
|
||||
{
|
||||
var actorComp = EnsureComp<UserInterfaceUserComponent>(actor);
|
||||
|
||||
if (actorComp.OpenInterfaces.TryGetValue(ent.Owner, out var keys))
|
||||
{
|
||||
keys.Remove(args.UiKey);
|
||||
@@ -282,6 +277,20 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
EnsureClientBui(ent, args.UiKey, ent.Comp.Interfaces[args.UiKey]);
|
||||
}
|
||||
|
||||
private void OnUserInterfaceStartup(Entity<UserInterfaceComponent> ent, ref ComponentStartup args)
|
||||
{
|
||||
// PlayerAttachedEvent will catch some of these.
|
||||
foreach (var (key, bui) in ent.Comp.ClientOpenInterfaces)
|
||||
{
|
||||
bui.Open();
|
||||
|
||||
if (ent.Comp.States.TryGetValue(key, out var state))
|
||||
{
|
||||
bui.UpdateState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUserInterfaceShutdown(EntityUid uid, UserInterfaceComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var bui in component.ClientOpenInterfaces.Values)
|
||||
@@ -387,17 +396,21 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (!ent.Comp.ClientOpenInterfaces.TryGetValue(key, out var cBui))
|
||||
continue;
|
||||
|
||||
cBui.State = buiState;
|
||||
cBui.UpdateState(buiState);
|
||||
}
|
||||
|
||||
// If UI not open then open it
|
||||
var attachedEnt = _player.LocalEntity;
|
||||
|
||||
// If we get the first state for an ent coming in then don't open BUIs yet, just defer it until later.
|
||||
var open = ent.Comp.LifeStage > ComponentLifeStage.Added;
|
||||
|
||||
if (attachedEnt != null)
|
||||
{
|
||||
foreach (var (key, value) in ent.Comp.Interfaces)
|
||||
{
|
||||
EnsureClientBui(ent, key, value);
|
||||
EnsureClientBui(ent, key, value, open);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,7 +418,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
/// <summary>
|
||||
/// Opens a client's BUI if not already open and applies the state to it.
|
||||
/// </summary>
|
||||
private void EnsureClientBui(Entity<UserInterfaceComponent> entity, Enum key, InterfaceData data)
|
||||
private void EnsureClientBui(Entity<UserInterfaceComponent> entity, Enum key, InterfaceData data, bool open = true)
|
||||
{
|
||||
// If it's out BUI open it up and apply the state, otherwise do nothing.
|
||||
var player = _player.LocalEntity;
|
||||
@@ -428,10 +441,16 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
var boundUserInterface = (BoundUserInterface) _factory.CreateInstance(type, [entity.Owner, key]);
|
||||
|
||||
entity.Comp.ClientOpenInterfaces[key] = boundUserInterface;
|
||||
|
||||
// This is just so we don't open while applying UI states.
|
||||
if (!open)
|
||||
return;
|
||||
|
||||
boundUserInterface.Open();
|
||||
|
||||
if (entity.Comp.States.TryGetValue(key, out var buiState))
|
||||
{
|
||||
boundUserInterface.State = buiState;
|
||||
boundUserInterface.UpdateState(buiState);
|
||||
}
|
||||
}
|
||||
@@ -873,12 +892,31 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), msg, bui.UiKey));
|
||||
}
|
||||
|
||||
public bool TryGetInterfaceData(Entity<UserInterfaceComponent?> entity,
|
||||
Enum key,
|
||||
[NotNullWhen(true)] out InterfaceData? data)
|
||||
{
|
||||
data = null;
|
||||
return Resolve(entity, ref entity.Comp, false) && entity.Comp.Interfaces.TryGetValue(key, out data);
|
||||
}
|
||||
|
||||
public float GetUiRange(Entity<UserInterfaceComponent?> entity, Enum key)
|
||||
{
|
||||
TryGetInterfaceData(entity, key, out var data);
|
||||
return data?.InteractionRange ?? 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
var query = AllEntityQuery<ActiveUserInterfaceComponent, UserInterfaceComponent>();
|
||||
// Run these in parallel because it's expensive.
|
||||
_rangeJob.ActorRanges.Clear();
|
||||
|
||||
// Handles closing the BUI if actors move out of range of them.
|
||||
// TODO iterate over BUI users, not BUI entities.
|
||||
// I.e., a user may have more than one BUI open, but its rare for a bui to be open by more than one user.
|
||||
// This means we won't have to fetch the user's transform as frequently.
|
||||
while (query.MoveNext(out var uid, out _, out var uiComp))
|
||||
{
|
||||
foreach (var (key, actors) in uiComp.Actors)
|
||||
@@ -887,106 +925,124 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
var data = uiComp.Interfaces[key];
|
||||
|
||||
// Short-circuit
|
||||
if (data.InteractionRange <= 0f || actors.Count == 0)
|
||||
if (data.InteractionRange <= 0f)
|
||||
continue;
|
||||
|
||||
// Okay so somehow UISystem is high up on the server profile
|
||||
// If that's actually still a problem turn this into an IParallelRobustJob and slam all the UIs in parallel.
|
||||
var xform = _xformQuery.GetComponent(uid);
|
||||
var coordinates = xform.Coordinates;
|
||||
var mapId = xform.MapID;
|
||||
|
||||
for (var i = actors.Count - 1; i >= 0; i--)
|
||||
foreach (var actor in actors)
|
||||
{
|
||||
var actor = actors[i];
|
||||
|
||||
if (CheckRange(uid, key, data, actor, coordinates, mapId))
|
||||
continue;
|
||||
|
||||
// Using the non-predicted one here seems fine?
|
||||
CloseUi((uid, uiComp), key, actor);
|
||||
_rangeJob.ActorRanges.Add((uid, key, data, actor, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_parallel.ProcessNow(_rangeJob, _rangeJob.ActorRanges.Count);
|
||||
|
||||
foreach (var data in _rangeJob.ActorRanges)
|
||||
{
|
||||
var uid = data.Ui;
|
||||
var actor = data.Actor;
|
||||
var key = data.Key;
|
||||
|
||||
if (data.Result || Deleted(uid) || Deleted(actor) || !_uiQuery.TryComp(uid, out var uiComp))
|
||||
continue;
|
||||
|
||||
CloseUi((uid, uiComp), key, actor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that the subscribed clients are still in range of the interface.
|
||||
/// </summary>
|
||||
private bool CheckRange(
|
||||
EntityUid uid,
|
||||
Entity<TransformComponent> UiEnt,
|
||||
Enum key,
|
||||
InterfaceData data,
|
||||
EntityUid actor,
|
||||
EntityCoordinates uiCoordinates,
|
||||
MapId uiMap)
|
||||
Entity<TransformComponent?> actor)
|
||||
{
|
||||
if (_ignoreUIRangeQuery.HasComponent(actor))
|
||||
return true;
|
||||
|
||||
if (!_xformQuery.TryGetComponent(actor, out var actorXform))
|
||||
if (!_xformQuery.Resolve(actor, ref actor.Comp) || actor.Comp.MapID != UiEnt.Comp.MapID)
|
||||
return false;
|
||||
|
||||
// Handle pluggable BoundUserInterfaceCheckRangeEvent
|
||||
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, key, data, actor);
|
||||
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
|
||||
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(UiEnt, key, data, actor!);
|
||||
RaiseLocalEvent(UiEnt.Owner, ref checkRangeEvent, true);
|
||||
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
|
||||
return true;
|
||||
|
||||
// We only check if the range check should be ignored if it did not pass.
|
||||
// The majority of the time the check will be passing and users generally do not have this component.
|
||||
if (_ignoreUIRangeQuery.HasComponent(actor))
|
||||
return true;
|
||||
|
||||
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
|
||||
return false;
|
||||
|
||||
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
|
||||
|
||||
if (uiMap != actorXform.MapID)
|
||||
return false;
|
||||
return _transforms.InRange(UiEnt!, actor, data.InteractionRange);
|
||||
}
|
||||
|
||||
return uiCoordinates.InRange(EntityManager, _transforms, actorXform.Coordinates, data.InteractionRange);
|
||||
/// <summary>
|
||||
/// Used for running UI raycast checks in parallel.
|
||||
/// </summary>
|
||||
private record struct ActorRangeCheckJob() : IParallelRobustJob
|
||||
{
|
||||
public EntityQuery<TransformComponent> XformQuery;
|
||||
public SharedUserInterfaceSystem System;
|
||||
public readonly List<(EntityUid Ui, Enum Key, InterfaceData Data, EntityUid Actor, bool Result)> ActorRanges = new();
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var data = ActorRanges[index];
|
||||
|
||||
if (!XformQuery.TryComp(data.Ui, out var uiXform))
|
||||
{
|
||||
data.Result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Result = System.CheckRange((data.Ui, uiXform), data.Key, data.Data, data.Actor);
|
||||
}
|
||||
|
||||
ActorRanges[index] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
|
||||
/// The event is raised directed at the entity that owns the interface.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
[PublicAPI]
|
||||
public struct BoundUserInterfaceCheckRangeEvent
|
||||
public struct BoundUserInterfaceCheckRangeEvent(
|
||||
Entity<TransformComponent> target,
|
||||
Enum uiKey,
|
||||
InterfaceData data,
|
||||
Entity<TransformComponent> actor)
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity owning the UI being checked for.
|
||||
/// </summary>
|
||||
public readonly EntityUid Target;
|
||||
public readonly EntityUid Target = target;
|
||||
|
||||
/// <summary>
|
||||
/// The UI itself.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public readonly Enum UiKey;
|
||||
public readonly Enum UiKey = uiKey;
|
||||
|
||||
public readonly InterfaceData Data;
|
||||
public readonly InterfaceData Data = data;
|
||||
|
||||
/// <summary>
|
||||
/// The player for which the UI is being checked.
|
||||
/// </summary>
|
||||
public readonly EntityUid Actor;
|
||||
public readonly Entity<TransformComponent> Actor = actor;
|
||||
|
||||
/// <summary>
|
||||
/// The result of the range check.
|
||||
/// </summary>
|
||||
public BoundUserInterfaceRangeResult Result;
|
||||
|
||||
public BoundUserInterfaceCheckRangeEvent(
|
||||
EntityUid target,
|
||||
Enum uiKey,
|
||||
InterfaceData data,
|
||||
EntityUid actor)
|
||||
{
|
||||
Target = target;
|
||||
UiKey = uiKey;
|
||||
Data = data;
|
||||
Actor = actor;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.GameStates
|
||||
{
|
||||
@@ -12,6 +13,7 @@ namespace Robust.Shared.GameStates
|
||||
|
||||
public ComponentHandleState(IComponentState? current, IComponentState? next)
|
||||
{
|
||||
DebugTools.Assert(current != null || next != null);
|
||||
Current = current;
|
||||
Next = next;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -335,48 +335,48 @@ namespace Robust.Shared.Map.Components
|
||||
/// Serialized state of a <see cref="MapGridComponentState"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class MapGridComponentState : ComponentState, IComponentDeltaState
|
||||
internal sealed class MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, Tile[]> fullGridData, GameTick lastTileModifiedTick) : ComponentState
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the chunks in the map grid.
|
||||
/// </summary>
|
||||
public ushort ChunkSize;
|
||||
|
||||
/// <summary>
|
||||
/// Networked chunk data.
|
||||
/// </summary>
|
||||
public List<ChunkDatum>? ChunkData;
|
||||
public ushort ChunkSize = chunkSize;
|
||||
|
||||
/// <summary>
|
||||
/// Networked chunk data containing the full grid state.
|
||||
/// </summary>
|
||||
public Dictionary<Vector2i, Tile[]>? FullGridData;
|
||||
|
||||
public bool FullState => FullGridData != null;
|
||||
public Dictionary<Vector2i, Tile[]> FullGridData = fullGridData;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new grid component delta state.
|
||||
/// Last game tick that the tile on the grid was modified.
|
||||
/// </summary>
|
||||
public MapGridComponentState(ushort chunkSize, List<ChunkDatum>? chunkData)
|
||||
{
|
||||
ChunkSize = chunkSize;
|
||||
ChunkData = chunkData;
|
||||
}
|
||||
public GameTick LastTileModifiedTick = lastTileModifiedTick;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialized state of a <see cref="MapGridComponentState"/>.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class MapGridComponentDeltaState(ushort chunkSize, List<ChunkDatum>? chunkData, GameTick lastTileModifiedTick)
|
||||
: ComponentState, IComponentDeltaState<MapGridComponentState>
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of the chunks in the map grid.
|
||||
/// </summary>
|
||||
public readonly ushort ChunkSize = chunkSize;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new full component state.
|
||||
/// Networked chunk data.
|
||||
/// </summary>
|
||||
public MapGridComponentState(ushort chunkSize, Dictionary<Vector2i, Tile[]> fullGridData)
|
||||
{
|
||||
ChunkSize = chunkSize;
|
||||
FullGridData = fullGridData;
|
||||
}
|
||||
public readonly List<ChunkDatum>? ChunkData = chunkData;
|
||||
|
||||
public void ApplyToFullState(IComponentState fullState)
|
||||
{
|
||||
var state = (MapGridComponentState)fullState;
|
||||
DebugTools.Assert(!FullState && state.FullState);
|
||||
/// <summary>
|
||||
/// Last game tick that the tile on the grid was modified.
|
||||
/// </summary>
|
||||
public GameTick LastTileModifiedTick = lastTileModifiedTick;
|
||||
|
||||
public void ApplyToFullState(MapGridComponentState state)
|
||||
{
|
||||
state.ChunkSize = ChunkSize;
|
||||
|
||||
if (ChunkData == null)
|
||||
@@ -389,14 +389,13 @@ namespace Robust.Shared.Map.Components
|
||||
else
|
||||
state.FullGridData![data.Index] = data.TileData;
|
||||
}
|
||||
|
||||
state.LastTileModifiedTick = LastTileModifiedTick;
|
||||
}
|
||||
|
||||
public IComponentState CreateNewFullState(IComponentState fullState)
|
||||
public MapGridComponentState CreateNewFullState(MapGridComponentState state)
|
||||
{
|
||||
var state = (MapGridComponentState)fullState;
|
||||
DebugTools.Assert(!FullState && state.FullState);
|
||||
|
||||
var fullGridData = new Dictionary<Vector2i, Tile[]>(state.FullGridData!.Count);
|
||||
var fullGridData = new Dictionary<Vector2i, Tile[]>(state.FullGridData.Count);
|
||||
|
||||
foreach (var (key, value) in state.FullGridData)
|
||||
{
|
||||
@@ -404,7 +403,7 @@ namespace Robust.Shared.Map.Components
|
||||
Array.Copy(value, arr, value.Length);
|
||||
}
|
||||
|
||||
var newState = new MapGridComponentState(ChunkSize, fullGridData);
|
||||
var newState = new MapGridComponentState(ChunkSize, fullGridData, LastTileModifiedTick);
|
||||
ApplyToFullState(newState);
|
||||
return newState;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
@@ -77,109 +76,55 @@ namespace Robust.Shared.Map
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms this set of coordinates from the entity's local space to the map space.
|
||||
/// </summary>
|
||||
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use ToMap() with TransformSystem overload")]
|
||||
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
|
||||
public MapCoordinates ToMap(IEntityManager entityManager)
|
||||
{
|
||||
return ToMap(entityManager, entityManager.System<SharedTransformSystem>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transforms this set of coordinates from the entity's local space to the map space.
|
||||
/// </summary>
|
||||
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
|
||||
/// <param name="transformSystem">Shared transform system for doing calculations.</param>
|
||||
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
|
||||
public MapCoordinates ToMap(IEntityManager entityManager, SharedTransformSystem transformSystem)
|
||||
{
|
||||
if(!IsValid(entityManager))
|
||||
return MapCoordinates.Nullspace;
|
||||
|
||||
var transform = entityManager.GetComponent<TransformComponent>(EntityId);
|
||||
var worldPos = transformSystem.GetWorldMatrix(transform).Transform(Position);
|
||||
return new MapCoordinates(worldPos, transform.MapID);
|
||||
return transformSystem.ToMapCoordinates(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform this set of coordinates from the entity's local space to the map space.
|
||||
/// </summary>
|
||||
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Use ToMapPos() with TransformSystem overload")]
|
||||
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
|
||||
public Vector2 ToMapPos(IEntityManager entityManager)
|
||||
{
|
||||
return ToMap(entityManager, entityManager.System<SharedTransformSystem>()).Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform this set of coordinates from the entity's local space to the map space.
|
||||
/// </summary>
|
||||
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
|
||||
/// <param name="transformSystem">Shared transform system for doing calculations.</param>
|
||||
[Obsolete("Use SharedTransformSystem.ToMapCoordinates()")]
|
||||
public Vector2 ToMapPos(IEntityManager entityManager, SharedTransformSystem transformSystem)
|
||||
{
|
||||
return ToMap(entityManager, transformSystem).Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates EntityCoordinates given an entity and some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
|
||||
[Obsolete("Use FromMap() with TransformSystem overload")]
|
||||
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
|
||||
public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
return FromMap(entity, coordinates, entMan.System<SharedTransformSystem>(), entMan);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates EntityCoordinates given an entity and some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="entity"/> is not on the same map as the <see cref="coordinates"/>.</exception>
|
||||
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
|
||||
public static EntityCoordinates FromMap(EntityUid entity, MapCoordinates coordinates, SharedTransformSystem transformSystem, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
var transform = entMan.GetComponent<TransformComponent>(entity);
|
||||
if(transform.MapID != coordinates.MapId)
|
||||
throw new InvalidOperationException("Entity is not on the same map!");
|
||||
|
||||
var localPos = transformSystem.GetInvWorldMatrix(transform).Transform(coordinates.Position);
|
||||
return new EntityCoordinates(entity, localPos);
|
||||
return transformSystem.ToCoordinates(entity, coordinates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates EntityCoordinates given an entity Uid and some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <param name="entityManager">Entity Manager containing the entity Id.</param>
|
||||
/// <param name="entityUid"></param>
|
||||
/// <param name="coordinates"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="entityUid"/> is not on the same map as the <see cref="coordinates"/>.</exception>
|
||||
[Obsolete("Use overload with other parameter order.")]
|
||||
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
|
||||
public static EntityCoordinates FromMap(IEntityManager entityManager, EntityUid entityUid, MapCoordinates coordinates)
|
||||
{
|
||||
return FromMap(entityUid, coordinates, entityManager.System<SharedTransformSystem>(), entityManager);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a set of EntityCoordinates given some MapCoordinates.
|
||||
/// </summary>
|
||||
/// <param name="mapManager"></param>
|
||||
/// <param name="coordinates"></param>
|
||||
[Obsolete("Use SharedTransformSystem.ToCoordinates()")]
|
||||
public static EntityCoordinates FromMap(IMapManager mapManager, MapCoordinates coordinates)
|
||||
{
|
||||
var mapId = coordinates.MapId;
|
||||
var mapEntity = mapManager.GetMapEntityId(mapId);
|
||||
|
||||
return new EntityCoordinates(mapEntity, coordinates.Position);
|
||||
return IoCManager.Resolve<IEntityManager>().System<SharedTransformSystem>().ToCoordinates(coordinates);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts this set of coordinates to Vector2i.
|
||||
/// </summary>
|
||||
[Obsolete("Use overload with TransformSystem")]
|
||||
public Vector2i ToVector2i(IEntityManager entityManager, IMapManager mapManager)
|
||||
{
|
||||
@@ -227,9 +172,10 @@ namespace Robust.Shared.Map
|
||||
/// <param name="entityManager">The Entity Manager holding this entity</param>
|
||||
/// <param name="entityId">The entity that the new coordinates will be local to</param>
|
||||
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
|
||||
[Obsolete("Use SharedTransformSystem.WithEntityId()")]
|
||||
public EntityCoordinates WithEntityId(IEntityManager entityManager, EntityUid entityId)
|
||||
{
|
||||
if(!entityManager.EntityExists(entityId))
|
||||
if (!entityManager.EntityExists(entityId))
|
||||
return new EntityCoordinates(entityId, Vector2.Zero);
|
||||
|
||||
return WithEntityId(entityId);
|
||||
@@ -240,6 +186,7 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity that the new coordinates will be local to</param>
|
||||
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
|
||||
[Obsolete("Use SharedTransformSystem.WithEntityId()")]
|
||||
public EntityCoordinates WithEntityId(EntityUid entity, IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
@@ -251,19 +198,13 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity that the new coordinates will be local to</param>
|
||||
/// <returns>A new set of EntityCoordinates local to a new entity.</returns>
|
||||
[Obsolete("Use SharedTransformSystem.WithEntityId()")]
|
||||
public EntityCoordinates WithEntityId(
|
||||
EntityUid entity,
|
||||
SharedTransformSystem transformSystem,
|
||||
IEntityManager? entMan = null)
|
||||
{
|
||||
IoCManager.Resolve(ref entMan);
|
||||
var mapPos = ToMap(entMan, transformSystem);
|
||||
|
||||
if(!IsValid(entMan) || entMan.GetComponent<TransformComponent>(entity).MapID != mapPos.MapId)
|
||||
return new EntityCoordinates(entity, Vector2.Zero);
|
||||
|
||||
var localPos = transformSystem.GetInvWorldMatrix(entity).Transform(mapPos.Position);
|
||||
return new EntityCoordinates(entity, localPos);
|
||||
return transformSystem.WithEntityId(this, entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -272,6 +213,7 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <returns>Grid EntityUid this entity is on or null</returns>
|
||||
[Obsolete("Use SharedTransformSystem.GetGrid()")]
|
||||
public EntityUid? GetGridUid(IEntityManager entityManager)
|
||||
{
|
||||
return !IsValid(entityManager) ? null : entityManager.GetComponent<TransformComponent>(EntityId).GridUid;
|
||||
@@ -283,6 +225,7 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <returns>Map Id these coordinates are on or <see cref="MapId.Nullspace"/></returns>
|
||||
[Obsolete("Use SharedTransformSystem.GetMapId()")]
|
||||
public MapId GetMapId(IEntityManager entityManager)
|
||||
{
|
||||
return !IsValid(entityManager) ? MapId.Nullspace : entityManager.GetComponent<TransformComponent>(EntityId).MapID;
|
||||
@@ -294,6 +237,7 @@ namespace Robust.Shared.Map
|
||||
/// </summary>
|
||||
/// <param name="entityManager"></param>
|
||||
/// <returns>Map Id these coordinates are on or null</returns>
|
||||
[Obsolete("Use SharedTransformSystem.GetMap()")]
|
||||
public EntityUid? GetMapUid(IEntityManager entityManager)
|
||||
{
|
||||
return !IsValid(entityManager) ? null : entityManager.GetComponent<TransformComponent>(EntityId).MapUid;
|
||||
@@ -316,38 +260,20 @@ namespace Robust.Shared.Map
|
||||
/// <param name="otherCoordinates">Other set of coordinates to use.</param>
|
||||
/// <param name="range">maximum distance between the two sets of coordinates.</param>
|
||||
/// <returns>True if the two points are within a given range.</returns>
|
||||
[Obsolete("Use overload with TransformSystem")]
|
||||
[Obsolete("Use TransformSystem.InRange()")]
|
||||
public bool InRange(IEntityManager entityManager, EntityCoordinates otherCoordinates, float range)
|
||||
{
|
||||
return InRange(entityManager, entityManager.System<SharedTransformSystem>(), otherCoordinates, range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two sets of coordinates to see if they are in range of each other.
|
||||
/// </summary>
|
||||
/// <param name="entityManager">Entity Manager containing the two entity Ids.</param>
|
||||
/// <param name="otherCoordinates">Other set of coordinates to use.</param>
|
||||
/// <param name="range">maximum distance between the two sets of coordinates.</param>
|
||||
/// <returns>True if the two points are within a given range.</returns>
|
||||
[Obsolete("Use TransformSystem.InRange()")]
|
||||
public bool InRange(
|
||||
IEntityManager entityManager,
|
||||
SharedTransformSystem transformSystem,
|
||||
EntityCoordinates otherCoordinates,
|
||||
float range)
|
||||
{
|
||||
if (!IsValid(entityManager) || !otherCoordinates.IsValid(entityManager))
|
||||
return false;
|
||||
|
||||
if (EntityId == otherCoordinates.EntityId)
|
||||
return (otherCoordinates.Position - Position).LengthSquared() < range * range;
|
||||
|
||||
var mapCoordinates = ToMap(entityManager, transformSystem);
|
||||
var otherMapCoordinates = otherCoordinates.ToMap(entityManager, transformSystem);
|
||||
|
||||
if (mapCoordinates.MapId != otherMapCoordinates.MapId)
|
||||
return false;
|
||||
|
||||
return mapCoordinates.InRange(otherMapCoordinates, range);
|
||||
return transformSystem.InRange(this, otherCoordinates, range);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
internal sealed class AuthManager : IAuthManager
|
||||
{
|
||||
public const string DefaultAuthServer = "https://central.spacestation14.io/auth/";
|
||||
public const string DefaultAuthServer = "https://auth.spacestation14.com/";
|
||||
|
||||
public NetUserId? UserId { get; set; }
|
||||
public string? Server { get; set; } = DefaultAuthServer;
|
||||
|
||||
@@ -249,6 +249,9 @@ namespace Robust.Shared.Network
|
||||
|
||||
IsServer = isServer;
|
||||
|
||||
_config.OnValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged);
|
||||
_config.OnValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged);
|
||||
|
||||
_config.OnValueChanged(CVars.NetVerbose, NetVerboseChanged);
|
||||
if (isServer)
|
||||
{
|
||||
@@ -272,6 +275,22 @@ namespace Robust.Shared.Network
|
||||
}
|
||||
}
|
||||
|
||||
private void LidgrenLogWarningChanged(bool newValue)
|
||||
{
|
||||
foreach (var netPeer in _netPeers)
|
||||
{
|
||||
netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.WarningMessage, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void LidgrenLogErrorChanged(bool newValue)
|
||||
{
|
||||
foreach (var netPeer in _netPeers)
|
||||
{
|
||||
netPeer.Peer.Configuration.SetMessageTypeEnabled(NetIncomingMessageType.ErrorMessage, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAuthModeChanged(int mode)
|
||||
{
|
||||
Auth = (AuthMode)mode;
|
||||
@@ -422,6 +441,8 @@ namespace Robust.Shared.Network
|
||||
_config.UnsubValueChanged(CVars.NetFakeLagMin, _fakeLagMinChanged);
|
||||
_config.UnsubValueChanged(CVars.NetFakeLagRand, _fakeLagRandomChanged);
|
||||
_config.UnsubValueChanged(CVars.NetFakeDuplicates, FakeDuplicatesChanged);
|
||||
_config.UnsubValueChanged(CVars.NetLidgrenLogWarning, LidgrenLogWarningChanged);
|
||||
_config.UnsubValueChanged(CVars.NetLidgrenLogError, LidgrenLogErrorChanged);
|
||||
|
||||
_serializer.ClientHandshakeComplete -= OnSerializerOnClientHandshakeComplete;
|
||||
|
||||
@@ -576,6 +597,14 @@ namespace Robust.Shared.Network
|
||||
// ping the client once per second.
|
||||
netConfig.PingInterval = 1f;
|
||||
|
||||
netConfig.SetMessageTypeEnabled(
|
||||
NetIncomingMessageType.WarningMessage,
|
||||
_config.GetCVar(CVars.NetLidgrenLogWarning));
|
||||
|
||||
netConfig.SetMessageTypeEnabled(
|
||||
NetIncomingMessageType.ErrorMessage,
|
||||
_config.GetCVar(CVars.NetLidgrenLogError));
|
||||
|
||||
var poolSize = _config.GetCVar(CVars.NetPoolSize);
|
||||
|
||||
if (poolSize <= 0)
|
||||
|
||||
@@ -48,6 +48,9 @@ public abstract partial class SharedJointSystem
|
||||
|
||||
private void OnRelayShutdown(EntityUid uid, JointRelayTargetComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_gameTiming.ApplyingState)
|
||||
return;
|
||||
|
||||
foreach (var relay in component.Relayed)
|
||||
{
|
||||
if (TerminatingOrDeleted(relay) || !_jointsQuery.TryGetComponent(relay, out var joint))
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations;
|
||||
|
||||
@@ -42,3 +45,59 @@ public readonly record struct EntProtoId(string Id) : IEquatable<string>, ICompa
|
||||
|
||||
public override string ToString() => Id ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EntProtoId"/>
|
||||
[Serializable]
|
||||
public readonly record struct EntProtoId<T>(string Id) : IEquatable<string>, IComparable<EntProtoId> where T : IComponent, new()
|
||||
{
|
||||
public static implicit operator string(EntProtoId<T> protoId)
|
||||
{
|
||||
return protoId.Id;
|
||||
}
|
||||
|
||||
public static implicit operator EntProtoId(EntProtoId<T> protoId)
|
||||
{
|
||||
return new EntProtoId(protoId.Id);
|
||||
}
|
||||
|
||||
public static implicit operator EntProtoId<T>(string id)
|
||||
{
|
||||
return new EntProtoId<T>(id);
|
||||
}
|
||||
|
||||
public static implicit operator EntProtoId<T>?(string? id)
|
||||
{
|
||||
return id == null ? default(EntProtoId<T>?) : new EntProtoId<T>(id);
|
||||
}
|
||||
|
||||
public bool Equals(string? other)
|
||||
{
|
||||
return Id == other;
|
||||
}
|
||||
|
||||
public int CompareTo(EntProtoId other)
|
||||
{
|
||||
return string.Compare(Id, other.Id, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override string ToString() => Id ?? string.Empty;
|
||||
|
||||
public T Get(IPrototypeManager? prototypes, IComponentFactory compFactory)
|
||||
{
|
||||
prototypes ??= IoCManager.Resolve<IPrototypeManager>();
|
||||
var proto = prototypes.Index(this);
|
||||
if (!proto.TryGetComponent(out T? comp, compFactory))
|
||||
{
|
||||
throw new ArgumentException($"{nameof(EntityPrototype)} {proto.ID} has no {nameof(T)}");
|
||||
}
|
||||
|
||||
return comp;
|
||||
}
|
||||
|
||||
public bool TryGet([NotNullWhen(true)] out T? comp, IPrototypeManager? prototypes, IComponentFactory compFactory)
|
||||
{
|
||||
prototypes ??= IoCManager.Resolve<IPrototypeManager>();
|
||||
var proto = prototypes.Index(this);
|
||||
return proto.TryGetComponent(out comp, compFactory);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Prototypes
|
||||
@@ -168,28 +169,37 @@ namespace Robust.Shared.Prototypes
|
||||
_loc = IoCManager.Resolve<ILocalizationManager>();
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory? factory = null) where T : IComponent
|
||||
[Obsolete("Pass in IComponentFactory")]
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component)
|
||||
where T : IComponent
|
||||
{
|
||||
if (factory == null)
|
||||
{
|
||||
factory = IoCManager.Resolve<IComponentFactory>();
|
||||
}
|
||||
var compName = IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T));
|
||||
return TryGetComponent(compName, out component);
|
||||
}
|
||||
|
||||
var compName = factory.GetComponentName(typeof(T));
|
||||
public bool TryGetComponent<T>([NotNullWhen(true)] out T? component, IComponentFactory factory) where T : IComponent, new()
|
||||
{
|
||||
var compName = factory.GetComponentName<T>();
|
||||
return TryGetComponent(compName, out component);
|
||||
}
|
||||
|
||||
public bool TryGetComponent<T>(string name, [NotNullWhen(true)] out T? component) where T : IComponent
|
||||
{
|
||||
DebugTools.AssertEqual(IoCManager.Resolve<IComponentFactory>().GetComponentName(typeof(T)), name);
|
||||
|
||||
if (!Components.TryGetValue(name, out var componentUnCast))
|
||||
{
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
// There are no duplicate component names
|
||||
// TODO Sanity check with names being in an attribute of the type instead
|
||||
component = (T) componentUnCast.Component;
|
||||
if (componentUnCast.Component is not T cast)
|
||||
{
|
||||
component = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
component = cast;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,8 +65,7 @@ namespace Robust.Shared.Serialization.Manager.Definition
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = field.FieldInfo.Name.AsSpan();
|
||||
attribute.Tag = $"{char.ToLowerInvariant(name[0])}{name[1..]}";
|
||||
attribute.Tag = DataDefinitionUtility.AutoGenerateTag(field.FieldInfo.Name);
|
||||
}
|
||||
|
||||
var dataFields = fieldDefs
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Definition;
|
||||
|
||||
public class DataDefinitionUtility
|
||||
{
|
||||
public static string AutoGenerateTag(string name)
|
||||
{
|
||||
var span = name.AsSpan();
|
||||
return $"{char.ToLowerInvariant(span[0])}{span.Slice(1).ToString()}";
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
@@ -440,5 +441,7 @@ namespace Robust.Shared.Serialization.Manager
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +264,26 @@ namespace Robust.Shared.Serialization.Manager
|
||||
return dataDefinition != null;
|
||||
}
|
||||
|
||||
public bool TryGetVariableType(Type type, string variableName, [NotNullWhen(true)] out Type? variableType)
|
||||
{
|
||||
if (!TryGetDefinition(type, out var definition))
|
||||
{
|
||||
variableType = null;
|
||||
return false;
|
||||
}
|
||||
var foundFieldDef = definition.BaseFieldDefinitions.FirstOrDefault(fieldDef => fieldDef?.Attribute is DataFieldAttribute attr && attr.Tag==variableName, null);
|
||||
if(foundFieldDef != null)
|
||||
{
|
||||
variableType = foundFieldDef.BackingField.FieldType;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
variableType = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Type ResolveConcreteType(Type baseType, string typeName)
|
||||
{
|
||||
var type = ReflectionManager.YamlTypeTagLookup(baseType, typeName);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Serialization.Markdown;
|
||||
using Robust.Shared.Serialization.Markdown.Mapping;
|
||||
using Robust.Shared.Serialization.Markdown.Sequence;
|
||||
using Robust.Shared.Serialization.Markdown.Validation;
|
||||
using Robust.Shared.Serialization.Markdown.Value;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
|
||||
@@ -40,3 +43,49 @@ public sealed class EntProtoIdSerializer : ITypeSerializer<EntProtoId, ValueData
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializer used automatically for <see cref="EntProtoId"/> types.
|
||||
/// </summary>
|
||||
[TypeSerializer]
|
||||
public sealed class EntProtoIdSerializer<T> : ITypeSerializer<EntProtoId<T>, ValueDataNode>, ITypeCopyCreator<EntProtoId<T>> where T : IComponent, new()
|
||||
{
|
||||
public ValidationNode Validate(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, ISerializationContext? context = null)
|
||||
{
|
||||
var prototypes = dependencies.Resolve<IPrototypeManager>();
|
||||
if (!prototypes.TryGetMapping(typeof(EntityPrototype), node.Value, out var mapping))
|
||||
return new ErrorNode(node, $"No {nameof(EntityPrototype)} found with id {node.Value} that has a {typeof(T).Name}");
|
||||
|
||||
if (!mapping.TryGet("components", out SequenceDataNode? components))
|
||||
return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}.");
|
||||
|
||||
var compFactory = dependencies.Resolve<IComponentFactory>();
|
||||
var registration = compFactory.GetRegistration<T>();
|
||||
foreach (var componentNode in components)
|
||||
{
|
||||
if (componentNode is MappingDataNode component &&
|
||||
component.TryGet("type", out ValueDataNode? compName) &&
|
||||
compName.Value == registration.Name)
|
||||
{
|
||||
return new ValidatedValueNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
return new ErrorNode(node, $"{nameof(EntityPrototype)} {node.Value} doesn't have a {typeof(T).Name}.");
|
||||
}
|
||||
|
||||
public EntProtoId<T> Read(ISerializationManager serialization, ValueDataNode node, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null, InstantiationDelegate<EntProtoId<T>>? instanceProvider = null)
|
||||
{
|
||||
return new EntProtoId<T>(node.Value);
|
||||
}
|
||||
|
||||
public DataNode Write(ISerializationManager serialization, EntProtoId<T> value, IDependencyCollection dependencies, bool alwaysWrite = false, ISerializationContext? context = null)
|
||||
{
|
||||
return new ValueDataNode(value.Id);
|
||||
}
|
||||
|
||||
public EntProtoId<T> CreateCopy(ISerializationManager serializationManager, EntProtoId<T> source, IDependencyCollection dependencies, SerializationHookContext hookCtx, ISerializationContext? context = null)
|
||||
{
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Reflection;
|
||||
using Robust.UnitTesting.Shared.Reflection;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
@@ -21,6 +21,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
var compInstance = new MetaDataComponent();
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
compFactory.RegisterClass<MetaDataComponent>();
|
||||
entManMock.Setup(m => m.ComponentFactory).Returns(compFactory);
|
||||
@@ -35,7 +36,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
entManMock.Setup(m => m.GetComponentInternal(entUid, CompIdx.Index<MetaDataComponent>()))
|
||||
.Returns(compInstance);
|
||||
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
@@ -80,6 +81,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
CompIdx.Index<MetaDataComponent>());
|
||||
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
compFacMock.Setup(m => m.GetRegistration(CompIdx.Index<MetaDataComponent>())).Returns(compRegistration);
|
||||
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration });
|
||||
@@ -92,7 +94,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
|
||||
.Returns(compInstance);
|
||||
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
@@ -137,6 +139,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
CompIdx.Index<MetaDataComponent>());
|
||||
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
compFacMock.Setup(m => m.GetRegistration(CompIdx.Index<MetaDataComponent>())).Returns(compRegistration);
|
||||
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(new[] { compRegistration });
|
||||
@@ -149,7 +152,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
entManMock.Setup(m => m.GetComponent(entUid, typeof(MetaDataComponent)))
|
||||
.Returns(compInstance);
|
||||
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
@@ -184,6 +187,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
|
||||
List<ComponentRegistration> allRefTypes = new();
|
||||
void Setup<T>(out T instance) where T : IComponent, new()
|
||||
@@ -208,7 +212,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
compFacMock.Setup(m => m.GetAllRegistrations()).Returns(allRefTypes.ToArray());
|
||||
|
||||
entManMock.Setup(m => m.ComponentFactory).Returns(compFacMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
bus.OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare();
|
||||
|
||||
// Subscribe
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Reflection;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
@@ -12,8 +13,9 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
{
|
||||
var compFacMock = new Mock<IComponentFactory>();
|
||||
var entManMock = new Mock<IEntityManager>();
|
||||
var reflectMock = new Mock<IReflectionManager>();
|
||||
entManMock.SetupGet(e => e.ComponentFactory).Returns(compFacMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object);
|
||||
var bus = new EntityEventBus(entManMock.Object, reflectMock.Object);
|
||||
return bus;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
|
||||
new NetEntity(64),
|
||||
new []
|
||||
{
|
||||
new ComponentChange(0, new MapGridComponentState(16, chunkData: null), default)
|
||||
new ComponentChange(0, new MapGridComponentDeltaState(16, chunkData: null, default), default)
|
||||
}, default);
|
||||
|
||||
serializer.Serialize(stream, payload);
|
||||
|
||||
@@ -80,7 +80,7 @@ public sealed partial class NoSharedReferencesTest : RobustIntegrationTest
|
||||
|
||||
// Assert that client-side dictionary of server component state also isn't contaminated by server references
|
||||
var componentStates = clientGameStateManager.GetFullRep()[cEntMan.GetNetEntity(cPlayer)];
|
||||
var clientLastTickStateObject = ((ExampleAutogeneratedComponent_AutoState)componentStates.First(x => x.Value is ExampleAutogeneratedComponent_AutoState).Value).ReferenceObject;
|
||||
var clientLastTickStateObject = ((ExampleAutogeneratedComponent_AutoState)componentStates.First(x => x.Value is ExampleAutogeneratedComponent_AutoState).Value!).ReferenceObject;
|
||||
Assert.That(clientLastTickStateObject, Is.Not.Null);
|
||||
Assert.That(ReferenceEquals(clientLastTickStateObject, serverObject), Is.False);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user