mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb8fab82d7 | ||
|
|
4930c97819 | ||
|
|
7122cf90e3 | ||
|
|
84d0110477 | ||
|
|
6f28c396cf | ||
|
|
b631f408f2 | ||
|
|
34f4cf9452 | ||
|
|
6a4e4cf3b4 | ||
|
|
8aefa5c53e | ||
|
|
2cfc981aa3 | ||
|
|
4987c324d9 | ||
|
|
5450ddd0ba | ||
|
|
378a10678c | ||
|
|
2e0735b92f | ||
|
|
5756d15333 | ||
|
|
b6f74b8dea | ||
|
|
3800c5707e | ||
|
|
8f49785b4e | ||
|
|
f274de0f10 | ||
|
|
e128338f9d | ||
|
|
588c46273e | ||
|
|
919de8ce0e | ||
|
|
af27d2d872 | ||
|
|
45bb8740a0 | ||
|
|
4a24539629 | ||
|
|
7536c4ec68 | ||
|
|
9268c8629d | ||
|
|
0bc0cafe64 | ||
|
|
8891f3fa0a | ||
|
|
4f96c2d233 | ||
|
|
ab55d5b2f2 | ||
|
|
806c5b694b | ||
|
|
6898053dbd | ||
|
|
ae625ebad8 | ||
|
|
3c754a4f49 | ||
|
|
d84cb6327c | ||
|
|
4bfd92dbc5 | ||
|
|
c7d228c223 | ||
|
|
f244c94905 | ||
|
|
01cac6465b | ||
|
|
5a5f238d9a | ||
|
|
089224cd44 | ||
|
|
9f807f1ad2 | ||
|
|
4be95ea375 | ||
|
|
03010bf4be | ||
|
|
dacaa974d4 | ||
|
|
9f73e0398a | ||
|
|
ccc383b1bf | ||
|
|
ceb59402a1 | ||
|
|
5a6b29fcd2 | ||
|
|
6b87cd1e1c | ||
|
|
cf2d6a1dbf | ||
|
|
f8c838f425 | ||
|
|
7405904041 | ||
|
|
2eeebab275 | ||
|
|
2856bb3626 | ||
|
|
be0189748b | ||
|
|
4529a7569a | ||
|
|
2b16e4db96 | ||
|
|
64f2245194 | ||
|
|
1029047e2f | ||
|
|
45dc9ad80e | ||
|
|
54ad808eea | ||
|
|
37c75df6a2 | ||
|
|
e93c1fae61 | ||
|
|
cd0a35f542 | ||
|
|
80f2dc6dd3 |
@@ -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.1.1" />
|
||||
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.2.2" />
|
||||
<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" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
<!-- This file automatically reset by Tools/version.py -->
|
||||
|
||||
|
||||
126
RELEASE-NOTES.md
126
RELEASE-NOTES.md
@@ -54,6 +54,132 @@ END TEMPLATE-->
|
||||
*None yet*
|
||||
|
||||
|
||||
## 253.0.2
|
||||
|
||||
|
||||
## 253.0.1
|
||||
|
||||
|
||||
## 253.0.0
|
||||
|
||||
### New features
|
||||
|
||||
* Add a new `SerializationManager.PushComposition()` overload that takes in a single parent instead of an array of parents.
|
||||
* `BoundUserInterfaceMessageAttempt` once again gets raised as a broadcast event, in addition to being directed.
|
||||
* This effectively reverts the breaking part of the changes made in v252.0.0
|
||||
* Fix CreateDistanceJoint using an int instead of a float for minimum distance.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* Fix deferred component removal not setting the component's life stage to `ComponentLifeStage.Stopped` if the component has not yet been initialised.
|
||||
* Fix some `EntitySystem.Resolve()` overloads not respecting the optional `logMissing` argument.
|
||||
* Fix screen-space overlays not being useable without first initializing/starting entity manager & systems
|
||||
* ItemList is now significantly optimized. VV's `AddComponent` window in particular should be much faster.
|
||||
* Fix some more MapValidator fields.
|
||||
* Fix popup text overflowing the sides of the screen.
|
||||
* Improve location reporting for non-writeable datafields via analyzer.
|
||||
|
||||
### Other
|
||||
|
||||
* TestPoint now uses generics rather than IPhysShape directly.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -11,6 +11,7 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID.
|
||||
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
|
||||
cmd-parse-failure-enum = {$arg} is not a {$enum} Enum.
|
||||
cmd-parse-failure-grid = {$arg} is not a valid grid.
|
||||
cmd-parse-failure-cultureinfo = "{$arg}" is not valid CultureInfo.
|
||||
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
|
||||
cmd-parse-failure-session = There is no session with username: {$username}
|
||||
|
||||
@@ -428,11 +429,20 @@ 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 = Throws an exception
|
||||
cmd-fuck-help = Usage: fuck
|
||||
|
||||
cmd-showpos-desc = Enables debug drawing over all entity positions in the game.
|
||||
cmd-showpos-desc = Show the position of all entities on the screen.
|
||||
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>.
|
||||
|
||||
@@ -563,3 +573,8 @@ cmd-pvs-override-info-desc = Prints information about any PVS overrides associat
|
||||
cmd-pvs-override-info-empty = Entity {$nuid} has no PVS overrides.
|
||||
cmd-pvs-override-info-global = Entity {$nuid} has a global override.
|
||||
cmd-pvs-override-info-clients = Entity {$nuid} has a session override for {$clients}.
|
||||
|
||||
cmd-localization_set_culture-desc = Set DefaultCulture for the client LocalizationManager
|
||||
cmd-localization_set_culture-help = Usage: localization_set_culture <cultureName>
|
||||
cmd-localization_set_culture-culture-name = <cultureName>
|
||||
cmd-localization_set_culture-changed = Localization changed to { $code } ({ $nativeName } / { $englishName })
|
||||
|
||||
@@ -14,6 +14,10 @@ tile-spawn-window-title = Place Tiles
|
||||
|
||||
console-line-edit-placeholder = Command Here
|
||||
|
||||
## OutputPanel
|
||||
|
||||
output-panel-scroll-down-button-text = Scroll Down
|
||||
|
||||
## Common Used
|
||||
|
||||
window-erase-button-text = Erase Mode
|
||||
|
||||
@@ -87,4 +87,66 @@ public sealed class DataDefinitionAnalyzerTest
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldNoVVReadWriteRule).WithSpan(35, 17, 35, 50).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadOnlyFieldTest()
|
||||
{
|
||||
const string code = """
|
||||
using System;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField]
|
||||
public readonly int Bad;
|
||||
|
||||
[DataField]
|
||||
public int Good;
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,12): error RA0019: Data field Bad in data definition Foo is readonly
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldWritableRule).WithSpan(15, 12, 15, 20).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ReadOnlyPropertyTest()
|
||||
{
|
||||
const string code = """
|
||||
using System;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.Serialization.Manager.Attributes
|
||||
{
|
||||
public class DataFieldBaseAttribute : Attribute;
|
||||
public class DataFieldAttribute : DataFieldBaseAttribute;
|
||||
public sealed class DataDefinitionAttribute : Attribute;
|
||||
}
|
||||
|
||||
[DataDefinition]
|
||||
public sealed partial class Foo
|
||||
{
|
||||
[DataField]
|
||||
public int Bad { get; }
|
||||
|
||||
[DataField]
|
||||
public int Good { get; private set; }
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(15,20): error RA0020: Data field property Bad in data definition Foo does not have a setter
|
||||
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldPropertyWritableRule).WithSpan(15, 20, 15, 28).WithArguments("Bad", "Foo")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
189
Robust.Analyzers.Tests/ForbidLiteralAnalyzerTest.cs
Normal file
189
Robust.Analyzers.Tests/ForbidLiteralAnalyzerTest.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using NUnit.Framework;
|
||||
using VerifyCS =
|
||||
Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<Robust.Analyzers.ForbidLiteralAnalyzer, Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
|
||||
|
||||
|
||||
namespace Robust.Analyzers.Tests;
|
||||
|
||||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
|
||||
[TestFixture]
|
||||
public sealed class ForbidLiteralAnalyzerTest
|
||||
{
|
||||
private static Task Verifier(string code, params DiagnosticResult[] expected)
|
||||
{
|
||||
var test = new CSharpAnalyzerTest<ForbidLiteralAnalyzer, DefaultVerifier>()
|
||||
{
|
||||
TestState =
|
||||
{
|
||||
Sources = { code },
|
||||
},
|
||||
};
|
||||
|
||||
TestHelper.AddEmbeddedSources(
|
||||
test.TestState,
|
||||
"Robust.Shared.Analyzers.ForbidLiteralAttribute.cs"
|
||||
);
|
||||
|
||||
test.TestState.Sources.Add(("TestTypeDefs.cs", TestTypeDefs));
|
||||
|
||||
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
|
||||
test.TestState.ExpectedDiagnostics.AddRange(expected);
|
||||
|
||||
return test.RunAsync();
|
||||
}
|
||||
|
||||
private const string TestTypeDefs = """
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Analyzers;
|
||||
|
||||
public sealed class TestClass
|
||||
{
|
||||
public static void OneParameterForbidden([ForbidLiteral] string value) { }
|
||||
public static void TwoParametersFirstForbidden([ForbidLiteral] string first, string second) { }
|
||||
public static void TwoParametersBothForbidden([ForbidLiteral] string first, [ForbidLiteral] string second) { }
|
||||
public static void ListParameterForbidden([ForbidLiteral] List<string> values) { }
|
||||
public static void ParamsListParameterForbidden([ForbidLiteral] params List<string> values) { }
|
||||
}
|
||||
|
||||
public record struct StringWrapper(string value)
|
||||
{
|
||||
private readonly string _value = value;
|
||||
|
||||
public static implicit operator string(StringWrapper wrapper)
|
||||
{
|
||||
return wrapper._value;
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
[Test]
|
||||
public async Task TestOneParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.OneParameterForbidden(_constValue);
|
||||
TestClass.OneParameterForbidden(StaticValue);
|
||||
TestClass.OneParameterForbidden(WrappedValue);
|
||||
TestClass.OneParameterForbidden("baz");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(12,41): error RA0033: The "value" parameter of OneParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(12, 41, 12, 46).WithArguments("value", "OneParameterForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestTwoParametersFirstForbidden()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.TwoParametersFirstForbidden(_constValue, "whatever");
|
||||
TestClass.TwoParametersFirstForbidden(_constValue, _constValue);
|
||||
TestClass.TwoParametersFirstForbidden("foo", "whatever");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(9,47): error RA0033: The "first" parameter of TwoParametersFirstForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(9, 47, 9, 52).WithArguments("first", "TwoParametersFirstForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestTwoParametersBothForbidden()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.TwoParametersBothForbidden(_constValue, _constValue);
|
||||
TestClass.TwoParametersBothForbidden(_constValue, StaticValue);
|
||||
TestClass.TwoParametersBothForbidden(_constValue, "whatever");
|
||||
TestClass.TwoParametersBothForbidden("whatever", _constValue);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,59): error RA0033: The "second" parameter of TwoParametersBothForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 59, 10, 69).WithArguments("second", "TwoParametersBothForbidden"),
|
||||
// /0/Test0.cs(11,46): error RA0033: The "first" parameter of TwoParametersBothForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(11, 46, 11, 56).WithArguments("first", "TwoParametersBothForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestListParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.ListParameterForbidden([_constValue, StaticValue, WrappedValue]);
|
||||
TestClass.ListParameterForbidden(["foo", _constValue, "bar"]);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,43): warning RA0033: The "values" parameter of ListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 43, 10, 48).WithArguments("values", "ListParameterForbidden"),
|
||||
// /0/Test0.cs(10,63): warning RA0033: The "values" parameter of ListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 63, 10, 68).WithArguments("values", "ListParameterForbidden")
|
||||
);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestParamsListParameter()
|
||||
{
|
||||
const string code = """
|
||||
public sealed class Tester
|
||||
{
|
||||
private const string _constValue = "foo";
|
||||
private static readonly string StaticValue = "bar";
|
||||
private static readonly StringWrapper WrappedValue = new("biz");
|
||||
|
||||
public void Test()
|
||||
{
|
||||
TestClass.ParamsListParameterForbidden(_constValue, StaticValue, WrappedValue);
|
||||
TestClass.ParamsListParameterForbidden("foo", _constValue, "bar");
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
await Verifier(code,
|
||||
// /0/Test0.cs(10,48): warning RA0033: The "values" parameter of ParamsListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 48, 10, 53).WithArguments("values", "ParamsListParameterForbidden"),
|
||||
// /0/Test0.cs(10,68): warning RA0033: The "values" parameter of ParamsListParameterForbidden forbids literal values
|
||||
VerifyCS.Diagnostic().WithSpan(10, 68, 10, 73).WithArguments("values", "ParamsListParameterForbidden")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\MustCallBaseAttribute.cs" LogicalName="Robust.Shared.IoC.MustCallBaseAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferNonGenericVariantForAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\PreferOtherTypeAttribute.cs" LogicalName="Robust.Shared.Analyzers.PreferOtherTypeAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\Analyzers\ForbidLiteralAttribute.cs" LogicalName="Robust.Shared.Analyzers.ForbidLiteralAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
|
||||
<EmbeddedResource Include="..\Robust.Shared\GameObjects\EventBusAttributes.cs" LogicalName="Robust.Shared.GameObjects.EventBusAttributes.cs" LinkBase="Implementations" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to mark any type containing a nested data definition as partial."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
public static readonly DiagnosticDescriptor DataFieldWritableRule = new(
|
||||
Diagnostics.IdDataFieldWritable,
|
||||
"Data field must not be readonly",
|
||||
"Data field {0} in data definition {1} is readonly",
|
||||
@@ -51,7 +51,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
"Make sure to remove the readonly modifier."
|
||||
);
|
||||
|
||||
private static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
public static readonly DiagnosticDescriptor DataFieldPropertyWritableRule = new(
|
||||
Diagnostics.IdDataFieldPropertyWritable,
|
||||
"Data field property must have a setter",
|
||||
"Data field property {0} in data definition {1} does not have a setter",
|
||||
@@ -149,7 +149,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (IsReadOnlyDataField(type, fieldSymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, context.Node.GetLocation(), fieldSymbol.Name, type.Name));
|
||||
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(fieldSymbol))
|
||||
@@ -185,7 +186,8 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
|
||||
if (IsReadOnlyDataField(type, propertySymbol))
|
||||
{
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, context.Node.GetLocation(), propertySymbol.Name, type.Name));
|
||||
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
|
||||
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
|
||||
}
|
||||
|
||||
if (HasRedundantTag(propertySymbol))
|
||||
@@ -285,6 +287,20 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryGetModifierLocation(MemberDeclarationSyntax syntax, SyntaxKind modifierKind, out Location location)
|
||||
{
|
||||
foreach (var modifier in syntax.Modifiers)
|
||||
{
|
||||
if (modifier.IsKind(modifierKind))
|
||||
{
|
||||
location = modifier.GetLocation();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
location = syntax.GetLocation();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsReadOnlyMember(ITypeSymbol type, ISymbol member)
|
||||
{
|
||||
if (member is IFieldSymbol field)
|
||||
|
||||
101
Robust.Analyzers/ForbidLiteralAnalyzer.cs
Normal file
101
Robust.Analyzers/ForbidLiteralAnalyzer.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Diagnostics;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using Robust.Roslyn.Shared;
|
||||
|
||||
namespace Robust.Analyzers;
|
||||
|
||||
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
||||
public sealed class ForbidLiteralAnalyzer : DiagnosticAnalyzer
|
||||
{
|
||||
private const string ForbidLiteralType = "Robust.Shared.Analyzers.ForbidLiteralAttribute";
|
||||
|
||||
public static DiagnosticDescriptor ForbidLiteralRule = new(
|
||||
Diagnostics.IdForbidLiteral,
|
||||
"Parameter forbids literal values",
|
||||
"The {0} parameter of {1} forbids literal values",
|
||||
"Usage",
|
||||
DiagnosticSeverity.Warning,
|
||||
true,
|
||||
"Pass in a validated wrapper type like ProtoId, or a const or static value."
|
||||
);
|
||||
|
||||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [ForbidLiteralRule];
|
||||
|
||||
public override void Initialize(AnalysisContext context)
|
||||
{
|
||||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
|
||||
context.EnableConcurrentExecution();
|
||||
context.RegisterOperationAction(AnalyzeOperation, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
private void AnalyzeOperation(OperationAnalysisContext context)
|
||||
{
|
||||
if (context.Operation is not IInvocationOperation invocationOperation)
|
||||
return;
|
||||
|
||||
// Check each parameter of the method invocation
|
||||
foreach (var argumentOperation in invocationOperation.Arguments)
|
||||
{
|
||||
// Check for our attribute on the parameter
|
||||
if (!AttributeHelper.HasAttribute(argumentOperation.Parameter, ForbidLiteralType, out _))
|
||||
continue;
|
||||
|
||||
// Handle parameters using the params keyword
|
||||
if (argumentOperation.Syntax is InvocationExpressionSyntax subExpressionSyntax)
|
||||
{
|
||||
// Check each param value
|
||||
foreach (var subArgument in subExpressionSyntax.ArgumentList.Arguments)
|
||||
{
|
||||
CheckArgumentSyntax(context, argumentOperation, subArgument);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not params, so just check the single parameter
|
||||
if (argumentOperation.Syntax is not ArgumentSyntax argumentSyntax)
|
||||
continue;
|
||||
|
||||
CheckArgumentSyntax(context, argumentOperation, argumentSyntax);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckArgumentSyntax(OperationAnalysisContext context, IArgumentOperation operation, ArgumentSyntax argumentSyntax)
|
||||
{
|
||||
// Handle collection types
|
||||
if (argumentSyntax.Expression is CollectionExpressionSyntax collectionExpressionSyntax)
|
||||
{
|
||||
// Check each value of the collection
|
||||
foreach (var elementSyntax in collectionExpressionSyntax.Elements)
|
||||
{
|
||||
if (elementSyntax is not ExpressionElementSyntax expressionSyntax)
|
||||
continue;
|
||||
|
||||
// Check if a literal was passed in
|
||||
if (expressionSyntax.Expression is not LiteralExpressionSyntax)
|
||||
continue;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(ForbidLiteralRule,
|
||||
expressionSyntax.GetLocation(),
|
||||
operation.Parameter.Name,
|
||||
(context.Operation as IInvocationOperation).TargetMethod.Name
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a collection, just a single value to check
|
||||
// Check if it's a literal
|
||||
if (argumentSyntax.Expression is not LiteralExpressionSyntax)
|
||||
return;
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(ForbidLiteralRule,
|
||||
argumentSyntax.GetLocation(),
|
||||
operation.Parameter.Name,
|
||||
(context.Operation as IInvocationOperation).TargetMethod.Name
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ using Xilium.CefGlue;
|
||||
|
||||
namespace Robust.Client.WebView.Cef
|
||||
{
|
||||
public static class Program
|
||||
internal 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)
|
||||
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -24,6 +25,7 @@ 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!;
|
||||
@@ -61,7 +63,10 @@ namespace Robust.Client.WebView.Cef
|
||||
|
||||
var cachePath = "";
|
||||
if (_resourceManager.UserData is WritableDirProvider userData)
|
||||
cachePath = userData.GetFullPath(new ResPath("/cef_cache"));
|
||||
{
|
||||
var rootDir = UserDataDir.GetRootUserDataDir(_gameController);
|
||||
cachePath = Path.Combine(rootDir, "cef_cache", "0");
|
||||
}
|
||||
|
||||
var settings = new CefSettings()
|
||||
{
|
||||
|
||||
@@ -173,29 +173,51 @@ namespace Robust.Client.Console.Commands
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowPositionsCommand : LocalizedCommands
|
||||
internal sealed class ShowPositionsCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showpos";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
|
||||
mgr.DebugPositions = !mgr.DebugPositions;
|
||||
_debugDrawing.DebugPositions = !_debugDrawing.DebugPositions;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShowRotationsCommand : LocalizedCommands
|
||||
internal sealed class ShowRotationsCommand : LocalizedEntityCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
[Dependency] private readonly DebugDrawingSystem _debugDrawing = default!;
|
||||
|
||||
public override string Command => "showrot";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
var mgr = _entitySystems.GetEntitySystem<DebugDrawingSystem>();
|
||||
mgr.DebugRotations = !mgr.DebugRotations;
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
67
Robust.Client/Console/Commands/LocalizationCommands.cs
Normal file
67
Robust.Client/Console/Commands/LocalizationCommands.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Robust.Client.Console.Commands;
|
||||
|
||||
[UsedImplicitly]
|
||||
internal sealed class LocalizationSetCulture : LocalizedCommands
|
||||
{
|
||||
private const string Name = "localization_set_culture";
|
||||
private const int ArgumentCount = 1;
|
||||
|
||||
public override string Command => Name;
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
if (args.Length != ArgumentCount)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-invalid-arg-number-error"));
|
||||
return;
|
||||
}
|
||||
|
||||
CultureInfo culture;
|
||||
try
|
||||
{
|
||||
culture = CultureInfo.GetCultureInfo(args[0], predefinedOnly: false);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-cultureinfo", ("arg", args[0])));
|
||||
return;
|
||||
}
|
||||
|
||||
LocalizationManager.SetCulture(culture);
|
||||
shell.WriteLine(LocalizationManager.GetString("cmd-localization_set_culture-changed",
|
||||
("code", culture.Name),
|
||||
("nativeName", culture.NativeName),
|
||||
("englishName", culture.EnglishName)));
|
||||
}
|
||||
|
||||
public override CompletionResult GetCompletion(IConsoleShell shell, string[] args)
|
||||
{
|
||||
return args.Length switch
|
||||
{
|
||||
1 => CompletionResult.FromHintOptions(GetCultureNames(),
|
||||
LocalizationManager.GetString("cmd-localization_set_culture-culture-name")),
|
||||
_ => CompletionResult.Empty
|
||||
};
|
||||
}
|
||||
|
||||
private static HashSet<string> GetCultureNames()
|
||||
{
|
||||
var cultureInfos = CultureInfo.GetCultures(CultureTypes.AllCultures)
|
||||
.Where(x => !string.IsNullOrEmpty(x.Name))
|
||||
.ToArray();
|
||||
|
||||
var allNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
allNames.UnionWith(cultureInfos.Select(x => x.TwoLetterISOLanguageName));
|
||||
allNames.UnionWith(cultureInfos.Select(x => x.Name));
|
||||
|
||||
return allNames;
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,15 @@ using Robust.Shared.IoC;
|
||||
|
||||
namespace Robust.Client.Console.Commands
|
||||
{
|
||||
public sealed class VelocitiesCommand : LocalizedCommands
|
||||
public sealed class ShowPlayerVelocityCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystems = default!;
|
||||
|
||||
public override string Command => "showvelocities";
|
||||
public override string Command => "showplayervelocity";
|
||||
|
||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
||||
{
|
||||
_entitySystems.GetEntitySystem<VelocityDebugSystem>().Enabled ^= true;
|
||||
_entitySystems.GetEntitySystem<ShowPlayerVelocityDebugSystem>().Enabled ^= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +1,221 @@
|
||||
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
|
||||
namespace Robust.Client.Debugging;
|
||||
|
||||
/// <summary>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// </summary>
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
{
|
||||
[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>
|
||||
/// A collection of visual debug overlays for the client game.
|
||||
/// Toggles the visual overlay of the local origin for each entity on screen.
|
||||
/// </summary>
|
||||
public sealed class DebugDrawingSystem : EntitySystem
|
||||
public bool DebugPositions
|
||||
{
|
||||
[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
|
||||
get => _debugPositions;
|
||||
set
|
||||
{
|
||||
get => _debugPositions;
|
||||
set
|
||||
if (value == DebugPositions)
|
||||
{
|
||||
if (value == DebugPositions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_debugPositions = value;
|
||||
_debugPositions = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, EntityManager, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
|
||||
}
|
||||
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityPositionOverlay(_lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local rotation.
|
||||
/// </summary>
|
||||
public bool DebugRotations
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the rotation for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugRotations
|
||||
{
|
||||
get => _debugRotations;
|
||||
set
|
||||
{
|
||||
get => _debugRotations;
|
||||
set
|
||||
if (value == DebugRotations)
|
||||
{
|
||||
if (value == DebugRotations)
|
||||
{
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_debugRotations = value;
|
||||
_debugRotations = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, EntityManager));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
|
||||
}
|
||||
if (value && !_overlayManager.HasOverlay<EntityRotationOverlay>())
|
||||
{
|
||||
_overlayManager.AddOverlay(new EntityRotationOverlay(_lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityRotationOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityPositionOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the local velocity for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugVelocities
|
||||
{
|
||||
get => _debugVelocities;
|
||||
set
|
||||
{
|
||||
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)
|
||||
if (value == DebugVelocities)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
_transform = transform;
|
||||
return;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
_debugVelocities = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityVelocityOverlay>())
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
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);
|
||||
}
|
||||
_overlayManager.AddOverlay(new EntityVelocityOverlay(EntityManager, _lookup, _transform));
|
||||
}
|
||||
else
|
||||
{
|
||||
_overlayManager.RemoveOverlay<EntityVelocityOverlay>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class EntityRotationOverlay : Overlay
|
||||
/// <summary>
|
||||
/// Toggles the visual overlay of the angular velocity for each entity on screen.
|
||||
/// </summary>
|
||||
public bool DebugAngularVelocities
|
||||
{
|
||||
get => _debugAngularVelocities;
|
||||
set
|
||||
{
|
||||
private readonly EntityLookupSystem _lookup;
|
||||
private readonly IEntityManager _entityManager;
|
||||
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
|
||||
public EntityRotationOverlay(EntityLookupSystem lookup, IEntityManager entityManager)
|
||||
if (value == DebugAngularVelocities)
|
||||
{
|
||||
_lookup = lookup;
|
||||
_entityManager = entityManager;
|
||||
return;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
_debugAngularVelocities = value;
|
||||
|
||||
if (value && !_overlayManager.HasOverlay<EntityAngularVelocityOverlay>())
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
|
||||
_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;
|
||||
|
||||
foreach (var entity in _lookup.GetEntitiesIntersecting(args.MapId, args.WorldBounds))
|
||||
{
|
||||
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
const float stubLength = 0.25f;
|
||||
|
||||
var drawLine = worldRotation.RotateVec(-Vector2.UnitY);
|
||||
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
|
||||
|
||||
worldHandle.DrawLine(center, center + drawLine * stubLength, Color.Red);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -413,8 +413,9 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
|
||||
var body = bodyEnt.Comp;
|
||||
var meta = _entityManager.GetComponent<MetaDataComponent>(bodyEnt);
|
||||
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner}");
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Ent: {bodyEnt.Owner} ({meta.EntityName})");
|
||||
row++;
|
||||
screenHandle.DrawString(_font, drawPos + new Vector2(0, row * lineHeight), $"Layer: {Convert.ToString(body.CollisionLayer, 2)}");
|
||||
row++;
|
||||
|
||||
@@ -384,7 +384,7 @@ namespace Robust.Client
|
||||
|
||||
_prof.Initialize();
|
||||
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
|
||||
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)
|
||||
|
||||
@@ -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 ReadOnlySpan<string> fields)
|
||||
public override void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
|
||||
{
|
||||
// TODO Prediction
|
||||
// does the client actually need to dirty the field?
|
||||
|
||||
@@ -30,6 +30,7 @@ using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
|
||||
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
|
||||
using Direction = Robust.Shared.Maths.Direction;
|
||||
using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
using SysVec4 = System.Numerics.Vector4;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
{
|
||||
@@ -753,12 +754,20 @@ namespace Robust.Client.GameObjects
|
||||
if (layerDatum.Shader == string.Empty)
|
||||
{
|
||||
layer.ShaderPrototype = null;
|
||||
layer.UnShaded = false;
|
||||
layer.Shader = null;
|
||||
}
|
||||
else if (layerDatum.Shader == SpriteSystem.UnshadedId.Id)
|
||||
{
|
||||
layer.ShaderPrototype = SpriteSystem.UnshadedId;
|
||||
layer.UnShaded = true;
|
||||
layer.Shader = null;
|
||||
}
|
||||
else if (prototypes.TryIndex<ShaderPrototype>(layerDatum.Shader, out var prototype))
|
||||
{
|
||||
layer.ShaderPrototype = layerDatum.Shader;
|
||||
layer.Shader = prototype.Instance();
|
||||
layer.UnShaded = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -835,11 +844,28 @@ namespace Robust.Client.GameObjects
|
||||
if (!TryGetLayer(layer, out var theLayer, true))
|
||||
return;
|
||||
|
||||
if (shader == null)
|
||||
{
|
||||
theLayer.UnShaded = false;
|
||||
theLayer.Shader = null;
|
||||
theLayer.ShaderPrototype = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (prototype == SpriteSystem.UnshadedId.Id)
|
||||
{
|
||||
theLayer.UnShaded = true;
|
||||
theLayer.ShaderPrototype = SpriteSystem.UnshadedId;
|
||||
theLayer.Shader = null;
|
||||
return;
|
||||
}
|
||||
|
||||
theLayer.UnShaded = false;
|
||||
theLayer.Shader = shader;
|
||||
theLayer.ShaderPrototype = prototype;
|
||||
}
|
||||
|
||||
public void LayerSetShader(object layerKey, ShaderInstance shader, string? prototype = null)
|
||||
public void LayerSetShader(object layerKey, ShaderInstance? shader, string? prototype = null)
|
||||
{
|
||||
if (!LayerMapTryGet(layerKey, out var layer, true))
|
||||
return;
|
||||
@@ -1493,10 +1519,18 @@ namespace Robust.Client.GameObjects
|
||||
{
|
||||
[ViewVariables] private readonly SpriteComponent _parent;
|
||||
|
||||
[ViewVariables] public string? ShaderPrototype;
|
||||
[ViewVariables] public ProtoId<ShaderPrototype>? ShaderPrototype;
|
||||
[ViewVariables] public ShaderInstance? Shader;
|
||||
[ViewVariables] public Texture? Texture;
|
||||
|
||||
/// <summary>
|
||||
/// If true, then this layer is drawn without lighting applied.
|
||||
/// Unshaded layers are given special treatment and don't just use the unshaded-shader to avoid having to
|
||||
/// unnecessarily swap out the light texture. This helps the number of batches that need to be sent to the
|
||||
/// GPU while drawing sprites.
|
||||
/// </summary>
|
||||
[ViewVariables] internal bool UnShaded;
|
||||
|
||||
private RSI? _rsi;
|
||||
[ViewVariables] public RSI? RSI
|
||||
{
|
||||
@@ -1663,6 +1697,7 @@ namespace Robust.Client.GameObjects
|
||||
if (toClone.Shader != null)
|
||||
{
|
||||
Shader = toClone.Shader.Mutable ? toClone.Shader.Duplicate() : toClone.Shader;
|
||||
UnShaded = toClone.UnShaded;
|
||||
ShaderPrototype = toClone.ShaderPrototype;
|
||||
}
|
||||
Texture = toClone.Texture;
|
||||
@@ -2078,6 +2113,20 @@ namespace Robust.Client.GameObjects
|
||||
drawingHandle.UseShader(Shader);
|
||||
|
||||
var layerColor = _parent.color * Color;
|
||||
|
||||
DebugTools.Assert(layerColor is {R: >= 0, G: >= 0, B: >= 0, A: >= 0}, "Negative colour modulation");
|
||||
|
||||
if (UnShaded)
|
||||
{
|
||||
DebugTools.AssertNull(Shader);
|
||||
|
||||
// Negative modulation values are used to disable light shading in the default shader.
|
||||
// Specifically we set colour = -1 - colour
|
||||
// This ensures that non-negative values become negative & is trivially invertible.
|
||||
// Alternatively we could just clamp the colour to [0,1] and subtract a constant.
|
||||
layerColor = new(new SysVec4(-1) - layerColor.RGBA);
|
||||
}
|
||||
|
||||
var textureSize = texture.Size / (float)EyeManager.PixelsPerMeter;
|
||||
var quad = Box2.FromDimensions(textureSize/-2, textureSize);
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Robust.Client.GameObjects
|
||||
foreach (var key in remie)
|
||||
{
|
||||
component.PlayingAnimations.Remove(key);
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = uid, Key = key, Finished = true};
|
||||
var completedEvent = new AnimationCompletedEvent(uid, component, key, true);
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, completedEvent, true);
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Robust.Client.GameObjects
|
||||
return;
|
||||
}
|
||||
|
||||
var completedEvent = new AnimationCompletedEvent {Uid = entity.Owner, Key = key, Finished = false};
|
||||
var completedEvent = new AnimationCompletedEvent(entity.Owner, entity.Comp, key, false);
|
||||
EntityManager.EventBus.RaiseLocalEvent(entity.Owner, completedEvent, true);
|
||||
}
|
||||
|
||||
@@ -202,13 +202,33 @@ namespace Robust.Client.GameObjects
|
||||
/// </summary>
|
||||
public sealed class AnimationCompletedEvent : EntityEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The entity associated with the event.
|
||||
/// </summary>
|
||||
public EntityUid Uid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The animation player component associated with the entity this event was raised on.
|
||||
/// </summary>
|
||||
public AnimationPlayerComponent AnimationPlayer { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The key associated with the animation that was completed.
|
||||
/// </summary>
|
||||
public string Key { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the animation finished by getting to its natural end.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(Robust.Client.GameObjects.AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// If false, it was removed prematurely via <see cref="AnimationPlayerSystem.Stop(EntityUid,AnimationPlayerComponent,string)"/> or similar overloads.
|
||||
/// </summary>
|
||||
public bool Finished { get; init; }
|
||||
|
||||
public AnimationCompletedEvent(EntityUid uid, AnimationPlayerComponent animationPlayer, string key, bool finished = true)
|
||||
{
|
||||
Uid = uid;
|
||||
AnimationPlayer = animationPlayer;
|
||||
Key = key;
|
||||
Finished = finished;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,6 @@ namespace Robust.Client.GameObjects;
|
||||
|
||||
public sealed class MapSystem : SharedMapSystem
|
||||
{
|
||||
[Dependency] private readonly IOverlayManager _overlayManager = default!;
|
||||
[Dependency] private readonly IResourceCache _resource = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
|
||||
protected override MapId GetNextMapId()
|
||||
{
|
||||
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
|
||||
@@ -23,16 +19,4 @@ public sealed class MapSystem : SharedMapSystem
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
_overlayManager.AddOverlay(new TileEdgeOverlay(EntityManager, _resource, _tileDefinitionManager));
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ namespace Robust.Client.GameObjects
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _xforms = default!;
|
||||
|
||||
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
|
||||
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,54 +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.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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,7 +384,6 @@ namespace Robust.Client.GameStates
|
||||
_processor.UpdateFullRep(curState);
|
||||
}
|
||||
|
||||
IEnumerable<NetEntity> createdEntities;
|
||||
using (_prof.Group("ApplyGameState"))
|
||||
{
|
||||
if (_timing.LastProcessedTick < targetProcessedTick && nextState != null)
|
||||
@@ -699,8 +698,9 @@ namespace Robust.Client.GameStates
|
||||
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw new KeyNotFoundException();
|
||||
#endif
|
||||
#else
|
||||
continue;
|
||||
#endif
|
||||
}
|
||||
|
||||
var compData = _compDataPool.Get();
|
||||
@@ -961,8 +961,9 @@ namespace Robust.Client.GameStates
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (data.Created)
|
||||
@@ -980,8 +981,9 @@ namespace Robust.Client.GameStates
|
||||
RequestFullState();
|
||||
#if !EXCEPTION_TOLERANCE
|
||||
throw;
|
||||
#endif
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
private const string UniModelMatrix = "modelMatrix";
|
||||
private const string UniTexturePixelSize = "TEXTURE_PIXEL_SIZE";
|
||||
private const string UniMainTexture = "TEXTURE";
|
||||
private const string UniLightTexture = "lightMap";
|
||||
private const string UniLightTexture = "lightMap"; // TODO CLYDE consistent shader variable naming
|
||||
private const string UniProjViewMatrices = "projectionViewMatrices";
|
||||
private const string UniUniformConstants = "uniformConstants";
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenToolkit.Graphics.OpenGL4;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Graphics;
|
||||
@@ -22,6 +23,9 @@ namespace Robust.Client.Graphics.Clyde
|
||||
/// </summary>
|
||||
private HashSet<Type> _erroredGridOverlays = new();
|
||||
|
||||
private Vertex2D[]? _chunkMeshBuilderVertexBuffer;
|
||||
private ushort[]? _chunkMeshBuilderIndexBuffer;
|
||||
|
||||
private int _verticesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * 4;
|
||||
private int _indicesPerChunk(MapChunk chunk) => chunk.ChunkSize * chunk.ChunkSize * GetQuadBatchIndexCount();
|
||||
|
||||
@@ -63,29 +67,78 @@ namespace Robust.Client.Graphics.Clyde
|
||||
gridProgram.SetUniform(UniIModUV, new Vector4(0, 0, 1, 1));
|
||||
}
|
||||
|
||||
var transform = _entityManager.GetComponent<TransformComponent>(mapGrid);
|
||||
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(transform));
|
||||
gridProgram.SetUniform(UniIModelMatrix, _transformSystem.GetWorldMatrix(mapGrid));
|
||||
var enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Handle base texture updates.
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
DebugTools.Assert(chunk.FilledTiles > 0);
|
||||
if (!data.TryGetValue(chunk.Indices, out MapChunkData? datum))
|
||||
data[chunk.Indices] = datum = _initChunkBuffers(mapGrid, chunk);
|
||||
var datum = EnsureChunkInitialized(data, chunk, mapGrid);
|
||||
|
||||
if (datum.Dirty)
|
||||
_updateChunkMesh(mapGrid, chunk, datum);
|
||||
|
||||
DebugTools.Assert(datum.TileCount > 0);
|
||||
if (datum.TileCount == 0)
|
||||
if (!datum.Dirty)
|
||||
continue;
|
||||
|
||||
BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
_updateChunkMesh(mapGrid, chunk, datum);
|
||||
|
||||
// Dirty edge tiles for next step.
|
||||
datum.EdgeDirty = true;
|
||||
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
var neighbor = chunk.Indices + new Vector2i(x, y);
|
||||
|
||||
if (!mapGrid.Comp.Chunks.TryGetValue(neighbor, out var neighborChunk))
|
||||
continue;
|
||||
|
||||
var neighborDatum = EnsureChunkInitialized(data, neighborChunk, mapGrid);
|
||||
neighborDatum.EdgeDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Handle edge sprites.
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var datum = data[chunk.Indices];
|
||||
|
||||
if (!datum.EdgeDirty)
|
||||
continue;
|
||||
|
||||
_updateChunkEdges(mapGrid, chunk, datum);
|
||||
}
|
||||
|
||||
enumerator = mapSystem.GetMapChunks(mapGrid.Owner, mapGrid.Comp, worldBounds);
|
||||
|
||||
// Draw chunks
|
||||
while (enumerator.MoveNext(out var chunk))
|
||||
{
|
||||
var datum = data[chunk.Indices];
|
||||
DebugTools.Assert(datum.TileCount > 0);
|
||||
if (datum.TileCount > 0)
|
||||
{
|
||||
BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
if (datum.EdgeCount > 0)
|
||||
{
|
||||
BindVertexArray(datum.EdgeVAO);
|
||||
CheckGlError();
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.EdgeCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
_debugStats.LastGLDrawCalls += 1;
|
||||
GL.DrawElements(GetQuadGLPrimitiveType(), datum.TileCount * GetQuadBatchIndexCount(), DrawElementsType.UnsignedShort, 0);
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
requiresFlush = false;
|
||||
@@ -117,6 +170,17 @@ namespace Robust.Client.Graphics.Clyde
|
||||
CullEmptyChunks();
|
||||
}
|
||||
|
||||
private MapChunkData EnsureChunkInitialized(Dictionary<Vector2i, MapChunkData> data, MapChunk chunk, Entity<MapGridComponent> mapGrid)
|
||||
{
|
||||
if (!data.TryGetValue(chunk.Indices, out var datum))
|
||||
{
|
||||
data[chunk.Indices] = datum = new MapChunkData();
|
||||
_initChunkBuffers(mapGrid, chunk, datum);
|
||||
}
|
||||
|
||||
return datum;
|
||||
}
|
||||
|
||||
private void CullEmptyChunks()
|
||||
{
|
||||
foreach (var (grid, chunks) in _mapChunkData)
|
||||
@@ -138,66 +202,141 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void _updateChunkMesh(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
Span<ushort> indexBuffer = stackalloc ushort[_indicesPerChunk(chunk)];
|
||||
Span<Vertex2D> vertexBuffer = stackalloc Vertex2D[_verticesPerChunk(chunk)];
|
||||
Span<ushort> indexBuffer = EnsureSize(ref _chunkMeshBuilderIndexBuffer, _indicesPerChunk(chunk));
|
||||
Span<Vertex2D> vertexBuffer = EnsureSize(ref _chunkMeshBuilderVertexBuffer, _verticesPerChunk(chunk));
|
||||
|
||||
var i = 0;
|
||||
var cSz = grid.Comp.ChunkSize;
|
||||
var cScaled = chunk.Indices * cSz;
|
||||
for (ushort x = 0; x < cSz; x++)
|
||||
var chunkSize = grid.Comp.ChunkSize;
|
||||
var chunkOriginScaled = chunk.Indices * chunkSize;
|
||||
|
||||
for (ushort x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < cSz; y++)
|
||||
for (ushort y = 0; y < chunkSize; y++)
|
||||
{
|
||||
var gridX = x + chunkOriginScaled.X;
|
||||
var gridY = y + chunkOriginScaled.Y;
|
||||
var tile = chunk.GetTile(x, y);
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
// Tile render
|
||||
if (x != chunkSize && y != chunkSize)
|
||||
{
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
// ReSharper disable once IntVariableOverflowInUncheckedContext
|
||||
if (tile.IsEmpty)
|
||||
continue;
|
||||
|
||||
var gx = x + cScaled.X;
|
||||
var gy = y + cScaled.Y;
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(tile);
|
||||
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gx, gy, region.Left, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gx + 1, gy, region.Right, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gx + 1, gy + 1, region.Right, region.Top, Color.White);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gx, gy + 1, region.Left, region.Top, Color.White);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
i += 1;
|
||||
Box2 region;
|
||||
if (regionMaybe == null || regionMaybe.Length <= tile.Variant)
|
||||
{
|
||||
region = _tileDefinitionManager.ErrorTileRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
region = regionMaybe[tile.Variant];
|
||||
}
|
||||
|
||||
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var indexSlice = indexBuffer[..(i * GetQuadBatchIndexCount())];
|
||||
var vertSlice = vertexBuffer[..(i * 4)];
|
||||
|
||||
GL.BindVertexArray(datum.VAO);
|
||||
CheckGlError();
|
||||
datum.EBO.Use();
|
||||
datum.VBO.Use();
|
||||
datum.EBO.Reallocate(indexBuffer[..(i * GetQuadBatchIndexCount())]);
|
||||
datum.VBO.Reallocate(vertexBuffer[..(i * 4)]);
|
||||
datum.Dirty = false;
|
||||
datum.EBO.Reallocate(indexSlice);
|
||||
datum.VBO.Reallocate(vertSlice);
|
||||
|
||||
datum.TileCount = i;
|
||||
datum.Dirty = false;
|
||||
}
|
||||
|
||||
private unsafe MapChunkData _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk)
|
||||
private void _updateChunkEdges(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
// Need a buffer that can potentially store all neighbor tiles
|
||||
Span<ushort> indexBuffer = EnsureSize(ref _chunkMeshBuilderIndexBuffer, _indicesPerChunk(chunk) * 8);
|
||||
Span<Vertex2D> vertexBuffer = EnsureSize(ref _chunkMeshBuilderVertexBuffer, _verticesPerChunk(chunk) * 8);
|
||||
|
||||
var i = 0;
|
||||
var chunkSize = grid.Comp.ChunkSize;
|
||||
var chunkOriginScaled = chunk.Indices * chunkSize;
|
||||
var maps = _entityManager.System<SharedMapSystem>();
|
||||
|
||||
for (ushort x = 0; x < chunkSize; x++)
|
||||
{
|
||||
for (ushort y = 0; y < chunkSize; y++)
|
||||
{
|
||||
var gridX = x + chunkOriginScaled.X;
|
||||
var gridY = y + chunkOriginScaled.Y;
|
||||
var tile = chunk.GetTile(x, y);
|
||||
var tileDef = _tileDefinitionManager[tile.TypeId];
|
||||
|
||||
// Edge render
|
||||
for (var nx = -1; nx <= 1; nx++)
|
||||
{
|
||||
for (var ny = -1; ny <= 1; ny++)
|
||||
{
|
||||
if (nx == 0 && ny == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(gridX + nx, gridY + ny);
|
||||
if (!maps.TryGetTile(grid.Comp, neighborIndices, out var neighborTile))
|
||||
continue;
|
||||
|
||||
var neighborDef = _tileDefinitionManager[neighborTile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tile.TypeId == neighborTile.TypeId || neighborDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// If neighbor is a lower priority then us then don't draw on our tile.
|
||||
if (neighborDef.EdgeSpritePriority < tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(nx, ny).AsDirection().GetOpposite();
|
||||
var regionMaybe = _tileDefinitionManager.TileAtlasRegion(neighborTile.TypeId, direction);
|
||||
|
||||
if (regionMaybe == null)
|
||||
continue;
|
||||
|
||||
var region = regionMaybe[0];
|
||||
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region);
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We don't save the edge buffers back because we might need to re-use it if a neighbor chunk updates.
|
||||
var indexSlice = indexBuffer[..(i * GetQuadBatchIndexCount())];
|
||||
var vertSlice = vertexBuffer[..(i * 4)];
|
||||
|
||||
GL.BindVertexArray(datum.EdgeVAO);
|
||||
CheckGlError();
|
||||
datum.EdgeEBO.Use();
|
||||
datum.EdgeVBO.Use();
|
||||
datum.EdgeEBO.Reallocate(indexSlice);
|
||||
datum.EdgeVBO.Reallocate(vertSlice);
|
||||
|
||||
datum.EdgeCount = i;
|
||||
datum.EdgeDirty = false;
|
||||
}
|
||||
|
||||
private unsafe void _initChunkBuffers(Entity<MapGridComponent> grid, MapChunk chunk, MapChunkData datum)
|
||||
{
|
||||
var vboSize = _verticesPerChunk(chunk) * sizeof(Vertex2D);
|
||||
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
|
||||
|
||||
// Base VAO
|
||||
var vao = GenVertexArray();
|
||||
BindVertexArray(vao);
|
||||
CheckGlError();
|
||||
|
||||
var vboSize = _verticesPerChunk(chunk) * sizeof(Vertex2D);
|
||||
var eboSize = _indicesPerChunk(chunk) * sizeof(ushort);
|
||||
|
||||
var vbo = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
vboSize, $"Grid {grid.Owner} chunk {chunk.Indices} VBO");
|
||||
var ebo = new GLBuffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
@@ -212,12 +351,30 @@ namespace Robust.Client.Graphics.Clyde
|
||||
vbo.Use();
|
||||
ebo.Use();
|
||||
|
||||
var datum = new MapChunkData(vao, vbo, ebo)
|
||||
{
|
||||
Dirty = true
|
||||
};
|
||||
datum.EBO = ebo;
|
||||
datum.VBO = vbo;
|
||||
datum.VAO = vao;
|
||||
|
||||
return datum;
|
||||
// EdgeVAO
|
||||
var edgeVao = GenVertexArray();
|
||||
BindVertexArray(edgeVao);
|
||||
CheckGlError();
|
||||
|
||||
var edgeVbo = new GLBuffer(this, BufferTarget.ArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
vboSize * 8, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeVBO");
|
||||
var edgeEbo = new GLBuffer(this, BufferTarget.ElementArrayBuffer, BufferUsageHint.DynamicDraw,
|
||||
eboSize * 8, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeEBO");
|
||||
|
||||
ObjectLabelMaybe(ObjectLabelIdentifier.VertexArray, vao, $"Grid {grid.Owner} chunk {chunk.Indices} EdgeVAO");
|
||||
SetupVAOLayout();
|
||||
CheckGlError();
|
||||
|
||||
edgeVbo.Use();
|
||||
edgeEbo.Use();
|
||||
|
||||
datum.EdgeEBO = edgeEbo;
|
||||
datum.EdgeVBO = edgeVbo;
|
||||
datum.EdgeVAO = edgeVao;
|
||||
}
|
||||
|
||||
private void DeleteChunk(MapChunkData data)
|
||||
@@ -254,19 +411,49 @@ namespace Robust.Client.Graphics.Clyde
|
||||
_mapChunkData.Remove(gridId);
|
||||
}
|
||||
|
||||
private static T[] EnsureSize<T>(ref T[]? field, int size)
|
||||
{
|
||||
if (field == null || field.Length < size)
|
||||
field = new T[size];
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
private void WriteTileToBuffers(
|
||||
int i,
|
||||
int gridX,
|
||||
int gridY,
|
||||
Span<Vertex2D> vertexBuffer,
|
||||
Span<ushort> indexBuffer,
|
||||
Box2 region)
|
||||
{
|
||||
var vIdx = i * 4;
|
||||
vertexBuffer[vIdx + 0] = new Vertex2D(gridX, gridY, region.Left, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 1] = new Vertex2D(gridX + 1, gridY, region.Right, region.Bottom, Color.White);
|
||||
vertexBuffer[vIdx + 2] = new Vertex2D(gridX + 1, gridY + 1, region.Right, region.Top, Color.White);
|
||||
vertexBuffer[vIdx + 3] = new Vertex2D(gridX, gridY + 1, region.Left, region.Top, Color.White);
|
||||
var nIdx = i * GetQuadBatchIndexCount();
|
||||
var tIdx = (ushort)(i * 4);
|
||||
QuadBatchIndexWrite(indexBuffer, ref nIdx, tIdx);
|
||||
}
|
||||
|
||||
private sealed class MapChunkData
|
||||
{
|
||||
public bool Dirty;
|
||||
public readonly uint VAO;
|
||||
public readonly GLBuffer VBO;
|
||||
public readonly GLBuffer EBO;
|
||||
public bool EdgeDirty = true;
|
||||
public bool Dirty = true;
|
||||
|
||||
public uint VAO;
|
||||
public GLBuffer VBO = default!;
|
||||
public GLBuffer EBO = default!;
|
||||
public int TileCount;
|
||||
|
||||
public MapChunkData(uint vao, GLBuffer vbo, GLBuffer ebo)
|
||||
public uint EdgeVAO;
|
||||
public GLBuffer EdgeVBO = default!;
|
||||
public GLBuffer EdgeEBO = default!;
|
||||
public int EdgeCount;
|
||||
|
||||
public MapChunkData()
|
||||
{
|
||||
VAO = vao;
|
||||
VBO = vbo;
|
||||
EBO = ebo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +123,13 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void RenderSingleWorldOverlay(Overlay overlay, Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
|
||||
{
|
||||
// Check that entity manager has started.
|
||||
// This is required for us to be able to use MapSystem.
|
||||
DebugTools.Assert(_entityManager.Started, "Entity manager should be started/initialized before rendering world-space overlays");
|
||||
|
||||
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
|
||||
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
|
||||
var args = new OverlayDrawArgs(space, null, vp, _renderHandle, new UIBox2i((0, 0), vp.Size), _mapSystem.GetMapOrInvalid(mapId), mapId, worldBox, worldBounds);
|
||||
|
||||
if (!overlay.BeforeDraw(args))
|
||||
@@ -152,6 +156,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void RenderOverlays(Viewport vp, OverlaySpace space, in Box2 worldBox, in Box2Rotated worldBounds)
|
||||
{
|
||||
DebugTools.Assert(space != OverlaySpace.ScreenSpaceBelowWorld && space != OverlaySpace.ScreenSpace);
|
||||
using (DebugGroup($"Overlays: {space}"))
|
||||
{
|
||||
foreach (var overlay in GetOverlaysForSpace(space))
|
||||
@@ -176,9 +181,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
var worldBounds = CalcWorldBounds(vp);
|
||||
var worldAABB = worldBounds.CalcBoundingBox();
|
||||
var mapId = vp.Eye!.Position.MapId;
|
||||
var mapId = vp.Eye?.Position.MapId ?? MapId.Nullspace;
|
||||
var mapUid = EntityUid.Invalid;
|
||||
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, _mapSystem.GetMapOrInvalid(mapId), mapId, worldAABB, worldBounds);
|
||||
// Screen space overlays may be getting used before entity manager & entity systems have been initialized.
|
||||
// This might mean that _mapSystem is currently null.
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (_entityManager.Started && _mapSystem != null)
|
||||
mapUid = _mapSystem.GetMapOrInvalid(mapId);
|
||||
|
||||
DebugTools.Assert(_mapSystem != null || !_entityManager.Started);
|
||||
|
||||
var args = new OverlayDrawArgs(space, vpControl, vp, handle, bounds, mapUid, mapId, worldAABB, worldBounds);
|
||||
|
||||
foreach (var overlay in list)
|
||||
{
|
||||
|
||||
@@ -98,9 +98,11 @@ 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();
|
||||
@@ -617,8 +619,9 @@ 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, worldAABB))
|
||||
foreach (var (uid, comp) in _lightTreeSystem.GetIntersectingTrees(map, lightAabb))
|
||||
{
|
||||
var bounds = _transformSystem.GetInvWorldMatrix(uid, xforms).TransformBox(worldBounds);
|
||||
comp.Tree.QueryAabb(ref state, LightQuery, bounds);
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
// UV coordinates in texture-space. I.e., (0,0) is the corner of the texture currently being used to draw.
|
||||
// When drawing a sprite from a texture atlas, (0,0) is the corner of the atlas, not the specific sprite being drawn.
|
||||
varying highp vec2 UV;
|
||||
|
||||
// UV coordinates in quad-space. I.e., when drawing a sprite from a texture atlas (0,0) is the corner of the sprite
|
||||
// currently being drawn.
|
||||
varying highp vec2 UV2;
|
||||
|
||||
// TBH I'm not sure what this is for. I think it is scree UV coordiantes, i.e., FRAGCOORD.xy * SCREEN_PIXEL_SIZE ?
|
||||
// TODO CLYDE Is this still needed?
|
||||
varying highp vec2 Pos;
|
||||
|
||||
// Vertex colour modulation. Note that negative values imply that the LIGHTMAP should be ignored. This is used to avoid
|
||||
// having to set the texture to a white/blank texture for sprites that have no light shading applied.
|
||||
varying highp vec4 VtxModulate;
|
||||
|
||||
// The current light map. Unless disabled, this is automatically sampled to create the LIGHT vector, which is then used
|
||||
// to modulate the output colour.
|
||||
// TODO CLYDE consistent shader variable naming
|
||||
uniform sampler2D lightMap;
|
||||
|
||||
// [SHADER_HEADER_CODE]
|
||||
@@ -11,11 +25,37 @@ void main()
|
||||
{
|
||||
highp vec4 FRAGCOORD = gl_FragCoord;
|
||||
|
||||
// The output colour. This should get set by the shader code block.
|
||||
// This will get modified by the LIGHT and MODULATE vectors.
|
||||
lowp vec4 COLOR;
|
||||
|
||||
lowp vec3 lightSample = texture2D(lightMap, Pos).rgb;
|
||||
// The light colour, usually sampled from the LIGHTMAP
|
||||
lowp vec4 LIGHT;
|
||||
|
||||
// Colour modulation vector.
|
||||
highp vec4 MODULATE;
|
||||
|
||||
// Sample the texture outside of the branch / with uniform control flow.
|
||||
LIGHT = texture2D(lightMap, Pos);
|
||||
|
||||
if (VtxModulate.x < 0.0)
|
||||
{
|
||||
// Negative VtxModulate implies unshaded/no lighting.
|
||||
MODULATE = -1.0 - VtxModulate;
|
||||
LIGHT = vec4(1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
MODULATE = VtxModulate;
|
||||
}
|
||||
|
||||
// TODO CLYDE consistent shader variable naming
|
||||
// Requires breaking changes.
|
||||
lowp vec3 lightSample = LIGHT.xyz;
|
||||
|
||||
// [SHADER_CODE]
|
||||
|
||||
gl_FragColor = zAdjustResult(COLOR * VtxModulate * vec4(lightSample, 1.0));
|
||||
LIGHT.xyz = lightSample;
|
||||
|
||||
gl_FragColor = zAdjustResult(COLOR * MODULATE * LIGHT);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ uniform mat3 modelMatrix;
|
||||
// Allows us to do texture atlassing with texture coordinates 0->1
|
||||
// Input texture coordinates get mapped to this range.
|
||||
uniform vec4 modifyUV;
|
||||
// TODO CLYDE Is this still needed?
|
||||
|
||||
// [SHADER_HEADER_CODE]
|
||||
|
||||
@@ -39,5 +40,15 @@ void main()
|
||||
Pos = (VERTEX + 1.0) / 2.0;
|
||||
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
|
||||
UV2 = tCoord2;
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
|
||||
// Negative modulation is being used as a hacky way to squeeze in lighting data.
|
||||
// I.e., negative modulation implies we ignore the lighting.
|
||||
if (modulate.x < 0.0)
|
||||
{
|
||||
VtxModulate = -1.0 - zFromSrgb(-1.0 - modulate);
|
||||
}
|
||||
else
|
||||
{
|
||||
VtxModulate = zFromSrgb(modulate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
varying highp vec2 UV;
|
||||
varying highp vec2 UV2;
|
||||
|
||||
// TODO CLYDE consistent shader variable naming
|
||||
uniform sampler2D lightMap;
|
||||
|
||||
// [SHADER_HEADER_CODE]
|
||||
|
||||
@@ -16,7 +16,7 @@ using Vector4 = Robust.Shared.Maths.Vector4;
|
||||
|
||||
namespace Robust.Client.Graphics
|
||||
{
|
||||
[Prototype("shader")]
|
||||
[Prototype]
|
||||
public sealed partial class ShaderPrototype : IPrototype, ISerializationHooks
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Client.Graphics;
|
||||
@@ -10,9 +11,11 @@ using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace Robust.Client.Map
|
||||
{
|
||||
@@ -29,7 +32,7 @@ namespace Robust.Client.Map
|
||||
|
||||
public Texture TileTextureAtlas => _tileTextureAtlas ?? Texture.Transparent;
|
||||
|
||||
private readonly Dictionary<int, Box2[]> _tileRegions = new();
|
||||
private FrozenDictionary<(int Id, Direction Direction), Box2[]> _tileRegions = FrozenDictionary<(int Id, Direction Direction), Box2[]>.Empty;
|
||||
|
||||
public Box2 ErrorTileRegion { get; private set; }
|
||||
|
||||
@@ -42,7 +45,14 @@ namespace Robust.Client.Map
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(int tileType)
|
||||
{
|
||||
if (_tileRegions.TryGetValue(tileType, out var region))
|
||||
return TileAtlasRegion(tileType, Direction.Invalid);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Box2[]? TileAtlasRegion(int tileType, Direction direction)
|
||||
{
|
||||
// ReSharper disable once CanSimplifyDictionaryTryGetValueWithGetValueOrDefault
|
||||
if (_tileRegions.TryGetValue((tileType, direction), out var region))
|
||||
{
|
||||
return region;
|
||||
}
|
||||
@@ -83,7 +93,8 @@ namespace Robust.Client.Map
|
||||
|
||||
internal void _genTextureAtlas()
|
||||
{
|
||||
_tileRegions.Clear();
|
||||
var sw = RStopwatch.StartNew();
|
||||
var tileRegs = new Dictionary<(int Id, Direction Direction), Box2[]>();
|
||||
_tileTextureAtlas = null;
|
||||
|
||||
var defList = TileDefs.Where(t => t.Sprite != null).ToList();
|
||||
@@ -94,7 +105,7 @@ namespace Robust.Client.Map
|
||||
|
||||
const int tileSize = EyeManager.PixelsPerMeter;
|
||||
|
||||
var tileCount = defList.Select(x => (int)x.Variants).Sum() + 1;
|
||||
var tileCount = defList.Select(x => x.Variants + x.EdgeSprites.Count).Sum() + 1;
|
||||
|
||||
var dimensionX = (int) Math.Ceiling(Math.Sqrt(tileCount));
|
||||
var dimensionY = (int) Math.Ceiling((float) tileCount / dimensionX);
|
||||
@@ -102,11 +113,11 @@ namespace Robust.Client.Map
|
||||
var imgWidth = dimensionX * tileSize;
|
||||
var imgHeight = dimensionY * tileSize;
|
||||
var sheet = new Image<Rgba32>(imgWidth, imgHeight);
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
// Add in the missing tile texture sprite as tile texture 0.
|
||||
{
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
ErrorTileRegion = Box2.FromDimensions(
|
||||
0, (h - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
@@ -154,25 +165,98 @@ namespace Robust.Client.Map
|
||||
var box = new UIBox2i(0, 0, tileSize, tileSize).Translated(new Vector2i(j * tileSize, 0));
|
||||
image.Blit(box, sheet, point);
|
||||
|
||||
var w = (float) sheet.Width;
|
||||
var h = (float) sheet.Height;
|
||||
|
||||
regionList[j] = Box2.FromDimensions(
|
||||
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
BumpColumn(ref row, ref column, dimensionX);
|
||||
}
|
||||
|
||||
_tileRegions.Add(def.TileId, regionList);
|
||||
tileRegs.Add((def.TileId, Direction.Invalid), regionList);
|
||||
|
||||
// Edges
|
||||
if (def.EdgeSprites.Count <= 0)
|
||||
continue;
|
||||
|
||||
foreach (var direction in DirectionExtensions.AllDirections)
|
||||
{
|
||||
if (!def.EdgeSprites.TryGetValue(direction, out var edge))
|
||||
continue;
|
||||
|
||||
using (var stream = _manager.ContentFileRead(edge))
|
||||
{
|
||||
image = Image.Load<Rgba32>(stream);
|
||||
}
|
||||
|
||||
if (image.Width != tileSize || image.Height != tileSize)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
$"Unable to load {path}, due to being unable to use tile textures with a dimension other than {tileSize}x{tileSize}.");
|
||||
}
|
||||
|
||||
Angle angle = Angle.Zero;
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle != Angle.Zero)
|
||||
{
|
||||
image.Mutate(o => o.Rotate((float)-angle.Degrees));
|
||||
}
|
||||
|
||||
var point = new Vector2i(column * tileSize, row * tileSize);
|
||||
var box = new UIBox2i(0, 0, tileSize, tileSize);
|
||||
image.Blit(box, sheet, point);
|
||||
|
||||
// If you ever need edge variants then you could just bump this.
|
||||
var edgeList = new Box2[1];
|
||||
edgeList[0] = Box2.FromDimensions(
|
||||
point.X / w, (h - point.Y - EyeManager.PixelsPerMeter) / h,
|
||||
tileSize / w, tileSize / h);
|
||||
|
||||
tileRegs.Add((def.TileId, direction), edgeList);
|
||||
BumpColumn(ref row, ref column, dimensionX);
|
||||
}
|
||||
}
|
||||
|
||||
_tileRegions = tileRegs.ToFrozenDictionary();
|
||||
_tileTextureAtlas = Texture.LoadFromImage(sheet, "Tile Atlas");
|
||||
_sawmill.Debug($"Tile atlas took {sw.Elapsed} to build");
|
||||
}
|
||||
|
||||
private void BumpColumn(ref int row, ref int column, int dimensionX)
|
||||
{
|
||||
column++;
|
||||
|
||||
if (column >= dimensionX)
|
||||
{
|
||||
column = 0;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
void IPostInjectInit.PostInject()
|
||||
|
||||
@@ -29,5 +29,12 @@ namespace Robust.Client.Map
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
Box2[]? TileAtlasRegion(int tileType);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the region inside the texture atlas to use to draw a tile type.
|
||||
/// Also handles edge sprites.
|
||||
/// </summary>
|
||||
/// <returns>If null, do not draw the tile at all.</returns>
|
||||
public Box2[]? TileAtlasRegion(int tileType, Direction direction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Client.Map;
|
||||
|
||||
/// <summary>
|
||||
/// Draws border sprites for tiles that support them.
|
||||
/// </summary>
|
||||
public sealed class TileEdgeOverlay : GridOverlay
|
||||
{
|
||||
private readonly IEntityManager _entManager;
|
||||
private readonly IResourceCache _resource;
|
||||
private readonly ITileDefinitionManager _tileDefManager;
|
||||
|
||||
public TileEdgeOverlay(IEntityManager entManager, IResourceCache resource, ITileDefinitionManager tileDefManager)
|
||||
{
|
||||
_entManager = entManager;
|
||||
_resource = resource;
|
||||
_tileDefManager = tileDefManager;
|
||||
ZIndex = -1;
|
||||
}
|
||||
|
||||
protected internal override void Draw(in OverlayDrawArgs args)
|
||||
{
|
||||
if (args.MapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var mapSystem = _entManager.System<SharedMapSystem>();
|
||||
var xformSystem = _entManager.System<SharedTransformSystem>();
|
||||
|
||||
var tileSize = Grid.Comp.TileSize;
|
||||
var tileDimensions = new Vector2(tileSize, tileSize);
|
||||
var (_, _, worldMatrix, invMatrix) = xformSystem.GetWorldPositionRotationMatrixWithInv(Grid.Owner);
|
||||
args.WorldHandle.SetTransform(worldMatrix);
|
||||
var bounds = args.WorldBounds;
|
||||
bounds = new Box2Rotated(bounds.Box.Enlarged(1), bounds.Rotation, bounds.Origin);
|
||||
var localAABB = invMatrix.TransformBox(bounds);
|
||||
|
||||
var enumerator = mapSystem.GetLocalTilesEnumerator(Grid.Owner, Grid, localAABB, false);
|
||||
|
||||
while (enumerator.MoveNext(out var tileRef))
|
||||
{
|
||||
var tileDef = _tileDefManager[tileRef.Tile.TypeId];
|
||||
|
||||
if (tileDef.EdgeSprites.Count == 0)
|
||||
continue;
|
||||
|
||||
// Get what tiles border us to determine what sprites we need to draw.
|
||||
for (var x = -1; x <= 1; x++)
|
||||
{
|
||||
for (var y = -1; y <= 1; y++)
|
||||
{
|
||||
if (x == 0 && y == 0)
|
||||
continue;
|
||||
|
||||
var neighborIndices = new Vector2i(tileRef.GridIndices.X + x, tileRef.GridIndices.Y + y);
|
||||
var neighborTile = mapSystem.GetTileRef(Grid.Owner, Grid, neighborIndices);
|
||||
var neighborDef = _tileDefManager[neighborTile.Tile.TypeId];
|
||||
|
||||
// If it's the same tile then no edge to be drawn.
|
||||
if (tileRef.Tile.TypeId == neighborTile.Tile.TypeId)
|
||||
continue;
|
||||
|
||||
// Don't draw if the the neighbor tile edges should draw over us (or if we have the same priority)
|
||||
if (neighborDef.EdgeSprites.Count != 0 && neighborDef.EdgeSpritePriority >= tileDef.EdgeSpritePriority)
|
||||
continue;
|
||||
|
||||
var direction = new Vector2i(x, y).AsDirection();
|
||||
|
||||
// No edge tile
|
||||
if (!tileDef.EdgeSprites.TryGetValue(direction, out var edgePath))
|
||||
continue;
|
||||
|
||||
var texture = _resource.GetResource<TextureResource>(edgePath);
|
||||
var box = Box2.FromDimensions(neighborIndices, tileDimensions);
|
||||
|
||||
var angle = Angle.Zero;
|
||||
|
||||
// If we ever need one for both cardinals and corners then update this.
|
||||
switch (direction)
|
||||
{
|
||||
// Corner sprites
|
||||
case Direction.SouthEast:
|
||||
break;
|
||||
case Direction.NorthEast:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.NorthWest:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.SouthWest:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
// Edge sprites
|
||||
case Direction.South:
|
||||
break;
|
||||
case Direction.East:
|
||||
angle = new Angle(MathF.PI / 2f);
|
||||
break;
|
||||
case Direction.North:
|
||||
angle = new Angle(MathF.PI);
|
||||
break;
|
||||
case Direction.West:
|
||||
angle = new Angle(MathF.PI * 1.5f);
|
||||
break;
|
||||
}
|
||||
|
||||
if (angle == Angle.Zero)
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, box);
|
||||
else
|
||||
args.WorldHandle.DrawTextureRect(texture.Texture, new Box2Rotated(box, angle, box.Center));
|
||||
|
||||
RequiresFlush = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.WorldHandle.SetTransform(Matrix3x2.Identity);
|
||||
}
|
||||
}
|
||||
@@ -71,21 +71,11 @@ 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)
|
||||
@@ -102,35 +92,6 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Graphics;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Maths;
|
||||
@@ -83,9 +84,23 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_updateScrollbarVisibility();
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<Item> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
|
||||
|
||||
_itemList.Add(item);
|
||||
|
||||
item.OnSelected += Select;
|
||||
item.OnDeselected += Deselect;
|
||||
}
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
public void Add(Item item)
|
||||
{
|
||||
if (item == null) return;
|
||||
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
|
||||
|
||||
_itemList.Add(item);
|
||||
@@ -93,9 +108,19 @@ namespace Robust.Client.UserInterface.Controls
|
||||
item.OnSelected += Select;
|
||||
item.OnDeselected += Deselect;
|
||||
|
||||
RecalculateContentHeight();
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
_scrollBar.MoveToEnd();
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
public void AddItems(IEnumerable<string> texts, Texture? icon = null, bool selectable = true, object? metadata = null)
|
||||
{
|
||||
var items = new ValueList<Item>();
|
||||
|
||||
foreach (var text in texts)
|
||||
{
|
||||
items.Add(new Item(this) {Text = text, Icon = icon, Selectable = selectable, Metadata = metadata});
|
||||
}
|
||||
|
||||
Add(items);
|
||||
}
|
||||
|
||||
public Item AddItem(string text, Texture? icon = null, bool selectable = true, object? metadata = null)
|
||||
@@ -107,11 +132,15 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var item in _itemList.ToArray())
|
||||
// Handle this manually so we can just clear all at once.
|
||||
foreach (var item in _itemList)
|
||||
{
|
||||
Remove(item);
|
||||
item.OnSelected -= Select;
|
||||
item.OnDeselected -= Deselect;
|
||||
}
|
||||
|
||||
_itemList.Clear();
|
||||
Recalculate();
|
||||
_totalContentHeight = 0;
|
||||
}
|
||||
|
||||
@@ -125,25 +154,35 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_itemList.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
private void InternalRemoveAt(int index)
|
||||
{
|
||||
if (_itemList.Count <= index)
|
||||
return;
|
||||
|
||||
// If you modify this then also make sure to update Clear!
|
||||
var item = _itemList[index];
|
||||
_itemList.RemoveAt(index);
|
||||
|
||||
item.OnSelected -= Select;
|
||||
item.OnDeselected -= Deselect;
|
||||
}
|
||||
|
||||
public bool Remove(Item item)
|
||||
{
|
||||
if (item == null) return false;
|
||||
|
||||
var value = _itemList.Remove(item);
|
||||
|
||||
item.OnSelected -= Select;
|
||||
item.OnDeselected -= Deselect;
|
||||
|
||||
RecalculateContentHeight();
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
_scrollBar.MoveToEnd();
|
||||
Recalculate();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
Remove(this[index]);
|
||||
InternalRemoveAt(index);
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
public IEnumerator<Item> GetEnumerator()
|
||||
@@ -161,16 +200,24 @@ namespace Robust.Client.UserInterface.Controls
|
||||
return _itemList.IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, Item item)
|
||||
private void InternalInsert(int index, Item item)
|
||||
{
|
||||
if (item == null) return;
|
||||
if(item.Owner != this) throw new ArgumentException("Item is owned by another ItemList!");
|
||||
|
||||
_itemList.Insert(index, item);
|
||||
|
||||
item.OnSelected += Select;
|
||||
item.OnDeselected += Deselect;
|
||||
}
|
||||
|
||||
public void Insert(int index, Item item)
|
||||
{
|
||||
InternalInsert(index, item);
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
private void Recalculate()
|
||||
{
|
||||
RecalculateContentHeight();
|
||||
if (_isAtBottom && ScrollFollowing)
|
||||
_scrollBar.MoveToEnd();
|
||||
@@ -191,7 +238,6 @@ namespace Robust.Client.UserInterface.Controls
|
||||
SetItems(newItems, (a,b) => string.Compare(a.Text, b.Text));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// This variant allows for a custom equality operator to compare items, when
|
||||
/// comparing the Item text is not desired.
|
||||
@@ -215,13 +261,13 @@ namespace Robust.Client.UserInterface.Controls
|
||||
else if (cmpResult > 0)
|
||||
{
|
||||
// Item exists in our list, but not in `newItems`. Remove it.
|
||||
RemoveAt(i);
|
||||
InternalRemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
else if (cmpResult < 0)
|
||||
{
|
||||
// A new entry which doesn't exist in our list. Insert it.
|
||||
Insert(i + 1, newItems[j]);
|
||||
InternalInsert(i + 1, newItems[j]);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
@@ -229,16 +275,18 @@ namespace Robust.Client.UserInterface.Controls
|
||||
// Any remaining items in our list don't exist in `newItems` so remove them
|
||||
while (i >= 0)
|
||||
{
|
||||
RemoveAt(i);
|
||||
InternalRemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
|
||||
// And finally, any remaining items in `newItems` don't exist in our list. Create them.
|
||||
while (j >= 0)
|
||||
{
|
||||
Insert(0, newItems[j]);
|
||||
InternalInsert(0, newItems[j]);
|
||||
j--;
|
||||
}
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
|
||||
// Without this attribute, this would compile into a property called "Item", causing problems with the Item class.
|
||||
@@ -290,9 +338,12 @@ namespace Robust.Client.UserInterface.Controls
|
||||
|
||||
public void ClearSelected(int? except = null)
|
||||
{
|
||||
foreach (var item in GetSelected())
|
||||
for (var i = 0; i < _itemList.Count; i++)
|
||||
{
|
||||
if(IndexOf(item) == except) continue;
|
||||
if (i == except)
|
||||
continue;
|
||||
|
||||
var item = _itemList[i];
|
||||
item.Selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.RichText;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -15,10 +16,23 @@ namespace Robust.Client.UserInterface.Controls
|
||||
[Virtual]
|
||||
public class OutputPanel : Control
|
||||
{
|
||||
public const string StyleClassOutputPanelScrollDownButton = "outputPanelScrollDownButton";
|
||||
|
||||
[Dependency] private readonly MarkupTagManager _tagManager = default!;
|
||||
|
||||
public const string StylePropertyStyleBox = "stylebox";
|
||||
|
||||
public bool ShowScrollDownButton
|
||||
{
|
||||
get => _showScrollDownButton;
|
||||
set
|
||||
{
|
||||
_showScrollDownButton = value;
|
||||
_updateScrollButtonVisibility();
|
||||
}
|
||||
}
|
||||
private bool _showScrollDownButton;
|
||||
|
||||
private readonly RingBufferList<RichTextEntry> _entries = new();
|
||||
private bool _isAtBottom = true;
|
||||
|
||||
@@ -26,6 +40,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
private bool _firstLine = true;
|
||||
private StyleBox? _styleBoxOverride;
|
||||
private VScrollBar _scrollBar;
|
||||
private Button _scrollDownButton;
|
||||
|
||||
public bool ScrollFollowing { get; set; } = true;
|
||||
|
||||
@@ -43,7 +58,25 @@ namespace Robust.Client.UserInterface.Controls
|
||||
HorizontalAlignment = HAlignment.Right
|
||||
};
|
||||
AddChild(_scrollBar);
|
||||
_scrollBar.OnValueChanged += _ => _isAtBottom = _scrollBar.IsAtEnd;
|
||||
|
||||
AddChild(_scrollDownButton = new Button()
|
||||
{
|
||||
Name = "scrollLiveBtn",
|
||||
StyleClasses = { StyleClassOutputPanelScrollDownButton },
|
||||
VerticalAlignment = VAlignment.Bottom,
|
||||
HorizontalAlignment = HAlignment.Center,
|
||||
Text = String.Format("⬇ {0} ⬇", Loc.GetString("output-panel-scroll-down-button-text")),
|
||||
MaxWidth = 300,
|
||||
Visible = false,
|
||||
});
|
||||
|
||||
_scrollDownButton.OnPressed += _ => ScrollToBottom();
|
||||
|
||||
_scrollBar.OnValueChanged += _ =>
|
||||
{
|
||||
_isAtBottom = _scrollBar.IsAtEnd;
|
||||
_updateScrollButtonVisibility();
|
||||
};
|
||||
}
|
||||
|
||||
public int EntryCount => _entries.Count;
|
||||
@@ -184,6 +217,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
var styleBoxSize = _getStyleBox()?.MinimumSize.Y ?? 0;
|
||||
|
||||
_scrollBar.Page = UIScale * (Height - styleBoxSize);
|
||||
_updateScrollButtonVisibility();
|
||||
_invalidateEntries();
|
||||
}
|
||||
|
||||
@@ -284,5 +318,10 @@ namespace Robust.Client.UserInterface.Controls
|
||||
_invalidOnVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void _updateScrollButtonVisibility()
|
||||
{
|
||||
_scrollDownButton.Visible = ShowScrollDownButton && !_isAtBottom;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Control xmlns="https://spacestation14.io"
|
||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace">
|
||||
<OutputPanel Name="Output" VerticalExpand="True" StyleClasses="monospace" ShowScrollDownButton="True">
|
||||
<OutputPanel.StyleBoxOverride>
|
||||
<gfx:StyleBoxFlat BackgroundColor="#25252add"
|
||||
ContentMarginLeftOverride="3" ContentMarginRightOverride="3"
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace Robust.Client.UserInterface.CustomControls
|
||||
}
|
||||
|
||||
button.EntityLabel.Text = entityLabelText;
|
||||
button.ActualButton.ToolTip = prototype.Description;
|
||||
|
||||
if (prototype == SelectedPrototype)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface.RichText;
|
||||
|
||||
[Prototype("font")]
|
||||
[Prototype]
|
||||
public sealed partial class FontPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
|
||||
@@ -5,9 +5,9 @@ using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Profiling;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Client.UserInterface;
|
||||
internal sealed partial class UserInterfaceManager
|
||||
@@ -61,7 +61,7 @@ internal sealed partial class UserInterfaceManager
|
||||
Title = string.IsNullOrEmpty(title) ? Loc.GetString("popup-title") : title,
|
||||
};
|
||||
|
||||
var label = new Label { Text = contents };
|
||||
var label = new RichTextLabel { Text = $"[color=white]{FormattedMessage.EscapeText(contents)}[/color]" };
|
||||
|
||||
var vBox = new BoxContainer
|
||||
{
|
||||
|
||||
@@ -2,11 +2,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using Robust.Client.AutoGenerated;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Client.UserInterface.XAML;
|
||||
using Robust.Shared.Collections;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Robust.Client.ViewVariables
|
||||
{
|
||||
@@ -51,15 +55,19 @@ namespace Robust.Client.ViewVariables
|
||||
_lastSearch = search;
|
||||
EntryItemList.ClearSelected();
|
||||
EntryItemList.Clear();
|
||||
|
||||
AddButton.Disabled = true;
|
||||
var items = new ValueList<string>();
|
||||
|
||||
foreach (var component in _entries)
|
||||
{
|
||||
if(!string.IsNullOrEmpty(search) && !component.Contains(search, StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
EntryItemList.AddItem(component);
|
||||
items.Add(component);
|
||||
}
|
||||
|
||||
EntryItemList.AddItems(items);
|
||||
}
|
||||
|
||||
private void OnSearchTextChanged(LineEdit.LineEditEventArgs obj)
|
||||
|
||||
@@ -36,6 +36,7 @@ public static class Diagnostics
|
||||
public const string IdUseNonGenericVariant = "RA0030";
|
||||
public const string IdPreferOtherType = "RA0031";
|
||||
public const string IdDuplicateDependency = "RA0032";
|
||||
public const string IdForbidLiteral = "RA0033";
|
||||
|
||||
public static SuppressionDescriptor MeansImplicitAssignment =>
|
||||
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PolySharpIncludeGeneratedTypes>System.Index;System.Diagnostics.CodeAnalysis.NotNullWhenAttribute;System.Runtime.CompilerServices.IsExternalInit;System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute</PolySharpIncludeGeneratedTypes>
|
||||
<NoWarn>RS2008</NoWarn>
|
||||
<NoWarn>RS2008;RS1038</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -35,13 +35,20 @@ public class Generator : IIncrementalGenerator
|
||||
.Where(static type => type != null);
|
||||
|
||||
initContext.RegisterSourceOutput(
|
||||
dataDefinitions,
|
||||
static (sourceContext, source) =>
|
||||
dataDefinitions.Collect(),
|
||||
static (sourceContext, sources) =>
|
||||
{
|
||||
// TODO: deduplicate based on name?
|
||||
var (name, code) = source!.Value;
|
||||
var done = new HashSet<string>();
|
||||
|
||||
sourceContext.AddSource(name, code);
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var (name, code) = source!.Value;
|
||||
|
||||
if (!done.Add(name))
|
||||
continue;
|
||||
|
||||
sourceContext.AddSource(name, code);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ namespace Robust.Server
|
||||
: null;
|
||||
|
||||
// Set up the VFS
|
||||
_resources.Initialize(dataDir);
|
||||
_resources.Initialize(dataDir, hideUserDataDir: false);
|
||||
|
||||
var mountOptions = _commandLineArgs != null
|
||||
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions) : Options.MountOptions;
|
||||
|
||||
@@ -8,6 +8,7 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
@@ -46,11 +47,11 @@ namespace Robust.Server.Console.Commands
|
||||
bool saveSuccess = _ent.System<MapLoaderSystem>().TrySaveGrid(uid, new ResPath(args[1]));
|
||||
if(saveSuccess)
|
||||
{
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
shell.WriteLine("Save successful. Look in the user data directory.");
|
||||
}
|
||||
else
|
||||
{
|
||||
shell.WriteError("Save unsuccessful!");
|
||||
shell.WriteError("Save unsuccessful!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ namespace Robust.Server.Console.Commands
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-savebp-id"));
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.Components<MapGridComponent>(args[0], _ent), Loc.GetString("cmd-hint-savebp-id"));
|
||||
case 2:
|
||||
var opts = CompletionHelper.UserFilePath(args[1], _resource.UserData);
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-savemap-path"));
|
||||
@@ -159,6 +160,7 @@ namespace Robust.Server.Console.Commands
|
||||
|
||||
public sealed class SaveMap : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _system = default!;
|
||||
[Dependency] private readonly IResourceManager _resource = default!;
|
||||
|
||||
@@ -169,7 +171,7 @@ namespace Robust.Server.Console.Commands
|
||||
switch (args.Length)
|
||||
{
|
||||
case 1:
|
||||
return CompletionResult.FromHint(Loc.GetString("cmd-hint-savemap-id"));
|
||||
return CompletionResult.FromHintOptions(CompletionHelper.MapIds(_entManager), Loc.GetString("cmd-hint-savemap-id"));
|
||||
case 2:
|
||||
var opts = CompletionHelper.UserFilePath(args[1], _resource.UserData);
|
||||
return CompletionResult.FromHintOptions(opts, Loc.GetString("cmd-hint-savemap-path"));
|
||||
|
||||
@@ -204,6 +204,23 @@ namespace Robust.Server.GameObjects
|
||||
|
||||
private void HandleEntityNetworkMessage(MsgEntity message)
|
||||
{
|
||||
if (_logLateMsgs)
|
||||
{
|
||||
var msgT = message.SourceTick;
|
||||
var cT = _gameTiming.CurTick;
|
||||
|
||||
if (msgT < cT)
|
||||
{
|
||||
_netEntSawmill.Warning(
|
||||
"Got late MsgEntity! Diff: {0}, msgT: {2}, cT: {3}, player: {1}, msg: {4}",
|
||||
(int) msgT.Value - (int) cT.Value,
|
||||
message.MsgChannel.UserName,
|
||||
msgT,
|
||||
cT,
|
||||
message.SystemMessage);
|
||||
}
|
||||
}
|
||||
|
||||
_queue.Add(message);
|
||||
}
|
||||
|
||||
|
||||
15
Robust.Server/ServerStatus/StatusExt.cs
Normal file
15
Robust.Server/ServerStatus/StatusExt.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace Robust.Server.ServerStatus;
|
||||
|
||||
/// <summary>
|
||||
/// Helper functions for working with <see cref="IStatusHandlerContext"/>.
|
||||
/// </summary>
|
||||
public static class StatusExt
|
||||
{
|
||||
/// <summary>
|
||||
/// Add <c>Access-Control-Allow-Origin: *</c> to the response headers for this request.
|
||||
/// </summary>
|
||||
public static void AddAllowOriginAny(this IStatusHandlerContext context)
|
||||
{
|
||||
context.ResponseHeaders.Add("Access-Control-Allow-Origin", "*");
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
OnStatusRequest?.Invoke(jObject);
|
||||
|
||||
context.AddAllowOriginAny();
|
||||
await context.RespondJsonAsync(jObject);
|
||||
|
||||
return true;
|
||||
@@ -121,6 +122,7 @@ namespace Robust.Server.ServerStatus
|
||||
|
||||
OnInfoRequest?.Invoke(jObject);
|
||||
|
||||
context.AddAllowOriginAny();
|
||||
await context.RespondJsonAsync(jObject);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -93,6 +93,7 @@ namespace Robust.Shared.Maths
|
||||
private const double CardinalSegment = 2 * Math.PI / 4.0; // Cut the circle into 4 pieces
|
||||
private const double CardinalOffset = CardinalSegment / 2.0; // offset the pieces by 1/2 their size
|
||||
|
||||
[Pure]
|
||||
public readonly Direction GetCardinalDir()
|
||||
{
|
||||
var ang = Theta % (2 * Math.PI);
|
||||
@@ -167,6 +168,7 @@ namespace Robust.Shared.Maths
|
||||
/// <summary>
|
||||
/// Removes revolutions from a positive or negative angle to make it as small as possible.
|
||||
/// </summary>
|
||||
[Pure]
|
||||
public readonly Angle Reduced()
|
||||
{
|
||||
return new(Reduce(Theta));
|
||||
@@ -213,11 +215,13 @@ namespace Robust.Shared.Maths
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public readonly Angle Opposite()
|
||||
{
|
||||
return new Angle(FlipPositive(Theta-Math.PI));
|
||||
}
|
||||
|
||||
[Pure]
|
||||
public readonly Angle FlipPositive()
|
||||
{
|
||||
return new(FlipPositive(Theta));
|
||||
|
||||
@@ -682,6 +682,7 @@ namespace Robust.Shared.Maths
|
||||
/// (which is copied to the output's Alpha value).
|
||||
/// Each has a range of 0.0 to 1.0.
|
||||
/// </param>
|
||||
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
|
||||
public static Color FromHcy(Vector4 hcy)
|
||||
{
|
||||
var hue = hcy.X * 360.0f;
|
||||
@@ -750,6 +751,7 @@ namespace Robust.Shared.Maths
|
||||
/// </returns>
|
||||
/// <param name="rgb">Color value to convert.</param>
|
||||
[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
||||
[Obsolete("The HCY color space is mathematically incorrect and these functions are broken, use something else")]
|
||||
public static Vector4 ToHcy(Color rgb)
|
||||
{
|
||||
var max = MathF.Max(rgb.R, MathF.Max(rgb.G, rgb.B));
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
@@ -36,6 +38,21 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
public static class DirectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of all cardinal and diagonal <see cref="Direction"/>s.
|
||||
/// </summary>
|
||||
public static readonly ImmutableArray<Direction> AllDirections =
|
||||
[
|
||||
Direction.South,
|
||||
Direction.SouthEast,
|
||||
Direction.East,
|
||||
Direction.NorthEast,
|
||||
Direction.North,
|
||||
Direction.NorthWest,
|
||||
Direction.West,
|
||||
Direction.SouthWest
|
||||
];
|
||||
|
||||
private const double Segment = 2 * Math.PI / 8.0; // Cut the circle into 8 pieces
|
||||
|
||||
public static Direction AsDir(this DirectionFlag directionFlag)
|
||||
@@ -238,6 +255,7 @@ namespace Robust.Shared.Maths
|
||||
/// </summary>
|
||||
/// <param name="dir"></param>
|
||||
/// <returns></returns>
|
||||
[Pure]
|
||||
public static Angle ToAngle(this Direction dir)
|
||||
{
|
||||
var ang = Segment * (int) dir;
|
||||
|
||||
11
Robust.Shared/Analyzers/ForbidLiteralAttribute.cs
Normal file
11
Robust.Shared/Analyzers/ForbidLiteralAttribute.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Analyzers;
|
||||
|
||||
/// <summary>
|
||||
/// Marks that values used for this parameter should not be literal values.
|
||||
/// This helps prevent magic numbers/strings/etc, by indicating that values
|
||||
/// should either be wrapped (for validation) or defined as constants or readonly statics.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter)]
|
||||
public sealed class ForbidLiteralAttribute : Attribute;
|
||||
@@ -8,7 +8,7 @@ namespace Robust.Shared.Audio;
|
||||
/// Contains audio defaults to set for sounds.
|
||||
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
|
||||
/// </summary>
|
||||
[Prototype("audioPreset")]
|
||||
[Prototype]
|
||||
public sealed partial class AudioPresetPrototype : IPrototype
|
||||
{
|
||||
[IdDataField]
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Audio;
|
||||
|
||||
[Prototype("soundCollection")]
|
||||
[Prototype]
|
||||
public sealed partial class SoundCollectionPrototype : IPrototype
|
||||
{
|
||||
[ViewVariables]
|
||||
|
||||
@@ -1874,5 +1874,12 @@ namespace Robust.Shared
|
||||
/// </summary>
|
||||
public static readonly CVarDef<int> ToolshedNearbyEntitiesLimit =
|
||||
CVarDef.Create("toolshed.nearby_entities_limit", 5, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/*
|
||||
* Localization
|
||||
*/
|
||||
|
||||
public static readonly CVarDef<string> LocCultureName =
|
||||
CVarDef.Create("loc.culture_name", "en-US", CVar.ARCHIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ sealed class AddMapCommand : LocalizedEntityCommands
|
||||
|
||||
sealed class RemoveMapCommand : LocalizedCommands
|
||||
{
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
public override string Command => "rmmap";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -51,14 +51,15 @@ sealed class RemoveMapCommand : LocalizedCommands
|
||||
}
|
||||
|
||||
var mapId = new MapId(int.Parse(args[0]));
|
||||
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
if (!mapSystem.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError($"Map {mapId.Value} does not exist.");
|
||||
return;
|
||||
}
|
||||
|
||||
_map.DeleteMap(mapId);
|
||||
mapSystem.DeleteMap(mapId);
|
||||
shell.WriteLine($"Map {mapId.Value} was removed.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
[Dependency] private readonly IMapManager _map = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
||||
public override string Command => "tp";
|
||||
public override bool RequireServerOrSingleplayer => true;
|
||||
@@ -46,7 +47,7 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
else
|
||||
mapId = transform.MapID;
|
||||
|
||||
if (!_map.MapExists(mapId))
|
||||
if (!_mapSystem.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError($"Map {mapId} doesn't exist!");
|
||||
return;
|
||||
@@ -60,13 +61,26 @@ internal sealed class TeleportCommand : LocalizedEntityCommands
|
||||
}
|
||||
else
|
||||
{
|
||||
var mapEnt = _map.GetMapEntityIdOrThrow(mapId);
|
||||
_transform.SetWorldPosition((entity, transform), position);
|
||||
_transform.SetParent(entity, transform, mapEnt);
|
||||
if (_mapSystem.TryGetMap(mapId, out var mapEnt))
|
||||
{
|
||||
_transform.SetWorldPosition((entity, transform), position);
|
||||
_transform.SetParent(entity, transform, mapEnt.Value);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -60,9 +60,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
internal string GetPath(ResPath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()))
|
||||
// Sanitise platform-specific path and standardize it for engine use.
|
||||
.Replace(Path.DirectorySeparatorChar, '/');
|
||||
return PathHelpers.SafeGetResourcePath(_directory.FullName, relPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,7 +14,11 @@ namespace Robust.Shared.ContentPack
|
||||
/// The directory to use for user data.
|
||||
/// If null, a virtual temporary file system is used instead.
|
||||
/// </param>
|
||||
void Initialize(string? userData);
|
||||
/// <param name="hideUserDataDir">
|
||||
/// If true, <see cref="IWritableDirProvider.RootDir"/> will be hidden on
|
||||
/// <see cref="IResourceManager.UserData"/>.
|
||||
/// </param>
|
||||
void Initialize(string? userData, bool hideUserDataDir);
|
||||
|
||||
/// <summary>
|
||||
/// Mounts a single stream as a content file. Useful for unit testing.
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Robust.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// The root path of this provider.
|
||||
/// Can be null if it's a virtual provider.
|
||||
/// Can be null if it's a virtual provider or the path is protected (e.g. on the client).
|
||||
/// </summary>
|
||||
string? RootDir { get; }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.ContentPack
|
||||
{
|
||||
@@ -63,5 +64,27 @@ 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(Path.DirectorySeparatorChar))
|
||||
throw new InvalidOperationException($"This branch should never be reached. Path: {path}");
|
||||
}
|
||||
|
||||
return retPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,13 +41,13 @@ namespace Robust.Shared.ContentPack
|
||||
public IWritableDirProvider UserData { get; private set; } = default!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void Initialize(string? userData)
|
||||
public virtual void Initialize(string? userData, bool hideRootDir)
|
||||
{
|
||||
Sawmill = _logManager.GetSawmill("res");
|
||||
|
||||
if (userData != null)
|
||||
{
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData));
|
||||
UserData = new WritableDirProvider(Directory.CreateDirectory(userData), hideRootDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -381,6 +381,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,17 +10,22 @@ namespace Robust.Shared.ContentPack
|
||||
/// <inheritdoc />
|
||||
internal sealed class WritableDirProvider : IWritableDirProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
private readonly bool _hideRootDir;
|
||||
|
||||
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>
|
||||
public WritableDirProvider(DirectoryInfo rootDir)
|
||||
/// <param name="hideRootDir">If true, <see cref="IWritableDirProvider.RootDir"/> is reported as null.</param>
|
||||
public WritableDirProvider(DirectoryInfo rootDir, bool hideRootDir)
|
||||
{
|
||||
// FullName does not have a trailing separator, and we MUST have a separator.
|
||||
RootDir = rootDir.FullName + Path.DirectorySeparatorChar.ToString();
|
||||
_hideRootDir = hideRootDir;
|
||||
}
|
||||
|
||||
#region File Access
|
||||
@@ -119,7 +124,7 @@ namespace Robust.Shared.ContentPack
|
||||
throw new FileNotFoundException();
|
||||
|
||||
var dirInfo = new DirectoryInfo(GetFullPath(path));
|
||||
return new WritableDirProvider(dirInfo);
|
||||
return new WritableDirProvider(dirInfo, _hideRootDir);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -180,20 +185,7 @@ namespace Robust.Shared.ContentPack
|
||||
|
||||
path = path.Clean();
|
||||
|
||||
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));
|
||||
return PathHelpers.SafeGetResourcePath(RootDir, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,9 +155,9 @@ public sealed class EntitySerializer : ISerializationContext,
|
||||
public event IsSerializableDelegate? OnIsSerializeable;
|
||||
public delegate void IsSerializableDelegate(Entity<MetaDataComponent> ent, ref bool serializable);
|
||||
|
||||
public EntitySerializer(IDependencyCollection _dependency, SerializationOptions options)
|
||||
public EntitySerializer(IDependencyCollection dependency, SerializationOptions options)
|
||||
{
|
||||
_dependency.InjectDependencies(this);
|
||||
dependency.InjectDependencies(this);
|
||||
|
||||
_log = _logMan.GetSawmill("entity_serializer");
|
||||
SerializerProvider.RegisterSerializer(this);
|
||||
|
||||
@@ -47,6 +47,7 @@ public sealed partial class MapLoaderSystem : EntitySystem
|
||||
Log.Info($"Saving serialized results to {path}");
|
||||
path = path.ToRootedPath();
|
||||
var document = new YamlDocument(data.ToYaml());
|
||||
_resourceManager.UserData.CreateDir(path.Directory);
|
||||
using var writer = _resourceManager.UserData.OpenWriteText(path);
|
||||
{
|
||||
var stream = new YamlStream {document};
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
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
|
||||
{
|
||||
/// <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.
|
||||
/// The visibility layer for the entity.
|
||||
/// Players whose visibility masks don't match this won't get state updates for it.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
/// <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;
|
||||
}
|
||||
|
||||
@@ -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 ReadOnlySpan<string> fields)
|
||||
public virtual void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params string[] fields)
|
||||
where T : IComponentDelta
|
||||
{
|
||||
var compReg = ComponentFactory.GetRegistration(CompIdx.Index<T>());
|
||||
|
||||
@@ -595,12 +595,23 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (!_deleteSet.Add(component))
|
||||
{
|
||||
// already deferred deletion
|
||||
// Already deferring deletion
|
||||
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Stopped);
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.LifeStage >= ComponentLifeStage.Initialized && component.LifeStage <= ComponentLifeStage.Running)
|
||||
DebugTools.Assert(component.LifeStage >= ComponentLifeStage.Added);
|
||||
|
||||
if (component.LifeStage is >= ComponentLifeStage.Initialized and < ComponentLifeStage.Stopping)
|
||||
LifeShutdown(uid, component, _componentFactory.GetIndex(component.GetType()));
|
||||
else if (component.LifeStage == ComponentLifeStage.Added)
|
||||
{
|
||||
// The component was added, but never initialized or started. It's kinda weird to add and then
|
||||
// immediately defer-remove a component, but oh well. Let's just set the life stage directly and not
|
||||
// raise shutdown events? The removal events will still get called later.
|
||||
// This is also what LifeShutdown() would also do, albeit behind a DebugAssert.
|
||||
component.LifeStage = ComponentLifeStage.Stopped;
|
||||
}
|
||||
#if EXCEPTION_TOLERANCE
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -16,6 +16,7 @@ public partial class EntityManager
|
||||
/// </summary>
|
||||
internal void LifeAddToEntity(EntityUid uid, IComponent component, CompIdx idx)
|
||||
{
|
||||
DebugTools.Assert(!_deleteSet.Contains(component));
|
||||
DebugTools.Assert(component.LifeStage == ComponentLifeStage.PreAdd);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
@@ -34,6 +35,7 @@ public partial class EntityManager
|
||||
/// </summary>
|
||||
internal void LifeInitialize(EntityUid uid, IComponent component, CompIdx idx)
|
||||
{
|
||||
DebugTools.Assert(!_deleteSet.Contains(component));
|
||||
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Added);
|
||||
|
||||
component.LifeStage = ComponentLifeStage.Initializing;
|
||||
@@ -47,6 +49,7 @@ public partial class EntityManager
|
||||
/// </summary>
|
||||
internal void LifeStartup(EntityUid uid, IComponent component, CompIdx idx)
|
||||
{
|
||||
DebugTools.Assert(!_deleteSet.Contains(component));
|
||||
DebugTools.Assert(component.LifeStage == ComponentLifeStage.Initialized);
|
||||
|
||||
component.LifeStage = ComponentLifeStage.Starting;
|
||||
|
||||
@@ -172,12 +172,22 @@ public partial class EntitySystem
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params ReadOnlySpan<string> fields)
|
||||
protected void DirtyFields<T>(EntityUid uid, T comp, MetaDataComponent? meta, params 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>
|
||||
|
||||
@@ -37,18 +37,22 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref MetaDataComponent? component,
|
||||
protected bool Resolve(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] ref MetaDataComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.MetaQuery.Resolve(uid, ref component);
|
||||
return EntityManager.MetaQuery.Resolve(uid, ref component, logMissing);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Resolve{TComp}(Robust.Shared.GameObjects.EntityUid,ref TComp?,bool)"/>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected bool Resolve(EntityUid uid, [NotNullWhen(true)] ref TransformComponent? component,
|
||||
protected bool Resolve(
|
||||
EntityUid uid,
|
||||
[NotNullWhen(true)] ref TransformComponent? component,
|
||||
bool logMissing = true)
|
||||
{
|
||||
return EntityManager.TransformQuery.Resolve(uid, ref component);
|
||||
return EntityManager.TransformQuery.Resolve(uid, ref component, logMissing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -193,7 +193,7 @@ namespace Robust.Shared.GameObjects
|
||||
ComponentEventHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
where TEvent : notnull
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
@@ -206,7 +206,7 @@ namespace Robust.Shared.GameObjects
|
||||
ComponentEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
where TEvent : notnull
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
@@ -219,7 +219,7 @@ namespace Robust.Shared.GameObjects
|
||||
EntityEventRefHandler<TComp, TEvent> handler,
|
||||
Type[]? before = null, Type[]? after = null)
|
||||
where TComp : IComponent
|
||||
where TEvent : EntityEventArgs
|
||||
where TEvent : notnull
|
||||
{
|
||||
System.SubscribeLocalEvent(handler, before, after);
|
||||
}
|
||||
@@ -229,7 +229,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used by extension methods for <see cref="Subscriptions"/>
|
||||
/// to unsubscribe from from external sources such as CVars.
|
||||
/// to unsubscribe from external sources such as CVars.
|
||||
/// </remarks>
|
||||
/// <param name="action">An action to be ran when the entity system is shut down.</param>
|
||||
public void RegisterUnsubscription(Action action)
|
||||
|
||||
@@ -96,6 +96,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
// If we're attached to the map we'll also just never disable collision due to how grid movement works.
|
||||
var canCollide = body.Awake ||
|
||||
body.ContactCount > 0 ||
|
||||
(TryComp(uid, out JointComponent? jointComponent) && jointComponent.JointCount > 0) ||
|
||||
xform.GridUid == null;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ 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;
|
||||
|
||||
@@ -406,7 +407,7 @@ public sealed partial class EntityLookupSystem
|
||||
var broadphaseInv = _transform.GetInvWorldMatrix(lookupUid);
|
||||
|
||||
var localBounds = broadphaseInv.TransformBounds(worldBounds);
|
||||
var polygon = _physics.GetPooled(localBounds);
|
||||
var polygon = new SlimPolygon(localBounds);
|
||||
var result = AnyEntitiesIntersecting(lookupUid,
|
||||
polygon,
|
||||
localBounds.CalcBoundingBox(),
|
||||
@@ -414,7 +415,6 @@ public sealed partial class EntityLookupSystem
|
||||
flags,
|
||||
ignored);
|
||||
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -458,9 +458,8 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return false;
|
||||
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var result = AnyEntitiesIntersecting(mapId, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -475,9 +474,8 @@ public sealed partial class EntityLookupSystem
|
||||
{
|
||||
if (mapId == MapId.Nullspace) return;
|
||||
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -487,18 +485,16 @@ 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 = _physics.GetPooled(worldBounds);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldBounds);
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
AddEntitiesIntersecting(mapId, intersecting, polygon, Physics.Transform.Empty, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return intersecting;
|
||||
}
|
||||
|
||||
@@ -761,11 +757,10 @@ public sealed partial class EntityLookupSystem
|
||||
return;
|
||||
|
||||
var localAABB = _transform.GetInvWorldMatrix(gridId).TransformBox(worldAABB);
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
var polygon = new SlimPolygon(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)
|
||||
@@ -774,11 +769,10 @@ public sealed partial class EntityLookupSystem
|
||||
return;
|
||||
|
||||
var localBounds = _transform.GetInvWorldMatrix(gridId).TransformBounds(worldBounds);
|
||||
var polygon = _physics.GetPooled(localBounds);
|
||||
var polygon = new SlimPolygon(localBounds);
|
||||
|
||||
AddEntitiesIntersecting(gridId, intersecting, polygon, localBounds.CalcBoundingBox(), Physics.Transform.Empty, flags, lookup);
|
||||
AddContained(intersecting, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -121,9 +121,8 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
var polygon = new SlimPolygon(localAABB);
|
||||
AddEntitiesIntersecting(lookupUid, intersecting, polygon, localAABB, Physics.Transform.Empty, flags, query, lookup);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
private void AddEntitiesIntersecting<T, TShape>(
|
||||
@@ -252,11 +251,10 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return false;
|
||||
|
||||
var polygon = _physics.GetPooled(localAABB);
|
||||
var polygon = new SlimPolygon(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;
|
||||
}
|
||||
@@ -427,10 +425,9 @@ public sealed partial class EntityLookupSystem
|
||||
|
||||
public bool AnyComponentsIntersecting(Type type, MapId mapId, Box2 worldAABB, EntityUid? ignored = null, LookupFlags flags = DefaultFlags)
|
||||
{
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var transform = Physics.Transform.Empty;
|
||||
var result = AnyComponentsIntersecting(type, mapId, polygon, transform, ignored, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -496,33 +493,30 @@ public sealed partial class EntityLookupSystem
|
||||
if (mapId == MapId.Nullspace)
|
||||
return;
|
||||
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldBounds);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(worldAABB);
|
||||
var shapeTransform = Physics.Transform.Empty;
|
||||
|
||||
GetEntitiesIntersecting(mapId, polygon, shapeTransform, entities, flags);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed partial class EntityLookupSystem
|
||||
if (!_broadQuery.Resolve(lookupUid, ref lookup))
|
||||
return;
|
||||
|
||||
var lookupPoly = new Polygon(localAABB);
|
||||
var lookupPoly = new SlimPolygon(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 Polygon(localBounds);
|
||||
var shape = new SlimPolygon(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 Polygon(localAABB);
|
||||
var shape = new SlimPolygon(localAABB);
|
||||
return AnyEntitiesIntersecting(lookupUid, shape, localAABB, Physics.Transform.Empty, flags, ignored, lookup);
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,10 @@ 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))
|
||||
@@ -170,4 +174,32 @@ 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;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public EntityUid? GetGrid(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.GridUid;
|
||||
return !Resolve(entity, ref entity.Comp, logMissing:false) ? null : entity.Comp.GridUid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -124,7 +124,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public MapId GetMapId(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? MapId.Nullspace : entity.Comp.MapID;
|
||||
return !Resolve(entity, ref entity.Comp, logMissing: false) ? MapId.Nullspace : entity.Comp.MapID;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,7 +137,7 @@ public abstract partial class SharedTransformSystem
|
||||
|
||||
public EntityUid? GetMap(Entity<TransformComponent?> entity)
|
||||
{
|
||||
return !Resolve(entity, ref entity.Comp) ? null : entity.Comp.MapUid;
|
||||
return !Resolve(entity, ref entity.Comp, logMissing: false) ? null : entity.Comp.MapUid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,10 +167,10 @@ public abstract partial class SharedTransformSystem
|
||||
/// </summary>
|
||||
public bool InRange(Entity<TransformComponent?> entA, Entity<TransformComponent?> entB, float range)
|
||||
{
|
||||
if (!Resolve(entA, ref entA.Comp))
|
||||
if (!Resolve(entA, ref entA.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
if (!Resolve(entB, ref entB.Comp))
|
||||
if (!Resolve(entB, ref entB.Comp, logMissing: false))
|
||||
return false;
|
||||
|
||||
if (!entA.Comp.ParentUid.IsValid() || !entB.Comp.ParentUid.IsValid())
|
||||
|
||||
@@ -121,9 +121,14 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
|
||||
if (msg.Message is not CloseBoundInterfaceMessage && ui.RequireInputValidation)
|
||||
{
|
||||
var attempt = new BoundUserInterfaceMessageAttempt(sender, uid, msg.UiKey, msg.Message);
|
||||
|
||||
RaiseLocalEvent(attempt);
|
||||
if (attempt.Cancelled)
|
||||
return;
|
||||
|
||||
RaiseLocalEvent(uid, attempt);
|
||||
if (attempt.Cancelled)
|
||||
return;
|
||||
}
|
||||
|
||||
// get the wrapped message and populate it with the sender & UI key information.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
@@ -95,9 +96,15 @@ namespace Robust.Shared.Localization
|
||||
CultureInfo? DefaultCulture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the culture has been loaded.
|
||||
/// Checks if the culture is loaded, if not,
|
||||
/// loads it via <see cref="ILocalizationManager.LoadCulture"/>
|
||||
/// and then set it as <see cref="ILocalizationManager.DefaultCulture"/>.
|
||||
/// </summary>
|
||||
void SetCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if the culture has been loaded.
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
bool HasCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
@@ -106,6 +113,17 @@ namespace Robust.Shared.Localization
|
||||
/// <param name="culture"></param>
|
||||
void LoadCulture(CultureInfo culture);
|
||||
|
||||
/// <summary>
|
||||
/// Loads <see cref="CultureInfo"/> obtained from <see cref="CVars.LocCultureName"/>,
|
||||
/// they are different for client and server, and also can be saved.
|
||||
/// </summary>
|
||||
CultureInfo SetDefaultCulture();
|
||||
|
||||
/// <summary>
|
||||
/// Returns all locale directories from the game's resources.
|
||||
/// </summary>
|
||||
List<CultureInfo> GetFoundCultures();
|
||||
|
||||
/// <summary>
|
||||
/// Sets culture to be used in the absence of the main one.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -12,6 +13,7 @@ using Linguini.Shared.Types.Bundle;
|
||||
using Linguini.Syntax.Ast;
|
||||
using Linguini.Syntax.Parser;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,6 +26,9 @@ namespace Robust.Shared.Localization
|
||||
{
|
||||
internal sealed partial class LocalizationManager : ILocalizationManagerInternal, IPostInjectInit
|
||||
{
|
||||
private static readonly ResPath LocaleDirPath = new("/Locale");
|
||||
|
||||
[Dependency] private readonly IConfigurationManager _configuration = default!;
|
||||
[Dependency] private readonly IResourceManager _res = default!;
|
||||
[Dependency] private readonly ILogManager _log = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
@@ -41,6 +46,18 @@ namespace Robust.Shared.Localization
|
||||
_prototype.PrototypesReloaded += OnPrototypesReloaded;
|
||||
}
|
||||
|
||||
public CultureInfo SetDefaultCulture()
|
||||
{
|
||||
var code = _configuration.GetCVar(CVars.LocCultureName);
|
||||
|
||||
var culture = CultureInfo.GetCultureInfo(code, predefinedOnly: false);
|
||||
SetCulture(culture);
|
||||
|
||||
// Return the culture for further work with it,
|
||||
// like of adding functions
|
||||
return culture;
|
||||
}
|
||||
|
||||
public string GetString(string messageId)
|
||||
{
|
||||
if (_defaultCulture == null)
|
||||
@@ -337,6 +354,17 @@ namespace Robust.Shared.Localization
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCulture(CultureInfo culture)
|
||||
{
|
||||
if (!HasCulture(culture))
|
||||
LoadCulture(culture);
|
||||
|
||||
if (DefaultCulture?.NameEquals(culture) ?? false)
|
||||
return;
|
||||
|
||||
DefaultCulture = culture;
|
||||
}
|
||||
|
||||
public bool HasCulture(CultureInfo culture)
|
||||
{
|
||||
return _contexts.ContainsKey(culture);
|
||||
@@ -344,6 +372,10 @@ namespace Robust.Shared.Localization
|
||||
|
||||
public void LoadCulture(CultureInfo culture)
|
||||
{
|
||||
// Attempting to load an already loaded culture
|
||||
if (HasCulture(culture))
|
||||
throw new InvalidOperationException("Culture is already loaded");
|
||||
|
||||
var bundle = LinguiniBuilder.Builder()
|
||||
.CultureInfo(culture)
|
||||
.SkipResources()
|
||||
@@ -358,6 +390,20 @@ namespace Robust.Shared.Localization
|
||||
DefaultCulture ??= culture;
|
||||
}
|
||||
|
||||
public List<CultureInfo> GetFoundCultures()
|
||||
{
|
||||
var result = new List<CultureInfo>();
|
||||
foreach (var name in _res.ContentGetDirectoryEntries(LocaleDirPath))
|
||||
{
|
||||
// Remove last "/" symbol
|
||||
// Example "en-US/" -> "en-US"
|
||||
var cultureName = name.TrimEnd('/');
|
||||
result.Add(CultureInfo.GetCultureInfo(cultureName, predefinedOnly: false));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void SetFallbackCluture(params CultureInfo[] cultures)
|
||||
{
|
||||
_fallbackCultures = Array.Empty<(CultureInfo, FluentBundle)>();
|
||||
@@ -438,7 +484,7 @@ namespace Robust.Shared.Localization
|
||||
// Load data from .ftl files.
|
||||
// Data is loaded from /Locale/<language-code>/*
|
||||
|
||||
var root = new ResPath($"/Locale/{culture.Name}/");
|
||||
var root = LocaleDirPath / culture.Name;
|
||||
|
||||
var files = resourceManager.ContentFindFiles(root)
|
||||
.Where(c => c.Filename.EndsWith(".ftl", StringComparison.InvariantCultureIgnoreCase))
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace Robust.Shared.Map.Commands;
|
||||
/// </summary>
|
||||
public sealed class AmbientLightCommand : IConsoleCommand
|
||||
{
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||
|
||||
public string Command => $"setambientlight";
|
||||
@@ -32,10 +31,11 @@ public sealed class AmbientLightCommand : IConsoleCommand
|
||||
}
|
||||
|
||||
var mapId = new MapId(mapInt);
|
||||
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
|
||||
|
||||
if (!_mapManager.MapExists(mapId))
|
||||
if (!mapSystem.MapExists(mapId))
|
||||
{
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid"));
|
||||
shell.WriteError(Loc.GetString("cmd-parse-failure-mapid", ("arg", mapId.Value)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ public sealed class AmbientLightCommand : IConsoleCommand
|
||||
}
|
||||
|
||||
var color = Color.FromSrgb(new Color(r, g, b, a));
|
||||
var mapSystem = _systems.GetEntitySystem<SharedMapSystem>();
|
||||
mapSystem.SetAmbientLight(mapId, color);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ 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;
|
||||
|
||||
@@ -224,48 +225,42 @@ internal partial class MapManager
|
||||
|
||||
public void FindGridsIntersecting(EntityUid mapEnt, Box2 worldAABB, GridCallback callback, bool approx = IMapManager.Approximate, bool includeMap = IMapManager.IncludeMap)
|
||||
{
|
||||
var polygon = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldAABB);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldBounds);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldBounds);
|
||||
var polygon = new SlimPolygon(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 = _physics.GetPooled(worldBounds);
|
||||
var polygon = new SlimPolygon(worldBounds);
|
||||
FindGridsIntersecting(mapEnt, polygon, worldBounds.CalcBoundingBox(), Transform.Empty, ref grids, approx, includeMap);
|
||||
_physics.ReturnPooled(polygon);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Robust.Shared.Map
|
||||
|
||||
@@ -27,7 +27,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.PointCount = 1;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = Vector2.Zero; // Also here
|
||||
p0.Id.Key = 0;
|
||||
|
||||
@@ -79,9 +79,9 @@ internal sealed partial class CollisionManager
|
||||
manifold.Type = ManifoldType.Circles;
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.LocalPoint = P;
|
||||
manifold.Points[0].Id.Key = 0;
|
||||
manifold.Points[0].Id.Features = cf;
|
||||
manifold.Points[0].LocalPoint = circleB.Position;
|
||||
manifold.Points._00.Id.Key = 0;
|
||||
manifold.Points._00.Id.Features = cf;
|
||||
manifold.Points._00.LocalPoint = circleB.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,9 +114,9 @@ internal sealed partial class CollisionManager
|
||||
manifold.Type = ManifoldType.Circles;
|
||||
manifold.LocalNormal = Vector2.Zero;
|
||||
manifold.LocalPoint = P;
|
||||
manifold.Points[0].Id.Key = 0;
|
||||
manifold.Points[0].Id.Features = cf;
|
||||
manifold.Points[0].LocalPoint = circleB.Position;
|
||||
manifold.Points._00.Id.Key = 0;
|
||||
manifold.Points._00.Id.Features = cf;
|
||||
manifold.Points._00.LocalPoint = circleB.Position;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,8 +142,8 @@ internal sealed partial class CollisionManager
|
||||
manifold.Type = ManifoldType.FaceA;
|
||||
manifold.LocalNormal = n;
|
||||
manifold.LocalPoint = A;
|
||||
manifold.Points[0].Id.Key = 0;
|
||||
manifold.Points[0].Id.Features = cf;
|
||||
manifold.Points[0].LocalPoint = circleB.Position;
|
||||
manifold.Points._00.Id.Key = 0;
|
||||
manifold.Points._00.Id.Features = cf;
|
||||
manifold.Points._00.LocalPoint = circleB.Position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,15 +238,15 @@ internal sealed partial class CollisionManager
|
||||
}
|
||||
|
||||
var pointCount = 0;
|
||||
var points = manifold.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < 2; ++i)
|
||||
{
|
||||
float separation;
|
||||
|
||||
separation = Vector2.Dot(refFace.normal, clipPoints2[i].V - refFace.v1);
|
||||
var separation = Vector2.Dot(refFace.normal, clipPoints2[i].V - refFace.v1);
|
||||
|
||||
if (separation <= radius)
|
||||
{
|
||||
ref var cp = ref manifold.Points[pointCount];
|
||||
ref var cp = ref points[pointCount];
|
||||
|
||||
if (primaryAxis.Type == EPAxisType.EdgeA)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,9 @@ 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();
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = normals[normalIndex];
|
||||
manifold.LocalPoint = (v1 + v2) * 0.5f;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
@@ -88,7 +88,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = (cLocal - v1).Normalized();
|
||||
manifold.LocalPoint = v1;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
@@ -107,7 +107,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = (cLocal - v2).Normalized();
|
||||
manifold.LocalPoint = v2;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
@@ -126,7 +126,7 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalNormal = normals[vertIndex1];
|
||||
manifold.LocalPoint = faceCenter;
|
||||
|
||||
ref var p0 = ref manifold.Points[0];
|
||||
ref var p0 = ref manifold.Points._00;
|
||||
|
||||
p0.LocalPoint = circleB.Position;
|
||||
p0.Id.Key = 0;
|
||||
|
||||
@@ -238,6 +238,8 @@ internal sealed partial class CollisionManager
|
||||
manifold.LocalPoint = planePoint;
|
||||
|
||||
int pointCount = 0;
|
||||
var points = manifold.Points.AsSpan;
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
{
|
||||
Vector2 value = clipPoints2[i].V;
|
||||
@@ -245,7 +247,7 @@ internal sealed partial class CollisionManager
|
||||
|
||||
if (separation <= totalRadius)
|
||||
{
|
||||
ref var cp = ref manifold.Points[pointCount];
|
||||
ref var cp = ref points[pointCount];
|
||||
cp.LocalPoint = Transform.MulT(xf2, clipPoints2[i].V);
|
||||
cp.Id = clipPoints2[i].ID;
|
||||
|
||||
|
||||
@@ -51,15 +51,18 @@ internal sealed partial class CollisionManager : IManifoldManager
|
||||
in Manifold manifold2)
|
||||
{
|
||||
// Detect persists and removes.
|
||||
var points1 = manifold1.Points.AsSpan;
|
||||
var points2 = manifold2.Points.AsSpan;
|
||||
|
||||
for (int i = 0; i < manifold1.PointCount; ++i)
|
||||
{
|
||||
ContactID id = manifold1.Points[i].Id;
|
||||
var id = points1[i].Id;
|
||||
|
||||
state1[i] = PointState.Remove;
|
||||
|
||||
for (int j = 0; j < manifold2.PointCount; ++j)
|
||||
{
|
||||
if (manifold2.Points[j].Id.Key == id.Key)
|
||||
if (points2[j].Id.Key == id.Key)
|
||||
{
|
||||
state1[i] = PointState.Persist;
|
||||
break;
|
||||
@@ -70,13 +73,13 @@ internal sealed partial class CollisionManager : IManifoldManager
|
||||
// Detect persists and adds.
|
||||
for (int i = 0; i < manifold2.PointCount; ++i)
|
||||
{
|
||||
ContactID id = manifold2.Points[i].Id;
|
||||
var id = points2[i].Id;
|
||||
|
||||
state2[i] = PointState.Add;
|
||||
|
||||
for (int j = 0; j < manifold1.PointCount; ++j)
|
||||
for (var j = 0; j < manifold1.PointCount; ++j)
|
||||
{
|
||||
if (manifold1.Points[j].Id.Key == id.Key)
|
||||
if (points1[j].Id.Key == id.Key)
|
||||
{
|
||||
state2[i] = PointState.Persist;
|
||||
break;
|
||||
|
||||
@@ -21,12 +21,9 @@
|
||||
*/
|
||||
|
||||
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;
|
||||
@@ -46,7 +43,7 @@ internal ref struct DistanceProxy
|
||||
|
||||
// GJK using Voronoi regions (Christer Ericson) and Barycentric coordinates.
|
||||
|
||||
internal DistanceProxy(Vector2[] vertices, float radius)
|
||||
internal DistanceProxy(ReadOnlySpan<Vector2> vertices, float radius)
|
||||
{
|
||||
Vertices = vertices;
|
||||
Radius = radius;
|
||||
@@ -71,9 +68,18 @@ internal ref struct DistanceProxy
|
||||
case ShapeType.Polygon:
|
||||
if (shape is Polygon poly)
|
||||
{
|
||||
Vertices = poly.Vertices.AsSpan()[..poly.VertexCount];
|
||||
Span<Vector2> verts = new Vector2[poly.VertexCount];
|
||||
poly._vertices.AsSpan[..poly.VertexCount].CopyTo(verts);
|
||||
Vertices = verts;
|
||||
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);
|
||||
@@ -151,7 +157,7 @@ internal ref struct DistanceProxy
|
||||
return Vertices[bestIndex];
|
||||
}
|
||||
|
||||
internal static DistanceProxy MakeProxy(Vector2[] vertices, int count, float radius )
|
||||
internal static DistanceProxy MakeProxy(ReadOnlySpan<Vector2> vertices, int count, float radius )
|
||||
{
|
||||
count = Math.Min(count, PhysicsConstants.MaxPolygonVertices);
|
||||
var proxy = new DistanceProxy(vertices[..count], radius);
|
||||
|
||||
@@ -25,6 +25,7 @@ using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Collision;
|
||||
|
||||
@@ -146,7 +147,7 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
/// <summary>
|
||||
/// Points of contact, can only be 0 -> 2.
|
||||
/// </summary>
|
||||
internal ManifoldPoint[] Points;
|
||||
internal FixedArray2<ManifoldPoint> Points;
|
||||
|
||||
public ManifoldType Type;
|
||||
|
||||
@@ -157,9 +158,12 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
LocalNormal.Equals(other.LocalNormal) &&
|
||||
LocalPoint.Equals(other.LocalPoint))) return false;
|
||||
|
||||
var points = Points.AsSpan;
|
||||
var otherPoints = other.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
if (!Points[i].Equals(other.Points[i])) return false;
|
||||
if (!points[i].Equals(otherPoints[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -172,9 +176,12 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
LocalNormal.EqualsApprox(other.LocalNormal) &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint))) return false;
|
||||
|
||||
var points = Points.AsSpan;
|
||||
var otherPoints = other.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
if (!Points[i].EqualsApprox(other.Points[i])) return false;
|
||||
if (!points[i].EqualsApprox(otherPoints[i])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -187,13 +194,26 @@ public struct Manifold : IEquatable<Manifold>, IApproxEquatable<Manifold>
|
||||
LocalNormal.EqualsApprox(other.LocalNormal, tolerance) &&
|
||||
LocalPoint.EqualsApprox(other.LocalPoint, tolerance))) return false;
|
||||
|
||||
var points = Points.AsSpan;
|
||||
var otherPoints = other.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < PointCount; i++)
|
||||
{
|
||||
if (!Points[i].EqualsApprox(other.Points[i], tolerance)) return false;
|
||||
if (!points[i].EqualsApprox(otherPoints[i], tolerance)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Manifold manifold && Equals(manifold);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(LocalNormal, LocalPoint, PointCount, Points, (int)Type);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ManifoldPoint : IEquatable<ManifoldPoint>, IApproxEquatable<ManifoldPoint>
|
||||
|
||||
@@ -173,10 +173,25 @@ 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 = poly.Vertices;
|
||||
Normals = poly.Normals;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -150,6 +150,15 @@ 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>
|
||||
@@ -197,16 +206,19 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
// Match old contact ids to new contact ids and copy the
|
||||
// stored impulses to warm start the solver.
|
||||
var points = Manifold.Points.AsSpan;
|
||||
var oldPoints = oldManifold.Points.AsSpan;
|
||||
|
||||
for (var i = 0; i < Manifold.PointCount; ++i)
|
||||
{
|
||||
var mp2 = Manifold.Points[i];
|
||||
var mp2 = points[i];
|
||||
mp2.NormalImpulse = 0.0f;
|
||||
mp2.TangentImpulse = 0.0f;
|
||||
var id2 = mp2.Id;
|
||||
|
||||
for (var j = 0; j < oldManifold.PointCount; ++j)
|
||||
{
|
||||
var mp1 = oldManifold.Points[j];
|
||||
var mp1 = oldPoints[j];
|
||||
|
||||
if (mp1.Id.Key == id2.Key)
|
||||
{
|
||||
@@ -216,7 +228,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
}
|
||||
}
|
||||
|
||||
Manifold.Points[i] = mp2;
|
||||
points[i] = mp2;
|
||||
}
|
||||
|
||||
if (touching != wasTouching)
|
||||
@@ -408,6 +420,28 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
[Pure, PublicAPI]
|
||||
public PhysicsComponent OurBody(EntityUid uid)
|
||||
{
|
||||
if (uid == EntityA)
|
||||
return BodyA!;
|
||||
else if (uid == EntityB)
|
||||
return BodyB!;
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
[Pure, PublicAPI]
|
||||
public PhysicsComponent OtherBody(EntityUid uid)
|
||||
{
|
||||
if (uid == EntityA)
|
||||
return BodyB!;
|
||||
else if (uid == EntityB)
|
||||
return BodyA!;
|
||||
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
[Flags]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user