Compare commits

...

78 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
metalgearsloth
6764ed56b0 Version: 217.2.1 2024-03-31 17:02:37 +11:00
Leon Friedrich
2b55d39e51 Make various ValueList enumerators use spans (#5019)
* Make various ValueList enumerators use spans

* Remove reference to EntityEventBus.OrderedRegistration

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 17:01:03 +11:00
metalgearsloth
fdc1de2430 Fix LineEdit tests (#5021) 2024-03-31 16:58:15 +11:00
metalgearsloth
99c5b0ad08 Version: 217.2.0 2024-03-31 15:29:26 +11:00
faint
9e2ab2a917 Double-clicking in LineEdit (#4831)
* Double-clicking in LineEdit

epic

* Fix IGameTiming dependency

* remove iocmanager.resolves

* test fix

* Update Robust.Client/UserInterface/Controls/LineEdit.cs

Co-authored-by: ShadowCommander <shadowjjt@gmail.com>

* review

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Co-authored-by: ShadowCommander <shadowjjt@gmail.com>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-31 15:25:31 +11:00
metalgearsloth
36eb857b55 Fix TimeSpan addition (#5018)
Rider thinks nullable timespan += is fine but it no fine.
2024-03-31 15:02:18 +11:00
metalgearsloth
92d7f2723a Add ComponentRegistry helpers to EntityManager (#4934)
* Add ComponentRegistry helpers to EntityManager

* Metadata here too

* Remove object cast

* knock

* Plural
2024-03-31 14:54:01 +11:00
Leon Friedrich
91d3f67a94 Make IntersectRayWithPredicate ignore non-hard fixtures (#5017) 2024-03-31 14:48:37 +11:00
ElectroJr
b28b5ed09b Version: 217.1.0 2024-03-30 21:16:44 -04:00
Leon Friedrich
30eed7957f Add an EffectiveCurTime for physics subticks (#5014)
* Add an `EffectiveCurTime` for physics subticks

* Release notes
2024-03-31 12:15:30 +11:00
Fildrance
73c1449811 Add GetItems() extension for IRobustRandom (#4975)
* refactor: RobustRandom and RandomExtensions namespace change to file-scoped

* refactor: IRobustRandom xml-doc methods rearranged to be more structured.

* feat: GetItems methods added to RandomExtensions, tests for new methods added.

* fix: GetItems will not request count-1 from next random, as System.Random.Next have upper bound excluded.

* fix: enforced standard deviation on picking next items in RandomExtensions.GetItems + fixed hashet initial capacity +removed mandatory hashset allocation

* refactor: specified border values interaction in IRobustRandom xml-doc

* refactor: updated relese-notes

* refactor: changed release-notes PROPERLY

* fix: order by which unique random items are picked in RandomExtensions.GetItems were fixed to ACTUALLY follow normal distribution

* refractor: added comment for devious RandomExtensions.GetItems only-unique logic

* reduce code duplication

* Cleanup code a bit

Rename variables, and make it a bit more compact.
Also, IMO the description is unnecessary

* Remove obsolete extension

* Remove incorrect O(n) comments.

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
2024-03-30 10:42:39 +11:00
Pieter-Jan Briers
958b5dd06d Fix MapComponent.LightingEnabled funky FOV.
There's two separate bool checks, one wasn't turned off, meaning the FOV kept being rendered but not updated.
2024-03-29 16:54:27 +01:00
metalgearsloth
4002cbddb9 Version: 217.0.0 2024-03-29 17:04:51 +11:00
metalgearsloth
c38a14e78f WorldPos review fix (#5011)
Forgot I merged ghub and tried pushing and didn't check it failed.
2024-03-29 16:56:25 +11:00
metalgearsloth
5164d99996 Implement VV for AudioParams + dump obsolete params (#4994)
* Implement sound VV

No params yet because I'm lazy but paths and collections work.

* Fixes

* rn

* review

* pitch

* VV sound params + dump unneeded params

BusName never used and per-source Attenuation got dropped.

* weh?
2024-03-29 16:54:14 +11:00
metalgearsloth
c79217ab66 Rework SetWorldPosition (#4915)
* Remove ents from containers for worldpos updates

Avoids bugs from content not checking for this every time. Physics and anything else important manually uses localposition updates so shouldn't adversely impact performance that much.

* Fixes

* Fix grid pos

* Also fix the joint setter

* Add both

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

Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>

---------

Co-authored-by: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com>
2024-03-29 16:49:26 +11:00
deltanedas
9ef7f7cb37 add AddUi to shared ui system (#4984)
Co-authored-by: deltanedas <@deltanedas:kde.org>
2024-03-27 14:36:11 +11:00
ShadowCommander
02ac314b1a Fix first measure of ScrollContainer scroll bars (#5005)
The first measure would include scroll bar size even if the available size fit all the contents. This would make the returned size larger than it should be.
2024-03-27 14:33:59 +11:00
metalgearsloth
8607ba1f16 Version: 216.0.0 2024-03-27 14:14:54 +11:00
Pieter-Jan Briers
2a9de462d5 Preserve tile maps when saving maps & related changes (#5003)
* Un-hardcode behavior to make a component not saved to map file.

MapSaveId is a special component that can't be saved to map files due to a hardcoded type check. This behavior can now be applied to any component with [UnsavedComponent].

Moved "component registration" attributes into a single file because they don't deserve their own (poorly organized) .cs files.

* Add ITileDefinitionManager.TryGetDefinition

Try-pattern version of the existing indexers.

* Preserve tile maps when saving maps

This changes the map saver and loader code so that the "tilemap" can be preserved between map modifications as much as possible.

The tile map from the loaded map gets stored onto MapSaveTileMapComponent components on all loaded grids. This tile map is then used when saving, meaning that changes to the engine's internal tile IDs do not cause diffs.

Fixes #5000

* Changelog

* Fix tests
2024-03-27 14:14:19 +11:00
metalgearsloth
c59ef5ab2d Fix buffered audio disposals (#5009) 2024-03-27 14:07:54 +11:00
Leon Friedrich
eba1d866fb Change default PVS LoD cvars (#5008) 2024-03-27 13:58:52 +11:00
Pieter-Jan Briers
ab2bff8f40 Make sawmill levels work differently. (#5006)
Before, sawmill log levels were very clunky to use. We set the root sawmill to something like debug or info, but then it becomes impossible to specify specific sawmills to be verbose. This is because sawmills checked the log level at every part of the hierarchy when navigating "up" to find log handlers to write into.

This changes the behavior so that the filter log level is the FIRST set log level encountered on the hierarchy. This means the log level is compared only once, and a sawmill down the hierarchy that has a level like Verbose set can cause verbose messages to come up even if the root is still set to Debug. This matches the logging behavior in libraries such as ASP.NET Core.
2024-03-27 13:27:54 +11:00
205 changed files with 4147 additions and 1807 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

@@ -1,4 +1,4 @@
# Release notes for RobustToolbox.
# Release notes for RobustToolbox.
<!--
NOTE: automatically updated sometimes by version.py.
@@ -54,6 +54,207 @@ 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
* Fix LineEdit tests on engine.
### Internal
* Make various ValueList enumerators access the span directly for performance.
## 217.2.0
### New features
* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes.
* Add double-clicking to LineEdit.
### Bugfixes
* Properly ignore non-hard fixtures for IntersectRayWithPredicate.
* Fix nullable TimeSpan addition on some platforms.
## 217.1.0
### New features
* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections.
* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step.
### Bugfixes
* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state.
### Internal
* `Shuffle<T>(Span<T>, System.Random)` has been removed, just use the builtin method.
## 217.0.0
### Breaking changes
* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this).
### New features
* Implement VV for AudioParams on SoundSpecifiers.
* Add AddUi to the shared UI system.
### Bugfixes
* Fix the first measure of ScrollContainer bars.
## 216.0.0
### Breaking changes
* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25.
### New features
* You can now specify a component to not be saved to map files with `[UnsavedComponent]`.
* Added `ITileDefinitionManager.TryGetDefinition`.
* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed.
### Bugfixes
* Fix buffered audio sources not being disposed.
## 215.3.1
### Bugfixes

View File

@@ -10,3 +10,18 @@ view-variable-instance-entity-client-components-search-bar-placeholder = Search
view-variable-instance-entity-server-components-search-bar-placeholder = Search
view-variable-instance-entity-add-window-server-components = Add Component [S]
view-variable-instance-entity-add-window-client-components = Add Component [C]
## SoundSpecifier
vv-sound-none = None
vv-sound-path = Path
vv-sound-collection = Collection
vv-sound-volume = volume
vv-sound-pitch = Pitch
vv-sound-max-distance = Max Distance
vv-sound-rolloff-factor = Rolloff Factor
vv-sound-reference-distance = Reference Distance
vv-sound-loop = Loop
vv-sound-play-offset = Play Offset (s)
vv-sound-variation = Pitch variation

View File

@@ -1,3 +0,0 @@
vv-sound-none = None
vv-sound-path = Path
vv-sound-collection = Collection

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

@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.Collections;
namespace Robust.Benchmarks.Collections;
[Virtual]
public class ValueListEnumerationBenchmarks
{
[Params(4, 16, 64)]
public int N { get; set; }
private sealed class Data(int i)
{
public readonly int I = i;
}
private ValueList<Data> _valueList;
private Data[] _array = default!;
[GlobalSetup]
public void Setup()
{
var list = new List<Data>(N);
for (var i = 0; i < N; i++)
{
list.Add(new(i));
}
_array = list.ToArray();
_valueList = new(list.ToArray());
}
[Benchmark]
public int ValueList()
{
var total = 0;
foreach (var ev in _valueList)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int ValueListSpan()
{
var total = 0;
foreach (var ev in _valueList.Span)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int Array()
{
var total = 0;
foreach (var ev in _array)
{
total += ev.I;
}
return total;
}
[Benchmark]
public int Span()
{
var total = 0;
foreach (var ev in _array.AsSpan())
{
total += ev.I;
}
return total;
}
}

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

@@ -612,6 +612,8 @@ namespace Robust.Client
{
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
}
_audio.FlushALDisposeQueues();
}
internal static void SetupLogging(

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

@@ -107,7 +107,7 @@ namespace Robust.Client.GameObjects
toDelete.Add(id);
}
foreach (var dead in toDelete)
foreach (var dead in toDelete.Span)
{
component.Containers.Remove(dead);
}
@@ -142,7 +142,7 @@ namespace Robust.Client.GameObjects
toRemove.Add(entity);
}
foreach (var entity in toRemove)
foreach (var entity in toRemove.Span)
{
Remove(
(entity, TransformQuery.GetComponent(entity), MetaQuery.GetComponent(entity)),
@@ -162,7 +162,7 @@ namespace Robust.Client.GameObjects
removedExpected.Add(netEntity);
}
foreach (var entityUid in removedExpected)
foreach (var entityUid in removedExpected.Span)
{
RemoveExpectedEntity(entityUid, out _);
}

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

@@ -11,6 +11,7 @@ using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Utility;
@@ -250,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);
@@ -514,7 +513,9 @@ namespace Robust.Client.Graphics.Clyde
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
{
ApplyFovToBuffer(viewport, eye);
var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId);
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
ApplyFovToBuffer(viewport, eye);
}
}

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

@@ -57,7 +57,7 @@ namespace Robust.Client.UserInterface
toRemove.Add(key);
}
foreach (var key in toRemove)
foreach (var key in toRemove.Span)
{
_playingAnimations.Remove(key);
AnimationCompleted?.Invoke(key);

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

@@ -4,6 +4,8 @@ using System.Numerics;
using System.Text;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Input;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -20,6 +22,8 @@ namespace Robust.Client.UserInterface.Controls
public class LineEdit : Control
{
[Dependency] private readonly IClyde _clyde = default!;
[Dependency] private readonly IConfigurationManager _cfgManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
private const float MouseScrollDelay = 0.001f;
@@ -46,6 +50,9 @@ namespace Robust.Client.UserInterface.Controls
private bool _mouseSelectingText;
private float _lastMousePosition;
private TimeSpan? _lastClickTime;
private Vector2? _lastClickPosition;
private bool IsPlaceHolderVisible => string.IsNullOrEmpty(_text) && _placeHolder != null;
public event Action<LineEditEventArgs>? OnTextChanged;
@@ -685,8 +692,26 @@ namespace Robust.Client.UserInterface.Controls
args.Handle();
}
}
// Double-clicking. Clicks delay should be <= 250ms and the distance < 10 pixels.
else if (args.Function == EngineKeyFunctions.UIClick && _lastClickPosition != null && _lastClickTime != null
&& _timing.RealTime - _lastClickTime <= TimeSpan.FromMilliseconds(_cfgManager.GetCVar(CVars.DoubleClickDelay))
&& (_lastClickPosition.Value - args.PointerLocation.Position).IsShorterThan(_cfgManager.GetCVar(CVars.DoubleClickRange)))
{
_lastClickTime = _timing.RealTime;
_lastClickPosition = args.PointerLocation.Position;
_lastMousePosition = args.RelativePosition.X;
_selectionStart = TextEditShared.PrevWordPosition(_text, GetIndexAtPos(args.RelativePosition.X));
_cursorPosition = TextEditShared.EndWordPosition(_text, GetIndexAtPos(args.RelativePosition.X));
args.Handle();
}
else
{
_lastClickTime = _timing.RealTime;
_lastClickPosition = args.PointerLocation.Position;
_mouseSelectingText = true;
_lastMousePosition = args.RelativePosition.X;

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

@@ -123,10 +123,10 @@ namespace Robust.Client.UserInterface.Controls
if (!ReturnMeasure)
return Vector2.Zero;
if (_vScrollEnabled)
if (_vScrollEnabled && size.Y >= availableSize.Y)
size.X += _vScrollBar.DesiredSize.X;
if (_hScrollEnabled)
if (_hScrollEnabled && size.X >= availableSize.X)
size.Y += _hScrollBar.DesiredSize.Y;
return size;

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

@@ -1,3 +1,4 @@
using System.Globalization;
using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -16,6 +17,10 @@ public sealed class VVPropEditorSoundSpecifier : VVPropEditor
private readonly IPrototypeManager _protoManager;
private readonly IResourceManager _resManager;
// Need to cache to some level just to make sure each edit doesn't reset the specifier to the default.
private SoundSpecifier? _specifier;
public VVPropEditorSoundSpecifier(IPrototypeManager protoManager, IResourceManager resManager)
{
_protoManager = protoManager;
@@ -39,7 +44,7 @@ public sealed class VVPropEditorSoundSpecifier : VVPropEditor
Editable = !ReadOnly,
};
var controls = new BoxContainer()
var pathControls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
@@ -57,10 +62,15 @@ public sealed class VVPropEditorSoundSpecifier : VVPropEditor
case SoundCollectionSpecifier collection:
typeButton.SelectId(1);
editBox.Text = collection.Collection ?? string.Empty;
_specifier = collection;
break;
case SoundPathSpecifier path:
typeButton.SelectId(2);
editBox.Text = path.Path.ToString();
_specifier = path;
break;
default:
_specifier = null;
break;
}
}
@@ -90,7 +100,11 @@ public sealed class VVPropEditorSoundSpecifier : VVPropEditor
if (!_protoManager.HasIndex<SoundCollectionPrototype>(args.Text))
return;
ValueChanged(new SoundCollectionSpecifier(args.Text));
_specifier = new SoundCollectionSpecifier(args.Text)
{
Params = _specifier?.Params ?? AudioParams.Default,
};
ValueChanged(_specifier);
break;
case 2:
var path = new ResPath(args.Text);
@@ -98,13 +112,282 @@ public sealed class VVPropEditorSoundSpecifier : VVPropEditor
if (!_resManager.ContentFileExists(path))
return;
ValueChanged(new SoundPathSpecifier(args.Text));
_specifier = new SoundPathSpecifier(args.Text)
{
Params = _specifier?.Params ?? AudioParams.Default,
};
ValueChanged(_specifier);
break;
default:
return;
}
};
// Audio params
/* Volume */
var volumeEdit = new LineEdit()
{
Text = _specifier?.Params.Volume.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
volumeEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithVolume(floatValue);
ValueChanged(_specifier);
};
var volumeContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-volume"),
},
volumeEdit,
}
};
/* Pitch */
var pitchEdit = new LineEdit()
{
Text = _specifier?.Params.Pitch.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
pitchEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithPitchScale(floatValue);
ValueChanged(_specifier);
};
var pitchContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-pitch"),
},
pitchEdit,
}
};
/* MaxDistance */
var maxDistanceEdit = new LineEdit()
{
Text = _specifier?.Params.MaxDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
maxDistanceEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithMaxDistance(floatValue);
ValueChanged(_specifier);
};
var maxDistanceContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-max-distance"),
},
maxDistanceEdit,
}
};
/* RolloffFactor */
var rolloffFactorEdit = new LineEdit()
{
Text = _specifier?.Params.RolloffFactor.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
rolloffFactorEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithRolloffFactor(floatValue);
ValueChanged(_specifier);
};
var rolloffFactorContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-rolloff-factor"),
},
rolloffFactorEdit,
}
};
/* ReferenceDistance */
var referenceDistanceEdit = new LineEdit()
{
Text = _specifier?.Params.ReferenceDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
referenceDistanceEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithReferenceDistance(floatValue);
ValueChanged(_specifier);
};
var referenceDistanceContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-reference-distance"),
},
referenceDistanceEdit,
}
};
/* Loop */
var loopButton = new Button()
{
Text = Loc.GetString("vv-sound-loop"),
Pressed = _specifier?.Params.Loop ?? false,
ToggleMode = true,
Disabled = ReadOnly || _specifier == null,
};
loopButton.OnPressed += args =>
{
if (_specifier == null)
return;
_specifier.Params = _specifier.Params.WithLoop(args.Button.Pressed);
ValueChanged(_specifier);
};
/* PlayOffsetSeconds */
var playOffsetEdit = new LineEdit()
{
Text = _specifier?.Params.PlayOffsetSeconds.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
playOffsetEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithPlayOffset(floatValue);
ValueChanged(_specifier);
};
var playOffsetContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-play-offset"),
},
playOffsetEdit,
}
};
/* Variation */
var variationEdit = new LineEdit()
{
Text = _specifier?.Params.Variation.ToString() ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
variationEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithVariation(floatValue);
ValueChanged(_specifier);
};
var variationContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-variation"),
},
variationEdit,
}
};
var audioParamsControls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
volumeContainer,
pitchContainer,
maxDistanceContainer,
rolloffFactorContainer,
referenceDistanceContainer,
loopButton,
playOffsetContainer,
variationContainer,
}
};
var controls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
pathControls,
audioParamsControls,
}
};
return controls;
}
}

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();
@@ -280,6 +279,9 @@ public sealed class MapLoaderSystem : EntitySystem
// Load the prototype data onto entities, e.g. transform parents, etc.
LoadEntities(data);
// Assign MapSaveTileMapComponent to all read grids.
SaveGridTileMap(data);
// Build the scene graph / transform hierarchy to know the order to startup entities.
// This also allows us to swap out the root node up front if necessary.
BuildEntityHierarchy(data);
@@ -576,6 +578,19 @@ public sealed class MapLoaderSystem : EntitySystem
meta.LastComponentRemoved = _timing.CurTick;
}
private void SaveGridTileMap(MapData mapData)
{
DebugTools.Assert(_context.TileMap != null);
foreach (var entity in mapData.EntitiesToDeserialize.Keys)
{
if (HasComp<MapGridComponent>(entity))
{
EnsureComp<MapSaveTileMapComponent>(entity).TileMap = _context.TileMap;
}
}
}
private void BuildEntityHierarchy(MapData mapData)
{
_stopwatch.Restart();
@@ -642,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)
{
@@ -659,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);
}
}
@@ -687,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.
@@ -705,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)
{
@@ -727,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}");
}
@@ -880,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);
}
@@ -948,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;
@@ -981,28 +990,74 @@ public sealed class MapLoaderSystem : EntitySystem
var gridQuery = GetEntityQuery<MapGridComponent>();
var tileDefs = new HashSet<int>();
Dictionary<int, string>? origTileMap = null;
foreach (var ent in entities)
{
if (!gridQuery.TryGetComponent(ent, out var grid))
continue;
var tileEnumerator = grid.GetAllTilesEnumerator(false);
var tileEnumerator = _mapSystem.GetAllTilesEnumerator(ent, grid, ignoreEmpty: false);
while (tileEnumerator.MoveNext(out var tileRef))
{
tileDefs.Add(tileRef.Value.Tile.TypeId);
}
if (TryComp(ent, out MapSaveTileMapComponent? saveTileMap))
origTileMap ??= saveTileMap.TileMap;
}
Dictionary<int, int> tileIdMap;
if (origTileMap != null)
{
tileIdMap = new Dictionary<int, int>();
// We are re-saving a map, so we have an original tile map we can preserve.
foreach (var (origId, prototypeId) in origTileMap)
{
// Skip removed tile definitions.
if (!_tileDefManager.TryGetDefinition(prototypeId, out var definition))
continue;
tileIdMap.Add(definition.TileId, origId);
}
// Assign new IDs for all new tile types.
var nextId = 0;
foreach (var tileId in tileDefs)
{
if (tileIdMap.ContainsKey(tileId))
continue;
// New tile, assign new ID that isn't taken by original tile map.
while (origTileMap.ContainsKey(nextId))
{
nextId += 1;
}
tileIdMap.Add(tileId, nextId);
nextId += 1;
}
}
else
{
// Make no-op tile ID map.
tileIdMap = tileDefs.ToDictionary(x => x, x => x);
}
DebugTools.Assert(
tileIdMap.Count == tileIdMap.Values.Distinct().Count(),
"Tile ID map has double mapped values??");
_context.TileWriteMap = tileIdMap;
var tileMap = new MappingDataNode();
rootNode.Add("tilemap", tileMap);
var ordered = new List<int>(tileDefs);
ordered.Sort();
foreach (var tyleId in ordered)
foreach (var (nativeId, mapId) in tileIdMap.OrderBy(x => x.Key))
{
var tileDef = _tileDefManager[tyleId];
tileMap.Add(tyleId.ToString(CultureInfo.InvariantCulture), tileDef.ID);
tileMap.Add(
mapId.ToString(CultureInfo.InvariantCulture),
_tileDefManager[nativeId].ID);
}
}
@@ -1036,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.
@@ -1061,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);
@@ -1176,11 +1231,12 @@ public sealed class MapLoaderSystem : EntitySystem
foreach (var component in EntityManager.GetComponents(entityUid))
{
if (component is MapSaveIdComponent)
var compType = component.GetType();
var registration = _factory.GetRegistration(compType);
if (registration.Unsaved)
continue;
var compType = component.GetType();
var compName = _factory.GetComponentName(compType);
var compName = registration.Name;
_context.CurrentComponent = compName;
MappingDataNode? compMapping;
MappingDataNode? protMapping = null;

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

@@ -9,7 +9,7 @@ namespace Robust.Server.GameObjects
/// This component stores the previous map UID of entities from map load.
/// This can then be used to re-serialize the entity with the same UID for the merge driver to recognize.
/// </remarks>
[RegisterComponent]
[RegisterComponent, UnsavedComponent]
public sealed partial class MapSaveIdComponent : Component
{
public int Uid { get; set; }

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
namespace Robust.Server.GameObjects;
/// <summary>
/// Used by <see cref="MapLoaderSystem"/> to track the original tile map from when a map was loaded.
/// </summary>
/// <remarks>
/// <para>
/// This component is used to reduce differences on map saving, by making it so that a tile map can be re-used between map saves even if internal engine IDs change.
/// </para>
/// <para>
/// This component is created on every grid entity read during map load.
/// This means loading a multi-grid map will create multiple of these components.
/// When re-saving the map, the map loader will arbitrarily choose which available <see cref="MapSaveTileMapComponent"/>
/// to use.
/// </para>
/// </remarks>
[RegisterComponent, UnsavedComponent]
internal sealed partial class MapSaveTileMapComponent : Component
{
public Dictionary<int, string> TileMap = [];
}

View File

@@ -48,7 +48,7 @@ internal sealed partial class PvsSystem
// We add chunk-size here so that its consistent with the normal PVS range setting.
// I.e., distance here is the Chebyshev distance to the centre of each chunk, but the normal pvs range only
// required that the chunk be touching the box, not the centre.
var count = distance < (_lowLodDistance + ChunkSize) / 2
var count = distance <= (_viewSize + ChunkSize) / 2
? chunk.Contents.Count
: chunk.LodCounts[0];

View File

@@ -62,12 +62,14 @@ internal sealed partial class PvsSystem : EntitySystem
public bool CullingEnabled { get; private set; }
/// <summary>
/// Size of the side of the view bounds square.
/// Size of the side of the view bounds square. Related to <see cref="CVars.NetMaxUpdateRange"/>
/// </summary>
private float _viewSize;
// see CVars.NetLowLodDistance
private float _lowLodDistance;
/// <summary>
/// Size of the side of the priority view bounds square. Related to <see cref="CVars.NetPvsPriorityRange"/>
/// </summary>
private float _priorityViewSize;
/// <summary>
/// Per-tick ack data to avoid re-allocating.
@@ -139,7 +141,7 @@ internal sealed partial class PvsSystem : EntitySystem
Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true);
Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
Subs.CVar(_configManager, CVars.NetLowLodRange, OnLodChanged, true);
Subs.CVar(_configManager, CVars.NetPvsPriorityRange, OnPriorityRangeChanged, true);
Subs.CVar(_configManager, CVars.NetForceAckThreshold, OnForceAckChanged, true);
Subs.CVar(_configManager, CVars.NetPvsAsync, OnAsyncChanged, true);
Subs.CVar(_configManager, CVars.NetPvsCompressLevel, ResetParallelism, true);
@@ -276,12 +278,13 @@ internal sealed partial class PvsSystem : EntitySystem
private void OnViewsizeChanged(float value)
{
_viewSize = value;
_viewSize = Math.Max(ChunkSize, value);
OnPriorityRangeChanged(_configManager.GetCVar(CVars.NetPvsPriorityRange));
}
private void OnLodChanged(float value)
private void OnPriorityRangeChanged(float value)
{
_lowLodDistance = Math.Clamp(value, ChunkSize, 100f);
_priorityViewSize = Math.Max(_viewSize, value);
}
private void OnForceAckChanged(int value)
@@ -387,7 +390,7 @@ internal sealed partial class PvsSystem : EntitySystem
private (Vector2 worldPos, float range, EntityUid? map) CalcViewBounds(Entity<TransformComponent, EyeComponent?> eye)
{
var size = Math.Max(eye.Comp2?.PvsSize ?? _viewSize, 1);
var size = Math.Max(eye.Comp2?.PvsSize ?? _priorityViewSize, 1);
return (_transform.GetWorldPosition(eye.Comp1), size / 2f, eye.Comp1.MapUid);
}

View File

@@ -104,12 +104,16 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
root.Add("version", new ValueDataNode("6"));
gridNode.Value = SerializeTiles(value);
Dictionary<int, int>? tileWriteMap = null;
if (context is MapSerializationContext mapContext)
tileWriteMap = mapContext.TileWriteMap;
gridNode.Value = SerializeTiles(value, tileWriteMap);
return root;
}
private static string SerializeTiles(MapChunk chunk)
private static string SerializeTiles(MapChunk chunk, Dictionary<int, int>? tileWriteMap)
{
// number of bytes written per tile, because sizeof(Tile) is useless.
const int structSize = 6;
@@ -125,7 +129,11 @@ internal sealed class MapChunkSerializer : ITypeSerializer<MapChunk, MappingData
for (ushort x = 0; x < chunk.ChunkSize; x++)
{
var tile = chunk.GetTile(x, y);
writer.Write(tile.TypeId);
var typeId = tile.TypeId;
if (tileWriteMap != null)
typeId = tileWriteMap[typeId];
writer.Write(typeId);
writer.Write((byte)tile.Flags);
writer.Write(tile.Variant);
}

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

@@ -29,12 +29,6 @@ namespace Robust.Shared.Audio
[DataDefinition]
public partial struct AudioParams
{
/// <summary>
/// The DistanceModel to use for this specific source.
/// </summary>
[DataField]
public Attenuation Attenuation { get; set; } = Attenuation.LinearDistanceClamped;
/// <summary>
/// Base volume to play the audio at, in dB.
/// </summary>
@@ -45,13 +39,13 @@ namespace Robust.Shared.Audio
/// Scale for the audio pitch.
/// </summary>
[DataField]
public float Pitch { get; set; } = Default.Pitch;
public float Pitch
{
get => _pitch;
set => _pitch = MathF.Max(0f, value);
}
/// <summary>
/// Audio bus to play on.
/// </summary>
[DataField]
public string BusName { get; set; } = Default.BusName;
private float _pitch = Default.Pitch;
/// <summary>
/// Only applies to positional audio.
@@ -89,7 +83,7 @@ namespace Robust.Shared.Audio
/// <summary>
/// The "default" audio configuration.
/// </summary>
public static readonly AudioParams Default = new(0, 1, "Master", SharedAudioSystem.DefaultSoundRange, 1, 1, false, 0f);
public static readonly AudioParams Default = new(0, 1, SharedAudioSystem.DefaultSoundRange, 1, 1, false, 0f);
// explicit parameterless constructor required so that default values get set properly.
public AudioParams() { }
@@ -97,21 +91,19 @@ namespace Robust.Shared.Audio
public AudioParams(
float volume,
float pitch,
string busName,
float maxDistance,
float refDistance,
bool loop,
float playOffsetSeconds,
float? variation = null)
: this(volume, pitch, busName, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation)
: this(volume, pitch, maxDistance, 1, refDistance, loop, playOffsetSeconds, variation)
{
}
public AudioParams(float volume, float pitch, string busName, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this()
public AudioParams(float volume, float pitch, float maxDistance,float rolloffFactor, float refDistance, bool loop, float playOffsetSeconds, float? variation = null) : this()
{
Volume = volume;
Pitch = pitch;
BusName = busName;
MaxDistance = maxDistance;
RolloffFactor = rolloffFactor;
ReferenceDistance = refDistance;
@@ -167,18 +159,6 @@ namespace Robust.Shared.Audio
return me;
}
/// <summary>
/// Returns a copy of this instance with a new bus name set, for easy chaining.
/// </summary>
/// <param name="bus">The new bus name.</param>
[Pure]
public readonly AudioParams WithBusName(string bus)
{
var me = this;
me.BusName = bus;
return me;
}
/// <summary>
/// Returns a copy of this instance with a new max distance set, for easy chaining.
/// </summary>
@@ -227,18 +207,6 @@ namespace Robust.Shared.Audio
return me;
}
/// <summary>
/// Returns a copy of this instance with attenuation set, for easy chaining.
/// </summary>
/// <param name="attenuation">The new attenuation.</param>
[Pure]
public readonly AudioParams WithAttenuation(Attenuation attenuation)
{
var me = this;
me.Attenuation = attenuation;
return me;
}
[Pure]
public readonly AudioParams WithPlayOffset(float offset)
{

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

@@ -225,18 +225,24 @@ namespace Robust.Shared
CVarDef.Create("net.pvs_async", true, CVar.ARCHIVE | CVar.SERVERONLY);
/// <summary>
/// View size to take for PVS calculations,
/// as the size of the sides of a square centered on the view points of clients.
/// View size to take for PVS calculations, as the size of the sides of a square centered on the view points of
/// clients. See also <see cref="NetPvsPriorityRange"/>.
/// </summary>
public static readonly CVarDef<float> NetMaxUpdateRange =
CVarDef.Create("net.pvs_range", 25f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Chunks whose centre is further than this distance away from a player's eye will contain fewer entities.
/// This has no effect if it is smaller than <see cref="NetMaxUpdateRange"/>
/// A variant of <see cref="NetMaxUpdateRange"/> that is used to limit the view-distance of entities with the
/// <see cref="MetaDataFlags.PvsPriority"/> flag set. This can be used to extend the range at which certain
/// entities become visible.
/// </summary>
public static readonly CVarDef<float> NetLowLodRange =
CVarDef.Create("net.low_lod_distance", 100f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <remarks>
/// This is useful for entities like lights and occluders to try and prevent noticeable pop-in as players
/// move around. Note that this has no effect if it is less than <see cref="NetMaxUpdateRange"/>, and that this
/// only works for entities that are directly parented to a grid or map.
/// </remarks>
public static readonly CVarDef<float> NetPvsPriorityRange =
CVarDef.Create("net.pvs_priority_range", 32.5f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
/// <summary>
/// Maximum allowed delay between the current tick and a client's last acknowledged tick before we send the
@@ -845,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.
@@ -880,6 +886,22 @@ namespace Robust.Shared
public static readonly CVarDef<string> RenderFOVColor =
CVarDef.Create("render.fov_color", Color.Black.ToHex(), CVar.ARCHIVE | CVar.CLIENTONLY);
/*
* CONTROLS
*/
/// <summary>
/// Milliseconds to wait to consider double-click delays.
/// </summary>
public static readonly CVarDef<int> DoubleClickDelay =
CVarDef.Create("controls.double_click_delay", 250, CVar.ARCHIVE | CVar.CLIENTONLY);
/// <summary>
/// Range in pixels for double-clicks
/// </summary>
public static readonly CVarDef<int> DoubleClickRange =
CVarDef.Create("controls.double_click_range", 10, CVar.ARCHIVE | CVar.CLIENTONLY);
/*
* DISPLAY
*/
@@ -1423,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

@@ -0,0 +1,28 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.GameObjects;
/// <summary>
/// Marks a component as being automatically registered by <see cref="IComponentFactory.DoAutoRegistrations" />
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(IComponent))]
[MeansImplicitUse]
public sealed class RegisterComponentAttribute : Attribute;
/// <summary>
/// Defines Name that this component is represented with in prototypes.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class ComponentProtoNameAttribute(string prototypeName) : Attribute
{
public string PrototypeName { get; } = prototypeName;
}
/// <summary>
/// Marks a component as not being saved when saving maps/grids.
/// </summary>
/// <seealso cref="ComponentRegistration.Unsaved"/>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public sealed class UnsavedComponentAttribute : Attribute;

View File

@@ -121,9 +121,11 @@ namespace Robust.Shared.GameObjects
if (!overwrite && lowerCaseNames.TryGetValue(lowerCaseName, out var prevName))
throw new InvalidOperationException($"{lowerCaseName} is already registered, previous: {prevName}");
var unsaved = type.HasCustomAttribute<UnsavedComponentAttribute>();
var idx = CompIdx.Index(type);
var registration = new ComponentRegistration(name, type, idx);
var registration = new ComponentRegistration(name, type, idx, unsaved);
idxToType[idx] = type;
names[name] = registration;

View File

@@ -1,18 +0,0 @@
using System;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Defines Name that this component is represented with in prototypes.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public sealed class ComponentProtoNameAttribute : Attribute
{
public string PrototypeName { get; }
public ComponentProtoNameAttribute(string prototypeName)
{
PrototypeName = prototypeName;
}
}
}

View File

@@ -21,6 +21,12 @@ public sealed class ComponentRegistration
public CompIdx Idx { get; }
/// <summary>
/// If this is true, the component will not be saved when saving a map/grid.
/// </summary>
/// <seealso cref="UnsavedComponentAttribute"/>
public bool Unsaved { get; }
/// <summary>
/// ID used to reference the component type across the network.
/// If null, no network synchronization will be available for this component.
@@ -35,11 +41,12 @@ public sealed class ComponentRegistration
// Internal for sandboxing.
// Avoid content passing an instance of this to ComponentFactory to get any type they want instantiated.
internal ComponentRegistration(string name, Type type, CompIdx idx)
internal ComponentRegistration(string name, Type type, CompIdx idx, bool unsaved = false)
{
Name = name;
Type = type;
Idx = idx;
Unsaved = unsaved;
}
public override string ToString()

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

@@ -329,7 +329,7 @@ namespace Robust.Shared.GameObjects
ref Unit unitRef,
EventData subs)
{
foreach (var handler in subs.BroadcastRegistrations)
foreach (var handler in subs.BroadcastRegistrations.Span)
{
if ((handler.Mask & source) != 0)
handler.Handler(ref unitRef);

View File

@@ -13,7 +13,7 @@ namespace Robust.Shared.GameObjects
EventData sub,
ref ValueList<OrderedEventDispatch> found)
{
foreach (var handler in sub.BroadcastRegistrations)
foreach (var handler in sub.BroadcastRegistrations.Span)
{
if ((handler.Mask & source) != 0)
found.Add(new OrderedEventDispatch(handler.Handler, handler.Order));
@@ -44,7 +44,7 @@ namespace Robust.Shared.GameObjects
{
found.Sort(OrderedEventDispatchComparer.Instance);
foreach (var (handler, _) in found)
foreach (var (handler, _) in found.Span)
{
handler(ref eventArgs);
}

View File

@@ -11,6 +11,7 @@ using Robust.Shared.GameStates;
using Robust.Shared.Log;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
#if EXCEPTION_TOLERANCE
@@ -176,6 +177,60 @@ namespace Robust.Shared.GameObjects
}
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AddComponents(EntityUid target, EntityPrototype prototype, bool removeExisting = true)
{
AddComponents(target, prototype.Components, removeExisting);
}
/// <inheritdoc />
public void AddComponents(EntityUid target, ComponentRegistry registry, bool removeExisting = true)
{
if (registry.Count == 0)
return;
var metadata = MetaQuery.GetComponent(target);
foreach (var (name, entry) in registry)
{
var reg = _componentFactory.GetRegistration(name);
if (HasComponent(target, reg.Type))
{
if (!removeExisting)
continue;
RemoveComponent(target, reg.Type, metadata);
}
var comp = _componentFactory.GetComponent(reg);
_serManager.CopyTo(entry.Component, ref comp, notNullableOverride: true);
AddComponent(target, comp, metadata: metadata);
}
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveComponents(EntityUid target, EntityPrototype prototype)
{
RemoveComponents(target, prototype.Components);
}
/// <inheritdoc />
public void RemoveComponents(EntityUid target, ComponentRegistry registry)
{
if (registry.Count == 0)
return;
var metadata = MetaQuery.GetComponent(target);
foreach (var entry in registry.Values)
{
RemoveComponent(target, entry.Component.GetType(), metadata);
}
}
public IComponent AddComponent(EntityUid uid, ushort netId, MetaDataComponent? meta = null)
{
var newComponent = _componentFactory.GetComponent(netId);
@@ -958,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;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
namespace Robust.Shared.GameObjects
@@ -38,6 +39,26 @@ namespace Robust.Shared.GameObjects
/// </summary>
int Count(Type component);
/// <summary>
/// Adds the specified components from the <see cref="EntityPrototype"/>
/// </summary>
void AddComponents(EntityUid target, EntityPrototype prototype, bool removeExisting = true);
/// <summary>
/// Adds the specified registry components to the target entity.
/// </summary>
void AddComponents(EntityUid target, ComponentRegistry registry, bool removeExisting = true);
/// <summary>
/// Removes the specified entity prototype components from the target entity.
/// </summary>
void RemoveComponents(EntityUid target, EntityPrototype prototype);
/// <summary>
/// Removes the specified registry components from the target entity.
/// </summary>
void RemoveComponents(EntityUid target, ComponentRegistry registry);
/// <summary>
/// Adds a Component type to an entity. If the entity is already Initialized, the component will
/// automatically be Initialized and Started.

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

@@ -1,16 +0,0 @@
using System;
using JetBrains.Annotations;
namespace Robust.Shared.GameObjects
{
/// <summary>
/// Marks a component as being automatically registered by <see cref="IComponentFactory.DoAutoRegistrations" />
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[BaseTypeRequired(typeof(IComponent))]
[MeansImplicitUse]
public sealed class RegisterComponentAttribute : Attribute
{
}
}

View File

@@ -67,7 +67,7 @@ public sealed partial class EntityLookupSystem
}
}
foreach (var uid in toAdd)
foreach (var uid in toAdd.Span)
{
intersecting.Add(uid);
}

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