Compare commits

...

40 Commits

Author SHA1 Message Date
DrSmugleaf
483b95d16d Version: 251.0.0-fixtures-fix 2025-04-22 16:48:48 -07:00
DrSmugleaf
7ac25f708e Fix SharedPhysicsSystem.CollideContacts looping contacts that are in nullspace (#5881)
* Fix SharedJointSystem.CreateDistanceJoint taking in an int for minimumDistance instead of a float

* Fix SharedPhysicsSystem.CollideContacts looping contacts that are in nullspace
2025-04-22 16:47:45 -07:00
metalgearsloth
588c46273e Version: 251.0.0 2025-04-10 20:53:09 +10:00
Whatstone
919de8ce0e SharedPhysicsSystem.Island: set position after velocity (#5801) 2025-04-09 01:10:17 +10:00
Errant
af27d2d872 equatable FormattedMessage, take 2 (#5780)
* improved Equals and getHashCode

* less jank
2025-04-08 16:50:08 +02:00
Tayrtahn
45bb8740a0 Add ForbidLiteralAttribute and analyzer (#5808)
* Add ForbidLiteral attribute, analyzer, and test

* Removed unused code

* Switch order of methods. It's better this way.
2025-04-08 16:39:38 +02:00
Tobias Berger
4a24539629 Don't implement GetMassData twice (#5816)
The removed body didn't calculate mass for circles
2025-04-09 00:14:45 +10:00
PJB3005
7536c4ec68 Log late MsgEntity again
Yay :)
2025-04-07 15:50:40 +02:00
metalgearsloth
9268c8629d Refactor TileEdgeOverlay (#5295)
* Refactor TileEdgeOverlay

* weh

* Updates

* exweh

* Stupid weh noises

* I am le stupid

* Add logging about tile atlas build time

Seems fine, but wanted to check.

* Don't over-allocate edge tile region array

* Add DirectionExtensions.AllDirections

* Clean up iteration in ClydeTileDefinitionManager

* Don't stackalloc large chunk edge buffers, other code cleanup.

* More release notes

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-04-03 07:52:28 +02:00
PJB3005
0bc0cafe64 Show entity name in "physics shapeinfo" output 2025-04-03 02:59:09 +02:00
PJB3005
8891f3fa0a Make EntitySystem.Subscriptions.SubscribeLocalEvent not require EntityEventArgs
This means it can be used with struct events.
2025-04-03 02:58:20 +02:00
Tornado Tech
4f96c2d233 Added separate localization & clean up (#5227)
* Added separate localization & clean up

* Added new methods docs

* Added GetFoundCultures method

* Clean up code

* Removed some formating shit

* Do better CultureInfo comparison

* Oops

* Review

* Command fixes

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-04-02 16:41:02 +02:00
PJB3005
ab55d5b2f2 Revert "Add Loc property to LocalizedCommands"
This reverts commit 806c5b694b.
2025-04-02 05:23:43 +02:00
PJB3005
806c5b694b Add Loc property to LocalizedCommands
Avoids some ~300 usages of static Loc in SS14 and RT
2025-04-02 05:21:43 +02:00
metalgearsloth
6898053dbd Add autocomplete to tp command (#5795) 2025-04-02 03:48:16 +02:00
metalgearsloth
ae625ebad8 Inline manifold points (#5794)
* Inline manifold points

Max is 2 so no reason to use an array over a fixedarray.

* this
2025-04-02 00:04:51 +11:00
metalgearsloth
3c754a4f49 Don't disable contacting collisionwake ents (#5798)
Good for some content stuff I don't think it caused issues.
2025-04-01 15:04:05 +11:00
Tayrtahn
d84cb6327c Fix SharedTransformSystem methods erroring on failed Resolves (#5787)
* Don't error when GetGrid fails

* Fix other Resolves in SharedTransformSystem
2025-04-01 04:59:47 +11:00
Ciarán Walsh
4bfd92dbc5 Add button to jump to live chat when scrolled up (#5750)
* Add button to jump to live chat when scrolled up

* Expose scroll button class name as a public constant

* Add localisation string to engine

* Make enabling the OutputPanel scroll button opt-in

* Enable scroll button for the debug console

* De-duplicate visibility logic

* Update scroll button visibility when the enabling property is changed
2025-03-30 03:07:54 +02:00
metalgearsloth
c7d228c223 savemap / savegrid autocomplete (#5784)
How mappers coping without this.
2025-03-27 18:54:13 +11:00
Tayrtahn
f244c94905 Switch from checking comp.Owner == ent to GetComp(ent) == comp (#5776) 2025-03-27 15:21:05 +11:00
Tayrtahn
01cac6465b Cleanup warnings in EntityCoordinates_Tests (#5771)
* Fix warnings

* Use transform refs to simplify WithEntityId
2025-03-27 15:20:20 +11:00
metalgearsloth
5a5f238d9a Version: 250.0.0 2025-03-27 15:12:01 +11:00
Tayrtahn
089224cd44 Cleanup warnings in EntityLookup_Test (#5775)
* Replace MapManager.DeleteMap calls with MapSystem.DeleteMap

* Remove unused resolves
2025-03-27 15:07:05 +11:00
Tayrtahn
9f807f1ad2 Cleanup warnings in ClientGameStateManager (#5774)
* Fix unreachable code

* Remove unused variable
2025-03-27 15:06:37 +11:00
metalgearsloth
4be95ea375 Add OtherBody API to contacts (#5779)
* Add OtherBody API to contacts

Thought I had a pr for this.

* Update Robust.Shared/Physics/Dynamics/Contacts/Contact.cs

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>

---------

Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com>
2025-03-27 15:06:04 +11:00
Jerry
03010bf4be Fix DirectoryNotFoundException when saving map or grid on Unix systems (#5773)
* fix(maploader): Fix DirectoryNotFoundException

Someone forgor to create directory before saving map or grid, this caused
an exception on trying to save smth anywhere else than /

* update release notes
2025-03-27 13:56:26 +11:00
Tayrtahn
dacaa974d4 Replace MapManager.DeleteMap with SharedMapSystem.DeleteMap in misc tests (#5777)
* Replace MapManager.DeleteMap with SharedMapSystem.DeleteMap in various tests

* Poke tests

* I guess this is was technically a breaking change?
2025-03-27 13:50:56 +11:00
DrSmugleaf
9f73e0398a Make MappingDataNode.Equals take 2952 times less time to run when loading maps (#5781) 2025-03-27 12:52:29 +11:00
Tayrtahn
ccc383b1bf Cleanup: Remove redundant Prototype names (#5721)
* Cleanup: Remove redundant Prototype names

* Actually this one probably should stay

* Suppress warning

* Remove warning suppression on AudioMetadataPrototype

* But wait, there's more!
2025-03-26 01:39:10 +01:00
PJB3005
ceb59402a1 Make status and info APIs have CORS allow-origin: *
Allows it to be queried from browser JS. No harm in not allowing this.

Added helper function StatusExt.AddAllowOriginAny to make this easy to add.
2025-03-26 01:16:23 +01:00
Errant
5a6b29fcd2 equatable FormattedMessage (#5772) 2025-03-25 15:32:47 +01:00
PJB3005
6b87cd1e1c Deprecate HCY color space functions
These functions use the wrong color primaries (Rec 601 instead of 709) to calculate the luminance, so I'm just gonna throw them out.

Also, I don't actually trust anybody to know to do sRGB conversion before using them.

Discovered this as a result of reviewing #5360
2025-03-24 04:47:34 +01:00
Leon Friedrich
cf2d6a1dbf Make unshaded sprite layers not require shader/texture changes. (#5248)
* Make unshaded sprite layers not require shader/texture changes.

* Always sample texture

* revert some variable name changes

* Use color SIMD
2025-03-24 02:37:04 +01:00
chromiumboy
f8c838f425 Pass AnimationPlayerComponent in AnimationCompletionEvent (#5755)
* Added a field for the animation player component to the animation completion event

* Addressed review comment

* Updated
2025-03-24 02:22:46 +01:00
Tayrtahn
7405904041 Add entity description as tooltip on entity spawn panel (#5761) 2025-03-23 14:07:22 +01:00
metalgearsloth
2eeebab275 Add pure to some angle methods (#5763)
Saw some of these floating around in mover code.
2025-03-22 13:43:18 +01:00
PJB3005
2856bb3626 Shut up RS1038 warnings
We aren't going to fix these until #5610 is figured out, and these aren't even an indicator of an issue itself.

They indicate that we have code fixes in the same assembly as analyzers, meaning the analyzers COULD fail if we relied on some code fix libs - something we don't do.
2025-03-22 06:20:43 +01:00
PJB3005
be0189748b Fix serialization source gen with partial types
This fixes RMC compilation
2025-03-22 06:09:48 +01:00
Tayrtahn
4529a7569a Replace uses of ProtoId<EntityPrototype> with ProtoId<EntityCategoryPrototype> (#5762) 2025-03-21 04:08:12 +01:00
88 changed files with 1744 additions and 556 deletions

View File

@@ -1,4 +1,4 @@
<Project>
<!-- This file automatically reset by Tools/version.py -->
<!-- This file automatically reset by Tools/version.py -->

View File

@@ -54,6 +54,74 @@ END TEMPLATE-->
*None yet*
## 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
* The default shader now interprets negative color modulation as a flag that indicates that the light map should be ignored.
* This can be used to avoid having to change the light map texture, thus reducing draw batches.
* Sprite layers that are set to use the "unshaded" shader prototype now use this.
* Any fragment shaders that previously the `VtxModulate` colour modulation variable should instead use the new `MODULATE` variable, as the former may now contain negative values.
### New features
* Add OtherBody API to contacts.
* Make FormattedMessages Equatable.
* AnimationCompletionEvent now has the AnimationPlayerComponent.
* Add entity description as a tooltip on the entity spawn panel.
### Bugfixes
* Fix serialization source generator breaking if a class has two partial locations.
* Fix map saving throwing a `DirectoryNotFoundException` when given a path with a non-existent directory. Now it once again creates any missing directories.
* Fix map loading taking a significant time due to MappingDataNode.Equals calls being slow.
### Other
* Add Pure to some Angle methods.
### Internal
* Cleanup some warnings in classes.
## 249.0.0
### Breaking changes

View File

@@ -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 })

View File

@@ -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

View 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")
);
}
}

View File

@@ -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>

View 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
));
}
}

View 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;
}
}

View File

@@ -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++;

View File

@@ -30,6 +30,7 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
using Direction = Robust.Shared.Maths.Direction;
using Vector4 = Robust.Shared.Maths.Vector4;
using SysVec4 = System.Numerics.Vector4;
namespace Robust.Client.GameObjects
{
@@ -753,12 +754,20 @@ namespace Robust.Client.GameObjects
if (layerDatum.Shader == string.Empty)
{
layer.ShaderPrototype = null;
layer.UnShaded = false;
layer.Shader = null;
}
else if (layerDatum.Shader == SpriteSystem.UnshadedId.Id)
{
layer.ShaderPrototype = SpriteSystem.UnshadedId;
layer.UnShaded = true;
layer.Shader = null;
}
else if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
{
layer.ShaderPrototype = layerDatum.Shader;
layer.Shader = prototype.Instance();
layer.UnShaded = false;
}
else
{
@@ -835,11 +844,28 @@ namespace Robust.Client.GameObjects
if (!TryGetLayer(layer, out var theLayer, true))
return;
if (shader == null)
{
theLayer.UnShaded = false;
theLayer.Shader = null;
theLayer.ShaderPrototype = null;
return;
}
if (prototype == SpriteSystem.UnshadedId.Id)
{
theLayer.UnShaded = true;
theLayer.ShaderPrototype = SpriteSystem.UnshadedId;
theLayer.Shader = null;
return;
}
theLayer.UnShaded = false;
theLayer.Shader = shader;
theLayer.ShaderPrototype = prototype;
}
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
public void LayerSetShader(object layerKey, ShaderInstance? shader, string? prototype = null)
{
if (!LayerMapTryGet(layerKey, out var layer, true))
return;
@@ -1493,10 +1519,18 @@ namespace Robust.Client.GameObjects
{
[ViewVariables] private readonly SpriteComponent _parent;
[ViewVariables] public string? ShaderPrototype;
[ViewVariables] public ProtoId<ShaderPrototype>? ShaderPrototype;
[ViewVariables] public ShaderInstance? Shader;
[ViewVariables] public Texture? Texture;
/// <summary>
/// If true, then this layer is drawn without lighting applied.
/// Unshaded layers are given special treatment and don't just use the unshaded-shader to avoid having to
/// unnecessarily swap out the light texture. This helps the number of batches that need to be sent to the
/// GPU while drawing sprites.
/// </summary>
[ViewVariables] internal bool UnShaded;
private RSI? _rsi;
[ViewVariables] public RSI? RSI
{
@@ -1663,6 +1697,7 @@ namespace Robust.Client.GameObjects
if (toClone.Shader != null)
{
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
UnShaded = toClone.UnShaded;
ShaderPrototype = toClone.ShaderPrototype;
}
Texture = toClone.Texture;
@@ -2078,6 +2113,20 @@ namespace Robust.Client.GameObjects
drawingHandle.UseShader(Shader);
var layerColor = _parent.color * Color;
DebugTools.Assert(layerColor is {R: >= 0, G: >= 0, B: >= 0, A: >= 0}, "Negative colour modulation");
if (UnShaded)
{
DebugTools.AssertNull(Shader);
// Negative modulation values are used to disable light shading in the default shader.
// Specifically we set colour = -1 - colour
// This ensures that non-negative values become negative & is trivially invertible.
// Alternatively we could just clamp the colour to [0,1] and subtract a constant.
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
}
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
var quad = Box2.FromDimensions(textureSize/-2, textureSize);

View File

@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
foreach (var key in remie)
{
component.PlayingAnimations.Remove(key);
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
var completedEvent = new AnimationCompletedEvent(uid, component, key, true);
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
}
@@ -187,7 +187,7 @@ namespace Robust.Client.GameObjects
return;
}
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
var completedEvent = new AnimationCompletedEvent(entity.Owner, entity.Comp, key, false);
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
}
@@ -202,13 +202,33 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class AnimationCompletedEvent : EntityEventArgs
{
/// <summary>
/// The entity associated with the event.
/// </summary>
public EntityUid Uid { get; init; }
/// <summary>
/// The animation player component associated with the entity this event was raised on.
/// </summary>
public AnimationPlayerComponent AnimationPlayer { get; init; }
/// <summary>
/// The key associated with the animation that was completed.
/// </summary>
public string Key { get; init; } = string.Empty;
/// <summary>
/// If true, the animation finished by getting to its natural end.
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.AnimationPlayerComponent,string)"/> or similar overloads.
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(EntityUid,AnimationPlayerComponent,string)"/> or similar overloads.
/// </summary>
public bool Finished { get; init; }
public AnimationCompletedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key, bool finished = true)
{
Uid = uid;
AnimationPlayer = animationPlayer;
Key = key;
Finished = finished;
}
}
}

View File

@@ -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>();
}
}

View File

@@ -37,6 +37,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedTransformSystem _xforms = default!;
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
/// <summary>

View File

@@ -384,7 +384,6 @@ namespace Robust.Client.GameStates
_processor.UpdateFullRep(curState);
}
IEnumerable<NetEntity> createdEntities;
using (_prof.Group("ApplyGameState"))
{
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
@@ -699,8 +698,9 @@ namespace Robust.Client.GameStates
#if !EXCEPTION_TOLERANCE
throw new KeyNotFoundException();
#endif
#else
continue;
#endif
}
var compData = _compDataPool.Get();
@@ -961,8 +961,9 @@ namespace Robust.Client.GameStates
RequestFullState();
#if !EXCEPTION_TOLERANCE
throw;
#endif
#else
return;
#endif
}
if (data.Created)
@@ -980,8 +981,9 @@ namespace Robust.Client.GameStates
RequestFullState();
#if !EXCEPTION_TOLERANCE
throw;
#endif
#else
return;
#endif
}
}

View File

@@ -21,7 +21,7 @@ namespace Robust.Client.Graphics.Clyde
private const string UniModelMatrix = "modelMatrix";
private const string UniTexturePixelSize = "TEXTURE_PIXEL_SIZE";
private const string UniMainTexture = "TEXTURE";
private const string UniLightTexture = "lightMap";
private const string UniLightTexture = "lightMap"; // TODO CLYDE consistent shader variable naming
private const string UniProjViewMatrices = "projectionViewMatrices";
private const string UniUniformConstants = "uniformConstants";

View File

@@ -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;
}
}
}

View File

@@ -1,8 +1,22 @@
// UV coordinates in texture-space. I.e., (0,0) is the corner of the texture currently being used to draw.
// When drawing a sprite from a texture atlas, (0,0) is the corner of the atlas, not the specific sprite being drawn.
varying highp vec2 UV;
// UV coordinates in quad-space. I.e., when drawing a sprite from a texture atlas (0,0) is the corner of the sprite
// currently being drawn.
varying highp vec2 UV2;
// TBH I'm not sure what this is for. I think it is scree UV coordiantes, i.e., FRAGCOORD.xy * SCREEN_PIXEL_SIZE ?
// TODO CLYDE Is this still needed?
varying highp vec2 Pos;
// Vertex colour modulation. Note that negative values imply that the LIGHTMAP should be ignored. This is used to avoid
// having to set the texture to a white/blank texture for sprites that have no light shading applied.
varying highp vec4 VtxModulate;
// The current light map. Unless disabled, this is automatically sampled to create the LIGHT vector, which is then used
// to modulate the output colour.
// TODO CLYDE consistent shader variable naming
uniform sampler2D lightMap;
// [SHADER_HEADER_CODE]
@@ -11,11 +25,37 @@ void main()
{
highp vec4 FRAGCOORD = gl_FragCoord;
// The output colour. This should get set by the shader code block.
// This will get modified by the LIGHT and MODULATE vectors.
lowp vec4 COLOR;
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
// The light colour, usually sampled from the LIGHTMAP
lowp vec4 LIGHT;
// Colour modulation vector.
highp vec4 MODULATE;
// Sample the texture outside of the branch / with uniform control flow.
LIGHT = texture2D(lightMap, Pos);
if (VtxModulate.x < 0.0)
{
// Negative VtxModulate implies unshaded/no lighting.
MODULATE = -1.0 - VtxModulate;
LIGHT = vec4(1.0);
}
else
{
MODULATE = VtxModulate;
}
// TODO CLYDE consistent shader variable naming
// Requires breaking changes.
lowp vec3 lightSample = LIGHT.xyz;
// [SHADER_CODE]
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
LIGHT.xyz = lightSample;
gl_FragColor = zAdjustResult(COLOR * MODULATE * LIGHT);
}

View File

@@ -18,6 +18,7 @@ uniform mat3 modelMatrix;
// Allows us to do texture atlassing with texture coordinates 0->1
// Input texture coordinates get mapped to this range.
uniform vec4 modifyUV;
// TODO CLYDE Is this still needed?
// [SHADER_HEADER_CODE]
@@ -39,5 +40,15 @@ void main()
Pos = (VERTEX + 1.0) / 2.0;
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
UV2 = tCoord2;
VtxModulate = zFromSrgb(modulate);
// Negative modulation is being used as a hacky way to squeeze in lighting data.
// I.e., negative modulation implies we ignore the lighting.
if (modulate.x < 0.0)
{
VtxModulate = -1.0 - zFromSrgb(-1.0 - modulate);
}
else
{
VtxModulate = zFromSrgb(modulate);
}
}

View File

@@ -1,6 +1,7 @@
varying highp vec2 UV;
varying highp vec2 UV2;
// TODO CLYDE consistent shader variable naming
uniform sampler2D lightMap;
// [SHADER_HEADER_CODE]

View File

@@ -16,7 +16,7 @@ using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.Graphics
{
[Prototype("shader")]
[Prototype]
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
{
[ViewVariables]

View File

@@ -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()

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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"

View File

@@ -59,6 +59,7 @@ namespace Robust.Client.UserInterface.CustomControls
}
button.EntityLabel.Text = entityLabelText;
button.ActualButton.ToolTip = prototype.Description;
if (prototype == SelectedPrototype)
{

View File

@@ -4,7 +4,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.RichText;
[Prototype("font")]
[Prototype]
public sealed partial class FontPrototype : IPrototype
{
[IdDataField]

View File

@@ -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.");

View File

@@ -14,7 +14,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ImplicitUsings>enable</ImplicitUsings>
<PolySharpIncludeGeneratedTypes>System.Index;System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;System.Runtime.CompilerServices.IsExternalInit;System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute</PolySharpIncludeGeneratedTypes>
<NoWarn>RS2008</NoWarn>
<NoWarn>RS2008;RS1038</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@@ -35,13 +35,20 @@ public class Generator : IIncrementalGenerator
.Where(static type => type != null);
initContext.RegisterSourceOutput(
dataDefinitions,
static (sourceContext, source) =>
dataDefinitions.Collect(),
static (sourceContext, sources) =>
{
// TODO: deduplicate based on name?
var (name, code) = source!.Value;
var done = new HashSet<string>();
sourceContext.AddSource(name, code);
foreach (var source in sources)
{
var (name, code) = source!.Value;
if (!done.Add(name))
continue;
sourceContext.AddSource(name, code);
}
}
);
}

View File

@@ -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"));

View File

@@ -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);
}

View File

@@ -0,0 +1,15 @@
namespace Robust.Server.ServerStatus;
/// <summary>
/// Helper functions for working with <see cref="IStatusHandlerContext"/>.
/// </summary>
public static class StatusExt
{
/// <summary>
/// Add <c>Access-Control-Allow-Origin: *</c> to the response headers for this request.
/// </summary>
public static void AddAllowOriginAny(this IStatusHandlerContext context)
{
context.ResponseHeaders.Add("Access-Control-Allow-Origin", "*");
}
}

View File

@@ -61,6 +61,7 @@ namespace Robust.Server.ServerStatus
OnStatusRequest?.Invoke(jObject);
context.AddAllowOriginAny();
await context.RespondJsonAsync(jObject);
return true;
@@ -121,6 +122,7 @@ namespace Robust.Server.ServerStatus
OnInfoRequest?.Invoke(jObject);
context.AddAllowOriginAny();
await context.RespondJsonAsync(jObject);
return true;

View File

@@ -93,6 +93,7 @@ namespace Robust.Shared.Maths
private const double CardinalSegment = 2 * Math.PI / 4.0; // Cut the circle into 4 pieces
private const double CardinalOffset = CardinalSegment / 2.0; // offset the pieces by 1/2 their size
[Pure]
public readonly Direction GetCardinalDir()
{
var ang = Theta % (2 * Math.PI);
@@ -167,6 +168,7 @@ namespace Robust.Shared.Maths
/// <summary>
/// Removes revolutions from a positive or negative angle to make it as small as possible.
/// </summary>
[Pure]
public readonly Angle Reduced()
{
return new(Reduce(Theta));
@@ -213,11 +215,13 @@ namespace Robust.Shared.Maths
return !(a == b);
}
[Pure]
public readonly Angle Opposite()
{
return new Angle(FlipPositive(Theta-Math.PI));
}
[Pure]
public readonly Angle FlipPositive()
{
return new(FlipPositive(Theta));

View File

@@ -682,6 +682,7 @@ namespace Robust.Shared.Maths
/// (which is copied to the output's Alpha value).
/// Each has a range of 0.0 to 1.0.
/// </param>
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
public static Color FromHcy(Vector4 hcy)
{
var hue = hcy.X * 360.0f;
@@ -750,6 +751,7 @@ namespace Robust.Shared.Maths
/// </returns>
/// <param name="rgb">Color value to convert.</param>
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
public static Vector4 ToHcy(Color rgb)
{
var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B));

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Numerics;
namespace Robust.Shared.Maths
@@ -36,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)
@@ -238,6 +255,7 @@ namespace Robust.Shared.Maths
/// </summary>
/// <param name="dir"></param>
/// <returns></returns>
[Pure]
public static Angle ToAngle(this Direction dir)
{
var ang = Segment * (int) dir;

View 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;

View File

@@ -8,7 +8,7 @@ namespace Robust.Shared.Audio;
/// Contains audio defaults to set for sounds.
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
/// </summary>
[Prototype("audioPreset")]
[Prototype]
public sealed partial class AudioPresetPrototype : IPrototype
{
[IdDataField]

View File

@@ -6,7 +6,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Shared.Audio;
[Prototype("soundCollection")]
[Prototype]
public sealed partial class SoundCollectionPrototype : IPrototype
{
[ViewVariables]

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -47,6 +47,7 @@ public sealed partial class MapLoaderSystem : EntitySystem
Log.Info($"Saving serialized results to {path}");
path = path.ToRootedPath();
var document = new YamlDocument(data.ToYaml());
_resourceManager.UserData.CreateDir(path.Directory);
using var writer = _resourceManager.UserData.OpenWriteText(path);
{
var stream = new YamlStream {document};

View File

@@ -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)

View File

@@ -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;

View File

@@ -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())

View File

@@ -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>

View File

@@ -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))

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>

View File

@@ -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)
@@ -417,6 +420,28 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
throw new InvalidOperationException();
}
[Pure, PublicAPI]
public PhysicsComponent OurBody(EntityUid uid)
{
if (uid == EntityA)
return BodyA!;
else if (uid == EntityB)
return BodyB!;
throw new InvalidOperationException();
}
[Pure, PublicAPI]
public PhysicsComponent OtherBody(EntityUid uid)
{
if (uid == EntityA)
return BodyB!;
else if (uid == EntityB)
return BodyA!;
throw new InvalidOperationException();
}
}
[Flags]

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -9,7 +9,7 @@ namespace Robust.Shared.Prototypes;
/// Prototype that represents some entity prototype category.
/// Useful for sorting or grouping entity prototypes for mapping/spawning UIs.
/// </summary>
[Prototype("entityCategory")]
[Prototype]
public sealed partial class EntityCategoryPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;

View File

@@ -21,7 +21,7 @@ namespace Robust.Shared.Prototypes
/// <summary>
/// Prototype that represents game entities.
/// </summary>
[Prototype("entity", -1)]
[Prototype(-1)]
public sealed partial class EntityPrototype : IPrototype, IInheritingPrototype, ISerializationHooks
{
private ILocalizationManager _loc = default!;

View File

@@ -6,7 +6,7 @@ namespace Robust.Shared.Prototypes;
/// <summary>
/// Prototype that represents an alias from one tile ID to another. These are used when deserializing entities from yaml.
/// </summary>
[Prototype("tileAlias")]
[Prototype]
public sealed partial class TileAliasPrototype : IPrototype
{
/// <summary>

View File

@@ -384,9 +384,16 @@ namespace Robust.Shared.Serialization.Markdown.Mapping
if (_children.Count != other._children.Count)
return false;
// Given that keys are unique and we do not care about the ordering, we know that if removing identical
// key-value pairs leaves us with an empty list then the mappings are equal.
return Except(other) == null && Tag == other.Tag;
foreach (var (key, otherValue) in other)
{
if (!_children.TryGetValue(key, out var ownValue) ||
!otherValue.Equals(ownValue))
{
return false;
}
}
return Tag == other.Tag;
}
public override MappingDataNode PushInheritance(MappingDataNode node)

View 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;
}
}

View File

@@ -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)

View File

@@ -16,7 +16,7 @@ namespace Robust.Shared.Utility;
/// </summary>
[PublicAPI]
[Serializable, NetSerializable]
public sealed partial class FormattedMessage : IReadOnlyList<MarkupNode>
public sealed partial class FormattedMessage : IEquatable<FormattedMessage>, IReadOnlyList<MarkupNode>
{
public static FormattedMessage Empty => new();
@@ -278,6 +278,33 @@ public sealed partial class FormattedMessage : IReadOnlyList<MarkupNode>
return GetEnumerator();
}
/// <inheritdoc />
public bool Equals(FormattedMessage? other)
{
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()
{
var hash = 0;
foreach (var node in _nodes)
{
hash = HashCode.Combine(hash, node.GetHashCode());
}
return hash;
}
/// <returns>The string without markup tags.</returns>
public override string ToString()
{

View File

@@ -26,8 +26,8 @@ public sealed partial class ComponentMapInitTest
var sim = simFactory.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
sim.Resolve<IEntityManager>().System<SharedMapSystem>().CreateMap(out var mapId);
var mapSystem = entManager.System<SharedMapSystem>();
mapSystem.CreateMap(out var mapId);
var ent = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
Assert.That(entManager.GetComponent<MetaDataComponent>(ent).EntityLifeStage, Is.EqualTo(EntityLifeStage.MapInitialized));
@@ -36,7 +36,7 @@ public sealed partial class ComponentMapInitTest
Assert.That(comp.Count, Is.EqualTo(1));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Reflect(false)]

View File

@@ -102,7 +102,7 @@ namespace Robust.UnitTesting.Shared
var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All);
Assert.That(outcome, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(Box2Cases))]
@@ -127,7 +127,7 @@ namespace Robust.UnitTesting.Shared
var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All);
Assert.That(outcome, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -156,7 +156,7 @@ namespace Robust.UnitTesting.Shared
lookup.GetLocalEntitiesIntersecting(grid.Owner, queryBounds, entities);
Assert.That(entities.Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -185,7 +185,7 @@ namespace Robust.UnitTesting.Shared
lookup.GetLocalEntitiesIntersecting(grid.Owner, queryTile, entities);
Assert.That(entities.Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
#endregion
@@ -200,7 +200,7 @@ namespace Robust.UnitTesting.Shared
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
entManager.System<SharedMapSystem>().CreateMap(spawnPos.MapId);
@@ -210,7 +210,7 @@ namespace Robust.UnitTesting.Shared
entManager.Spawn(null, spawnPos);
Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(IntersectingCases))]
@@ -236,7 +236,7 @@ namespace Robust.UnitTesting.Shared
var bounds = new Box2Rotated(Box2.CenteredAround(queryPos.Position, new Vector2(range, range)));
Assert.That(lookup.GetEntitiesIntersecting(queryPos.MapId, bounds).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(InRangeCases))]
@@ -260,7 +260,7 @@ namespace Robust.UnitTesting.Shared
_ = entManager.SpawnEntity(null, spawnPos);
Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
[Test, TestCaseSource(nameof(InRangeCases))]
@@ -271,7 +271,7 @@ namespace Robust.UnitTesting.Shared
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
entManager.System<SharedMapSystem>().CreateMap(spawnPos.MapId);
@@ -281,7 +281,7 @@ namespace Robust.UnitTesting.Shared
entManager.Spawn(null, spawnPos);
Assert.That(lookup.GetEntitiesInRange(queryPos.MapId, queryPos.Position, range).Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -309,7 +309,7 @@ namespace Robust.UnitTesting.Shared
var outcome = lookup.AnyLocalEntitiesIntersecting(grid.Owner, queryBounds, LookupFlags.All);
Assert.That(outcome, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
/// <summary>
@@ -338,7 +338,7 @@ namespace Robust.UnitTesting.Shared
lookup.GetLocalEntitiesIntersecting(grid.Owner, queryBounds, entities);
Assert.That(entities.Count > 0, Is.EqualTo(result));
mapManager.DeleteMap(spawnPos.MapId);
mapSystem.DeleteMap(spawnPos.MapId);
}
#endregion
@@ -383,7 +383,7 @@ namespace Robust.UnitTesting.Shared
entManager.DeleteEntity(dummy);
entManager.DeleteEntity(grid);
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}
}

View File

@@ -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
{

View File

@@ -39,13 +39,13 @@ namespace Robust.UnitTesting.Shared.GameObjects
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(0));
var mapId = sim.CreateMap().MapId;
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(1));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
Assert.That(entManager.Count<TransformComponent>(), Is.EqualTo(0));
}
}

View File

@@ -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));
}
}
}

View File

@@ -53,7 +53,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(2, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(2));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Test]
@@ -75,7 +75,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(1, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(2));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Test]
@@ -106,7 +106,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(1, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(2));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
[Test]
@@ -130,7 +130,7 @@ public sealed class GridSplit_Tests
mapSystem.SetTile(gridEnt, new Vector2i(1, 0), Tile.Empty);
Assert.That(mapManager.GetAllGrids(mapId).Count(), Is.EqualTo(3));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
/// <summary>
@@ -181,6 +181,6 @@ public sealed class GridSplit_Tests
Assert.That(dummyXform.GridUid, Is.EqualTo(newGrid.Owner));
Assert.That(newGridXform._children, Does.Contain(dummy));
});
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}

View File

@@ -39,6 +39,7 @@ public sealed class MapGridMap_Tests
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = sim.CreateMap().MapId;
mapManager.CreateGridEntity(mapId);
@@ -49,6 +50,6 @@ public sealed class MapGridMap_Tests
entManager.TickUpdate(0.016f, false);
});
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}

View File

@@ -20,10 +20,10 @@ public sealed class Fixtures_Test
var sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var sysManager = sim.Resolve<IEntitySystemManager>();
var fixturesSystem = sysManager.GetEntitySystem<FixtureSystem>();
var physicsSystem = sysManager.GetEntitySystem<SharedPhysicsSystem>();
var mapSystem = sysManager.GetEntitySystem<SharedMapSystem>();
var map = sim.CreateMap().MapId;
var ent = sim.SpawnEntity(null, new MapCoordinates(Vector2.Zero, map));
@@ -36,6 +36,6 @@ public sealed class Fixtures_Test
Assert.That(fixture.Density, Is.EqualTo(10f));
Assert.That(body.Mass, Is.EqualTo(10f));
mapManager.DeleteMap(map);
mapSystem.DeleteMap(map);
}
}

View 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));
});
}
}

View File

@@ -24,8 +24,8 @@ public sealed class Joints_Test
var sim = factory.InitializeInstance();
var entManager = sim.Resolve<IEntityManager>();
var mapManager = sim.Resolve<IMapManager>();
var jointSystem = entManager.System<SharedJointSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = sim.CreateMap().MapId;
@@ -53,7 +53,7 @@ public sealed class Joints_Test
Assert.That(entManager.GetComponent<JointRelayTargetComponent>(uidC).Relayed, Is.Empty);
Assert.That(entManager.GetComponent<JointComponent>(uidA).Relay, Is.EqualTo(null));
});
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
/// <summary>
@@ -65,11 +65,11 @@ public sealed class Joints_Test
var factory = RobustServerSimulation.NewSimulation();
var server = factory.InitializeInstance();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
var fixtureSystem = entManager.EntitySysManager.GetEntitySystem<FixtureSystem>();
var jointSystem = entManager.EntitySysManager.GetEntitySystem<JointSystem>();
var broadphaseSystem = entManager.EntitySysManager.GetEntitySystem<SharedBroadphaseSystem>();
var physicsSystem = server.Resolve<IEntitySystemManager>().GetEntitySystem<SharedPhysicsSystem>();
var mapSystem = entManager.System<SharedMapSystem>();
var mapId = server.CreateMap().MapId;
@@ -106,6 +106,6 @@ public sealed class Joints_Test
broadphaseSystem.FindNewContacts(mapId);
Assert.That(body1.Contacts, Has.Count.EqualTo(1));
mapManager.DeleteMap(mapId);
mapSystem.DeleteMap(mapId);
}
}

View File

@@ -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));

View File

@@ -20,10 +20,13 @@ public abstract partial class EntitySpawnHelpersTest : RobustIntegrationTest
{
protected ServerIntegrationInstance Server = default!;
protected IEntityManager EntMan = default!;
protected IMapManager MapMan = default!;
protected SharedMapSystem MapSys = default!;
protected SharedTransformSystem Xforms = default!;
protected SharedContainerSystem Container = default!;
// Even if unused, content / downstream tests might use this class, so removal would be a breaking change?
protected IMapManager MapMan = default!;
protected EntityUid Map;
protected MapId MapId;
protected EntityUid Parent; // entity parented to the map.
@@ -43,6 +46,7 @@ public abstract partial class EntitySpawnHelpersTest : RobustIntegrationTest
await Server.WaitIdleAsync();
MapMan = Server.ResolveDependency<IMapManager>();
EntMan = Server.ResolveDependency<IEntityManager>();
MapSys = EntMan.System<SharedMapSystem>();
Xforms = EntMan.System<SharedTransformSystem>();
Container = EntMan.System<SharedContainerSystem>();

View File

@@ -116,7 +116,7 @@ public sealed class SpawnInContainerOrDropTest : EntitySpawnHelpersTest
Assert.That(xform.GridUid, Is.Null);
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -121,7 +121,7 @@ public sealed class SpawnNextToOrDropTest : EntitySpawnHelpersTest
Assert.That(EntMan.GetComponent<MetaDataComponent>(uid).EntityLifeStage, Is.LessThan(EntityLifeStage.MapInitialized));
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -42,7 +42,7 @@ public sealed class TrySpawnInContainerTest : EntitySpawnHelpersTest
Assert.That(EntMan.EntityExists(uid), Is.False);
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -50,7 +50,7 @@ public sealed class TrySpawnNextToTest : EntitySpawnHelpersTest
Assert.That(EntMan.EntityExists(uid), Is.False);
});
await Server.WaitPost(() =>MapMan.DeleteMap(MapId));
await Server.WaitPost(() => MapSys.DeleteMap(MapId));
Server.Dispose();
}
}

View File

@@ -197,21 +197,21 @@ public sealed class TestEnumerableCommand : ToolshedCommand
public sealed class TestNestedArrayCommand : ToolshedCommand
{
[CommandImplementation]
public ProtoId<EntityPrototype>[] Impl() => Array.Empty<ProtoId<EntityPrototype>>();
public ProtoId<EntityCategoryPrototype>[] Impl() => [];
}
[ToolshedCommand]
public sealed class TestNestedListCommand : ToolshedCommand
{
[CommandImplementation]
public List<ProtoId<EntityPrototype>> Impl() => new();
public List<ProtoId<EntityCategoryPrototype>> Impl() => new();
}
[ToolshedCommand]
public sealed class TestNestedEnumerableCommand : ToolshedCommand
{
private static ProtoId<EntityPrototype>[] _arr = Array.Empty<ProtoId<EntityPrototype>>();
private static ProtoId<EntityCategoryPrototype>[] _arr = [];
[CommandImplementation]
public IEnumerable<ProtoId<EntityPrototype>> Impl() => _arr.OrderByDescending(x => x.Id);
public IEnumerable<ProtoId<EntityCategoryPrototype>> Impl() => _arr.OrderByDescending(x => x.Id);
}

View File

@@ -383,11 +383,11 @@ public sealed class ToolshedTests : ToolshedTest
AssertResult("testenumerable testenumerableinfer 1", typeof(int));
// Repeat but with nested types. i.e. extracting T when piping ProtoId<T> -> IEnumerable<ProtoId<T>>
AssertResult("testnestedarray testnestedarrayinfer", typeof(EntityPrototype));
AssertResult("testnestedlist testnestedlistinfer", typeof(EntityPrototype));
AssertResult("testnestedarray testnestedenumerableinfer", typeof(EntityPrototype));
AssertResult("testnestedlist testnestedenumerableinfer", typeof(EntityPrototype));
AssertResult("testnestedenumerable testnestedenumerableinfer", typeof(EntityPrototype));
AssertResult("testnestedarray testnestedarrayinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedlist testnestedlistinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedarray testnestedenumerableinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedlist testnestedenumerableinfer", typeof(EntityCategoryPrototype));
AssertResult("testnestedenumerable testnestedenumerableinfer", typeof(EntityCategoryPrototype));
// The map command used to work when the piped type was passed as an IEnumerable<T> directly, but would fail
// when given a List<T> or something else that implemented the interface.