mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
24 Commits
v250.0.3
...
v251.0.1-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5173fd02ad | ||
|
|
3cc2c96b49 | ||
|
|
483b95d16d | ||
|
|
7ac25f708e | ||
|
|
588c46273e | ||
|
|
919de8ce0e | ||
|
|
af27d2d872 | ||
|
|
45bb8740a0 | ||
|
|
4a24539629 | ||
|
|
7536c4ec68 | ||
|
|
9268c8629d | ||
|
|
0bc0cafe64 | ||
|
|
8891f3fa0a | ||
|
|
4f96c2d233 | ||
|
|
ab55d5b2f2 | ||
|
|
806c5b694b | ||
|
|
6898053dbd | ||
|
|
ae625ebad8 | ||
|
|
3c754a4f49 | ||
|
|
d84cb6327c | ||
|
|
4bfd92dbc5 | ||
|
|
c7d228c223 | ||
|
|
f244c94905 | ||
|
|
01cac6465b |
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
@@ -54,6 +54,46 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 251.0.1-fixtures-fix
|
||||
|
||||
|
||||
## 251.0.0-fixtures-fix
|
||||
|
||||
|
||||
## 251.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* Localization is now separate between client and server and is handled via cvar.
|
||||
* Contacting entities no longer can be disabled for CollisionWake to avoid destroying the contacts unnecessarily.
|
||||
|
||||
### New features
|
||||
|
||||
* Added `DirectionExtensions.AllDirections`, which contains a list of all `Direction`s for easy enumeration.
|
||||
* Add ForbidLiteralAttribute.
|
||||
* Log late MsgEntity again.
|
||||
* Show entity name in `physics shapeinfo` output.
|
||||
* Make SubscribeLocalEvent not require EntityEventArgs.
|
||||
* Add autocomplete to `tp` command.
|
||||
* Add button to jump to live chat when scrolled up.
|
||||
* Add autocomplete to `savemap` and `savegrid`.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix velocity not re-applying correctly on re-parenting.
|
||||
* Fix Equatable on FormattedMessage.
|
||||
* Fix SharedTransformSystem methods logging errors on resolves.
|
||||
|
||||
### Other
|
||||
|
||||
* Significantly optimized tile edge rendering.
|
||||
|
||||
### Internal
|
||||
|
||||
* Remove duplicate GetMassData method.
|
||||
* Inline manifold points for physics.
|
||||
|
||||
|
||||
## 250.0.0
|
||||
|
||||
### Breaking changes
|
||||
|
||||
@@ -11,6 +11,7 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID.
|
||||
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
|
||||
cmd-parse-failure-enum = {$arg} is not a {$enum} Enum.
|
||||
cmd-parse-failure-grid = {$arg} is not a valid grid.
|
||||
cmd-parse-failure-cultureinfo = "{$arg}" is not valid CultureInfo.
|
||||
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
cmd-parse-failure-session = There is no session with username: {$username}
|
||||
|
||||
@@ -572,3 +573,8 @@ cmd-pvs-override-info-desc = Prints information about any PVS overrides associat
|
||||
cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
|
||||
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
|
||||
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
|
||||
|
||||
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager
|
||||
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
|
||||
cmd-localization_set_culture-culture-name = <cultureName>
|
||||
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })
|
||||
|
||||
@@ -14,6 +14,10 @@ tile-spawn-window-title = Place Tiles
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
|
||||
## OutputPanel
|
||||
|
||||
output-panel-scroll-down-button-text = Scroll Down
|
||||
|
||||
## Common Used
|
||||
|
||||
window-erase-button-text = Erase Mode
|
||||
|
||||
189
Robust.Analyzers.Tests/ForbidLiteralAnalyzerTest.cs
Normal file
189
Robust.Analyzers.Tests/ForbidLiteralAnalyzerTest.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ForbidLiteralAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class ForbidLiteralAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<ForbidLiteralAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code },
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.ForbidLiteralAttribute.cs"
|
||||
);
|
||||
|
||||
test.TestState.Sources.Add(("TestTypeDefs.cs", TestTypeDefs));
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
private const string TestTypeDefs = """
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public sealed class TestClass
|
||||
{
|
||||
public static void OneParameterForbidden([ForbidLiteral] string value) { }
|
||||
public static void TwoParametersFirstForbidden([ForbidLiteral] string first, string second) { }
|
||||
public static void TwoParametersBothForbidden([ForbidLiteral] string first, [ForbidLiteral] string second) { }
|
||||
public static void ListParameterForbidden([ForbidLiteral] List<string> values) { }
|
||||
public static void ParamsListParameterForbidden([ForbidLiteral] params List<string> values) { }
|
||||
}
|
||||
|
||||
public record struct StringWrapper(string value)
|
||||
{
|
||||
private readonly string _value = value;
|
||||
|
||||
public static implicit operator string(StringWrapper wrapper)
|
||||
{
|
||||
return wrapper._value;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task TestOneParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.OneParameterForbidden(_constValue);
|
||||
TestClass.OneParameterForbidden(StaticValue);
|
||||
TestClass.OneParameterForbidden(WrappedValue);
|
||||
TestClass.OneParameterForbidden("baz");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(12,41): error RA0033: The "value" parameter of OneParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(12, 41, 12, 46).WithArguments("value", "OneParameterForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestTwoParametersFirstForbidden()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.TwoParametersFirstForbidden(_constValue, "whatever");
|
||||
TestClass.TwoParametersFirstForbidden(_constValue, _constValue);
|
||||
TestClass.TwoParametersFirstForbidden("foo", "whatever");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(9,47): error RA0033: The "first" parameter of TwoParametersFirstForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(9, 47, 9, 52).WithArguments("first", "TwoParametersFirstForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestTwoParametersBothForbidden()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.TwoParametersBothForbidden(_constValue, _constValue);
|
||||
TestClass.TwoParametersBothForbidden(_constValue, StaticValue);
|
||||
TestClass.TwoParametersBothForbidden(_constValue, "whatever");
|
||||
TestClass.TwoParametersBothForbidden("whatever", _constValue);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,59): error RA0033: The "second" parameter of TwoParametersBothForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 59, 10, 69).WithArguments("second", "TwoParametersBothForbidden"),
|
||||
// /0/Test0.cs(11,46): error RA0033: The "first" parameter of TwoParametersBothForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(11, 46, 11, 56).WithArguments("first", "TwoParametersBothForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestListParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.ListParameterForbidden([_constValue, StaticValue, WrappedValue]);
|
||||
TestClass.ListParameterForbidden(["foo", _constValue, "bar"]);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,43): warning RA0033: The "values" parameter of ListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 43, 10, 48).WithArguments("values", "ListParameterForbidden"),
|
||||
// /0/Test0.cs(10,63): warning RA0033: The "values" parameter of ListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 63, 10, 68).WithArguments("values", "ListParameterForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestParamsListParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.ParamsListParameterForbidden(_constValue, StaticValue, WrappedValue);
|
||||
TestClass.ParamsListParameterForbidden("foo", _constValue, "bar");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,48): warning RA0033: The "values" parameter of ParamsListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 48, 10, 53).WithArguments("values", "ParamsListParameterForbidden"),
|
||||
// /0/Test0.cs(10,68): warning RA0033: The "values" parameter of ParamsListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 68, 10, 73).WithArguments("values", "ParamsListParameterForbidden")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ForbidLiteralAttribute.cs" LogicalName="Robust.Shared.Analyzers.ForbidLiteralAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
101
Robust.Analyzers/ForbidLiteralAnalyzer.cs
Normal file
101
Robust.Analyzers/ForbidLiteralAnalyzer.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class ForbidLiteralAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string ForbidLiteralType = "Robust.Shared.Analyzers.ForbidLiteralAttribute";
|
||||
|
||||
public static DiagnosticDescriptor ForbidLiteralRule = new(
|
||||
Diagnostics.IdForbidLiteral,
|
||||
"Parameter forbids literal values",
|
||||
"The {0} parameter of {1} forbids literal values",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true,
|
||||
"Pass in a validated wrapper type like ProtoId, or a const or static value."
|
||||
);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [ForbidLiteralRule];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private void AnalyzeOperation(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation invocationOperation)
|
||||
return;
|
||||
|
||||
// Check each parameter of the method invocation
|
||||
foreach (var argumentOperation in invocationOperation.Arguments)
|
||||
{
|
||||
// Check for our attribute on the parameter
|
||||
if (!AttributeHelper.HasAttribute(argumentOperation.Parameter, ForbidLiteralType, out _))
|
||||
continue;
|
||||
|
||||
// Handle parameters using the params keyword
|
||||
if (argumentOperation.Syntax is InvocationExpressionSyntax subExpressionSyntax)
|
||||
{
|
||||
// Check each param value
|
||||
foreach (var subArgument in subExpressionSyntax.ArgumentList.Arguments)
|
||||
{
|
||||
CheckArgumentSyntax(context, argumentOperation, subArgument);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not params, so just check the single parameter
|
||||
if (argumentOperation.Syntax is not ArgumentSyntax argumentSyntax)
|
||||
continue;
|
||||
|
||||
CheckArgumentSyntax(context, argumentOperation, argumentSyntax);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckArgumentSyntax(OperationAnalysisContext context, IArgumentOperation operation, ArgumentSyntax argumentSyntax)
|
||||
{
|
||||
// Handle collection types
|
||||
if (argumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax)
|
||||
{
|
||||
// Check each value of the collection
|
||||
foreach (var elementSyntax in collectionExpressionSyntax.Elements)
|
||||
{
|
||||
if (elementSyntax is not ExpressionElementSyntax expressionSyntax)
|
||||
continue;
|
||||
|
||||
// Check if a literal was passed in
|
||||
if (expressionSyntax.Expression is not LiteralExpressionSyntax)
|
||||
continue;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(ForbidLiteralRule,
|
||||
expressionSyntax.GetLocation(),
|
||||
operation.Parameter.Name,
|
||||
(context.Operation as IInvocationOperation).TargetMethod.Name
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a collection, just a single value to check
|
||||
// Check if it's a literal
|
||||
if (argumentSyntax.Expression is not LiteralExpressionSyntax)
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(ForbidLiteralRule,
|
||||
argumentSyntax.GetLocation(),
|
||||
operation.Parameter.Name,
|
||||
(context.Operation as IInvocationOperation).TargetMethod.Name
|
||||
));
|
||||
}
|
||||
}
|
||||
67
Robust.Client/Console/Commands/LocalizationCommands.cs
Normal file
67
Robust.Client/Console/Commands/LocalizationCommands.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class LocalizationSetCulture : LocalizedCommands
|
||||
{
|
||||
private const string Name = "localization_set_culture";
|
||||
private const int ArgumentCount = 1;
|
||||
|
||||
public override string Command => Name;
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != ArgumentCount)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
CultureInfo culture;
|
||||
try
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(args[0], predefinedOnly: false);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-cultureinfo", ("arg", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
LocalizationManager.SetCulture(culture);
|
||||
shell.WriteLine(LocalizationManager.GetString("cmd-localization_set_culture-changed",
|
||||
("code", culture.Name),
|
||||
("nativeName", culture.NativeName),
|
||||
("englishName", culture.EnglishName)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHintOptions(GetCultureNames(),
|
||||
LocalizationManager.GetString("cmd-localization_set_culture-culture-name")),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static HashSet<string> GetCultureNames()
|
||||
{
|
||||
var cultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures)
|
||||
.Where(x => !string.IsNullOrEmpty(x.Name))
|
||||
.ToArray();
|
||||
|
||||
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
allNames.UnionWith(cultureInfos.Select(x => x.TwoLetterISOLanguageName));
|
||||
allNames.UnionWith(cultureInfos.Select(x => x.Name));
|
||||
|
||||
return allNames;
|
||||
}
|
||||
}
|
||||
@@ -413,8 +413,9 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
var body = bodyEnt.Comp;
|
||||
var meta = _entityManager.GetComponent<MetaDataComponent>(bodyEnt);
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner} ({meta.EntityName})");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using static Robust.Shared.Containers.ContainerManagerComponent;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -58,7 +57,7 @@ namespace Robust.Client.GameObjects
|
||||
if (!RemoveExpectedEntity(meta.NetEntity, out var container))
|
||||
return;
|
||||
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container);
|
||||
Insert((uid, TransformQuery.GetComponent(uid), MetaQuery.GetComponent(uid), null), container, force: true);
|
||||
}
|
||||
|
||||
public override void ShutdownContainer(BaseContainer container)
|
||||
@@ -232,7 +231,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
Insert(message.Entity, container);
|
||||
Insert(message.Entity, container, force: true);
|
||||
}
|
||||
|
||||
public void AddExpectedEntity(NetEntity netEntity, BaseContainer container)
|
||||
|
||||
@@ -9,10 +9,6 @@ namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
@@ -23,16 +19,4 @@ public sealed class MapSystem : SharedMapSystem
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
@@ -22,6 +23,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// </summary>
|
||||
private HashSet<Type> _erroredGridOverlays = new();
|
||||
|
||||
private Vertex2D[]? _chunkMeshBuilderVertexBuffer;
|
||||
private ushort[]? _chunkMeshBuilderIndexBuffer;
|
||||
|
||||
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
@@ -63,29 +67,78 @@ namespace Robust.Client.Graphics.Clyde
|
||||
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
|
||||
}
|
||||
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
|
||||
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(transform));
|
||||
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(mapGrid));
|
||||
var enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Handle base texture updates.
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
DebugTools.Assert(chunk.FilledTiles > 0);
|
||||
if (!data.TryGetValue(chunk.Indices, out MapChunkData? datum))
|
||||
data[chunk.Indices] = datum = _initChunkBuffers(mapGrid, chunk);
|
||||
var datum = EnsureChunkInitialized(data, chunk, mapGrid);
|
||||
|
||||
if (datum.Dirty)
|
||||
_updateChunkMesh(mapGrid, chunk, datum);
|
||||
|
||||
DebugTools.Assert(datum.TileCount > 0);
|
||||
if (datum.TileCount == 0)
|
||||
if (!datum.Dirty)
|
||||
continue;
|
||||
|
||||
BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
_updateChunkMesh(mapGrid, chunk, datum);
|
||||
|
||||
// Dirty edge tiles for next step.
|
||||
datum.EdgeDirty = true;
|
||||
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
var neighbor = chunk.Indices + new Vector2i(x, y);
|
||||
|
||||
if (!mapGrid.Comp.Chunks.TryGetValue(neighbor, out var neighborChunk))
|
||||
continue;
|
||||
|
||||
var neighborDatum = EnsureChunkInitialized(data, neighborChunk, mapGrid);
|
||||
neighborDatum.EdgeDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Handle edge sprites.
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var datum = data[chunk.Indices];
|
||||
|
||||
if (!datum.EdgeDirty)
|
||||
continue;
|
||||
|
||||
_updateChunkEdges(mapGrid, chunk, datum);
|
||||
}
|
||||
|
||||
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Draw chunks
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var datum = data[chunk.Indices];
|
||||
DebugTools.Assert(datum.TileCount > 0);
|
||||
if (datum.TileCount > 0)
|
||||
{
|
||||
BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
if (datum.EdgeCount > 0)
|
||||
{
|
||||
BindVertexArray(datum.EdgeVAO);
|
||||
CheckGlError();
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.EdgeCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
requiresFlush = false;
|
||||
@@ -117,6 +170,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CullEmptyChunks();
|
||||
}
|
||||
|
||||
private MapChunkData EnsureChunkInitialized(Dictionary<Vector2i, MapChunkData> data, MapChunk chunk, Entity<MapGridComponent> mapGrid)
|
||||
{
|
||||
if (!data.TryGetValue(chunk.Indices, out var datum))
|
||||
{
|
||||
data[chunk.Indices] = datum = new MapChunkData();
|
||||
_initChunkBuffers(mapGrid, chunk, datum);
|
||||
}
|
||||
|
||||
return datum;
|
||||
}
|
||||
|
||||
private void CullEmptyChunks()
|
||||
{
|
||||
foreach (var (grid, chunks) in _mapChunkData)
|
||||
@@ -138,66 +202,141 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _updateChunkMesh(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
Span<ushort> indexBuffer = stackalloc ushort[_indicesPerChunk(chunk)];
|
||||
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
|
||||
Span<ushort> indexBuffer = EnsureSize(ref _chunkMeshBuilderIndexBuffer, _indicesPerChunk(chunk));
|
||||
Span<Vertex2D> vertexBuffer = EnsureSize(ref _chunkMeshBuilderVertexBuffer, _verticesPerChunk(chunk));
|
||||
|
||||
var i = 0;
|
||||
var cSz = grid.Comp.ChunkSize;
|
||||
var cScaled = chunk.Indices * cSz;
|
||||
for (ushort x = 0; x < cSz; x++)
|
||||
var chunkSize = grid.Comp.ChunkSize;
|
||||
var chunkOriginScaled = chunk.Indices * chunkSize;
|
||||
|
||||
for (ushort x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < cSz; y++)
|
||||
for (ushort y = 0; y < chunkSize; y++)
|
||||
{
|
||||
var gridX = x + chunkOriginScaled.X;
|
||||
var gridY = y + chunkOriginScaled.Y;
|
||||
var tile = chunk.GetTile(x, y);
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
// Tile render
|
||||
if (x != chunkSize && y != chunkSize)
|
||||
{
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
// ReSharper disable once IntVariableOverflowInUncheckedContext
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var gx = x + cScaled.X;
|
||||
var gy = y + cScaled.Y;
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top, Color.White);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top, Color.White);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
{
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
|
||||
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var indexSlice = indexBuffer[..(i * GetQuadBatchIndexCount())];
|
||||
var vertSlice = vertexBuffer[..(i * 4)];
|
||||
|
||||
GL.BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
datum.EBO.Use();
|
||||
datum.VBO.Use();
|
||||
datum.EBO.Reallocate(indexBuffer[..(i * GetQuadBatchIndexCount())]);
|
||||
datum.VBO.Reallocate(vertexBuffer[..(i * 4)]);
|
||||
datum.Dirty = false;
|
||||
datum.EBO.Reallocate(indexSlice);
|
||||
datum.VBO.Reallocate(vertSlice);
|
||||
|
||||
datum.TileCount = i;
|
||||
datum.Dirty = false;
|
||||
}
|
||||
|
||||
private unsafe MapChunkData _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk)
|
||||
private void _updateChunkEdges(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
// Need a buffer that can potentially store all neighbor tiles
|
||||
Span<ushort> indexBuffer = EnsureSize(ref _chunkMeshBuilderIndexBuffer, _indicesPerChunk(chunk) * 8);
|
||||
Span<Vertex2D> vertexBuffer = EnsureSize(ref _chunkMeshBuilderVertexBuffer, _verticesPerChunk(chunk) * 8);
|
||||
|
||||
var i = 0;
|
||||
var chunkSize = grid.Comp.ChunkSize;
|
||||
var chunkOriginScaled = chunk.Indices * chunkSize;
|
||||
var maps = _entityManager.System<SharedMapSystem>();
|
||||
|
||||
for (ushort x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < chunkSize; y++)
|
||||
{
|
||||
var gridX = x + chunkOriginScaled.X;
|
||||
var gridY = y + chunkOriginScaled.Y;
|
||||
var tile = chunk.GetTile(x, y);
|
||||
var tileDef = _tileDefinitionManager[tile.TypeId];
|
||||
|
||||
// Edge render
|
||||
for (var nx = -1; nx <= 1; nx++)
|
||||
{
|
||||
for (var ny = -1; ny <= 1; ny++)
|
||||
{
|
||||
if (nx == 0 && ny == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(gridX + nx, gridY + ny);
|
||||
if (!maps.TryGetTile(grid.Comp, neighborIndices, out var neighborTile))
|
||||
continue;
|
||||
|
||||
var neighborDef = _tileDefinitionManager[neighborTile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tile.TypeId == neighborTile.TypeId || neighborDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// If neighbor is a lower priority then us then don't draw on our tile.
|
||||
if (neighborDef.EdgeSpritePriority < tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(nx, ny).AsDirection().GetOpposite();
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(neighborTile.TypeId, direction);
|
||||
|
||||
if (regionMaybe == null)
|
||||
continue;
|
||||
|
||||
var region = regionMaybe[0];
|
||||
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We don't save the edge buffers back because we might need to re-use it if a neighbor chunk updates.
|
||||
var indexSlice = indexBuffer[..(i * GetQuadBatchIndexCount())];
|
||||
var vertSlice = vertexBuffer[..(i * 4)];
|
||||
|
||||
GL.BindVertexArray(datum.EdgeVAO);
|
||||
CheckGlError();
|
||||
datum.EdgeEBO.Use();
|
||||
datum.EdgeVBO.Use();
|
||||
datum.EdgeEBO.Reallocate(indexSlice);
|
||||
datum.EdgeVBO.Reallocate(vertSlice);
|
||||
|
||||
datum.EdgeCount = i;
|
||||
datum.EdgeDirty = false;
|
||||
}
|
||||
|
||||
private unsafe void _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
var vboSize = _verticesPerChunk(chunk) * sizeof(Vertex2D);
|
||||
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
|
||||
|
||||
// Base VAO
|
||||
var vao = GenVertexArray();
|
||||
BindVertexArray(vao);
|
||||
CheckGlError();
|
||||
|
||||
var vboSize = _verticesPerChunk(chunk) * sizeof(Vertex2D);
|
||||
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
|
||||
|
||||
var vbo = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
vboSize, $"Grid {grid.Owner} chunk {chunk.Indices} VBO");
|
||||
var ebo = new GLBuffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
@@ -212,12 +351,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
vbo.Use();
|
||||
ebo.Use();
|
||||
|
||||
var datum = new MapChunkData(vao, vbo, ebo)
|
||||
{
|
||||
Dirty = true
|
||||
};
|
||||
datum.EBO = ebo;
|
||||
datum.VBO = vbo;
|
||||
datum.VAO = vao;
|
||||
|
||||
return datum;
|
||||
// EdgeVAO
|
||||
var edgeVao = GenVertexArray();
|
||||
BindVertexArray(edgeVao);
|
||||
CheckGlError();
|
||||
|
||||
var edgeVbo = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
vboSize * 8, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeVBO");
|
||||
var edgeEbo = new GLBuffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
eboSize * 8, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeEBO");
|
||||
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeVAO");
|
||||
SetupVAOLayout();
|
||||
CheckGlError();
|
||||
|
||||
edgeVbo.Use();
|
||||
edgeEbo.Use();
|
||||
|
||||
datum.EdgeEBO = edgeEbo;
|
||||
datum.EdgeVBO = edgeVbo;
|
||||
datum.EdgeVAO = edgeVao;
|
||||
}
|
||||
|
||||
private void DeleteChunk(MapChunkData data)
|
||||
@@ -254,19 +411,49 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mapChunkData.Remove(gridId);
|
||||
}
|
||||
|
||||
private static T[] EnsureSize<T>(ref T[]? field, int size)
|
||||
{
|
||||
if (field == null || field.Length < size)
|
||||
field = new T[size];
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
private void WriteTileToBuffers(
|
||||
int i,
|
||||
int gridX,
|
||||
int gridY,
|
||||
Span<Vertex2D> vertexBuffer,
|
||||
Span<ushort> indexBuffer,
|
||||
Box2 region)
|
||||
{
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gridX, gridY, region.Left, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gridX + 1, gridY, region.Right, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gridX + 1, gridY + 1, region.Right, region.Top, Color.White);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gridX, gridY + 1, region.Left, region.Top, Color.White);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
}
|
||||
|
||||
private sealed class MapChunkData
|
||||
{
|
||||
public bool Dirty;
|
||||
public readonly uint VAO;
|
||||
public readonly GLBuffer VBO;
|
||||
public readonly GLBuffer EBO;
|
||||
public bool EdgeDirty = true;
|
||||
public bool Dirty = true;
|
||||
|
||||
public uint VAO;
|
||||
public GLBuffer VBO = default!;
|
||||
public GLBuffer EBO = default!;
|
||||
public int TileCount;
|
||||
|
||||
public MapChunkData(uint vao, GLBuffer vbo, GLBuffer ebo)
|
||||
public uint EdgeVAO;
|
||||
public GLBuffer EdgeVBO = default!;
|
||||
public GLBuffer EdgeEBO = default!;
|
||||
public int EdgeCount;
|
||||
|
||||
public MapChunkData()
|
||||
{
|
||||
VAO = vao;
|
||||
VBO = vbo;
|
||||
EBO = ebo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -10,9 +11,11 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Robust.Client.Map
|
||||
{
|
||||
@@ -29,7 +32,7 @@ namespace Robust.Client.Map
|
||||
|
||||
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
|
||||
|
||||
private readonly Dictionary<int, Box2[]> _tileRegions = new();
|
||||
private FrozenDictionary<(int Id, Direction Direction), Box2[]> _tileRegions = FrozenDictionary<(int Id, Direction Direction), Box2[]>.Empty;
|
||||
|
||||
public Box2 ErrorTileRegion { get; private set; }
|
||||
|
||||
@@ -42,7 +45,14 @@ namespace Robust.Client.Map
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(int tileType)
|
||||
{
|
||||
if (_tileRegions.TryGetValue(tileType, out var region))
|
||||
return TileAtlasRegion(tileType, Direction.Invalid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(int tileType, Direction direction)
|
||||
{
|
||||
// ReSharper disable once CanSimplifyDictionaryTryGetValueWithGetValueOrDefault
|
||||
if (_tileRegions.TryGetValue((tileType, direction), out var region))
|
||||
{
|
||||
return region;
|
||||
}
|
||||
@@ -83,7 +93,8 @@ namespace Robust.Client.Map
|
||||
|
||||
internal void _genTextureAtlas()
|
||||
{
|
||||
_tileRegions.Clear();
|
||||
var sw = RStopwatch.StartNew();
|
||||
var tileRegs = new Dictionary<(int Id, Direction Direction), Box2[]>();
|
||||
_tileTextureAtlas = null;
|
||||
|
||||
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
|
||||
@@ -94,7 +105,7 @@ namespace Robust.Client.Map
|
||||
|
||||
const int tileSize = EyeManager.PixelsPerMeter;
|
||||
|
||||
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
|
||||
var tileCount = defList.Select(x => x.Variants + x.EdgeSprites.Count).Sum() + 1;
|
||||
|
||||
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
|
||||
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
|
||||
@@ -102,11 +113,11 @@ namespace Robust.Client.Map
|
||||
var imgWidth = dimensionX * tileSize;
|
||||
var imgHeight = dimensionY * tileSize;
|
||||
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
// Add in the missing tile texture sprite as tile texture 0.
|
||||
{
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
ErrorTileRegion = Box2.FromDimensions(
|
||||
0, (h - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
@@ -154,25 +165,98 @@ namespace Robust.Client.Map
|
||||
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
|
||||
image.Blit(box, sheet, point);
|
||||
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
regionList[j] = Box2.FromDimensions(
|
||||
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
BumpColumn(ref row, ref column, dimensionX);
|
||||
}
|
||||
|
||||
_tileRegions.Add(def.TileId, regionList);
|
||||
tileRegs.Add((def.TileId, Direction.Invalid), regionList);
|
||||
|
||||
// Edges
|
||||
if (def.EdgeSprites.Count <= 0)
|
||||
continue;
|
||||
|
||||
foreach (var direction in DirectionExtensions.AllDirections)
|
||||
{
|
||||
if (!def.EdgeSprites.TryGetValue(direction, out var edge))
|
||||
continue;
|
||||
|
||||
using (var stream = _manager.ContentFileRead(edge))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
if (image.Width != tileSize || image.Height != tileSize)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"Unable to load {path}, due to being unable to use tile textures with a dimension other than {tileSize}x{tileSize}.");
|
||||
}
|
||||
|
||||
Angle angle = Angle.Zero;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle != Angle.Zero)
|
||||
{
|
||||
image.Mutate(o => o.Rotate((float)-angle.Degrees));
|
||||
}
|
||||
|
||||
var point = new Vector2i(column * tileSize, row * tileSize);
|
||||
var box = new UIBox2i(0, 0, tileSize, tileSize);
|
||||
image.Blit(box, sheet, point);
|
||||
|
||||
// If you ever need edge variants then you could just bump this.
|
||||
var edgeList = new Box2[1];
|
||||
edgeList[0] = Box2.FromDimensions(
|
||||
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
|
||||
tileRegs.Add((def.TileId, direction), edgeList);
|
||||
BumpColumn(ref row, ref column, dimensionX);
|
||||
}
|
||||
}
|
||||
|
||||
_tileRegions = tileRegs.ToFrozenDictionary();
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
_sawmill.Debug($"Tile atlas took {sw.Elapsed} to build");
|
||||
}
|
||||
|
||||
private void BumpColumn(ref int row, ref int column, int dimensionX)
|
||||
{
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
|
||||
@@ -29,5 +29,12 @@ namespace Robust.Client.Map
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2[]? TileAtlasRegion(int tileType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the region inside the texture atlas to use to draw a tile type.
|
||||
/// Also handles edge sprites.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
public Box2[]? TileAtlasRegion(int tileType, Direction direction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Map;
|
||||
|
||||
/// <summary>
|
||||
/// Draws border sprites for tiles that support them.
|
||||
/// </summary>
|
||||
public sealed class TileEdgeOverlay : GridOverlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IResourceCache _resource;
|
||||
private readonly ITileDefinitionManager _tileDefManager;
|
||||
|
||||
public TileEdgeOverlay(IEntityManager entManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_resource = resource;
|
||||
_tileDefManager = tileDefManager;
|
||||
ZIndex = -1;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var mapSystem = _entManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
var tileSize = Grid.Comp.TileSize;
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var bounds = args.WorldBounds;
|
||||
bounds = new Box2Rotated(bounds.Box.Enlarged(1), bounds.Rotation, bounds.Origin);
|
||||
var localAABB = invMatrix.TransformBox(bounds);
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
|
||||
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
if (tileDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// Get what tiles border us to determine what sprites we need to draw.
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
|
||||
var neighborTile = mapSystem.GetTileRef(Grid.Owner, Grid, neighborIndices);
|
||||
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
|
||||
continue;
|
||||
|
||||
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
|
||||
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(x, y).AsDirection();
|
||||
|
||||
// No edge tile
|
||||
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
|
||||
continue;
|
||||
|
||||
var texture = _resource.GetResource<TextureResource>(edgePath);
|
||||
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
|
||||
// If we ever need one for both cardinals and corners then update this.
|
||||
switch (direction)
|
||||
{
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle == Angle.Zero)
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, box);
|
||||
else
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
|
||||
|
||||
RequiresFlush = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -15,10 +16,23 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[Virtual]
|
||||
public class OutputPanel : Control
|
||||
{
|
||||
public const string StyleClassOutputPanelScrollDownButton = "outputPanelScrollDownButton";
|
||||
|
||||
[Dependency] private readonly MarkupTagManager _tagManager = default!;
|
||||
|
||||
public const string StylePropertyStyleBox = "stylebox";
|
||||
|
||||
public bool ShowScrollDownButton
|
||||
{
|
||||
get => _showScrollDownButton;
|
||||
set
|
||||
{
|
||||
_showScrollDownButton = value;
|
||||
_updateScrollButtonVisibility();
|
||||
}
|
||||
}
|
||||
private bool _showScrollDownButton;
|
||||
|
||||
private readonly RingBufferList<RichTextEntry> _entries = new();
|
||||
private bool _isAtBottom = true;
|
||||
|
||||
@@ -26,6 +40,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private bool _firstLine = true;
|
||||
private StyleBox? _styleBoxOverride;
|
||||
private VScrollBar _scrollBar;
|
||||
private Button _scrollDownButton;
|
||||
|
||||
public bool ScrollFollowing { get; set; } = true;
|
||||
|
||||
@@ -43,7 +58,25 @@ namespace Robust.Client.UserInterface.Controls
|
||||
HorizontalAlignment = HAlignment.Right
|
||||
};
|
||||
AddChild(_scrollBar);
|
||||
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
|
||||
|
||||
AddChild(_scrollDownButton = new Button()
|
||||
{
|
||||
Name = "scrollLiveBtn",
|
||||
StyleClasses = { StyleClassOutputPanelScrollDownButton },
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Text = String.Format("⬇ {0} ⬇", Loc.GetString("output-panel-scroll-down-button-text")),
|
||||
MaxWidth = 300,
|
||||
Visible = false,
|
||||
});
|
||||
|
||||
_scrollDownButton.OnPressed += _ => ScrollToBottom();
|
||||
|
||||
_scrollBar.OnValueChanged += _ =>
|
||||
{
|
||||
_isAtBottom = _scrollBar.IsAtEnd;
|
||||
_updateScrollButtonVisibility();
|
||||
};
|
||||
}
|
||||
|
||||
public int EntryCount => _entries.Count;
|
||||
@@ -184,6 +217,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var styleBoxSize = _getStyleBox()?.MinimumSize.Y ?? 0;
|
||||
|
||||
_scrollBar.Page = UIScale * (Height - styleBoxSize);
|
||||
_updateScrollButtonVisibility();
|
||||
_invalidateEntries();
|
||||
}
|
||||
|
||||
@@ -284,5 +318,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_invalidOnVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateScrollButtonVisibility()
|
||||
{
|
||||
_scrollDownButton.Visible = ShowScrollDownButton && !_isAtBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace">
|
||||
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace" ShowScrollDownButton="True">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252add"
|
||||
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
|
||||
|
||||
@@ -36,6 +36,7 @@ public static class Diagnostics
|
||||
public const string IdUseNonGenericVariant = "RA0030";
|
||||
public const string IdPreferOtherType = "RA0031";
|
||||
public const string IdDuplicateDependency = "RA0032";
|
||||
public const string IdForbidLiteral = "RA0033";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -46,11 +47,11 @@ namespace Robust.Server.Console.Commands
|
||||
bool saveSuccess = _ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
|
||||
if(saveSuccess)
|
||||
{
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError("Save unsuccessful!");
|
||||
shell.WriteError("Save unsuccessful!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ namespace Robust.Server.Console.Commands
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-savebp-id"));
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.Components<MapGridComponent>(args[0], _ent), Loc.GetString("cmd-hint-savebp-id"));
|
||||
case 2:
|
||||
var opts = CompletionHelper.UserFilePath(args[1], _resource.UserData);
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-savemap-path"));
|
||||
@@ -159,6 +160,7 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
public sealed class SaveMap : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
@@ -169,7 +171,7 @@ namespace Robust.Server.Console.Commands
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-savemap-id"));
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entManager), Loc.GetString("cmd-hint-savemap-id"));
|
||||
case 2:
|
||||
var opts = CompletionHelper.UserFilePath(args[1], _resource.UserData);
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-savemap-path"));
|
||||
|
||||
@@ -204,6 +204,23 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private void HandleEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
if (_logLateMsgs)
|
||||
{
|
||||
var msgT = message.SourceTick;
|
||||
var cT = _gameTiming.CurTick;
|
||||
|
||||
if (msgT < cT)
|
||||
{
|
||||
_netEntSawmill.Warning(
|
||||
"Got late MsgEntity! Diff: {0}, msgT: {2}, cT: {3}, player: {1}, msg: {4}",
|
||||
(int) msgT.Value - (int) cT.Value,
|
||||
message.MsgChannel.UserName,
|
||||
msgT,
|
||||
cT,
|
||||
message.SystemMessage);
|
||||
}
|
||||
}
|
||||
|
||||
_queue.Add(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -37,6 +38,21 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
public static class DirectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of all cardinal and diagonal <see cref="Direction"/>s.
|
||||
/// </summary>
|
||||
public static readonly ImmutableArray<Direction> AllDirections =
|
||||
[
|
||||
Direction.South,
|
||||
Direction.SouthEast,
|
||||
Direction.East,
|
||||
Direction.NorthEast,
|
||||
Direction.North,
|
||||
Direction.NorthWest,
|
||||
Direction.West,
|
||||
Direction.SouthWest
|
||||
];
|
||||
|
||||
private const double Segment = 2 * Math.PI / 8.0; // Cut the circle into 8 pieces
|
||||
|
||||
public static Direction AsDir(this DirectionFlag directionFlag)
|
||||
|
||||
11
Robust.Shared/Analyzers/ForbidLiteralAttribute.cs
Normal file
11
Robust.Shared/Analyzers/ForbidLiteralAttribute.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
/// <summary>
|
||||
/// Marks that values used for this parameter should not be literal values.
|
||||
/// This helps prevent magic numbers/strings/etc, by indicating that values
|
||||
/// should either be wrapped (for validation) or defined as constants or readonly statics.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class ForbidLiteralAttribute : Attribute;
|
||||
@@ -1874,5 +1874,12 @@ namespace Robust.Shared
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ToolshedNearbyEntitiesLimit =
|
||||
CVarDef.Create("toolshed.nearby_entities_limit", 5, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* Localization
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<string> LocCultureName =
|
||||
CVarDef.Create("loc.culture_name", "en-US", CVar.ARCHIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,17 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
|
||||
shell.WriteLine($"Teleported {shell.Player} to {mapId}:{posX},{posY}.");
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHint("<x>"),
|
||||
2 => CompletionResult.FromHint("<y>"),
|
||||
3 => CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entityManager), "[MapId]"),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class TeleportToCommand : LocalizedEntityCommands
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Robust.Shared.GameObjects
|
||||
ComponentEventHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
where TEvent : notnull
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
@@ -206,7 +206,7 @@ namespace Robust.Shared.GameObjects
|
||||
ComponentEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
where TEvent : notnull
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
@@ -219,7 +219,7 @@ namespace Robust.Shared.GameObjects
|
||||
EntityEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
where TEvent : notnull
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
@@ -229,7 +229,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used by extension methods for <see cref="Subscriptions"/>
|
||||
/// to unsubscribe from from external sources such as CVars.
|
||||
/// to unsubscribe from external sources such as CVars.
|
||||
/// </remarks>
|
||||
/// <param name="action">An action to be ran when the entity system is shut down.</param>
|
||||
public void RegisterUnsubscription(Action action)
|
||||
|
||||
@@ -96,6 +96,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// If we're attached to the map we'll also just never disable collision due to how grid movement works.
|
||||
var canCollide = body.Awake ||
|
||||
body.ContactCount > 0 ||
|
||||
(TryComp(uid, out JointComponent? jointComponent) && jointComponent.JointCount > 0) ||
|
||||
xform.GridUid == null;
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public EntityUid? GetGrid(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.GridUid;
|
||||
return !Resolve(entity, ref entity.Comp, logMissing:false) ? null : entity.Comp.GridUid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +124,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public MapId GetMapId(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? MapId.Nullspace : entity.Comp.MapID;
|
||||
return !Resolve(entity, ref entity.Comp, logMissing: false) ? MapId.Nullspace : entity.Comp.MapID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,7 +137,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public EntityUid? GetMap(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.MapUid;
|
||||
return !Resolve(entity, ref entity.Comp, logMissing: false) ? null : entity.Comp.MapUid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,10 +167,10 @@ public abstract partial class SharedTransformSystem
|
||||
/// </summary>
|
||||
public bool InRange(Entity<TransformComponent?> entA, Entity<TransformComponent?> entB, float range)
|
||||
{
|
||||
if (!Resolve(entA, ref entA.Comp))
|
||||
if (!Resolve(entA, ref entA.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
if (!Resolve(entB, ref entB.Comp))
|
||||
if (!Resolve(entB, ref entB.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
if (!entA.Comp.ParentUid.IsValid() || !entB.Comp.ParentUid.IsValid())
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
@@ -95,9 +96,15 @@ namespace Robust.Shared.Localization
|
||||
CultureInfo? DefaultCulture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the culture has been loaded.
|
||||
/// Checks if the culture is loaded, if not,
|
||||
/// loads it via <see cref="ILocalizationManager.LoadCulture"/>
|
||||
/// and then set it as <see cref="ILocalizationManager.DefaultCulture"/>.
|
||||
/// </summary>
|
||||
void SetCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the culture has been loaded.
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
bool HasCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
@@ -106,6 +113,17 @@ namespace Robust.Shared.Localization
|
||||
/// <param name="culture"></param>
|
||||
void LoadCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Loads <see cref="CultureInfo"/> obtained from <see cref="CVars.LocCultureName"/>,
|
||||
/// they are different for client and server, and also can be saved.
|
||||
/// </summary>
|
||||
CultureInfo SetDefaultCulture();
|
||||
|
||||
/// <summary>
|
||||
/// Returns all locale directories from the game's resources.
|
||||
/// </summary>
|
||||
List<CultureInfo> GetFoundCultures();
|
||||
|
||||
/// <summary>
|
||||
/// Sets culture to be used in the absence of the main one.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -12,6 +13,7 @@ using Linguini.Shared.Types.Bundle;
|
||||
using Linguini.Syntax.Ast;
|
||||
using Linguini.Syntax.Parser;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +26,9 @@ namespace Robust.Shared.Localization
|
||||
{
|
||||
internal sealed partial class LocalizationManager : ILocalizationManagerInternal, IPostInjectInit
|
||||
{
|
||||
private static readonly ResPath LocaleDirPath = new("/Locale");
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
@@ -41,6 +46,18 @@ namespace Robust.Shared.Localization
|
||||
_prototype.PrototypesReloaded += OnPrototypesReloaded;
|
||||
}
|
||||
|
||||
public CultureInfo SetDefaultCulture()
|
||||
{
|
||||
var code = _configuration.GetCVar(CVars.LocCultureName);
|
||||
|
||||
var culture = CultureInfo.GetCultureInfo(code, predefinedOnly: false);
|
||||
SetCulture(culture);
|
||||
|
||||
// Return the culture for further work with it,
|
||||
// like of adding functions
|
||||
return culture;
|
||||
}
|
||||
|
||||
public string GetString(string messageId)
|
||||
{
|
||||
if (_defaultCulture == null)
|
||||
@@ -337,6 +354,17 @@ namespace Robust.Shared.Localization
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCulture(CultureInfo culture)
|
||||
{
|
||||
if (!HasCulture(culture))
|
||||
LoadCulture(culture);
|
||||
|
||||
if (DefaultCulture?.NameEquals(culture) ?? false)
|
||||
return;
|
||||
|
||||
DefaultCulture = culture;
|
||||
}
|
||||
|
||||
public bool HasCulture(CultureInfo culture)
|
||||
{
|
||||
return _contexts.ContainsKey(culture);
|
||||
@@ -344,6 +372,10 @@ namespace Robust.Shared.Localization
|
||||
|
||||
public void LoadCulture(CultureInfo culture)
|
||||
{
|
||||
// Attempting to load an already loaded culture
|
||||
if (HasCulture(culture))
|
||||
throw new InvalidOperationException("Culture is already loaded");
|
||||
|
||||
var bundle = LinguiniBuilder.Builder()
|
||||
.CultureInfo(culture)
|
||||
.SkipResources()
|
||||
@@ -358,6 +390,20 @@ namespace Robust.Shared.Localization
|
||||
DefaultCulture ??= culture;
|
||||
}
|
||||
|
||||
public List<CultureInfo> GetFoundCultures()
|
||||
{
|
||||
var result = new List<CultureInfo>();
|
||||
foreach (var name in _res.ContentGetDirectoryEntries(LocaleDirPath))
|
||||
{
|
||||
// Remove last "/" symbol
|
||||
// Example "en-US/" -> "en-US"
|
||||
var cultureName = name.TrimEnd('/');
|
||||
result.Add(CultureInfo.GetCultureInfo(cultureName, predefinedOnly: false));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SetFallbackCluture(params CultureInfo[] cultures)
|
||||
{
|
||||
_fallbackCultures = Array.Empty<(CultureInfo, FluentBundle)>();
|
||||
@@ -438,7 +484,7 @@ namespace Robust.Shared.Localization
|
||||
// Load data from .ftl files.
|
||||
// Data is loaded from /Locale/<language-code>/*
|
||||
|
||||
var root = new ResPath($"/Locale/{culture.Name}/");
|
||||
var root = LocaleDirPath / culture.Name;
|
||||
|
||||
var files = resourceManager.ContentFindFiles(root)
|
||||
.Where(c => c.Filename.EndsWith(".ftl", StringComparison.InvariantCultureIgnoreCase))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
|
||||
@@ -27,7 +27,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.PointCount = 1;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = Vector2.Zero; // Also here
|
||||
p0.Id.Key = 0;
|
||||
|
||||
@@ -79,9 +79,9 @@ internal sealed partial class CollisionManager
|
||||
manifold.Type = ManifoldType.Circles;
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.LocalPoint = P;
|
||||
manifold.Points[0].Id.Key = 0;
|
||||
manifold.Points[0].Id.Features = cf;
|
||||
manifold.Points[0].LocalPoint = circleB.Position;
|
||||
manifold.Points._00.Id.Key = 0;
|
||||
manifold.Points._00.Id.Features = cf;
|
||||
manifold.Points._00.LocalPoint = circleB.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,9 +114,9 @@ internal sealed partial class CollisionManager
|
||||
manifold.Type = ManifoldType.Circles;
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.LocalPoint = P;
|
||||
manifold.Points[0].Id.Key = 0;
|
||||
manifold.Points[0].Id.Features = cf;
|
||||
manifold.Points[0].LocalPoint = circleB.Position;
|
||||
manifold.Points._00.Id.Key = 0;
|
||||
manifold.Points._00.Id.Features = cf;
|
||||
manifold.Points._00.LocalPoint = circleB.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,8 +142,8 @@ internal sealed partial class CollisionManager
|
||||
manifold.Type = ManifoldType.FaceA;
|
||||
manifold.LocalNormal = n;
|
||||
manifold.LocalPoint = A;
|
||||
manifold.Points[0].Id.Key = 0;
|
||||
manifold.Points[0].Id.Features = cf;
|
||||
manifold.Points[0].LocalPoint = circleB.Position;
|
||||
manifold.Points._00.Id.Key = 0;
|
||||
manifold.Points._00.Id.Features = cf;
|
||||
manifold.Points._00.LocalPoint = circleB.Position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,15 +238,15 @@ internal sealed partial class CollisionManager
|
||||
}
|
||||
|
||||
var pointCount = 0;
|
||||
var points = manifold.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < 2; ++i)
|
||||
{
|
||||
float separation;
|
||||
|
||||
separation = Vector2.Dot(refFace.normal, clipPoints2[i].V - refFace.v1);
|
||||
var separation = Vector2.Dot(refFace.normal, clipPoints2[i].V - refFace.v1);
|
||||
|
||||
if (separation <= radius)
|
||||
{
|
||||
ref var cp = ref manifold.Points[pointCount];
|
||||
ref var cp = ref points[pointCount];
|
||||
|
||||
if (primaryAxis.Type == EPAxisType.EdgeA)
|
||||
{
|
||||
|
||||
@@ -64,7 +64,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = normals[normalIndex];
|
||||
manifold.LocalPoint = (v1 + v2) * 0.5f;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
@@ -88,7 +88,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = (cLocal - v1).Normalized();
|
||||
manifold.LocalPoint = v1;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
@@ -107,7 +107,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = (cLocal - v2).Normalized();
|
||||
manifold.LocalPoint = v2;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
@@ -126,7 +126,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = normals[vertIndex1];
|
||||
manifold.LocalPoint = faceCenter;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
|
||||
@@ -238,6 +238,8 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalPoint = planePoint;
|
||||
|
||||
int pointCount = 0;
|
||||
var points = manifold.Points.AsSpan;
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
Vector2 value = clipPoints2[i].V;
|
||||
@@ -245,7 +247,7 @@ internal sealed partial class CollisionManager
|
||||
|
||||
if (separation <= totalRadius)
|
||||
{
|
||||
ref var cp = ref manifold.Points[pointCount];
|
||||
ref var cp = ref points[pointCount];
|
||||
cp.LocalPoint = Transform.MulT(xf2, clipPoints2[i].V);
|
||||
cp.Id = clipPoints2[i].ID;
|
||||
|
||||
|
||||
@@ -51,15 +51,18 @@ internal sealed partial class CollisionManager : IManifoldManager
|
||||
in Manifold manifold2)
|
||||
{
|
||||
// Detect persists and removes.
|
||||
var points1 = manifold1.Points.AsSpan;
|
||||
var points2 = manifold2.Points.AsSpan;
|
||||
|
||||
for (int i = 0; i < manifold1.PointCount; ++i)
|
||||
{
|
||||
ContactID id = manifold1.Points[i].Id;
|
||||
var id = points1[i].Id;
|
||||
|
||||
state1[i] = PointState.Remove;
|
||||
|
||||
for (int j = 0; j < manifold2.PointCount; ++j)
|
||||
{
|
||||
if (manifold2.Points[j].Id.Key == id.Key)
|
||||
if (points2[j].Id.Key == id.Key)
|
||||
{
|
||||
state1[i] = PointState.Persist;
|
||||
break;
|
||||
@@ -70,13 +73,13 @@ internal sealed partial class CollisionManager : IManifoldManager
|
||||
// Detect persists and adds.
|
||||
for (int i = 0; i < manifold2.PointCount; ++i)
|
||||
{
|
||||
ContactID id = manifold2.Points[i].Id;
|
||||
var id = points2[i].Id;
|
||||
|
||||
state2[i] = PointState.Add;
|
||||
|
||||
for (int j = 0; j < manifold1.PointCount; ++j)
|
||||
for (var j = 0; j < manifold1.PointCount; ++j)
|
||||
{
|
||||
if (manifold1.Points[j].Id.Key == id.Key)
|
||||
if (points1[j].Id.Key == id.Key)
|
||||
{
|
||||
state2[i] = PointState.Persist;
|
||||
break;
|
||||
|
||||
@@ -25,6 +25,7 @@ using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Collision;
|
||||
|
||||
@@ -146,7 +147,7 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
/// <summary>
|
||||
/// Points of contact, can only be 0 -> 2.
|
||||
/// </summary>
|
||||
internal ManifoldPoint[] Points;
|
||||
internal FixedArray2<ManifoldPoint> Points;
|
||||
|
||||
public ManifoldType Type;
|
||||
|
||||
@@ -157,9 +158,12 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
LocalNormal.Equals(other.LocalNormal) &&
|
||||
LocalPoint.Equals(other.LocalPoint))) return false;
|
||||
|
||||
var points = Points.AsSpan;
|
||||
var otherPoints = other.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
if (!Points[i].Equals(other.Points[i])) return false;
|
||||
if (!points[i].Equals(otherPoints[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -172,9 +176,12 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
LocalNormal.EqualsApprox(other.LocalNormal) &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint))) return false;
|
||||
|
||||
var points = Points.AsSpan;
|
||||
var otherPoints = other.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
if (!Points[i].EqualsApprox(other.Points[i])) return false;
|
||||
if (!points[i].EqualsApprox(otherPoints[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -187,13 +194,26 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
LocalNormal.EqualsApprox(other.LocalNormal, tolerance) &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint, tolerance))) return false;
|
||||
|
||||
var points = Points.AsSpan;
|
||||
var otherPoints = other.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
if (!Points[i].EqualsApprox(other.Points[i], tolerance)) return false;
|
||||
if (!points[i].EqualsApprox(otherPoints[i], tolerance)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Manifold manifold && Equals(manifold);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(LocalNormal, LocalPoint, PointCount, Points, (int)Type);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ManifoldPoint : IEquatable<ManifoldPoint>, IApproxEquatable<ManifoldPoint>
|
||||
|
||||
@@ -206,16 +206,19 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
// Match old contact ids to new contact ids and copy the
|
||||
// stored impulses to warm start the solver.
|
||||
var points = Manifold.Points.AsSpan;
|
||||
var oldPoints = oldManifold.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < Manifold.PointCount; ++i)
|
||||
{
|
||||
var mp2 = Manifold.Points[i];
|
||||
var mp2 = points[i];
|
||||
mp2.NormalImpulse = 0.0f;
|
||||
mp2.TangentImpulse = 0.0f;
|
||||
var id2 = mp2.Id;
|
||||
|
||||
for (var j = 0; j < oldManifold.PointCount; ++j)
|
||||
{
|
||||
var mp1 = oldManifold.Points[j];
|
||||
var mp1 = oldPoints[j];
|
||||
|
||||
if (mp1.Id.Key == id2.Key)
|
||||
{
|
||||
@@ -225,7 +228,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
}
|
||||
}
|
||||
|
||||
Manifold.Points[i] = mp2;
|
||||
points[i] = mp2;
|
||||
}
|
||||
|
||||
if (touching != wasTouching)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
@@ -37,7 +38,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
/// </summary>
|
||||
public int IndexB { get; set; }
|
||||
|
||||
public Vector2[] LocalPoints;
|
||||
internal FixedArray2<Vector2> LocalPoints;
|
||||
|
||||
public Vector2 LocalNormal;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
*/
|
||||
|
||||
using System.Numerics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
{
|
||||
@@ -39,13 +40,13 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
public int IndexB;
|
||||
|
||||
// Use 2 as its the max number of manifold points.
|
||||
public VelocityConstraintPoint[] Points;
|
||||
public FixedArray2<VelocityConstraintPoint> Points;
|
||||
|
||||
public Vector2 Normal;
|
||||
|
||||
public System.Numerics.Vector4 NormalMass;
|
||||
public Vector4 NormalMass;
|
||||
|
||||
public System.Numerics.Vector4 K;
|
||||
public Vector4 K;
|
||||
|
||||
public float InvMassA;
|
||||
public float InvMassB;
|
||||
|
||||
@@ -77,113 +77,7 @@ namespace Robust.Shared.Physics.Systems
|
||||
{
|
||||
var data = new MassData();
|
||||
|
||||
// Box2D just calls fixture.GetMassData which just calls the shape method anyway soooo
|
||||
// we can just cut out the middle-man
|
||||
switch (shape)
|
||||
{
|
||||
case ChainShape:
|
||||
data.Mass = 0f;
|
||||
data.Center = Vector2.Zero;
|
||||
data.I = 0f;
|
||||
break;
|
||||
case EdgeShape edge:
|
||||
data.Mass = 0.0f;
|
||||
data.Center = (edge.Vertex1 + edge.Vertex2) * 0.5f;
|
||||
data.I = 0.0f;
|
||||
break;
|
||||
case PhysShapeCircle circle:
|
||||
// massData->mass = density * b2_pi * m_radius * m_radius;
|
||||
data.Center = circle.Position;
|
||||
|
||||
// inertia about the local origin
|
||||
data.I = data.Mass * (0.5f * circle.Radius * circle.Radius + Vector2.Dot(circle.Position, circle.Position));
|
||||
break;
|
||||
case PhysShapeAabb aabb:
|
||||
var polygon = (PolygonShape) aabb;
|
||||
GetMassData(polygon, ref data, density);
|
||||
break;
|
||||
case Polygon fastPoly:
|
||||
return GetMassData(new PolygonShape(fastPoly), density);
|
||||
case SlimPolygon slim:
|
||||
return GetMassData(new PolygonShape(slim), density);
|
||||
case PolygonShape poly:
|
||||
// Polygon mass, centroid, and inertia.
|
||||
// Let rho be the polygon density in mass per unit area.
|
||||
// Then:
|
||||
// mass = rho * int(dA)
|
||||
// centroid.x = (1/mass) * rho * int(x * dA)
|
||||
// centroid.y = (1/mass) * rho * int(y * dA)
|
||||
// I = rho * int((x*x + y*y) * dA)
|
||||
//
|
||||
// We can compute these integrals by summing all the integrals
|
||||
// for each triangle of the polygon. To evaluate the integral
|
||||
// for a single triangle, we make a change of variables to
|
||||
// the (u,v) coordinates of the triangle:
|
||||
// x = x0 + e1x * u + e2x * v
|
||||
// y = y0 + e1y * u + e2y * v
|
||||
// where 0 <= u && 0 <= v && u + v <= 1.
|
||||
//
|
||||
// We integrate u from [0,1-v] and then v from [0,1].
|
||||
// We also need to use the Jacobian of the transformation:
|
||||
// D = cross(e1, e2)
|
||||
//
|
||||
// Simplification: triangle centroid = (1/3) * (p1 + p2 + p3)
|
||||
//
|
||||
// The rest of the derivation is handled by computer algebra.
|
||||
|
||||
var count = poly.VertexCount;
|
||||
DebugTools.Assert(count >= 3);
|
||||
|
||||
Vector2 center = new(0.0f, 0.0f);
|
||||
var area = 0.0f;
|
||||
var I = 0.0f;
|
||||
|
||||
// Get a reference point for forming triangles.
|
||||
// Use the first vertex to reduce round-off errors.
|
||||
var s = poly.Vertices[0];
|
||||
|
||||
const float k_inv3 = 1.0f / 3.0f;
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
// Triangle vertices.
|
||||
var e1 = poly.Vertices[i] - s;
|
||||
var e2 = i + 1 < count ? poly.Vertices[i+1] - s : poly.Vertices[0] - s;
|
||||
|
||||
var D = Vector2Helpers.Cross(e1, e2);
|
||||
|
||||
var triangleArea = 0.5f * D;
|
||||
area += triangleArea;
|
||||
|
||||
// Area weighted centroid
|
||||
center += (e1 + e2) * triangleArea * k_inv3;
|
||||
|
||||
float ex1 = e1.X, ey1 = e1.Y;
|
||||
float ex2 = e2.X, ey2 = e2.Y;
|
||||
|
||||
var intx2 = ex1*ex1 + ex2*ex1 + ex2*ex2;
|
||||
var inty2 = ey1*ey1 + ey2*ey1 + ey2*ey2;
|
||||
|
||||
I += (0.25f * k_inv3 * D) * (intx2 + inty2);
|
||||
}
|
||||
|
||||
// Total mass
|
||||
data.Mass = density * area;
|
||||
|
||||
// Center of mass
|
||||
DebugTools.Assert(area > float.Epsilon);
|
||||
center *= 1.0f / area;
|
||||
data.Center = center + s;
|
||||
|
||||
// Inertia tensor relative to the local origin (point s).
|
||||
data.I = density * I;
|
||||
|
||||
// Shift to center of mass then to original body origin.
|
||||
data.I += data.Mass * (Vector2.Dot(data.Center, data.Center) - Vector2.Dot(center, center));
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Cannot get MassData for {shape} as it's not implemented!");
|
||||
}
|
||||
GetMassData(shape, ref data, density);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
@@ -113,10 +113,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
#if DEBUG
|
||||
contact._debugPhysics = _debugPhysicsSystem;
|
||||
#endif
|
||||
contact.Manifold = new Manifold
|
||||
{
|
||||
Points = new ManifoldPoint[2]
|
||||
};
|
||||
contact.Manifold = new Manifold();
|
||||
|
||||
return contact;
|
||||
}
|
||||
@@ -427,6 +424,12 @@ public abstract partial class SharedPhysicsSystem
|
||||
var xformA = _xformQuery.GetComponent(uidA);
|
||||
var xformB = _xformQuery.GetComponent(uidB);
|
||||
|
||||
if (xformA.MapID == MapId.Nullspace || xformB.MapID == MapId.Nullspace)
|
||||
{
|
||||
DestroyContact(contact);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this contact flagged for filtering?
|
||||
if ((contact.Flags & ContactFlags.Filter) != 0x0)
|
||||
{
|
||||
|
||||
@@ -1026,12 +1026,6 @@ public abstract partial class SharedPhysicsSystem
|
||||
var angle = angles[offset + i];
|
||||
var xform = xformQuery.GetComponent(uid);
|
||||
|
||||
// Temporary NaN guards until PVS is fixed.
|
||||
if (!float.IsNaN(position.X) && !float.IsNaN(position.Y))
|
||||
{
|
||||
_transform.SetLocalPositionRotation(xform, xform.LocalPosition + position, xform.LocalRotation + angle);
|
||||
}
|
||||
|
||||
var linVelocity = linearVelocities[offset + i];
|
||||
var physicsDirtied = false;
|
||||
|
||||
@@ -1047,6 +1041,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
physicsDirtied |= SetAngularVelocity(uid, angVelocity, false, body: body);
|
||||
}
|
||||
|
||||
// Temporary NaN guards until PVS is fixed.
|
||||
// May reparent object and change body's velocity.
|
||||
if (!float.IsNaN(position.X) && !float.IsNaN(position.Y))
|
||||
{
|
||||
_transform.SetLocalPositionRotation(xform, xform.LocalPosition + position, xform.LocalRotation + angle);
|
||||
}
|
||||
|
||||
if (physicsDirtied)
|
||||
Dirty(uid, body);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ public abstract partial class SharedPhysicsSystem
|
||||
velocityConstraint.TangentSpeed = contact.TangentSpeed;
|
||||
velocityConstraint.IndexA = bodyA.IslandIndex[island.Index];
|
||||
velocityConstraint.IndexB = bodyB.IslandIndex[island.Index];
|
||||
Array.Resize(ref velocityConstraint.Points, 2);
|
||||
// Don't need to reset point data as it all gets set below.
|
||||
|
||||
var (invMassA, invMassB) = GetInvMass(bodyA, bodyB);
|
||||
@@ -87,7 +86,6 @@ public abstract partial class SharedPhysicsSystem
|
||||
(positionConstraint.InvMassA, positionConstraint.InvMassB) = (invMassA, invMassB);
|
||||
positionConstraint.LocalCenterA = bodyA.LocalCenter;
|
||||
positionConstraint.LocalCenterB = bodyB.LocalCenter;
|
||||
Array.Resize(ref positionConstraint.LocalPoints, 2);
|
||||
|
||||
positionConstraint.InvIA = bodyA.InvI;
|
||||
positionConstraint.InvIB = bodyB.InvI;
|
||||
@@ -97,11 +95,14 @@ public abstract partial class SharedPhysicsSystem
|
||||
positionConstraint.RadiusA = radiusA;
|
||||
positionConstraint.RadiusB = radiusB;
|
||||
positionConstraint.Type = manifold.Type;
|
||||
var points = manifold.Points.AsSpan;
|
||||
var posPoints = positionConstraint.LocalPoints.AsSpan;
|
||||
var velPoints = velocityConstraint.Points.AsSpan;
|
||||
|
||||
for (var j = 0; j < pointCount; ++j)
|
||||
{
|
||||
var contactPoint = manifold.Points[j];
|
||||
ref var constraintPoint = ref velocityConstraint.Points[j];
|
||||
var contactPoint = points[j];
|
||||
ref var constraintPoint = ref velPoints[j];
|
||||
|
||||
if (_warmStarting)
|
||||
{
|
||||
@@ -120,7 +121,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
constraintPoint.TangentMass = 0.0f;
|
||||
constraintPoint.VelocityBias = 0.0f;
|
||||
|
||||
positionConstraint.LocalPoints[j] = contactPoint.LocalPoint;
|
||||
posPoints[j] = contactPoint.LocalPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,10 +221,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
velocityConstraint.Normal = normal;
|
||||
|
||||
int pointCount = velocityConstraint.PointCount;
|
||||
var velPoints = velocityConstraint.Points.AsSpan;
|
||||
|
||||
for (int j = 0; j < pointCount; ++j)
|
||||
{
|
||||
ref var vcp = ref velocityConstraint.Points[j];
|
||||
ref var vcp = ref velPoints[j];
|
||||
|
||||
vcp.RelativeVelocityA = points[j] - centerA;
|
||||
vcp.RelativeVelocityB = points[j] - centerB;
|
||||
@@ -256,8 +258,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
// If we have two points, then prepare the block solver.
|
||||
if (velocityConstraint.PointCount == 2)
|
||||
{
|
||||
var vcp1 = velocityConstraint.Points[0];
|
||||
var vcp2 = velocityConstraint.Points[1];
|
||||
var vcp1 = velocityConstraint.Points._00;
|
||||
var vcp2 = velocityConstraint.Points._01;
|
||||
|
||||
var rn1A = Vector2Helpers.Cross(vcp1.RelativeVelocityA, velocityConstraint.Normal);
|
||||
var rn1B = Vector2Helpers.Cross(vcp1.RelativeVelocityB, velocityConstraint.Normal);
|
||||
@@ -299,6 +301,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
for (var i = 0; i < island.Contacts.Count; ++i)
|
||||
{
|
||||
var velocityConstraint = velocityConstraints[i];
|
||||
var velPoints = velocityConstraint.Points.AsSpan;
|
||||
|
||||
var indexA = velocityConstraint.IndexA;
|
||||
var indexB = velocityConstraint.IndexB;
|
||||
@@ -318,7 +321,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
for (var j = 0; j < pointCount; ++j)
|
||||
{
|
||||
var constraintPoint = velocityConstraint.Points[j];
|
||||
var constraintPoint = velPoints[j];
|
||||
var P = normal * constraintPoint.NormalImpulse + tangent * constraintPoint.TangentImpulse;
|
||||
angVelocityA -= invIA * Vector2Helpers.Cross(constraintPoint.RelativeVelocityA, P);
|
||||
linVelocityA -= P * invMassA;
|
||||
@@ -386,12 +389,13 @@ public abstract partial class SharedPhysicsSystem
|
||||
var friction = velocityConstraint.Friction;
|
||||
|
||||
DebugTools.Assert(pointCount is 1 or 2);
|
||||
var velPoints = velocityConstraint.Points.AsSpan;
|
||||
|
||||
// Solve tangent constraints first because non-penetration is more important
|
||||
// than friction.
|
||||
for (var j = 0; j < pointCount; ++j)
|
||||
{
|
||||
ref var velConstraintPoint = ref velocityConstraint.Points[j];
|
||||
ref var velConstraintPoint = ref velPoints[j];
|
||||
|
||||
// Relative velocity at contact
|
||||
var dv = vB + Vector2Helpers.Cross(wB, velConstraintPoint.RelativeVelocityB) - vA - Vector2Helpers.Cross(wA, velConstraintPoint.RelativeVelocityA);
|
||||
@@ -419,7 +423,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
// Solve normal constraints
|
||||
if (velocityConstraint.PointCount == 1)
|
||||
{
|
||||
ref var vcp = ref velocityConstraint.Points[0];
|
||||
ref var vcp = ref velocityConstraint.Points._00;
|
||||
|
||||
// Relative velocity at contact
|
||||
Vector2 dv = vB + Vector2Helpers.Cross(wB, vcp.RelativeVelocityB) - vA - Vector2Helpers.Cross(wA, vcp.RelativeVelocityA);
|
||||
@@ -476,8 +480,8 @@ public abstract partial class SharedPhysicsSystem
|
||||
// = A * x + b'
|
||||
// b' = b - A * a;
|
||||
|
||||
ref var cp1 = ref velocityConstraint.Points[0];
|
||||
ref var cp2 = ref velocityConstraint.Points[1];
|
||||
ref var cp1 = ref velocityConstraint.Points._00;
|
||||
ref var cp2 = ref velocityConstraint.Points._01;
|
||||
|
||||
Vector2 a = new Vector2(cp1.NormalImpulse, cp2.NormalImpulse);
|
||||
DebugTools.Assert(a.X >= 0.0f && a.Y >= 0.0f);
|
||||
@@ -643,14 +647,16 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
for (var i = 0; i < island.Contacts.Count; ++i)
|
||||
{
|
||||
ContactVelocityConstraint velocityConstraint = velocityConstraints[i];
|
||||
var velocityConstraint = velocityConstraints[i];
|
||||
ref var manifold = ref island.Contacts[velocityConstraint.ContactIndex].Manifold;
|
||||
var manPoints = manifold.Points.AsSpan;
|
||||
var velPoints = velocityConstraint.Points.AsSpan;
|
||||
|
||||
for (var j = 0; j < velocityConstraint.PointCount; ++j)
|
||||
{
|
||||
ref var point = ref manifold.Points[j];
|
||||
point.NormalImpulse = velocityConstraint.Points[j].NormalImpulse;
|
||||
point.TangentImpulse = velocityConstraint.Points[j].TangentImpulse;
|
||||
ref var point = ref manPoints[j];
|
||||
point.NormalImpulse = velPoints[j].NormalImpulse;
|
||||
point.TangentImpulse = velPoints[j].TangentImpulse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,7 +800,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
normal = new Vector2(1.0f, 0.0f);
|
||||
Vector2 pointA = Physics.Transform.Mul(xfA, manifold.LocalPoint);
|
||||
Vector2 pointB = Physics.Transform.Mul(xfB, manifold.Points[0].LocalPoint);
|
||||
Vector2 pointB = Physics.Transform.Mul(xfB, manifold.Points._00.LocalPoint);
|
||||
|
||||
if ((pointA - pointB).LengthSquared() > float.Epsilon * float.Epsilon)
|
||||
{
|
||||
@@ -812,10 +818,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
normal = Physics.Transform.Mul(xfA.Quaternion2D, manifold.LocalNormal);
|
||||
Vector2 planePoint = Physics.Transform.Mul(xfA, manifold.LocalPoint);
|
||||
var manPoints = manifold.Points.AsSpan;
|
||||
|
||||
for (int i = 0; i < manifold.PointCount; ++i)
|
||||
{
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfB, manifold.Points[i].LocalPoint);
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfB, manPoints[i].LocalPoint);
|
||||
Vector2 cA = clipPoint + normal * (radiusA - Vector2.Dot(clipPoint - planePoint, normal));
|
||||
Vector2 cB = clipPoint - normal * radiusB;
|
||||
points[i] = (cA + cB) * 0.5f;
|
||||
@@ -827,10 +834,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
{
|
||||
normal = Physics.Transform.Mul(xfB.Quaternion2D, manifold.LocalNormal);
|
||||
Vector2 planePoint = Physics.Transform.Mul(xfB, manifold.LocalPoint);
|
||||
var manPoints = manifold.Points.AsSpan;
|
||||
|
||||
for (int i = 0; i < manifold.PointCount; ++i)
|
||||
{
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfA, manifold.Points[i].LocalPoint);
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfA, manPoints[i].LocalPoint);
|
||||
Vector2 cB = clipPoint + normal * (radiusB - Vector2.Dot(clipPoint - planePoint, normal));
|
||||
Vector2 cA = clipPoint - normal * radiusA;
|
||||
points[i] = (cA + cB) * 0.5f;
|
||||
@@ -863,7 +871,7 @@ public abstract partial class SharedPhysicsSystem
|
||||
case ManifoldType.Circles:
|
||||
{
|
||||
Vector2 pointA = Physics.Transform.Mul(xfA, pc.LocalPoint);
|
||||
Vector2 pointB = Physics.Transform.Mul(xfB, pc.LocalPoints[0]);
|
||||
Vector2 pointB = Physics.Transform.Mul(xfB, pc.LocalPoints._00);
|
||||
normal = pointB - pointA;
|
||||
|
||||
//FPE: Fix to handle zero normalization
|
||||
@@ -877,10 +885,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
case ManifoldType.FaceA:
|
||||
{
|
||||
var pcPoints = pc.LocalPoints.AsSpan;
|
||||
normal = Physics.Transform.Mul(xfA.Quaternion2D, pc.LocalNormal);
|
||||
Vector2 planePoint = Physics.Transform.Mul(xfA, pc.LocalPoint);
|
||||
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfB, pc.LocalPoints[index]);
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfB, pcPoints[index]);
|
||||
separation = Vector2.Dot(clipPoint - planePoint, normal) - pc.RadiusA - pc.RadiusB;
|
||||
point = clipPoint;
|
||||
}
|
||||
@@ -888,10 +897,11 @@ public abstract partial class SharedPhysicsSystem
|
||||
|
||||
case ManifoldType.FaceB:
|
||||
{
|
||||
var pcPoints = pc.LocalPoints.AsSpan;
|
||||
normal = Physics.Transform.Mul(xfB.Quaternion2D, pc.LocalNormal);
|
||||
Vector2 planePoint = Physics.Transform.Mul(xfB, pc.LocalPoint);
|
||||
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfA, pc.LocalPoints[index]);
|
||||
Vector2 clipPoint = Physics.Transform.Mul(xfA, pcPoints[index]);
|
||||
separation = Vector2.Dot(clipPoint - planePoint, normal) - pc.RadiusA - pc.RadiusB;
|
||||
point = clipPoint;
|
||||
|
||||
|
||||
11
Robust.Shared/Utility/CultureInfoExtension.cs
Normal file
11
Robust.Shared/Utility/CultureInfoExtension.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace Robust.Shared.Utility;
|
||||
|
||||
public static class CultureInfoExtension
|
||||
{
|
||||
public static bool NameEquals(this CultureInfo cultureInfo, CultureInfo otherCultureInfo)
|
||||
{
|
||||
return cultureInfo.Name == otherCultureInfo.Name;
|
||||
}
|
||||
}
|
||||
@@ -79,12 +79,34 @@ namespace Robust.Shared.Utility
|
||||
}
|
||||
}
|
||||
|
||||
internal struct FixedArray2<T>
|
||||
internal struct FixedArray2<T> : IEquatable<FixedArray2<T>>
|
||||
{
|
||||
public T _00;
|
||||
public T _01;
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 2);
|
||||
|
||||
internal FixedArray2(T x0, T x1)
|
||||
{
|
||||
_00 = x0;
|
||||
_01 = x1;
|
||||
}
|
||||
|
||||
public bool Equals(FixedArray2<T> other)
|
||||
{
|
||||
return EqualityComparer<T>.Default.Equals(_00, other._00) &&
|
||||
EqualityComparer<T>.Default.Equals(_01, other._01);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is FixedArray2<T> other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_00, _01);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct FixedArray4<T> : IEquatable<FixedArray4<T>>
|
||||
@@ -96,12 +118,20 @@ namespace Robust.Shared.Utility
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 4);
|
||||
|
||||
internal FixedArray4(T x0, T x1, T x2, T x3)
|
||||
{
|
||||
_00 = x0;
|
||||
_01 = x1;
|
||||
_02 = x2;
|
||||
_03 = x3;
|
||||
}
|
||||
|
||||
public bool Equals(FixedArray4<T> other)
|
||||
{
|
||||
return _00?.Equals(other._00) == true &&
|
||||
_01?.Equals(other._01) == true &&
|
||||
_02?.Equals(other._02) == true &&
|
||||
_03?.Equals(other._03) == true;
|
||||
return EqualityComparer<T>.Default.Equals(_00, other._00) &&
|
||||
EqualityComparer<T>.Default.Equals(_01, other._01) &&
|
||||
EqualityComparer<T>.Default.Equals(_02, other._02) &&
|
||||
EqualityComparer<T>.Default.Equals(_03, other._03);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
@@ -128,16 +158,28 @@ namespace Robust.Shared.Utility
|
||||
|
||||
public Span<T> AsSpan => MemoryMarshal.CreateSpan(ref _00, 8);
|
||||
|
||||
internal FixedArray8(T x0, T x1, T x2, T x3, T x4, T x5, T x6, T x7)
|
||||
{
|
||||
_00 = x0;
|
||||
_01 = x1;
|
||||
_02 = x2;
|
||||
_03 = x3;
|
||||
_04 = x4;
|
||||
_05 = x5;
|
||||
_06 = x6;
|
||||
_07 = x7;
|
||||
}
|
||||
|
||||
public bool Equals(FixedArray8<T> other)
|
||||
{
|
||||
return _00?.Equals(other._00) == true &&
|
||||
_01?.Equals(other._01) == true &&
|
||||
_02?.Equals(other._02) == true &&
|
||||
_03?.Equals(other._03) == true &&
|
||||
_04?.Equals(other._04) == true &&
|
||||
_05?.Equals(other._05) == true &&
|
||||
_06?.Equals(other._06) == true &&
|
||||
_07?.Equals(other._07) == true;
|
||||
return EqualityComparer<T>.Default.Equals(_00, other._00) &&
|
||||
EqualityComparer<T>.Default.Equals(_01, other._01) &&
|
||||
EqualityComparer<T>.Default.Equals(_02, other._02) &&
|
||||
EqualityComparer<T>.Default.Equals(_03, other._03) &&
|
||||
EqualityComparer<T>.Default.Equals(_04, other._04) &&
|
||||
EqualityComparer<T>.Default.Equals(_05, other._05) &&
|
||||
EqualityComparer<T>.Default.Equals(_06, other._06) &&
|
||||
EqualityComparer<T>.Default.Equals(_07, other._07);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
|
||||
@@ -281,13 +281,28 @@ public sealed partial class FormattedMessage : IEquatable<FormattedMessage>, IRe
|
||||
/// <inheritdoc />
|
||||
public bool Equals(FormattedMessage? other)
|
||||
{
|
||||
return other?.ToMarkup() == ToMarkup();
|
||||
if (_nodes.Count != other?._nodes.Count)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < _nodes.Count; i++)
|
||||
{
|
||||
if (!_nodes[i].Equals(other?._nodes[i]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ToMarkup().GetHashCode();
|
||||
var hash = 0;
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
hash = HashCode.Combine(hash, node.GetHashCode());
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
/// <returns>The string without markup tags.</returns>
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed partial class EntityManagerCopyTests
|
||||
|
||||
var targetComp = entManager.CopyComponent(original, target, comp);
|
||||
|
||||
Assert.That(targetComp!.Owner == target);
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
}
|
||||
@@ -67,7 +67,7 @@ public sealed partial class EntityManagerCopyTests
|
||||
|
||||
var targetComp = entManager.CopyComponent(original, target, (IComponent) comp);
|
||||
|
||||
Assert.That(targetComp!.Owner == target);
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(((AComponent) targetComp).Value, Is.EqualTo(comp.Value));
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
}
|
||||
@@ -102,10 +102,10 @@ public sealed partial class EntityManagerCopyTests
|
||||
var targetComp = entManager.GetComponent<AComponent>(target);
|
||||
var targetComp2 = entManager.GetComponent<BComponent>(target);
|
||||
|
||||
Assert.That(targetComp!.Owner == target);
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
|
||||
|
||||
Assert.That(targetComp2!.Owner == target);
|
||||
Assert.That(entManager.GetComponent<BComponent>(target), Is.EqualTo(targetComp2));
|
||||
Assert.That(targetComp2.Value, Is.EqualTo(comp2.Value));
|
||||
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
@@ -142,16 +142,16 @@ public sealed partial class EntityManagerCopyTests
|
||||
var targetComp = entManager.GetComponent<AComponent>(target);
|
||||
var targetComp2 = entManager.GetComponent<BComponent>(target);
|
||||
|
||||
Assert.That(targetComp!.Owner == target);
|
||||
Assert.That(entManager.GetComponent<AComponent>(target), Is.EqualTo(targetComp));
|
||||
Assert.That(targetComp.Value, Is.EqualTo(comp.Value));
|
||||
|
||||
Assert.That(targetComp2!.Owner == target);
|
||||
Assert.That(entManager.GetComponent<BComponent>(target), Is.EqualTo(targetComp2));
|
||||
Assert.That(targetComp2.Value, Is.EqualTo(comp2.Value));
|
||||
|
||||
Assert.That(!ReferenceEquals(comp, targetComp));
|
||||
Assert.That(!ReferenceEquals(comp2, targetComp2));
|
||||
}
|
||||
|
||||
|
||||
[DataDefinition]
|
||||
private sealed partial class AComponent : Component
|
||||
{
|
||||
|
||||
@@ -97,10 +97,11 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
public void NoParent_OffsetZero()
|
||||
{
|
||||
var entMan = IoCManager.Resolve<IEntityManager>();
|
||||
var xformSys = entMan.System<SharedTransformSystem>();
|
||||
var uid = entMan.SpawnEntity(null, MapCoordinates.Nullspace);
|
||||
var xform = entMan.GetComponent<TransformComponent>(uid);
|
||||
Assert.That(xform.Coordinates.Position, Is.EqualTo(Vector2.Zero));
|
||||
xform.LocalPosition = Vector2.One;
|
||||
xformSys.SetLocalPosition(uid, Vector2.One);
|
||||
Assert.That(xform.Coordinates.Position, Is.EqualTo(Vector2.Zero));
|
||||
}
|
||||
|
||||
@@ -108,12 +109,13 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
public void GetGridId_Map()
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
var mapEnt = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(mapEnt).Coordinates.GetGridUid(entityManager), Is.Null);
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.GetGridUid(entityManager), Is.Null);
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.EntityId, Is.EqualTo(mapEnt));
|
||||
Assert.That(xformSys.GetGrid(entityManager.GetComponent<TransformComponent>(mapEnt).Coordinates), Is.Null);
|
||||
Assert.That(xformSys.GetGrid(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.Null);
|
||||
Assert.That(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates.EntityId, Is.EqualTo(mapEnt));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -121,6 +123,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
@@ -128,20 +131,21 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(gridEnt, Vector2.Zero));
|
||||
|
||||
// Grids aren't parented to other grids.
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).Coordinates.GetGridUid(entityManager), Is.Null);
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.GetGridUid(entityManager), Is.EqualTo(grid.Owner));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.EntityId, Is.EqualTo(gridEnt));
|
||||
Assert.That(xformSys.GetGrid(entityManager.GetComponent<TransformComponent>(gridEnt).Coordinates), Is.Null);
|
||||
Assert.That(xformSys.GetGrid(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(grid.Owner));
|
||||
Assert.That(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates.EntityId, Is.EqualTo(gridEnt));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetMapId_Map()
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
var mapEnt = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new MapCoordinates(Vector2.Zero, mapId));
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(mapEnt).Coordinates.GetMapId(entityManager), Is.EqualTo(mapId));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.GetMapId(entityManager), Is.EqualTo(mapId));
|
||||
Assert.That(xformSys.GetMapId(entityManager.GetComponent<TransformComponent>(mapEnt).Coordinates), Is.EqualTo(mapId));
|
||||
Assert.That(xformSys.GetMapId(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(mapId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -149,14 +153,15 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
var gridEnt = grid.Owner;
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(gridEnt, Vector2.Zero));
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).Coordinates.GetMapId(entityManager), Is.EqualTo(mapId));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.GetMapId(entityManager), Is.EqualTo(mapId));
|
||||
Assert.That(xformSys.GetMapId(entityManager.GetComponent<TransformComponent>(gridEnt).Coordinates), Is.EqualTo(mapId));
|
||||
Assert.That(xformSys.GetMapId(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(mapId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -164,6 +169,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
var mapEnt = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
@@ -175,7 +181,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
Assert.That(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates.EntityId, Is.EqualTo(gridEnt));
|
||||
|
||||
// Reparenting the entity should return correct results.
|
||||
entityManager.GetComponent<TransformComponent>(newEnt).AttachParent(mapEnt);
|
||||
xformSys.SetParent(newEnt, mapEnt);
|
||||
|
||||
Assert.That(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates.EntityId, Is.EqualTo(mapEnt));
|
||||
}
|
||||
@@ -185,6 +191,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
var mapEnt = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
@@ -205,7 +212,7 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
Assert.That(newEntCoords.EntityId, Is.EqualTo(gridEnt));
|
||||
|
||||
// Reparenting the entity should return correct results.
|
||||
newEntTransform.AttachParent(mapEnt);
|
||||
xformSys.SetParent(newEnt, mapEnt);
|
||||
var newEntCoords2 = newEntTransform.Coordinates;
|
||||
|
||||
Assert.That(newEntCoords2.IsValid(entityManager), Is.EqualTo(true));
|
||||
@@ -235,19 +242,18 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
var transformSystem = entityManager.System<SharedTransformSystem>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
var gridEnt = grid.Owner;
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, entPos));
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.ToMap(entityManager, transformSystem), Is.EqualTo(new MapCoordinates(entPos, mapId)));
|
||||
Assert.That(xformSys.ToMapCoordinates(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(new MapCoordinates(entPos, mapId)));
|
||||
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalPosition += gridPos;
|
||||
xformSys.SetLocalPosition(gridEnt, entityManager.GetComponent<TransformComponent>(gridEnt).LocalPosition + gridPos);
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.ToMap(entityManager, transformSystem), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId)));
|
||||
Assert.That(xformSys.ToMapCoordinates(entityManager.GetComponent<TransformComponent>(newEnt).Coordinates), Is.EqualTo(new MapCoordinates(entPos + gridPos, mapId)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -255,35 +261,38 @@ namespace Robust.UnitTesting.Shared.Map
|
||||
{
|
||||
var entityManager = IoCManager.Resolve<IEntityManager>();
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
var xformSys = entityManager.System<SharedTransformSystem>();
|
||||
|
||||
var mapEnt = entityManager.System<SharedMapSystem>().CreateMap(out var mapId);
|
||||
var grid = mapManager.CreateGridEntity(mapId);
|
||||
var gridEnt = grid.Owner;
|
||||
var newEnt = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(grid, Vector2.Zero));
|
||||
var newEntXform = entityManager.GetComponent<TransformComponent>(newEnt);
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.WithEntityId(mapEnt).Position, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(xformSys.WithEntityId(newEntXform.Coordinates, mapEnt).Position, Is.EqualTo(Vector2.Zero));
|
||||
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).LocalPosition = Vector2.One;
|
||||
xformSys.SetLocalPosition(newEnt, Vector2.One);
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.WithEntityId(mapEnt).Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(newEntXform.Coordinates.Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(xformSys.WithEntityId(newEntXform.Coordinates, mapEnt).Position, Is.EqualTo(Vector2.One));
|
||||
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(gridEnt).LocalPosition = Vector2.One;
|
||||
xformSys.SetLocalPosition(gridEnt, Vector2.One);
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.WithEntityId(mapEnt).Position, Is.EqualTo(new Vector2(2, 2)));
|
||||
Assert.That(newEntXform.Coordinates.Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(xformSys.WithEntityId(newEntXform.Coordinates, mapEnt).Position, Is.EqualTo(new Vector2(2, 2)));
|
||||
|
||||
var newEntTwo = entityManager.CreateEntityUninitialized(null, new EntityCoordinates(newEnt, Vector2.Zero));
|
||||
var newEntTwoXform = entityManager.GetComponent<TransformComponent>(newEntTwo);
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEntTwo).Coordinates.Position, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEntTwo).Coordinates.WithEntityId(mapEnt).Position, Is.EqualTo(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.WithEntityId(mapEnt).Position));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEntTwo).Coordinates.WithEntityId(gridEnt).Position, Is.EqualTo(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEnt).Coordinates.Position));
|
||||
Assert.That(newEntTwoXform.Coordinates.Position, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(xformSys.WithEntityId(newEntTwoXform.Coordinates, mapEnt).Position, Is.EqualTo(xformSys.WithEntityId(newEntXform.Coordinates, mapEnt).Position));
|
||||
Assert.That(xformSys.WithEntityId(newEntTwoXform.Coordinates, gridEnt).Position, Is.EqualTo(newEntXform.Coordinates.Position));
|
||||
|
||||
IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEntTwo).LocalPosition = -Vector2.One;
|
||||
xformSys.SetLocalPosition(newEntTwo, -Vector2.One);
|
||||
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEntTwo).Coordinates.Position, Is.EqualTo(-Vector2.One));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEntTwo).Coordinates.WithEntityId(mapEnt).Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(newEntTwo).Coordinates.WithEntityId(gridEnt).Position, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(newEntTwoXform.Coordinates.Position, Is.EqualTo(-Vector2.One));
|
||||
Assert.That(xformSys.WithEntityId(newEntTwoXform.Coordinates, mapEnt).Position, Is.EqualTo(Vector2.One));
|
||||
Assert.That(xformSys.WithEntityId(newEntTwoXform.Coordinates, gridEnt).Position, Is.EqualTo(Vector2.Zero));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
223
Robust.UnitTesting/Shared/Physics/GridReparentVelocity_Test.cs
Normal file
223
Robust.UnitTesting/Shared/Physics/GridReparentVelocity_Test.cs
Normal file
@@ -0,0 +1,223 @@
|
||||
using System.Numerics;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.UnitTesting.Server;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Physics;
|
||||
|
||||
[TestFixture, TestOf(typeof(SharedPhysicsSystem))]
|
||||
public sealed class GridReparentVelocity_Test
|
||||
{
|
||||
private ISimulation _sim = default!;
|
||||
private IEntitySystemManager _systems = default!;
|
||||
private IEntityManager _entManager = default!;
|
||||
private IMapManager _mapManager = default!;
|
||||
private FixtureSystem _fixtureSystem = default!;
|
||||
private SharedMapSystem _mapSystem = default!;
|
||||
private SharedPhysicsSystem _physSystem = default!;
|
||||
|
||||
// Test objects.
|
||||
private EntityUid _mapUid = default!;
|
||||
private MapId _mapId = default!;
|
||||
private EntityUid _gridUid = default!;
|
||||
private EntityUid _objUid = default!;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public void FixtureSetup()
|
||||
{
|
||||
_sim = RobustServerSimulation.NewSimulation()
|
||||
.InitializeInstance();
|
||||
|
||||
_systems = _sim.Resolve<IEntitySystemManager>();
|
||||
_entManager = _sim.Resolve<IEntityManager>();
|
||||
_mapManager = _sim.Resolve<IMapManager>();
|
||||
_fixtureSystem = _systems.GetEntitySystem<FixtureSystem>();
|
||||
_mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
|
||||
_physSystem = _systems.GetEntitySystem<SharedPhysicsSystem>();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mapUid = _mapSystem.CreateMap(out _mapId);
|
||||
|
||||
// Spawn a 1x1 grid centered at (0.5, 0.5), ensure it's movable and its velocity has no damping.
|
||||
var gridEnt = _mapManager.CreateGridEntity(_mapId);
|
||||
var gridPhys = _entManager.GetComponent<PhysicsComponent>(gridEnt);
|
||||
_physSystem.SetSleepingAllowed(gridEnt, gridPhys, false);
|
||||
_physSystem.SetBodyType(gridEnt, BodyType.Dynamic, body: gridPhys);
|
||||
_physSystem.SetLinearDamping(gridEnt, gridPhys, 0.0f);
|
||||
_physSystem.SetAngularDamping(gridEnt, gridPhys, 0.0f);
|
||||
|
||||
_mapSystem.SetTile(gridEnt, Vector2i.Zero, new Tile(1));
|
||||
_physSystem.WakeBody(gridEnt, body: gridPhys);
|
||||
|
||||
_gridUid = gridEnt.Owner;
|
||||
}
|
||||
|
||||
// Spawn a bullet-like test object at the given position.
|
||||
public EntityUid SetupTestObject(EntityCoordinates coords)
|
||||
{
|
||||
var obj = _entManager.SpawnEntity(null, coords);
|
||||
|
||||
var objPhys = _entManager.EnsureComponent<PhysicsComponent>(obj);
|
||||
var objFix = _entManager.EnsureComponent<FixturesComponent>(obj);
|
||||
|
||||
// Set up physics (no velocity damping, dynamic body, physics enabled)
|
||||
_entManager.GetComponent<PhysicsComponent>(obj);
|
||||
_physSystem.SetSleepingAllowed(obj, objPhys, false);
|
||||
_physSystem.SetBodyType(obj, BodyType.Dynamic, body: objPhys);
|
||||
_physSystem.SetLinearDamping(obj, objPhys, 0.0f);
|
||||
_physSystem.SetAngularDamping(obj, objPhys, 0.0f);
|
||||
|
||||
// Set up fixture.
|
||||
var poly = new PolygonShape();
|
||||
poly.SetAsBox(0.1f, 0.1f);
|
||||
_fixtureSystem.CreateFixture(obj, "fix1", new Fixture(poly, 0, 0, false), manager: objFix, body: objPhys);
|
||||
_physSystem.WakeBody(obj, body: objPhys);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void Teardown()
|
||||
{
|
||||
_entManager.DeleteEntity(_gridUid);
|
||||
_gridUid = default!;
|
||||
_entManager.DeleteEntity(_objUid);
|
||||
_objUid = default!;
|
||||
_mapSystem.DeleteMap(_mapId);
|
||||
_mapId = default!;
|
||||
_entManager.DeleteEntity(_mapUid);
|
||||
}
|
||||
|
||||
// Moves an object off of a moving grid, checks for conservation of linear velocity.
|
||||
[Test]
|
||||
public void TestLinearVelocityOnlyMoveOffGrid()
|
||||
{
|
||||
// Spawn our test object in the middle of the grid, ensure it has no damping.
|
||||
_objUid = SetupTestObject(new EntityCoordinates(_gridUid, 0.5f, 0.5f));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Our object should start on the grid.
|
||||
Assert.That(_entManager.GetComponent<TransformComponent>(_objUid).ParentUid, Is.EqualTo(_gridUid));
|
||||
|
||||
// Set the velocity of the grid and our object.
|
||||
Assert.That(_physSystem.SetLinearVelocity(_objUid, new Vector2(3.5f, 4.75f)), Is.True);
|
||||
Assert.That(_physSystem.SetLinearVelocity(_gridUid, new Vector2(1.0f, 2.0f)), Is.True);
|
||||
|
||||
// Wait a second to clear the grid
|
||||
_physSystem.Update(1.0f);
|
||||
|
||||
// The object should be parented to the map and maintain its map velocity, the grid should be unchanged.
|
||||
var objXform = _entManager.GetComponent<TransformComponent>(_objUid);
|
||||
var gridXform = _entManager.GetComponent<TransformComponent>(_gridUid);
|
||||
Assert.That(objXform.ParentUid, Is.EqualTo(_mapUid), $"Object is not on map - actual position: {objXform.ParentUid} {objXform.LocalPosition}, grid position: {gridXform.ParentUid} {gridXform.LocalPosition}");
|
||||
Assert.That(_entManager.GetComponent<PhysicsComponent>(_objUid).LinearVelocity, Is.EqualTo(new Vector2(4.5f, 6.75f)));
|
||||
Assert.That(_entManager.GetComponent<PhysicsComponent>(_gridUid).LinearVelocity, Is.EqualTo(new Vector2(1.0f, 2.0f)));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
// Moves an object onto a moving grid, checks for conservation of linear velocity.
|
||||
public void TestLinearVelocityOnlyMoveOntoGrid()
|
||||
{
|
||||
// Spawn our test object 1 m off of the middle of the grid in both directions.
|
||||
_objUid = SetupTestObject(new EntityCoordinates(_mapUid, 1.5f, 1.5f));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Assert that we start off the grid.
|
||||
Assert.That(_entManager.GetComponent<TransformComponent>(_objUid).ParentUid, Is.EqualTo(_mapUid));
|
||||
|
||||
// Set the velocity of the grid and our object.
|
||||
Assert.That(_physSystem.SetLinearVelocity(_objUid, new Vector2(-2.0f, -3.0f)), Is.True);
|
||||
Assert.That(_physSystem.SetLinearVelocity(_gridUid, new Vector2(-1.0f, -2.0f)), Is.True);
|
||||
|
||||
// Wait a second to move onto the middle of the grid
|
||||
_physSystem.Update(1.0f);
|
||||
|
||||
// The object should be parented to the grid and maintain its map velocity (slowing down), the grid should be unchanged.
|
||||
var objXform = _entManager.GetComponent<TransformComponent>(_objUid);
|
||||
var gridXform = _entManager.GetComponent<TransformComponent>(_gridUid);
|
||||
Assert.That(objXform.ParentUid, Is.EqualTo(_gridUid), $"Object is not on grid - actual position: {objXform.ParentUid} {objXform.LocalPosition}, grid position: {gridXform.ParentUid} {gridXform.LocalPosition}");
|
||||
Assert.That(_entManager.GetComponent<PhysicsComponent>(_objUid).LinearVelocity, Is.EqualTo(new Vector2(-1.0f, -1.0f)));
|
||||
Assert.That(_entManager.GetComponent<PhysicsComponent>(_gridUid).LinearVelocity, Is.EqualTo(new Vector2(-1.0f, -2.0f)));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
// Moves a rotating object off of a rotating grid, checks for conservation of angular velocity.
|
||||
public void TestLinearAndAngularVelocityMoveOffGrid()
|
||||
{
|
||||
// Spawn our test object in the middle of the grid.
|
||||
_objUid = SetupTestObject(new EntityCoordinates(_gridUid, 0.5f, 0.5f));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Our object should start on the grid.
|
||||
Assert.That(_entManager.GetComponent<TransformComponent>(_objUid).ParentUid, Is.EqualTo(_gridUid));
|
||||
|
||||
// Set the velocity of the grid and our object.
|
||||
Assert.That(_physSystem.SetLinearVelocity(_objUid, new Vector2(3.5f, 4.75f)), Is.True);
|
||||
Assert.That(_physSystem.SetAngularVelocity(_objUid, 1.0f), Is.True);
|
||||
Assert.That(_physSystem.SetLinearVelocity(_gridUid, new Vector2(1.0f, 2.0f)), Is.True);
|
||||
Assert.That(_physSystem.SetAngularVelocity(_gridUid, 2.0f), Is.True);
|
||||
|
||||
// Wait a second to clear the grid
|
||||
_physSystem.Update(1.0f);
|
||||
|
||||
// The object should be parented to the map and maintain its map velocity, the grid should be unchanged.
|
||||
var objXform = _entManager.GetComponent<TransformComponent>(_objUid);
|
||||
var gridXform = _entManager.GetComponent<TransformComponent>(_gridUid);
|
||||
Assert.That(objXform.ParentUid, Is.EqualTo(_mapUid), $"Object is not on map - actual position: {objXform.ParentUid} {objXform.LocalPosition}, grid position: {gridXform.ParentUid} {gridXform.LocalPosition}");
|
||||
// Not checking object's linear velocity in this case, non-zero contribution from grid angular velocity.
|
||||
Assert.That(_entManager.GetComponent<PhysicsComponent>(_objUid).AngularVelocity, Is.EqualTo(3.0f));
|
||||
var gridPhys = _entManager.GetComponent<PhysicsComponent>(_gridUid);
|
||||
Assert.That(gridPhys.LinearVelocity, Is.EqualTo(new Vector2(1.0f, 2.0f)));
|
||||
Assert.That(gridPhys.AngularVelocity, Is.EqualTo(2.0f));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
// Moves a rotating object onto a rotating grid, checks for conservation of angular velocity.
|
||||
public void TestLinearAndAngularVelocityMoveOntoGrid()
|
||||
{
|
||||
// Spawn our test object 1 m off of the middle of the grid in both directions.
|
||||
_objUid = SetupTestObject(new EntityCoordinates(_mapUid, 1.5f, 1.5f));
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
// Assert that we start off the grid.
|
||||
Assert.That(_entManager.GetComponent<TransformComponent>(_objUid).ParentUid, Is.EqualTo(_mapUid));
|
||||
|
||||
// Set the velocity of the grid and our object.
|
||||
Assert.That(_physSystem.SetLinearVelocity(_objUid, new Vector2(-2.0f, -3.0f)), Is.True);
|
||||
Assert.That(_physSystem.SetAngularVelocity(_objUid, 1.0f), Is.True);
|
||||
Assert.That(_physSystem.SetLinearVelocity(_gridUid, new Vector2(-1.0f, -2.0f)), Is.True);
|
||||
Assert.That(_physSystem.SetAngularVelocity(_gridUid, 2.0f), Is.True);
|
||||
|
||||
// Wait a second to move onto the middle of the grid
|
||||
_physSystem.Update(1.0f);
|
||||
|
||||
// The object should be parented to the grid and maintain its map velocity (slowing down), the grid should be unchanged.
|
||||
var objXform = _entManager.GetComponent<TransformComponent>(_objUid);
|
||||
var gridXform = _entManager.GetComponent<TransformComponent>(_gridUid);
|
||||
Assert.That(objXform.ParentUid, Is.EqualTo(_gridUid), $"Object is not on grid - actual position: {objXform.ParentUid} {objXform.LocalPosition}, grid position: {gridXform.ParentUid} {gridXform.LocalPosition}");
|
||||
// Not checking object's linear velocity in this case, non-zero contribution from grid angular velocity.
|
||||
Assert.That(_entManager.GetComponent<PhysicsComponent>(_objUid).AngularVelocity, Is.EqualTo(-1.0f));
|
||||
var gridPhys = _entManager.GetComponent<PhysicsComponent>(_gridUid);
|
||||
Assert.That(gridPhys.LinearVelocity, Is.EqualTo(new Vector2(-1.0f, -2.0f)));
|
||||
Assert.That(gridPhys.AngularVelocity, Is.EqualTo(2.0f));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Collision;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.UnitTesting.Shared.Physics
|
||||
{
|
||||
@@ -76,10 +77,7 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
{
|
||||
var transformB = new Transform(Vector2.One, 0f);
|
||||
var transformA = new Transform(transformB.Position + new Vector2(0.5f, 0.0f), 0f);
|
||||
var manifold = new Manifold()
|
||||
{
|
||||
Points = new ManifoldPoint[2]
|
||||
};
|
||||
var manifold = new Manifold();
|
||||
|
||||
var expectedManifold = new Manifold
|
||||
{
|
||||
@@ -87,17 +85,24 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
LocalNormal = new Vector2(-1, 0),
|
||||
LocalPoint = new Vector2(-0.5f, 0),
|
||||
PointCount = 2,
|
||||
Points = new ManifoldPoint[]
|
||||
{
|
||||
new() {LocalPoint = new Vector2(0.5f, -0.5f), Id = new ContactID {Key = 65795}},
|
||||
new() {LocalPoint = new Vector2(0.5f, 0.5f), Id = new ContactID {Key = 66051}}
|
||||
}
|
||||
Points = new FixedArray2<ManifoldPoint>(
|
||||
new ManifoldPoint
|
||||
{
|
||||
LocalPoint = new Vector2(0.5f, -0.5f),
|
||||
Id = new ContactID {Key = 65795}
|
||||
},
|
||||
new ManifoldPoint
|
||||
{
|
||||
LocalPoint = new Vector2(0.5f, 0.5f),
|
||||
Id = new ContactID {Key = 66051}
|
||||
}
|
||||
)
|
||||
};
|
||||
_manifoldManager.CollidePolygons(ref manifold, _polyA, transformA, _polyB, transformB);
|
||||
|
||||
for (var i = 0; i < manifold.Points.Length; i++)
|
||||
for (var i = 0; i < manifold.PointCount; i++)
|
||||
{
|
||||
Assert.That(manifold.Points[i], Is.EqualTo(expectedManifold.Points[i]));
|
||||
Assert.That(manifold.Points.AsSpan[i], Is.EqualTo(expectedManifold.Points.AsSpan[i]));
|
||||
}
|
||||
|
||||
Assert.That(manifold, Is.EqualTo(expectedManifold));
|
||||
|
||||
Reference in New Issue
Block a user