Compare commits

..

2 Commits

Author SHA1 Message Date
DrSmugleaf
3ae65f9e3d Version: 248.0.2-source-gen-debug 2025-03-20 21:13:47 -07:00
DrSmugleaf
af6fc51def Source gen changes 2025-03-20 18:06:10 -07:00
130 changed files with 1149 additions and 2777 deletions

View File

@@ -57,7 +57,7 @@
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />
<PackageVersion Include="SpaceWizards.Sodium" Version="0.2.1" />
<PackageVersion Include="TerraFX.Interop.Windows" Version="10.0.26100.1" />

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,103 +54,7 @@ END TEMPLATE-->
*None yet*
## 252.0.1
## 252.0.0
### Breaking changes
* BoundUserInterfaceMessageAttempt is raised directed against entities and no longer broadcast.
## 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
* Layer is now read-only on VisibilityComponent and isn't serialized.
### New features
* Added a debug overlay for the linear and angular velocity of all entities on the screen. Use the `showvel` and `showangvel` commands to toggle it.
* Add a GetWorldManifold overload that doesn't require a span of points.
* Added a GetVisMaskEvent. Calling `RefreshVisibilityMask` will raise it and subscribers can update the vismask via the event rather than subscribers having to each manually try and handle the vismask directly.
### Bugfixes
* `BoxContainer` no longer causes stretching children to go below their minimum size.
* Fix lights on other grids getting clipped due to ignoring the light range cvar.
* Fix the `showvelocities` command.
* Fix the DirtyFields overload not being sandbox safe for content.
### Internal
* Polygon vertices are now inlined with FixedArray8 and a separate SlimPolygon using FixedArray4 for hot paths rather than using pooled arrays.
## 248.0.2-source-gen-debug
## 248.0.2

View File

@@ -11,7 +11,6 @@ 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}
@@ -429,20 +428,11 @@ cmd-entfo-help = Usage: entfo <entityuid>
The entity UID can be prefixed with 'c' to convert it to a client entity UID.
cmd-fuck-desc = Throws an exception
cmd-fuck-help = Usage: fuck
cmd-fuck-help = Throws an exception
cmd-showpos-desc = Show the position of all entities on the screen.
cmd-showpos-desc = Enables debug drawing over all entity positions in the game.
cmd-showpos-help = Usage: showpos
cmd-showrot-desc = Show the rotation of all entities on the screen.
cmd-showrot-help = Usage: showrot
cmd-showvel-desc = Show the local velocity of all entites on the screen.
cmd-showvel-help = Usage: showvel
cmd-showangvel-desc = Show the angular velocity of all entities on the screen.
cmd-showangvel-help = Usage: showangvel
cmd-sggcell-desc = Lists entities on a snap grid cell.
cmd-sggcell-help = Usage: sggcell <gridID> <vector2i>\nThat vector2i param is in the form x<int>,y<int>.
@@ -573,8 +563,3 @@ 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,10 +14,6 @@ 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

@@ -1,189 +0,0 @@
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,7 +13,6 @@
<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

@@ -1,101 +0,0 @@
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

@@ -6,7 +6,7 @@ using Xilium.CefGlue;
namespace Robust.Client.WebView.Cef
{
internal static class Program
public static class Program
{
// This was supposed to be the main entry for the subprocess program... It doesn't work.
public static int Main(string[] args)

View File

@@ -5,7 +5,6 @@ using System.Net;
using System.Reflection;
using System.Text;
using Robust.Client.Console;
using Robust.Client.Utility;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
@@ -25,7 +24,6 @@ namespace Robust.Client.WebView.Cef
[Dependency] private readonly IDependencyCollection _dependencyCollection = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameControllerInternal _gameController = default!;
[Dependency] private readonly IResourceManagerInternal _resourceManager = default!;
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -63,10 +61,7 @@ namespace Robust.Client.WebView.Cef
var cachePath = "";
if (_resourceManager.UserData is WritableDirProvider userData)
{
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
cachePath = Path.Combine(rootDir, "cef_cache", "0");
}
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
var settings = new CefSettings()
{

View File

@@ -173,51 +173,29 @@ namespace Robust.Client.Console.Commands
}
}
internal sealed class ShowPositionsCommand : LocalizedEntityCommands
internal sealed class ShowPositionsCommand : LocalizedCommands
{
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
public override string Command => "showpos";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_debugDrawing.DebugPositions = !_debugDrawing.DebugPositions;
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
mgr.DebugPositions = !mgr.DebugPositions;
}
}
internal sealed class ShowRotationsCommand : LocalizedEntityCommands
internal sealed class ShowRotationsCommand : LocalizedCommands
{
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
public override string Command => "showrot";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_debugDrawing.DebugRotations = !_debugDrawing.DebugRotations;
}
}
internal sealed class ShowVelocitiesCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
public override string Command => "showvel";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_debugDrawing.DebugVelocities = !_debugDrawing.DebugVelocities;
}
}
internal sealed class ShowAngularVelocitiesCommand : LocalizedEntityCommands
{
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
public override string Command => "showangvel";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_debugDrawing.DebugAngularVelocities = !_debugDrawing.DebugAngularVelocities;
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
mgr.DebugRotations = !mgr.DebugRotations;
}
}

View File

@@ -1,67 +0,0 @@
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

@@ -5,15 +5,15 @@ using Robust.Shared.IoC;
namespace Robust.Client.Console.Commands
{
public sealed class ShowPlayerVelocityCommand : LocalizedCommands
public sealed class VelocitiesCommand : LocalizedCommands
{
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
public override string Command => "showplayervelocity";
public override string Command => "showvelocities";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
_entitySystems.GetEntitySystem<ShowPlayerVelocityDebugSystem>().Enabled ^= true;
_entitySystems.GetEntitySystem<VelocityDebugSystem>().Enabled ^= true;
}
}
}

View File

@@ -1,221 +1,140 @@
using System.Numerics;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
using System.Numerics;
namespace Robust.Client.Debugging;
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public sealed class DebugDrawingSystem : EntitySystem
namespace Robust.Client.Debugging
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private bool _debugPositions;
private bool _debugRotations;
private bool _debugVelocities;
private bool _debugAngularVelocities;
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// A collection of visual debug overlays for the client game.
/// </summary>
public bool DebugPositions
public sealed class DebugDrawingSystem : EntitySystem
{
get => _debugPositions;
set
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly TransformSystem _transform = default!;
private bool _debugPositions;
private bool _debugRotations;
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
public bool DebugPositions
{
if (value == DebugPositions)
get => _debugPositions;
set
{
return;
}
if (value == DebugPositions)
{
return;
}
_debugPositions = value;
_debugPositions = value;
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
}
/// <summary>
/// Toggles the visual overlay of the rotation for each entity on screen.
/// </summary>
public bool DebugRotations
{
get => _debugRotations;
set
/// <summary>
/// Toggles the visual overlay of the local rotation.
/// </summary>
public bool DebugRotations
{
if (value == DebugRotations)
get => _debugRotations;
set
{
return;
}
if (value == DebugRotations)
{
return;
}
_debugRotations = value;
_debugRotations = value;
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
{
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
{
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, EntityManager));
}
else
{
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
}
}
}
}
/// <summary>
/// Toggles the visual overlay of the local velocity for each entity on screen.
/// </summary>
public bool DebugVelocities
{
get => _debugVelocities;
set
private sealed class EntityPositionOverlay : Overlay
{
if (value == DebugVelocities)
private readonly EntityLookupSystem _lookup;
private readonly IEntityManager _entityManager;
private readonly SharedTransformSystem _transform;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(EntityLookupSystem lookup, IEntityManager entityManager, SharedTransformSystem transform)
{
return;
_lookup = lookup;
_entityManager = entityManager;
_transform = transform;
}
_debugVelocities = value;
protected internal override void Draw(in OverlayDrawArgs args)
{
const float stubLength = 0.25f;
if (value && !_overlayManager.HasOverlay<EntityVelocityOverlay>())
{
_overlayManager.AddOverlay(new EntityVelocityOverlay(EntityManager, _lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityVelocityOverlay>();
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = _transform.GetWorldPositionRotation(entity);
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
}
}
}
}
/// <summary>
/// Toggles the visual overlay of the angular velocity for each entity on screen.
/// </summary>
public bool DebugAngularVelocities
{
get => _debugAngularVelocities;
set
private sealed class EntityRotationOverlay : Overlay
{
if (value == DebugAngularVelocities)
private readonly EntityLookupSystem _lookup;
private readonly IEntityManager _entityManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityRotationOverlay(EntityLookupSystem lookup, IEntityManager entityManager)
{
return;
_lookup = lookup;
_entityManager = entityManager;
}
_debugAngularVelocities = value;
if (value && !_overlayManager.HasOverlay<EntityAngularVelocityOverlay>())
protected internal override void Draw(in OverlayDrawArgs args)
{
_overlayManager.AddOverlay(new EntityAngularVelocityOverlay(EntityManager, _lookup, _transform));
}
else
{
_overlayManager.RemoveOverlay<EntityAngularVelocityOverlay>();
}
}
}
private sealed class EntityPositionOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
protected internal override void Draw(in OverlayDrawArgs args)
{
const float stubLength = 0.25f;
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
worldHandle.DrawLine(center, center + xLine * stubLength, Color.Red);
worldHandle.DrawLine(center, center + yLine * stubLength, Color.Green);
}
}
}
private sealed class EntityRotationOverlay(EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
protected internal override void Draw(in OverlayDrawArgs args)
{
const float stubLength = 0.25f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
var (center, worldRotation) = _transform.GetWorldPositionRotation(uid);
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
}
}
}
private sealed class EntityVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
protected internal override void Draw(in OverlayDrawArgs args)
{
const float multiplier = 0.2f;
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
continue;
var center = _transform.GetWorldPosition(uid);
var localVelocity = physicsComp.LinearVelocity;
if (localVelocity != Vector2.Zero)
worldHandle.DrawLine(center, center + localVelocity * multiplier, Color.Yellow);
}
}
}
private sealed class EntityAngularVelocityOverlay(IEntityManager _entityManager, EntityLookupSystem _lookup, SharedTransformSystem _transform) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
protected internal override void Draw(in OverlayDrawArgs args)
{
const float multiplier = (float)(0.2 / (2 * System.Math.PI));
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var physicsQuery = _entityManager.GetEntityQuery<PhysicsComponent>();
foreach (var uid in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
{
if(!physicsQuery.TryGetComponent(uid, out var physicsComp))
continue;
var center = _transform.GetWorldPosition(uid);
var angularVelocity = physicsComp.AngularVelocity;
if (angularVelocity != 0.0f)
worldHandle.DrawCircle(center, angularVelocity * multiplier, angularVelocity > 0 ? Color.Magenta : Color.Blue, false);
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
}
}
}
}
}

View File

@@ -413,9 +413,8 @@ 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} ({meta.EntityName})");
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner}");
row++;
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
row++;

View File

@@ -384,7 +384,7 @@ namespace Robust.Client
_prof.Initialize();
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

@@ -117,7 +117,7 @@ namespace Robust.Client.GameObjects
base.DirtyField(uid, comp, fieldName, metadata);
}
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
{
// TODO Prediction
// does the client actually need to dirty the field?

View File

@@ -30,7 +30,6 @@ 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
{
@@ -754,20 +753,12 @@ 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
{
@@ -844,28 +835,11 @@ 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;
@@ -1519,18 +1493,10 @@ namespace Robust.Client.GameObjects
{
[ViewVariables] private readonly SpriteComponent _parent;
[ViewVariables] public ProtoId<ShaderPrototype>? ShaderPrototype;
[ViewVariables] public string? 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
{
@@ -1697,7 +1663,6 @@ 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;
@@ -2113,20 +2078,6 @@ 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, component, key, true);
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
}
@@ -187,7 +187,7 @@ namespace Robust.Client.GameObjects
return;
}
var completedEvent = new AnimationCompletedEvent(entity.Owner, entity.Comp, key, false);
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
}
@@ -202,33 +202,13 @@ 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(EntityUid,AnimationPlayerComponent,string)"/> or similar overloads.
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.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,6 +9,10 @@ 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.
@@ -19,4 +23,16 @@ 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

@@ -1,66 +0,0 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics.Components;
namespace Robust.Client.GameObjects;
public sealed class ShowPlayerVelocityDebugSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly TransformSystem _transform = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
internal bool Enabled
{
get => _label.Parent != null;
set
{
if (value)
{
_uiManager.WindowRoot.AddChild(_label);
}
else
{
_label.Orphan();
}
}
}
private Label _label = default!;
public override void Initialize()
{
base.Initialize();
_label = new Label();
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled)
{
_label.Visible = false;
return;
}
var player = _playerManager.LocalEntity;
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
{
_label.Visible = false;
return;
}
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
_label.Visible = true;
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
}
}

View File

@@ -37,7 +37,6 @@ 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

@@ -0,0 +1,54 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Components;
namespace Robust.Client.GameObjects
{
public sealed class VelocityDebugSystem : EntitySystem
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly TransformSystem _transform = default!;
internal bool Enabled { get; set; }
private Label _label = default!;
public override void Initialize()
{
base.Initialize();
_label = new Label();
IoCManager.Resolve<IUserInterfaceManager>().StateRoot.AddChild(_label);
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
if (!Enabled)
{
_label.Visible = false;
return;
}
var player = _playerManager.LocalEntity;
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
{
_label.Visible = false;
return;
}
var screenPos = _eyeManager.WorldToScreen(_transform.GetWorldPosition(Transform(player.Value)));
LayoutContainer.SetPosition(_label, screenPos + new Vector2(0, 50));
_label.Visible = true;
_label.Text = $"Speed: {body.LinearVelocity.Length():0.00}\nLinear: {body.LinearVelocity.X:0.00}, {body.LinearVelocity.Y:0.00}\nAngular: {body.AngularVelocity}";
}
}
}

View File

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

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"; // TODO CLYDE consistent shader variable naming
private const string UniLightTexture = "lightMap";
private const string UniProjViewMatrices = "projectionViewMatrices";
private const string UniUniformConstants = "uniformConstants";

View File

@@ -2,7 +2,6 @@ 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;
@@ -23,9 +22,6 @@ 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();
@@ -67,78 +63,29 @@ namespace Robust.Client.Graphics.Clyde
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
}
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(mapGrid));
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(transform));
var enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
// Handle base texture updates.
while (enumerator.MoveNext(out var chunk))
{
DebugTools.Assert(chunk.FilledTiles > 0);
var datum = EnsureChunkInitialized(data, chunk, mapGrid);
if (!data.TryGetValue(chunk.Indices, out MapChunkData? datum))
data[chunk.Indices] = datum = _initChunkBuffers(mapGrid, chunk);
if (!datum.Dirty)
continue;
if (datum.Dirty)
_updateChunkMesh(mapGrid, chunk, datum);
_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();
if (datum.TileCount == 0)
continue;
_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();
}
BindVertexArray(datum.VAO);
CheckGlError();
_debugStats.LastGLDrawCalls += 1;
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
CheckGlError();
}
requiresFlush = false;
@@ -170,17 +117,6 @@ 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)
@@ -202,141 +138,66 @@ namespace Robust.Client.Graphics.Clyde
private void _updateChunkMesh(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
{
Span<ushort> indexBuffer = EnsureSize(ref _chunkMeshBuilderIndexBuffer, _indicesPerChunk(chunk));
Span<Vertex2D> vertexBuffer = EnsureSize(ref _chunkMeshBuilderVertexBuffer, _verticesPerChunk(chunk));
Span<ushort> indexBuffer = stackalloc ushort[_indicesPerChunk(chunk)];
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
var i = 0;
var chunkSize = grid.Comp.ChunkSize;
var chunkOriginScaled = chunk.Indices * chunkSize;
for (ushort x = 0; x < chunkSize; x++)
var cSz = grid.Comp.ChunkSize;
var cScaled = chunk.Indices * cSz;
for (ushort x = 0; x < cSz; x++)
{
for (ushort y = 0; y < chunkSize; y++)
for (ushort y = 0; y < cSz; y++)
{
var gridX = x + chunkOriginScaled.X;
var gridY = y + chunkOriginScaled.Y;
var tile = chunk.GetTile(x, y);
if (tile.IsEmpty)
continue;
// Tile render
if (x != chunkSize && y != chunkSize)
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
Box2 region;
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
{
// ReSharper disable once IntVariableOverflowInUncheckedContext
if (tile.IsEmpty)
continue;
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
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;
region = _tileDefinitionManager.ErrorTileRegion;
}
else
{
region = regionMaybe[tile.Variant];
}
var gx = x + cScaled.X;
var gy = y + cScaled.Y;
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;
}
}
var indexSlice = indexBuffer[..(i * GetQuadBatchIndexCount())];
var vertSlice = vertexBuffer[..(i * 4)];
GL.BindVertexArray(datum.VAO);
CheckGlError();
datum.EBO.Use();
datum.VBO.Use();
datum.EBO.Reallocate(indexSlice);
datum.VBO.Reallocate(vertSlice);
datum.TileCount = i;
datum.EBO.Reallocate(indexBuffer[..(i * GetQuadBatchIndexCount())]);
datum.VBO.Reallocate(vertexBuffer[..(i * 4)]);
datum.Dirty = false;
datum.TileCount = i;
}
private void _updateChunkEdges(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
private unsafe MapChunkData _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk)
{
// 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,
@@ -351,30 +212,12 @@ namespace Robust.Client.Graphics.Clyde
vbo.Use();
ebo.Use();
datum.EBO = ebo;
datum.VBO = vbo;
datum.VAO = vao;
var datum = new MapChunkData(vao, vbo, ebo)
{
Dirty = true
};
// 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;
return datum;
}
private void DeleteChunk(MapChunkData data)
@@ -411,49 +254,19 @@ 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 EdgeDirty = true;
public bool Dirty = true;
public uint VAO;
public GLBuffer VBO = default!;
public GLBuffer EBO = default!;
public bool Dirty;
public readonly uint VAO;
public readonly GLBuffer VBO;
public readonly GLBuffer EBO;
public int TileCount;
public uint EdgeVAO;
public GLBuffer EdgeVBO = default!;
public GLBuffer EdgeEBO = default!;
public int EdgeCount;
public MapChunkData()
public MapChunkData(uint vao, GLBuffer vbo, GLBuffer ebo)
{
VAO = vao;
VBO = vbo;
EBO = ebo;
}
}
}

View File

@@ -98,11 +98,9 @@ namespace Robust.Client.Graphics.Clyde
private LightCapacityComparer _lightCap = new();
private ShadowCapacityComparer _shadowCap = new ShadowCapacityComparer();
private float _maxLightRadius;
private unsafe void InitLighting()
{
_cfg.OnValueChanged(CVars.MaxLightRadius, val => { _maxLightRadius = val;}, true);
// Other...
LoadLightingShaders();
@@ -619,9 +617,8 @@ namespace Robust.Client.Graphics.Clyde
// Use worldbounds for this one as we only care if the light intersects our actual bounds
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
var state = (this, count: 0, shadowCastingCount: 0, xforms, worldAABB);
var lightAabb = worldAABB.Enlarged(_maxLightRadius);
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, lightAabb))
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, worldAABB))
{
var bounds = _transformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
comp.Tree.QueryAabb(ref state, LightQuery, bounds);

View File

@@ -1,22 +1,8 @@
// 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]
@@ -25,37 +11,11 @@ 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;
// 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;
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
// [SHADER_CODE]
LIGHT.xyz = lightSample;
gl_FragColor = zAdjustResult(COLOR * MODULATE * LIGHT);
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
}

View File

@@ -18,7 +18,6 @@ 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]
@@ -40,15 +39,5 @@ void main()
Pos = (VERTEX + 1.0) / 2.0;
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
UV2 = tCoord2;
// 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);
}
VtxModulate = zFromSrgb(modulate);
}

View File

@@ -1,7 +1,6 @@
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]
[Prototype("shader")]
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
{
[ViewVariables]

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
using Robust.Client.Graphics;
@@ -11,11 +10,9 @@ 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
{
@@ -32,7 +29,7 @@ namespace Robust.Client.Map
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
private FrozenDictionary<(int Id, Direction Direction), Box2[]> _tileRegions = FrozenDictionary<(int Id, Direction Direction), Box2[]>.Empty;
private readonly Dictionary<int, Box2[]> _tileRegions = new();
public Box2 ErrorTileRegion { get; private set; }
@@ -45,14 +42,7 @@ namespace Robust.Client.Map
/// <inheritdoc />
public Box2[]? TileAtlasRegion(int tileType)
{
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))
if (_tileRegions.TryGetValue(tileType, out var region))
{
return region;
}
@@ -93,8 +83,7 @@ namespace Robust.Client.Map
internal void _genTextureAtlas()
{
var sw = RStopwatch.StartNew();
var tileRegs = new Dictionary<(int Id, Direction Direction), Box2[]>();
_tileRegions.Clear();
_tileTextureAtlas = null;
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
@@ -105,7 +94,7 @@ namespace Robust.Client.Map
const int tileSize = EyeManager.PixelsPerMeter;
var tileCount = defList.Select(x => x.Variants + x.EdgeSprites.Count).Sum() + 1;
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
@@ -113,11 +102,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);
@@ -165,98 +154,25 @@ 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);
BumpColumn(ref row, ref column, dimensionX);
column++;
if (column >= dimensionX)
{
column = 0;
row++;
}
}
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.Add(def.TileId, regionList);
}
_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,12 +29,5 @@ 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

@@ -0,0 +1,125 @@
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

@@ -71,11 +71,21 @@ namespace Robust.Client.UserInterface.Controls
}
// First, we measure non-stretching children.
var stretching = new List<Control>();
float totalStretchRatio = 0;
foreach (var child in Children)
{
if (!child.Visible)
continue;
var stretch = Vertical ? child.VerticalExpand : child.HorizontalExpand;
if (stretch)
{
totalStretchRatio += child.SizeFlagsStretchRatio;
stretching.Add(child);
continue;
}
child.Measure(availableSize);
if (Vertical)
@@ -92,6 +102,35 @@ namespace Robust.Client.UserInterface.Controls
}
}
if (stretching.Count == 0)
return desiredSize;
// Then we measure stretching children
foreach (var child in stretching)
{
var size = availableSize;
if (Vertical)
{
size.Y *= child.SizeFlagsStretchRatio / totalStretchRatio;
child.Measure(size);
desiredSize.Y += child.DesiredSize.Y;
desiredSize.X = Math.Max(desiredSize.X, child.DesiredSize.X);
}
else
{
size.X *= child.SizeFlagsStretchRatio / totalStretchRatio;
child.Measure(size);
desiredSize.X += child.DesiredSize.X;
desiredSize.Y = Math.Max(desiredSize.Y, child.DesiredSize.Y);
}
// TODO Maybe make BoxContainer.MeasureOverride more rigorous.
// This should check if size < desired size. If it is, treat child as non-stretching (see the code in
// ArrangeOverride). This requires remeasuring all stretching controls + the control that just became
// non-stretching. But the re-measured controls might then become smaller (e.g. rich text wrapping),
// leading to a recursion problem.
}
return desiredSize;
}

View File

@@ -4,7 +4,6 @@ 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;
@@ -16,23 +15,10 @@ 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;
@@ -40,7 +26,6 @@ namespace Robust.Client.UserInterface.Controls
private bool _firstLine = true;
private StyleBox? _styleBoxOverride;
private VScrollBar _scrollBar;
private Button _scrollDownButton;
public bool ScrollFollowing { get; set; } = true;
@@ -58,25 +43,7 @@ namespace Robust.Client.UserInterface.Controls
HorizontalAlignment = HAlignment.Right
};
AddChild(_scrollBar);
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();
};
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
}
public int EntryCount => _entries.Count;
@@ -217,7 +184,6 @@ namespace Robust.Client.UserInterface.Controls
var styleBoxSize = _getStyleBox()?.MinimumSize.Y ?? 0;
_scrollBar.Page = UIScale * (Height - styleBoxSize);
_updateScrollButtonVisibility();
_invalidateEntries();
}
@@ -318,10 +284,5 @@ 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" ShowScrollDownButton="True">
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace">
<OutputPanel.StyleBoxOverride>
<gfx:StyleBoxFlat BackgroundColor="#25252add"
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"

View File

@@ -59,7 +59,6 @@ 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]
[Prototype("font")]
public sealed partial class FontPrototype : IPrototype
{
[IdDataField]

View File

@@ -36,7 +36,6 @@ 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;RS1038</NoWarn>
<NoWarn>RS2008</NoWarn>
</PropertyGroup>
<ItemGroup>

View File

@@ -19,119 +19,117 @@ public class Generator : IIncrementalGenerator
public void Initialize(IncrementalGeneratorInitializationContext initContext)
{
IncrementalValuesProvider<(string name, string code)?> dataDefinitions = initContext.SyntaxProvider
.CreateSyntaxProvider(
static (node, _) => node is TypeDeclarationSyntax,
static (context, _) =>
{
var type = (TypeDeclarationSyntax)context.Node;
var symbol = (ITypeSymbol)context.SemanticModel.GetDeclaredSymbol(type)!;
if (!IsDataDefinition(symbol))
return null;
return GenerateForDataDefinition(type, symbol);
}
)
.Where(static type => type != null);
initContext.RegisterSourceOutput(
dataDefinitions.Collect(),
static (sourceContext, sources) =>
IncrementalValuesProvider<TypeDeclarationSyntax> dataDefinitions = initContext.SyntaxProvider.CreateSyntaxProvider(
static (node, _) => node is TypeDeclarationSyntax,
static (context, _) =>
{
var done = new HashSet<string>();
var type = (TypeDeclarationSyntax) context.Node;
var symbol = (ITypeSymbol) context.SemanticModel.GetDeclaredSymbol(type)!;
return IsDataDefinition(symbol) ? type : null;
}
).Where(static type => type != null)!;
foreach (var source in sources)
var comparer = new DataDefinitionComparer();
initContext.RegisterSourceOutput(
initContext.CompilationProvider.Combine(dataDefinitions.WithComparer(comparer).Collect()),
static (sourceContext, source) =>
{
var (compilation, declarations) = source;
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
var declarationsGenerated = new HashSet<string>();
var deltaType = compilation.GetTypeByMetadataName(ComponentDeltaInterfaceName)!;
foreach (var declaration in declarations)
{
var (name, code) = source!.Value;
builder.Clear();
containingTypes.Clear();
if (!done.Add(name))
var type = compilation.GetSemanticModel(declaration.SyntaxTree).GetDeclaredSymbol(declaration)!;
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
if (!declarationsGenerated.Add(symbolName))
continue;
sourceContext.AddSource(name, code);
var nonPartial = !IsPartial(declaration);
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
? string.Empty
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
var containingType = type.ContainingType;
while (containingType != null)
{
containingTypes.Push(containingType);
containingType = containingType.ContainingType;
}
var containingTypesStart = new StringBuilder();
var containingTypesEnd = new StringBuilder();
foreach (var parent in containingTypes)
{
var syntax = (ClassDeclarationSyntax) parent.DeclaringSyntaxReferences[0].GetSyntax();
if (!IsPartial(syntax))
{
nonPartial = true;
continue;
}
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
containingTypesEnd.AppendLine("}");
}
var definition = GetDataDefinition(type);
if (nonPartial || definition.InvalidFields)
continue;
builder.AppendLine($$"""
#nullable enable
using System;
using Robust.Shared.Analyzers;
using Robust.Shared.IoC;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Exceptions;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
#pragma warning disable CS0618 // Type or member is obsolete
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
#pragma warning disable RA0002 // Robust access analyzer
{{namespaceString}}
{{containingTypesStart}}
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
{
{{GetConstructor(definition)}}
{{GetCopyMethods(definition, deltaType)}}
{{GetInstantiators(definition, deltaType)}}
}
{{containingTypesEnd}}
""");
var sourceText = CSharpSyntaxTree
.ParseText(builder.ToString())
.GetRoot()
.NormalizeWhitespace()
.ToFullString();
sourceContext.AddSource($"{symbolName}.g.cs", sourceText);
}
}
);
}
private static (string, string)? GenerateForDataDefinition(
TypeDeclarationSyntax declaration,
ITypeSymbol type)
{
var builder = new StringBuilder();
var containingTypes = new Stack<INamedTypeSymbol>();
containingTypes.Clear();
var symbolName = type
.ToDisplayString()
.Replace('<', '{')
.Replace('>', '}');
var nonPartial = !IsPartial(declaration);
var namespaceString = type.ContainingNamespace.IsGlobalNamespace
? string.Empty
: $"namespace {type.ContainingNamespace.ToDisplayString()};";
var containingType = type.ContainingType;
while (containingType != null)
{
containingTypes.Push(containingType);
containingType = containingType.ContainingType;
}
var containingTypesStart = new StringBuilder();
var containingTypesEnd = new StringBuilder();
foreach (var parent in containingTypes)
{
var syntax = (ClassDeclarationSyntax)parent.DeclaringSyntaxReferences[0].GetSyntax();
if (!IsPartial(syntax))
{
nonPartial = true;
continue;
}
containingTypesStart.AppendLine($"{GetPartialTypeDefinitionLine(parent)}\n{{");
containingTypesEnd.AppendLine("}");
}
var definition = GetDataDefinition(type);
if (nonPartial || definition.InvalidFields)
return null;
builder.AppendLine($$"""
#nullable enable
using System;
using Robust.Shared.Analyzers;
using Robust.Shared.IoC;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Exceptions;
using Robust.Shared.Serialization.TypeSerializers.Interfaces;
#pragma warning disable CS0618 // Type or member is obsolete
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0108 // Member hides inherited member; missing new keyword
#pragma warning disable RA0002 // Robust access analyzer
{{namespaceString}}
{{containingTypesStart}}
{{GetPartialTypeDefinitionLine(type)}} : ISerializationGenerated<{{definition.GenericTypeName}}>
{
{{GetConstructor(definition)}}
{{GetCopyMethods(definition)}}
{{GetInstantiators(definition)}}
}
{{containingTypesEnd}}
""");
return ($"{symbolName}.g.cs", builder.ToString());
}
private static DataDefinition GetDataDefinition(ITypeSymbol definition)
{
var fields = new List<DataField>();
@@ -198,7 +196,7 @@ public class Generator : IIncrementalGenerator
return builder.ToString();
}
private static string GetCopyMethods(DataDefinition definition)
private static string GetCopyMethods(DataDefinition definition, ITypeSymbol deltaType)
{
var builder = new StringBuilder();
@@ -269,36 +267,36 @@ public class Generator : IIncrementalGenerator
{{baseCopy}}
""");
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true))
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, true, deltaType))
{
var interfaceModifiers = baseType != null &&
baseType.AllInterfaces.Any(i => i.ToDisplayString() == interfaceName)
var interfaceModifiers = baseType != null && baseType.AllInterfaces.Contains(@interface, SymbolEqualityComparer.Default)
? "override "
: modifiers;
var interfaceName = @interface.ToDisplayString();
builder.AppendLine($$"""
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
var def = ({{definition.GenericTypeName}}) target;
Copy(ref def, serialization, hookCtx, context);
target = def;
}
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void InternalCopy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
var def = ({{definition.GenericTypeName}}) target;
Copy(ref def, serialization, hookCtx, context);
target = def;
}
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
InternalCopy(ref target, serialization, hookCtx, context);
}
""");
/// <seealso cref="ISerializationManager.CopyTo"/>
[Obsolete("Use ISerializationManager.CopyTo instead")]
public {{interfaceModifiers}} void Copy(ref {{interfaceName}} target, ISerializationManager serialization, SerializationHookContext hookCtx, ISerializationContext? context = null)
{
InternalCopy(ref target, serialization, hookCtx, context);
}
""");
}
return builder.ToString();
}
private static string GetInstantiators(DataDefinition definition)
private static string GetInstantiators(DataDefinition definition, ITypeSymbol deltaType)
{
var builder = new StringBuilder();
var modifiers = string.Empty;
@@ -332,28 +330,27 @@ public class Generator : IIncrementalGenerator
""");
}
foreach (var interfaceName in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false))
foreach (var @interface in InternalGetImplicitDataDefinitionInterfaces(definition.Type, false, deltaType))
{
var interfaceName = @interface.ToDisplayString();
builder.AppendLine($$"""
{{interfaceName}} {{interfaceName}}.Instantiate()
{
return Instantiate();
}
{{interfaceName}} {{interfaceName}}.Instantiate()
{
return Instantiate();
}
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
{
return Instantiate();
}
""");
{{interfaceName}} ISerializationGenerated<{{interfaceName}}>.Instantiate()
{
return Instantiate();
}
""");
}
return builder.ToString();
}
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
private static IEnumerable<string> InternalGetImplicitDataDefinitionInterfaces(
ITypeSymbol type,
bool all)
private static IEnumerable<ITypeSymbol> InternalGetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all, ITypeSymbol deltaType)
{
var symbols = GetImplicitDataDefinitionInterfaces(type, all);
@@ -371,10 +368,10 @@ public class Generator : IIncrementalGenerator
return symbols;
}
if (symbols.Any(x => x == ComponentDeltaInterfaceName))
if (symbols.Any(x => x.ToDisplayString() == deltaType.ToDisplayString()))
return symbols;
return symbols.Append(ComponentDeltaInterfaceName);
return symbols.Append(deltaType);
}
// TODO serveronly? do we care? who knows!!

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -94,13 +93,13 @@ internal static class Types
return false;
}
internal static IEnumerable<string> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
internal static IEnumerable<ITypeSymbol> GetImplicitDataDefinitionInterfaces(ITypeSymbol type, bool all)
{
var interfaces = all ? type.AllInterfaces : type.Interfaces;
foreach (var @interface in interfaces)
{
if (IsImplicitDataDefinitionInterface(@interface))
yield return @interface.ToDisplayString();
yield return @interface;
}
}

View File

@@ -297,7 +297,7 @@ namespace Robust.Server
: null;
// Set up the VFS
_resources.Initialize(dataDir, hideUserDataDir: false);
_resources.Initialize(dataDir);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;

View File

@@ -8,7 +8,6 @@ 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;
@@ -47,11 +46,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!");
}
}
@@ -60,7 +59,7 @@ namespace Robust.Server.Console.Commands
switch (args.Length)
{
case 1:
return CompletionResult.FromHintOptions(CompletionHelper.Components<MapGridComponent>(args[0], _ent), Loc.GetString("cmd-hint-savebp-id"));
return CompletionResult.FromHint(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"));
@@ -160,7 +159,6 @@ 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!;
@@ -171,7 +169,7 @@ namespace Robust.Server.Console.Commands
switch (args.Length)
{
case 1:
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entManager), Loc.GetString("cmd-hint-savemap-id"));
return CompletionResult.FromHint(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,23 +204,6 @@ 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

@@ -1,15 +0,0 @@
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,7 +61,6 @@ namespace Robust.Server.ServerStatus
OnStatusRequest?.Invoke(jObject);
context.AddAllowOriginAny();
await context.RespondJsonAsync(jObject);
return true;
@@ -122,7 +121,6 @@ namespace Robust.Server.ServerStatus
OnInfoRequest?.Invoke(jObject);
context.AddAllowOriginAny();
await context.RespondJsonAsync(jObject);
return true;

View File

@@ -93,7 +93,6 @@ 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);
@@ -168,7 +167,6 @@ 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));
@@ -215,13 +213,11 @@ 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,7 +682,6 @@ 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;
@@ -751,7 +750,6 @@ 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,6 +1,4 @@
using System;
using System.Collections.Immutable;
using System.Diagnostics.Contracts;
using System.Numerics;
namespace Robust.Shared.Maths
@@ -38,21 +36,6 @@ 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)
@@ -255,7 +238,6 @@ 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

@@ -1,11 +0,0 @@
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]
[Prototype("audioPreset")]
public sealed partial class AudioPresetPrototype : IPrototype
{
[IdDataField]

View File

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

View File

@@ -1874,12 +1874,5 @@ 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,17 +67,6 @@ 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

@@ -60,7 +60,9 @@ namespace Robust.Shared.ContentPack
internal string GetPath(ResPath relPath)
{
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
// Sanitise platform-specific path and standardize it for engine use.
.Replace(Path.DirectorySeparatorChar, '/');
}
/// <inheritdoc />

View File

@@ -14,11 +14,7 @@ namespace Robust.Shared.ContentPack
/// The directory to use for user data.
/// If null, a virtual temporary file system is used instead.
/// </param>
/// <param name="hideUserDataDir">
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
/// <see cref="IResourceManager.UserData"/>.
/// </param>
void Initialize(string? userData, bool hideUserDataDir);
void Initialize(string? userData);
/// <summary>
/// Mounts a single stream as a content file. Useful for unit testing.

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
{
/// <summary>
/// The root path of this provider.
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
/// Can be null if it's a virtual provider.
/// </summary>
string? RootDir { get; }

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
{
@@ -64,27 +63,5 @@ namespace Robust.Shared.ContentPack
!OperatingSystem.IsWindows()
&& !OperatingSystem.IsMacOS();
internal static string SafeGetResourcePath(string baseDir, ResPath path)
{
var relSysPath = path.ToRelativeSystemPath();
if (relSysPath.Contains("\\..") || relSysPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
var retPath = Path.GetFullPath(Path.Join(baseDir, relSysPath));
// better safe than sorry check
if (!retPath.StartsWith(baseDir))
{
// Allow path to match if it's just missing the directory separator at the end.
if (retPath != baseDir.TrimEnd('\\'))
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return retPath;
}
}
}

View File

@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
public IWritableDirProvider UserData { get; private set; } = default!;
/// <inheritdoc />
public virtual void Initialize(string? userData, bool hideRootDir)
public virtual void Initialize(string? userData)
{
Sawmill = _logManager.GetSawmill("res");
if (userData != null)
{
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
}
else
{
@@ -381,10 +381,6 @@ namespace Robust.Shared.ContentPack
{
var rootDir = loader.GetPath(new ResPath(@"/"));
// TODO: GET RID OF THIS.
// This code shouldn't be passing OS disk paths through ResPath.
rootDir = rootDir.Replace(Path.DirectorySeparatorChar, '/');
yield return new ResPath(rootDir);
}
}

View File

@@ -10,22 +10,17 @@ namespace Robust.Shared.ContentPack
/// <inheritdoc />
internal sealed class WritableDirProvider : IWritableDirProvider
{
private readonly bool _hideRootDir;
/// <inheritdoc />
public string RootDir { get; }
string? IWritableDirProvider.RootDir => _hideRootDir ? null : RootDir;
/// <summary>
/// Constructs an instance of <see cref="WritableDirProvider"/>.
/// </summary>
/// <param name="rootDir">Root file system directory to allow writing.</param>
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
public WritableDirProvider(DirectoryInfo rootDir)
{
// FullName does not have a trailing separator, and we MUST have a separator.
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
_hideRootDir = hideRootDir;
}
#region File Access
@@ -124,7 +119,7 @@ namespace Robust.Shared.ContentPack
throw new FileNotFoundException();
var dirInfo = new DirectoryInfo(GetFullPath(path));
return new WritableDirProvider(dirInfo, _hideRootDir);
return new WritableDirProvider(dirInfo);
}
/// <inheritdoc />
@@ -185,7 +180,20 @@ namespace Robust.Shared.ContentPack
path = path.Clean();
return PathHelpers.SafeGetResourcePath(RootDir, path);
return GetFullPath(RootDir, path);
}
private static string GetFullPath(string root, ResPath path)
{
var relPath = path.ToRelativeSystemPath();
if (relPath.Contains("\\..") || relPath.Contains("/.."))
{
// Hard cap on any exploit smuggling a .. in there.
// Since that could allow leaving sandbox.
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
}
return Path.GetFullPath(Path.Combine(root, relPath));
}
}
}

View File

@@ -47,7 +47,6 @@ 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

@@ -1,22 +1,20 @@
using Robust.Shared.Serialization.Manager.Attributes;
namespace Robust.Shared.GameObjects;
/// <summary>
/// Controls PVS visibility of entities. THIS COMPONENT CONTROLS WHETHER ENTITIES ARE NETWORKED TO PLAYERS
/// AND SHOULD NOT BE USED AS THE SOLE WAY TO HIDE AN ENTITY FROM A PLAYER.
/// </summary>
[RegisterComponent]
[Access(typeof(SharedVisibilitySystem))]
public sealed partial class VisibilityComponent : Component
namespace Robust.Shared.GameObjects
{
/// <summary>
/// The visibility layer for the entity.
/// Players whose visibility masks don't match this won't get state updates for it.
/// Controls PVS visibility of entities. THIS COMPONENT CONTROLS WHETHER ENTITIES ARE NETWORKED TO PLAYERS
/// AND SHOULD NOT BE USED AS THE SOLE WAY TO HIDE AN ENTITY FROM A PLAYER.
/// </summary>
/// <remarks>
/// Not serialized as visibility is normally immediate (i.e. prior to MapInit) and content should be handling it as such.
/// </remarks>
[DataField(readOnly: true)]
public ushort Layer = 1;
[RegisterComponent]
[Access(typeof(SharedVisibilitySystem))]
public sealed partial class VisibilityComponent : Component
{
/// <summary>
/// The visibility layer for the entity.
/// Players whose visibility masks don't match this won't get state updates for it.
/// </summary>
[DataField("layer")]
public ushort Layer = 1;
}
}

View File

@@ -59,7 +59,7 @@ public abstract partial class EntityManager
Dirty(uid, comp, metadata);
}
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
where T : IComponentDelta
{
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());

View File

@@ -172,22 +172,12 @@ public partial class EntitySystem
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
where T : IComponentDelta
{
EntityManager.DirtyFields(uid, comp, meta, fields);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected void DirtyFields<T>(Entity<T?> ent, MetaDataComponent? meta, params string[] fields)
where T : IComponentDelta
{
if (!Resolve(ent, ref ent.Comp))
return;
EntityManager.DirtyFields(ent, ent.Comp, meta, fields);
}
/// <summary>
/// Marks a component as dirty. This also implicitly dirties the entity this component belongs to.
/// </summary>

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 : notnull
where TEvent : EntityEventArgs
{
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 : notnull
where TEvent : EntityEventArgs
{
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 : notnull
where TEvent : EntityEventArgs
{
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 external sources such as CVars.
/// to unsubscribe from 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,7 +96,6 @@ 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

@@ -9,7 +9,6 @@ using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Shapes;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;
@@ -407,7 +406,7 @@ public sealed partial class EntityLookupSystem
var broadphaseInv = _transform.GetInvWorldMatrix(lookupUid);
var localBounds = broadphaseInv.TransformBounds(worldBounds);
var polygon = new SlimPolygon(localBounds);
var polygon = _physics.GetPooled(localBounds);
var result = AnyEntitiesIntersecting(lookupUid,
polygon,
localBounds.CalcBoundingBox(),
@@ -415,6 +414,7 @@ public sealed partial class EntityLookupSystem
flags,
ignored);
_physics.ReturnPooled(polygon);
return result;
}
@@ -458,8 +458,9 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return false;
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
return result;
}
@@ -474,8 +475,9 @@ public sealed partial class EntityLookupSystem
{
if (mapId == MapId.Nullspace) return;
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
}
#endregion
@@ -485,16 +487,18 @@ public sealed partial class EntityLookupSystem
public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
// Don't need to check contained entities as they have the same bounds as the parent.
var polygon = new SlimPolygon(worldBounds);
var polygon = _physics.GetPooled(worldBounds);
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
return result;
}
public HashSet<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags)
{
var intersecting = new HashSet<EntityUid>();
var polygon = new SlimPolygon(worldBounds);
var polygon = _physics.GetPooled(worldBounds);
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
_physics.ReturnPooled(polygon);
return intersecting;
}
@@ -757,10 +761,11 @@ public sealed partial class EntityLookupSystem
return;
var localAABB = _transform.GetInvWorldMatrix(gridId).TransformBox(worldAABB);
var polygon = new SlimPolygon(localAABB);
var polygon = _physics.GetPooled(localAABB);
AddEntitiesIntersecting(gridId, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, lookup);
AddContained(intersecting, flags);
_physics.ReturnPooled(polygon);
}
public void GetEntitiesIntersecting(EntityUid gridId, Box2Rotated worldBounds, HashSet<EntityUid> intersecting, LookupFlags flags = DefaultFlags)
@@ -769,10 +774,11 @@ public sealed partial class EntityLookupSystem
return;
var localBounds = _transform.GetInvWorldMatrix(gridId).TransformBounds(worldBounds);
var polygon = new SlimPolygon(localBounds);
var polygon = _physics.GetPooled(localBounds);
AddEntitiesIntersecting(gridId, intersecting, polygon, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, lookup);
AddContained(intersecting, flags);
_physics.ReturnPooled(polygon);
}
#endregion

View File

@@ -121,8 +121,9 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return;
var polygon = new SlimPolygon(localAABB);
var polygon = _physics.GetPooled(localAABB);
AddEntitiesIntersecting(lookupUid, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, query, lookup);
_physics.ReturnPooled(polygon);
}
private void AddEntitiesIntersecting<T, TShape>(
@@ -251,10 +252,11 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return false;
var polygon = new SlimPolygon(localAABB);
var polygon = _physics.GetPooled(localAABB);
var (lookupPos, lookupRot) = _transform.GetWorldPositionRotation(lookupUid);
var transform = new Transform(lookupPos, lookupRot);
var result = AnyComponentsIntersecting(lookupUid, polygon, localAABB, transform, flags, query, ignored, lookup);
_physics.ReturnPooled(polygon);
return result;
}
@@ -425,9 +427,10 @@ public sealed partial class EntityLookupSystem
public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, EntityUid? ignored = null, LookupFlags flags = DefaultFlags)
{
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
var transform = Physics.Transform.Empty;
var result = AnyComponentsIntersecting(type, mapId, polygon, transform, ignored, flags);
_physics.ReturnPooled(polygon);
return result;
}
@@ -493,30 +496,33 @@ public sealed partial class EntityLookupSystem
if (mapId == MapId.Nullspace)
return;
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
var transform = Physics.Transform.Empty;
GetEntitiesIntersecting(type, mapId, polygon, transform, intersecting, flags);
_physics.ReturnPooled(polygon);
}
public void GetEntitiesIntersecting<T>(MapId mapId, Box2Rotated worldBounds, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
if (mapId == MapId.Nullspace) return;
var polygon = new SlimPolygon(worldBounds);
var polygon = _physics.GetPooled(worldBounds);
var shapeTransform = Physics.Transform.Empty;
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
_physics.ReturnPooled(polygon);
}
public void GetEntitiesIntersecting<T>(MapId mapId, Box2 worldAABB, HashSet<Entity<T>> entities, LookupFlags flags = DefaultFlags) where T : IComponent
{
if (mapId == MapId.Nullspace) return;
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
var shapeTransform = Physics.Transform.Empty;
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
_physics.ReturnPooled(polygon);
}
#endregion

View File

@@ -26,7 +26,7 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return;
var lookupPoly = new SlimPolygon(localAABB);
var lookupPoly = new Polygon(localAABB);
AddEntitiesIntersecting(lookupUid, intersecting, lookupPoly, localAABB, Physics.Transform.Empty, flags, lookup);
}
@@ -40,7 +40,7 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return;
var shape = new SlimPolygon(localBounds);
var shape = new Polygon(localBounds);
var localAABB = localBounds.CalcBoundingBox();
AddEntitiesIntersecting(lookupUid, intersecting, shape, localAABB, Physics.Transform.Empty, flags);
@@ -55,7 +55,7 @@ public sealed partial class EntityLookupSystem
if (!_broadQuery.Resolve(lookupUid, ref lookup))
return false;
var shape = new SlimPolygon(localAABB);
var shape = new Polygon(localAABB);
return AnyEntitiesIntersecting(lookupUid, shape, localAABB, Physics.Transform.Empty, flags, ignored, lookup);
}

View File

@@ -159,10 +159,6 @@ public abstract class SharedEyeSystem : EntitySystem
eye.Comp.PvsScale = Math.Clamp(scale, 0.1f, 100f);
}
/// <summary>
/// Overwrites visibility mask of an entity's eye.
/// If you wish for other systems to potentially change it consider raising <see cref="RefreshVisibilityMask"/>.
/// </summary>
public void SetVisibilityMask(EntityUid uid, int value, EyeComponent? eyeComponent = null)
{
if (!Resolve(uid, ref eyeComponent))
@@ -174,32 +170,4 @@ public abstract class SharedEyeSystem : EntitySystem
eyeComponent.VisibilityMask = value;
DirtyField(uid, eyeComponent, nameof(EyeComponent.VisibilityMask));
}
/// <summary>
/// Updates the visibility mask for an entity by raising a <see cref="GetVisMaskEvent"/>
/// </summary>
public void RefreshVisibilityMask(Entity<EyeComponent?> entity)
{
if (!Resolve(entity.Owner, ref entity.Comp, false))
return;
var ev = new GetVisMaskEvent()
{
Entity = entity.Owner,
};
RaiseLocalEvent(entity.Owner, ref ev, true);
SetVisibilityMask(entity.Owner, ev.VisibilityMask, entity.Comp);
}
}
/// <summary>
/// Event raised to update the vismask of an entity's eye.
/// </summary>
[ByRefEvent]
public record struct GetVisMaskEvent()
{
public EntityUid Entity;
public int VisibilityMask = EyeComponent.DefaultVisibilityMask;
}

View File

@@ -111,7 +111,7 @@ public abstract partial class SharedTransformSystem
public EntityUid? GetGrid(Entity<TransformComponent?> entity)
{
return !Resolve(entity, ref entity.Comp, logMissing:false) ? null : entity.Comp.GridUid;
return !Resolve(entity, ref entity.Comp) ? 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, logMissing: false) ? MapId.Nullspace : entity.Comp.MapID;
return !Resolve(entity, ref entity.Comp) ? 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, logMissing: false) ? null : entity.Comp.MapUid;
return !Resolve(entity, ref entity.Comp) ? 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, logMissing: false))
if (!Resolve(entA, ref entA.Comp))
return false;
if (!Resolve(entB, ref entB.Comp, logMissing: false))
if (!Resolve(entB, ref entB.Comp))
return false;
if (!entA.Comp.ParentUid.IsValid() || !entB.Comp.ParentUid.IsValid())

View File

@@ -121,7 +121,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (msg.Message is not CloseBoundInterfaceMessage && ui.RequireInputValidation)
{
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey, msg.Message);
RaiseLocalEvent(uid, attempt);
RaiseLocalEvent(attempt);
if (attempt.Cancelled)
return;
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using JetBrains.Annotations;
@@ -96,15 +95,9 @@ namespace Robust.Shared.Localization
CultureInfo? DefaultCulture { get; set; }
/// <summary>
/// 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.
/// Checks if the culture has been loaded.
/// </summary>
/// <param name="culture"></param>
bool HasCulture(CultureInfo culture);
/// <summary>
@@ -113,17 +106,6 @@ 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,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
@@ -13,7 +12,6 @@ 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;
@@ -26,9 +24,6 @@ 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!;
@@ -46,18 +41,6 @@ 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)
@@ -354,17 +337,6 @@ 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);
@@ -372,10 +344,6 @@ 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()
@@ -390,20 +358,6 @@ 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)>();
@@ -484,7 +438,7 @@ namespace Robust.Shared.Localization
// Load data from .ftl files.
// Data is loaded from /Locale/<language-code>/*
var root = LocaleDirPath / culture.Name;
var root = new ResPath($"/Locale/{culture.Name}/");
var files = resourceManager.ContentFindFiles(root)
.Where(c => c.Filename.EndsWith(".ftl", StringComparison.InvariantCultureIgnoreCase))

View File

@@ -8,7 +8,6 @@ using Robust.Shared.Map.Enumerators;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Shapes;
namespace Robust.Shared.Map;
@@ -225,42 +224,48 @@ internal partial class MapManager
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2 worldAABB, ref TState state, GridCallback<TState> callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref state, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, ref List<Entity<MapGridComponent>> grids,
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = new SlimPolygon(worldAABB);
var polygon = _physics.GetPooled(worldAABB);
FindGridsIntersecting(mapEnt, polygon, worldAABB, Transform.Empty, ref grids, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, GridCallback callback, bool approx = IMapManager.Approximate,
bool includeMap = IMapManager.IncludeMap)
{
var polygon = new SlimPolygon(worldBounds);
var polygon = _physics.GetPooled(worldBounds);
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting<TState>(EntityUid mapEnt, Box2Rotated worldBounds, ref TState state, GridCallback<TState> callback,
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = new SlimPolygon(worldBounds);
var polygon = _physics.GetPooled(worldBounds);
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref state, callback, approx, includeMap);
_physics.ReturnPooled(polygon);
}
public void FindGridsIntersecting(EntityUid mapEnt, Box2Rotated worldBounds, ref List<Entity<MapGridComponent>> grids,
bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
{
var polygon = new SlimPolygon(worldBounds);
var polygon = _physics.GetPooled(worldBounds);
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref grids, approx, includeMap);
_physics.ReturnPooled(polygon);
}
#endregion

View File

@@ -1,6 +1,8 @@
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._00;
ref var p0 = ref manifold.Points[0];
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._00.Id.Key = 0;
manifold.Points._00.Id.Features = cf;
manifold.Points._00.LocalPoint = circleB.Position;
manifold.Points[0].Id.Key = 0;
manifold.Points[0].Id.Features = cf;
manifold.Points[0].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._00.Id.Key = 0;
manifold.Points._00.Id.Features = cf;
manifold.Points._00.LocalPoint = circleB.Position;
manifold.Points[0].Id.Key = 0;
manifold.Points[0].Id.Features = cf;
manifold.Points[0].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._00.Id.Key = 0;
manifold.Points._00.Id.Features = cf;
manifold.Points._00.LocalPoint = circleB.Position;
manifold.Points[0].Id.Key = 0;
manifold.Points[0].Id.Features = cf;
manifold.Points[0].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)
{
var separation = Vector2.Dot(refFace.normal, clipPoints2[i].V - refFace.v1);
float separation;
separation = Vector2.Dot(refFace.normal, clipPoints2[i].V - refFace.v1);
if (separation <= radius)
{
ref var cp = ref points[pointCount];
ref var cp = ref manifold.Points[pointCount];
if (primaryAxis.Type == EPAxisType.EdgeA)
{

View File

@@ -12,9 +12,7 @@ internal sealed partial class CollisionManager
/// <param name="xfA">The transform for the first shape.</param>
/// <param name="xfB">The transform for the seconds shape.</param>
/// <returns></returns>
public bool TestOverlap<T, U>(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB)
where T : IPhysShape
where U : IPhysShape
public bool TestOverlap<T, U>(T shapeA, int indexA, U shapeB, int indexB, in Transform xfA, in Transform xfB) where T : IPhysShape where U : IPhysShape
{
var input = new DistanceInput();

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._00;
ref var p0 = ref manifold.Points[0];
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._00;
ref var p0 = ref manifold.Points[0];
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._00;
ref var p0 = ref manifold.Points[0];
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._00;
ref var p0 = ref manifold.Points[0];
p0.LocalPoint = circleB.Position;
p0.Id.Key = 0;

View File

@@ -238,8 +238,6 @@ 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;
@@ -247,7 +245,7 @@ internal sealed partial class CollisionManager
if (separation <= totalRadius)
{
ref var cp = ref points[pointCount];
ref var cp = ref manifold.Points[pointCount];
cp.LocalPoint = Transform.MulT(xf2, clipPoints2[i].V);
cp.Id = clipPoints2[i].ID;

View File

@@ -51,18 +51,15 @@ 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)
{
var id = points1[i].Id;
ContactID id = manifold1.Points[i].Id;
state1[i] = PointState.Remove;
for (int j = 0; j < manifold2.PointCount; ++j)
{
if (points2[j].Id.Key == id.Key)
if (manifold2.Points[j].Id.Key == id.Key)
{
state1[i] = PointState.Persist;
break;
@@ -73,13 +70,13 @@ internal sealed partial class CollisionManager : IManifoldManager
// Detect persists and adds.
for (int i = 0; i < manifold2.PointCount; ++i)
{
var id = points2[i].Id;
ContactID id = manifold2.Points[i].Id;
state2[i] = PointState.Add;
for (var j = 0; j < manifold1.PointCount; ++j)
for (int j = 0; j < manifold1.PointCount; ++j)
{
if (points1[j].Id.Key == id.Key)
if (manifold1.Points[j].Id.Key == id.Key)
{
state2[i] = PointState.Persist;
break;

View File

@@ -21,9 +21,12 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Shapes;
@@ -43,7 +46,7 @@ internal ref struct DistanceProxy
// GJK using Voronoi regions (Christer Ericson) and Barycentric coordinates.
internal DistanceProxy(ReadOnlySpan<Vector2> vertices, float radius)
internal DistanceProxy(Vector2[] vertices, float radius)
{
Vertices = vertices;
Radius = radius;
@@ -68,18 +71,9 @@ internal ref struct DistanceProxy
case ShapeType.Polygon:
if (shape is Polygon poly)
{
Span<Vector2> verts = new Vector2[poly.VertexCount];
poly._vertices.AsSpan[..poly.VertexCount].CopyTo(verts);
Vertices = verts;
Vertices = poly.Vertices.AsSpan()[..poly.VertexCount];
Radius = poly.Radius;
}
else if (shape is SlimPolygon fast)
{
Span<Vector2> verts = new Vector2[fast.VertexCount];
fast._vertices.AsSpan[..fast.VertexCount].CopyTo(verts);
Vertices = verts;
Radius = fast.Radius;
}
else
{
var polyShape = Unsafe.As<PolygonShape>(shape);
@@ -157,7 +151,7 @@ internal ref struct DistanceProxy
return Vertices[bestIndex];
}
internal static DistanceProxy MakeProxy(ReadOnlySpan<Vector2> vertices, int count, float radius )
internal static DistanceProxy MakeProxy(Vector2[] vertices, int count, float radius )
{
count = Math.Min(count, PhysicsConstants.MaxPolygonVertices);
var proxy = new DistanceProxy(vertices[..count], radius);

View File

@@ -25,7 +25,6 @@ using System.Numerics;
using System.Runtime.InteropServices;
using Robust.Shared.Localization;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Collision;
@@ -147,7 +146,7 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
/// <summary>
/// Points of contact, can only be 0 -> 2.
/// </summary>
internal FixedArray2<ManifoldPoint> Points;
internal ManifoldPoint[] Points;
public ManifoldType Type;
@@ -158,12 +157,9 @@ 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(otherPoints[i])) return false;
if (!Points[i].Equals(other.Points[i])) return false;
}
return true;
@@ -176,12 +172,9 @@ 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(otherPoints[i])) return false;
if (!Points[i].EqualsApprox(other.Points[i])) return false;
}
return true;
@@ -194,26 +187,13 @@ 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(otherPoints[i], tolerance)) return false;
if (!Points[i].EqualsApprox(other.Points[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

@@ -173,25 +173,10 @@ namespace Robust.Shared.Physics.Collision.Shapes
{
}
internal PolygonShape(SlimPolygon poly)
{
Vertices = new Vector2[poly.VertexCount];
Normals = new Vector2[poly.VertexCount];
poly._vertices.AsSpan[..VertexCount].CopyTo(Vertices);
poly._normals.AsSpan[..VertexCount].CopyTo(Normals);
Centroid = poly.Centroid;
}
internal PolygonShape(Polygon poly)
{
Vertices = new Vector2[poly.VertexCount];
Normals = new Vector2[poly.VertexCount];
poly._vertices.AsSpan[..VertexCount].CopyTo(Vertices);
poly._normals.AsSpan[..VertexCount].CopyTo(Normals);
Vertices = poly.Vertices;
Normals = poly.Normals;
Centroid = poly.Centroid;
}

View File

@@ -150,15 +150,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
Friction = MathF.Sqrt((FixtureA?.Friction ?? 0.0f) * (FixtureB?.Friction ?? 0.0f));
}
public void GetWorldManifold(Transform transformA, Transform transformB, out Vector2 normal)
{
var shapeA = FixtureA?.Shape!;
var shapeB = FixtureB?.Shape!;
Span<Vector2> points = stackalloc Vector2[PhysicsConstants.MaxPolygonVertices];
SharedPhysicsSystem.InitializeManifold(ref Manifold, transformA, transformB, shapeA.Radius, shapeB.Radius, out normal, points);
}
/// <summary>
/// Gets the world manifold.
/// </summary>
@@ -206,19 +197,16 @@ 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 = points[i];
var mp2 = Manifold.Points[i];
mp2.NormalImpulse = 0.0f;
mp2.TangentImpulse = 0.0f;
var id2 = mp2.Id;
for (var j = 0; j < oldManifold.PointCount; ++j)
{
var mp1 = oldPoints[j];
var mp1 = oldManifold.Points[j];
if (mp1.Id.Key == id2.Key)
{
@@ -228,7 +216,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
}
}
points[i] = mp2;
Manifold.Points[i] = mp2;
}
if (touching != wasTouching)
@@ -420,28 +408,6 @@ 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,7 +22,6 @@
using System.Numerics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Dynamics.Contacts
{
@@ -38,7 +37,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
/// </summary>
public int IndexB { get; set; }
internal FixedArray2<Vector2> LocalPoints;
public Vector2[] LocalPoints;
public Vector2 LocalNormal;

View File

@@ -21,7 +21,6 @@
*/
using System.Numerics;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Dynamics.Contacts
{
@@ -40,13 +39,13 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
public int IndexB;
// Use 2 as its the max number of manifold points.
public FixedArray2<VelocityConstraintPoint> Points;
public VelocityConstraintPoint[] Points;
public Vector2 Normal;
public Vector4 NormalMass;
public System.Numerics.Vector4 NormalMass;
public Vector4 K;
public System.Numerics.Vector4 K;
public float InvMassA;
public float InvMassB;

View File

@@ -11,28 +11,19 @@ namespace Robust.Shared.Physics.Shapes;
// Internal so people don't use it when it will have breaking changes very soon.
internal record struct Polygon : IPhysShape
{
[DataField]
public byte VertexCount { get; internal set; }
/// <summary>
/// Vertices associated with this polygon. Will be sliced to <see cref="VertexCount"/>
/// </summary>
/// <remarks>
/// Consider using _vertices if doing engine work.
/// </remarks>
public Vector2[] Vertices => _vertices.AsSpan[..VertexCount].ToArray();
public Vector2[] Normals => _normals.AsSpan[..VertexCount].ToArray();
public static Polygon Empty = new(Box2.Empty);
[DataField]
internal FixedArray8<Vector2> _vertices;
public Vector2[] Vertices;
internal FixedArray8<Vector2> _normals;
public byte VertexCount;
public Vector2[] Normals;
public Vector2 Centroid;
public int ChildCount => 1;
public float Radius { get; set; } = PhysicsConstants.PolygonRadius;
public float Radius { get; set; }
public ShapeType ShapeType => ShapeType.Polygon;
// Hopefully this one is short-lived for a few months
@@ -44,58 +35,65 @@ internal record struct Polygon : IPhysShape
public Polygon(PolygonShape polyShape)
{
Unsafe.SkipInit(out this);
Vertices = new Vector2[polyShape.VertexCount];
Normals = new Vector2[polyShape.Normals.Length];
Radius = polyShape.Radius;
Centroid = polyShape.Centroid;
VertexCount = (byte) polyShape.VertexCount;
polyShape.Vertices.AsSpan()[..VertexCount].CopyTo(_vertices.AsSpan);
polyShape.Normals.AsSpan()[..VertexCount].CopyTo(_normals.AsSpan);
}
public Polygon(Box2 box)
{
Unsafe.SkipInit(out this);
Radius = 0f;
VertexCount = 4;
_vertices._00 = box.BottomLeft;
_vertices._01 = box.BottomRight;
_vertices._02 = box.TopRight;
_vertices._03 = box.TopLeft;
_normals._00 = new Vector2(0.0f, -1.0f);
_normals._01 = new Vector2(1.0f, 0.0f);
_normals._02 = new Vector2(0.0f, 1.0f);
_normals._03 = new Vector2(-1.0f, 0.0f);
}
public Polygon(Box2Rotated bounds)
{
Unsafe.SkipInit(out this);
Radius = 0f;
VertexCount = 4;
_vertices._00 = bounds.BottomLeft;
_vertices._01 = bounds.BottomRight;
_vertices._02 = bounds.TopRight;
_vertices._03 = bounds.TopLeft;
CalculateNormals(_vertices.AsSpan, _normals.AsSpan, 4);
Centroid = bounds.Center;
Array.Copy(polyShape.Vertices, Vertices, Vertices.Length);
Array.Copy(polyShape.Normals, Normals, Vertices.Length);
VertexCount = (byte) Vertices.Length;
}
/// <summary>
/// Manually constructed polygon for internal use to take advantage of pooling.
/// </summary>
internal Polygon(ReadOnlySpan<Vector2> vertices, ReadOnlySpan<Vector2> normals, Vector2 centroid, byte count)
internal Polygon(Vector2[] vertices, Vector2[] normals, Vector2 centroid, byte count)
{
Unsafe.SkipInit(out this);
vertices[..VertexCount].CopyTo(_vertices.AsSpan);
normals[..VertexCount].CopyTo(_normals.AsSpan);
Vertices = vertices;
Normals = normals;
Centroid = centroid;
VertexCount = count;
}
public Polygon(Box2 aabb)
{
Unsafe.SkipInit(out this);
Vertices = new Vector2[4];
Normals = new Vector2[4];
Radius = 0f;
Vertices[0] = aabb.BottomLeft;
Vertices[1] = aabb.BottomRight;
Vertices[2] = aabb.TopRight;
Vertices[3] = aabb.TopLeft;
Normals[0] = new Vector2(0.0f, -1.0f);
Normals[1] = new Vector2(1.0f, 0.0f);
Normals[2] = new Vector2(0.0f, 1.0f);
Normals[3] = new Vector2(-1.0f, 0.0f);
VertexCount = 4;
Centroid = aabb.Center;
}
public Polygon(Box2Rotated bounds)
{
Unsafe.SkipInit(out this);
Vertices = new Vector2[4];
Normals = new Vector2[4];
Radius = 0f;
Vertices[0] = bounds.BottomLeft;
Vertices[1] = bounds.BottomRight;
Vertices[2] = bounds.TopRight;
Vertices[3] = bounds.TopLeft;
CalculateNormals(Normals, 4);
VertexCount = 4;
Centroid = bounds.Center;
}
public Polygon(Vector2[] vertices)
@@ -106,15 +104,16 @@ internal record struct Polygon : IPhysShape
if (hull.Count < 3)
{
VertexCount = 0;
Vertices = [];
Normals = [];
return;
}
VertexCount = (byte) vertices.Length;
var vertSpan = _vertices.AsSpan;
vertices.AsSpan().CopyTo(vertSpan);
Vertices = vertices;
Normals = new Vector2[vertices.Length];
Set(hull);
Centroid = ComputeCentroid(vertSpan);
Centroid = ComputeCentroid(Vertices);
}
public static explicit operator Polygon(PolygonShape polyShape)
@@ -126,32 +125,32 @@ internal record struct Polygon : IPhysShape
{
DebugTools.Assert(hull.Count >= 3);
var vertexCount = hull.Count;
var verts = _vertices.AsSpan;
var norms = _normals.AsSpan;
Array.Resize(ref Vertices, vertexCount);
Array.Resize(ref Normals, vertexCount);
for (var i = 0; i < vertexCount; i++)
{
verts[i] = hull.Points[i];
Vertices[i] = hull.Points[i];
}
// Compute normals. Ensure the edges have non-zero length.
CalculateNormals(verts, norms, vertexCount);
CalculateNormals(Normals, vertexCount);
}
public static void CalculateNormals(ReadOnlySpan<Vector2> vertices, Span<Vector2> normals, int count)
internal void CalculateNormals(Span<Vector2> normals, int count)
{
for (var i = 0; i < count; i++)
{
var next = i + 1 < count ? i + 1 : 0;
var edge = vertices[next] - vertices[i];
var edge = Vertices[next] - Vertices[i];
DebugTools.Assert(edge.LengthSquared() > float.Epsilon * float.Epsilon);
var temp = Vector2Helpers.Cross(edge, 1f);
normals[i] = temp.Normalized();
Normals[i] = temp.Normalized();
}
}
public static Vector2 ComputeCentroid(ReadOnlySpan<Vector2> vs)
private static Vector2 ComputeCentroid(Vector2[] vs)
{
var count = vs.Length;
DebugTools.Assert(count >= 3);
@@ -192,15 +191,13 @@ internal record struct Polygon : IPhysShape
public Box2 ComputeAABB(Transform transform, int childIndex)
{
DebugTools.Assert(VertexCount > 0);
DebugTools.Assert(childIndex == 0);
var verts = _vertices.AsSpan;
var lower = Transform.Mul(transform, verts[0]);
var lower = Transform.Mul(transform, Vertices[0]);
var upper = lower;
for (var i = 1; i < VertexCount; ++i)
{
var v = Transform.Mul(transform, verts[i]);
var v = Transform.Mul(transform, Vertices[i]);
lower = Vector2.Min(lower, v);
upper = Vector2.Max(upper, v);
}
@@ -211,41 +208,12 @@ internal record struct Polygon : IPhysShape
public bool Equals(IPhysShape? other)
{
if (other is SlimPolygon slim)
{
return Equals(slim);
}
return other is Polygon poly && Equals(poly);
}
public bool Equals(Polygon other)
{
if (VertexCount != other.VertexCount) return false;
var ourVerts = _vertices.AsSpan;
var otherVerts = other._vertices.AsSpan;
if (other is not PolygonShape poly) return false;
if (VertexCount != poly.VertexCount) return false;
for (var i = 0; i < VertexCount; i++)
{
var vert = ourVerts[i];
if (!vert.Equals(otherVerts[i])) return false;
}
return true;
}
public bool Equals(SlimPolygon other)
{
if (VertexCount != other.VertexCount) return false;
var ourVerts = _vertices.AsSpan;
var otherVerts = other._vertices.AsSpan;
for (var i = 0; i < VertexCount; i++)
{
var vert = ourVerts[i];
if (!vert.Equals(otherVerts[i])) return false;
var vert = Vertices[i];
if (!vert.Equals(poly.Vertices[i])) return false;
}
return true;
@@ -253,6 +221,6 @@ internal record struct Polygon : IPhysShape
public override int GetHashCode()
{
return HashCode.Combine(VertexCount, _vertices.AsSpan.ToArray(), Radius);
return HashCode.Combine(VertexCount, Vertices.AsSpan(0, VertexCount).ToArray(), Radius);
}
}

View File

@@ -1,103 +0,0 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
namespace Robust.Shared.Physics.Shapes;
/// <summary>
/// Polygon backed by FixedArray4 to be smaller.
/// Useful for internal ops where the inputs are boxes to avoid the additional padding.
/// </summary>
internal record struct SlimPolygon : IPhysShape
{
public Vector2[] Vertices => _vertices.AsSpan[..VertexCount].ToArray();
public Vector2[] Normals => _normals.AsSpan[..VertexCount].ToArray();
[DataField]
public FixedArray4<Vector2> _vertices;
public FixedArray4<Vector2> _normals;
public Vector2 Centroid;
public byte VertexCount => 4;
public int ChildCount => 1;
public float Radius { get; set; } = PhysicsConstants.PolygonRadius;
public ShapeType ShapeType => ShapeType.Polygon;
public SlimPolygon(Box2 box)
{
Unsafe.SkipInit(out this);
Radius = 0f;
_vertices._00 = box.BottomLeft;
_vertices._01 = box.BottomRight;
_vertices._02 = box.TopRight;
_vertices._03 = box.TopLeft;
_normals._00 = new Vector2(0.0f, -1.0f);
_normals._01 = new Vector2(1.0f, 0.0f);
_normals._02 = new Vector2(0.0f, 1.0f);
_normals._03 = new Vector2(-1.0f, 0.0f);
}
public SlimPolygon(Box2Rotated bounds)
{
Unsafe.SkipInit(out this);
Radius = 0f;
_vertices._00 = bounds.BottomLeft;
_vertices._01 = bounds.BottomRight;
_vertices._02 = bounds.TopRight;
_vertices._03 = bounds.TopLeft;
Polygon.CalculateNormals(_vertices.AsSpan, _normals.AsSpan, 4);
Centroid = bounds.Center;
}
public Box2 ComputeAABB(Transform transform, int childIndex)
{
DebugTools.Assert(VertexCount > 0);
DebugTools.Assert(childIndex == 0);
var verts = _vertices.AsSpan;
var lower = Transform.Mul(transform, verts[0]);
var upper = lower;
for (var i = 1; i < VertexCount; ++i)
{
var v = Transform.Mul(transform, verts[i]);
lower = Vector2.Min(lower, v);
upper = Vector2.Max(upper, v);
}
var r = new Vector2(Radius, Radius);
return new Box2(lower - r, upper + r);
}
public bool Equals(SlimPolygon other)
{
return Radius.Equals(other.Radius) && _vertices.AsSpan[..VertexCount].SequenceEqual(other._vertices.AsSpan[..VertexCount]);
}
public readonly override int GetHashCode()
{
return HashCode.Combine(_vertices, _normals, Centroid, Radius);
}
public bool Equals(IPhysShape? other)
{
if (other is Polygon poly)
{
return poly.Equals(this);
}
return other is SlimPolygon slim && Equals(slim);
}
}

View File

@@ -40,29 +40,13 @@ namespace Robust.Shared.Physics.Systems
return true;
}
case SlimPolygon slim:
{
var pLocal = Physics.Transform.MulT(xform.Quaternion2D, worldPoint - xform.Position);
var norms = slim._normals.AsSpan;
var verts = slim._vertices.AsSpan;
for (var i = 0; i < slim.VertexCount; i++)
{
var dot = Vector2.Dot(norms[i], pLocal - verts[i]);
if (dot > 0f) return false;
}
return true;
}
case Polygon poly:
{
var pLocal = Physics.Transform.MulT(xform.Quaternion2D, worldPoint - xform.Position);
var norms = poly._normals.AsSpan;
var verts = poly._vertices.AsSpan;
for (var i = 0; i < poly.VertexCount; i++)
{
var dot = Vector2.Dot(norms[i], pLocal - verts[i]);
var dot = Vector2.Dot(poly.Normals[i], pLocal - poly.Vertices[i]);
if (dot > 0f) return false;
}
@@ -77,7 +61,109 @@ namespace Robust.Shared.Physics.Systems
{
var data = new MassData();
GetMassData(shape, ref data, density);
// 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 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!");
}
return data;
}
@@ -109,12 +195,6 @@ namespace Robust.Shared.Physics.Systems
var polygon = (PolygonShape) aabb;
GetMassData(polygon, ref data, density);
break;
case Polygon fastPoly:
GetMassData(new PolygonShape(fastPoly), ref data, density);
break;
case SlimPolygon slim:
GetMassData(new PolygonShape(slim), ref data, density);
break;
case PolygonShape poly:
// Polygon mass, centroid, and inertia.
// Let rho be the polygon density in mass per unit area.

View File

@@ -191,12 +191,6 @@ public sealed partial class RayCastSystem
private CastOutput RayCastPolygon(RayCastInput input, Polygon shape)
{
var verts = shape._vertices.AsSpan;
var output = new CastOutput()
{
Fraction = 0f,
};
if (shape.Radius == 0.0f)
{
// Put the ray into the polygon's frame of reference.
@@ -207,15 +201,18 @@ public sealed partial class RayCastSystem
var index = -1;
var norms = shape._normals.AsSpan;
var output = new CastOutput()
{
Fraction = 0f,
};
for ( var i = 0; i < shape.VertexCount; ++i )
{
// p = p1 + a * d
// dot(normal, p - v) = 0
// dot(normal, p1 - v) + a * dot(normal, d) = 0
float numerator = Vector2.Dot(norms[i], Vector2.Subtract( verts[i], p1 ) );
float denominator = Vector2.Dot(norms[i], d );
float numerator = Vector2.Dot(shape.Normals[i], Vector2.Subtract( shape.Vertices[i], p1 ) );
float denominator = Vector2.Dot(shape.Normals[i], d );
if ( denominator == 0.0f )
{
@@ -260,7 +257,7 @@ public sealed partial class RayCastSystem
if (index >= 0)
{
output.Fraction = lower;
output.Normal = norms[index];
output.Normal = shape.Normals[index];
output.Point = Vector2.Add(p1, lower * d);
output.Hit = true;
}
@@ -268,24 +265,17 @@ public sealed partial class RayCastSystem
return output;
}
Span<Vector2> proxyBVerts = new Vector2[]
{
input.Origin,
};
// TODO_ERIN this is not working for ray vs box (zero radii)
// TODO_ERIN this is not working for ray vs box (zero radii)
var castInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy(verts, shape.VertexCount, shape.Radius),
ProxyB = DistanceProxy.MakeProxy(proxyBVerts, 1, 0.0f),
ProxyA = DistanceProxy.MakeProxy(shape.Vertices, shape.VertexCount, shape.Radius),
ProxyB = DistanceProxy.MakeProxy([input.Origin], 1, 0.0f),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
TranslationB = input.Translation,
MaxFraction = input.MaxFraction
};
ShapeCast(ref output, castInput);
return output;
return ShapeCast(castInput);
}
// Ray vs line segment
@@ -446,9 +436,12 @@ public sealed partial class RayCastSystem
// "Smooth Mesh Contacts with GJK" in Game Physics Pearls. 2010
// todo this is failing when used to raycast a box
// todo this converges slowly with a radius
private void ShapeCast(ref CastOutput output, in ShapeCastPairInput input)
private CastOutput ShapeCast(ShapeCastPairInput input)
{
output.Fraction = input.MaxFraction;
var output = new CastOutput()
{
Fraction = input.MaxFraction,
};
var proxyA = input.ProxyA;
var count = input.ProxyB.Vertices.Length;
@@ -520,15 +513,15 @@ public sealed partial class RayCastSystem
if ( vr <= 0.0f )
{
// miss
return;
}
return output;
}
lambda = ( vp - sigma ) / vr;
if ( lambda > maxFraction )
{
// too far
return;
}
return output;
}
// reset the simplex
simplex.Count = 0;
@@ -569,7 +562,7 @@ public sealed partial class RayCastSystem
{
// Overlap
// Yes this means you need to manually query for overlaps.
return;
return output;
}
// Get search direction.
@@ -583,7 +576,7 @@ public sealed partial class RayCastSystem
if ( iter == 0 || lambda == 0.0f )
{
// Initial overlap
return;
return output;
}
// Prepare output.
@@ -598,6 +591,7 @@ public sealed partial class RayCastSystem
output.Fraction = lambda;
output.Iterations = iter;
output.Hit = true;
return output;
}
private int FindSupport(DistanceProxy proxy, Vector2 direction)
@@ -619,14 +613,9 @@ public sealed partial class RayCastSystem
private CastOutput ShapeCastCircle(ShapeCastInput input, PhysShapeCircle shape)
{
Span<Vector2> proxyAVerts = new[]
{
shape.Position,
};
var pairInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy(proxyAVerts, 1, shape.Radius ),
ProxyA = DistanceProxy.MakeProxy([shape.Position], 1, shape.Radius ),
ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius ),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
@@ -634,8 +623,7 @@ public sealed partial class RayCastSystem
MaxFraction = input.MaxFraction
};
var output = new CastOutput();
ShapeCast(ref output, pairInput);
var output = ShapeCast(pairInput);
return output;
}
@@ -643,7 +631,7 @@ public sealed partial class RayCastSystem
{
var pairInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy(shape._vertices.AsSpan, shape.VertexCount, shape.Radius),
ProxyA = DistanceProxy.MakeProxy(shape.Vertices, shape.VertexCount, shape.Radius),
ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
@@ -651,30 +639,21 @@ public sealed partial class RayCastSystem
MaxFraction = input.MaxFraction
};
var output = new CastOutput();
ShapeCast(ref output, pairInput);
var output = ShapeCast(pairInput);
return output;
}
private CastOutput ShapeCastSegment(ShapeCastInput input, EdgeShape shape)
{
Span<Vector2> proxyAVerts = new[]
{
shape.Vertex0,
};
var pairInput = new ShapeCastPairInput();
pairInput.ProxyA = DistanceProxy.MakeProxy([shape.Vertex0], 2, 0.0f);
pairInput.ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius);
pairInput.TransformA = Physics.Transform.Empty;
pairInput.TransformB = Physics.Transform.Empty;
pairInput.TranslationB = input.Translation;
pairInput.MaxFraction = input.MaxFraction;
var pairInput = new ShapeCastPairInput
{
ProxyA = DistanceProxy.MakeProxy(proxyAVerts, 2, 0.0f),
ProxyB = DistanceProxy.MakeProxy(input.Points, input.Count, input.Radius),
TransformA = Physics.Transform.Empty,
TransformB = Physics.Transform.Empty,
TranslationB = input.Translation,
MaxFraction = input.MaxFraction
};
var output = new CastOutput();
ShapeCast(ref output, pairInput);
var output = ShapeCast(pairInput);
return output;
}

View File

@@ -275,9 +275,6 @@ public sealed partial class RayCastSystem : EntitySystem
case PhysShapeCircle circle:
CastCircle(entity, ref result, circle, originTransform, translation, filter, callback);
break;
case SlimPolygon slim:
CastPolygon(entity, ref result, new PolygonShape(slim), originTransform, translation, filter, callback);
break;
case Polygon poly:
CastPolygon(entity, ref result, new PolygonShape(poly), originTransform, translation, filter, callback);
break;

View File

@@ -113,7 +113,10 @@ public abstract partial class SharedPhysicsSystem
#if DEBUG
contact._debugPhysics = _debugPhysicsSystem;
#endif
contact.Manifold = new Manifold();
contact.Manifold = new Manifold
{
Points = new ManifoldPoint[2]
};
return contact;
}

View File

@@ -1026,6 +1026,12 @@ 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;
@@ -1041,13 +1047,6 @@ 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

@@ -0,0 +1,55 @@
using System.Buffers;
using System.Numerics;
using Microsoft.Extensions.ObjectPool;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Shapes;
namespace Robust.Shared.Physics.Systems;
public abstract partial class SharedPhysicsSystem
{
/// <summary>
/// Gets a polygon with pooled arrays backing it.
/// </summary>
internal Polygon GetPooled(Box2 box)
{
var vertices = ArrayPool<Vector2>.Shared.Rent(4);
var normals = ArrayPool<Vector2>.Shared.Rent(4);
var centroid = box.Center;
vertices[0] = box.BottomLeft;
vertices[1] = box.BottomRight;
vertices[2] = box.TopRight;
vertices[3] = box.TopLeft;
normals[0] = new Vector2(0.0f, -1.0f);
normals[1] = new Vector2(1.0f, 0.0f);
normals[2] = new Vector2(0.0f, 1.0f);
normals[3] = new Vector2(-1.0f, 0.0f);
return new Polygon(vertices, normals, centroid, 4);
}
internal Polygon GetPooled(Box2Rotated box)
{
var vertices = ArrayPool<Vector2>.Shared.Rent(4);
var normals = ArrayPool<Vector2>.Shared.Rent(4);
var centroid = box.Center;
vertices[0] = box.BottomLeft;
vertices[1] = box.BottomRight;
vertices[2] = box.TopRight;
vertices[3] = box.TopLeft;
var polygon = new Polygon(vertices, normals, centroid, 4);
polygon.CalculateNormals(normals, 4);
return polygon;
}
internal void ReturnPooled(Polygon polygon)
{
ArrayPool<Vector2>.Shared.Return(polygon.Vertices);
ArrayPool<Vector2>.Shared.Return(polygon.Normals);
}
}

View File

@@ -67,6 +67,7 @@ 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);
@@ -86,6 +87,7 @@ 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;
@@ -95,14 +97,11 @@ 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 = points[j];
ref var constraintPoint = ref velPoints[j];
var contactPoint = manifold.Points[j];
ref var constraintPoint = ref velocityConstraint.Points[j];
if (_warmStarting)
{
@@ -121,7 +120,7 @@ public abstract partial class SharedPhysicsSystem
constraintPoint.TangentMass = 0.0f;
constraintPoint.VelocityBias = 0.0f;
posPoints[j] = contactPoint.LocalPoint;
positionConstraint.LocalPoints[j] = contactPoint.LocalPoint;
}
}
}
@@ -221,11 +220,10 @@ 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 velPoints[j];
ref var vcp = ref velocityConstraint.Points[j];
vcp.RelativeVelocityA = points[j] - centerA;
vcp.RelativeVelocityB = points[j] - centerB;
@@ -258,8 +256,8 @@ public abstract partial class SharedPhysicsSystem
// If we have two points, then prepare the block solver.
if (velocityConstraint.PointCount == 2)
{
var vcp1 = velocityConstraint.Points._00;
var vcp2 = velocityConstraint.Points._01;
var vcp1 = velocityConstraint.Points[0];
var vcp2 = velocityConstraint.Points[1];
var rn1A = Vector2Helpers.Cross(vcp1.RelativeVelocityA, velocityConstraint.Normal);
var rn1B = Vector2Helpers.Cross(vcp1.RelativeVelocityB, velocityConstraint.Normal);
@@ -301,7 +299,6 @@ 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;
@@ -321,7 +318,7 @@ public abstract partial class SharedPhysicsSystem
for (var j = 0; j < pointCount; ++j)
{
var constraintPoint = velPoints[j];
var constraintPoint = velocityConstraint.Points[j];
var P = normal * constraintPoint.NormalImpulse + tangent * constraintPoint.TangentImpulse;
angVelocityA -= invIA * Vector2Helpers.Cross(constraintPoint.RelativeVelocityA, P);
linVelocityA -= P * invMassA;
@@ -389,13 +386,12 @@ 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 velPoints[j];
ref var velConstraintPoint = ref velocityConstraint.Points[j];
// Relative velocity at contact
var dv = vB + Vector2Helpers.Cross(wB, velConstraintPoint.RelativeVelocityB) - vA - Vector2Helpers.Cross(wA, velConstraintPoint.RelativeVelocityA);
@@ -423,7 +419,7 @@ public abstract partial class SharedPhysicsSystem
// Solve normal constraints
if (velocityConstraint.PointCount == 1)
{
ref var vcp = ref velocityConstraint.Points._00;
ref var vcp = ref velocityConstraint.Points[0];
// Relative velocity at contact
Vector2 dv = vB + Vector2Helpers.Cross(wB, vcp.RelativeVelocityB) - vA - Vector2Helpers.Cross(wA, vcp.RelativeVelocityA);
@@ -480,8 +476,8 @@ public abstract partial class SharedPhysicsSystem
// = A * x + b'
// b' = b - A * a;
ref var cp1 = ref velocityConstraint.Points._00;
ref var cp2 = ref velocityConstraint.Points._01;
ref var cp1 = ref velocityConstraint.Points[0];
ref var cp2 = ref velocityConstraint.Points[1];
Vector2 a = new Vector2(cp1.NormalImpulse, cp2.NormalImpulse);
DebugTools.Assert(a.X >= 0.0f && a.Y >= 0.0f);
@@ -647,16 +643,14 @@ public abstract partial class SharedPhysicsSystem
{
for (var i = 0; i < island.Contacts.Count; ++i)
{
var velocityConstraint = velocityConstraints[i];
ContactVelocityConstraint 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 manPoints[j];
point.NormalImpulse = velPoints[j].NormalImpulse;
point.TangentImpulse = velPoints[j].TangentImpulse;
ref var point = ref manifold.Points[j];
point.NormalImpulse = velocityConstraint.Points[j].NormalImpulse;
point.TangentImpulse = velocityConstraint.Points[j].TangentImpulse;
}
}
}
@@ -800,7 +794,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._00.LocalPoint);
Vector2 pointB = Physics.Transform.Mul(xfB, manifold.Points[0].LocalPoint);
if ((pointA - pointB).LengthSquared() > float.Epsilon * float.Epsilon)
{
@@ -818,11 +812,10 @@ 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, manPoints[i].LocalPoint);
Vector2 clipPoint = Physics.Transform.Mul(xfB, manifold.Points[i].LocalPoint);
Vector2 cA = clipPoint + normal * (radiusA - Vector2.Dot(clipPoint - planePoint, normal));
Vector2 cB = clipPoint - normal * radiusB;
points[i] = (cA + cB) * 0.5f;
@@ -834,11 +827,10 @@ 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, manPoints[i].LocalPoint);
Vector2 clipPoint = Physics.Transform.Mul(xfA, manifold.Points[i].LocalPoint);
Vector2 cB = clipPoint + normal * (radiusB - Vector2.Dot(clipPoint - planePoint, normal));
Vector2 cA = clipPoint - normal * radiusA;
points[i] = (cA + cB) * 0.5f;
@@ -871,7 +863,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._00);
Vector2 pointB = Physics.Transform.Mul(xfB, pc.LocalPoints[0]);
normal = pointB - pointA;
//FPE: Fix to handle zero normalization
@@ -885,11 +877,10 @@ 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, pcPoints[index]);
Vector2 clipPoint = Physics.Transform.Mul(xfB, pc.LocalPoints[index]);
separation = Vector2.Dot(clipPoint - planePoint, normal) - pc.RadiusA - pc.RadiusB;
point = clipPoint;
}
@@ -897,11 +888,10 @@ 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, pcPoints[index]);
Vector2 clipPoint = Physics.Transform.Mul(xfA, pc.LocalPoints[index]);
separation = Vector2.Dot(clipPoint - planePoint, normal) - pc.RadiusA - pc.RadiusB;
point = clipPoint;

Some files were not shown because too many files have changed in this diff Show More