Compare commits

...

55 Commits

Author SHA1 Message Date
Pieter-Jan Briers
ab99d5be07 Version: 219.2.2 2024-08-11 19:32:35 +02:00
Pieter-Jan Briers
11c05509cb Compile compat fixes
(cherry picked from commit 025d90d281)
(cherry picked from commit 799702b814)
(cherry picked from commit 4600ee8e5788891f1b610e2d5141fb4e1228d323)
2024-08-11 19:32:35 +02:00
Pieter-Jan Briers
e1120b0d4c Version: 219.2.1 2024-08-11 17:56:08 +02:00
Pieter-Jan Briers
73c22ff6ac Security updates (#5353)
* Fix security bug in WritableDirProvider.OpenOsWindow()

Reported by @NarryG and @nyeogmi

* Sandbox updates

* Update ImageSharp again

(cherry picked from commit 7d778248ee)
(cherry picked from commit f66cda74e95619ddba2221bda644bf4394619805)
(cherry picked from commit db8ba83866c523e08e4fba0b80cd954f4f190613)
2024-08-11 17:56:08 +02:00
metalgearsloth
eb63809999 Version: 219.2.0 2024-04-25 00:31:04 +10:00
Leon Friedrich
4c3c74865c Fix yaml linter & improve validation of static fields (#5056) 2024-04-25 00:27:02 +10:00
Nemanja
b624f5b70f Add SetMapCoordinates (#5065)
* add set map coordinates function

* mmmmmmm yes

* Update Robust.Shared/GameObjects/Systems/SharedTransformSystem.Component.cs

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-04-24 22:55:58 +10:00
Pieter-Jan Briers
6566a7658a Fix DebugCoordsPanel freezing when hovering a UI control.
It would bail out of the entire update logic if you aren't hovering over a map position, which isn't great when the control displays far more than in-simulation mouse position info.
2024-04-23 20:34:29 +02:00
metalgearsloth
9e3e1cc929 Optimise physics networking a lot (#5064)
Avoids unnecessarily dirtying every single tick when a mob is moving. Also avoids the getcomponent every time which is nice.
2024-04-23 22:36:40 +10:00
ElectroJr
4e87d93009 Version: 219.1.3 2024-04-23 00:26:17 -04:00
Leon Friedrich
1031ae4cc5 Fix mapping not pausing maps (#5063) 2024-04-23 14:25:40 +10:00
ElectroJr
73da147b88 Version: 219.1.2 2024-04-21 04:58:44 -04:00
Leon Friedrich
0ab59d70b1 More mapinit fixes (#5058) 2024-04-21 18:57:20 +10:00
ElectroJr
8e8470ac7e Version: 219.1.1 2024-04-21 02:50:34 -04:00
Leon Friedrich
15f94bd094 Fix mapinit persistence when overwriting existing maps (#5057)
* Fix mapinit persistence when overwriting existing maps

* Hours wasted chasing fucking chickens in a crate
2024-04-21 16:48:39 +10:00
DrSmugleaf
68888c4370 Make remaining IPrototypes partial (#5053) 2024-04-21 07:39:54 +10:00
ElectroJr
19f87dfbb3 Version: 219.1.0 2024-04-20 16:52:47 -04:00
Leon Friedrich
68e5b6924d Add ComponentRegistry overrides to more entity spawn methods (#5051) 2024-04-19 13:16:17 +10:00
Leon Friedrich
9f913cd2d9 Fix RecursiveMapInit (#5052)
* Fix RecursiveMapInit

* re-use sawmill
2024-04-19 13:15:42 +10:00
Vasilis
ec37d1c137 Auth is now required by default (#5050) 2024-04-18 17:51:25 +02:00
metalgearsloth
ea58924495 Audio stuff (#5048)
Better overlay debug and uses the adjusted distance for cutoff instead.
2024-04-18 14:36:29 +10:00
keronshb
c5aa735506 Adds rotation to Map Position spawns (#5047)
* add angle to rotation

* fixes inheritance

* proxy

* Fixes maprot

* changes angle to default and uses set coords overload
2024-04-18 14:25:07 +10:00
metalgearsloth
f5a6e52c7f Version: 219.0.0 2024-04-18 14:16:40 +10:00
Leon Friedrich
d5c4981648 Partial MapManager refactor (#5042)
* MapManager rejig

* Update Tests

* A
2024-04-18 14:05:02 +10:00
Pieter-Jan Briers
8c6170661d Die 2024-04-18 03:11:24 +02:00
metalgearsloth
1901059755 Version: 218.2.0 2024-04-17 19:06:59 +10:00
metalgearsloth
8cdec92be6 Add slider locking (#5044)
* Add slider locking

Useful if you just want like a playback slider without the client intefering.

* Name alignment
2024-04-17 19:04:52 +10:00
metalgearsloth
0a00e7ec29 Network audio states (#5024)
* Network audio states

Lets server pause or stop audio or whatever.

* Better API and state fix

* Bunch of fixes

- TimedDespawn proccing too early.
- PlaybackPosition setting
- SetState setting.

* Clamps and despawn fixes

* fix
2024-04-17 17:55:22 +10:00
DrSmugleaf
8c25a83066 Expose worldPosition in SpriteComponent.Render (#5043)
* Expose worldPosition in SpriteComponent.Render

* Set default value to zero
2024-04-17 16:17:12 +10:00
Pieter-Jan Briers
0fadfc2d9b Allow control layout properties to be set via style sheet. (take 2) (#5037)
* Reapply "Allow control layout properties to be set via style sheet." (#5035)

This reverts commit af36d24892.

* Fix tests

MaxSize properties had wrong default. Oops.
2024-04-17 00:42:12 +02:00
metalgearsloth
a6e7224672 Version: 218.1.0 2024-04-16 22:39:23 +10:00
metalgearsloth
37796f4806 Add a Vertical tab container (#4948)
* Vertical tab container

* weh

* Tab review
2024-04-16 22:33:43 +10:00
Pieter-Jan Briers
a5494d1df2 Change default hub URL 2024-04-15 22:27:25 +02:00
Errant
e2525a2103 Fix division remainder issue in color.cs (#5040) 2024-04-15 19:13:43 +02:00
Pieter-Jan Briers
b50f68866f Enable roslyn extension tests in CI (#5038)
* Enable roslyn extension tests in CI

* I'll be real I kinda just hoped that last one would work. dotnet test's --help documentation is useless garbage so I couldn't tell if that was supported or not. Guess not.

* Actually fix the Roslyn tests.

As far as I can tell, Roslyn tests haven't worked since #2976.

The tests used a pretty awful technique of linking the test code against the analyzer, so that the analyzer's copy of the relevant attributes got included into the test. This then broke when the namespace got changed by the linked PR.

Now the tests get an EmbeddedResource for the necessary test files compiled instead.

Also applied this to DependencyAssignAnalyzerTest because why not.
2024-04-14 09:26:07 +02:00
Pieter-Jan Briers
03a4d3e0a0 Add IEquatable`1.Equals to sandbox
Why wasn't this in here wtf.
2024-04-14 08:36:25 +02:00
metalgearsloth
af8fb52a4f Version: 218.0.0 2024-04-14 15:00:07 +10:00
metalgearsloth
fd60dc2887 Add EntManager + ProtoManager helpers for random picks (#4869)
* Add EntManager + ProtoManager helpers for random picks

Lets us cleanup content a bit from these being re-implemented.

* weh
2024-04-14 14:55:55 +10:00
metalgearsloth
cd24fd46b6 Default worldpos to null (#5036)
I think this is slightly more robust than defaulting to 0 actually while still fixing the issue.
2024-04-14 14:47:30 +10:00
metalgearsloth
44cc7127fa Default SetWorldPos to 0 rotation (#5034)
More closely aligns with the old default.
2024-04-14 14:28:16 +10:00
metalgearsloth
af36d24892 Revert "Allow control layout properties to be set via style sheet." (#5035)
This reverts commit 8c4deb2067.

# Conflicts:
#	RELEASE-NOTES.md
2024-04-14 14:14:44 +10:00
Leon Friedrich
caa8ff0f2d Modify container/spawn helper methods (#5030)
* Modify container/spawn helper methods

* A
2024-04-14 13:47:21 +10:00
Pieter-Jan Briers
cd67c67a5c Add analyzer to warn for assignment to dependency fields. 2024-04-14 05:14:12 +02:00
deltanedas
57b328e8c2 add CopyData to appearance system (#5022)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-04-14 02:24:31 +02:00
Pieter-Jan Briers
4874b1db68 Update UI themes on prototype reload. 2024-04-14 02:15:53 +02:00
Pieter-Jan Briers
814ad08884 Allow scaling the line height of a RichTextLabel 2024-04-14 02:10:01 +02:00
Pieter-Jan Briers
8c4deb2067 Allow control layout properties to be set via style sheet.
This works by setting the stylesheet values into the regular control property fields when updated. This means 0 performance overhead except when updating styles, and even then it's probably negligible. A bitfield is used to track which properties are set and how.

This code is all done manual for now. I wanted to make a source gen for this but couldn't be arsed at the moment. The code manually written here is basically what a future source gen would generate optimally.
2024-04-14 02:09:00 +02:00
Pieter-Jan Briers
f6a5120e56 Fix exception when inspecting element in some cases.
Happened when I was trying to develop item status stuff.
2024-04-14 02:05:40 +02:00
Pieter-Jan Briers
c1b8bf8e52 Make StatusHost request headers case insensitive
Woops that's how HTTP is supposed to work.
2024-04-11 02:28:46 +02:00
Pieter-Jan Briers
4b193bad26 Add non-generic GetCVar.
Surprised we didn't have this.
2024-04-11 02:25:40 +02:00
Tayrtahn
8db3da4852 Add Type tracking to FieldNotFoundErrorNode (#5032)
* Add Type tracking to FieldNotFoundErrorNode

* Suggested changes, plus xmldoc and primary constructor conversion.
2024-04-08 19:27:02 +02:00
Leon Friedrich
0c271fc2f8 Remove uneccesary Exists() checks in container system (#5031)
* Remove `Exists()` checks in container system

* A

* A
2024-04-08 01:24:09 +02:00
Pieter-Jan Briers
ed406c06b7 Fix HTTP errors on watchdog ping not being reported 2024-04-05 23:23:46 +02:00
Pieter-Jan Briers
b31940b489 Improve logging for watchdog pinsg 2024-04-05 02:22:06 +02:00
Pieter-Jan Briers
84360c653d Add better environment variable config system
ROBUST_CVARS had multiple issues:

* Not composable, i.e. two independent systems can't easily layer CVars to set as they all have to go into one var
* Not sanitary, there's no way to store things that have a ";" in them because it'd always get used as separator.

This adds a new ROBUST_CVAR_* system. For example I can set ROBUST_CVAR_game__hostname=foobar to set a CVar via a single env var. A double underscore ("__") is replaced with a period to make the CVar names safe for environment variables.

Also made Robust.Shared.Configuration.EnvironmentVariables internal because wtf that should not be public no.
2024-04-04 02:24:57 +02:00
164 changed files with 2465 additions and 1408 deletions

View File

@@ -27,7 +27,8 @@ jobs:
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Test Engine
- name: Robust.UnitTesting
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
- name: Robust.Analyzers.Tests
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0

View File

@@ -33,10 +33,10 @@ jobs:
mkdir "release/${{ steps.parse_version.outputs.version }}"
mv release/*.zip "release/${{ steps.parse_version.outputs.version }}"
- name: Upload files to centcomm
- name: Upload files to Suns
uses: appleboy/scp-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
source: "release/${{ steps.parse_version.outputs.version }}"
@@ -46,7 +46,7 @@ jobs:
- name: Update manifest JSON
uses: appleboy/ssh-action@master
with:
host: centcomm.spacestation14.io
host: suns.spacestation14.com
username: robust-build-push
key: ${{ secrets.CENTCOMM_ROBUST_BUILDS_PUSH_KEY }}
script: /home/robust-build-push/push.ps1 ${{ steps.parse_version.outputs.version }}

View File

@@ -1,4 +1,14 @@
<Project>
<PropertyGroup>
<!--
We actually set ManagePackageVersionsCentrally manually in another import file.
Since .NET SDK 8.0.300, ManagePackageVersionsCentrally is automatically set if Directory.Packages.props exists.
https://github.com/NuGet/NuGet.Client/pull/5572
We actively negate this here, as we have some packages in tree we don't want such automatic behavior for.
We use Directory.Build.props to get copy the state *after* our MSBuild config but before Nuget's config.
-->
<ManagePackageVersionsCentrally />
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="BenchmarkDotNet" Version="0.13.12" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
@@ -45,7 +55,7 @@
<PackageVersion Include="Serilog" Version="3.1.1" />
<PackageVersion Include="Serilog.Sinks.Loki" Version="4.0.0-beta3" />
<PackageVersion Include="SharpZstd.Interop" Version="1.5.2-beta2" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.3" />
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" />
<PackageVersion Include="SpaceWizards.HttpListener" Version="0.1.0" />
<PackageVersion Include="SpaceWizards.NFluidsynth" Version="0.1.1" />
<PackageVersion Include="SpaceWizards.SharpFont" Version="1.0.2" />

View File

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

View File

@@ -54,6 +54,134 @@ END TEMPLATE-->
*None yet*
## 219.2.2
## 219.2.1
## 219.2.0
### New features
* Add SetMapCoordinates to TransformSystem.
* Improve YAML Linter and validation of static fields.
### Bugfixes
* Fix DebugCoordsPanel freezing when hovering a control.
### Other
* Optimise physics networking to not dirty every tick of movement.
## 219.1.3
### Bugfixes
* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map.
## 219.1.2
### Bugfixes
* Fix map-loader not map-initialising grids when loading into a post-init map.
## 219.1.1
### Bugfixes
* Fix map-loader not map-initialising maps when overwriting a post-init map.
## 219.1.0
### New features
* Added a new optional arguments to various entity spawning methods, including a new argument to set the entity's rotation.
### Bugfixes
* Fixes map initialisation not always initialising all entities on a map.
### Other
* The default value of the `auth.mode` cvar has changed
## 219.0.0
### Breaking changes
* Move most IMapManager functionality to SharedMapSystem.
## 218.2.0
### New features
* Control layout properties such as `Margin` can now be set via style sheets.
* Expose worldposition in SpriteComponent.Render
* Network audio entity Play/Pause/Stop states and playback position.
* Add `Disabled` functionality to `Slider` control.
## 218.1.0
### New features
* Add IEquatable.Equals to the sandbox.
* Enable roslyn extensions tests in CI.
* Add a VerticalTabContainer control to match the horizontal one.
### Bugfixes
* Fix divison remainder issue for Colors, fixing purples.
### Other
* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`.
## 218.0.0
### Breaking changes
* `Robust.Shared.Configuration.EnvironmentVariables` is now internal and no longer usable by content.
### New features
* Add TryGetRandom to EntityManager to get a random entity with the specified component and TryGetRandom to IPrototypeManager to return a random prototype of the specified type.
* Add CopyData to AppearanceSystem.
* Update UI themes on prototype reloads.
* Allow scaling the line height of a RichTextLabel.
* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".".
* Added non-generic variant of `GetCVar` to `IConfigurationManager`.
* Add type tracking to FieldNotFoundErrorNode for serialization.
* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`.
* UI theme prototypes are now updated when reloaded.
* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields.
### Bugfixes
* Request headers in `IStatusHandlerContext` are now case-insensitive.
* SetWorldPosition rotation now more closely aligns with prior behavior.
* Fix exception when inspecting elements in some cases.
* Fix HTTP errors on watchdog ping not being reported.
### Other
* Add an analyzer for redundantly assigning to dependency fields.
### Internal
* Remove redundant Exists checks in ContainerSystem.
* Improve logging on watchdog pings.
## 217.2.1
### Bugfixes
@@ -69,7 +197,7 @@ END TEMPLATE-->
### New features
* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes.
* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes.
* Add double-clicking to LineEdit.
### Bugfixes

View File

@@ -20,11 +20,16 @@ public sealed class AccessAnalyzer_Test
{
TestState =
{
AdditionalReferences = { typeof(AccessAnalyzer).Assembly },
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.Analyzers.AccessAttribute.cs",
"Robust.Shared.Analyzers.AccessPermissions.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);

View File

@@ -0,0 +1,58 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using NUnit.Framework;
using VerifyCS =
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.DependencyAssignAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
public sealed class DependencyAssignAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DependencyAssignAnalyzer, NUnitVerifier>()
{
TestState =
{
Sources = { code }
},
};
TestHelper.AddEmbeddedSources(
test.TestState,
"Robust.Shared.IoC.DependencyAttribute.cs"
);
// ExpectedDiagnostics cannot be set, so we need to AddRange here...
test.TestState.ExpectedDiagnostics.AddRange(expected);
return test.RunAsync();
}
[Test]
public async Task Test()
{
const string code = """
using Robust.Shared.IoC;
public sealed class Foo
{
[Dependency]
private object? Field;
public Foo()
{
Field = "A";
}
}
""";
await Verifier(code,
// /0/Test0.cs(10,9): warning RA0025: Tried to assign to [Dependency] field 'Field'. Remove [Dependency] or inject it via field injection instead.
VerifyCS.Diagnostic().WithSpan(10, 9, 10, 20).WithArguments("Field"));
}
}

View File

@@ -6,6 +6,13 @@
<Import Project="..\MSBuild\Robust.Properties.targets"/>
<Import Project="..\MSBuild\Robust.Engine.props"/>
<!-- Engine source files needed to make the tests work -->
<ItemGroup>
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LogicalName="Robust.Shared.Analyzers.AccessAttribute.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LogicalName="Robust.Shared.Analyzers.AccessPermissions.cs" LinkBase="Implementations" />
<EmbeddedResource Include="..\Robust.Shared\IoC\DependencyAttribute.cs" LogicalName="Robust.Shared.IoC.DependencyAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Text;
namespace Robust.Analyzers.Tests;
public static class TestHelper
{
public static void AddEmbeddedSources(SolutionState state, params string[] embeddedFiles)
{
AddEmbeddedSources(state, (IEnumerable<string>) embeddedFiles);
}
public static void AddEmbeddedSources(SolutionState state, IEnumerable<string> embeddedFiles)
{
foreach (var fileName in embeddedFiles)
{
using var stream = typeof(AccessAnalyzer_Test).Assembly.GetManifestResourceStream(fileName)!;
state.Sources.Add((fileName, SourceText.From(stream)));
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DependencyAssignAnalyzer : DiagnosticAnalyzer
{
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
private static readonly DiagnosticDescriptor Rule = new (
Diagnostics.IdDependencyFieldAssigned,
"Assignment to dependency field",
"Tried to assign to [Dependency] field '{0}'. Remove [Dependency] or inject it via field injection instead.",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.RegisterOperationAction(CheckAssignment, OperationKind.SimpleAssignment);
}
private static void CheckAssignment(OperationAnalysisContext context)
{
if (context.Operation is not ISimpleAssignmentOperation assignment)
return;
if (assignment.Target is not IFieldReferenceOperation fieldRef)
return;
var field = fieldRef.Field;
var attributes = field.GetAttributes();
if (attributes.Length == 0)
return;
var depAttribute = context.Compilation.GetTypeByMetadataName(DependencyAttributeType);
if (!HasAttribute(attributes, depAttribute))
return;
context.ReportDiagnostic(Diagnostic.Create(Rule, assignment.Syntax.GetLocation(), field.Name));
}
private static bool HasAttribute(ImmutableArray<AttributeData> attributes, ISymbol symbol)
{
foreach (var attribute in attributes)
{
if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, symbol))
return true;
}
return false;
}
}

View File

@@ -2,24 +2,29 @@
<ItemGroup>
<!-- Needed for NotNullableFlagAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" />
<Compile Include="..\Robust.Shared\Analyzers\NotNullableFlagAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for FriendAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" />
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" />
<Compile Include="..\Robust.Shared\Analyzers\AccessAttribute.cs" LinkBase="Implementations" />
<Compile Include="..\Robust.Shared\Analyzers\AccessPermissions.cs" LinkBase="Implementations" />
</ItemGroup>
<ItemGroup>
<!-- Needed for PreferGenericVariantAnalyzer. -->
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" />
<Compile Include="..\Robust.Shared\Analyzers\PreferGenericVariantAttribute.cs" LinkBase="Implementations" />
</ItemGroup>
<Import Project="../Robust.Roslyn.Shared/Robust.Roslyn.Shared.props" />
<PropertyGroup>
<Nullable>disable</Nullable>
<!--
Rider seems to get really confused with hot reload if we directly compile in the above-linked classes.
As such, they have an #if to change their namespace in this project.
-->
<DefineConstants>$(DefineConstants);ROBUST_ANALYZERS_IMPL</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -26,9 +26,8 @@ public partial class AddRemoveComponentBenchmark
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
var map = _simulation.CreateMap().Uid;
var coords = new EntityCoordinates(map, default);
for (var i = 0; i < N; i++)
{

View File

@@ -29,8 +29,8 @@ public partial class ComponentIteratorBenchmark
Comps = new A[N+2];
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
var map = _simulation.CreateMap().MapId;
var coords = new MapCoordinates(default, map);
for (var i = 0; i < N; i++)
{

View File

@@ -31,8 +31,8 @@ public partial class GetComponentBenchmark
Comps = new A[N+2];
var coords = new MapCoordinates(0, 0, new MapId(1));
_simulation.AddMap(coords.MapId);
var map = _simulation.CreateMap().Uid;
var coords = new EntityCoordinates(map, default);
for (var i = 0; i < N; i++)
{

View File

@@ -29,10 +29,9 @@ public partial class SpawnDeleteEntityBenchmark
.InitializeInstance();
_entityManager = _simulation.Resolve<IEntityManager>();
_mapCoords = new MapCoordinates(0, 0, new MapId(1));
var uid = _simulation.AddMap(_mapCoords.MapId);
_entCoords = new EntityCoordinates(uid, 0, 0);
var (map, mapId) = _simulation.CreateMap();
_mapCoords = new MapCoordinates(default, mapId);
_entCoords = new EntityCoordinates(map, 0, 0);
}
[Benchmark(Baseline = true)]

View File

@@ -91,8 +91,7 @@ public class RecursiveMoveBenchmark : RobustIntegrationTest
// Set up map and spawn player
server.WaitPost(() =>
{
var mapId = mapMan.CreateMap();
var map = mapMan.GetMapEntityId(mapId);
var map = server.ResolveDependency<SharedMapSystem>().CreateMap(out var mapId);
var gridComp = mapMan.CreateGridEntity(mapId);
var grid = gridComp.Owner;
mapSys.SetTile(grid, gridComp, Vector2i.Zero, new Tile(1));

View File

@@ -74,11 +74,13 @@ public sealed class AudioOverlay : Overlay
output.Clear();
output.AppendLine("Audio Source");
output.AppendLine("Runtime:");
output.AppendLine($"- Distance: {_audio.GetAudioDistance(distance.Length()):0.00}");
output.AppendLine($"- Occlusion: {posOcclusion:0.0000}");
output.AppendLine("Params:");
output.AppendLine($"- RolloffFactor: {comp.RolloffFactor:0.0000}");
output.AppendLine($"- Volume: {comp.Volume:0.0000}");
output.AppendLine($"- Reference distance: {comp.ReferenceDistance}");
output.AppendLine($"- Max distance: {comp.MaxDistance}");
output.AppendLine($"- Reference distance: {comp.ReferenceDistance:0.00}");
output.AppendLine($"- Max distance: {comp.MaxDistance:0.00}");
var outputText = output.ToString().Trim();
var dimensions = screenHandle.GetDimensions(_font, outputText, 1f);
var buffer = new Vector2(3f, 3f);

View File

@@ -126,6 +126,33 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
component.Source.SetAuxiliary(null);
}
switch (component.State)
{
case AudioState.Playing:
component.StartPlaying();
break;
case AudioState.Paused:
component.Pause();
break;
case AudioState.Stopped:
component.StopPlaying();
component.PlaybackPosition = 0f;
break;
}
// If playback position changed then update it.
if (!string.IsNullOrEmpty(component.FileName))
{
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
var currentPosition = component.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
if (diff > 0.1f)
{
component.PlaybackPosition = position;
}
}
}
/// <summary>
@@ -361,7 +388,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
var distance = delta.Length();
// Out of range so just clip it for us.
if (distance > component.MaxDistance)
if (GetAudioDistance(distance) > component.MaxDistance)
{
// Still keeps the source playing, just with no volume.
component.Gain = 0f;

View File

@@ -314,6 +314,8 @@ public abstract class BaseAudioSource : IAudioSource
set
{
_checkDisposed();
value = MathF.Max(value, 0f);
AL.Source(SourceHandle, ALSourcef.SecOffset, value);
Master._checkAlError($"Tried to set invalid playback position of {value:0.00}");
}

View File

@@ -285,6 +285,7 @@ namespace Robust.Client
/// <summary>
/// Enumeration of the run levels of the BaseClient.
/// </summary>
/// <seealso cref="ClientRunLevelExt"/>
public enum ClientRunLevel : byte
{
Error = 0,
@@ -315,6 +316,21 @@ namespace Robust.Client
SinglePlayerGame,
}
/// <summary>
/// Helper functions for working with <see cref="ClientRunLevel"/>.
/// </summary>
public static class ClientRunLevelExt
{
/// <summary>
/// Check if a <see cref="ClientRunLevel"/> is <see cref="ClientRunLevel.InGame"/>
/// or <see cref="ClientRunLevel.SinglePlayerGame"/>.
/// </summary>
public static bool IsInGameLike(this ClientRunLevel runLevel)
{
return runLevel is ClientRunLevel.InGame or ClientRunLevel.SinglePlayerGame;
}
}
/// <summary>
/// Event arguments for when something changed with the player.
/// </summary>

View File

@@ -11,7 +11,7 @@ namespace Robust.Client.Console.Commands
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
var type = Type.GetType(args[0]);
var type = GetType(args[0]);
if (type == null)
{
@@ -25,6 +25,17 @@ namespace Robust.Client.Console.Commands
shell.WriteLine(sig);
}
}
private Type? GetType(string name)
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
if (assembly.GetType(name) is { } type)
return type;
}
return null;
}
}
#endif
}

View File

@@ -8,11 +8,9 @@ using System.Text;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Animations;
using Robust.Shared.ComponentTrees;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -1237,9 +1235,9 @@ namespace Robust.Client.GameObjects
public IEnumerable<ISpriteLayer> AllLayers => Layers;
// Lobby SpriteView rendering path
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default)
{
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection);
}
[DataField("noRot")] private bool _screenLock = false;

View File

@@ -1,12 +1,9 @@
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.Physics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Client.GameObjects;
@@ -16,6 +13,17 @@ public sealed class MapSystem : SharedMapSystem
[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.
var id = new MapId(--LastMapId);
while (MapManager.MapExists(id))
{
id = new MapId(--LastMapId);
}
return id;
}
public override void Initialize()
{
base.Initialize();
@@ -27,9 +35,4 @@ public sealed class MapSystem : SharedMapSystem
base.Shutdown();
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
}

View File

@@ -1138,7 +1138,7 @@ namespace Robust.Client.GameStates
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container))
{
containerSys.Remove((ent.Value, xform, meta), container, false, true);
}
@@ -1329,23 +1329,8 @@ namespace Robust.Client.GameStates
foreach (var (comp, cur, next) in _compStateWork.Values)
{
try
{
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
}
#pragma warning disable CS0168 // Variable is declared but never used
catch (Exception e)
#pragma warning restore CS0168 // Variable is declared but never used
{
#if EXCEPTION_TOLERANCE
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
#else
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
throw;
#endif
}
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
}
}
@@ -1414,7 +1399,7 @@ namespace Robust.Client.GameStates
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
{
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container, null, true);
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
}
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);

View File

@@ -124,7 +124,7 @@ namespace Robust.Client.Graphics.Clyde
{
foreach (var (grid, chunks) in _mapChunkData)
{
var gridComp = _mapManager.GetGridComp(grid);
var gridComp = _entityManager.GetComponent<MapGridComponent>(grid);
foreach (var (index, chunk) in chunks)
{
if (!chunk.Dirty || gridComp.Chunks.ContainsKey(index))

View File

@@ -251,10 +251,8 @@ namespace Robust.Client.Graphics.Clyde
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
{
if (mapId == MapId.Nullspace)
return;
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
var worldOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceEntities);

View File

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

View File

@@ -0,0 +1,132 @@
using System;
using System.Runtime.CompilerServices;
namespace Robust.Client.UserInterface;
public partial class Control
{
private LayoutStyleProperties _layoutStyleOverride;
private LayoutStyleProperties _layoutStyleSheet;
private void UpdateLayoutStyleProperties()
{
var propertiesSet = LayoutStyleProperties.None;
// Assumed most controls will have little or no style properties,
// so iterating once is less expensive overall then checking 10+ properties.
// C# switch statements are compiled efficiently anyways.
foreach (var (key, value) in _styleProperties)
{
switch (key)
{
case nameof(SizeFlagsStretchRatio):
UpdateField(ref _sizeFlagsStretchRatio, value, LayoutStyleProperties.StretchRatio);
break;
case nameof(MinWidth):
UpdateField(ref _minWidth, value, LayoutStyleProperties.MinWidth);
break;
case nameof(MinHeight):
UpdateField(ref _minHeight, value, LayoutStyleProperties.MinHeight);
break;
case nameof(SetWidth):
UpdateField(ref _setWidth, value, LayoutStyleProperties.SetWidth);
break;
case nameof(SetHeight):
UpdateField(ref _setHeight, value, LayoutStyleProperties.SetHeight);
break;
case nameof(MaxWidth):
UpdateField(ref _maxWidth, value, LayoutStyleProperties.MaxWidth);
break;
case nameof(MaxHeight):
UpdateField(ref _maxHeight, value, LayoutStyleProperties.MaxHeight);
break;
case nameof(HorizontalExpand):
UpdateField(ref _horizontalExpand, value, LayoutStyleProperties.HorizontalExpand);
break;
case nameof(VerticalExpand):
UpdateField(ref _verticalExpand, value, LayoutStyleProperties.VerticalExpand);
break;
case nameof(HorizontalAlignment):
UpdateField(ref _horizontalAlignment, value, LayoutStyleProperties.HorizontalAlignment);
break;
case nameof(VerticalAlignment):
UpdateField(ref _verticalAlignment, value, LayoutStyleProperties.VerticalAlignment);
break;
case nameof(Margin):
UpdateField(ref _margin, value, LayoutStyleProperties.Margin);
break;
}
}
// Reset cleared properties back to defaults.
var toClear = _layoutStyleSheet & ~propertiesSet;
if (toClear != 0)
{
ClearField(ref _sizeFlagsStretchRatio, DefaultStretchRatio, LayoutStyleProperties.StretchRatio);
ClearField(ref _minWidth, 0, LayoutStyleProperties.MinWidth);
ClearField(ref _minHeight, 0, LayoutStyleProperties.MinHeight);
ClearField(ref _setWidth, DefaultSetSize, LayoutStyleProperties.SetWidth);
ClearField(ref _setHeight, DefaultSetSize, LayoutStyleProperties.SetHeight);
ClearField(ref _maxWidth, DefaultMaxSize, LayoutStyleProperties.MaxWidth);
ClearField(ref _maxHeight, DefaultMaxSize, LayoutStyleProperties.MaxHeight);
ClearField(ref _horizontalExpand, false, LayoutStyleProperties.HorizontalExpand);
ClearField(ref _verticalExpand, false, LayoutStyleProperties.VerticalExpand);
ClearField(ref _horizontalAlignment, DefaultHAlignment, LayoutStyleProperties.HorizontalAlignment);
ClearField(ref _verticalAlignment, DefaultVAlignment, LayoutStyleProperties.VerticalAlignment);
ClearField(ref _margin, default, LayoutStyleProperties.Margin);
}
_layoutStyleSheet = propertiesSet;
return;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void UpdateField<T>(ref T field, object value, LayoutStyleProperties flag)
{
if ((_layoutStyleOverride & flag) != 0)
return;
// TODO: Probably need better error handling...
if (value is not T valueCast)
return;
field = valueCast;
propertiesSet |= flag;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ClearField<T>(ref T field, T defaultValue, LayoutStyleProperties flag)
{
if ((toClear & flag) == 0)
return;
field = defaultValue;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetLayoutStyleProp(LayoutStyleProperties flag)
{
_layoutStyleOverride |= flag;
}
[Flags]
private enum LayoutStyleProperties : short
{
// @formatter:off
None = 0,
Margin = 1 << 0,
MinWidth = 1 << 1,
MinHeight = 1 << 2,
SetWidth = 1 << 3,
SetHeight = 1 << 4,
MaxWidth = 1 << 5,
MaxHeight = 1 << 6,
StretchRatio = 1 << 7,
HorizontalExpand = 1 << 8,
VerticalExpand = 1 << 9,
HorizontalAlignment = 1 << 10,
VerticalAlignment = 1 << 11,
// @formatter:on
}
}

View File

@@ -12,24 +12,30 @@ namespace Robust.Client.UserInterface
public partial class Control
{
private const float DefaultStretchRatio = 1;
private const float DefaultSetSize = float.NaN;
private const float DefaultMaxSize = float.PositiveInfinity;
private const HAlignment DefaultHAlignment = HAlignment.Stretch;
private const VAlignment DefaultVAlignment = VAlignment.Stretch;
private Vector2 _size;
[ViewVariables] internal Vector2? PreviousMeasure;
[ViewVariables] internal UIBox2? PreviousArrange;
private float _sizeFlagsStretchRatio = 1;
private float _sizeFlagsStretchRatio = DefaultStretchRatio;
private float _minWidth;
private float _minHeight;
private float _setWidth = float.NaN;
private float _setHeight = float.NaN;
private float _maxWidth = float.PositiveInfinity;
private float _maxHeight = float.PositiveInfinity;
private float _setWidth = DefaultSetSize;
private float _setHeight = DefaultSetSize;
private float _maxWidth = DefaultMaxSize;
private float _maxHeight = DefaultMaxSize;
private bool _horizontalExpand;
private bool _verticalExpand;
private HAlignment _horizontalAlignment = HAlignment.Stretch;
private VAlignment _verticalAlignment = VAlignment.Stretch;
private HAlignment _horizontalAlignment = DefaultHAlignment;
private VAlignment _verticalAlignment = DefaultVAlignment;
private Thickness _margin;
private bool _measuring;
private bool _arranging;
@@ -53,6 +59,7 @@ namespace Robust.Client.UserInterface
set
{
_margin = value;
SetLayoutStyleProp(LayoutStyleProperties.Margin);
InvalidateMeasure();
}
}
@@ -242,6 +249,7 @@ namespace Robust.Client.UserInterface
set
{
_horizontalAlignment = value;
SetLayoutStyleProp(LayoutStyleProperties.HorizontalAlignment);
InvalidateArrange();
}
}
@@ -258,6 +266,7 @@ namespace Robust.Client.UserInterface
set
{
_verticalAlignment = value;
SetLayoutStyleProp(LayoutStyleProperties.VerticalAlignment);
InvalidateArrange();
}
}
@@ -276,6 +285,7 @@ namespace Robust.Client.UserInterface
set
{
_horizontalExpand = value;
SetLayoutStyleProp(LayoutStyleProperties.HorizontalExpand);
Parent?.InvalidateMeasure();
}
}
@@ -294,6 +304,7 @@ namespace Robust.Client.UserInterface
set
{
_verticalExpand = value;
SetLayoutStyleProp(LayoutStyleProperties.VerticalExpand);
Parent?.InvalidateArrange();
}
}
@@ -318,6 +329,7 @@ namespace Robust.Client.UserInterface
_sizeFlagsStretchRatio = value;
SetLayoutStyleProp(LayoutStyleProperties.StretchRatio);
Parent?.InvalidateArrange();
}
}
@@ -394,6 +406,7 @@ namespace Robust.Client.UserInterface
set
{
_minWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.MinWidth);
InvalidateMeasure();
}
}
@@ -408,6 +421,7 @@ namespace Robust.Client.UserInterface
set
{
_minHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.MinHeight);
InvalidateMeasure();
}
}
@@ -422,6 +436,7 @@ namespace Robust.Client.UserInterface
set
{
_setWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.SetWidth);
InvalidateMeasure();
}
}
@@ -436,6 +451,7 @@ namespace Robust.Client.UserInterface
set
{
_setHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.SetHeight);
InvalidateMeasure();
}
}
@@ -450,6 +466,7 @@ namespace Robust.Client.UserInterface
set
{
_maxWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.MaxWidth);
InvalidateMeasure();
}
}
@@ -464,6 +481,7 @@ namespace Robust.Client.UserInterface
set
{
_maxHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.MaxHeight);
InvalidateMeasure();
}
}

View File

@@ -239,6 +239,7 @@ namespace Robust.Client.UserInterface
protected virtual void StylePropertiesChanged()
{
UpdateLayoutStyleProperties();
InvalidateMeasure();
}

View File

@@ -641,7 +641,11 @@ namespace Robust.Client.UserInterface
foreach (var child in Children.ToArray())
{
RemoveChild(child);
// This checks fails in some obscure cases like using the element inspector in the dev window.
// Why? Well I could probably spend 15 minutes in a debugger to find out,
// but I'd probably still end up with this fix.
if (child.Parent == this)
RemoveChild(child);
}
}

View File

@@ -5,7 +5,6 @@ using Robust.Shared.Timing;
namespace Robust.Client.UserInterface.Controllers;
// Notices your UIController, *UwU Whats this?*
/// <summary>
/// Each <see cref="UIController"/> is instantiated as a singleton by <see cref="UserInterfaceManager"/>
/// <see cref="UIController"/> can use <see cref="DependencyAttribute"/> for regular IoC dependencies

View File

@@ -6,6 +6,7 @@ using Robust.Client.UserInterface.RichText;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Controls
{
@@ -16,6 +17,26 @@ namespace Robust.Client.UserInterface.Controls
private FormattedMessage? _message;
private RichTextEntry _entry;
private float _lineHeightScale = 1;
private bool _lineHeightOverride;
[ViewVariables(VVAccess.ReadWrite)]
public float LineHeightScale
{
get
{
if (!_lineHeightOverride && TryGetStyleProperty(nameof(LineHeightScale), out float value))
return value;
return _lineHeightScale;
}
set
{
_lineHeightScale = value;
_lineHeightOverride = true;
InvalidateMeasure();
}
}
public RichTextLabel()
{
@@ -47,7 +68,7 @@ namespace Robust.Client.UserInterface.Controls
}
var font = _getFont();
_entry.Update(font, availableSize.X * UIScale, UIScale);
_entry.Update(font, availableSize.X * UIScale, UIScale, LineHeightScale);
return new Vector2(_entry.Width / UIScale, _entry.Height / UIScale);
}
@@ -61,7 +82,7 @@ namespace Robust.Client.UserInterface.Controls
return;
}
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale);
_entry.Draw(handle, _getFont(), SizeBox, 0, new MarkupDrawingContext(), UIScale, LineHeightScale);
}
[Pure]

View File

@@ -2,6 +2,7 @@
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Input;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using static Robust.Client.UserInterface.Controls.LayoutContainer;
@@ -32,6 +33,11 @@ namespace Robust.Client.UserInterface.Controls
public bool Grabbed => _grabbed;
/// <summary>
/// Whether the slider can be adjusted.
/// </summary>
public bool Disabled { get; set; }
public StyleBox? ForegroundStyleBoxOverride
{
get => _foregroundStyleBoxOverride;
@@ -132,7 +138,7 @@ namespace Robust.Client.UserInterface.Controls
{
base.KeyBindDown(args);
if (args.Function != EngineKeyFunctions.UIClick)
if (args.Function != EngineKeyFunctions.UIClick || Disabled)
{
return;
}
@@ -146,7 +152,7 @@ namespace Robust.Client.UserInterface.Controls
{
base.KeyBindUp(args);
if (args.Function != EngineKeyFunctions.UIClick) return;
if (args.Function != EngineKeyFunctions.UIClick || !_grabbed) return;
_grabbed = false;
OnReleased?.Invoke(this);

View File

@@ -0,0 +1,34 @@
<!-- Organised with tabs in a vertical list to the left and the contents to the right -->
<BoxContainer Name="Container" xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:graphics="clr-namespace:Robust.Client.Graphics"
Orientation="Horizontal"
MouseFilter="Pass">
<ScrollContainer VerticalExpand="True"
HScrollEnabled="False"
ReturnMeasure="True"
Margin="5 5 0 5">
<PanelContainer Margin="5" VerticalExpand="False"
VerticalAlignment="Top">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<BoxContainer Name="TabContainer" Margin="5" Orientation="Vertical"/>
</PanelContainer>
</ScrollContainer>
<ScrollContainer VerticalExpand="True"
HScrollEnabled="False"
HorizontalAlignment="Stretch"
HorizontalExpand="True"
ReturnMeasure="True"
Margin="0 5 5 5">
<PanelContainer Margin="5" HorizontalExpand="True" HorizontalAlignment="Stretch">
<PanelContainer.PanelOverride>
<graphics:StyleBoxFlat BackgroundColor="#1B1B1E" />
</PanelContainer.PanelOverride>
<BoxContainer Margin="5"
Orientation="Vertical"
Name="ContentsContainer"/>
</PanelContainer>
</ScrollContainer>
</BoxContainer>

View File

@@ -0,0 +1,97 @@
using System.Collections.Generic;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Maths;
namespace Robust.Client.UserInterface.Controls;
[GenerateTypedNameReferences]
public sealed partial class VerticalTabContainer : BoxContainer
{
private readonly Dictionary<Control, BaseButton> _tabs = new();
// Just used to order controls in case one gets removed.
private readonly List<Control> _controls = new();
private readonly ButtonGroup _tabGroup = new(false);
private Control? _currentControl;
public VerticalTabContainer()
{
RobustXamlLoader.Load(this);
}
public int AddTab(Control control, string title)
{
var button = new Button()
{
Text = title,
Group = _tabGroup,
};
TabContainer.AddChild(button);
ContentsContainer.AddChild(control);
var index = ChildCount - 1;
button.OnPressed += args =>
{
SelectTab(control);
};
_controls.Add(control);
_tabs.Add(control, button);
// Existing tabs
if (ContentsContainer.ChildCount > 1)
{
control.Visible = false;
}
// First tab
else
{
SelectTab(control);
}
return index;
}
protected override void ChildRemoved(Control child)
{
if (_tabs.Remove(child, out var button))
{
button.Dispose();
}
// Set the current tab to a different control
if (_currentControl == child)
{
var previous = _controls.IndexOf(child) - 1;
if (previous > -1)
{
var setControl = _controls[previous];
SelectTab(setControl);
}
else
{
_currentControl = null;
}
}
_controls.Remove(child);
base.ChildRemoved(child);
}
private void SelectTab(Control control)
{
if (_currentControl != null)
{
_currentControl.Visible = false;
}
var button = _tabs[control];
button.Pressed = true;
control.Visible = true;
_currentControl = control;
}
}

View File

@@ -20,6 +20,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IClyde _displayManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IBaseClient _baseClient = default!;
private readonly StringBuilder _textBuilder = new();
private readonly char[] _textBuffer = new char[1024];
@@ -58,30 +59,36 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
_textBuilder.Clear();
var isInGame = _baseClient.RunLevel.IsInGameLike();
var mouseScreenPos = _inputManager.MouseScreenPosition;
var screenSize = _displayManager.ScreenSize;
var screenScale = _displayManager.MainWindow.ContentScale;
EntityCoordinates mouseGridPos;
TileRef tile;
EntityCoordinates mouseGridPos = default;
TileRef tile = default;
MapCoordinates mouseWorldMap = default;
var mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
if (mouseWorldMap == MapCoordinates.Nullspace)
return;
var mapSystem = _entityManager.System<SharedMapSystem>();
var xformSystem = _entityManager.System<SharedTransformSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
if (isInGame)
{
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
}
else
{
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
mouseWorldMap.Position);
tile = new TileRef(EntityUid.Invalid, mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
mouseWorldMap = _eyeManager.PixelToMap(mouseScreenPos);
if (mouseWorldMap != MapCoordinates.Nullspace)
{
var mapSystem = _entityManager.System<SharedMapSystem>();
var xformSystem = _entityManager.System<SharedTransformSystem>();
if (_mapManager.TryFindGridAt(mouseWorldMap, out var mouseGridUid, out var mouseGrid))
{
mouseGridPos = mapSystem.MapToGrid(mouseGridUid, mouseWorldMap);
tile = mapSystem.GetTileRef(mouseGridUid, mouseGrid, mouseGridPos);
}
else
{
mouseGridPos = new EntityCoordinates(_mapManager.GetMapEntityId(mouseWorldMap.MapId),
mouseWorldMap.Position);
tile = new TileRef(EntityUid.Invalid,
mouseGridPos.ToVector2i(_entityManager, _mapManager, xformSystem), Tile.Empty);
}
}
}
var controlHovered = UserInterfaceManager.CurrentlyHovered;
@@ -95,32 +102,37 @@ Mouse Pos:
{tile}
GUI: {controlHovered}");
_textBuilder.AppendLine("\nAttached NetEntity:");
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
if (controlledEntity == EntityUid.Invalid)
if (isInGame)
{
_textBuilder.AppendLine("No attached netentity.");
}
else
{
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
var xformSystem = _entityManager.System<SharedTransformSystem>();
var playerCoordinates = entityTransform.Coordinates;
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
var gridRotation = entityTransform.GridUid != null
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
: Angle.Zero;
_textBuilder.AppendLine("\nAttached NetEntity:");
var controlledEntity = _playerManager.LocalSession?.AttachedEntity ?? EntityUid.Invalid;
_textBuilder.Append($@" Screen: {playerScreen}
if (controlledEntity == EntityUid.Invalid)
{
_textBuilder.AppendLine("No attached netentity.");
}
else
{
var entityTransform = _entityManager.GetComponent<TransformComponent>(controlledEntity);
var playerWorldOffset = xformSystem.GetMapCoordinates(entityTransform);
var playerScreen = _eyeManager.WorldToScreen(playerWorldOffset.Position);
var playerCoordinates = entityTransform.Coordinates;
var playerRotation = xformSystem.GetWorldRotation(entityTransform);
var gridRotation = entityTransform.GridUid != null
? xformSystem.GetWorldRotation(entityTransform.GridUid.Value)
: Angle.Zero;
_textBuilder.Append($@" Screen: {playerScreen}
{playerWorldOffset}
{_entityManager.GetNetCoordinates(playerCoordinates)}
Rotation: {playerRotation.Degrees:F2}°
NEntId: {_entityManager.GetNetEntity(controlledEntity)}
Grid NEntId: {_entityManager.GetNetEntity(entityTransform.GridUid)}
Grid Rotation: {gridRotation.Degrees:F2}°");
}
}
_contents.TextMemory = FormatHelpers.BuilderToMemory(_textBuilder, _textBuffer);

View File

@@ -5,7 +5,7 @@ using Robust.Shared.Utility;
namespace Robust.Client.UserInterface.RichText;
[Prototype("font")]
public sealed class FontPrototype : IPrototype
public sealed partial class FontPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;

View File

@@ -71,7 +71,8 @@ namespace Robust.Client.UserInterface
/// <param name="defaultFont">The font being used for display.</param>
/// <param name="maxSizeX">The maximum horizontal size of the container of this entry.</param>
/// <param name="uiScale"></param>
public void Update(Font defaultFont, float maxSizeX, float uiScale)
/// <param name="lineHeightScale"></param>
public void Update(Font defaultFont, float maxSizeX, float uiScale, float lineHeightScale = 1)
{
// This method is gonna suck due to complexity.
// Bear with me here.
@@ -159,7 +160,7 @@ namespace Robust.Client.UserInterface
if (!context.Font.TryPeek(out var font))
font = defaultFont;
src.Height += font.GetLineHeight(uiScale);
src.Height += GetLineHeight(font, uiScale, lineHeightScale);
}
}
}
@@ -170,7 +171,8 @@ namespace Robust.Client.UserInterface
UIBox2 drawBox,
float verticalOffset,
MarkupDrawingContext context,
float uiScale)
float uiScale,
float lineHeightScale = 1)
{
context.Clear();
context.Color.Push(_defaultColor);
@@ -197,7 +199,7 @@ namespace Robust.Client.UserInterface
if (lineBreakIndex < LineBreaks.Count &&
LineBreaks[lineBreakIndex] == globalBreakCounter)
{
baseLine = new Vector2(drawBox.Left, baseLine.Y + font.GetLineHeight(uiScale) + controlYAdvance);
baseLine = new Vector2(drawBox.Left, baseLine.Y + GetLineHeight(font, uiScale, lineHeightScale) + controlYAdvance);
controlYAdvance = 0;
lineBreakIndex += 1;
}
@@ -216,7 +218,7 @@ namespace Robust.Client.UserInterface
control.Position = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale);
control.Measure(new Vector2(Width, Height));
var advanceX = control.DesiredPixelSize.X;
controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - font.GetLineHeight(uiScale)) * invertedScale);
controlYAdvance = Math.Max(0f, (control.DesiredPixelSize.Y - GetLineHeight(font, uiScale, lineHeightScale)) * invertedScale);
baseLine += new Vector2(advanceX, 0);
}
}
@@ -242,5 +244,11 @@ namespace Robust.Client.UserInterface
tag.PopDrawContext(node, context);
return tag.TextAfter(node);
}
private static int GetLineHeight(Font font, float uiScale, float lineHeightScale)
{
var height = font.GetLineHeight(uiScale);
return (int)(height * lineHeightScale);
}
}
}

View File

@@ -1,12 +1,10 @@
using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.ContentPack;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
@@ -18,7 +16,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface.Themes;
[Prototype("uiTheme")]
public sealed class UITheme : IPrototype
public sealed partial class UITheme : IPrototype
{
private IResourceCache? _cache;
private IUserInterfaceManager? _uiMan;

View File

@@ -2,6 +2,7 @@
using Robust.Client.UserInterface.Themes;
using Robust.Shared;
using Robust.Shared.Log;
using Robust.Shared.Prototypes;
namespace Robust.Client.UserInterface;
@@ -18,11 +19,29 @@ internal partial class UserInterfaceManager
{
DefaultTheme = _protoManager.Index<UITheme>(UITheme.DefaultName);
CurrentTheme = DefaultTheme;
ReloadThemes();
_configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true);
_protoManager.PrototypesReloaded += OnPrototypesReloaded;
}
private void OnPrototypesReloaded(PrototypesReloadedEventArgs eventArgs)
{
if (eventArgs.WasModified<UITheme>())
{
_sawmillUI.Debug("Reloading UI themes due to prototype reload");
ReloadThemes();
}
}
private void ReloadThemes()
{
_themes.Clear();
foreach (var proto in _protoManager.EnumeratePrototypes<UITheme>())
{
_themes.Add(proto.ID, proto);
}
_configurationManager.OnValueChanged(CVars.InterfaceTheme, SetThemeOrPrevious, true);
SetThemeOrPrevious(CurrentTheme.ID);
}
//Try to set the current theme, if the theme is not found do nothing

View File

@@ -28,6 +28,7 @@ public static class Diagnostics
public const string IdComponentPauseNoFields = "RA0022";
public const string IdComponentPauseNoParentAttribute = "RA0023";
public const string IdComponentPauseWrongTypeAttribute = "RA0024";
public const string IdDependencyFieldAssigned = "RA0025";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -70,6 +70,11 @@ namespace Robust.Server.Console.Commands
}
var mapId = new MapId(mapInt);
if (!_map.MapExists(mapId))
{
shell.WriteError($"map {args[0]} does not exist");
return;
}
if (shell.Player == null)
{
@@ -110,13 +115,6 @@ namespace Robust.Server.Console.Commands
private void SetupPlayer(MapId mapId, IConsoleShell shell)
{
if (mapId == MapId.Nullspace) return;
if (!_map.MapExists(mapId))
{
_map.CreateMap(mapId);
}
_map.SetMapPaused(mapId, false);
var mapUid = _map.GetMapEntityIdOrThrow(mapId);
_ent.System<Gravity2DController>().SetGravity(mapUid, new Vector2(0, -9.8f));

View File

@@ -49,7 +49,6 @@ public sealed class MapLoaderSystem : EntitySystem
private ISawmill _logLoader = default!;
private ISawmill _logWriter = default!;
private static readonly MapLoadOptions DefaultLoadOptions = new();
private const int MapFormatVersion = 6;
private const int BackwardsVersion = 2;
@@ -132,7 +131,7 @@ public sealed class MapLoaderSystem : EntitySystem
public bool TryLoad(MapId mapId, string path, [NotNullWhen(true)] out IReadOnlyList<EntityUid>? rootUids,
MapLoadOptions? options = null)
{
options ??= DefaultLoadOptions;
options ??= new();
var resPath = new ResPath(path).ToRootedPath();
@@ -658,11 +657,13 @@ public sealed class MapLoaderSystem : EntitySystem
var xformQuery = GetEntityQuery<TransformComponent>();
// We just need to cache the old mapuid and point to the new mapuid.
if (HasComp<MapComponent>(rootNode))
if (TryComp(rootNode, out MapComponent? mapComp))
{
// If map exists swap out
if (_mapManager.MapExists(data.TargetMap))
if (_mapSystem.TryGetMap(data.TargetMap, out var existing))
{
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
data.MapIsPaused = _mapSystem.IsPaused(existing.Value);
// Map exists but we also have a map file with stuff on it soooo swap out the old map.
if (data.Options.LoadMap)
{
@@ -675,26 +676,28 @@ public sealed class MapLoaderSystem : EntitySystem
data.Options.Rotation = Angle.Zero;
}
_mapManager.SetMapEntity(data.TargetMap, rootNode);
Del(existing);
EnsureComp<LoadedMapComponent>(rootNode);
mapComp.MapId = data.TargetMap;
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
}
// Otherwise just ignore the map in the file.
else
{
var oldRootUid = data.Entities[0];
var newRootUid = _mapManager.GetMapEntityId(data.TargetMap);
data.Entities[0] = newRootUid;
data.Entities[0] = existing.Value;
foreach (var ent in data.Entities)
{
if (ent == newRootUid)
if (ent == existing)
continue;
var xform = xformQuery.GetComponent(ent);
if (!xform.ParentUid.IsValid() || xform.ParentUid.Equals(oldRootUid))
{
_transform.SetParent(ent, xform, newRootUid);
_transform.SetParent(ent, xform, existing.Value);
}
}
@@ -703,16 +706,9 @@ public sealed class MapLoaderSystem : EntitySystem
}
else
{
// If we're loading a file with a map then swap out the entityuid
// TODO: Mapmanager nonsense
var AAAAA = _mapManager.CreateMap(data.TargetMap);
if (!data.MapIsPostInit)
{
_mapManager.AddUninitializedMap(data.TargetMap);
}
_mapManager.SetMapEntity(data.TargetMap, rootNode);
data.MapIsPaused = !data.MapIsPostInit;
mapComp.MapId = data.TargetMap;
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
EnsureComp<LoadedMapComponent>(rootNode);
// Nothing should have invalid uid except for the root node.
@@ -721,17 +717,15 @@ public sealed class MapLoaderSystem : EntitySystem
else
{
// No map file root, in that case create a new map / get the one we're loading onto.
var mapNode = _mapManager.GetMapEntityId(data.TargetMap);
if (!mapNode.IsValid())
if (!_mapSystem.TryGetMap(data.TargetMap, out var mapNode))
{
// Map doesn't exist so we'll start it up now so we can re-attach the preinit entities to it for later.
_mapManager.CreateMap(data.TargetMap);
_mapManager.AddUninitializedMap(data.TargetMap);
mapNode = _mapManager.GetMapEntityId(data.TargetMap);
DebugTools.Assert(mapNode.IsValid());
mapNode = _mapSystem.CreateMap(data.TargetMap, false);
}
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
data.MapIsPaused = _mapSystem.IsPaused(mapNode.Value);
// If anything has an invalid parent (e.g. it's some form of root node) then parent it to the map.
foreach (var ent in data.Entities)
{
@@ -743,12 +737,11 @@ public sealed class MapLoaderSystem : EntitySystem
if (!xform.ParentUid.IsValid())
{
_transform.SetParent(ent, xform, mapNode);
_transform.SetParent(ent, xform, mapNode.Value);
}
}
}
data.MapIsPaused = _mapManager.IsMapPaused(data.TargetMap);
_logLoader.Debug($"Swapped out root node in {_stopwatch.Elapsed}");
}
@@ -896,7 +889,7 @@ public sealed class MapLoaderSystem : EntitySystem
{
EntityManager.SetLifeStage(metadata, EntityLifeStage.MapInitialized);
}
else if (_mapManager.IsMapInitialized(data.TargetMap))
else if (data.Options.DoMapInit)
{
_serverEntityManager.RunMapInit(uid, metadata);
}
@@ -964,7 +957,7 @@ public sealed class MapLoaderSystem : EntitySystem
// Yes, post-init maps do not have EntityLifeStage >= EntityLifeStage.MapInitialized
bool postInit;
if (TryComp(uid, out MapComponent? mapComp))
postInit = !mapComp.MapPreInit;
postInit = mapComp.MapInitialized;
else
postInit = metadata.EntityLifeStage >= EntityLifeStage.MapInitialized;
@@ -1098,17 +1091,17 @@ public sealed class MapLoaderSystem : EntitySystem
}
}
private bool IsSaveable(EntityUid uid, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> transformQuery)
private bool IsSaveable(EntityUid uid)
{
// Don't serialize things parented to un savable things.
// For example clothes inside a person.
while (uid.IsValid())
{
var meta = metaQuery.GetComponent(uid);
var meta = MetaData(uid);
if (meta.EntityDeleted || meta.EntityPrototype?.MapSavable == false) break;
uid = transformQuery.GetComponent(uid).ParentUid;
uid = Transform(uid).ParentUid;
}
// If we manage to get up to the map (root node) then it's saveable.
@@ -1123,7 +1116,7 @@ public sealed class MapLoaderSystem : EntitySystem
EntityQuery<TransformComponent> transformQuery,
EntityQuery<MapSaveIdComponent> saveCompQuery)
{
if (!IsSaveable(uid, metaQuery, transformQuery))
if (!IsSaveable(uid))
return;
entities.Add(uid);

View File

@@ -5,9 +5,9 @@ using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Map.Events;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Server.GameObjects
{
@@ -18,6 +18,16 @@ namespace Robust.Server.GameObjects
private bool _deleteEmptyGrids;
protected override MapId GetNextMapId()
{
var id = new MapId(++LastMapId);
while (MapManager.MapExists(id))
{
id = new MapId(++LastMapId);
}
return id;
}
protected override void UpdatePvsChunks(Entity<TransformComponent, MetaDataComponent> grid)
{
_pvs.GridParentChanged(grid);
@@ -31,11 +41,6 @@ namespace Robust.Server.GameObjects
Subs.CVar(_cfg, CVars.GameDeleteEmptyGrids, SetGridDeletion, true);
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
private void SetGridDeletion(bool value)
{
_deleteEmptyGrids = value;

View File

@@ -53,5 +53,7 @@ namespace Robust.Server.Maps
/// This should be set to false if you want to load a map file onto an existing map and do not wish to overwrite the existing entity.
/// </remarks>
public bool LoadMap { get; set; } = true;
public bool DoMapInit = false;
}
}

View File

@@ -256,7 +256,7 @@ namespace Robust.Server.ServerStatus
_context = context;
RequestMethod = new HttpMethod(context.Request.HttpMethod!);
var headers = new Dictionary<string, StringValues>();
var headers = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
foreach (string? key in context.Request.Headers.Keys)
{
if (key == null)

View File

@@ -157,11 +157,14 @@ namespace Robust.Server.ServerStatus
try
{
// Passing null as content works so...
await _httpClient.PostAsync(new Uri(_baseUri, $"server_api/{_watchdogKey}/ping"), null!);
_sawmill.Debug("Sending ping to watchdog...");
using var resp = await _httpClient.PostAsync(new Uri(_baseUri, $"server_api/{_watchdogKey}/ping"), null!);
resp.EnsureSuccessStatusCode();
_sawmill.Debug("Succeeded in sending ping to watchdog");
}
catch (HttpRequestException e)
{
_sawmill.Warning("Failed to send ping to watchdog:\n{0}", e);
_sawmill.Error("Failed to send ping to watchdog:\n{0}", e);
}
}

View File

@@ -592,7 +592,11 @@ namespace Robust.Shared.Maths
if (c != 0)
{
if (max == rgb.R)
{
h = (rgb.G - rgb.B) / c % 6.0f;
if (h < 0f)
h += 6.0f;
}
else if (max == rgb.G)
h = (rgb.B - rgb.R) / c + 2.0f;
else if (max == rgb.B)
@@ -774,7 +778,11 @@ namespace Robust.Shared.Maths
var h = 0.0f;
if (max == rgb.R)
{
h = (rgb.G - rgb.B) / c % 6.0f;
if (h < 0f)
h += 6.0f;
}
else if (max == rgb.G)
h = (rgb.B - rgb.R) / c + 2.0f;
else if (max == rgb.B)

View File

@@ -1,6 +1,6 @@
using System;
#if NETSTANDARD2_0
#if ROBUST_ANALYZERS_IMPL
namespace Robust.Shared.Analyzers.Implementation;
#else
namespace Robust.Shared.Analyzers;

View File

@@ -1,6 +1,6 @@
using System;
#if NETSTANDARD2_0
#if ROBUST_ANALYZERS_IMPL
namespace Robust.Shared.Analyzers.Implementation;
#else
namespace Robust.Shared.Analyzers;

View File

@@ -1,6 +1,6 @@
using System;
#if NETSTANDARD2_0
#if ROBUST_ANALYZERS_IMPL
namespace Robust.Shared.Analyzers.Implementation;
#else
namespace Robust.Shared.Analyzers;

View File

@@ -1,6 +1,6 @@
using System;
#if NETSTANDARD2_0
#if ROBUST_ANALYZERS_IMPL
namespace Robust.Shared.Analyzers.Implementation;
#else
namespace Robust.Shared.Analyzers;

View File

@@ -10,7 +10,7 @@ namespace Robust.Shared.Audio;
/// to allow the server to know audio lengths without shipping the large audio files themselves.
/// </summary>
[Prototype(ProtoName)]
public sealed class AudioMetadataPrototype : IPrototype
public sealed partial class AudioMetadataPrototype : IPrototype
{
public const string ProtoName = "audioMetadata";

View File

@@ -9,7 +9,7 @@ namespace Robust.Shared.Audio;
/// This can be used by <see cref="Content.Shared.Audio.SharedContentAudioSystem"/> to apply an audio preset.
/// </summary>
[Prototype("audioPreset")]
public sealed class AudioPresetPrototype : IPrototype
public sealed partial class AudioPresetPrototype : IPrototype
{
[IdDataField]
public string ID { get; } = default!;

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Audio.Sources;
using Robust.Shared.Audio.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
using Robust.Shared.ViewVariables;
@@ -90,11 +91,24 @@ public sealed partial class AudioComponent : Component, IAudioSource
public void StartPlaying() => Source.StartPlaying();
/// <inheritdoc />
public void StopPlaying() => Source.StopPlaying();
public void StopPlaying()
{
PlaybackPosition = 0f;
Source.StopPlaying();
}
/// <inheritdoc />
public void Restart() => Source.Restart();
[DataField, AutoNetworkedField]
public AudioState State = AudioState.Playing;
/// <summary>
/// Time when the audio was paused so we can offset it later if relevant.
/// </summary>
[DataField, AutoNetworkedField]
public TimeSpan? PauseTime;
/// <summary>
/// <see cref="IAudioSource.Playing"/>
/// </summary>
@@ -208,6 +222,7 @@ public sealed partial class AudioComponent : Component, IAudioSource
/// <see cref="IAudioSource.PlaybackPosition"/>
/// </summary>
[ViewVariables]
[Access(Other = AccessPermissions.ReadWriteExecute)]
public float PlaybackPosition
{
get => Source.PlaybackPosition;
@@ -240,6 +255,14 @@ public sealed partial class AudioComponent : Component, IAudioSource
}
}
[Serializable, NetSerializable]
public enum AudioState : byte
{
Stopped,
Playing,
Paused,
}
[Flags]
public enum AudioFlags : byte
{

View File

@@ -1,13 +1,13 @@
using System.Collections.Generic;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
using System.Collections.Generic;
namespace Robust.Shared.Audio;
[Prototype("soundCollection")]
public sealed class SoundCollectionPrototype : IPrototype
public sealed partial class SoundCollectionPrototype : IPrototype
{
[ViewVariables]
[IdDataField]

View File

@@ -55,6 +55,141 @@ public abstract partial class SharedAudioSystem : EntitySystem
SubscribeLocalEvent<AudioComponent, EntityUnpausedEvent>(OnAudioUnpaused);
}
/// <summary>
/// Sets the playback position of audio to the specified spot.
/// </summary>
public void SetPlaybackPosition(Entity<AudioComponent?>? nullEntity, float position)
{
if (nullEntity == null)
return;
var entity = nullEntity.Value;
if (!Resolve(entity.Owner, ref entity.Comp, false))
return;
var audioLength = GetAudioLength(entity.Comp.FileName);
if (audioLength.TotalSeconds < position)
{
// Just stop it and return
if (!_netManager.IsClient)
QueueDel(nullEntity.Value);
entity.Comp.StopPlaying();
return;
}
if (position < 0f)
{
Log.Error($"Tried to set playback position for {ToPrettyString(entity.Owner)} / {entity.Comp.FileName} outside of bounds");
return;
}
// If we're paused then the current position is <pause time - start time>, else it's <cur time - start time>
var currentPos = (entity.Comp.PauseTime ?? Timing.CurTime) - entity.Comp.AudioStart;
var timeOffset = TimeSpan.FromSeconds(position - currentPos.TotalSeconds);
DebugTools.Assert(currentPos > TimeSpan.Zero);
// Rounding.
if (Math.Abs(timeOffset.TotalSeconds) <= 0.01)
{
return;
}
if (entity.Comp.PauseTime != null)
{
entity.Comp.PauseTime = entity.Comp.PauseTime.Value + timeOffset;
// Paused audio doesn't have TimedDespawn so.
}
else
{
// Bump it back so the actual playback positions moves forward
entity.Comp.AudioStart -= timeOffset;
// need to ensure it doesn't despawn too early.
if (TryComp(entity.Owner, out TimedDespawnComponent? despawn))
{
despawn.Lifetime -= (float) timeOffset.TotalSeconds;
}
}
entity.Comp.PlaybackPosition = position;
// Network the new playback position.
Dirty(entity);
}
/// <summary>
/// Calculates playback position considering length paused.
/// </summary>
/// <param name="component"></param>
/// <returns></returns>
private float GetPlaybackPosition(AudioComponent component)
{
return (float) (Timing.CurTime - (component.PauseTime ?? TimeSpan.Zero) - component.AudioStart).TotalSeconds;
}
/// <summary>
/// Sets the shared state for an audio entity.
/// </summary>
public void SetState(EntityUid? entity, AudioState state, bool force = false, AudioComponent? component = null)
{
if (entity == null || !Resolve(entity.Value, ref component, false))
return;
if (component.State == state && !force)
return;
// Unpause it
if (component.State == AudioState.Paused && state == AudioState.Playing)
{
var pauseOffset = Timing.CurTime - component.PauseTime;
component.AudioStart += pauseOffset ?? TimeSpan.Zero;
component.PlaybackPosition = (float) (Timing.CurTime - component.AudioStart).TotalSeconds;
}
// If we were stopped then played then restart audiostart to now.
if (component.State == AudioState.Stopped && state == AudioState.Playing)
{
component.AudioStart = Timing.CurTime;
component.PauseTime = null;
}
switch (state)
{
case AudioState.Stopped:
component.AudioStart = Timing.CurTime;
component.PauseTime = null;
component.StopPlaying();
RemComp<TimedDespawnComponent>(entity.Value);
break;
case AudioState.Paused:
// Set it to current time so we can easily unpause it later.
component.PauseTime = Timing.CurTime;
component.Pause();
RemComp<TimedDespawnComponent>(entity.Value);
break;
case AudioState.Playing:
component.PauseTime = null;
component.StartPlaying();
// Reset TimedDespawn so the audio still gets cleaned up.
if (!component.Looping)
{
var timed = EnsureComp<TimedDespawnComponent>(entity.Value);
var audioLength = GetAudioLength(component.FileName);
timed.Lifetime = (float) audioLength.TotalSeconds + 0.01f;
}
break;
}
component.State = state;
Dirty(entity.Value, component);
}
protected void SetZOffset(float value)
{
ZOffset = value;
@@ -505,4 +640,12 @@ public abstract partial class SharedAudioSystem : EntitySystem
{
public NetEntity NetEntity;
}
public bool IsPlaying(EntityUid? stream, AudioComponent? component = null)
{
if (stream == null || !Resolve(stream.Value, ref component, false))
return false;
return component.State == AudioState.Playing;
}
}

View File

@@ -851,7 +851,7 @@ namespace Robust.Shared
/// See the documentation of the <see cref="Network.AuthMode"/> enum for values.
/// </summary>
public static readonly CVarDef<int> AuthMode =
CVarDef.Create("auth.mode", (int) Network.AuthMode.Optional, CVar.SERVERONLY);
CVarDef.Create("auth.mode", (int) Network.AuthMode.Required, CVar.SERVERONLY);
/// <summary>
/// Allow unauthenticated localhost connections, even if the auth mode is set to required.
@@ -1445,7 +1445,7 @@ namespace Robust.Shared
/// Comma-separated list of URLs of hub servers to advertise to.
/// </summary>
public static readonly CVarDef<string> HubUrls =
CVarDef.Create("hub.hub_urls", "https://central.spacestation14.io/hub/", CVar.SERVERONLY);
CVarDef.Create("hub.hub_urls", "https://hub.spacestation14.com/", CVar.SERVERONLY);
/// <summary>
/// URL of this server to advertise.

View File

@@ -558,17 +558,21 @@ namespace Robust.Shared.Configuration
OverrideDefault(def.Name, value);
}
/// <inheritdoc />
public T GetCVar<T>(string name)
public object GetCVar(string name)
{
using var _ = Lock.ReadGuard();
if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered)
//TODO: Make flags work, required non-derpy net system.
return (T)(GetConfigVarValue(cVar))!;
return GetConfigVarValue(cVar);
throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'");
}
/// <inheritdoc />
public T GetCVar<T>(string name)
{
return (T)GetCVar(name);
}
public T GetCVar<T>(CVarDef<T> def) where T : notnull
{
return GetCVar<T>(def.Name);

View File

@@ -1,9 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace Robust.Shared.Configuration
{
public static class EnvironmentVariables
internal static class EnvironmentVariables
{
/// <summary>
/// The environment variable for configuring CVar overrides. The value
@@ -12,23 +13,38 @@ namespace Robust.Shared.Configuration
/// </summary>
public const string ConfigVarEnvironmentVariable = "ROBUST_CVARS";
public const string SingleVarPrefix = "ROBUST_CVAR_";
/// <summary>
/// Get the CVar overrides defined in the relevant environment variable.
/// </summary>
public static IEnumerable<(string, string)> GetEnvironmentCVars()
internal static IEnumerable<(string, string)> GetEnvironmentCVars()
{
var eVarString = Environment.GetEnvironmentVariable(ConfigVarEnvironmentVariable);
if (eVarString == null)
{
yield break;
}
// Handle ROBUST_CVARS.
var eVarString = Environment.GetEnvironmentVariable(ConfigVarEnvironmentVariable) ?? "";
foreach (var cVarPair in eVarString.Split(';', StringSplitOptions.RemoveEmptyEntries))
{
var pairParts = cVarPair.Split('=', 2);
yield return (pairParts[0], pairParts[1]);
}
// Handle ROBUST_CVAR_*
foreach (DictionaryEntry entry in Environment.GetEnvironmentVariables())
{
var key = (string)entry.Key;
var value = (string?)entry.Value;
if (value == null)
continue;
if (!key.StartsWith(SingleVarPrefix))
continue;
var varName = key[SingleVarPrefix.Length..].Replace("__", ".");
yield return (varName, value);
}
}
}
}

View File

@@ -118,6 +118,13 @@ namespace Robust.Shared.Configuration
/// <param name="value">The new default value of the CVar.</param>
void OverrideDefault<T>(CVarDef<T> def, T value) where T : notnull;
/// <summary>
/// Get the value of a CVar.
/// </summary>
/// <param name="name">The name of the CVar.</param>
/// <returns></returns>
object GetCVar(string name);
/// <summary>
/// Get the value of a CVar.
/// </summary>

View File

@@ -10,7 +10,8 @@ namespace Robust.Shared.Console.Commands;
sealed class AddMapCommand : LocalizedCommands
{
[Dependency] private readonly IMapManager _map = default!;
[Dependency] private readonly IMapManagerInternal _map = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
public override string Command => "addmap";
public override bool RequireServerOrSingleplayer => true;
@@ -24,11 +25,8 @@ sealed class AddMapCommand : LocalizedCommands
if (!_map.MapExists(mapId))
{
_map.CreateMap(mapId);
if (args.Length >= 2 && args[1] == "false")
{
_map.AddUninitializedMap(mapId);
}
var init = args.Length < 2 || !bool.Parse(args[1]);
_entMan.System<SharedMapSystem>().CreateMap(mapId, runMapInit: init);
shell.WriteLine($"Map with ID {mapId} created.");
return;

View File

@@ -135,6 +135,25 @@ public abstract partial class SharedContainerSystem
return true;
}
/// <summary>
/// Attempts to insert an entity into a container. If it fails, it will instead drop the entity next to the
/// container entity.
/// </summary>
/// <returns>Whether or not the entity was successfully inserted</returns>
public bool InsertOrDrop(Entity<TransformComponent?, MetaDataComponent?, PhysicsComponent?> toInsert,
BaseContainer container,
TransformComponent? containerXform = null)
{
if (!Resolve(toInsert.Owner, ref toInsert.Comp1) || !Resolve(container.Owner, ref containerXform))
return false;
if (Insert(toInsert, container, containerXform))
return true;
_transform.DropNextTo(toInsert, (container.Owner, containerXform));
return false;
}
/// <summary>
/// Checks if the entity can be inserted into the given container.
/// </summary>

View File

@@ -168,9 +168,16 @@ namespace Robust.Shared.Containers
return containerManager.Containers.TryGetValue(id, out container);
}
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null, bool skipExistCheck = false)
[Obsolete("Use variant without skipExistCheck argument")]
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, bool skipExistCheck)
{
if (!Resolve(uid, ref containerManager, false) || !(skipExistCheck || Exists(containedUid)))
return TryGetContainingContainer(uid, containedUid, out container);
}
public bool TryGetContainingContainer(EntityUid uid, EntityUid containedUid, [NotNullWhen(true)] out BaseContainer? container, ContainerManagerComponent? containerManager = null)
{
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
{
container = null;
return false;
@@ -191,7 +198,8 @@ namespace Robust.Shared.Containers
public bool ContainsEntity(EntityUid uid, EntityUid containedUid, ContainerManagerComponent? containerManager = null)
{
if (!Resolve(uid, ref containerManager, false) || !Exists(containedUid))
DebugTools.Assert(Exists(containedUid));
if (!Resolve(uid, ref containerManager, false))
return false;
foreach (var container in containerManager.Containers.Values)
@@ -251,7 +259,7 @@ namespace Robust.Shared.Containers
if (!Resolve(uid, ref transform, false))
return false;
return TryGetContainingContainer(transform.ParentUid, uid, out container, skipExistCheck: true);
return TryGetContainingContainer(transform.ParentUid, uid, out container);
}
/// <summary>

View File

@@ -32,7 +32,7 @@ namespace Robust.Shared.ContentPack
String("short").ThenReturn(PrimitiveTypeCode.Int16);
private static readonly Parser<char, PrimitiveTypeCode> UInt16TypeParser =
String("ushort").ThenReturn(PrimitiveTypeCode.UInt32);
String("ushort").ThenReturn(PrimitiveTypeCode.UInt16);
private static readonly Parser<char, PrimitiveTypeCode> Int32TypeParser =
String("int").ThenReturn(PrimitiveTypeCode.Int32);

View File

@@ -84,12 +84,146 @@ Types:
- "bool get_HasContents()"
Lidgren.Network:
NetBuffer:
All: True
Methods:
- "byte[] get_Data()"
- "void set_Data(byte[])"
- "int get_LengthBytes()"
- "void set_LengthBytes(int)"
- "int get_LengthBits()"
- "void set_LengthBits(int)"
- "long get_Position()"
- "void set_Position(long)"
- "int get_PositionInBytes()"
- "byte[] PeekDataBuffer()"
- "bool PeekBoolean()"
- "byte PeekByte()"
- "sbyte PeekSByte()"
- "byte PeekByte(int)"
- "System.Span`1<byte> PeekBytes(System.Span`1<byte>)"
- "byte[] PeekBytes(int)"
- "void PeekBytes(byte[], int, int)"
- "short PeekInt16()"
- "ushort PeekUInt16()"
- "int PeekInt32()"
- "int PeekInt32(int)"
- "uint PeekUInt32()"
- "uint PeekUInt32(int)"
- "ulong PeekUInt64()"
- "long PeekInt64()"
- "ulong PeekUInt64(int)"
- "long PeekInt64(int)"
- "float PeekFloat()"
- "System.Half PeekHalf()"
- "float PeekSingle()"
- "double PeekDouble()"
- "string PeekString()"
- "int PeekStringSize()"
- "bool ReadBoolean()"
- "byte ReadByte()"
- "bool ReadByte(ref byte)"
- "sbyte ReadSByte()"
- "byte ReadByte(int)"
- "System.Span`1<byte> ReadBytes(System.Span`1<byte>)"
- "byte[] ReadBytes(int)"
- "bool ReadBytes(int, ref byte[])"
- "bool TryReadBytes(System.Span`1<byte>)"
- "void ReadBytes(byte[], int, int)"
- "void ReadBits(System.Span`1<byte>, int)"
- "void ReadBits(byte[], int, int)"
- "short ReadInt16()"
- "ushort ReadUInt16()"
- "int ReadInt32()"
- "bool ReadInt32(ref int)"
- "int ReadInt32(int)"
- "uint ReadUInt32()"
- "bool ReadUInt32(ref uint)"
- "uint ReadUInt32(int)"
- "ulong ReadUInt64()"
- "long ReadInt64()"
- "ulong ReadUInt64(int)"
- "long ReadInt64(int)"
- "float ReadFloat()"
- "System.Half ReadHalf()"
- "float ReadSingle()"
- "bool ReadSingle(ref float)"
- "double ReadDouble()"
- "uint ReadVariableUInt32()"
- "bool ReadVariableUInt32(ref uint)"
- "int ReadVariableInt32()"
- "long ReadVariableInt64()"
- "ulong ReadVariableUInt64()"
- "float ReadSignedSingle(int)"
- "float ReadUnitSingle(int)"
- "float ReadRangedSingle(float, float, int)"
- "int ReadRangedInteger(int, int)"
- "long ReadRangedInteger(long, long)"
- "string ReadString()"
- "bool ReadString(ref string)"
- "double ReadTime(Lidgren.Network.NetConnection, bool)"
- "System.Net.IPEndPoint ReadIPEndPoint()"
- "void SkipPadBits()"
- "void ReadPadBits()"
- "void SkipPadBits(int)"
- "void EnsureBufferSize(int)"
- "void Write(bool)"
- "void Write(byte)"
- "void WriteAt(int, byte)"
- "void Write(sbyte)"
- "void Write(byte, int)"
- "void Write(byte[])"
- "void Write(System.ReadOnlySpan`1<byte>)"
- "void Write(byte[], int, int)"
- "void Write(ushort)"
- "void WriteAt(int, ushort)"
- "void Write(ushort, int)"
- "void Write(short)"
- "void WriteAt(int, short)"
- "void Write(int)"
- "void WriteAt(int, int)"
- "void Write(uint)"
- "void WriteAt(int, uint)"
- "void Write(uint, int)"
- "void Write(int, int)"
- "void Write(ulong)"
- "void WriteAt(int, ulong)"
- "void Write(ulong, int)"
- "void Write(long)"
- "void Write(long, int)"
- "void Write(System.Half)"
- "void Write(float)"
- "void Write(double)"
- "int WriteVariableUInt32(uint)"
- "int WriteVariableInt32(int)"
- "int WriteVariableInt64(long)"
- "int WriteVariableUInt64(ulong)"
- "void WriteSignedSingle(float, int)"
- "void WriteUnitSingle(float, int)"
- "void WriteRangedSingle(float, float, float, int)"
- "int WriteRangedInteger(int, int, int)"
- "int WriteRangedInteger(long, long, long)"
- "void Write(string)"
- "void Write(System.Net.IPEndPoint)"
- "void WriteTime(bool)"
- "void WriteTime(double, bool)"
- "void WritePadBits()"
- "void WritePadBits(int)"
- "void Write(Lidgren.Network.NetBuffer)"
- "void Zero(int)"
- "void .ctor()"
NetDeliveryMethod: { }
NetIncomingMessage:
All: True
Methods:
- "Lidgren.Network.NetIncomingMessageType get_MessageType()"
- "Lidgren.Network.NetDeliveryMethod get_DeliveryMethod()"
- "int get_SequenceChannel()"
- "System.Net.IPEndPoint get_SenderEndPoint()"
- "Lidgren.Network.NetConnection get_SenderConnection()"
- "double get_ReceiveTime()"
- "double ReadTime(bool)"
- "string ToString()"
NetOutgoingMessage:
All: True
Methods:
- "string ToString()"
Nett:
CommentLocation: { } # Enum
Toml:
@@ -951,7 +1085,9 @@ Types:
IComparable: { All: True }
IComparable`1: { All: True }
IDisposable: { All: True }
IEquatable`1: { }
IEquatable`1:
Methods:
- "bool Equals(!0)"
IFormatProvider: { All: True }
IFormattable: { All: True }
Index: { All: True }

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using Robust.Shared.Utility;
namespace Robust.Shared.ContentPack
@@ -135,11 +136,37 @@ namespace Robust.Shared.ContentPack
path = path.Directory;
var fullPath = GetFullPath(path);
Process.Start(new ProcessStartInfo
if (OperatingSystem.IsWindows())
{
UseShellExecute = true,
FileName = fullPath,
});
Process.Start(new ProcessStartInfo
{
FileName = "explorer.exe",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsMacOS())
{
Process.Start(new ProcessStartInfo
{
FileName = "open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD())
{
Process.Start(new ProcessStartInfo
{
FileName = "xdg-open",
Arguments = ".",
WorkingDirectory = fullPath,
});
}
else
{
throw new NotSupportedException("Opening OS windows not supported on this OS");
}
}
#endregion

View File

@@ -101,7 +101,6 @@ namespace Robust.Shared.GameObjects
internal bool _mapIdInitialized;
internal bool _gridInitialized;
// TODO: Cache this.
/// <summary>
/// The EntityUid of the map which this object is on, if any.
/// </summary>

View File

@@ -1013,6 +1013,11 @@ namespace Robust.Shared.GameObjects
}
}
/// <summary>
/// Internal variant of <see cref="GetComponents"/> that directly returns the actual component set.
/// </summary>
internal IReadOnlyCollection<IComponent> GetComponentsInternal(EntityUid uid) => _entCompIndex[uid];
/// <inheritdoc />
public int ComponentCount(EntityUid uid)
{

View File

@@ -5,16 +5,17 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Robust.Shared.Containers;
using Robust.Shared.Maths;
namespace Robust.Shared.GameObjects;
public partial class EntityManager
{
// This method will soon be marked as obsolete.
// This method will soon(TM) be marked as obsolete.
public EntityUid SpawnEntity(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> SpawnAttachedTo(protoName, coordinates, overrides);
// This method will soon be marked as obsolete.
// This method will soon(TM) be marked as obsolete.
public EntityUid SpawnEntity(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
=> Spawn(protoName, coordinates, overrides);
@@ -83,12 +84,16 @@ public partial class EntityManager
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null)
=> Spawn(protoName, MapCoordinates.Nullspace, overrides);
public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
public EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true)
{
var entity = CreateEntityUninitialized(protoName, coordinates, overrides);
var entity = CreateEntityUninitialized(protoName, MapCoordinates.Nullspace, overrides);
InitializeAndStartEntity(entity, doMapInit);
return entity;
}
public virtual EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
{
var entity = CreateEntityUninitialized(protoName, coordinates, overrides, rotation);
InitializeAndStartEntity(entity, coordinates.MapId);
return entity;
}
@@ -111,32 +116,19 @@ public partial class EntityManager
if (!xform.ParentUid.IsValid())
return false;
if (!MetaQuery.TryGetComponent(target, out var meta))
return false;
if ((meta.Flags & MetaDataFlags.InContainer) == 0)
if (!_containers.TryGetContainingContainer(target, out var container))
{
uid = SpawnAttachedTo(protoName, xform.Coordinates, overrides);
uid = SpawnNextToOrDrop(protoName, target, xform, overrides);
return true;
}
if (!TryGetComponent(xform.ParentUid, out ContainerManagerComponent? containerComp))
return false;
foreach (var container in containerComp.Containers.Values)
{
if (!container.Contains(target))
continue;
uid = Spawn(protoName, overrides);
if (_containers.Insert(uid.Value, container))
return true;
DeleteEntity(uid.Value);
uid = null;
return false;
}
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
uid = Spawn(protoName, overrides, doMapInit);
if (_containers.Insert(uid.Value, container))
return true;
DeleteEntity(uid.Value);
uid = null;
return false;
}
@@ -155,7 +147,8 @@ public partial class EntityManager
if (!containerComp.Containers.TryGetValue(containerId, out var container))
return false;
uid = Spawn(protoName, overrides);
var doMapInit = _mapSystem.IsInitialized(TransformQuery.GetComponent(containerUid).MapUid);
uid = Spawn(protoName, overrides, doMapInit);
if (_containers.Insert(uid.Value, container))
return true;
@@ -171,7 +164,8 @@ public partial class EntityManager
if (!xform.ParentUid.IsValid())
return Spawn(protoName);
var uid = Spawn(protoName, overrides);
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
var uid = Spawn(protoName, overrides, doMapInit);
_xforms.DropNextTo(uid, target);
return uid;
}
@@ -184,14 +178,28 @@ public partial class EntityManager
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
var uid = Spawn(protoName, overrides);
return SpawnInContainerOrDrop(protoName, containerUid, containerId, out _, xform, containerComp, overrides);
}
public EntityUid SpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
out bool inserted,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null)
{
inserted = true;
xform ??= TransformQuery.GetComponent(containerUid);
var doMapInit = _mapSystem.IsInitialized(xform.MapUid);
var uid = Spawn(protoName, overrides, doMapInit);
if ((containerComp == null && !TryGetComponent(containerUid, out containerComp))
|| !containerComp.Containers.TryGetValue(containerId, out var container)
|| !_containers.Insert(uid, container))
{
xform ??= TransformQuery.GetComponent(containerUid);
inserted = false;
if (xform.ParentUid.IsValid())
_xforms.DropNextTo(uid, (containerUid, xform));
}

View File

@@ -9,6 +9,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Profiling;
@@ -297,14 +298,13 @@ namespace Robust.Shared.GameObjects
}
/// <inheritdoc />
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
{
var newEntity = CreateEntity(prototypeName, out _, overrides);
var transform = TransformQuery.GetComponent(newEntity);
if (coordinates.MapId == MapId.Nullspace)
{
DebugTools.Assert(_mapManager.GetMapEntityId(coordinates.MapId) == EntityUid.Invalid);
transform._parent = EntityUid.Invalid;
transform.Anchored = false;
return newEntity;
@@ -323,7 +323,7 @@ namespace Robust.Shared.GameObjects
else
{
coords = new EntityCoordinates(mapEnt, coordinates.Position);
_xforms.SetCoordinates(newEntity, transform, coords, null, newParent: mapXform);
_xforms.SetCoordinates(newEntity, transform, coords, rotation, newParent: mapXform);
}
return newEntity;
@@ -821,15 +821,22 @@ namespace Robust.Shared.GameObjects
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
{
var doMapInit = _mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
InitializeAndStartEntity(entity, doMapInit);
}
public void InitializeAndStartEntity(Entity<MetaDataComponent?> entity, bool doMapInit)
{
if (!MetaQuery.Resolve(entity.Owner, ref entity.Comp))
return;
try
{
var meta = MetaQuery.GetComponent(entity);
InitializeEntity(entity, meta);
StartEntity(entity);
InitializeEntity(entity.Owner, entity.Comp);
StartEntity(entity.Owner);
// If the map we're initializing the entity on is initialized, run map init on it.
if (_mapManager.IsMapInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID))
RunMapInit(entity, meta);
if (doMapInit)
RunMapInit(entity.Owner, entity.Comp);
}
catch (Exception e)
{
@@ -859,7 +866,7 @@ namespace Robust.Shared.GameObjects
DebugTools.Assert(meta.EntityLifeStage == EntityLifeStage.Initialized, $"Expected entity {ToPrettyString(entity)} to be initialized, was {meta.EntityLifeStage}");
SetLifeStage(meta, EntityLifeStage.MapInitialized);
EventBus.RaiseLocalEvent(entity, MapInitEventInstance, false);
EventBus.RaiseLocalEvent(entity, MapInitEventInstance);
}
/// <inheritdoc />

View File

@@ -1,4 +1,7 @@
namespace Robust.Shared.GameObjects
using Robust.Shared.Collections;
using Robust.Shared.Random;
namespace Robust.Shared.GameObjects
{
public static class EntityManagerExt
{
@@ -19,5 +22,79 @@
return default;
}
/// <summary>
/// Picks an entity at random with the supplied component.
/// </summary>
public static bool TryGetRandom<TComp1>(this IEntityManager entManager, IRobustRandom random, out EntityUid entity, bool includePaused = false) where TComp1 : IComponent
{
var entities = new ValueList<EntityUid>();
if (includePaused)
{
var query = entManager.AllEntityQueryEnumerator<TComp1>();
while (query.MoveNext(out var uid, out _))
{
entities.Add(uid);
}
}
else
{
var query = entManager.EntityQueryEnumerator<TComp1>();
while (query.MoveNext(out var uid, out _))
{
entities.Add(uid);
}
}
if (entities.Count == 0)
{
entity = EntityUid.Invalid;
return false;
}
entity = random.Pick(entities);
return true;
}
/// <summary>
/// Picks an entity at random with the supplied components.
/// </summary>
public static bool TryGetRandom<TComp1, TComp2>(this IEntityManager entManager, IRobustRandom random, out EntityUid entity, bool includePaused = false)
where TComp1 : IComponent
where TComp2 : IComponent
{
var entities = new ValueList<EntityUid>();
if (includePaused)
{
var query = entManager.AllEntityQueryEnumerator<TComp1, TComp2>();
while (query.MoveNext(out var uid, out _, out _))
{
entities.Add(uid);
}
}
else
{
var query = entManager.EntityQueryEnumerator<TComp1, TComp2>();
while (query.MoveNext(out var uid, out _, out _))
{
entities.Add(uid);
}
}
if (entities.Count == 0)
{
entity = EntityUid.Invalid;
return false;
}
entity = random.Pick(entities);
return true;
}
}
}

View File

@@ -5,8 +5,10 @@ using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using TerraFX.Interop.Windows;
namespace Robust.Shared.GameObjects;
@@ -699,32 +701,32 @@ public partial class EntitySystem
#region Entity Spawning
// This method will be obsoleted soon.
// This method will be obsoleted soon(TM).
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype, EntityCoordinates coordinates)
{
return ((IEntityManager)EntityManager).SpawnEntity(prototype, coordinates);
}
/// <inheritdoc cref="IEntityManager.Spawn(string?, MapCoordinates, ComponentRegistry?)" />
/// <inheritdoc cref="IEntityManager.Spawn(string?, MapCoordinates, ComponentRegistry?, Angle)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype, MapCoordinates coordinates)
=> EntityManager.Spawn(prototype, coordinates);
protected EntityUid Spawn(string? prototype, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
=> EntityManager.Spawn(prototype, coordinates, overrides, rotation);
/// <inheritdoc cref="IEntityManager.Spawn(string?, ComponentRegistry?)" />
/// <inheritdoc cref="IEntityManager.Spawn(string?, ComponentRegistry?, bool)" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid Spawn(string? prototype = null)
=> EntityManager.Spawn(prototype);
protected EntityUid Spawn(string? prototype = null, ComponentRegistry? overrides = null, bool doMapInit = true)
=> EntityManager.Spawn(prototype, overrides, doMapInit);
/// <inheritdoc cref="IEntityManager.SpawnAttachedTo" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates)
=> EntityManager.SpawnAttachedTo(prototype, coordinates);
protected EntityUid SpawnAttachedTo(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> EntityManager.SpawnAttachedTo(prototype, coordinates, overrides);
/// <inheritdoc cref="IEntityManager.SpawnAtPosition" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates)
=> EntityManager.SpawnAtPosition(prototype, coordinates);
protected EntityUid SpawnAtPosition(string? prototype, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
=> EntityManager.SpawnAtPosition(prototype, coordinates, overrides);
/// <inheritdoc cref="IEntityManager.TrySpawnInContainer" />
[MethodImpl(MethodImplOptions.AggressiveInlining)]

View File

@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
namespace Robust.Shared.GameObjects;
@@ -27,12 +28,12 @@ public partial interface IEntityManager
/// <summary>
/// Spawns an entity in nullspace.
/// </summary>
EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null);
EntityUid Spawn(string? protoName = null, ComponentRegistry? overrides = null, bool doMapInit = true);
/// <summary>
/// Spawns an entity at a specific world position. The entity will either be parented to the map or a grid.
/// </summary>
EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
EntityUid Spawn(string? protoName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
/// <summary>
/// Spawns an entity and then parents it to the entity that the given entity coordinates are relative to.
@@ -46,7 +47,7 @@ public partial interface IEntityManager
EntityUid SpawnAtPosition(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
/// <summary>
/// Attempts to spawn an entity inside of a container.
/// Attempt to spawn an entity and insert it into a container. If the insertion fails, the entity gets deleted.
/// </summary>
bool TrySpawnInContainer(
string? protoName,
@@ -58,9 +59,9 @@ public partial interface IEntityManager
/// <summary>
/// Attempts to spawn an entity inside of a container. If it fails to insert into the container, it will
/// instead attempt to spawn the entity next to the target.
/// instead drop the entity next to the target (see <see cref="SpawnNextToOrDrop"/>).
/// </summary>
public EntityUid SpawnInContainerOrDrop(
EntityUid SpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
@@ -68,9 +69,20 @@ public partial interface IEntityManager
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <inheritdoc cref="SpawnInContainerOrDrop(string?,Robust.Shared.GameObjects.EntityUid,string,Robust.Shared.GameObjects.TransformComponent?,Robust.Shared.Containers.ContainerManagerComponent?,Robust.Shared.Prototypes.ComponentRegistry?)"/>
EntityUid SpawnInContainerOrDrop(
string? protoName,
EntityUid containerUid,
string containerId,
out bool inserted,
TransformComponent? xform = null,
ContainerManagerComponent? containerComp = null,
ComponentRegistry? overrides = null);
/// <summary>
/// Attempts to spawn an entity adjacent to some other entity. If the other entity is in a container, this will
/// attempt to insert the new entity into the same container.
/// Attempts to spawn an entity adjacent to some other target entity. If the target entity is in
/// a container, this will attempt to insert the spawned entity into the same container. If the insertion fails,
/// the entity is deleted. If the entity is not in a container, this behaves like <see cref="SpawnNextToOrDrop"/>.
/// </summary>
bool TrySpawnNextTo(
string? protoName,

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Prometheus;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -76,10 +77,12 @@ namespace Robust.Shared.GameObjects
EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null);
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null);
EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!);
void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null);
void InitializeAndStartEntity(Entity<MetaDataComponent?> entity, bool doMapInit);
void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null);
void StartEntity(EntityUid entity);

View File

@@ -74,6 +74,28 @@ public abstract class SharedAppearanceSystem : EntitySystem
return component.AppearanceData.TryGetValue(key, out value);
}
/// <summary>
/// Copies appearance data from <c>src</c> to <c>dest</c>.
/// If <c>src</c> has no <see cref="AppearanceComponent"/> nothing is done.
/// If <c>dest</c> has no <c>AppearanceComponent</c> then it is created.
/// </summary>
public void CopyData(Entity<AppearanceComponent?> src, Entity<AppearanceComponent?> dest)
{
if (!Resolve(src, ref src.Comp, false))
return;
dest.Comp ??= EnsureComp<AppearanceComponent>(dest);
dest.Comp.AppearanceData.Clear();
foreach (var (key, value) in src.Comp.AppearanceData)
{
dest.Comp.AppearanceData[key] = value;
}
Dirty(dest, dest.Comp);
QueueUpdate(dest, dest.Comp);
}
}
[Serializable, NetSerializable]

View File

@@ -1,68 +1,188 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public abstract partial class SharedMapSystem
{
protected int LastMapId;
private void InitializeMap()
{
SubscribeLocalEvent<MapComponent, ComponentAdd>(OnMapAdd);
SubscribeLocalEvent<MapComponent, ComponentInit>(OnMapInit);
SubscribeLocalEvent<MapComponent, ComponentAdd>(OnComponentAdd);
SubscribeLocalEvent<MapComponent, ComponentInit>(OnCompInit);
SubscribeLocalEvent<MapComponent, ComponentStartup>(OnCompStartup);
SubscribeLocalEvent<MapComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MapComponent, ComponentShutdown>(OnMapRemoved);
SubscribeLocalEvent<MapComponent, ComponentHandleState>(OnMapHandleState);
SubscribeLocalEvent<MapComponent, ComponentGetState>(OnMapGetState);
}
public bool MapExists([NotNullWhen(true)] MapId? mapId)
{
return mapId != null && Maps.ContainsKey(mapId.Value);
}
public EntityUid GetMap(MapId mapId)
{
return Maps[mapId];
}
public bool TryGetMap([NotNullWhen(true)] MapId? mapId, [NotNullWhen(true)] out EntityUid? uid)
{
if (mapId == null || !Maps.TryGetValue(mapId.Value, out var map))
{
uid = null;
return false;
}
uid = map;
return true;
}
private void OnMapHandleState(EntityUid uid, MapComponent component, ref ComponentHandleState args)
{
if (args.Current is not MapComponentState state)
return;
component.MapId = state.MapId;
if (!MapManager.MapExists(state.MapId))
if (component.MapId == MapId.Nullspace)
{
var mapInternal = (IMapManagerInternal)MapManager;
mapInternal.CreateMap(state.MapId, uid);
if (state.MapId == MapId.Nullspace)
throw new Exception($"Received invalid map state? {ToPrettyString(uid)}");
component.MapId = state.MapId;
Maps.Add(component.MapId, uid);
RecursiveMapIdUpdate(uid, uid, component.MapId);
}
DebugTools.AssertEqual(component.MapId, state.MapId);
component.LightingEnabled = state.LightingEnabled;
var xformQuery = GetEntityQuery<TransformComponent>();
component.MapInitialized = state.Initialized;
xformQuery.GetComponent(uid).ChangeMapId(state.MapId, xformQuery);
if (LifeStage(uid) >= EntityLifeStage.Initialized)
SetPaused(uid, state.MapPaused);
else
component.MapPaused = state.MapPaused;
}
MapManager.SetMapPaused(state.MapId, state.MapPaused);
private void RecursiveMapIdUpdate(EntityUid uid, EntityUid mapUid, MapId mapId)
{
// This is required only in the event where an entity becomes a map AFTER children have already been attached to it.
// AFAIK, this currently only happens when the client applies entity states out of order (i.e., ignoring transform hierarchy),
// which itself only happens if PVS is disabled.
// TODO MAPS remove this
var xform = Transform(uid);
xform.MapUid = mapUid;
xform.MapID = mapId;
xform._mapIdInitialized = true;
foreach (var child in xform._children)
{
RecursiveMapIdUpdate(child, mapUid, mapId);
}
}
private void OnMapGetState(EntityUid uid, MapComponent component, ref ComponentGetState args)
{
args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused);
args.State = new MapComponentState(component.MapId, component.LightingEnabled, component.MapPaused, component.MapInitialized);
}
protected abstract void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args);
protected abstract MapId GetNextMapId();
private void OnMapInit(EntityUid uid, MapComponent component, ComponentInit args)
private void OnComponentAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
// ordered startups when
EnsureComp<PhysicsMapComponent>(uid);
EnsureComp<GridTreeComponent>(uid);
EnsureComp<MovedGridsComponent>(uid);
}
private void OnCompInit(EntityUid uid, MapComponent component, ComponentInit args)
{
if (component.MapId == MapId.Nullspace)
component.MapId = GetNextMapId();
DebugTools.AssertEqual(component.MapId.IsClientSide, IsClientSide(uid));
if (!Maps.TryAdd(component.MapId, uid))
{
if (Maps[component.MapId] != uid)
throw new Exception($"Attempted to initialize a map {ToPrettyString(uid)} with a duplicate map id {component.MapId}");
}
var msg = new MapChangedEvent(uid, component.MapId, true);
RaiseLocalEvent(uid, msg, true);
}
private void OnCompStartup(EntityUid uid, MapComponent component, ComponentStartup args)
{
if (component.MapPaused)
RecursiveSetPaused(uid, true);
}
private void OnMapRemoved(EntityUid uid, MapComponent component, ComponentShutdown args)
{
DebugTools.Assert(component.MapId != MapId.Nullspace);
Log.Info($"Deleting map {component.MapId}");
var iMap = (IMapManagerInternal)MapManager;
iMap.RemoveMapId(component.MapId);
Maps.Remove(component.MapId);
var msg = new MapChangedEvent(uid, component.MapId, false);
RaiseLocalEvent(uid, msg, true);
}
/// <summary>
/// Creates a new map, automatically assigning a map id.
/// </summary>
public EntityUid CreateMap(out MapId mapId, bool runMapInit = true)
{
mapId = GetNextMapId();
var uid = CreateMap(mapId, runMapInit);
return uid;
}
/// <inheritdoc cref="CreateMap(out Robust.Shared.Map.MapId,bool)"/>
public EntityUid CreateMap(bool runMapInit = true) => CreateMap(out _, runMapInit);
/// <summary>
/// Creates a new map with the specified map id.
/// </summary>
/// <exception cref="ArgumentException">Throws if an invalid or already existing map id is provided.</exception>
public EntityUid CreateMap(MapId mapId, bool runMapInit = true)
{
if (Maps.ContainsKey(mapId))
throw new ArgumentException($"Map with id {mapId} already exists");
if (mapId == MapId.Nullspace)
throw new ArgumentException($"Cannot create a null-space map");
if (_netManager.IsServer && mapId.IsClientSide)
throw new ArgumentException($"Attempted to create a client-side map on the server?");
if (_netManager.IsClient && _netManager.IsConnected && !mapId.IsClientSide)
throw new ArgumentException($"Attempted to create a client-side map entity with a non client-side map ID?");
var uid = EntityManager.CreateEntityUninitialized(null);
var map = _factory.GetComponent<MapComponent>();
map.MapId = mapId;
AddComp(uid, map);
// Give the entity a name, mainly for debugging. Content can always override this with a localized name.
var meta = MetaData(uid);
_meta.SetEntityName(uid, $"Map Entity", meta);
// Initialize components. this should add the map id to the collections.
EntityManager.InitializeComponents(uid, meta);
EntityManager.StartComponents(uid);
DebugTools.Assert(Maps[mapId] == uid);
if (runMapInit)
InitializeMap((uid, map));
else
SetPaused((uid, map), true);
return uid;
}
}

View File

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public abstract partial class SharedMapSystem
{
public bool IsInitialized(MapId mapId)
{
if (mapId == MapId.Nullspace)
return true; // Nullspace is always initialized
if(!Maps.TryGetValue(mapId, out var uid))
throw new ArgumentException($"Map {mapId} does not exist.");
return IsInitialized(uid);
}
public bool IsInitialized(EntityUid? map)
{
if (map == null)
return true; // Nullspace is always initialized
return IsInitialized(map.Value);
}
public bool IsInitialized(Entity<MapComponent?> map)
{
if (!_mapQuery.Resolve(map, ref map.Comp))
return false;
return map.Comp.MapInitialized;
}
private void OnMapInit(EntityUid uid, MapComponent component, MapInitEvent args)
{
DebugTools.Assert(!component.MapInitialized);
component.MapInitialized = true;
EntityManager.Dirty(uid, component);
}
public void InitializeMap(MapId mapId, bool unpause = true)
{
if(!Maps.TryGetValue(mapId, out var uid))
throw new ArgumentException($"Map {mapId} does not exist.");
InitializeMap(uid, unpause);
}
public void InitializeMap(Entity<MapComponent?> map, bool unpause = true)
{
if (!_mapQuery.Resolve(map, ref map.Comp))
return;
if (map.Comp.MapInitialized)
throw new ArgumentException($"Map {ToPrettyString(map)} is already initialized.");
RecursiveMapInit(map.Owner);
if (unpause)
SetPaused(map, false);
}
private void RecursiveMapInit(EntityUid entity)
{
var toInitialize = new List<EntityUid> {entity};
for (var i = 0; i < toInitialize.Count; i++)
{
var uid = toInitialize[i];
// toInitialize might contain deleted entities.
if(!_metaQuery.TryComp(uid, out var meta))
continue;
if (meta.EntityLifeStage == EntityLifeStage.MapInitialized)
continue;
toInitialize.AddRange(Transform(uid)._children);
EntityManager.RunMapInit(uid, meta);
}
}
}

View File

@@ -0,0 +1,60 @@
using System;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Robust.Shared.GameObjects;
public abstract partial class SharedMapSystem
{
public bool IsPaused(MapId mapId)
{
if (mapId == MapId.Nullspace)
return false;
if(!Maps.TryGetValue(mapId, out var uid))
throw new ArgumentException($"Map {mapId} does not exist.");
return IsPaused(uid);
}
public bool IsPaused(Entity<MapComponent?> map)
{
if (!_mapQuery.Resolve(map, ref map.Comp))
return false;
return map.Comp.MapPaused;
}
public void SetPaused(MapId mapId, bool paused)
{
if(!Maps.TryGetValue(mapId, out var uid))
throw new ArgumentException($"Map {mapId} does not exist.");
SetPaused(uid, paused);
}
public void SetPaused(Entity<MapComponent?> map, bool paused)
{
if (!_mapQuery.Resolve(map, ref map.Comp))
return;
if (map.Comp.MapPaused == paused)
return;
map.Comp.MapPaused = paused;
if (map.Comp.LifeStage < ComponentLifeStage.Initializing)
return;
Dirty(map);
RecursiveSetPaused(map, paused);
}
private void RecursiveSetPaused(EntityUid entity, bool paused)
{
_meta.SetEntityPaused(entity, paused);
foreach (var child in Transform(entity)._children)
{
RecursiveSetPaused(child, paused);
}
}
}

View File

@@ -20,17 +20,23 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly FixtureSystem _fixtures = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
private EntityQuery<MapComponent> _mapQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<TransformComponent> _xformQuery;
internal Dictionary<MapId, EntityUid> Maps { get; } = new();
public override void Initialize()
{
base.Initialize();
_mapQuery = GetEntityQuery<MapComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
InitializeMap();

View File

@@ -199,47 +199,34 @@ public abstract partial class SharedTransformSystem
#region Component Lifetime
private (EntityUid?, MapId) InitializeMapUid(EntityUid uid, TransformComponent xform)
{
if (xform._mapIdInitialized)
return (xform.MapUid, xform.MapID);
if (xform.ParentUid.IsValid())
{
(xform.MapUid, xform.MapID) = InitializeMapUid(xform.ParentUid, Transform(xform.ParentUid));
}
else if (_mapQuery.TryComp(uid, out var mapComp))
{
DebugTools.AssertNotEqual(mapComp.MapId, MapId.Nullspace);
xform.MapUid = uid;
xform.MapID = mapComp.MapId;
}
else
{
xform.MapUid = null;
xform.MapID = MapId.Nullspace;
}
xform._mapIdInitialized = true;
return (xform.MapUid, xform.MapID);
}
private void OnCompInit(EntityUid uid, TransformComponent component, ComponentInit args)
{
// Children MAY be initialized here before their parents are.
// We do this whole dance to handle this recursively,
// setting _mapIdInitialized along the way to avoid going to the MapComponent every iteration.
static MapId FindMapIdAndSet(EntityUid uid, TransformComponent xform, IEntityManager entMan, EntityQuery<TransformComponent> xformQuery, IMapManager mapManager)
{
if (xform._mapIdInitialized)
return xform.MapID;
MapId value;
if (xform.ParentUid.IsValid())
{
value = FindMapIdAndSet(xform.ParentUid, xformQuery.GetComponent(xform.ParentUid), entMan, xformQuery, mapManager);
}
else
{
// second level node, terminates recursion up the branch of the tree
if (entMan.TryGetComponent(uid, out MapComponent? mapComp))
{
value = mapComp.MapId;
}
else
{
// We allow entities to be spawned directly into null-space.
value = MapId.Nullspace;
}
}
xform.MapUid = value == MapId.Nullspace ? null : mapManager.GetMapEntityId(value);
xform.MapID = value;
xform._mapIdInitialized = true;
return value;
}
if (!component._mapIdInitialized)
{
FindMapIdAndSet(uid, component, EntityManager, XformQuery, _mapManager);
component._mapIdInitialized = true;
}
InitializeMapUid(uid, component);
// Has to be done if _parent is set from ExposeData.
if (component.ParentUid.IsValid())
@@ -522,6 +509,8 @@ public abstract partial class SharedTransformSystem
throw new InvalidOperationException($"Attempted to re-parent to a terminating object. Entity: {ToPrettyString(uid)}, new parent: {ToPrettyString(value.EntityId)}");
}
InitializeMapUid(value.EntityId, newParent);
// Check for recursive/circular transform hierarchies.
if (xform.MapUid == newParent.MapUid)
{
@@ -881,6 +870,28 @@ public abstract partial class SharedTransformSystem
return GetMapCoordinates(entity.Comp);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetMapCoordinates(EntityUid entity, MapCoordinates coordinates)
{
var xform = XformQuery.GetComponent(entity);
SetMapCoordinates((entity, xform), coordinates);
}
public void SetMapCoordinates(Entity<TransformComponent> entity, MapCoordinates coordinates)
{
var mapUid = _map.GetMap(coordinates.MapId);
if (!_gridQuery.HasComponent(entity) &&
_mapManager.TryFindGridAt(mapUid, coordinates.Position, out var targetGrid, out _))
{
var invWorldMatrix = GetInvWorldMatrix(targetGrid);
SetCoordinates(entity, new EntityCoordinates(targetGrid, invWorldMatrix.Transform(coordinates.Position)));
}
else
{
SetCoordinates(entity, new EntityCoordinates(mapUid, coordinates.Position));
}
}
[Pure]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityUid uid)
{
@@ -993,7 +1004,7 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPosition(Entity<TransformComponent> entity, Vector2 worldPos)
{
SetWorldPositionRotation(entity.Owner, worldPos, entity.Comp.LocalRotation, entity.Comp);
SetWorldPositionRotationInternal(entity.Owner, worldPos, null, entity.Comp);
}
#endregion
@@ -1079,10 +1090,17 @@ public abstract partial class SharedTransformSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetWorldPositionRotation(EntityUid uid, Vector2 worldPos, Angle worldRot, TransformComponent? component = null)
{
SetWorldPositionRotationInternal(uid, worldPos, worldRot, component);
}
private void SetWorldPositionRotationInternal(EntityUid uid, Vector2 worldPos, Angle? worldRot = null, TransformComponent? component = null)
{
if (!XformQuery.Resolve(uid, ref component))
return;
// If no worldRot supplied then default the new rotation to 0.
if (!component._parent.IsValid() || component.MapUid == null)
{
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
@@ -1453,8 +1471,7 @@ public abstract partial class SharedTransformSystem
while (targetXform.ParentUid.IsValid())
{
if (_container.IsEntityInContainer(targetUid)
&& _container.TryGetContainingContainer(targetXform.ParentUid, targetUid, out var container,
skipExistCheck: true)
&& _container.TryGetContainingContainer(targetXform.ParentUid, targetUid, out var container)
&& _container.Insert((entity, xform, null, null), container))
{
return;

View File

@@ -26,6 +26,7 @@ namespace Robust.Shared.GameObjects
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly SharedContainerSystem _container = default!;
private EntityQuery<MapComponent> _mapQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
protected EntityQuery<TransformComponent> XformQuery;
@@ -50,6 +51,7 @@ namespace Robust.Shared.GameObjects
UpdatesOutsidePrediction = true;
_mapQuery = GetEntityQuery<MapComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
XformQuery = GetEntityQuery<TransformComponent>();

View File

@@ -12,35 +12,29 @@ namespace Robust.Shared.Map.Components
public sealed partial class MapComponent : Component
{
[ViewVariables(VVAccess.ReadWrite)]
[DataField("lightingEnabled")]
[DataField]
public bool LightingEnabled { get; set; } = true;
[ViewVariables(VVAccess.ReadOnly)]
public MapId MapId { get; internal set; } = MapId.Nullspace;
[ViewVariables(VVAccess.ReadOnly)]
public bool MapPaused { get; set; } = false;
[DataField, Access(typeof(SharedMapSystem), typeof(MapManager))]
public bool MapPaused;
//TODO replace MapPreInit with the map's entity life stage
[ViewVariables(VVAccess.ReadOnly)]
public bool MapPreInit { get; set; } = false;
[DataField, Access(typeof(SharedMapSystem), typeof(MapManager))]
public bool MapInitialized;
}
/// <summary>
/// Serialized state of a <see cref="MapGridComponentState"/>.
/// </summary>
[Serializable, NetSerializable]
public sealed class MapComponentState : ComponentState
public sealed class MapComponentState(MapId mapId, bool lightingEnabled, bool paused, bool init)
: ComponentState
{
public MapId MapId;
public bool LightingEnabled;
public bool MapPaused;
public MapComponentState(MapId mapId, bool lightingEnabled, bool paused)
{
MapId = mapId;
LightingEnabled = lightingEnabled;
MapPaused = paused;
}
public MapId MapId = mapId;
public bool LightingEnabled = lightingEnabled;
public bool MapPaused = paused;
public bool Initialized = init;
}
}

View File

@@ -23,9 +23,6 @@ namespace Robust.Shared.Map
public const bool Approximate = false;
public const bool IncludeMap = true;
[Obsolete("Use EntityQuery<MapGridComponent>")]
IEnumerable<MapGridComponent> GetAllGrids();
/// <summary>
/// Should the OnTileChanged event be suppressed? This is useful for initially loading the map
/// so that you don't spam an event for each of the million station tiles.
@@ -42,16 +39,7 @@ namespace Robust.Shared.Map
void Restart();
/// <summary>
/// Creates a new map.
/// </summary>
/// <param name="mapId">
/// If provided, the new map will use this ID. If not provided, a new ID will be selected automatically.
/// </param>
/// <returns>The new map.</returns>
/// <exception cref="InvalidOperationException">
/// Throw if an explicit ID for the map or default grid is passed and a map or grid with the specified ID already exists, respectively.
/// </exception>
[Obsolete("Use MapSystem")]
MapId CreateMap(MapId? mapId = null);
/// <summary>
@@ -59,24 +47,12 @@ namespace Robust.Shared.Map
/// </summary>
/// <param name="mapId">The map ID to check existence of.</param>
/// <returns>True if the map exists, false otherwise.</returns>
bool MapExists(MapId mapId);
bool MapExists([NotNullWhen(true)] MapId? mapId);
/// <summary>
/// Creates a new entity, then sets it as the map entity.
/// </summary>
/// <returns>Newly created entity.</returns>
EntityUid CreateNewMapEntity(MapId mapId);
/// <summary>
/// Sets the MapEntity(root node) for a given map. If an entity is already set, it will be deleted
/// before the new one is set.
/// </summary>
/// <param name="updateChildren">Should we re-parent children from the old map to the new one, or delete them.</param>
void SetMapEntity(MapId mapId, EntityUid newMapEntityId, bool updateChildren = true);
/// <summary>
/// Returns the map entity ID for a given map.
/// Returns the map entity ID for a given map, or an invalid entity Id if the map does not exist.
/// </summary>
[Obsolete("Use TryGetMap")]
EntityUid GetMapEntityId(MapId mapId);
/// <summary>
@@ -93,6 +69,7 @@ namespace Robust.Shared.Map
MapGridComponent CreateGrid(MapId currentMapId, in GridCreateOptions options);
MapGridComponent CreateGrid(MapId currentMapId);
Entity<MapGridComponent> CreateGridEntity(MapId currentMapId, GridCreateOptions? options = null);
Entity<MapGridComponent> CreateGridEntity(EntityUid map, GridCreateOptions? options = null);
[Obsolete("Use GetComponent<MapGridComponent>(uid)")]
MapGridComponent GetGrid(EntityUid gridId);
@@ -233,17 +210,13 @@ namespace Robust.Shared.Map
#endregion
void DeleteGrid(EntityUid euid);
bool HasMapEntity(MapId mapId);
[Obsolete("Just delete the grid entity")]
void DeleteGrid(EntityUid euid);
bool IsGrid(EntityUid uid);
bool IsMap(EntityUid uid);
[Obsolete("Whatever this is used for, it is a terrible idea. Create a new map and get it's MapId.")]
MapId NextMapId();
MapGridComponent GetGridComp(EntityUid euid);
//
// Pausing functions
//
@@ -252,14 +225,15 @@ namespace Robust.Shared.Map
void DoMapInitialize(MapId mapId);
// TODO rename this to actually be descriptive or just remove it.
[Obsolete("Use CreateMap's runMapInit argument")]
void AddUninitializedMap(MapId mapId);
[Pure]
[Obsolete("Use MapSystem")]
bool IsMapPaused(MapId mapId);
[Pure]
[Obsolete("Use MapSystem")]
bool IsMapInitialized(MapId mapId);
}
public struct GridCreateOptions

View File

@@ -1,23 +1,15 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
namespace Robust.Shared.Map
{
/// <inheritdoc />
internal interface IMapManagerInternal : IMapManager
{
IGameTiming GameTiming { get; }
/// <summary>
/// Raises the OnTileChanged event.
/// </summary>
/// <param name="tileRef">A reference to the new tile.</param>
/// <param name="oldTile">The old tile that got replaced.</param>
void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk);
MapId CreateMap(MapId? mapId, EntityUid euid);
void RemoveMapId(MapId mapId);
}
}

View File

@@ -53,5 +53,7 @@ namespace Robust.Shared.Map
{
return Value.ToString();
}
public bool IsClientSide => Value < 0;
}
}

View File

@@ -12,32 +12,15 @@ using Robust.Shared.Utility;
namespace Robust.Shared.Map;
internal partial class MapManager
{
[Obsolete("Use GetComponent<MapGridComponent>(uid)")]
public MapGridComponent GetGridComp(EntityUid euid)
{
return EntityManager.GetComponent<MapGridComponent>(euid);
}
[Obsolete("Use EntityQuery instead.")]
public IEnumerable<MapGridComponent> GetAllGrids()
{
var compQuery = EntityManager.AllEntityQueryEnumerator<MapGridComponent>();
while (compQuery.MoveNext(out var comp))
{
yield return comp;
}
}
// ReSharper disable once MethodOverloadWithOptionalParameter
public MapGridComponent CreateGrid(MapId currentMapId, ushort chunkSize = 16)
{
return CreateGrid(currentMapId, chunkSize, default);
return CreateGrid(GetMapEntityIdOrThrow(currentMapId), chunkSize, default);
}
public MapGridComponent CreateGrid(MapId currentMapId, in GridCreateOptions options)
{
return CreateGrid(currentMapId, options.ChunkSize, default);
return CreateGrid(GetMapEntityIdOrThrow(currentMapId), options.ChunkSize, default);
}
public MapGridComponent CreateGrid(MapId currentMapId)
@@ -46,18 +29,19 @@ internal partial class MapManager
}
public Entity<MapGridComponent> CreateGridEntity(MapId currentMapId, GridCreateOptions? options = null)
{
return CreateGridEntity(GetMapEntityIdOrThrow(currentMapId), options);
}
public Entity<MapGridComponent> CreateGridEntity(EntityUid map, GridCreateOptions? options = null)
{
options ??= GridCreateOptions.Default;
return CreateGrid(currentMapId, options.Value.ChunkSize, default);
return CreateGrid(map, options.Value.ChunkSize, default);
}
[Obsolete("Use GetComponent<MapGridComponent>(uid)")]
public MapGridComponent GetGrid(EntityUid gridId)
{
DebugTools.Assert(gridId.IsValid());
return GetGridComp(gridId);
}
=> EntityManager.GetComponent<MapGridComponent>(gridId);
[Obsolete("Use HasComponent<MapGridComponent>(uid)")]
public bool IsGrid(EntityUid uid)
@@ -108,10 +92,6 @@ internal partial class MapManager
public virtual void DeleteGrid(EntityUid euid)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
// Possible the grid was already deleted / is invalid
if (!EntityManager.TryGetComponent<MapGridComponent>(euid, out var iGrid))
{
@@ -141,10 +121,6 @@ internal partial class MapManager
/// <param name="oldTile">The old tile that got replaced.</param>
public void RaiseOnTileChanged(TileRef tileRef, Tile oldTile, Vector2i chunk)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (SuppressOnTileChanged)
return;
@@ -153,7 +129,7 @@ internal partial class MapManager
EntityManager.EventBus.RaiseLocalEvent(euid, ref ev, true);
}
protected Entity<MapGridComponent> CreateGrid(MapId currentMapId, ushort chunkSize, EntityUid forcedGridEuid)
protected Entity<MapGridComponent> CreateGrid(EntityUid map, ushort chunkSize, EntityUid forcedGridEuid)
{
var gridEnt = EntityManager.CreateEntityUninitialized(null, forcedGridEuid);
@@ -166,8 +142,7 @@ internal partial class MapManager
//are applied. After they are applied the parent may be different, but the MapId will
//be the same. This causes TransformComponent.ParentUid of a grid to be unsafe to
//use in transform states anytime before the state parent is properly set.
var fallbackParentEuid = GetMapEntityIdOrThrow(currentMapId);
EntityManager.GetComponent<TransformComponent>(gridEnt).AttachParent(fallbackParentEuid);
EntityManager.GetComponent<TransformComponent>(gridEnt).AttachParent(map);
var meta = EntityManager.GetComponent<MetaDataComponent>(gridEnt);
EntityManager.System<MetaDataSystem>().SetEntityName(gridEnt, $"grid", meta);

View File

@@ -1,12 +0,0 @@
namespace Robust.Shared.Map;
internal partial class MapManager
{
public void RemoveMapId(MapId mapId)
{
if (mapId == MapId.Nullspace)
return;
_mapEntities.Remove(mapId);
}
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
using Robust.Shared.Utility;
@@ -27,131 +28,42 @@ public sealed class MapEventArgs : EventArgs
internal partial class MapManager
{
private readonly Dictionary<MapId, EntityUid> _mapEntities = new();
private MapId _highestMapId = MapId.Nullspace;
private Dictionary<MapId, EntityUid> _mapEntities => _mapSystem.Maps;
/// <inheritdoc />
public virtual void DeleteMap(MapId mapId)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (!_mapEntities.TryGetValue(mapId, out var ent) || !ent.IsValid())
throw new InvalidOperationException($"Attempted to delete nonexistent map '{mapId}'");
EntityManager.DeleteEntity(ent);
DebugTools.Assert(!_mapEntities.ContainsKey(mapId));
}
/// <inheritdoc />
public MapId CreateMap(MapId? mapId = null)
{
return ((IMapManagerInternal) this).CreateMap(mapId, default);
if (mapId != null)
{
_mapSystem.CreateMap(mapId.Value);
return mapId.Value;
}
_mapSystem.CreateMap(out var map);
return map;
}
/// <inheritdoc />
public bool MapExists(MapId mapId)
public bool MapExists([NotNullWhen(true)] MapId? mapId)
{
return _mapEntities.ContainsKey(mapId);
}
/// <inheritdoc />
public EntityUid CreateNewMapEntity(MapId mapId)
{
DebugTools.Assert(mapId != MapId.Nullspace);
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
var newEntity = EntityManager.CreateEntityUninitialized(null);
SetMapEntity(mapId, newEntity);
EntityManager.InitializeComponents(newEntity);
EntityManager.StartComponents(newEntity);
return newEntity;
}
/// <inheritdoc />
public void SetMapEntity(MapId mapId, EntityUid newMapEntity, bool updateChildren = true)
{
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
if (!_mapEntities.ContainsKey(mapId))
throw new InvalidOperationException($"Map {mapId} does not exist.");
foreach (var kvEntity in _mapEntities)
{
if (kvEntity.Value == newMapEntity)
{
if (mapId == kvEntity.Key)
return;
throw new InvalidOperationException(
$"Entity {newMapEntity} is already the root node of another map {kvEntity.Key}.");
}
}
MapComponent? mapComp;
// If this is being done as part of maploader then we want to copy the preinit state across mainly.
bool preInit = false;
bool paused = false;
// remove existing graph
if (_mapEntities.TryGetValue(mapId, out var oldEntId))
{
if (EntityManager.TryGetComponent(oldEntId, out mapComp))
{
preInit = mapComp.MapPreInit;
paused = mapComp.MapPaused;
}
EntityManager.System<SharedTransformSystem>().ReparentChildren(oldEntId, newMapEntity);
//Note: EntityUid.Invalid gets passed in here
//Note: This prevents setting a subgraph as the root, since the subgraph will be deleted
EntityManager.DeleteEntity(oldEntId);
}
var raiseEvent = false;
// re-use or add map component
if (!EntityManager.TryGetComponent(newMapEntity, out mapComp))
mapComp = EntityManager.AddComponent<MapComponent>(newMapEntity);
else
{
raiseEvent = true;
if (mapComp.MapId != mapId)
{
_sawmill.Warning($"Setting map {mapId} root to entity {newMapEntity}, but entity thinks it is root node of map {mapComp.MapId}.");
}
}
_sawmill.Debug($"Setting map {mapId} entity to {newMapEntity}");
// set as new map entity
mapComp.MapPreInit = preInit;
mapComp.MapPaused = paused;
mapComp.MapId = mapId;
_mapEntities[mapId] = newMapEntity;
// Yeah this sucks but I just want to save maps for now, deal.
if (raiseEvent)
{
var ev = new MapChangedEvent(newMapEntity, mapId, true);
EntityManager.EventBus.RaiseLocalEvent(newMapEntity, ev, true);
}
return _mapSystem.MapExists(mapId);
}
/// <inheritdoc />
public EntityUid GetMapEntityId(MapId mapId)
{
if (_mapEntities.TryGetValue(mapId, out var entId))
return entId;
if (_mapSystem.TryGetMap(mapId, out var entId))
return entId.Value;
return EntityUid.Invalid;
}
@@ -161,13 +73,12 @@ internal partial class MapManager
/// </summary>
public EntityUid GetMapEntityIdOrThrow(MapId mapId)
{
return _mapEntities[mapId];
return _mapSystem.GetMap(mapId);
}
/// <inheritdoc />
public bool HasMapEntity(MapId mapId)
public bool TryGetMap([NotNullWhen(true)] MapId? mapId, [NotNullWhen(true)] out EntityUid? uid)
{
return _mapEntities.ContainsKey(mapId);
return _mapSystem.TryGetMap(mapId, out uid);
}
/// <inheritdoc />
@@ -181,67 +92,4 @@ internal partial class MapManager
{
return EntityManager.HasComponent<MapComponent>(uid);
}
/// <inheritdoc />
public MapId NextMapId()
{
return _highestMapId = new MapId(_highestMapId.Value + 1);
}
MapId IMapManagerInternal.CreateMap(MapId? mapId, EntityUid entityUid)
{
if (mapId == MapId.Nullspace)
throw new InvalidOperationException("Attempted to create a null-space map.");
#if DEBUG
DebugTools.Assert(_dbgGuardRunning);
#endif
var actualId = mapId ?? new MapId(_highestMapId.Value + 1);
if (MapExists(actualId))
throw new InvalidOperationException($"A map with ID {actualId} already exists");
if (_highestMapId.Value < actualId.Value)
_highestMapId = actualId;
_sawmill.Info($"Creating new map {actualId}");
if (actualId != MapId.Nullspace) // nullspace isn't bound to an entity
{
Entity<MapComponent> result = default;
var query = EntityManager.AllEntityQueryEnumerator<MapComponent>();
while (query.MoveNext(out var uid, out var map))
{
if (map.MapId != actualId)
continue;
result = (uid, map);
break;
}
if (result != default)
{
DebugTools.Assert(mapId != null);
_mapEntities.Add(actualId, result);
_sawmill.Debug($"Rebinding map {actualId} to entity {result.Owner}");
}
else
{
var newEnt = EntityManager.CreateEntityUninitialized(null, entityUid);
_mapEntities.Add(actualId, newEnt);
var mapComp = EntityManager.AddComponent<MapComponent>(newEnt);
mapComp.MapId = actualId;
var meta = EntityManager.GetComponent<MetaDataComponent>(newEnt);
EntityManager.System<MetaDataSystem>().SetEntityName(newEnt, $"map {actualId}", meta);
EntityManager.Dirty(newEnt, mapComp, meta);
EntityManager.InitializeComponents(newEnt, meta);
EntityManager.StartComponents(newEnt);
_sawmill.Debug($"Binding map {actualId} to entity {newEnt}");
}
}
return actualId;
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Globalization;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.Map.Components;
@@ -8,145 +7,44 @@ namespace Robust.Shared.Map
{
internal partial class MapManager
{
/// <inheritdoc />
public void SetMapPaused(MapId mapId, bool paused)
{
if(mapId == MapId.Nullspace)
return;
if(!MapExists(mapId))
throw new ArgumentException("That map does not exist.");
var mapUid = GetMapEntityId(mapId);
var mapComp = EntityManager.GetComponent<MapComponent>(mapUid);
if (mapComp.MapPaused == paused)
return;
mapComp.MapPaused = paused;
EntityManager.Dirty(mapUid, mapComp);
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
var metaSystem = EntityManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
RecursiveSetPaused(mapUid, paused, in xformQuery, in metaQuery, in metaSystem);
_mapSystem.SetPaused(mapId, paused);
}
private static void RecursiveSetPaused(EntityUid entity, bool paused,
in EntityQuery<TransformComponent> xformQuery,
in EntityQuery<MetaDataComponent> metaQuery,
in MetaDataSystem system)
public void SetMapPaused(EntityUid uid, bool paused)
{
system.SetEntityPaused(entity, paused, metaQuery.GetComponent(entity));
var xform = xformQuery.GetComponent(entity);
foreach (var child in xform._children)
{
RecursiveSetPaused(child, paused, in xformQuery, in metaQuery, in system);
}
_mapSystem.SetPaused(uid, paused);
}
/// <inheritdoc />
public void DoMapInitialize(MapId mapId)
{
if(!MapExists(mapId))
throw new ArgumentException("That map does not exist.");
if (IsMapInitialized(mapId))
throw new ArgumentException("That map is already initialized.");
var mapEnt = GetMapEntityId(mapId);
var mapComp = EntityManager.GetComponent<MapComponent>(mapEnt);
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var metaQuery = EntityManager.GetEntityQuery<MetaDataComponent>();
var metaSystem = EntityManager.EntitySysManager.GetEntitySystem<MetaDataSystem>();
mapComp.MapPreInit = false;
mapComp.MapPaused = false;
EntityManager.Dirty(mapEnt, mapComp);
RecursiveDoMapInit(mapEnt, in xformQuery, in metaQuery, in metaSystem);
_mapSystem.InitializeMap(mapId);
}
private void RecursiveDoMapInit(EntityUid entity,
in EntityQuery<TransformComponent> xformQuery,
in EntityQuery<MetaDataComponent> metaQuery,
in MetaDataSystem system)
public bool IsMapInitialized(MapId mapId)
{
// RunMapInit can modify the TransformTree
// ToArray caches deleted euids, we check here if they still exist.
if(!metaQuery.TryGetComponent(entity, out var meta))
return;
EntityManager.RunMapInit(entity, meta);
system.SetEntityPaused(entity, false, meta);
foreach (var child in xformQuery.GetComponent(entity)._children.ToArray())
{
RecursiveDoMapInit(child, in xformQuery, in metaQuery, in system);
}
return _mapSystem.IsInitialized(mapId);
}
/// <inheritdoc />
public void AddUninitializedMap(MapId mapId)
{
SetMapPreInit(mapId);
}
private bool CheckMapPause(MapId mapId)
{
if(mapId == MapId.Nullspace)
return false;
var mapEuid = GetMapEntityId(mapId);
if (!EntityManager.TryGetComponent<MapComponent>(mapEuid, out var map))
return false;
return map.MapPaused;
}
private void SetMapPreInit(MapId mapId)
{
if(mapId == MapId.Nullspace)
return;
var mapEuid = GetMapEntityId(mapId);
var mapComp = EntityManager.GetComponent<MapComponent>(mapEuid);
mapComp.MapPreInit = true;
}
private bool CheckMapPreInit(MapId mapId)
{
if(mapId == MapId.Nullspace)
return false;
var mapEuid = GetMapEntityId(mapId);
if (!EntityManager.TryGetComponent<MapComponent>(mapEuid, out var map))
return false;
return map.MapPreInit;
var ent = GetMapEntityId(mapId);
EntityManager.GetComponent<MapComponent>(ent).MapInitialized = false;
var meta = EntityManager.GetComponent<MetaDataComponent>(ent);
((EntityManager)EntityManager).SetLifeStage(meta, EntityLifeStage.Initialized);
}
/// <inheritdoc />
public bool IsMapPaused(MapId mapId)
{
if(mapId == MapId.Nullspace)
return false;
var mapEuid = GetMapEntityId(mapId);
if (!EntityManager.TryGetComponent<MapComponent>(mapEuid, out var map))
return false;
return map.MapPaused || map.MapPreInit;
return _mapSystem.IsPaused(mapId);
}
/// <inheritdoc />
public bool IsMapInitialized(MapId mapId)
public bool IsMapPaused(EntityUid uid)
{
return !CheckMapPreInit(mapId);
return _mapSystem.IsPaused(uid);
}
/// <summary>

View File

@@ -3,13 +3,9 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Shared.Map;
@@ -23,60 +19,36 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
[Dependency] private readonly IConsoleHost _conhost = default!;
private ISawmill _sawmill = default!;
private ISawmill _sawmill => _mapSystem.Log;
private FixtureSystem _fixtureSystem = default!;
private SharedMapSystem _mapSystem = default!;
private SharedPhysicsSystem _physics = default!;
private SharedTransformSystem _transformSystem = default!;
private EntityQuery<FixturesComponent> _fixturesQuery;
private EntityQuery<GridTreeComponent> _gridTreeQuery;
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<TransformComponent> _xformQuery;
/// <inheritdoc />
public void Initialize()
{
_fixturesQuery = EntityManager.GetEntityQuery<FixturesComponent>();
_gridTreeQuery = EntityManager.GetEntityQuery<GridTreeComponent>();
_gridQuery = EntityManager.GetEntityQuery<MapGridComponent>();
_physicsQuery = EntityManager.GetEntityQuery<PhysicsComponent>();
_xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
_sawmill = Logger.GetSawmill("map");
#if DEBUG
DebugTools.Assert(!_dbgGuardInit);
DebugTools.Assert(!_dbgGuardRunning);
_dbgGuardInit = true;
#endif
InitializeMapPausing();
}
/// <inheritdoc />
public void Startup()
{
_fixtureSystem = EntityManager.System<FixtureSystem>();
_physics = EntityManager.System<SharedPhysicsSystem>();
_transformSystem = EntityManager.System<SharedTransformSystem>();
_mapSystem = EntityManager.System<SharedMapSystem>();
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
_dbgGuardRunning = true;
#endif
_sawmill.Debug("Starting...");
}
/// <inheritdoc />
public void Shutdown()
{
#if DEBUG
DebugTools.Assert(_dbgGuardInit);
#endif
_sawmill.Debug("Stopping...");
// TODO: AllEntityQuery instead???
@@ -102,9 +74,4 @@ internal partial class MapManager : IMapManagerInternal, IEntityEventSubscriber
EntityManager.DeleteEntity(uid);
}
}
#if DEBUG
private bool _dbgGuardInit;
private bool _dbgGuardRunning;
#endif
}

View File

@@ -313,55 +313,60 @@ public partial class SharedPhysicsSystem
Dirty(uid, body);
}
public void SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
public bool SetAngularVelocity(EntityUid uid, float value, bool dirty = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body))
return;
return false;
if (body.BodyType == BodyType.Static)
return;
return false;
if (value * value > 0.0f)
{
if (!WakeBody(uid, manager: manager, body: body))
return;
return false;
}
// CloseToPercent tolerance needs to be small enough such that an angular velocity just above
// sleep-tolerance can damp down to sleeping.
if (MathHelper.CloseToPercent(body.AngularVelocity, value, 0.00001f))
return;
return false;
body.AngularVelocity = value;
if (dirty)
Dirty(uid, body);
return true;
}
/// <summary>
/// Attempts to set the body to collidable, wake it, then move it.
/// </summary>
public void SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
public bool SetLinearVelocity(EntityUid uid, Vector2 velocity, bool dirty = true, bool wakeBody = true, FixturesComponent? manager = null, PhysicsComponent? body = null)
{
if (!Resolve(uid, ref body))
return;
return false;
if (body.BodyType == BodyType.Static) return;
if (body.BodyType == BodyType.Static)
return false;
if (wakeBody && Vector2.Dot(velocity, velocity) > 0.0f)
{
if (!WakeBody(uid, manager: manager, body: body))
return;
return false;
}
if (body.LinearVelocity.EqualsApprox(velocity, 0.0000001f))
return;
return false;
body.LinearVelocity = velocity;
if (dirty)
Dirty(uid, body);
return true;
}
public void SetAngularDamping(EntityUid uid, PhysicsComponent body, float value, bool dirty = true)

View File

@@ -660,13 +660,11 @@ public abstract partial class SharedPhysicsSystem
});
// Update data sequentially
var metaQuery = GetEntityQuery<MetaDataComponent>();
for (var i = 0; i < actualIslands.Length; i++)
{
var island = actualIslands[i];
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery, metaQuery);
UpdateBodies(in island, solvedPositions, solvedAngles, linearVelocities, angularVelocities, xformQuery);
SleepBodies(in island, sleepStatus);
}
@@ -1001,8 +999,7 @@ public abstract partial class SharedPhysicsSystem
float[] angles,
Vector2[] linearVelocities,
float[] angularVelocities,
EntityQuery<TransformComponent> xformQuery,
EntityQuery<MetaDataComponent> metaQuery)
EntityQuery<TransformComponent> xformQuery)
{
foreach (var (joint, error) in island.BrokenJoints)
{
@@ -1035,21 +1032,22 @@ public abstract partial class SharedPhysicsSystem
}
var linVelocity = linearVelocities[offset + i];
var physicsDirtied = false;
if (!float.IsNaN(linVelocity.X) && !float.IsNaN(linVelocity.Y))
{
SetLinearVelocity(uid, linVelocity, false, body: body);
physicsDirtied |= SetLinearVelocity(uid, linVelocity, false, body: body);
}
var angVelocity = angularVelocities[offset + i];
if (!float.IsNaN(angVelocity))
{
SetAngularVelocity(uid, angVelocity, false, body: body);
physicsDirtied |= SetAngularVelocity(uid, angVelocity, false, body: body);
}
// TODO: Should check if the values update.
Dirty(uid, body, metaQuery.GetComponent(uid));
if (physicsDirtied)
Dirty(uid, body);
}
}

View File

@@ -6,7 +6,7 @@ namespace Robust.Shared.Prototypes;
/// Prototype that represents game entities.
/// </summary>
[Prototype("entityCategory")]
public sealed class EntityCategoryPrototype : IPrototype
public sealed partial class EntityCategoryPrototype : IPrototype
{
[IdDataField]
public string ID { get; private set; } = default!;
@@ -22,4 +22,4 @@ public sealed class EntityCategoryPrototype : IPrototype
/// </summary>
[DataField("description")]
public string? Description { get; private set; }
}
}

View File

@@ -3,6 +3,8 @@ using System.Collections.Frozen;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Robust.Shared.Random;
using Robust.Shared.Reflection;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.Markdown;
@@ -271,7 +273,8 @@ public interface IPrototypeManager
out Dictionary<Type, HashSet<string>> prototypes);
/// <summary>
/// This method uses reflection to validate that prototype id fields correspond to valid prototypes.
/// This method uses reflection to validate that all static prototype id fields correspond to valid prototypes.
/// This will validate all known to <see cref="IReflectionManager"/>
/// </summary>
/// <remarks>
/// This will validate any field that has either a <see cref="ValidatePrototypeIdAttribute{T}"/> attribute, or a
@@ -279,7 +282,12 @@ public interface IPrototypeManager
/// </remarks>
/// <param name="prototypes">A collection prototypes to use for validation. Any prototype not in this collection
/// will be considered invalid.</param>
List<string> ValidateFields(Dictionary<Type, HashSet<string>> prototypes);
List<string> ValidateStaticFields(Dictionary<Type, HashSet<string>> prototypes);
/// <summary>
/// This is a variant of <see cref="ValidateStaticFields(System.Collections.Generic.Dictionary{System.Type,System.Collections.Generic.HashSet{string}})"/> that only validates a single type.
/// </summary>
List<string> ValidateStaticFields(Type type, Dictionary<Type, HashSet<string>> prototypes);
/// <summary>
/// This method will serialize all loaded prototypes into yaml and then validate them. This can be used to ensure
@@ -386,6 +394,11 @@ public interface IPrototypeManager
/// For example: /Prototypes/Guidebook
/// </param>
void AbstractDirectory(ResPath path);
/// <summary>
/// Tries to get a random prototype.
/// </summary>
bool TryGetRandom<T>(IRobustRandom random, [NotNullWhen(true)] out IPrototype? prototype) where T : class, IPrototype;
}
internal interface IPrototypeManagerInternal : IPrototypeManager

View File

@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using Robust.Shared.Utility;
using BindingFlags = System.Reflection.BindingFlags;
@@ -13,35 +12,41 @@ namespace Robust.Shared.Prototypes;
public partial class PrototypeManager
{
/// <inheritdoc/>
public List<string> ValidateFields(Dictionary<Type, HashSet<string>> prototypes)
public List<string> ValidateStaticFields(Dictionary<Type, HashSet<string>> prototypes)
{
var errors = new List<string>();
foreach (var type in _reflectionManager.FindAllTypes())
{
// TODO validate public static fields on abstract classes that have no implementations?
if (!type.IsAbstract)
ValidateType(type, errors, prototypes);
ValidateStaticFieldsInternal(type, errors, prototypes);
}
return errors;
}
/// <summary>
/// Validate all fields defined on this type and all base types.
/// </summary>
private void ValidateType(Type type, List<string> errors, Dictionary<Type, HashSet<string>> prototypes)
/// <inheritdoc/>
public List<string> ValidateStaticFields(Type type, Dictionary<Type, HashSet<string>> prototypes)
{
object? instance = null;
Type? baseType = type;
var errors = new List<string>();
ValidateStaticFieldsInternal(type, errors, prototypes);
return errors;
}
var flags = BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public |
BindingFlags.DeclaredOnly;
/// <summary>
/// Validate all static fields defined on this type and all base types.
/// </summary>
private void ValidateStaticFieldsInternal(Type type, List<string> errors, Dictionary<Type, HashSet<string>> prototypes)
{
var baseType = type;
var flags = BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly;
while (baseType != null)
{
foreach (var field in baseType.GetFields(flags))
{
ValidateField(field, type, ref instance, errors, prototypes);
DebugTools.Assert(field.IsStatic);
ValidateStaticField(field, type, errors, prototypes);
}
// We need to get the fields on the base type separately in order to get the private fields
@@ -49,92 +54,110 @@ public partial class PrototypeManager
}
}
private void ValidateField(
private void ValidateStaticField(
FieldInfo field,
Type type,
ref object? instance,
List<string> errors,
Dictionary<Type, HashSet<string>> prototypes)
{
DebugTools.Assert(field.IsStatic);
DebugTools.Assert(!field.HasCustomAttribute<DataFieldAttribute>(), "Datafields should not be static");
// Is this even a prototype id related field?
if (!TryGetFieldPrototype(field, out var proto, out var canBeNull, out var canBeEmpty))
if (!TryGetFieldPrototype(field, out var proto))
return;
if (!TryGetFieldValue(field, type, ref instance, errors, out var value))
return;
var id = value?.ToString();
if (id == null)
if (!prototypes.TryGetValue(proto, out var validIds))
{
if (!canBeNull)
errors.Add($"Prototype id field failed validation. Fields should not be null. Field: {field.Name} in {type.FullName}");
errors.Add($"Prototype id field failed validation. Unknown prototype kind {proto.Name}. Field: {field.Name} in {type.FullName}");
return;
}
if (string.IsNullOrWhiteSpace(id))
if (!TryGetIds(field, proto, out var ids))
{
if (!canBeEmpty)
errors.Add($"Prototype id field failed validation. Non-optional non-nullable data-fields must have a default value. Field: {field.Name} in {type.FullName}");
TryGetIds(field, proto, out _);
DebugTools.Assert($"Failed to get ids, despite resolving the field into a prototype kind?");
return;
}
if (!prototypes.TryGetValue(proto, out var ids))
foreach (var id in ids)
{
errors.Add($"Prototype id field failed validation. Unknown prototype kind. Field: {field.Name} in {type.FullName}");
return;
}
if (!ids.Contains(id))
{
errors.Add($"Prototype id field failed validation. Unknown prototype: {id}. Field: {field.Name} in {type.FullName}");
if (!validIds.Contains(id))
errors.Add($"Prototype id field failed validation. Unknown prototype: {id} of type {proto.Name}. Field: {field.Name} in {type.FullName}");
}
}
/// <summary>
/// Get the value of some field. If this is not a static field, this will create instance of the object in order to
/// validate default field values.
/// Extract prototype ids from a string, IEnumerable{string}, EntProtoId, IEnumerable{EntProtoId}, ProtoId{T}, or IEnumerable{ProtoId{T}} field.
/// </summary>
private bool TryGetFieldValue(FieldInfo field, Type type, ref object? instance, List<string> errors, out object? value)
private bool TryGetIds(FieldInfo field, Type proto, [NotNullWhen(true)] out string[]? ids)
{
value = null;
ids = null;
var value = field.GetValue(null);
if (value == null)
return false;
if (field.IsStatic || instance != null)
if (value is string str)
{
value = field.GetValue(instance);
ids = [str];
return true;
}
var constructor = type.GetConstructor(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance,
Type.EmptyTypes);
// TODO handle parameterless record constructors.
// Figure out how ISerializationManager does it, or just re-use that code somehow.
// In the meantime, record data fields need an explicit parameterless ctor.
if (constructor == null)
if (value is IEnumerable<string> strEnum)
{
errors.Add($"Prototype id field failed validation. Could not create instance to validate default value. Field: {field.Name} in {type.FullName}");
return false;
ids = strEnum.ToArray();
return true;
}
instance = constructor.Invoke(Array.Empty<object>());
value = field.GetValue(instance);
if (value is EntProtoId protoId)
{
ids = [protoId];
return true;
}
return true;
if (value is IEnumerable<EntProtoId> protoIdEnum)
{
ids = protoIdEnum.Select(x=> x.Id).ToArray();
return true;
}
if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
{
ids = [value.ToString()!];
return true;
}
foreach (var iface in field.FieldType.GetInterfaces())
{
if (!iface.IsGenericType)
continue;
if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>))
continue;
var enumType = iface.GetGenericArguments().Single();
if (!enumType.IsGenericType)
continue;
if (enumType.GetGenericTypeDefinition() != typeof(ProtoId<>))
continue;
ids = GetIdsMethod.MakeGenericMethod(proto).Invoke(null, [value]) as string[];
return ids != null;
}
return false;
}
private bool TryGetFieldPrototype(
FieldInfo field,
[NotNullWhen(true)] out Type? proto,
out bool canBeNull,
out bool canBeEmpty)
private static MethodInfo GetIdsMethod = typeof(PrototypeManager).GetMethod(nameof(GetIds), BindingFlags.NonPublic | BindingFlags.Static)!;
private static string[] GetIds<T>(IEnumerable<ProtoId<T>> enumerable) where T : class, IPrototype
{
proto = null;
canBeNull = false;
canBeEmpty = false;
return enumerable.Select(x => x.Id).ToArray();
}
private bool TryGetFieldPrototype(FieldInfo field, [NotNullWhen(true)] out Type? proto)
{
// Validate anything with the attribute
var attrib = field.GetCustomAttribute(typeof(ValidatePrototypeIdAttribute<>), false);
if (attrib != null)
{
@@ -142,46 +165,40 @@ public partial class PrototypeManager
return true;
}
if (!field.TryGetCustomAttribute(out DataFieldAttribute? dataField))
return false;
if (TryGetPrototypeFromType(field.FieldType, out proto))
return true;
var fieldType = field.FieldType;
canBeEmpty = dataField.Required;
DebugTools.Assert(!field.IsStatic);
// Resolve nullable structs
if (fieldType.IsGenericType && fieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
// Allow validating arrays or lists.
foreach (var iface in field.FieldType.GetInterfaces().Where(x => x.IsGenericType))
{
fieldType = fieldType.GetGenericArguments().Single();
canBeNull = true;
if (iface.GetGenericTypeDefinition() != typeof(IEnumerable<>))
continue;
var enumType = iface.GetGenericArguments().Single();
if (TryGetPrototypeFromType(enumType, out proto))
return true;
}
if (fieldType == typeof(EntProtoId))
proto = null;
return false;
}
private bool TryGetPrototypeFromType(Type type, [NotNullWhen(true)] out Type? proto)
{
if (type == typeof(EntProtoId))
{
proto = typeof(EntityPrototype);
return true;
}
if (fieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(ProtoId<>))
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ProtoId<>))
{
proto = field.FieldType.GetGenericArguments().Single();
proto = type.GetGenericArguments().Single();
DebugTools.Assert(proto != typeof(EntityPrototype), "Use EntProtoId instead of ProtoId<EntityPrototype>");
return true;
}
// As far as I know there is no way to check for the nullability of a string field, so we will assume that null
// values imply that the field itself is properly marked as nullable.
canBeNull = true;
if (dataField.CustomTypeSerializer == null)
return false;
if (!dataField.CustomTypeSerializer.IsGenericType)
return false;
if (dataField.CustomTypeSerializer.GetGenericTypeDefinition() != typeof(PrototypeIdSerializer<>))
return false;
proto = dataField.CustomTypeSerializer.GetGenericArguments().First();
return true;
proto = null;
return false;
}
}

View File

@@ -53,7 +53,7 @@ public partial class PrototypeManager
var mapping = node.ToDataNodeCast<MappingDataNode>();
var id = mapping.Get<ValueDataNode>("id").Value;
var data = new PrototypeValidationData(mapping, resourcePath.ToString());
var data = new PrototypeValidationData(id, mapping, resourcePath.ToString());
mapping.Remove("type");
if (prototypes.GetOrNew(type).TryAdd(id, data))
@@ -65,10 +65,14 @@ public partial class PrototypeManager
}
}
var ctx = new YamlValidationContext();
var errors = new List<ErrorNode>();
foreach (var (type, instances) in prototypes)
{
foreach (var data in instances.Values)
var defaultErrorOccurred = false;
foreach (var (id, data) in instances)
{
errors.Clear();
EnsurePushed(data, instances, type);
if (data.Mapping.TryGet("abstract", out ValueDataNode? abstractNode)
&& bool.Parse(abstractNode.Value))
@@ -76,9 +80,25 @@ public partial class PrototypeManager
continue;
}
var result = _serializationManager.ValidateNode(type, data.Mapping).GetErrors().ToHashSet();
if (result.Count > 0)
dict.GetOrNew(data.File).UnionWith(result);
// Validate yaml directly
errors.AddRange(_serializationManager.ValidateNode(type, data.Mapping).GetErrors());
if (errors.Count > 0)
dict.GetOrNew(data.File).UnionWith(errors);
// Create instance & re-serialize it, to validate the default values of data-fields. We still validate
// the yaml directly just in case reading & writing the fields somehow modifies their values.
try
{
var instance = _serializationManager.Read(type, data.Mapping, ctx);
var mapping = _serializationManager.WriteValue(type, instance, alwaysWrite: true, ctx);
errors.AddRange(_serializationManager.ValidateNode(type, mapping, ctx).GetErrors());
if (errors.Count > 0)
dict.GetOrNew(data.File).UnionWith(errors);
}
catch (Exception ex)
{
errors.Add(new ErrorNode(new ValueDataNode(), $"Caught Exception while validating {type} prototype {id}. Exception: {ex}"));
}
}
}
@@ -152,12 +172,17 @@ public partial class PrototypeManager
private sealed class PrototypeValidationData
{
public readonly string Id;
public MappingDataNode Mapping;
public readonly string File;
public bool Pushed;
public PrototypeValidationData(MappingDataNode mapping, string file)
public string[]? Parents;
public MappingDataNode[]? ParentMappings;
public PrototypeValidationData(string id, MappingDataNode mapping, string file)
{
Id = id;
File = file;
Mapping = mapping;
}
@@ -176,23 +201,22 @@ public partial class PrototypeManager
if (!data.Mapping.TryGet(ParentDataFieldAttribute.Name, out var parentNode))
return;
var parents = _serializationManager.Read<string[]>(parentNode, notNullableOverride: true);
var parentNodes = new MappingDataNode[parents.Length];
DebugTools.AssertNull(data.Parents);
DebugTools.AssertNull(data.ParentMappings);
data.Parents = _serializationManager.Read<string[]>(parentNode, notNullableOverride: true);
data.ParentMappings = new MappingDataNode[data.Parents.Length];
foreach (var parentId in parents)
var i = 0;
foreach (var parentId in data.Parents)
{
var parent = prototypes[parentId];
EnsurePushed(parent, prototypes, type);
for (var i = 0; i < parents.Length; i++)
{
parentNodes[i] = parent.Mapping;
}
data.ParentMappings[i++] = parent.Mapping;
}
data.Mapping = _serializationManager.PushCompositionWithGenericNode(
type,
parentNodes,
data.ParentMappings,
data.Mapping);
}
}

View File

@@ -1082,6 +1082,34 @@ namespace Robust.Shared.Prototypes
_prototypeDataCache[prototype.ID] = data;
return data;
}
public bool TryGetRandom<T>(IRobustRandom random, [NotNullWhen(true)] out IPrototype? prototype) where T : class, IPrototype
{
var count = Count<T>();
if (count == 0)
{
prototype = null;
return false;
}
var index = 0;
var picked = random.Next(count);
foreach (var proto in EnumeratePrototypes<T>())
{
if (index == picked)
{
prototype = proto;
return true;
}
index++;
}
throw new ArgumentOutOfRangeException($"Unable to pick valid prototype for {typeof(T)}?");
}
}
public sealed class InvalidPrototypeNameException : Exception

View File

@@ -8,7 +8,7 @@ namespace Robust.Shared.Prototypes;
/// Tile alias prototypes, unlike tile prototypes, are implemented here, as they're really just fed to TileDefinitionManager.
/// </summary>
[Prototype("tileAlias")]
public sealed class TileAliasPrototype : IPrototype
public sealed partial class TileAliasPrototype : IPrototype
{
/// <summary>
/// The target tile ID to alias to.

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