Compare commits

...

96 Commits

Author SHA1 Message Date
Pieter-Jan Briers
32bca7cfd4 Version: 237.1.0 2024-10-19 12:03:52 +02:00
wixoa
008babebc6 Fix some window UIScale bugs (#5499)
* Fix some window UIScale bugs

* Use CalculateAutoScale()
2024-10-19 00:08:16 +02:00
Pieter-Jan Briers
c65c4ba57e Made csi reflection helpers get members up the inheritance chain too 2024-10-18 18:40:39 +02:00
Pieter-Jan Briers
eb5b838e61 Made csi type auto-completion aware of generic types 2024-10-18 18:40:39 +02:00
Pieter-Jan Briers
6b43036c9d Fix UniqueIndexHkm memory leaking
Yeah that's just great this goddamn data structure had no damn API to ever remove anything from it. Incredible.
2024-10-18 18:40:39 +02:00
ElectroJr
f23a55793d Version: 237.0.0 2024-10-18 16:11:33 +13:00
wixoa
46143d2589 Separate window creation in OSWindow.Show() to allow creation in the background (#5489)
* OSWindow rework
OSWindow now created ClydeWindow and WindowRoot immediately, but non-visible in the background
Also added the ability to programatically resize an open window

* Implement window resizing on SDL2

* Revert OSWindow changes

* Split `Show()` into `Create()` and `Show()`

* Formatting
2024-10-17 17:21:38 +02:00
MilenVolf
ba7d1452c1 Add Erase button for TileSpawnWindow (#5488)
* Add Erase button for TileSpawnWindow

Small QoL for mappers. Basically, it just selects space tile on "Erase" button toggled.

* Remove copy paste. Conevrt this into method
2024-10-13 15:55:57 +02:00
Pieter-Jan Briers
1c1343466e Improve docs for IConsoleShell.Player
Just realized this relation of "no player = server console" is not clearly documented.
2024-10-11 15:14:38 +02:00
Pieter-Jan Briers
0d534e8bcd Allow watchdog to specify more information about why the server should restart.
Had a plan to use this, but realized for what I'm doing immediately I don't quite need it yet.

/update server endpoint can now receive a Reason code and Message field. These are available with IWatchdogApi.RestartRequested.

Cleaned up IWatchdogApi: Added comments, moved symbols that should only be called by the engine to an internal interface. Also cleaned up some code in WatchdogApi to remove some IDE warnings.
2024-10-11 00:26:37 +02:00
Pieter-Jan Briers
c83c6f9592 Fix RobustSerializer breaking for non-seekable streams.
Shows up in replay loading from zip files, as the stream may be compressed. The statistics code in RobustSerializer assumes the stream is always seekable (by accessing .Position).

Now we don't run the statistics logic when reading/writing non-seekable-streams.
2024-10-10 04:31:37 +02:00
Pieter-Jan Briers
c794bd84bf Replay load: remove unnecessary bufferSize parameter.
This usage really doesn't make sense, and it makes the usage invalid if the size is zero. Now realistically I don't think this happens except in edge-case replay files, but it's still silly.

Removed if for no other reason than spite for making me look at this code and reason about it.
2024-10-10 04:06:39 +02:00
Pieter-Jan Briers
d1d43f834b Version: 236.1.0 2024-10-08 22:59:51 +02:00
Pieter-Jan Briers
9505cb68df Add SwitchExpressionException to sandbox
Fixes #5450
2024-10-07 19:05:17 +02:00
Ed
9763f5fdf4 filter entities (#5473) 2024-10-07 18:48:39 +02:00
Mervill
80a963ec05 Replace obsolete functions in MapSystem (#5483) 2024-10-07 18:48:09 +02:00
Mervill
3a670ec25e Replace obsolete functions in EntityLookup Test (#5482) 2024-10-07 18:34:22 +02:00
mhamster
e45950a557 Update BaseServer.cs (#5487)
+ Server now gives a proper reason of shutdown when shutting down before main loop has been started
2024-10-07 14:06:49 +02:00
eoineoineoin
6f1427ef3c Interface to remove a controls child by index (#5485)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-10-06 22:51:53 +02:00
Mervill
9a7d1a39c1 Replace obsolete functions in GridFixtureSystem (#5484) 2024-10-06 02:03:01 +02:00
Mervill
9be903ee56 Replace obsolete functions in GridRotation Tests (#5481) 2024-10-05 13:21:26 +10:00
Mervill
72f9f9c343 Trim unused method variables (#5480) 2024-10-05 13:19:21 +10:00
metalgearsloth
3ad760a99e Add another lookup overload (#5477) 2024-10-03 18:56:44 +02:00
metalgearsloth
e2f3722ce9 Set sprite flicks immediately (#5467)
So on content we have an issue where the animation is played in doorsystem but sprite visibility is controlled by airlocksystem. The issue then is that we get a single frame where the incorrect sprite is shown before it corrects itself. The easiest way to reproduce this is to walk into a door that denies you and observe it shows the incorrect sprite then flickers to the denied one.

There might be more systems with these issues which is why I did this here instead.
2024-10-03 18:56:06 +02:00
Vasilis
b4beca6562 Expose GameTitle, WindowIconSet and SplashLogo to content (#5475)
* Expose GameTitle to public

Requirement for upstream ss14 pr

* Missing method implemented

* Add windowiconset and splashlogo (I think this is what pjb meant?)

I don't think its worth it to add the other stuff (modules, assemblyprefix, autocnnect, clientassemblies)

* Docs
2024-10-01 12:05:44 +02:00
Pieter-Jan Briers
ea02260230 Add LineEdit.SelectAllOnFocus 2024-10-01 01:07:08 +02:00
Leon Friedrich
3b243e487d Add required keyword attributes to sandbox whitelist (#5474)
* Add `required` keyword attributes to sandbox

* Release notes
2024-09-30 16:00:24 +02:00
Pieter-Jan Briers
f40ccb7558 New HWID system prep (#5446)
* New HWID system prep

* Allow HWID to be disabled.

Both client and server can now request HWID to be disabled.

On the server via CVar, if disabled the client won't send it.

On the client via env var, if disabled it won't be sent to the client.

This involved moving legacy HWID to be sent in MsgEncryptionResponse instead of MsgLoginStart. This means the legacy HWID won't be available anymore if the connection isn't authenticated.

* Fix tests

* Fix another test

* Review

* Thanks Rider
2024-09-29 00:29:02 +02:00
Stalen
f467a7027b Added MuteSounds property for BaseButton control (#5465) 2024-09-29 00:25:56 +02:00
eoineoineoin
c9d7d442d9 Make IPlayerManager accessible to derived classes (#5471)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-28 23:48:07 +02:00
eoineoineoin
342626ad9b Account for scale when calculating sprite offset (#5470)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-28 17:31:24 +02:00
metalgearsloth
1c3ea968e4 Version: 236.0.0 2024-09-28 19:16:35 +10:00
Pieter-Jan Briers
f0ed3537ee Duplicate dependency field analyzer (#5463)
* Duplicate dependency field analyzer

Detects cases of duplicate [Dependency] fields in a type. We apparently have 27 of these across RT + SS14.

* Fix duplicate dependencies in Robust
2024-09-28 15:35:18 +10:00
metalgearsloth
74e7e61a98 Revert "Make resetting contacts on the client only set is touching if it is true" (#5469)
This reverts commit cdb94748c8.
2024-09-28 14:33:37 +10:00
metalgearsloth
fb9b0ae89b Remove IsTouching set on physics prediction (#5468)
Just because an entity sleeps doesn't mean it's not touching necessarily. This causes client to mispredict against server and continuously fire collision events if we try to move into an entity.

Easiest way to reproduced is to walk into a locked airlock and watch it flicker constantly.
2024-09-28 14:13:05 +10:00
Stalen
dbe297b1fc Activate XAML hot reload on file rename (for VS support) (#5429) 2024-09-24 09:43:00 +10:00
Leon Friedrich
b84917e8e4 Obsolete some static localization methods (#5460) 2024-09-24 09:40:42 +10:00
Leon Friedrich
abb3f65fe4 Make EnsureEntityDictionary use TryAdd (#5461) 2024-09-24 09:40:16 +10:00
Leon Friedrich
41ec2dc131 Try improve PVS exception tolerance a bit more (#5454) 2024-09-24 09:39:33 +10:00
eoineoineoin
e714dcc83c Fix TabContainer click detection when UIScale was not == 1.0 (#5456)
* Fix tabcontainer click detection when UIScale was not == 1.0

* Remove whitespace

---------

Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-22 14:41:27 +02:00
ShadowCommander
46291af1be Add ProtoId parser to Toolshed (#5220)
* Add ProtoId parser to Toolshed

* Change obsolete FromMarkup to FromMarkupOrThrow
2024-09-21 21:57:08 +10:00
Leon Friedrich
ad929c9955 Fix ICommonSession.Ping (#5453) 2024-09-20 16:43:12 +02:00
metalgearsloth
c86cb0b795 Version: 235.0.0 2024-09-18 12:13:35 +10:00
metalgearsloth
8d03feb84f Transform precision thing (#5451)
Just noticed it but probably doesn't affect anything really, we'll go from 64bit to 32bit after the math operations and not before.
2024-09-18 12:08:36 +10:00
Plykiya
0fa21ee2d2 Completely obsolete noSpawn (#5364) 2024-09-18 11:48:13 +10:00
eoineoineoin
9be0f032e8 Fix DistanceJoints drawn by physics debug system (#5439)
Co-authored-by: Eoin Mcloughlin <helloworld@eoinrul.es>
2024-09-18 11:45:01 +10:00
Leon Friedrich
afffb33446 Stop empty audio system filters from playing sounds for all players (#5444)
* Fix audio system empty filter bug

* The nullable attributes are lying
2024-09-18 11:44:16 +10:00
Leon Friedrich
19a87fb67a Remove incorrect NotNullIfNotNull attributes in SharedAudioSystem (#5449) 2024-09-18 11:43:49 +10:00
DrSmugleaf
2fda62a274 Fix physics.maxlinvelocity not being a replicated cvar (#5445) 2024-09-17 12:51:54 +10:00
ike709
5218bf70b0 Bump cefglue (#5441)
* Bump cefglue

* Another bump

* Third time's the charm

---------

Co-authored-by: ike709 <ike709@github.com>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2024-09-16 22:31:10 +02:00
Pieter-Jan Briers
4f95c07ab3 Add missing Roslyn components to solution 2024-09-16 21:34:59 +02:00
Lgibb18
786acae47a Fix tags with controls in RichText and OutputPanel (#5428)
* Controls in RichText fixes

* useless

* Get FormattedMessage from RichTextLabel

* dont go through nodes

* Comments and minor changes

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2024-09-16 14:13:59 +10:00
Leon Friedrich
f81e30a031 Try fix invalid PVS index bug (#5422)
* Try fix invalid PVS index bug

* bounds check

* More Asserts

* fix assert?

* remove deletion

* a

* A!
2024-09-16 14:12:20 +10:00
Leon Friedrich
f5c1d870f9 Improve FlushEntities() error logs (#5427)
* Improve FlushEntities() error logs

* log count before flush
2024-09-16 14:06:04 +10:00
Leon Friedrich
4949b34c88 Fix "to" and "take" toolshed commands (#5438) 2024-09-13 22:34:30 +10:00
metalgearsloth
0f60ad9018 Version: 234.1.0 2024-09-12 17:56:50 +10:00
metalgearsloth
f7287b181d Fix audioparams for playglobal (#5437) 2024-09-12 17:48:37 +10:00
Fildrance
45b7500d93 feat: added audio system predicted method for only one receiver (#5435)
* feat: added audio system predicted method for only one receiver

* renamed to PlayLocal

* tweak

---------

Co-authored-by: pa.pecherskij <pa.pecherskij@interfax.ru>
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-09-12 17:41:40 +10:00
metalgearsloth
dbe6f65880 Version: 234.0.0 2024-09-12 13:56:27 +10:00
metalgearsloth
4faef1bfd3 Add another lookup override (#5436) 2024-09-12 13:53:36 +10:00
Kara
48d70a09c6 Remove most fully-obsoleted code (#5433) 2024-09-11 19:38:26 +10:00
Pieter-Jan Briers
f682fb9cc7 Obsolete some useless type proxies on IResourceCache
These aren't even used, but they're pretty objectively bad ideas so let's obsolete them so we can get rid of them later.
2024-09-09 11:22:07 +02:00
Pieter-Jan Briers
814e5bcf17 Mark large replays as requiring Server GC.
This should significantly improve loading performance of large replays.

System can be controlled by replay.server_gc_size_threshold, which defaults to 50 MiB.

This is the engine-side of https://github.com/space-wizards/SS14.Launcher/issues/177
2024-09-09 08:23:20 +02:00
metalgearsloth
dbc4e80e61 Version: 233.1.0 2024-09-08 17:55:56 +10:00
metalgearsloth
5eb5ddd96e Add some entitylookup methods (#5431) 2024-09-08 17:22:48 +10:00
Leon Friedrich
405ed378c0 Re-attempt FlushEntities() on failure (#5423) 2024-09-06 20:51:34 +10:00
Leon Friedrich
be9db264dd Minor toolshed fixes / tweaks (#5315)
* Don't use markup for type names

* Cache TypeTypeParser completions

* Cache all type parsers

* Release notes

* More IConError fixes

* a
2024-09-06 10:48:50 +10:00
Pieter-Jan Briers
2f73f6190d Fix warning in ScriptGlobalsShared.cs 2024-09-04 21:31:37 +02:00
metalgearsloth
f3dfa1f666 Move testbed command to benchmarks (#5424) 2024-09-03 22:02:00 +10:00
Pieter-Jan Briers
b0d17e9527 Fix dead code equals method in Polygon
Fixes #5420

Sloth clarified it's dead code from copy pasting.
2024-09-02 13:17:02 +02:00
Pieter-Jan Briers
4c81e68bf1 Remove last FormattedMessage.FromMarkup calls 2024-09-02 07:36:34 +02:00
Pieter-Jan Briers
4490751001 Fix warnings in FormattedMessageSerializerTest.cs 2024-09-02 07:33:26 +02:00
Pieter-Jan Briers
bc8d2c154c Fix warnings in EntityManager_Components_Tests.cs 2024-09-02 07:32:47 +02:00
Pieter-Jan Briers
3c83f8e62a Make Rider not complain about Is. in Robust.UnitTesting, globally. 2024-09-02 07:27:23 +02:00
Pieter-Jan Briers
c36919d76a Fix warnings in ArithmeticTest.cs 2024-09-02 05:45:08 +02:00
Pieter-Jan Briers
70a853cdd5 Fix most warnings in AnchoredSystemTests.cs
Remaining warnings are cases where AnchorEntity, SetWorldPosition and SetLocalPosition subtly different from their component counterparts, and this triggers a test failure. Some help would be appreciated here.
2024-09-02 05:38:40 +02:00
Pieter-Jan Briers
fd3eb092cc EntityUid-only overloads for some TransformSystem methods
AnchorEntity and Unanchor
2024-09-02 05:37:49 +02:00
Pieter-Jan Briers
c740026014 Entity<T> overloads for some MapSystem methods.
GetTileRef, TileIndicesFor, and GetAnchoredEntities
2024-09-02 05:36:11 +02:00
Pieter-Jan Briers
44f9262d1a SimulationExtensions helpers for RobustServerSimulation
No more need to manually resolve IEntityManager in every test.
2024-09-02 05:30:10 +02:00
Pieter-Jan Briers
df2160b151 Fix warnings in Broadphase_Test.cs 2024-09-02 04:10:54 +02:00
Pieter-Jan Briers
5c7b1e6823 Fix warnings in ToolshedTest.cs 2024-09-02 04:01:24 +02:00
Pieter-Jan Briers
eaf7a6ba0f Fix warnings in ToolshedTypesTest.cs 2024-09-02 03:59:55 +02:00
Pieter-Jan Briers
9ab4286592 Wait why is that even returning a task in the first place 2024-09-02 03:59:25 +02:00
Pieter-Jan Briers
3f02ef3730 Fix warnings in PvsSystemTests.cs 2024-09-02 03:57:54 +02:00
Pieter-Jan Briers
2f17cbb1dc Fix warnings in ToolshedTypesTest.BugCheck.cs 2024-09-02 03:57:09 +02:00
Pieter-Jan Briers
c2657812f5 Fix warnings in GridTraversalTest.cs 2024-09-02 03:56:42 +02:00
Pieter-Jan Briers
f17f077849 Fix warnings in FormattedMessage_Test.cs 2024-09-02 03:55:14 +02:00
Pieter-Jan Briers
306deddbd2 Fix warnings in TransformComponent_Tests.cs 2024-09-02 03:54:52 +02:00
Pieter-Jan Briers
cdd8df743a Fix warnings in Transform_Test.cs 2024-09-02 03:49:06 +02:00
Pieter-Jan Briers
e7ac5ad047 Fix warnings in UserInterfaceManagerTest.cs 2024-09-02 03:45:46 +02:00
Pieter-Jan Briers
0e621a26be Fix warnings in ControlTest.cs 2024-09-02 03:45:46 +02:00
Pieter-Jan Briers
bbcc7cfe1f Fix warnings in GameLoop_Test.cs 2024-09-02 03:45:46 +02:00
Mervill
1208c25dcd resolve instances of the CS8974 warning (#5418) 2024-09-02 03:29:10 +02:00
Pieter-Jan Briers
38c227b692 Fix MarkupNode equality
Implement GetHashCode()

Fix Equals doing reference equality on the attributes.

Small code cleanup.

Actually mark it as IEquatable<MarkupNode> because we already implement Equals() so there's no reason not to.
2024-09-02 03:27:25 +02:00
Mervill
4e73d72753 Remove unused IoC dependencies (#5419) 2024-09-01 23:23:02 +02:00
Pieter-Jan Briers
b1e1a0cd88 Quick warning fixes (#5417) 2024-09-01 04:54:28 +02:00
240 changed files with 2428 additions and 1923 deletions

View File

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

View File

@@ -54,6 +54,165 @@ END TEMPLATE-->
*None yet*
## 237.1.0
### New features
* csi's auto import-system can now handle generic types.
* csi's reflection helpers (like `fld()`) handle private members up the inheritance chain.
### Bugfixes
* Fix `UniqueIndexHkm<,>` and, by extension, entity data storage memory leaking.
* Fix bugs related to UIScale on `OSWindow`s.
## 237.0.0
### Breaking changes
* `IClydeWindow.Size` is now settable, allowing window sizes to be changed after creation.
### New features
* The game server's `/update` endpoint now supports passing more information on why an update is available.
* This information is accessible via `IWatchdogApi.RestartRequested`.
* Information can be specified by passing a JSON object with a `Reason` code and `Message` field.
* Added an "Erase" button to the tile spawn menu.
* Added `OSWindow.Create()`, which allows OS windows to be created & initialised without immediately opening/showing them.
### Other
* Made `WatchdogApi` and some members of `IWatchdogApi` private. These symbols should never have been accessed by content.
## 236.1.0
### New features
* `RequiredMemberAttribute` and `SetsRequiredMembersAttribute` have been added to the sandbox whitelist. I.e., you can now use the `required` keyword in client/shared code.
* Added `SwitchExpressionException` to sandbox. This type gets used if you have a `switch` expression with no default case.
* Added `LineEdit.SelectAllOnFocus`.
* `GameTitle`, `WindowIconSet` and `SplashLogo` are exposed in `IGameController`. These will return said information set in game options or whatever is set in `manifest.yml`.
* `BoundUserInterface` inheritors now have access to `PlayerManager`.
* Added `MuteSounds` bool to `BaseButton`.
* The engine has a new future-proof HWID system.
* The auth server now manages HWIDs. This avoids HWID impersonation attacks.
* The auth server can return multiple HWIDs. They are accessible in `NetUserData.ModernHWIds`.
* The auth server also returns a trust score factor, accessible as `NetUserData.Trust`.
* HWID can be disabled client side (`ROBUST_AUTH_ALLOW_HWID` env var) or server side (`net.hwid` cvar).
* The old HWID system is still in place. It is intended that content switches to placing new bans against the new HWIDs.
* Old HWIDs no longer work if the connection is not authenticated.
* `launchauth` command now recognizes `SS14_LAUNCHER_APPDATA_NAME`.
* Added new overload to `EntityLookupSystem.GetEntitiesIntersecting`.
* Added `Control.RemoveChild(int childIndex)`.
* `build.entities_category_filter` allows filtering the entity spawn panel to a specific category.
### Bugfixes
* Fixed `SpriteView` offset calculations when scaled.
### Other
* Sprite flicks are applied immediately when started.
* More warning fixes.
* If the server gets shut down before finishing startup, the reason is now logged properly.
## 236.0.0
### Breaking changes
* Revert IsTouching only being set to true if the contact were laready touching in clientside physics prediction.
* Don't touch IsTouching if both bodies are asleep for clientside physics contacts. This change and the one above should fix a lot of clientside contact issues, particularly around repeated incorrect clientside contact events.
### New features
* Added an analyzer to detect duplicate Dependency fields.
### Bugfixes
* Auto-networked dictionaries now use `TryAdd()` to avoid duplicate key errors when a dictionary contains multiple unknown networked entities.
* Fixed `ICommonSession.Ping` always returning zero instead of the ping. Note that this will still return zero for client-side code when trying to get the ping of other players.
* Hot reload XAML files on rename to fix them potentially not being reloaded with Visual Studio.
* Fix TabContainer click detection for non-1.0 UI scales.
### Other
* Obsolete some static localization methods.
* Tried to improve PVS tolerance to exceptions occurring.
## 235.0.0
### Breaking changes
* Several different `AudioSystem` methods were incorrectly given a `[return: NotNullIfNotNull]` attribute. Content code that uses these methods needs to be updated to perform null checks.
* noSpawn is no longer obsolete and is now removed in lieu of the EntityCategory HideSpawnMenu.
### Bugfixes
* physics.maxlinvelocity is now a replicated cvar.
* Fix DistanceJoint debug drawing in physics not using the local anchors.
* Fixed filtered AudioSystem methods playing a sound for all players when given an empty filter.
* Fixed equality checks for `MarkupNode` not properly handling attributes.
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
* Fixed a PVS error that could occur when trying to delete the first entity that gets created in a round.
* Fixed the "to" and "take" toolshed commands not working as intended.
* Rich text controls within an `OutputPanel` control will now become invisible when they are out of view.
### Other
* Improve precision for Quaternion2D constructor from angles.
## 234.1.0
### New features
* SharedAudioSystem now has PlayLocal which only runs audio locally on the client.
### Bugfixes
* Fix AudioParams not being passed through on PlayGlobal methods.
## 234.0.0
### Breaking changes
* Remove a lot of obsoleted code that has been obsoleted for a while.
### New features
* Add another GetLocalEntitiesIntersecting override.
### Other
* Mark large replays as requiring Server GC.
* Obsolete some IResourceCache proxies.
## 233.1.0
### New features
* Add GetGridEntities and another GetEntitiesIntersecting overload to EntityLookupSystem.
* `MarkupNode` is now `IEquatable<MarkupNode>`. It already supported equality checks, now it implements the interface.
* Added `Entity<T>` overloads to the following `SharedMapSystem` methods: `GetTileRef`, `GetAnchoredEntities`, `TileIndicesFor`.
* Added `EntityUid`-only overloads to the following `SharedTransformSystem` methods: `AnchorEntity`, `Unanchor`.
### Bugfixes
* Fixed equality checks for `MarkupNode` not properly handling attributes.
* Fixed toolshed commands failing to generate error messages when working with array types
* Fixed `MarkupNode` not having a `GetHashCode()` implementation.
### Other
* If `EntityManager.FlushEntities()` fails to delete all entities, it will now attempt to do so a second time before throwing an exception.
## 233.0.2
### Bugfixes
@@ -83,7 +242,7 @@ END TEMPLATE-->
### Internal
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
* `ClientGameStateManager` now only initialises or starts entities after their parents have already been initialized. There are also some new debug asserts to try ensure that this rule isn't broken elsewhere.
* Engine version script now supports dashes.

View File

@@ -18,3 +18,9 @@
description: entity-category-desc-hide
hideSpawnMenu: true
inheritable: false
# Entity prototypes added by the fork. With CVar you can hide all entities without this category
- type: entityCategory
id: ForkFiltered
name: entity-category-name-fork
description: entity-category-desc-fork

View File

@@ -4,9 +4,16 @@ entity-spawn-window-title = Entity Spawn Panel
entity-spawn-window-search-bar-placeholder = search
entity-spawn-window-clear-button = Clear
entity-spawn-window-replace-button-text = Replace
entity-spawn-window-erase-button-text = Erase Mode
entity-spawn-window-override-menu-tooltip = Override placement
## TileSpawnWindow
tile-spawn-window-title = Place Tiles
## Console
console-line-edit-placeholder = Command Here
## Common Used
window-erase-button-text = Erase Mode

View File

@@ -7,3 +7,6 @@ entity-category-desc-spawner = Entity prototypes that spawn other entities.
entity-category-name-hide = Hidden
entity-category-desc-hide = Entity prototypes that should be hidden from entity spawn menus
entity-category-name-fork = Fork Filtered
entity-category-desc-fork = Entity prototypes added by the fork. With CVar you can hide all entities without this category

View File

@@ -219,9 +219,9 @@ command-description-MulVecCommand =
command-description-DivVecCommand =
Divides every element in the input by a scalar (single value).
command-description-rng-to =
Returns a number from its input to its argument (i.e. n..m inclusive)
Returns a number between the input (inclusive) and the argument (exclusive).
command-description-rng-from =
Returns a number to its input from its argument (i.e. m..n inclusive)
Returns a number between the argument (inclusive) and the input (exclusive))
command-description-rng-prob =
Returns a boolean based on the input probability/chance (from 0 to 1)
command-description-sum =

View File

@@ -0,0 +1,63 @@
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.DuplicateDependencyAnalyzer>;
namespace Robust.Analyzers.Tests;
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)]
[TestFixture]
[TestOf(typeof(DuplicateDependencyAnalyzer))]
public sealed class DuplicateDependencyAnalyzerTest
{
private static Task Verifier(string code, params DiagnosticResult[] expected)
{
var test = new CSharpAnalyzerTest<DuplicateDependencyAnalyzer, 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;
[Dependency]
private object? Field2;
[Dependency]
private string? DifferentField;
private string? NonDependency1;
private string? NonDependency2;
}
""";
await Verifier(code,
// /0/Test0.cs(9,21): warning RA0032: Another [Dependency] field of type 'object?' already exists in this type as field 'Field'
VerifyCS.Diagnostic().WithSpan(9, 21, 9, 27).WithArguments("object?", "Field"));
}
}

View File

@@ -0,0 +1,126 @@
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Robust.Roslyn.Shared;
namespace Robust.Analyzers;
#nullable enable
/// <summary>
/// Analyzer that detects duplicate <c>[Dependency]</c> fields inside a single type.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class DuplicateDependencyAnalyzer : DiagnosticAnalyzer
{
private const string DependencyAttributeType = "Robust.Shared.IoC.DependencyAttribute";
private static readonly DiagnosticDescriptor Rule = new(
Diagnostics.IdDuplicateDependency,
"Duplicate dependency field",
"Another [Dependency] field of type '{0}' already exists in this type with field '{1}'",
"Usage",
DiagnosticSeverity.Warning,
true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(compilationContext =>
{
var dependencyAttributeType = compilationContext.Compilation.GetTypeByMetadataName(DependencyAttributeType);
if (dependencyAttributeType == null)
return;
compilationContext.RegisterSymbolStartAction(symbolContext =>
{
var typeSymbol = (INamedTypeSymbol)symbolContext.Symbol;
// Only deal with non-static classes, doesn't make sense to have dependencies in anything else.
if (typeSymbol.TypeKind != TypeKind.Class || typeSymbol.IsStatic)
return;
var state = new AnalyzerState(dependencyAttributeType);
symbolContext.RegisterSyntaxNodeAction(state.AnalyzeField, SyntaxKind.FieldDeclaration);
symbolContext.RegisterSymbolEndAction(state.End);
},
SymbolKind.NamedType);
});
}
private sealed class AnalyzerState(INamedTypeSymbol dependencyAttributeType)
{
private readonly Dictionary<ITypeSymbol, List<IFieldSymbol>> _dependencyFields = new(SymbolEqualityComparer.Default);
public void AnalyzeField(SyntaxNodeAnalysisContext context)
{
var field = (FieldDeclarationSyntax)context.Node;
if (field.AttributeLists.Count == 0)
return;
if (context.ContainingSymbol is not IFieldSymbol fieldSymbol)
return;
// Can't have [Dependency]s for non-reference types.
if (!fieldSymbol.Type.IsReferenceType)
return;
if (!IsDependency(context.ContainingSymbol))
return;
lock (_dependencyFields)
{
if (!_dependencyFields.TryGetValue(fieldSymbol.Type, out var dependencyFields))
{
dependencyFields = [];
_dependencyFields.Add(fieldSymbol.Type, dependencyFields);
}
dependencyFields.Add(fieldSymbol);
}
}
private bool IsDependency(ISymbol symbol)
{
foreach (var attributeData in symbol.GetAttributes())
{
if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, dependencyAttributeType))
return true;
}
return false;
}
public void End(SymbolAnalysisContext context)
{
lock (_dependencyFields)
{
foreach (var pair in _dependencyFields)
{
var fieldType = pair.Key;
var fields = pair.Value;
if (fields.Count <= 1)
continue;
// Sort so we can have deterministic order to skip reporting for a single field.
// Whichever sorts first doesn't get reported.
fields.Sort(static (a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal));
// Start at index 1 to skip first field.
var firstField = fields[0];
for (var i = 1; i < fields.Count; i++)
{
var field = fields[i];
context.ReportDiagnostic(
Diagnostic.Create(Rule, field.Locations[0], fieldType.ToDisplayString(), firstField.Name));
}
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Microsoft.Extensions.Configuration;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
[MediumRunJob]
public class PhysicsBoxStackBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 30; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
[Benchmark]
public void BoxStack()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 10000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
var columnCount = 1;
var rowCount = 15;
PolygonShape shape;
for (var j = 0; j < columnCount; j++)
{
for (var i = 0; i < rowCount; i++)
{
var x = 0.0f;
var boxUid = entManager.SpawnEntity(null,
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
shape = new PolygonShape();
shape.SetAsBox(0.5f, 0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
}
physics.WakeBody(groundUid, body: ground);
}
}

View File

@@ -0,0 +1,92 @@
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
public class PhysicsCircleStackBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 30; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
[Benchmark]
public void CircleStack()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 10000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
var columnCount = 1;
var rowCount = 15;
PhysShapeCircle shape;
for (var j = 0; j < columnCount; j++)
{
for (var i = 0; i < rowCount; i++)
{
var x = 0.0f;
var boxUid = entManager.SpawnEntity(null,
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
shape = new PhysShapeCircle(0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
// TODO: Need to detect shape and work out if we need to use fixedrotation
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
}
physics.WakeBody(groundUid, body: ground);
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
public class PhysicsPyramidBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 300; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
[Benchmark]
public void Pyramid()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 5000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
const byte count = 20;
// Setup ground
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
physics.WakeBody(groundUid, body: ground);
// Setup boxes
float a = 0.5f;
PolygonShape shape = new();
shape.SetAsBox(a, a);
var x = new Vector2(-7.0f, 0.75f);
Vector2 y;
Vector2 deltaX = new Vector2(0.5625f, 1.25f);
Vector2 deltaY = new Vector2(1.125f, 0.0f);
for (var i = 0; i < count; ++i)
{
y = x;
for (var j = i; j < count; ++j)
{
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(y, mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
y += deltaY;
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
x += deltaX;
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Systems;
using Robust.UnitTesting.Server;
namespace Robust.Benchmarks.Physics;
[Virtual]
public class PhysicsTumblerBenchmark
{
private ISimulation _sim = default!;
[GlobalSetup]
public void Setup()
{
_sim = RobustServerSimulation.NewSimulation().InitializeInstance();
var entManager = _sim.Resolve<IEntityManager>();
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
entManager.System<SharedMapSystem>().CreateMap(out var mapId);
SetupTumbler(entManager, mapId);
for (var i = 0; i < 800; i++)
{
entManager.TickUpdate(0.016f, false);
var boxUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var box = entManager.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
physics.SetFixedRotation(boxUid, false, body: box);
var shape = new PolygonShape();
shape.SetAsBox(0.125f, 0.125f);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
physics.WakeBody(boxUid, body: box);
physics.SetSleepingAllowed(boxUid, box, false);
}
}
[Benchmark]
public void Tumbler()
{
var entManager = _sim.Resolve<IEntityManager>();
for (var i = 0; i < 5000; i++)
{
entManager.TickUpdate(0.016f, false);
}
}
private void SetupTumbler(IEntityManager entManager, MapId mapId)
{
var physics = entManager.System<SharedPhysicsSystem>();
var fixtures = entManager.System<FixtureSystem>();
var joints = entManager.System<SharedJointSystem>();
var groundUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
var ground = entManager.AddComponent<PhysicsComponent>(groundUid);
// Due to lookup changes fixtureless bodies are invalid, so
var cShape = new PhysShapeCircle(1f);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
var bodyUid = entManager.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var body = entManager.AddComponent<PhysicsComponent>(bodyUid);
physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
physics.SetSleepingAllowed(bodyUid, body, false);
physics.SetFixedRotation(bodyUid, false, body: body);
// TODO: Box2D just deref, bleh shape structs someday
var shape1 = new PolygonShape();
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
var shape2 = new PolygonShape();
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
var shape3 = new PolygonShape();
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
var shape4 = new PolygonShape();
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
physics.WakeBody(groundUid, body: ground);
physics.WakeBody(bodyUid, body: body);
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
revolute.LocalAnchorA = new Vector2(0f, 10f);
revolute.LocalAnchorB = new Vector2(0f, 0f);
revolute.ReferenceAngle = 0f;
revolute.MotorSpeed = 0.05f * MathF.PI;
revolute.MaxMotorTorque = 100000000f;
revolute.EnableMotor = true;
}
}

View File

@@ -453,6 +453,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null; // uhh Lets hope predicted audio never needs to somehow store the playing audio....
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayLocal(
SoundSpecifier? sound,
EntityUid source,
EntityUid? soundInitiator,
AudioParams? audioParams = null
)
{
return PlayPredicted(sound, source, soundInitiator, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null)
{
if (Timing.IsFirstTimePredicted && sound != null)

View File

@@ -33,12 +33,6 @@ public interface IMidiRenderer : IDisposable
/// </summary>
bool LoopMidi { get; set; }
/// <summary>
/// This increases all note on velocities to 127.
/// </summary>
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
bool VolumeBoost { get; set; }
/// <summary>
/// The midi program (instrument) the renderer is using.
/// </summary>

View File

@@ -205,14 +205,6 @@ internal sealed class MidiRenderer : IMidiRenderer
}
}
[ViewVariables(VVAccess.ReadWrite)]
[Obsolete($"Use {nameof(VelocityOverride)} instead, you can set it to 127 to achieve the same effect.")]
public bool VolumeBoost
{
get => VelocityOverride == 127;
set => VelocityOverride = value ? 127 : null;
}
[ViewVariables(VVAccess.ReadWrite)]
public EntityUid? TrackingEntity { get; set; } = null;

View File

@@ -8,6 +8,7 @@ using Robust.Client.GameObjects;
using Robust.Client.GameStates;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Client.HWId;
using Robust.Client.Input;
using Robust.Client.Map;
using Robust.Client.Placement;
@@ -158,6 +159,7 @@ namespace Robust.Client
deps.Register<IXamlProxyHelper, XamlProxyHelper>();
deps.Register<MarkupTagManager>();
deps.Register<IHWId, BasicHWId>();
}
}
}

View File

@@ -20,8 +20,9 @@ namespace Robust.Client.Console.Commands
{
var wantName = args.Length > 0 ? args[0] : null;
var basePath = Path.GetDirectoryName(UserDataDir.GetUserDataDir(_gameController))!;
var dbPath = Path.Combine(basePath, "launcher", "settings.db");
var basePath = UserDataDir.GetRootUserDataDir(_gameController);
var launcherDirName = Environment.GetEnvironmentVariable("SS14_LAUNCHER_APPDATA_NAME") ?? "launcher";
var dbPath = Path.Combine(basePath, launcherDirName, "settings.db");
#if USE_SYSTEM_SQLITE
SQLitePCL.raw.SetProvider(new SQLitePCL.SQLite3Provider_sqlite3());

View File

@@ -14,15 +14,6 @@ namespace Robust.Client.Credits
/// </summary>
public static class CreditsManager
{
/// <summary>
/// Gets a list of open source software used in the engine and their license.
/// </summary>
[Obsolete("Use overload that takes in an explicit resource manager instead.")]
public static IEnumerable<LicenseEntry> GetLicenses()
{
return GetLicenses(IoCManager.Resolve<IResourceManager>());
}
/// <summary>
/// Gets a list of open source software used in the engine and their license.
/// </summary>

View File

@@ -544,7 +544,7 @@ namespace Robust.Client.Debugging
switch (joint)
{
case DistanceJoint:
worldHandle.DrawLine(xf1, xf2, JointColor);
worldHandle.DrawLine(p1, p2, JointColor);
break;
case PrismaticJoint prisma:
var pA = Transform.Mul(xfa, joint.LocalAnchorA);

View File

@@ -112,14 +112,28 @@ namespace Robust.Client
_commandLineArgs = args;
}
public string GameTitle()
{
return Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox";
}
public string WindowIconSet()
{
return Options.WindowIconSet?.ToString() ?? _resourceManifest!.WindowIconSet ?? "";
}
public string SplashLogo()
{
return Options.SplashLogo?.ToString() ?? _resourceManifest!.SplashLogo ?? "";
}
internal bool StartupContinue(DisplayMode displayMode)
{
DebugTools.AssertNotNull(_resourceManifest);
_clyde.InitializePostWindowing();
_audio.InitializePostWindowing();
_clyde.SetWindowTitle(
Options.DefaultWindowTitle ?? _resourceManifest!.DefaultWindowTitle ?? "RobustToolbox");
_clyde.SetWindowTitle(GameTitle());
_taskManager.Initialize();
_parallelMgr.Initialize();
@@ -399,10 +413,8 @@ namespace Robust.Client
// Handle GameControllerOptions implicit CVar overrides.
_configurationManager.OverrideConVars(new[]
{
(CVars.DisplayWindowIconSet.Name,
options.WindowIconSet?.ToString() ?? _resourceManifest.WindowIconSet ?? ""),
(CVars.DisplaySplashLogo.Name,
options.SplashLogo?.ToString() ?? _resourceManifest.SplashLogo ?? "")
(CVars.DisplayWindowIconSet.Name, WindowIconSet()),
(CVars.DisplaySplashLogo.Name, SplashLogo())
});
}

View File

@@ -99,15 +99,6 @@ namespace Robust.Client.GameObjects
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
}
/// <summary>
/// Start playing an animation.
/// </summary>
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
public void Play(AnimationPlayerComponent component, Animation animation, string key)
{
Play(new Entity<AnimationPlayerComponent>(component.Owner, component), animation, key);
}
public void Play(Entity<AnimationPlayerComponent> ent, Animation animation, string key)
{
AddComponent(ent);
@@ -152,6 +143,14 @@ namespace Robust.Client.GameObjects
}
#endif
foreach (var track in animation.AnimationTracks)
{
if (track is not AnimationTrackSpriteFlick)
continue;
track.AdvancePlayback(ent.Owner, 0, 0, 0f);
}
ent.Comp.PlayingAnimations.Add(key, playback);
}

View File

@@ -17,7 +17,6 @@ namespace Robust.Client.GameObjects
{
public sealed class ContainerSystem : SharedContainerSystem
{
[Dependency] private readonly INetManager _netMan = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly PointLightSystem _lightSys = default!;

View File

@@ -20,7 +20,6 @@ namespace Robust.Client.GameObjects
/// </summary>
public sealed class InputSystem : SharedInputSystem, IPostInjectInit
{
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IInputManager _inputManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IClientGameStateManager _stateManager = default!;

View File

@@ -34,9 +34,6 @@ namespace Robust.Client.GameStates
/// </summary>
int GetApplicableStateCount();
[Obsolete("use GetApplicableStateCount()")]
int CurrentBufferSize => GetApplicableStateCount();
/// <summary>
/// Total number of game states currently in the state buffer.
/// </summary>

View File

@@ -15,7 +15,6 @@ namespace Robust.Client.GameStates
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly EntityLookupSystem _lookup;

View File

@@ -109,6 +109,9 @@ namespace Robust.Client.Graphics.Clyde
private void SendWindowResized(WindowReg reg, Vector2i oldSize)
{
if (!reg.IsVisible) // Only send this for open windows
return;
var loaded = RtToLoaded(reg.RenderTarget);
loaded.Size = reg.FramebufferSize;

View File

@@ -343,6 +343,8 @@ namespace Robust.Client.Graphics.Clyde
if (isMain)
_mainWindow = reg;
reg.IsVisible = parameters.Visible;
_windows.Add(reg);
_windowHandles.Add(reg.Handle);
@@ -444,6 +446,12 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.CursorSet(_mainWindow!, cursor);
}
private void SetWindowSize(WindowReg reg, Vector2i size)
{
DebugTools.AssertNotNull(_windowing);
_windowing!.WindowSetSize(reg, size);
}
private void SetWindowVisible(WindowReg reg, bool visible)
{
@@ -533,7 +541,11 @@ namespace Robust.Client.Graphics.Clyde
_clyde.DoDestroyWindow(Reg);
}
public Vector2i Size => Reg.FramebufferSize;
public Vector2i Size
{
get => Reg.FramebufferSize;
set => _clyde.SetWindowSize(Reg, value);
}
public IRenderTarget RenderTarget => Reg.RenderTarget;

View File

@@ -32,7 +32,6 @@ namespace Robust.Client.Graphics.Clyde
internal sealed partial class Clyde : IClydeInternal, IPostInjectInit, IEntityEventSubscriber
{
[Dependency] private readonly IClydeTileDefinitionManager _tileDefinitionManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly ILightManager _lightManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!;

View File

@@ -517,7 +517,7 @@ namespace Robust.Client.Graphics.Clyde
RenderTarget = renderTarget;
}
public Vector2i Size { get; } = default;
public Vector2i Size { get; set; } = default;
public bool IsDisposed { get; private set; }
public WindowId Id { get; set; }
public IRenderTarget RenderTarget { get; }

View File

@@ -85,6 +85,10 @@ namespace Robust.Client.Graphics.Clyde
WinThreadWinSetMonitor(cmd);
break;
case CmdWinSetSize cmd:
WinThreadWinSetSize(cmd);
break;
case CmdWinSetVisible cmd:
WinThreadWinSetVisible(cmd);
break;
@@ -234,6 +238,11 @@ namespace Robust.Client.Graphics.Clyde
nint Window
) : CmdBase;
private sealed record CmdWinSetSize(
nint Window,
int W, int H
) : CmdBase;
private sealed record CmdWinSetVisible(
nint Window,
bool Visible

View File

@@ -84,6 +84,13 @@ namespace Robust.Client.Graphics.Clyde
);
}
public void WindowSetSize(WindowReg window, Vector2i size)
{
var reg = (GlfwWindowReg) window;
SendCmd(new CmdWinSetSize((nint) reg.GlfwWindow, size.X, size.Y));
}
public void WindowSetVisible(WindowReg window, bool visible)
{
var reg = (GlfwWindowReg) window;
@@ -92,6 +99,13 @@ namespace Robust.Client.Graphics.Clyde
SendCmd(new CmdWinSetVisible((nint) reg.GlfwWindow, visible));
}
private void WinThreadWinSetSize(CmdWinSetSize cmd)
{
var win = (Window*) cmd.Window;
GLFW.SetWindowSize(win, cmd.W, cmd.H);
}
private void WinThreadWinSetVisible(CmdWinSetVisible cmd)
{
var win = (Window*) cmd.Window;

View File

@@ -39,6 +39,7 @@ namespace Robust.Client.Graphics.Clyde
void WindowDestroy(WindowReg reg);
void WindowSetTitle(WindowReg window, string title);
void WindowSetMonitor(WindowReg window, IClydeMonitor monitor);
void WindowSetSize(WindowReg window, Vector2i size);
void WindowSetVisible(WindowReg window, bool visible);
void WindowRequestAttention(WindowReg window);
void WindowSwapBuffers(WindowReg window);

View File

@@ -93,6 +93,10 @@ internal partial class Clyde
WinThreadWinRequestAttention(cmd);
break;
case CmdWinSetSize cmd:
WinThreadWinSetSize(cmd);
break;
case CmdWinSetVisible cmd:
WinThreadWinSetVisible(cmd);
break;
@@ -246,6 +250,11 @@ internal partial class Clyde
nint Window
) : CmdBase;
private sealed record CmdWinSetSize(
nint Window,
int W, int H
) : CmdBase;
private sealed record CmdWinSetVisible(
nint Window,
bool Visible

View File

@@ -336,11 +336,22 @@ internal partial class Clyde
_sawmill.Warning("WindowSetMonitor not implemented on SDL2");
}
public void WindowSetSize(WindowReg window, Vector2i size)
{
SendCmd(new CmdWinSetSize(WinPtr(window), size.X, size.Y));
}
public void WindowSetVisible(WindowReg window, bool visible)
{
window.IsVisible = visible;
SendCmd(new CmdWinSetVisible(WinPtr(window), visible));
}
private static void WinThreadWinSetSize(CmdWinSetSize cmd)
{
SDL_SetWindowSize(cmd.Window, cmd.W, cmd.H);
}
private static void WinThreadWinSetVisible(CmdWinSetVisible cmd)
{
if (cmd.Visible)

View File

@@ -14,7 +14,7 @@ namespace Robust.Client.Graphics
WindowId Id { get; }
IRenderTarget RenderTarget { get; }
string Title { get; set; }
Vector2i Size { get; }
Vector2i Size { get; set; }
bool IsFocused { get; }
bool IsMinimized { get; }
bool IsVisible { get; set; }

View File

@@ -0,0 +1,86 @@
using System;
using System.IO;
using System.Security.Cryptography;
using Microsoft.Win32;
using Robust.Client.Utility;
using Robust.Shared.IoC;
using Robust.Shared.Network;
namespace Robust.Client.HWId;
internal sealed class BasicHWId : IHWId
{
[Dependency] private readonly IGameControllerInternal _gameController = default!;
public const int LengthHwid = 32;
public byte[] GetLegacy()
{
if (OperatingSystem.IsWindows())
return GetWindowsHWid("Hwid");
return [];
}
public byte[] GetModern()
{
byte[] raw;
if (OperatingSystem.IsWindows())
raw = GetWindowsHWid("Hwid2");
else
raw = GetFileHWid();
return [0, ..raw];
}
private static byte[] GetWindowsHWid(string keyName)
{
const string keyPath = @"HKEY_CURRENT_USER\SOFTWARE\Space Wizards\Robust";
var regKey = Registry.GetValue(keyPath, keyName, null);
if (regKey is byte[] { Length: LengthHwid } bytes)
return bytes;
var newId = new byte[LengthHwid];
RandomNumberGenerator.Fill(newId);
Registry.SetValue(
keyPath,
keyName,
newId,
RegistryValueKind.Binary);
return newId;
}
private byte[] GetFileHWid()
{
var path = UserDataDir.GetRootUserDataDir(_gameController);
var hwidPath = Path.Combine(path, ".hwid");
var value = ReadHWidFile(hwidPath);
if (value != null)
return value;
value = RandomNumberGenerator.GetBytes(LengthHwid);
File.WriteAllBytes(hwidPath, value);
return value;
}
private static byte[]? ReadHWidFile(string path)
{
try
{
var value = File.ReadAllBytes(path);
if (value.Length == LengthHwid)
return value;
}
catch (FileNotFoundException)
{
// First time the file won't exist.
}
return null;
}
}

View File

@@ -26,5 +26,20 @@ public interface IGameController
/// This exists to give content module more control over tick updating.
/// </summary>
event Action<FrameEventArgs>? TickUpdateOverride;
/// <summary>
/// Get the games Title, if Options.DefaultWindowTitle or if defaultWindowTitle is not set in the manifest.yml, it will default to RobustToolbox.
/// </summary>
string GameTitle();
/// <summary>
/// Get the games Window Icon set, if Options.WindowIconSet or if windowIconSet is not set in the manifest.yml, it will default to an empty string.
/// </summary>
string WindowIconSet();
/// <summary>
/// Get the games Splash Logo, if Options.SplashLogo or if splashLogo is not set in the manifest.yml, it will default to an empty string.
/// </summary>
string SplashLogo();
}

View File

@@ -156,7 +156,6 @@ public sealed partial class PhysicsSystem
if (activeA == false && activeB == false)
{
contact.IsTouching = false;
continue;
}

View File

@@ -261,7 +261,6 @@ namespace Robust.Client.Player
// This is a new userid, so we create a new session.
DebugTools.Assert(state.UserId != LocalPlayer?.UserId);
var newSession = (ICommonSessionInternal)CreateAndAddSession(state.UserId, state.Name);
newSession.SetPing(state.Ping);
SetStatus(newSession, state.Status);
SetAttachedEntity(newSession, controlled, out _, true);
dirty = true;
@@ -271,7 +270,6 @@ namespace Robust.Client.Player
// Check if the data is actually different
if (session.Name == state.Name
&& session.Status == state.Status
&& session.Ping == state.Ping
&& session.AttachedEntity == controlled)
{
continue;
@@ -280,7 +278,6 @@ namespace Robust.Client.Player
dirty = true;
var local = (ICommonSessionInternal)session;
local.SetName(state.Name);
local.SetPing(state.Ping);
SetStatus(local, state.Status);
SetAttachedEntity(local, controlled, out _, true);
}

View File

@@ -23,7 +23,7 @@ public sealed partial class ReplayLoadManager
// Scratch data used by UpdateEntityStates.
// Avoids copying changes for every change to an entity between checkpoints, instead copies once per checkpoint on
// first change. We can also use this to avoid building a dictionary of ComponentChange inside the inner loop.
private class UpdateScratchData
private sealed class UpdateScratchData
{
public Dictionary<ushort, ComponentChange> Changes;
public EntityState lastChange;

View File

@@ -52,7 +52,7 @@ public sealed partial class ReplayLoadManager
var uncompressedSize = BitConverter.ToInt32(intBuf);
var decompressedStream = new MemoryStream(uncompressedSize);
decompressStream.CopyTo(decompressedStream, uncompressedSize);
decompressStream.CopyTo(decompressedStream);
decompressedStream.Position = 0;
DebugTools.Assert(uncompressedSize == decompressedStream.Length);

View File

@@ -48,7 +48,10 @@ public interface IResourceCache : IResourceManager
event Action<TextureLoadedEventArgs> OnRawTextureLoaded;
event Action<RsiLoadedEventArgs> OnRsiLoaded;
[Obsolete("Fetch these through IoC directly instead")]
IClyde Clyde { get; }
[Obsolete("Fetch these through IoC directly instead")]
IFontManager FontManager { get; }
}

View File

@@ -762,7 +762,23 @@ namespace Robust.Client.UserInterface
throw new InvalidOperationException("The provided control is not a direct child of this control.");
}
_orderedChildren.Remove(child);
var childIndex = _orderedChildren.IndexOf(child);
RemoveChild(childIndex);
}
/// <summary>
/// Removes the child at a specific index from this control.
/// </summary>
/// <param name="childIndex">The index of the child to remove.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if the provided child index is out of range
/// </exception>
public void RemoveChild(int childIndex)
{
DebugTools.Assert(!Disposed, "Control has been disposed.");
var child = _orderedChildren[childIndex];
_orderedChildren.RemoveAt(childIndex);
child.Parent = null;

View File

@@ -7,6 +7,8 @@ using Robust.Client.Placement;
using Robust.Client.ResourceManagement;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
@@ -19,9 +21,9 @@ namespace Robust.Client.UserInterface.Controllers.Implementations;
public sealed class EntitySpawningUIController : UIController
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPlacementManager _placement = default!;
[Dependency] private readonly IPrototypeManager _prototypes = default!;
[Dependency] private readonly IResourceCache _resources = default!;
private EntitySpawnWindow? _window;
private readonly List<EntityPrototype> _shownEntities = new();
@@ -193,6 +195,9 @@ public sealed class EntitySpawningUIController : UIController
_window.SelectedButton = null;
searchStr = searchStr?.ToLowerInvariant();
var categoryFilter = _cfg.GetCVar(CVars.EntitiesCategoryFilter);
_prototypes.TryIndex<EntityCategoryPrototype>(categoryFilter, out var filter);
foreach (var prototype in _prototypes.EnumeratePrototypes<EntityPrototype>())
{
if (prototype.Abstract)
@@ -205,6 +210,11 @@ public sealed class EntitySpawningUIController : UIController
continue;
}
if (filter is not null && !prototype.Categories.Contains(filter))
{
continue;
}
if (searchStr != null && !DoesEntityMatchSearch(prototype, searchStr))
{
continue;

View File

@@ -27,6 +27,7 @@ public sealed class TileSpawningUIController : UIController
private readonly List<ITileDefinition> _shownTiles = new();
private bool _clearingTileSelections;
private bool _eraseTile;
public override void Initialize()
{
@@ -35,6 +36,37 @@ public sealed class TileSpawningUIController : UIController
_placement.PlacementChanged += ClearTileSelection;
}
private void StartTilePlacement(int tileType)
{
var newObjInfo = new PlacementInformation
{
PlacementOption = "AlignTileAny",
TileType = tileType,
Range = 400,
IsTile = true
};
_placement.BeginPlacing(newObjInfo);
}
private void OnTileEraseToggled(ButtonToggledEventArgs args)
{
if (_window == null || _window.Disposed)
return;
_placement.Clear();
if (args.Pressed)
{
_eraseTile = true;
StartTilePlacement(0);
}
else
_eraseTile = false;
args.Button.Pressed = args.Pressed;
}
public void ToggleWindow()
{
EnsureWindow();
@@ -60,6 +92,8 @@ public sealed class TileSpawningUIController : UIController
_window.SearchBar.OnTextChanged += OnTileSearchChanged;
_window.TileList.OnItemSelected += OnTileItemSelected;
_window.TileList.OnItemDeselected += OnTileItemDeselected;
_window.EraseButton.Pressed = _eraseTile;
_window.EraseButton.OnToggled += OnTileEraseToggled;
BuildTileList();
}
@@ -76,6 +110,7 @@ public sealed class TileSpawningUIController : UIController
_clearingTileSelections = true;
_window.TileList.ClearSelected();
_clearingTileSelections = false;
_window.EraseButton.Pressed = false;
}
private void OnTileClearPressed(ButtonEventArgs args)
@@ -102,16 +137,7 @@ public sealed class TileSpawningUIController : UIController
private void OnTileItemSelected(ItemList.ItemListSelectedEventArgs args)
{
var definition = _shownTiles[args.ItemIndex];
var newObjInfo = new PlacementInformation
{
PlacementOption = "AlignTileAny",
TileType = definition.TileId,
Range = 400,
IsTile = true
};
_placement.BeginPlacing(newObjInfo);
StartTilePlacement(definition.TileId);
}
private void OnTileItemDeselected(ItemList.ItemListDeselectedEventArgs args)

View File

@@ -26,6 +26,7 @@ namespace Robust.Client.UserInterface.Controls
private bool _enableAllKeybinds;
private ButtonGroup? _group;
private bool _toggleMode;
private bool _muteSounds;
/// <summary>
/// Specifies the group this button belongs to.
@@ -135,7 +136,8 @@ namespace Robust.Client.UserInterface.Controls
if (Pressed != value)
return;
UserInterfaceManager.ClickSound();
if (!MuteSounds)
UserInterfaceManager.ClickSound();
}
/// <summary>
@@ -199,6 +201,16 @@ namespace Robust.Client.UserInterface.Controls
}
}
/// <summary>
/// If <c>true</c>, this button will not emit sounds when the mouse is pressed or hovered over.
/// </summary>
[ViewVariables]
public bool MuteSounds
{
get => _muteSounds;
set => _muteSounds = value;
}
/// <summary>
/// Fired when the button is pushed down by the mouse.
/// </summary>
@@ -298,7 +310,8 @@ namespace Robust.Client.UserInterface.Controls
}
else
{
UserInterfaceManager.ClickSound();
if (!MuteSounds)
UserInterfaceManager.ClickSound();
}
OnPressed?.Invoke(buttonEventArgs);
@@ -353,7 +366,7 @@ namespace Robust.Client.UserInterface.Controls
{
base.MouseEntered();
if (!Disabled)
if (!Disabled && !MuteSounds)
{
UserInterfaceManager.HoverSound();
}

View File

@@ -53,6 +53,15 @@ namespace Robust.Client.UserInterface.Controls
private TimeSpan? _lastClickTime;
private Vector2? _lastClickPosition;
// Keep track of the frame on which we got focus, so we can implement SelectAllOnFocus properly.
// Otherwise, there's no way to keep track of whether the KeyDown is the one that focused the text box,
// to avoid text selection stomping on the behavior.
// This isn't a great way to do it.
// A better fix would be to annotate all input events with some unique sequence ID,
// and expose the input event that focused the control in KeyboardFocusEntered.
// But that sounds like a refactor I'm not doing today.
private uint _focusedOnFrame;
private bool IsPlaceHolderVisible => !(HidePlaceHolderOnFocus && HasKeyboardFocus()) && string.IsNullOrEmpty(_text) && _placeHolder != null;
public event Action<LineEditEventArgs>? OnTextChanged;
@@ -190,6 +199,11 @@ namespace Robust.Client.UserInterface.Controls
public bool IgnoreNext { get; set; }
/// <summary>
/// If true, all the text in the LineEdit will be automatically selected whenever it is focused.
/// </summary>
public bool SelectAllOnFocus { get; set; }
private (int start, int length)? _imeData;
@@ -709,7 +723,7 @@ namespace Robust.Client.UserInterface.Controls
args.Handle();
}
else
else if (!(SelectAllOnFocus && _focusedOnFrame == _timing.CurFrame))
{
_lastClickTime = _timing.RealTime;
_lastClickPosition = args.PointerLocation.Position;
@@ -868,6 +882,13 @@ namespace Robust.Client.UserInterface.Controls
{
_clyde.TextInputStart();
}
_focusedOnFrame = _timing.CurFrame;
if (SelectAllOnFocus)
{
CursorPosition = _text.Length;
SelectionStart = 0;
}
}
protected internal override void KeyboardFocusExited()

View File

@@ -1,6 +1,5 @@
using System;
using System.ComponentModel;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.IoC;
@@ -33,9 +32,9 @@ namespace Robust.Client.UserInterface.Controls
public IClydeWindow? Owner { get; set; }
/// <summary>
/// Whether the window is currently open.
/// Whether the window is created and currently open.
/// </summary>
public bool IsOpen => ClydeWindow != null;
public bool IsOpen => ClydeWindow?.IsVisible ?? false;
/// <summary>
/// The title of the window.
@@ -97,12 +96,13 @@ namespace Robust.Client.UserInterface.Controls
}
/// <summary>
/// Show the window to the user.
/// Create the window if not already created.
/// This window is not visible by default, call <see cref="Show"/> to display it.
/// </summary>
public void Show()
public IClydeWindow Create()
{
if (IsOpen)
return;
if (ClydeWindow != null)
return ClydeWindow;
var parameters = new WindowCreateParameters();
@@ -127,6 +127,7 @@ namespace Robust.Client.UserInterface.Controls
parameters.Styles = WindowStyles;
parameters.Owner = Owner;
parameters.StartupLocation = StartupLocation;
parameters.Visible = false;
ClydeWindow = _clyde.CreateWindow(parameters);
ClydeWindow.RequestClosed += OnWindowRequestClosed;
@@ -136,6 +137,19 @@ namespace Robust.Client.UserInterface.Controls
_root = UserInterfaceManager.CreateWindowRoot(ClydeWindow);
_root.AddChild(this);
// Resize the window by our UIScale
ClydeWindow.Size = new((int)(ClydeWindow.Size.X * UIScale), (int)(ClydeWindow.Size.Y * UIScale));
return ClydeWindow;
}
/// <summary>
/// Show the window to the user, creating it if necessary
/// </summary>
public void Show()
{
ClydeWindow = Create();
ClydeWindow.IsVisible = true;
Shown();
}
@@ -179,7 +193,7 @@ namespace Robust.Client.UserInterface.Controls
private void OnWindowResized(WindowResizedEventArgs obj)
{
SetSize = obj.NewSize;
SetSize = obj.NewSize / UIScale;
}
private void RealClosed()

View File

@@ -127,6 +127,7 @@ namespace Robust.Client.UserInterface.Controls
var style = _getStyleBox();
var font = _getFont();
var lineSeparation = font.GetLineSeparation(UIScale);
style?.Draw(handle, PixelSizeBox, UIScale);
var contentBox = _getContentBox();
@@ -141,18 +142,26 @@ namespace Robust.Client.UserInterface.Controls
{
if (entryOffset + entry.Height < 0)
{
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
// Controls within the entry are the children of this control, which means they are drawn separately
// after this Draw call, so we have to mark them as invisible to prevent them from being drawn.
//
// An alternative option is to ensure that the control position updating logic in entry.Draw is always
// run, and then setting RectClipContent = true to use scissor box testing to handle the controls
// visibility
entry.HideControls();
entryOffset += entry.Height + lineSeparation;
continue;
}
if (entryOffset > contentBox.Height)
{
break;
entry.HideControls();
continue;
}
entry.Draw(_tagManager, handle, font, contentBox, entryOffset, context, UIScale);
entryOffset += entry.Height + font.GetLineSeparation(UIScale);
entryOffset += entry.Height + lineSeparation;
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Numerics;
using JetBrains.Annotations;
using Robust.Client.Graphics;

View File

@@ -245,7 +245,7 @@ namespace Robust.Client.UserInterface.Controls
var offset = SpriteOffset
? Vector2.Zero
: - (-_eyeRotation).RotateVec(sprite.Offset) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
: - (-_eyeRotation).RotateVec(sprite.Offset * _scale) * new Vector2(1, -1) * EyeManager.PixelsPerMeter;
var position = PixelSize / 2 + offset * stretch * UIScale;
var scale = Scale * UIScale * stretch;

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.Graphics;
using Robust.Shared.Input;
@@ -21,6 +22,8 @@ namespace Robust.Client.UserInterface.Controls
private int _currentTab;
private bool _tabsVisible = true;
// The right-most coordinate of each tab header
private List<float> _tabRight = new();
public int CurrentTab
{
@@ -157,11 +160,14 @@ namespace Robust.Client.UserInterface.Controls
var headerOffset = 0f;
_tabRight.Clear();
// Then, draw the tabs.
for (var i = 0; i < ChildCount; i++)
{
if (!GetTabVisible(i))
{
_tabRight.Add(headerOffset);
continue;
}
@@ -214,6 +220,8 @@ namespace Robust.Client.UserInterface.Controls
}
headerOffset += boxAdvance;
// Remember the right-most point of this tab, for testing clicked areas
_tabRight.Add(headerOffset);
}
}
@@ -283,46 +291,17 @@ namespace Robust.Client.UserInterface.Controls
args.Handle();
var relX = args.RelativePixelPosition.X;
var font = _getFont();
var boxActive = _getTabBoxActive();
var boxInactive = _getTabBoxInactive();
var headerOffset = 0f;
float tabLeft = 0;
for (var i = 0; i < ChildCount; i++)
{
if (!GetTabVisible(i))
if (relX > tabLeft && relX <= _tabRight[i])
{
continue;
}
var title = GetActualTabTitle(i);
var titleLength = 0;
// Get string length.
foreach (var rune in title.EnumerateRunes())
{
if (!font.TryGetCharMetrics(rune, UIScale, out var metrics))
{
continue;
}
titleLength += metrics.Advance;
}
var active = _currentTab == i;
var box = active ? boxActive : boxInactive;
var boxAdvance = titleLength + (box?.MinimumSize.X ?? 0);
if (headerOffset < relX && headerOffset + boxAdvance > relX)
{
// Got em.
CurrentTab = i;
return;
}
headerOffset += boxAdvance;
// Next tab starts here
tabLeft = _tabRight[i];
}
}

View File

@@ -13,7 +13,7 @@
</ScrollContainer>
<BoxContainer Orientation="Horizontal">
<Button Name="ReplaceButton" Access="Public" ToggleMode="True" Text="{Loc entity-spawn-window-replace-button-text}"/>
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc entity-spawn-window-erase-button-text}"/>
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc window-erase-button-text}"/>
<OptionButton Name="OverrideMenu" Access="Public" HorizontalExpand="True" ToolTip="{Loc entity-spawn-window-override-menu-tooltip}" />
</BoxContainer>
<Label Name="RotationLabel" Access="Public"/>

View File

@@ -1,10 +0,0 @@
using System;
namespace Robust.Client.UserInterface.CustomControls;
[Obsolete("Use DefaultWindow instead")]
[Virtual]
public class SS14Window : DefaultWindow
{
}

View File

@@ -1,6 +1,6 @@
<TileSpawnWindow
xmlns="https://spacestation14.io"
Title="Place Tiles"
Title="{Loc tile-spawn-window-title}"
SetSize="300 300"
MinSize="300 200">
<BoxContainer Orientation="Vertical">
@@ -9,5 +9,8 @@
<Button Name="ClearButton" Access="Public" Text="Clear"/>
</BoxContainer>
<ItemList Name="TileList" Access="Public" VerticalExpand="True"/>
<BoxContainer Orientation="Horizontal">
<Button Name="EraseButton" Access="Public" ToggleMode="True" Text="{Loc window-erase-button-text}"/>
</BoxContainer>
</BoxContainer>
</TileSpawnWindow>

View File

@@ -118,9 +118,6 @@ namespace Robust.Client.UserInterface
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
continue;
if (ProcessRune(ref this, new Rune(' '), out breakLine))
continue;
control.Measure(new Vector2(Width, Height));
var desiredSize = control.DesiredPixelSize;
@@ -167,6 +164,16 @@ namespace Robust.Client.UserInterface
}
}
internal readonly void HideControls()
{
if (_tagControls == null)
return;
foreach (var control in _tagControls.Values)
{
control.Visible = false;
}
}
public readonly void Draw(
MarkupTagManager tagManager,
DrawingHandleScreen handle,
@@ -216,8 +223,11 @@ namespace Robust.Client.UserInterface
if (_tagControls == null || !_tagControls.TryGetValue(nodeIndex, out var control))
continue;
var invertedScale = 1f / uiScale;
// Controls may have been previously hidden via HideControls due to being "out-of frame".
// If this ever gets replaced with RectClipContents / scissor box testing, this can be removed.
control.Visible = true;
var invertedScale = 1f / uiScale;
control.Position = new Vector2(baseLine.X * invertedScale, (baseLine.Y - defaultFont.GetAscent(uiScale)) * invertedScale);
control.Measure(new Vector2(Width, Height));
var advanceX = control.DesiredPixelSize.X;

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Shared;
using Robust.Shared.Map;
using Robust.Shared.Utility;
@@ -28,10 +29,11 @@ internal sealed partial class UserInterfaceManager
{
MouseFilter = Control.MouseFilterMode.Ignore,
HorizontalAlignment = Control.HAlignment.Stretch,
VerticalAlignment = Control.VAlignment.Stretch,
UIScaleSet = window.ContentScale.X
VerticalAlignment = Control.VAlignment.Stretch
};
newRoot.UIScaleSet = CalculateAutoScale(newRoot);
_roots.Add(newRoot);
_windowsToRoot.Add(window.Id, newRoot);

View File

@@ -58,16 +58,16 @@ internal sealed class XamlHotReloadManager : IXamlHotReloadManager
var watcher = new FileSystemWatcher(location)
{
IncludeSubdirectories = true,
NotifyFilter = NotifyFilters.LastWrite,
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
};
watcher.Changed += (_, args) =>
void OnWatcherEvent(object sender, FileSystemEventArgs args)
{
switch (args.ChangeType)
{
case WatcherChangeTypes.Renamed:
case WatcherChangeTypes.Deleted:
return;
case WatcherChangeTypes.Renamed:
case WatcherChangeTypes.Created:
case WatcherChangeTypes.Changed:
case WatcherChangeTypes.All:
@@ -98,7 +98,10 @@ internal sealed class XamlHotReloadManager : IXamlHotReloadManager
_xamlProxyManager.SetImplementation(resourceFileName, newText);
});
};
}
watcher.Changed += OnWatcherEvent;
watcher.Renamed += OnWatcherEvent;
watcher.EnableRaisingEvents = true;
return watcher;
}

View File

@@ -1,7 +1,6 @@
using System;
using System.IO;
using JetBrains.Annotations;
using Robust.Shared.IoC;
namespace Robust.Client.Utility
{
@@ -9,6 +8,12 @@ namespace Robust.Client.Utility
{
[Pure]
public static string GetUserDataDir(IGameControllerInternal gameController)
{
return Path.Combine(GetRootUserDataDir(gameController), "data");
}
[Pure]
public static string GetRootUserDataDir(IGameControllerInternal gameController)
{
string appDataDir;
@@ -30,8 +35,7 @@ namespace Robust.Client.Utility
appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
#endif
return Path.Combine(appDataDir, gameController.Options.UserDataDirectoryName, "data");
return Path.Combine(appDataDir, gameController.Options.UserDataDirectoryName);
}
}
}

View File

@@ -35,6 +35,7 @@ public static class Diagnostics
public const string IdDataFieldNoVVReadWrite = "RA0029";
public const string IdUseNonGenericVariant = "RA0030";
public const string IdPreferOtherType = "RA0031";
public const string IdDuplicateDependency = "RA0032";
public static SuppressionDescriptor MeansImplicitAssignment =>
new SuppressionDescriptor("RADC1000", "CS0649", "Marked as implicitly assigned.");

View File

@@ -66,32 +66,26 @@ public sealed partial class AudioSystem : SharedAudioSystem
private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter)
{
var count = filter.Count;
DebugTools.Assert(component.IncludedEntities == null);
component.IncludedEntities = new();
if (count == 0)
if (filter.Count == 0)
return;
_pvs.AddSessionOverrides(uid, filter);
var ents = new HashSet<EntityUid>(count);
foreach (var session in filter.Recipients)
{
var ent = session.AttachedEntity;
if (ent == null)
continue;
ents.Add(ent.Value);
if (session.AttachedEntity is {} ent)
component.IncludedEntities.Add(ent);
}
DebugTools.Assert(component.IncludedEntities == null);
component.IncludedEntities = ents;
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
var entity = SetupAudio(filename, audioParams);
AddAudioFilter(entity, entity.Comp, playerFilter);
entity.Comp.Global = true;
@@ -175,6 +169,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
return (entity, entity.Comp);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayLocal(
SoundSpecifier? sound,
EntityUid source,
EntityUid? soundInitiator,
AudioParams? audioParams = null
)
{
return null;
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null)
{

View File

@@ -86,7 +86,7 @@ namespace Robust.Server
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
[Dependency] private readonly IWatchdogApiInternal _watchdogApi = default!;
[Dependency] private readonly HubManager _hubManager = default!;
[Dependency] private readonly IScriptHost _scriptHost = default!;
[Dependency] private readonly IMetricsManagerInternal _metricsManager = default!;
@@ -566,7 +566,7 @@ namespace Robust.Server
// Don't start the main loop. This only works if a reason is passed to Shutdown(...)
if (_shutdownReason != null)
{
_logger.Fatal("Shutdown has been requested before the main loop has been started, complying.");
_logger.Fatal("Shutdown has been requested before the main loop has been started, complying. Reason: {0}", _shutdownReason);
}
else _mainLoop.Run();

View File

@@ -1,335 +0,0 @@
// MIT License
// Copyright (c) 2019 Erin Catto
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
using System;
using System.Numerics;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Collision.Shapes;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Controllers;
using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Physics.Dynamics.Joints;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Timing;
namespace Robust.Server.Console.Commands
{
/*
* I didn't use blueprints because this is way easier to iterate upon as I can shit out testbed upon testbed on new maps
* and never have to leave my debugger.
*/
/// <summary>
/// Copies of Box2D's physics testbed for debugging.
/// </summary>
public sealed class TestbedCommand : LocalizedCommands
{
[Dependency] private readonly IEntityManager _ent = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "testbed";
public override void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 2)
{
shell.WriteError("Require 2 args for testbed!");
return;
}
if (!int.TryParse(args[0], out var mapInt))
{
shell.WriteError($"Unable to parse map {args[0]}");
return;
}
var mapId = new MapId(mapInt);
if (!_map.MapExists(mapId))
{
shell.WriteError($"map {args[0]} does not exist");
return;
}
if (shell.Player == null)
{
shell.WriteError("No player found");
return;
}
Action testbed;
SetupPlayer(mapId, shell);
switch (args[1])
{
case "boxstack":
testbed = () => CreateBoxStack(mapId);
break;
case "circlestack":
testbed = () => CreateCircleStack(mapId);
break;
case "pyramid":
testbed = () => CreatePyramid(mapId);
break;
case "tumbler":
testbed = () => CreateTumbler(mapId);
break;
default:
shell.WriteError($"testbed {args[0]} not found!");
return;
}
Timer.Spawn(1000, () =>
{
if (!_map.MapExists(mapId)) return;
testbed();
});
shell.WriteLine($"Testbed on map {mapId}");
}
private void SetupPlayer(MapId mapId, IConsoleShell shell)
{
_map.SetMapPaused(mapId, false);
var mapUid = _map.GetMapEntityIdOrThrow(mapId);
_ent.System<Gravity2DController>().SetGravity(mapUid, new Vector2(0, -9.8f));
shell.ExecuteCommand("aghost");
shell.ExecuteCommand($"tp 0 0 {mapId}");
shell.RemoteExecuteCommand($"physics shapes");
return;
}
private void CreateBoxStack(MapId mapId)
{
var physics = _ent.System<SharedPhysicsSystem>();
var fixtures = _ent.System<FixtureSystem>();
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
var columnCount = 1;
var rowCount = 15;
PolygonShape shape;
for (var j = 0; j < columnCount; j++)
{
for (var i = 0; i < rowCount; i++)
{
var x = 0.0f;
var boxUid = _ent.SpawnEntity(null,
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 1.1f * i), mapId));
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
shape = new PolygonShape();
shape.SetAsBox(0.5f, 0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true), body: box);
physics.WakeBody(boxUid, body: box);
}
}
physics.WakeBody(groundUid, body: ground);
}
private void CreateCircleStack(MapId mapId)
{
var physics = _ent.System<SharedPhysicsSystem>();
var fixtures = _ent.System<FixtureSystem>();
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
var vertical = new EdgeShape(new Vector2(20, 0), new Vector2(20, 20));
fixtures.CreateFixture(groundUid, "fix2", new Fixture(vertical, 2, 2, true), body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
var columnCount = 1;
var rowCount = 15;
PhysShapeCircle shape;
for (var j = 0; j < columnCount; j++)
{
for (var i = 0; i < rowCount; i++)
{
var x = 0.0f;
var boxUid = _ent.SpawnEntity(null,
new MapCoordinates(new Vector2(xs[j] + x, 0.55f + 2.1f * i), mapId));
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
shape = new PhysShapeCircle(0.5f);
physics.SetFixedRotation(boxUid, false, body: box);
// TODO: Need to detect shape and work out if we need to use fixedrotation
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f));
physics.WakeBody(boxUid, body: box);
}
}
physics.WakeBody(groundUid, body: ground);
}
private void CreatePyramid(MapId mapId)
{
const byte count = 20;
// Setup ground
var physics = _ent.System<SharedPhysicsSystem>();
var fixtures = _ent.System<FixtureSystem>();
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(40, 0), new Vector2(-40, 0));
fixtures.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 2, 2, true), body: ground);
physics.WakeBody(groundUid, body: ground);
// Setup boxes
float a = 0.5f;
PolygonShape shape = new();
shape.SetAsBox(a, a);
var x = new Vector2(-7.0f, 0.75f);
Vector2 y;
Vector2 deltaX = new Vector2(0.5625f, 1.25f);
Vector2 deltaY = new Vector2(1.125f, 0.0f);
for (var i = 0; i < count; ++i)
{
y = x;
for (var j = i; j < count; ++j)
{
var boxUid = _ent.SpawnEntity(null, new MapCoordinates(y, mapId));
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 5f), body: box);
y += deltaY;
physics.WakeBody(boxUid, body: box);
}
x += deltaX;
}
}
private void CreateTumbler(MapId mapId)
{
var physics = _ent.System<SharedPhysicsSystem>();
var fixtures = _ent.System<FixtureSystem>();
var joints = _ent.System<SharedJointSystem>();
var groundUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 0f, mapId));
var ground = _ent.AddComponent<PhysicsComponent>(groundUid);
// Due to lookup changes fixtureless bodies are invalid, so
var cShape = new PhysShapeCircle(1f);
fixtures.CreateFixture(groundUid, "fix1", new Fixture(cShape, 0, 0, false));
var bodyUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var body = _ent.AddComponent<PhysicsComponent>(bodyUid);
physics.SetBodyType(bodyUid, BodyType.Dynamic, body: body);
physics.SetSleepingAllowed(bodyUid, body, false);
physics.SetFixedRotation(bodyUid, false, body: body);
// TODO: Box2D just deref, bleh shape structs someday
var shape1 = new PolygonShape();
shape1.SetAsBox(0.5f, 10.0f, new Vector2(10.0f, 0.0f), 0.0f);
fixtures.CreateFixture(bodyUid, "fix1", new Fixture(shape1, 2, 0, true, 20f));
var shape2 = new PolygonShape();
shape2.SetAsBox(0.5f, 10.0f, new Vector2(-10.0f, 0.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix2", new Fixture(shape2, 2, 0, true, 20f));
var shape3 = new PolygonShape();
shape3.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, 10.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix3", new Fixture(shape3, 2, 0, true, 20f));
var shape4 = new PolygonShape();
shape4.SetAsBox(10.0f, 0.5f, new Vector2(0.0f, -10.0f), 0f);
fixtures.CreateFixture(bodyUid, "fix4", new Fixture(shape4, 2, 0, true, 20f));
physics.WakeBody(groundUid, body: ground);
physics.WakeBody(bodyUid, body: body);
var revolute = joints.CreateRevoluteJoint(groundUid, bodyUid);
revolute.LocalAnchorA = new Vector2(0f, 10f);
revolute.LocalAnchorB = new Vector2(0f, 0f);
revolute.ReferenceAngle = 0f;
revolute.MotorSpeed = 0.05f * MathF.PI;
revolute.MaxMotorTorque = 100000000f;
revolute.EnableMotor = true;
// Box2D has this as 800 which is jesus christo.
// Wouldn't recommend higher than 100 in debug and higher than 300 on release unless
// you really want a profile.
var count = 300;
for (var i = 0; i < count; i++)
{
Timer.Spawn(i * 20, () =>
{
if (!_map.MapExists(mapId)) return;
var boxUid = _ent.SpawnEntity(null, new MapCoordinates(0f, 10f, mapId));
var box = _ent.AddComponent<PhysicsComponent>(boxUid);
physics.SetBodyType(boxUid, BodyType.Dynamic, body: box);
physics.SetFixedRotation(boxUid, false, body: box);
var shape = new PolygonShape();
shape.SetAsBox(0.125f, 0.125f);
fixtures.CreateFixture(boxUid, "fix1", new Fixture(shape, 2, 2, true, 0.0625f), body: box);
physics.WakeBody(boxUid, body: box);
});
}
}
}
}

View File

@@ -162,7 +162,6 @@ namespace Robust.Server.Console
{
var message = new MsgConCmdReg();
var counter = 0;
var toolshedCommands = _toolshed.DefaultEnvironment.AllCommands().ToArray();
message.Commands = new List<MsgConCmdReg.Command>(AvailableCommands.Count + toolshedCommands.Length);
var commands = new HashSet<string>();

View File

@@ -53,20 +53,21 @@ namespace Robust.Server.GameObjects
var query = AllEntityQuery<MapGridComponent>();
while (query.MoveNext(out var uid, out var grid))
{
if (!GridEmpty(grid)) continue;
if (!GridEmpty((uid, grid)))
continue;
toDelete.Add(uid);
}
foreach (var uid in toDelete)
{
MapManager.DeleteGrid(uid);
EntityManager.DeleteEntity(uid);
}
}
}
private bool GridEmpty(MapGridComponent grid)
private bool GridEmpty(Entity<MapGridComponent> entity)
{
return !(grid.GetAllTiles().Any());
return !(GetAllTiles(entity, entity).Any());
}
private void HandleGridEmpty(EntityUid uid, MapGridComponent component, EmptyGridEvent args)
@@ -74,7 +75,7 @@ namespace Robust.Server.GameObjects
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
return;
MapManager.DeleteGrid(args.GridId);
EntityManager.DeleteEntity(args.GridId);
}
}
}

View File

@@ -141,9 +141,10 @@ internal sealed class PvsChunk
{
// TODO ARCH multi-component queries
if (!meta.TryGetComponent(child, out var childMeta)
|| !xform.TryGetComponent(child, out var childXform))
|| !xform.TryGetComponent(child, out var childXform)
|| childMeta.EntityLifeStage >= EntityLifeStage.Terminating)
{
DebugTools.Assert($"PVS chunk contains a deleted entity: {child}");
DebugTools.Assert($"PVS chunk contains a delete or terminating entity: {child}");
MarkDirty();
return false;
}
@@ -188,9 +189,10 @@ internal sealed class PvsChunk
{
// TODO ARCH multi-component queries
if (!meta.TryGetComponent(child, out var childMeta)
|| !xform.TryGetComponent(child, out var childXform))
|| !xform.TryGetComponent(child, out var childXform)
|| childMeta.EntityLifeStage >= EntityLifeStage.Terminating)
{
DebugTools.Assert($"PVS chunk contains a deleted entity: {child}");
DebugTools.Assert($"PVS chunk contains a delete or terminating entity: {child}");
MarkDirty();
return false;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -190,6 +191,15 @@ internal struct PvsMetadata
private byte Pad0;
public uint Marker;
#endif
[Conditional("DEBUG")]
public void Validate(MetaDataComponent comp)
{
DebugTools.AssertEqual(NetEntity, comp.NetEntity);
DebugTools.AssertEqual(VisMask, comp.VisibilityMask);
DebugTools.AssertEqual(LifeStage, comp.EntityLifeStage);
DebugTools.Assert(LastModifiedTick == comp.EntityLastModifiedTick || LastModifiedTick.Value == 0);
}
}
[StructLayout(LayoutKind.Sequential, Size = 16)]

View File

@@ -250,13 +250,6 @@ public sealed class PvsOverrideSystem : EntitySystem
AddSessionOverride(uid.Value, session);
}
[Obsolete("Use variant that takes in an EntityUid")]
public void AddSessionOverrides(NetEntity entity, Filter filter, bool removeExistingOverride = true)
{
if (TryGetEntity(entity, out var uid))
AddSessionOverrides(uid.Value, filter);
}
[Obsolete("Don't use this, clear specific overrides")]
public void ClearOverride(NetEntity entity)
{

View File

@@ -232,6 +232,7 @@ internal sealed partial class PvsSystem
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void AddEntityToChunk(EntityUid uid, MetaDataComponent meta, PvsChunkLocation location)
{
DebugTools.Assert(meta.EntityLifeStage < EntityLifeStage.Terminating);
ref var chunk = ref CollectionsMarshal.GetValueRefOrAddDefault(_chunks, location, out var existing);
if (!existing)
{

View File

@@ -245,8 +245,6 @@ internal sealed partial class PvsSystem
private void OnEntityAdded(Entity<MetaDataComponent> entity)
{
DebugTools.Assert(entity.Comp.PvsData.Index == default);
AssignEntityPointer(entity.Comp);
}
@@ -255,6 +253,7 @@ internal sealed partial class PvsSystem
/// </summary>
private void AssignEntityPointer(MetaDataComponent meta)
{
DebugTools.Assert(meta.PvsData == PvsIndex.Invalid);
if (_dataFreeListHead == PvsIndex.Invalid)
{
ExpandEntityCapacity();
@@ -267,8 +266,6 @@ internal sealed partial class PvsSystem
ref var freeLink = ref Unsafe.As<PvsMetadata, PvsMetadataFreeLink>(ref metadata);
_dataFreeListHead = freeLink.NextFree;
// TODO: re-introduce this assert.
// DebugTools.AssertEqual(((PvsMetadata*) ptr)->NetEntity, NetEntity.Invalid);
DebugTools.AssertNotEqual(meta.NetEntity, NetEntity.Invalid);
meta.PvsData = index;
@@ -287,9 +284,9 @@ internal sealed partial class PvsSystem
private void OnEntityDeleted(Entity<MetaDataComponent> entity)
{
var ptr = entity.Comp.PvsData;
entity.Comp.PvsData = default;
entity.Comp.PvsData = PvsIndex.Invalid;
if (ptr == default)
if (ptr == PvsIndex.Invalid)
return;
_incomingReturns.Add(ptr);
@@ -300,7 +297,8 @@ internal sealed partial class PvsSystem
/// </summary>
private void AfterEntityFlush()
{
DebugTools.Assert(EntityManager.EntityCount == 0);
if (EntityManager.EntityCount > 0)
throw new Exception("Cannot reset PVS data without first deleting all entities.");
ClearPvsData();
ShrinkDataMemory();

View File

@@ -49,7 +49,7 @@ namespace Robust.Server.GameStates
private void OnEntityDirty(Entity<MetaDataComponent> uid)
{
if (uid.Comp.PvsData != default)
if (uid.Comp.PvsData != PvsIndex.Invalid)
{
ref var meta = ref _metadataMemory.GetRef(uid.Comp.PvsData.Index);
meta.LastModifiedTick = uid.Comp.EntityLastModifiedTick;

View File

@@ -107,7 +107,7 @@ internal sealed partial class PvsSystem
internal void SyncMetadata(MetaDataComponent meta)
{
if (meta.PvsData == default)
if (meta.PvsData == PvsIndex.Invalid)
return;
ref var ptr = ref _metadataMemory.GetRef(meta.PvsData.Index);

View File

@@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.GameObjects;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Server.GameStates;
@@ -25,6 +26,7 @@ internal sealed partial class PvsSystem
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedGlobalOverride))
{
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
meta.Validate(ent.Meta);
if ((mask & meta.VisMask) == meta.VisMask)
AddEntity(session, ref ent, ref meta, fromTick);
}
@@ -51,6 +53,7 @@ internal sealed partial class PvsSystem
foreach (ref var ent in CollectionsMarshal.AsSpan(_cachedForceOverride))
{
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
meta.Validate(ent.Meta);
if ((mask & meta.VisMask) == meta.VisMask)
AddEntity(session, ref ent, ref meta, fromTick);
}

View File

@@ -57,6 +57,7 @@ internal sealed partial class PvsSystem
foreach (ref var ent in span)
{
ref var meta = ref _metadataMemory.GetRef(ent.Ptr.Index);
meta.Validate(ent.Meta);
if ((mask & meta.VisMask) == meta.VisMask)
AddEntity(session, ref ent, ref meta, fromTick);
}
@@ -78,8 +79,7 @@ internal sealed partial class PvsSystem
if (meta.LifeStage >= EntityLifeStage.Terminating)
{
Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}, lifestage is {meta.LifeStage}.\n{Environment.StackTrace}");
EntityManager.QueueDeleteEntity(ent.Uid);
Log.Error($"Attempted to send deleted entity: {ToPrettyString(ent.Uid)}, Meta lifestage: {ent.Meta.EntityLifeStage}, PVS lifestage: {meta.LifeStage}.\n{Environment.StackTrace}");
return;
}

View File

@@ -129,7 +129,6 @@ internal sealed partial class PvsSystem : EntitySystem
SubscribeLocalEvent<MapChangedEvent>(OnMapChanged);
SubscribeLocalEvent<GridRemovalEvent>(OnGridRemoved);
SubscribeLocalEvent<EntityTerminatingEvent>(OnEntityTerminating);
SubscribeLocalEvent<TransformComponent, TransformStartupEvent>(OnTransformStartup);
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
@@ -137,6 +136,7 @@ internal sealed partial class PvsSystem : EntitySystem
EntityManager.EntityAdded += OnEntityAdded;
EntityManager.EntityDeleted += OnEntityDeleted;
EntityManager.AfterEntityFlush += AfterEntityFlush;
EntityManager.BeforeEntityTerminating += OnEntityTerminating;
Subs.CVar(_configManager, CVars.NetPVS, SetPvs, true);
Subs.CVar(_configManager, CVars.NetMaxUpdateRange, OnViewsizeChanged, true);
@@ -162,6 +162,7 @@ internal sealed partial class PvsSystem : EntitySystem
EntityManager.EntityAdded -= OnEntityAdded;
EntityManager.EntityDeleted -= OnEntityDeleted;
EntityManager.AfterEntityFlush -= AfterEntityFlush;
EntityManager.BeforeEntityTerminating -= OnEntityTerminating;
_parallelMgr.ParallelCountChanged -= ResetParallelism;

View File

@@ -285,7 +285,7 @@ namespace Robust.Server.Physics
foreach (var index in node.Indices)
{
var tilePos = offset + index;
tileData.Add((tilePos, oldGrid.GetTileRef(tilePos).Tile));
tileData.Add((tilePos, _maps.GetTileRef(oldGridUid, oldGrid, tilePos).Tile));
}
}
@@ -355,7 +355,7 @@ namespace Robust.Server.Physics
}
// Set tiles on old grid
oldGrid.SetTiles(tileData);
_maps.SetTiles(oldGridUid, oldGrid, tileData);
GenerateSplitNodes(newGridUid, newGrid);
SendNodeDebug(newGridUid);
}
@@ -388,7 +388,7 @@ namespace Robust.Server.Physics
private void GenerateSplitNodes(EntityUid gridUid, MapGridComponent grid)
{
foreach (var chunk in grid.GetMapChunks().Values)
foreach (var chunk in _maps.GetMapChunks(gridUid, grid).Values)
{
var group = CreateNodes(gridUid, grid, chunk);
_nodes[gridUid].Add(chunk.Indices, group);
@@ -479,7 +479,7 @@ namespace Robust.Server.Physics
if (index.X == 0)
{
// Check West
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X - 1, chunk.Indices.Y), out neighborChunk) &&
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X - 1, chunk.Indices.Y), out neighborChunk) &&
TryGetNode(gridEuid, neighborChunk, new Vector2i(chunk.ChunkSize - 1, index.Y), out neighborNode))
{
chunkNode.Neighbors.Add(neighborNode);
@@ -490,7 +490,7 @@ namespace Robust.Server.Physics
if (index.Y == 0)
{
// Check South
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X, chunk.Indices.Y - 1), out neighborChunk) &&
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X, chunk.Indices.Y - 1), out neighborChunk) &&
TryGetNode(gridEuid, neighborChunk, new Vector2i(index.X, chunk.ChunkSize - 1), out neighborNode))
{
chunkNode.Neighbors.Add(neighborNode);
@@ -501,7 +501,7 @@ namespace Robust.Server.Physics
if (index.X == chunk.ChunkSize - 1)
{
// Check East
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X + 1, chunk.Indices.Y), out neighborChunk) &&
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X + 1, chunk.Indices.Y), out neighborChunk) &&
TryGetNode(gridEuid, neighborChunk, new Vector2i(0, index.Y), out neighborNode))
{
chunkNode.Neighbors.Add(neighborNode);
@@ -512,7 +512,7 @@ namespace Robust.Server.Physics
if (index.Y == chunk.ChunkSize - 1)
{
// Check North
if (grid.TryGetChunk(new Vector2i(chunk.Indices.X, chunk.Indices.Y + 1), out neighborChunk) &&
if (_maps.TryGetChunk(gridEuid, grid, new Vector2i(chunk.Indices.X, chunk.Indices.Y + 1), out neighborChunk) &&
TryGetNode(gridEuid, neighborChunk, new Vector2i(index.X, 0), out neighborNode))
{
chunkNode.Neighbors.Add(neighborNode);

View File

@@ -137,8 +137,7 @@ namespace Robust.Server.Player
{
UserId = client.UserId,
Name = client.Name,
Status = client.Status,
Ping = client.Channel!.Ping
Status = client.Status
};
list.Add(info);
}

View File

@@ -79,6 +79,7 @@ namespace Robust.Server
deps.Register<IViewVariablesManager, ServerViewVariablesManager>();
deps.Register<IServerViewVariablesInternal, ServerViewVariablesManager>();
deps.Register<IWatchdogApi, WatchdogApi>();
deps.Register<IWatchdogApiInternal, WatchdogApi>();
deps.Register<IScriptHost, ScriptHost>();
deps.Register<IMetricsManager, MetricsManager>();
deps.Register<IMetricsManagerInternal, MetricsManager>();
@@ -97,6 +98,7 @@ namespace Robust.Server
deps.Register<NetworkResourceManager>();
deps.Register<IHttpClientHolder, HttpClientHolder>();
deps.Register<UploadedContentManager>();
deps.Register<IHWId, DummyHWId>();
}
}
}

View File

@@ -24,34 +24,8 @@ namespace Robust.Server.ServerStatus
IDictionary<string, string> ResponseHeaders { get; }
bool KeepAlive { get; set; }
[Obsolete("Use async versions instead")]
T? RequestBodyJson<T>();
Task<T?> RequestBodyJsonAsync<T>();
[Obsolete("Use async versions instead")]
void Respond(
string text,
HttpStatusCode code = HttpStatusCode.OK,
string contentType = "text/plain");
[Obsolete("Use async versions instead")]
void Respond(
string text,
int code = 200,
string contentType = "text/plain");
[Obsolete("Use async versions instead")]
void Respond(
byte[] data,
HttpStatusCode code = HttpStatusCode.OK,
string contentType = "text/plain");
[Obsolete("Use async versions instead")]
void Respond(
byte[] data,
int code = 200,
string contentType = "text/plain");
Task RespondNoContentAsync();
Task RespondAsync(
@@ -74,14 +48,8 @@ namespace Robust.Server.ServerStatus
int code = 200,
string contentType = "text/plain");
[Obsolete("Use async versions instead")]
void RespondError(HttpStatusCode code);
Task RespondErrorAsync(HttpStatusCode code);
[Obsolete("Use async versions instead")]
void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK);
Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK);
Task<Stream> RespondStreamAsync(HttpStatusCode code = HttpStatusCode.OK);

View File

@@ -27,19 +27,6 @@ namespace Robust.Server.ServerStatus
/// </summary>
event Action<JsonNode> OnInfoRequest;
/// <summary>
/// Set information used by automatic-client-zipping to determine the layout of your dev setup,
/// and which assembly files to send.
/// </summary>
/// <param name="clientBinFolder">
/// The name of your client project in the bin/ folder on the top of your project.
/// </param>
/// <param name="clientAssemblyNames">
/// The list of client assemblies to send from the aforementioned folder.
/// </param>
[Obsolete("This API is deprecated as it cannot share information with standalone packaging. Use SetMagicAczProvider instead")]
void SetAczInfo(string clientBinFolder, string[] clientAssemblyNames);
void SetMagicAczProvider(IMagicAczProvider provider);
/// <summary>

View File

@@ -2,12 +2,92 @@ using System;
namespace Robust.Server.ServerStatus
{
/// <summary>
/// API for interacting with <c>SS14.Watchdog</c>.
/// </summary>
public interface IWatchdogApi
{
/// <summary>
/// Raised when the game server should restart for an update.
/// </summary>
/// <remarks>
/// <para>
/// This only indicates that the game server should restart as soon as possible without disruption,
/// e.g. at the end of a round. It should not shut down immediately unless possible.
/// </para>
/// <para>
/// This the same event as <see cref="RestartRequested"/>, but without additional data available such as reason.
/// </para>
/// </remarks>
event Action UpdateReceived;
/// <summary>
/// Raised when the watchdog has indicated that the server should restart as soon as possible.
/// </summary>
/// <remarks>
/// <para>
/// This only indicates that the game server should restart as soon as possible without disruption,
/// e.g. at the end of a round. It should not shut down immediately unless possible.
/// </para>
/// <para>
/// This the same event as <see cref="UpdateReceived"/>, but with additional data.
/// </para>
/// </remarks>
event Action<RestartRequestedData> RestartRequested;
}
/// <summary>
/// Engine-internal API for <see cref="IWatchdogApi"/>.
/// </summary>
internal interface IWatchdogApiInternal : IWatchdogApi
{
void Heartbeat();
void Initialize();
}
/// <summary>
/// Event data used by <see cref="IWatchdogApi.RestartRequested"/>.
/// </summary>
public sealed class RestartRequestedData
{
internal static readonly RestartRequestedData DefaultData = new(RestartRequestedReason.UpdateAvailable, null);
/// <summary>
/// Primary reason code for why the server should be restarted.
/// </summary>
public RestartRequestedReason Reason { get; }
/// <summary>
/// A message provided with additional information about the restart reason. Not always provided.
/// </summary>
public string? AdditionalMessage { get; }
internal RestartRequestedData(RestartRequestedReason reason, string? additionalMessage)
{
Reason = reason;
AdditionalMessage = additionalMessage;
}
}
/// <summary>
/// Primary reason codes for why the server should restart in <see cref="RestartRequestedData"/>.
/// </summary>
public enum RestartRequestedReason : byte
{
/// <summary>
/// Restart reason does not fall in an existing category.
/// </summary>
Other = 0,
/// <summary>
/// The server should restart because an update is available.
/// </summary>
UpdateAvailable,
/// <summary>
/// The server should restart for maintenance.
/// </summary>
Maintenance,
}
}

View File

@@ -171,22 +171,6 @@ internal sealed partial class StatusHost
// -- Information Input --
public void SetAczInfo(string clientBinFolder, string[] clientAssemblyNames)
{
_aczLock.Wait();
try
{
if (_aczPrepared != null)
throw new InvalidOperationException("ACZ already prepared");
_aczInfo = (clientBinFolder, clientAssemblyNames);
}
finally
{
_aczLock.Release();
}
}
public void SetMagicAczProvider(IMagicAczProvider provider)
{
_magicAczProvider = provider;

View File

@@ -269,57 +269,11 @@ namespace Robust.Server.ServerStatus
_responseHeaders = new Dictionary<string, string>();
}
public T? RequestBodyJson<T>()
{
return JsonSerializer.Deserialize<T>(RequestBody);
}
public async Task<T?> RequestBodyJsonAsync<T>()
{
return await JsonSerializer.DeserializeAsync<T>(RequestBody);
}
public void Respond(string text, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
{
Respond(text, (int)code, contentType);
}
public void Respond(string text, int code = 200, string contentType = MediaTypeNames.Text.Plain)
{
_context.Response.StatusCode = code;
_context.Response.ContentType = contentType;
if (RequestMethod == HttpMethod.Head)
{
return;
}
using var writer = new StreamWriter(_context.Response.OutputStream, EncodingHelpers.UTF8);
writer.Write(text);
}
public void Respond(byte[] data, HttpStatusCode code = HttpStatusCode.OK, string contentType = MediaTypeNames.Text.Plain)
{
Respond(data, (int)code, contentType);
}
public void Respond(byte[] data, int code = 200, string contentType = MediaTypeNames.Text.Plain)
{
_context.Response.StatusCode = code;
_context.Response.ContentType = contentType;
_context.Response.ContentLength64 = data.Length;
if (RequestMethod == HttpMethod.Head)
{
_context.Response.Close();
return;
}
_context.Response.OutputStream.Write(data);
_context.Response.Close();
}
public Task RespondNoContentAsync()
{
RespondShared();
@@ -373,27 +327,11 @@ namespace Robust.Server.ServerStatus
_context.Response.Close();
}
public void RespondError(HttpStatusCode code)
{
Respond(code.ToString(), code);
}
public Task RespondErrorAsync(HttpStatusCode code)
{
return RespondAsync(code.ToString(), code);
}
public void RespondJson(object jsonData, HttpStatusCode code = HttpStatusCode.OK)
{
RespondShared();
_context.Response.ContentType = "application/json";
JsonSerializer.Serialize(_context.Response.OutputStream, jsonData);
_context.Response.Close();
}
public async Task RespondJsonAsync(object jsonData, HttpStatusCode code = HttpStatusCode.OK)
{
RespondShared();

View File

@@ -2,6 +2,7 @@ using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
@@ -15,11 +16,9 @@ using Robust.Shared.Network;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
#nullable enable
namespace Robust.Server.ServerStatus
{
public sealed class WatchdogApi : IWatchdogApi, IPostInjectInit
internal sealed class WatchdogApi : IWatchdogApiInternal, IPostInjectInit
{
[Dependency] private readonly IStatusHost _statusHost = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
@@ -42,7 +41,7 @@ namespace Robust.Server.ServerStatus
HttpClientUserAgent.AddUserAgent(_httpClient);
}
public void PostInject()
void IPostInjectInit.PostInject()
{
_sawmill = Logger.GetSawmill("watchdogApi");
@@ -52,7 +51,7 @@ namespace Robust.Server.ServerStatus
private async Task<bool> UpdateHandler(IStatusHandlerContext context)
{
if (context.RequestMethod != HttpMethod.Post || context.Url!.AbsolutePath != "/update")
if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/update")
{
return false;
}
@@ -73,7 +72,46 @@ namespace Robust.Server.ServerStatus
return true;
}
_taskManager.RunOnMainThread(() => UpdateReceived?.Invoke());
RestartRequestParameters? parameters = null;
if (context.RequestHeaders.TryGetValue("Content-Type", out var contentType)
&& contentType == MediaTypeNames.Application.Json)
{
try
{
parameters = await context.RequestBodyJsonAsync<RestartRequestParameters>();
}
catch (JsonException)
{
// parameters null so it'll catch the block down below.
}
if (parameters == null)
{
await context.RespondErrorAsync(HttpStatusCode.BadRequest);
return true;
}
}
RestartRequestedData restartData;
if (parameters == null)
{
restartData = RestartRequestedData.DefaultData;
}
else
{
// Allow parsing to fail for forwards compatibility.
var reasonCode = Enum.TryParse<RestartRequestedReason>(parameters.Reason, out var code)
? code
: RestartRequestedReason.Other;
restartData = new RestartRequestedData(reasonCode, parameters.Message);
}
_taskManager.RunOnMainThread(() =>
{
RestartRequested?.Invoke(restartData);
UpdateReceived?.Invoke();
});
await context.RespondAsync("Success", HttpStatusCode.OK);
@@ -86,7 +124,7 @@ namespace Robust.Server.ServerStatus
/// </remarks>
private async Task<bool> ShutdownHandler(IStatusHandlerContext context)
{
if (context.RequestMethod != HttpMethod.Post || context.Url!.AbsolutePath != "/shutdown")
if (context.RequestMethod != HttpMethod.Post || context.Url.AbsolutePath != "/shutdown")
{
return false;
}
@@ -106,7 +144,8 @@ namespace Robust.Server.ServerStatus
if (auth != _watchdogToken)
{
_sawmill.Verbose(
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}", auth,
"received POST /shutdown with invalid authentication token. Ignoring {0}, {1}",
auth,
_watchdogToken);
await context.RespondErrorAsync(HttpStatusCode.Unauthorized);
return true;
@@ -137,6 +176,7 @@ namespace Robust.Server.ServerStatus
}
public event Action? UpdateReceived;
public event Action<RestartRequestedData>? RestartRequested;
public async void Heartbeat()
{
@@ -204,5 +244,12 @@ namespace Robust.Server.ServerStatus
// ReSharper disable once RedundantDefaultMemberInitializer
public string Reason { get; set; } = default!;
}
[UsedImplicitly]
private sealed class RestartRequestParameters
{
public string Reason { get; set; } = nameof(RestartRequestedReason.Other);
public string? Message { get; set; }
}
}
}

View File

@@ -81,7 +81,7 @@ public record struct NoSuchPlayerError(string Username) : IConError
{
public FormattedMessage DescribeInner()
{
return FormattedMessage.FromMarkup($"No player with the username/GUID {Username} could be found.");
return FormattedMessage.FromUnformatted($"No player with the username/GUID {Username} could be found.");
}
public string? Expression { get; set; }

View File

@@ -87,33 +87,33 @@ namespace Robust.Shared.Scripting
public object? prop(object target, string name)
{
return target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic)
!.GetValue(target);
var prop = (PropertyInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Property, name);
return prop!.GetValue(target);
}
public void setprop(object target, string name, object? value)
{
target.GetType().GetProperty(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
!.SetValue(target, value);
var prop = (PropertyInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Property, name);
prop!.SetValue(target, value);
}
public object? fld(object target, string name)
{
return target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
!.GetValue(target);
var fld = (FieldInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Field, name);
return fld!.GetValue(target);
}
public void setfld(object target, string name, object? value)
{
target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
!.SetValue(target, value);
var fld = (FieldInfo?) ReflectionGetInstanceMember(target.GetType(), MemberTypes.Field, name);
fld!.SetValue(target, value);
}
public object? call(object target, string name, params object[] args)
{
var t = target.GetType();
// TODO: overloads
var m = t.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
var m = (MethodInfo?) ReflectionGetInstanceMember(t, MemberTypes.Method, name);
return m!.Invoke(target, args);
}
@@ -206,8 +206,11 @@ namespace Robust.Shared.Scripting
public void Dirty(EntityUid uid)
=> ent.DirtyEntity(uid);
#pragma warning disable CS0618 // Type or member is obsolete
// Remove this helper when component.Owner finally gets removed.
public void Dirty(Component comp)
=> ent.Dirty(comp.Owner, comp);
#pragma warning restore CS0618 // Type or member is obsolete
public string Name(EntityUid uid)
=> ent.GetComponent<MetaDataComponent>(uid).EntityName;
@@ -284,5 +287,21 @@ namespace Robust.Shared.Scripting
}
public Dictionary<string, object?> Variables { get; } = new();
private static MemberInfo? ReflectionGetInstanceMember(Type type, MemberTypes memberType, string name)
{
for (var curType = type; curType != null; curType = curType.BaseType)
{
var member = curType.GetMember(
name,
memberType,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (member.Length > 0)
return member[0];
}
return null;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Numerics;
using System.Reflection;
using System.Runtime.Loader;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Lidgren.Network;
using Microsoft.CodeAnalysis;
@@ -14,14 +15,13 @@ using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.CodeAnalysis.Text;
using Robust.Shared.Maths;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Scripting
{
internal static class ScriptInstanceShared
internal static partial class ScriptInstanceShared
{
public static CSharpParseOptions ParseOptions { get; } =
new(kind: SourceCodeKind.Script, languageVersion: LanguageVersion.Latest);
@@ -186,11 +186,12 @@ namespace Robust.Shared.Scripting
var assemblies = ScriptInstanceShared.GetAutoImportAssemblies(refl).ToArray();
foreach (var m in missing)
{
var mName = ConvertMissingTypeName(m);
foreach (var assembly in assemblies)
{
foreach (var type in assembly.DefinedTypes)
{
if (type.IsPublic && type.Name == m)
if (type.IsPublic && type.Name == mName)
{
found.Add(type.Namespace!);
goto nextMissing;
@@ -225,5 +226,22 @@ namespace Robust.Shared.Scripting
return "<CSharpObjectFormatter.FormatObject threw>";
}
}
private static string ConvertMissingTypeName(string name)
{
var match = TypeMissingParserRegex().Match(name);
var typeName = match.Groups[1].Value;
if (match.Groups[2].Success)
{
// We have generics
var genericCount = match.Groups[2].Length + 1;
return $"{typeName}`{genericCount}";
}
return match.Groups[1].Value;
}
[GeneratedRegex("^(.+?)(?:<(,*)>)?$")]
private static partial Regex TypeMissingParserRegex();
}
}

View File

@@ -17,9 +17,6 @@ public abstract partial class SoundSpecifier
{
[DataField("params")]
public AudioParams Params { get; set; } = AudioParams.Default;
[Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")]
public abstract string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null);
}
[Serializable, NetSerializable]
@@ -45,12 +42,6 @@ public sealed partial class SoundPathSpecifier : SoundSpecifier
if (@params.HasValue)
Params = @params.Value;
}
[Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")]
public override string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null)
{
return Path.ToString();
}
}
[Serializable, NetSerializable]
@@ -70,15 +61,4 @@ public sealed partial class SoundCollectionSpecifier : SoundSpecifier
if (@params.HasValue)
Params = @params.Value;
}
[Obsolete("Use SharedAudioSystem.GetSound(), or just pass sound specifier directly into SharedAudioSystem.")]
public override string GetSound(IRobustRandom? rand = null, IPrototypeManager? proto = null)
{
if (Collection == null)
return string.Empty;
IoCManager.Resolve(ref rand, ref proto);
var soundCollection = proto.Index<SoundCollectionPrototype>(Collection);
return rand.Pick(soundCollection.PickFiles).ToString();
}
}

View File

@@ -417,7 +417,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null);
/// <summary>
@@ -425,10 +424,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, sound.Params);
return sound == null ? null : PlayGlobal(GetSound(sound), playerFilter, recordReplay, audioParams ?? sound.Params);
}
/// <summary>
@@ -436,7 +434,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="recipient">The player that will hear the sound.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null);
/// <summary>
@@ -444,10 +441,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="recipient">The player that will hear the sound.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient)
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, ICommonSession recipient, AudioParams? audioParams = null)
{
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params);
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params);
}
public abstract void LoadStream<T>(Entity<AudioComponent> entity, T stream);
@@ -457,7 +453,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="recipient">The player that will hear the sound.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null);
/// <summary>
@@ -465,10 +460,9 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="recipient">The player that will hear the sound.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayGlobal(SoundSpecifier? sound, EntityUid recipient, AudioParams? audioParams = null)
{
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, sound.Params);
return sound == null ? null : PlayGlobal(GetSound(sound), recipient, audioParams ?? sound.Params);
}
/// <summary>
@@ -477,7 +471,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null);
/// <summary>
@@ -486,7 +479,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null);
/// <summary>
@@ -495,7 +487,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null);
/// <summary>
@@ -504,7 +495,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
{
return sound == null ? null : PlayEntity(GetSound(sound), playerFilter, uid, recordReplay, audioParams ?? sound.Params);
@@ -516,7 +506,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
{
return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params);
@@ -528,7 +517,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayEntity(SoundSpecifier? sound, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
{
return sound == null ? null : PlayEntity(GetSound(sound), recipient, uid, audioParams ?? sound.Params);
@@ -539,7 +527,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityUid uid, AudioParams? audioParams = null)
{
return sound == null ? null : PlayPvs(GetSound(sound), uid, audioParams ?? sound.Params);
@@ -550,7 +537,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="coordinates">The EntityCoordinates to attach the audio source to.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(SoundSpecifier? sound, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return sound == null ? null : PlayPvs(GetSound(sound), coordinates, audioParams ?? sound.Params);
@@ -561,7 +547,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="coordinates">The EntityCoordinates to attach the audio source to.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename,
EntityCoordinates coordinates, AudioParams? audioParams = null);
@@ -570,10 +555,17 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="uid">The UID of the entity "emitting" the audio.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPvs(string? filename, EntityUid uid,
AudioParams? audioParams = null);
/// <summary>
/// Plays a predicted sound following an entity for only one entity. The client-side system plays this sound as normal, server will do nothing.
/// </summary>
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="source">The UID of the entity "emitting" the audio.</param>
/// <param name="soundInitiator">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayLocal(SoundSpecifier? sound, EntityUid source, EntityUid? soundInitiator, AudioParams? audioParams = null);
/// <summary>
/// Plays a predicted sound following an entity. The server will send the sound to every player in PVS range,
/// unless that player is attached to the "user" entity that initiated the sound. The client-side system plays
@@ -582,7 +574,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="source">The UID of the entity "emitting" the audio.</param>
/// <param name="user">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
[return: NotNullIfNotNull("sound")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityUid source, EntityUid? user, AudioParams? audioParams = null);
/// <summary>
@@ -593,7 +584,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="coordinates">The entitycoordinates "emitting" the audio</param>
/// <param name="user">The UID of the user that initiated this sound. This is usually some player's controlled entity.</param>
[return: NotNullIfNotNull("sound")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayPredicted(SoundSpecifier? sound, EntityCoordinates coordinates, EntityUid? user, AudioParams? audioParams = null);
/// <summary>
@@ -602,7 +592,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null);
/// <summary>
@@ -611,7 +600,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
/// <summary>
@@ -620,7 +608,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
[return: NotNullIfNotNull("filename")]
public abstract (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null);
/// <summary>
@@ -629,7 +616,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="playerFilter">The set of players that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
return sound == null ? null : PlayStatic(GetSound(sound), playerFilter, coordinates, recordReplay, audioParams);
@@ -641,7 +627,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params);
@@ -653,7 +638,6 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="sound">The sound specifier that points the audio file(s) that should be played.</param>
/// <param name="recipient">The player that will hear the sound.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
[return: NotNullIfNotNull("sound")]
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return sound == null ? null : PlayStatic(GetSound(sound), recipient, coordinates, audioParams ?? sound.Params);

View File

@@ -394,6 +394,18 @@ namespace Robust.Shared
public static readonly CVarDef<int> NetEncryptionThreadChannelSize =
CVarDef.Create("net.encryption_thread_channel_size", 16);
/// <summary>
/// Whether the server should request HWID system for client identification.
/// </summary>
/// <remarks>
/// <para>
/// Note that modern HWIDs are only available if the connection is authenticated.
/// </para>
/// </remarks>
public static readonly CVarDef<bool> NetHWId =
CVarDef.Create("net.hwid", true, CVar.SERVERONLY);
/**
* SUS
*/
@@ -671,6 +683,13 @@ namespace Robust.Shared
public static readonly CVarDef<string> BuildManifestHash =
CVarDef.Create("build.manifest_hash", "");
/// <summary>
/// Allows you to disable the display of all entities in the spawn menu that are not labeled with the ShowSpawnMenu category.
/// This is useful for forks that just want to disable the standard upstream content
/// </summary>
public static readonly CVarDef<string> EntitiesCategoryFilter =
CVarDef.Create("build.entities_category_filter", "");
/*
* WATCHDOG
*/
@@ -1288,7 +1307,7 @@ namespace Robust.Shared
/// Default is 35 m/s. Around half a tile per tick at 60 ticks per second.
/// </remarks>
public static readonly CVarDef<float> MaxLinVelocity =
CVarDef.Create("physics.maxlinvelocity", 35f);
CVarDef.Create("physics.maxlinvelocity", 35f, CVar.SERVER | CVar.REPLICATED);
/// <summary>
/// Maximum angular velocity in full rotations per second.
@@ -1636,6 +1655,16 @@ namespace Robust.Shared
public static readonly CVarDef<long> ReplayMaxUncompressedSize = CVarDef.Create("replay.max_uncompressed_size",
1024L * 1024, CVar.ARCHIVE);
/// <summary>
/// Size of the replay (in kilobytes) at which point the replay is considered "large",
/// and replay clients should enable server GC (if possible) to improve performance.
/// </summary>
/// <remarks>
/// Set to -1 to never make replays use server GC.
/// </remarks>
public static readonly CVarDef<long> ReplayServerGCSizeThreshold =
CVarDef.Create("replay.server_gc_size_threshold", 50L * 1024);
/// <summary>
/// Uncompressed size of individual files created by the replay (in kilobytes), where each file contains data
/// for one or more ticks. Actual files may be slightly larger, this is just a threshold for the file to get

View File

@@ -30,8 +30,13 @@ namespace Robust.Shared.Console
bool IsServer { get; }
/// <summary>
/// The remote peer that owns this shell, or the local player if this is a client-side local shell (<see cref="IsLocal" /> is true and <see cref="IsClient"/> is true).
/// The remote peer that owns this shell, or the local player if this is a local shell.
/// </summary>
/// <remarks>
/// <para>
/// This parameter is null for commands executed directly from the server console, as that has no player.
/// </para>
/// </remarks>
ICommonSession? Player { get; }
/// <summary>

View File

@@ -19,7 +19,6 @@ namespace Robust.Shared.Containers
[RegisterComponent, ComponentProtoName("ContainerContainer")]
public sealed partial class ContainerManagerComponent : Component, ISerializationHooks
{
[Dependency] private readonly IDynamicTypeFactoryInternal _dynFactory = default!;
[Dependency] private readonly IEntityManager _entMan = default!;
[DataField("containers")]
@@ -34,19 +33,6 @@ namespace Robust.Shared.Containers
}
}
[Obsolete]
public T MakeContainer<T>(EntityUid uid, string id)
where T : BaseContainer
=> _entMan.System<SharedContainerSystem>().MakeContainer<T>(uid, id, this);
[Obsolete]
public BaseContainer GetContainer(string id)
=> _entMan.System<SharedContainerSystem>().GetContainer(Owner, id, this);
[Obsolete]
public bool HasContainer(string id)
=> _entMan.System<SharedContainerSystem>().HasContainer(Owner, id, this);
[Obsolete]
public bool TryGetContainer(string id, [NotNullWhen(true)] out BaseContainer? container)
=> _entMan.System<SharedContainerSystem>().TryGetContainer(Owner, id, out container, this);
@@ -55,20 +41,6 @@ namespace Robust.Shared.Containers
public bool TryGetContainer(EntityUid entity, [NotNullWhen(true)] out BaseContainer? container)
=> _entMan.System<SharedContainerSystem>().TryGetContainingContainer(Owner, entity, out container, this);
[Obsolete]
public bool ContainsEntity(EntityUid entity)
=> _entMan.System<SharedContainerSystem>().ContainsEntity(Owner, entity, this);
[Obsolete]
public bool Remove(EntityUid toremove,
TransformComponent? xform = null,
MetaDataComponent? meta = null,
bool reparent = true,
bool force = false,
EntityCoordinates? destination = null,
Angle? localRotation = null)
=> _entMan.System<SharedContainerSystem>().RemoveEntity(Owner, toremove, this, xform, meta, reparent, force, destination, localRotation);
[Obsolete]
public AllContainersEnumerable GetAllContainers()
=> _entMan.System<SharedContainerSystem>().GetAllContainers(Owner, this);

View File

@@ -193,16 +193,6 @@ namespace Robust.Shared.Containers
return true;
}
[Obsolete("Use variant without skipExistCheck argument")]
public bool TryGetContainingContainer(
EntityUid uid,
EntityUid containedUid,
[NotNullWhen(true)] out BaseContainer? container,
bool skipExistCheck)
{
return TryGetContainingContainer(uid, containedUid, out container);
}
public bool TryGetContainingContainer(
EntityUid uid,
EntityUid containedUid,

View File

@@ -480,6 +480,7 @@ Types:
NotNullAttribute: { All: True }
NotNullIfNotNullAttribute: { All: True }
NotNullWhenAttribute: { All: True }
SetsRequiredMembersAttribute: { All : True}
SuppressMessageAttribute: { All: True }
System.Diagnostics:
DebuggableAttribute: { All: True }
@@ -588,7 +589,6 @@ Types:
Vector2: { All: True }
Vector3: { All: True }
Vector4: { All: True }
Matrix3x2: { All: True }
System.Reflection:
Assembly:
Methods:
@@ -653,8 +653,10 @@ Types:
NullableContextAttribute: { All: True }
PreserveBaseOverridesAttribute: { All: True }
RefSafetyRulesAttribute: { All: True }
RequiredMemberAttribute: { All: True }
RuntimeCompatibilityAttribute: { All: True }
RuntimeHelpers: { All: True }
SwitchExpressionException: { All: True }
TaskAwaiter: { All: True }
TaskAwaiter`1: { All: True }
TupleElementNamesAttribute: { All: True }

View File

@@ -46,17 +46,4 @@ public sealed partial class AppearanceComponent : Component
get { return _appearanceDataInit; }
set { AppearanceData = value ?? AppearanceData; _appearanceDataInit = value; }
}
[Obsolete("Use SharedAppearanceSystem instead")]
public bool TryGetData<T>(Enum key, [NotNullWhen(true)] out T data)
{
if (AppearanceData.TryGetValue(key, out var dat) && dat is T)
{
data = (T)dat;
return true;
}
data = default!;
return false;
}
}

View File

@@ -204,7 +204,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Offset into internal PVS data.
/// </summary>
internal PvsIndex PvsData;
internal PvsIndex PvsData = PvsIndex.Invalid;
}
[Flags]
@@ -254,5 +254,8 @@ namespace Robust.Shared.GameObjects
/// An invalid index. This is also used as a marker value in the free list.
/// </summary>
public static readonly PvsIndex Invalid = new PvsIndex(-1);
// TODO PVS
// Consider making 0 an invalid value.
// it prevents default structs from accidentally being used.
}
}

View File

@@ -15,43 +15,6 @@ namespace Robust.Shared.GameObjects
return entMan.EnsureComponent<TimerComponent>(entity);
}
public static void AddTimer(this EntityUid entity, Timer timer, CancellationToken cancellationToken = default)
{
entity
.EnsureTimerComponent()
.AddTimer(timer, cancellationToken);
}
/// <summary>
/// Creates a task that will complete after a given delay.
/// The task is resumed on the main game logic thread.
/// </summary>
/// <param name="entity">The entity to add the timer to.</param>
/// <param name="milliseconds">The length of time, in milliseconds, to delay for.</param>
/// <param name="cancellationToken"></param>
/// <returns>The task that can be awaited.</returns>
public static Task DelayTask(this EntityUid entity, int milliseconds, CancellationToken cancellationToken = default)
{
return entity
.EnsureTimerComponent()
.Delay(milliseconds, cancellationToken);
}
/// <summary>
/// Creates a task that will complete after a given delay.
/// The task is resumed on the main game logic thread.
/// </summary>
/// <param name="entity">The entity to add the timer to.</param>
/// <param name="duration">The length of time to delay for.</param>
/// <param name="cancellationToken"></param>
/// <returns>The task that can be awaited.</returns>
public static Task DelayTask(this EntityUid entity, TimeSpan duration, CancellationToken cancellationToken = default)
{
return entity
.EnsureTimerComponent()
.Delay((int) duration.TotalMilliseconds, cancellationToken);
}
/// <summary>
/// Schedule an action to be fired after a certain delay.
/// The action will be resumed on the main game logic thread.
@@ -84,21 +47,6 @@ namespace Robust.Shared.GameObjects
.Spawn((int) duration.TotalMilliseconds, onFired, cancellationToken);
}
/// <summary>
/// Schedule an action that repeatedly fires after a delay specified in milliseconds.
/// </summary>
/// <param name="entity">The entity to add the timer to.</param>
/// <param name="milliseconds">The length of time, in milliseconds, to delay before firing the repeated action.</param>
/// <param name="onFired">The action to fire.</param>
/// <param name="cancellationToken">The CancellationToken for stopping the Timer.</param>
[Obsolete("Use a system update loop instead")]
public static void SpawnRepeatingTimer(this EntityUid entity, int milliseconds, Action onFired, CancellationToken cancellationToken)
{
entity
.EnsureTimerComponent()
.SpawnRepeating(milliseconds, onFired, cancellationToken);
}
/// <summary>
/// Schedule an action that repeatedly fires after a delay.
/// </summary>

View File

@@ -386,9 +386,6 @@ namespace Robust.Shared.GameObjects
}
}
[Obsolete("Use ChildEnumerator")]
public IEnumerable<EntityUid> ChildEntities => _children;
public TransformChildrenEnumerator ChildEnumerator => new(_children.GetEnumerator());
[ViewVariables] public int ChildCount => _children.Count;
@@ -405,16 +402,6 @@ namespace Robust.Shared.GameObjects
_entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().AttachToGridOrMap(Owner, this);
}
/// <summary>
/// Sets another entity as the parent entity, maintaining world position.
/// </summary>
/// <param name="newParent"></param>
[Obsolete("Use TransformSystem.SetParent() instead")]
public void AttachParent(TransformComponent newParent)
{
_entMan.EntitySysManager.GetEntitySystem<SharedTransformSystem>().SetParent(Owner, this, newParent.Owner, newParent);
}
internal void UpdateChildMapIdsRecursive(
MapId newMapId,
EntityUid? newUid,
@@ -458,14 +445,6 @@ namespace Robust.Shared.GameObjects
return (worldPos, worldRot);
}
/// <see cref="GetWorldPositionRotation()"/>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation) GetWorldPositionRotation(EntityQuery<TransformComponent> xforms)
{
var (worldPos, worldRot, _) = GetWorldPositionRotationMatrix(xforms);
return (worldPos, worldRot);
}
/// <summary>
/// Get the WorldPosition, WorldRotation, and WorldMatrix of this entity faster than each individually.
/// </summary>
@@ -502,16 +481,6 @@ namespace Robust.Shared.GameObjects
return GetWorldPositionRotationMatrix(xforms);
}
/// <summary>
/// Get the WorldPosition, WorldRotation, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
[Obsolete("Use the system method instead")]
public (Vector2 WorldPosition, Angle WorldRotation, Matrix3x2 InvWorldMatrix) GetWorldPositionRotationInvMatrix()
{
var xformQuery = _entMan.GetEntityQuery<TransformComponent>();
return GetWorldPositionRotationInvMatrix(xformQuery);
}
/// <summary>
/// Get the WorldPosition, WorldRotation, and InvWorldMatrix of this entity faster than each individually.
/// </summary>
@@ -654,13 +623,6 @@ namespace Robust.Shared.GameObjects
/// If true, the entity is being detached to null-space
/// </summary>
public readonly bool Detaching = detaching;
[Obsolete("Use constructor that takes in EntityUid")]
public AnchorStateChangedEvent(TransformComponent transform, bool detaching = false)
: this(transform.Owner, transform, detaching)
{
}
}
/// <summary>

View File

@@ -12,7 +12,7 @@ namespace Robust.Shared.GameObjects
public abstract class BoundUserInterface : IDisposable
{
[Dependency] protected readonly IEntityManager EntMan = default!;
[Dependency] private readonly ISharedPlayerManager _playerManager = default!;
[Dependency] protected readonly ISharedPlayerManager PlayerManager = default!;
protected readonly SharedUserInterfaceSystem UiSystem;
public readonly Enum UiKey;
@@ -79,7 +79,7 @@ namespace Robust.Shared.GameObjects
/// </summary>
public void Close()
{
UiSystem.CloseUi(Owner, UiKey, _playerManager.LocalEntity, predicted: true);
UiSystem.CloseUi(Owner, UiKey, PlayerManager.LocalEntity, predicted: true);
}
/// <summary>

View File

@@ -281,24 +281,6 @@ namespace Robust.Shared.GameObjects
}
}
/// <inheritdoc />
[Obsolete]
public CompInitializeHandle<T> AddComponentUninitialized<T>(EntityUid uid) where T : IComponent, new()
{
var reg = _componentFactory.GetRegistration<T>();
var newComponent = (T)_componentFactory.GetComponent(reg);
#pragma warning disable CS0618 // Type or member is obsolete
newComponent.Owner = uid;
#pragma warning restore CS0618 // Type or member is obsolete
if (!uid.IsValid() || !EntityExists(uid))
throw new ArgumentException($"Entity {uid} is not valid.", nameof(uid));
AddComponentInternal(uid, newComponent, false, true, null);
return new CompInitializeHandle<T>(this, uid, newComponent, reg.Idx);
}
public void AddComponent(
EntityUid uid,
EntityPrototype.ComponentRegistryEntry entry,

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