Compare commits

...

71 Commits

Author SHA1 Message Date
PJB3005
0d0d949752 Version: 263.0.1 2025-09-14 14:55:50 +02:00
PJB3005
d43fc89055 Squashed commit of the following:
commit d4f265c314
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sun Sep 14 14:32:44 2025 +0200

    Fix incorrect path combine in DirLoader and WritableDirProvider

    This (and the other couple past commits) reported by Elelzedel.

commit 7654d38612
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 22:50:51 2025 +0200

    Move CEF cache out of data directory

    Don't want content messing with this...

commit cdcc255123
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:11:16 2025 +0200

    Make Robust.Client.WebView.Cef.Program internal.

commit 2f56a6a110
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:10:46 2025 +0200

    Update SpaceWizards.NFluidSynth to 0.2.2

commit 16fc48cef2
Author: PJB3005 <pieterjan.briers+git@gmail.com>
Date:   Sat Sep 13 19:09:43 2025 +0200

    Hide IWritableDirProvider.RootDir on client

    This shouldn't be exposed.

(cherry picked from commit 2f07159336bc640e41fbbccfdec4133a68c13bdb)
(cherry picked from commit d6c3212c74373ed2420cc4be2cf10fcd899c2106)
(cherry picked from commit bfa70d7e2ca6758901b680547fcfa9b24e0610b7)
(cherry picked from commit 06e52f5d58efc1491915822c2650f922673c82c6)
2025-09-14 14:55:50 +02:00
PJB3005
f9d0dd551a Version: 263.0.0 2025-06-22 13:36:07 +02:00
Aiden
b2540a6e08 Static Field Assert (#5926)
* Static Field Assert

* Update Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>

---------

Co-authored-by: GoobBot <uristmchands@proton.me>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-06-21 23:51:28 +02:00
DrSmugleaf
66d898ee91 Add GetMessage and SetMessage methods to OutputPanel (#5956)
* Add GetMessage and SetMessage methods to OutputPanel

* Copy paste bad

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-06-21 23:33:03 +02:00
ThereDrD
310dc676ea fix: use maxSizeX instead of Width in rich text entry measure (#5989)
Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-06-21 15:16:38 +02:00
Perry Fraser
41844d2d30 Adjust how OpenAL extensions are requested (#6000)
* fix: use correct device in OAL extension lookup

* fix: don't try to set non-existent window icons

* Revert "fix: don't try to set non-existent window icons"

This reverts commit 793958fb8c.

Moving to other PR.
2025-06-21 15:16:02 +02:00
slarticodefast
c6f3af20d6 fully obsolete container methods (#6007) 2025-06-21 14:42:57 +02:00
Pieter-Jan Briers
5501209b35 Add API to load maps from byte stream (#6029)
In case you don't want to load from a ResPath.
2025-06-21 14:41:32 +02:00
Tayrtahn
9b2ef75762 Optimize DataDefinitionAnalyzer a bit (#6035)
* Skip fields/properties not in DataDefs

* Only check IsDataField once per field/property

* Remove pointless foreach loop

* Remove an extra IsDataDefinition check

* Revert unneeded changes from testing

* Revert "Remove pointless foreach loop"

This reverts commit f05d566904.

* Restore analysis of multiple declarations
2025-06-21 01:15:59 +02:00
Tayrtahn
196e59b7e4 Clean up all missing EntitySystem proxy method uses (#6027)
* Clean up all missing EntitySystem proxy method uses

* Restore comment

* Fix bad change that caused closure allocation

* tuple

* Revert "tuple"

This reverts commit 14581a40aa.

* Revert "Fix bad change that caused closure allocation"

This reverts commit 215b2559ed.

* Revert "Restore comment"

This reverts commit 4a47a36557.

* Revert "Clean up all missing EntitySystem proxy method uses"

This reverts commit 3b1fe4ce7f.

* Redo with improved code fixer.
Let's see how it fares this time
2025-06-21 00:05:09 +02:00
Perry Fraser
2c936b5973 fix: don't delete people who are teleported to themselves (#6040) 2025-06-20 14:00:27 +02:00
Tayrtahn
7765e71dca Add tests for remaining DataDefinitionAnalyzer diagnostics (#6034)
* Add tests for partial datadefs and partial nested datadefs

* Add test for redundant datafield tag

* Blank lines upset me
2025-06-20 02:26:08 +02:00
TrixxedHeart
d8ae71d8cd adds typeselector (thanks pbj) (#6038) 2025-06-20 02:24:03 +02:00
Tayrtahn
a74812ce5b Make AddComp where clause consistent with AddComponent (#6028) 2025-06-19 10:21:13 +10:00
PJB3005
a7f9b0a6db Fix debug assert when loading MIDI on Windows.
Fixes #6020

The assert was caused by the native OS path (C:\Windows\...) being passed through a ResPath. Bad. While looking at this I realized the sound font loader callback system was a mess and I should probably clean it up, so I did.

The file name is now properly namespaced in the loader callback, which should avoid spaghetti like this in the future. The details of how this works are a pain in the ass because Fluidsynth isn't well-designed.

I split LoadSoundfont() into two functions: one for resource, one for user paths. The other is kept there but compatible.

I can't believe I spent 3 hours on dealing with this nonsense and most of it is just due to Fluidsynth being poorly designed...
2025-06-18 03:25:58 +02:00
Amy
3aac92e4b2 soft only plz (#6030) 2025-06-17 18:56:44 +02:00
PJB3005
c152fb8953 Fix culture-based parsing in TimespanSerializer 2025-06-17 16:03:20 +02:00
DrSmugleaf
10ea5498cf Fix error in some localization functions when an argument is not an EntityUid (#6022) 2025-06-15 14:07:13 +02:00
Walker Fowlkes
324606e5a3 Add a new toolshed command spawn:in (#6021)
* added spawn:in command.

* better annotations

* use EntityManager.System

* ftl

* make it lazy cached.

* Fix typo (it's -> its)

---------

Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-06-15 14:05:27 +02:00
Tayrtahn
a8227f7faa Replace static logger call in FileDialogManager (#6018) 2025-06-13 23:20:23 +02:00
PJB3005
9f55400c58 Avoid closure allocation in physics SolveIsland()
There's a parallel call in there that's only used when the island should be processed parallel internally. This isn't done for all islands, so allocating the closure in every case is a massive waste.
2025-06-13 15:00:47 +02:00
PJB3005
8b971f7ae7 Add CompletionHelper.PrototypeIdsLimited API
Somebody ignored the doc comment saying "don't use this with EntityPrototype" so now just *typing* a Tippy command causes the server to lag. Great.

This still isn't too great for performance but at least it's better, and I don't want to commit to making PrototypeManager semi-thread-safe.
2025-06-13 00:15:17 +02:00
PJB3005
e3c7e361ae Avoid server stutters from scsi init
Task.Run go brr
2025-06-12 23:33:48 +02:00
Tayrtahn
5c48dcb211 Fix TabContainer.CurrentTab setter (#6017) 2025-06-12 00:31:03 +02:00
B_Kirill
694de028c2 AudioSystem logging extension (#5959)
* AudioSystem logging extension

* Redo

* Fix

* review
2025-06-11 02:17:49 +02:00
PJB3005
d41c9e7662 Properly catch errors when executing client commands.
Previously these errors propagated all the way into Clyde. Guh.

Probably still need more error handling around the input system, but this is important regardless.
2025-06-11 02:11:32 +02:00
B3CKDOOR
76134e0f8d Adding "Attribution-NonCommercial-NoDerivatives 4.0 International" (#6008)
* Adding "Attribution-NonCommercial-NoDerivatives 4.0 International"

Adding the "Attribution-NonCommercial-NoDerivatives 4.0 International" License type, this is getting marked as an "invalid" license when its actually a valid license.

[License link](https://creativecommons.org/licenses/by-nc-nd/4.0/)

* Darn, forgot a comma
2025-06-09 20:57:54 +02:00
Perry Fraser
2983517e43 fix: don't try to set non-existent window icons (#6016) 2025-06-09 20:56:01 +02:00
metalgearsloth
18849be0b4 Version: 262.0.0 2025-06-09 23:56:58 +10:00
Leon Friedrich
c6a1d82bb1 Validate that Toolshed command arguments have parsers (#6014)
* Add Nullable<T> support to ToolshedManager.TryParse

* Check that command arguments are parseable

* release notes

* a

* A is for Array

* Fix test

* Fix indentation
2025-06-09 23:47:29 +10:00
Leon Friedrich
d89e1a43c6 Add PvsResetTest (#6015) 2025-06-09 23:39:06 +10:00
Leon Friedrich
d894ef70ef Misc SpriteSystem fixes (#6001)
* Move GetPrototypeTextures to SpriteSystem

* Fix tests

* Fix #6002

* release notes

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-06-09 18:59:37 +10:00
DrSmugleaf
c7ea2793ca Fix TransformComponent state handling changing the coordinates of detached entities (#6006)
* Fix TransformComponent state handling changing the coordinates of detached entities

* Make ResetPredictedEntities not handle state for detached entities
2025-06-09 17:44:36 +10:00
Perry Fraser
0c61ff2bee fix: default audio params for PlayStatic (#6011) 2025-06-09 00:28:12 +02:00
Tayrtahn
343a34eac7 Fix warning CS0168 in EntityDeserializer (#6013)
* Fix warning CS0168 in EntityDeserializer

* Actually, why use try-catch just to rethrow anyway?
2025-06-09 00:04:03 +02:00
metalgearsloth
7be41f4890 Add ignoredcomps to IsDefault (#5998) 2025-06-05 23:54:47 +10:00
metalgearsloth
293470a5fe Fix incorrect saved window positions (#5927)
* Fix incorrect saved window positions

As of however many UI PRs ago windows store their last position on the client and it re-opens windows at that position.

The issue is that the code to avoid windows being able to go off-screen was immediately bulldozing this value, at least if the x <= 0. Now we just don't run it until we have a valid measure (probably the frame after) and avoid unnecessarily having an incorrect position applied.

* Explainer
2025-06-05 23:35:28 +10:00
metalgearsloth
2b8057acf0 Version: 261.2.0 2025-06-05 22:55:09 +10:00
Tayrtahn
bec3caa5da Fix error when using tpto on a grid (#5991)
* Fix error when using tpto on a grid

* Calculate map coords outside of loop
2025-06-05 22:45:30 +10:00
metalgearsloth
ea6126563b Add NearestChunkEnumerator (#5972)
Not super fast but want it for biome loading to prio chunks nearby first.
2025-06-05 22:36:22 +10:00
slarticodefast
00494ad9eb fix TryQueueDelete (#5996) 2025-06-05 22:34:50 +10:00
Tayrtahn
6672b7b1bd Correct misleading error message in ShareMapSystem.OnParentChange (#5992) 2025-06-05 22:29:47 +10:00
ruddygreat
8dc55e8748 fix the lifestage checks on predicted entity deletion (#5993)
Co-authored-by: Ruddygreat <ruddygreat1@gmail.com>
2025-06-05 22:29:22 +10:00
Tayrtahn
44ea2cd396 Implement IEquatable for ResolvedPathSpecifier and ResolvedCollectionSpecifier (#5980) 2025-06-01 18:10:55 +10:00
metalgearsloth
2c5604432b Update some GetComponentName usages (#5942)
Rider tells me to use generic and generic one seems better.
2025-06-01 17:54:43 +10:00
Tayrtahn
c696466522 Remove ITileDefinition.ID (#5982) 2025-06-01 17:51:59 +10:00
slarticodefast
01bb98e400 fix static grid center of mass (#5985) 2025-05-29 23:05:54 +10:00
metalgearsloth
af08e747de Defer grid state handling TileChangedEvent (#5981)
Rather than doing the old raise-event-per-tile we just raise it at the end.
2025-05-29 09:21:45 +10:00
metalgearsloth
8c35c2c380 Version: 261.1.0 2025-05-29 00:15:26 +10:00
Tayrtahn
6d46d3f4a5 Cleanup most warnings in unit tests (#5946)
* 2 warnings in JointDeletion_Test

* 1 warning in Collision_Test

* 2 warnings in Color_Test (deleted test of deprecated HCY color space)

* 1 warning in MapVelocity_Test

* 2 warnings in MapManager_Tests

* 2 warnings in MapPauseTests

* 1 warning in NetDisconnectMessageTest

* 1 warning in ContainerTests

* Suppress 1 warning in EntityEventBusTests.ComponentEvent

* 4 warnings in MapGridMap_Tests

* 1 warning in GridDeletion_Test

* Remove TryGetContainingContainer foolishness

---------

Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2025-05-29 00:12:58 +10:00
Tayrtahn
50e06e43fa Automatic Sawmill generation for UIControllers (#5967) 2025-05-29 00:06:55 +10:00
metalgearsloth
986b0f979d Fix physics forces not autoclearing (#5978)
* Fix physics forces not autoclearing

* Changes
2025-05-29 00:04:55 +10:00
metalgearsloth
a51d786dee Version: 261.0.0 2025-05-28 19:34:47 +10:00
metalgearsloth
5f5fed5d6c Update contact xform usage (#5977)
Forgot this one as well.
2025-05-28 19:30:50 +10:00
Tayrtahn
e475cc7898 Cleanup warning in SpriteBoundsOverlay (#5944)
* Cleanup warning in SpriteBoundsOverlay

* Make better use of the primary constructor
2025-05-28 19:28:53 +10:00
metalgearsloth
ee8ea4ec3b Purge PhysicsMapComponent (#5766)
* Replace PhysicsMapComponent

- Dumb idea
- Lots of book-keeping and perf overhead.
- Much saner this way.

* stuff

* More work

* Purge

* Fixes

* Eh?

* Fixes

* Also this

* weh

* Fixes

* ice-cream

* Fix

* Fix stacking / gravity

* Gravity query

* MoveBuffer optimisations

* Fixes for test

* World gravity

* Fix build

* Avoid some transform resolves for contactless ents

* Less getcomps

* Fix contact caching

* Possibly less copies

* reh

* bulldoze

* Test "fix"

* seikrets

* a

* I saw this but now I decideded against it

* true
2025-05-28 19:18:36 +10:00
slarticodefast
7482451ec4 optimize ToMapCoordinates (#5953) 2025-05-28 12:07:23 +10:00
metalgearsloth
dddf5cd2fb Add entities to SpawnEntitiesAttachedTo (#5971)
* Add entities to SpawnEntitiesAttachedTo

Need it for biome stuff.

* factorio
2025-05-27 19:45:48 +10:00
metalgearsloth
01979c451d Make RaiseMoveEvent internal (#5918)
I don't think content should really be calling this tbh.
2025-05-27 19:45:04 +10:00
slarticodefast
181a5ef0b4 fix GetMapLinearVelocity (#5950)
* fix GetMapLinearVelocity

* resolve and adjust other methods
2025-05-27 19:41:43 +10:00
Princess Cheeseballs
e7c7011cc0 Init Commit (#5909) 2025-05-27 19:34:32 +10:00
metalgearsloth
dc97615fd4 Add some Box2i methods (#5969)
Equivalent to Box2.
2025-05-27 19:26:50 +10:00
metalgearsloth
3b4944376b Fix FastNoiseLite fractal bounding (#5970)
This shouldn't be datafielded because it gets set by other datafields.
2025-05-27 19:14:49 +10:00
Tayrtahn
fa6bd8f7ba Cleanup TypeSerializer Logger warnings (#5966) 2025-05-24 20:23:28 +02:00
Whatstone
2398cbcf26 GameController: init LocMgr before init broadcast (#5965) 2025-05-24 19:02:26 +02:00
Tayrtahn
38ce48a83f Cleanup 3 warnings in SharedContainerSystem (#5949)
* Cleanup 3 warnings in SharedContainerSystem

* Don't call Transform twice
2025-05-24 19:00:57 +02:00
PJB3005
4e7de2f272 File dialog fixes and improvements
File dialog requests can now specify the share and access mode they want out of the opened file. This means read-only access is now possible.

While doing this I noticed that the SDL3 backend had a memory leak *and* didn't match the behavior of the other backends. Cleaned up the code to avoid that.

In-engine commands that *can* specify read-only access on file open now do.
2025-05-24 16:38:01 +02:00
Cami
b61075c660 Stopped recursive updates for controls that are not visible, as it vi… (#5960)
* Stopped recursive updates for controls that are not visible, as it violates framerate in large menus.

* Update Robust.Client/UserInterface/Control.cs

---------

Co-authored-by: Cam <Nop>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
2025-05-23 17:42:47 +02:00
Tayrtahn
7b571dc80e Fix 2 instances of warning CS0162 (#5951) 2025-05-23 17:37:08 +02:00
TemporalOroboros
f1c76ca899 Remove unused obsolete TryGetContainingContainer override (#5660)
* Remove unused obsolete TryGetContainingContainer override

* poke tests

---------

Co-authored-by: PJB3005 <pieterjan.briers+git@gmail.com>
2025-05-23 17:36:38 +02:00
157 changed files with 2276 additions and 1699 deletions

View File

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

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,149 @@ END TEMPLATE-->
*None yet*
## 263.0.1
## 263.0.0
### Breaking changes
* Fully removed some non-`Entity<T>` container methods.
### New features
* `IMidiRenderer.LoadSoundfont` has been split into `LoadSoundfontResource` and `LoadSoundfontUser`, the original now being deprecated.
* Client command execution now properly catches errors instead of letting them bubble up through the input stack.
* Added `CompletionHelper.PrototypeIdsLimited` API to allow commands to autocomplete entity prototype IDs.
* Added `spawn:in` Toolshed command.
* Added `MapLoaderSystem.TryLoadGeneric` overload to load from a `Stream`.
* Added `OutputPanel.GetMessage()` and `OutputPanel.SetMessage()` to allow replacing individual messages.
### Bugfixes
* Fixed debug asserts when using MIDI on Windows.
* Fixed an error getting logged on startup on macOS related to window icons.
* `CC-BY-NC-ND-4.0` is now a valid license for the RGA validator.
* Fixed `TabContainer.CurrentTab` clamping against the wrong value.
* Fix culture-based parsing in `TimespanSerializer`.
* Fixed grid rendering blowing up on tile IDs that aren't registered.
* Fixed debug assert when loading MIDI soundfonts on Windows.
* Make `ColorSelectorSliders` properly update the dropdown when changing `SelectorType`.
* Fixed `tpto` allowing teleports to oneself, thereby causing them to be deleted.
* Fix OpenAL extensions being requested incorrectly, causing an error on macOS.
* Fixed horizontal measuring of markup controls in rich text.
### Other
* Improved logging for some audio entity errors.
* Avoided more server stutters when using `csci`.
* Improved physics performance.
* Made various localization functions like `GENDER()` not throw if passed a string instead of an `EntityUid`.
* The generic clause on `EntitySystem.AddComp<T>` has been changed to `IComponent` (from `Component`) for consistency with `IEntityManager.AddComponent<T>`.
* `DataDefinitionAnalyzer` has been optimized somewhat.
* Improved assert logging error message when static data fields are encountered.
### Internal
* Warning cleanup.
* Added more tests for `DataDefinitionAnalyzer`.
* Consistently use `EntitySystem` proxy methods in engine.
## 262.0.0
### Breaking changes
* Toolshed commands will now validate that each non-generic command argument is parseable (i.e., has a corresponding type parser). This check can be disabled by explicitly marking the argument as unparseable via `CommandArgumentAttribute.Unparseable`.
### New features
* `ToolshedManager.TryParse` now also supports nullable value types.
* Add an ignoredComponents arg to IsDefault.
### Bugfixes
* Fix `SpriteComponent.Layer.Visible` setter not marking a sprite's bounding box as dirty.
* The audio params in the passed SoundSpecifier for PlayStatic(SoundSpecifier, Filter, ...) will now be used as a default like other PlayStatic overrides.
* Fix windows not saving their positions correctly when their x position is <= 0.
* Fix transform state handling overriding PVS detachment.
## 261.2.0
### New features
* Implement IEquatable for ResolvedPathSpecifier & ResolvedCollectionSpecifier.
* Add NearestChunkEnumerator.
### Bugfixes
* Fix static entities not having the center of mass updated.
* Fix TryQueueDelete.
* Fix tpto potentially parenting grids to non-map entities.
### Other
* TileChangedEvent is now raised once in clientside grid state handling rather than per tile.
* Removed ITileDefinition.ID as it was redundant.
* Change the lifestage checks on predicted entity deletion to check for terminating.
### Internal
* Update some `GetComponentName<T>` uses to generic.
## 261.1.0
### New features
* Automatically create logger sawmills for `UIController`s similar to `EntitySystem`s.
### Bugfixes
* Fix physics forces not auto-clearing / respecting the cvar.
### Internal
* Cleanup more compiler warnings in unit tests.
## 261.0.0
### Breaking changes
* Remove unused TryGetContainingContainer override.
* Stop recursive FrameUpdates for controls that are not visible.
* Initialize LocMgr earlier in the callstack for GameController.
* Fix FastNoiseLise fractal bounding and remove its DataField property as it should be derived on other properties updating.
* Make RaiseMoveEvent internal.
* MovedGridsComponent and PhysicsMapComponent are now purged and properties on `SharedPhysicsSystem`. Additionally the TransformComponent for Awake entities is stored alongside the PhysicsComponent for them.
* TransformComponent is now stored on physics contacts.
* Gravity2DComponent and Gravity2DController were moved to SharedPhysicsSystem.
### New features
* `IFileDialogManager` now allows specifying `FileAccess` and `FileShare` modes.
* Add Intersects and Enlarged to Box2i in line with Box2.
* Make `KeyFrame`s on `AnimationTrackProperty` public settable.
* Add the spawned entities to a returned array from `SpawnEntitiesAttachedTo`.
### Bugfixes
* Fixed SDL3 file dialog implementation having a memory leak and not opening files read-write.
* Fix GetMapLinearVelocity.
### Other
* `uploadfile` and `loadprototype` commands now only open files with read access.
* Optimize `ToMapCoordinates`.
### Internal
* Cleanup on internals of `IFileDialogManager`, removing duplicate code.
* Fix Contacts not correctly being marked as `Touching` while contact is ongoing.
## 260.2.0
### New features

View File

@@ -195,6 +195,8 @@ command-description-spawn-at =
Spawns an entity at the given coordinates.
command-description-spawn-on =
Spawns an entity on the given entity, at it's coordinates.
command-description-spawn-in =
Spawns an entity in the given container on the given entity, dropping it at its coordinates if it doesn't fit
command-description-spawn-attached =
Spawns an entity attached to the given entity, at (0 0) relative to it.
command-description-mappos =

View File

@@ -55,7 +55,7 @@ public sealed class DataDefinitionAnalyzerTest
namespace Robust.Shared.Serialization.Manager.Attributes
{
public class DataFieldBaseAttribute : Attribute;
public class DataFieldAttribute : DataFieldBaseAttribute;
public class DataFieldAttribute(string? tag = null) : DataFieldBaseAttribute;
public sealed class DataDefinitionAttribute : Attribute;
public sealed class NotYamlSerializableAttribute : Attribute;
}
@@ -117,6 +117,61 @@ public sealed class DataDefinitionAnalyzerTest
);
}
[Test]
public async Task PartialDataDefinitionTest()
{
const string code = """
using Robust.Shared.Serialization.Manager.Attributes;
[DataDefinition]
public sealed class Foo { }
""";
await Verifier(code,
// /0/Test0.cs(4,15): error RA0017: Type Foo is a DataDefinition but is not partial
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataDefinitionPartialRule).WithSpan(4, 15, 4, 20).WithArguments("Foo")
);
}
[Test]
public async Task NestedPartialDataDefinitionTest()
{
const string code = """
using Robust.Shared.Serialization.Manager.Attributes;
public sealed class Foo
{
[DataDefinition]
public sealed partial class Nested { }
}
""";
await Verifier(code,
// /0/Test0.cs(3,15): error RA0018: Type Foo contains nested data definition Nested but is not partial
VerifyCS.Diagnostic(DataDefinitionAnalyzer.NestedDataDefinitionPartialRule).WithSpan(3, 15, 3, 20).WithArguments("Foo", "Nested")
);
}
[Test]
public async Task RedundantDataFieldTagTest()
{
const string code = """
using Robust.Shared.Serialization.Manager.Attributes;
[DataDefinition]
public sealed partial class Foo
{
[DataField("someValue")]
public int SomeValue;
}
""";
await Verifier(code,
// /0/Test0.cs(6,6): info RA0027: Data field SomeValue in data definition Foo has an explicitly set tag that matches autogenerated tag
VerifyCS.Diagnostic(DataDefinitionAnalyzer.DataFieldRedundantTagRule).WithSpan(6, 6, 6, 28).WithArguments("SomeValue", "Foo")
);
}
[Test]
public async Task ReadOnlyPropertyTest()
{

View File

@@ -22,7 +22,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private const string DataFieldAttributeName = "DataField";
private const string ViewVariablesAttributeName = "ViewVariables";
private static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
public static readonly DiagnosticDescriptor DataDefinitionPartialRule = new(
Diagnostics.IdDataDefinitionPartial,
"Type must be partial",
"Type {0} is a DataDefinition but is not partial",
@@ -32,7 +32,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to mark any type that is a data definition as partial."
);
private static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
public static readonly DiagnosticDescriptor NestedDataDefinitionPartialRule = new(
Diagnostics.IdNestedDataDefinitionPartial,
"Type must be partial",
"Type {0} contains nested data definition {1} but is not partial",
@@ -62,7 +62,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
"Make sure to add a setter."
);
private static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
public static readonly DiagnosticDescriptor DataFieldRedundantTagRule = new(
Diagnostics.IdDataFieldRedundantTag,
"Data field has redundant tag specified",
"Data field {0} in data definition {1} has an explicitly set tag that matches autogenerated tag",
@@ -102,14 +102,23 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
context.RegisterSymbolStartAction(symbolContext =>
{
if (symbolContext.Symbol is not INamedTypeSymbol typeSymbol)
return;
context.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
context.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
if (!IsDataDefinition(typeSymbol))
return;
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.ClassDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.StructDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.RecordStructDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataDefinition, SyntaxKind.InterfaceDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataField, SyntaxKind.FieldDeclaration);
symbolContext.RegisterSyntaxNodeAction(AnalyzeDataFieldProperty, SyntaxKind.PropertyDeclaration);
}, SymbolKind.NamedType);
}
private void AnalyzeDataDefinition(SyntaxNodeAnalysisContext context)
@@ -117,8 +126,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (context.Node is not TypeDeclarationSyntax declaration)
return;
var type = context.SemanticModel.GetDeclaredSymbol(declaration)!;
if (!IsDataDefinition(type))
if (context.ContainingSymbol is not INamedTypeSymbol type)
return;
if (!IsPartial(declaration))
@@ -129,7 +137,7 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
var containingType = type.ContainingType;
while (containingType != null)
{
var containingTypeDeclaration = (TypeDeclarationSyntax) containingType.DeclaringSyntaxReferences[0].GetSyntax();
var containingTypeDeclaration = (TypeDeclarationSyntax)containingType.DeclaringSyntaxReferences[0].GetSyntax();
if (!IsPartial(containingTypeDeclaration))
{
context.ReportDiagnostic(Diagnostic.Create(NestedDataDefinitionPartialRule, containingTypeDeclaration.Keyword.GetLocation(), containingType.Name, type.Name));
@@ -144,27 +152,26 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (context.Node is not FieldDeclarationSyntax field)
return;
var typeDeclaration = field.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration == null)
return;
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
if (!IsDataDefinition(type))
if (context.ContainingSymbol?.ContainingType is not INamedTypeSymbol type)
return;
foreach (var variable in field.Declaration.Variables)
{
var fieldSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (fieldSymbol == null)
continue;
if (!IsDataField(fieldSymbol, out _, out var datafieldAttribute))
continue;
if (IsReadOnlyDataField(type, fieldSymbol))
{
TryGetModifierLocation(field, SyntaxKind.ReadOnlyKeyword, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldWritableRule, location, fieldSymbol.Name, type.Name));
}
if (HasRedundantTag(fieldSymbol))
if (HasRedundantTag(fieldSymbol, datafieldAttribute))
{
TryGetAttributeLocation(field, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, fieldSymbol.Name, type.Name));
@@ -196,25 +203,28 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
if (context.Node is not PropertyDeclarationSyntax property)
return;
var typeDeclaration = property.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration == null)
if (context.ContainingSymbol is not IPropertySymbol propertySymbol)
return;
var type = context.SemanticModel.GetDeclaredSymbol(typeDeclaration)!;
if (!IsDataDefinition(type) || type.IsRecord || type.IsValueType)
if (propertySymbol.ContainingType is not INamedTypeSymbol type)
return;
if (type.IsRecord || type.IsValueType)
return;
var propertySymbol = context.SemanticModel.GetDeclaredSymbol(property);
if (propertySymbol == null)
return;
if (!IsDataField(propertySymbol, out _, out var datafieldAttribute))
return;
if (IsReadOnlyDataField(type, propertySymbol))
{
var location = property.AccessorList != null ? property.AccessorList.GetLocation() : property.GetLocation();
context.ReportDiagnostic(Diagnostic.Create(DataFieldPropertyWritableRule, location, propertySymbol.Name, type.Name));
}
if (HasRedundantTag(propertySymbol))
if (HasRedundantTag(propertySymbol, datafieldAttribute))
{
TryGetAttributeLocation(property, DataFieldAttributeName, out var location);
context.ReportDiagnostic(Diagnostic.Create(DataFieldRedundantTagRule, location, propertySymbol.Name, type.Name));
@@ -242,9 +252,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static bool IsReadOnlyDataField(ITypeSymbol type, ISymbol field)
{
if (!IsDataField(field, out _, out _))
return false;
return IsReadOnlyMember(type, field);
}
@@ -369,17 +376,14 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
return false;
}
private static bool HasRedundantTag(ISymbol symbol)
private static bool HasRedundantTag(ISymbol symbol, AttributeData datafieldAttribute)
{
if (!IsDataField(symbol, out var _, out var attribute))
return false;
// No args, no problem
if (attribute.ConstructorArguments.Length == 0)
if (datafieldAttribute.ConstructorArguments.Length == 0)
return false;
// If a tag is explicitly specified, it will be the first argument...
var tagArgument = attribute.ConstructorArguments[0];
var tagArgument = datafieldAttribute.ConstructorArguments[0];
// ...but the first arg could also something else, since tag is optional
// so we make sure that it's a string
if (tagArgument.Value is not string explicitName)
@@ -394,9 +398,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static bool HasVVReadWrite(ISymbol symbol)
{
if (!IsDataField(symbol, out _, out _))
return false;
// Make sure it has ViewVariablesAttribute
AttributeData? viewVariablesAttribute = null;
foreach (var attr in symbol.GetAttributes())
@@ -422,9 +423,6 @@ public sealed class DataDefinitionAnalyzer : DiagnosticAnalyzer
private static bool IsNotYamlSerializable(ISymbol field, ITypeSymbol type)
{
if (!IsDataField(field, out _, out _))
return false;
return HasAttribute(type, NotYamlSerializableName);
}

View File

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

View File

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

View File

@@ -13,7 +13,7 @@ namespace Robust.Client.Animations
/// </summary>
public abstract class AnimationTrackProperty : AnimationTrack
{
public List<KeyFrame> KeyFrames { get; protected set; } = new();
public List<KeyFrame> KeyFrames { get; set; } = new();
/// <summary>
/// How to interpolate values when between two keyframes.

View File

@@ -57,8 +57,8 @@ internal sealed partial class AudioManager : IAudioInternal
_checkAlError();
// Load up AL context extensions.
var s = ALC.GetString(ALDevice.Null, AlcGetString.Extensions) ?? "";
foreach (var extension in s.Split(' '))
var s = ALC.GetString(_openALDevice, AlcGetString.Extensions) ?? "";
foreach (var extension in s.Split(' ', StringSplitOptions.RemoveEmptyEntries))
{
_alContextExtensions.Add(extension);
}

View File

@@ -582,7 +582,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
if (TerminatingOrDeleted(entity))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entity)}");
LogAudioPlaybackOnInvalidEntity(specifier, entity);
return null;
}
@@ -626,7 +626,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}");
LogAudioPlaybackOnInvalidEntity(specifier, coordinates.EntityId);
return null;
}
@@ -753,6 +753,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return _resourceCache.GetResource<AudioResource>(filename).AudioStream.Length;
}
private void LogAudioPlaybackOnInvalidEntity(ResolvedSoundSpecifier? specifier, EntityUid entityId)
{
var soundInfo = specifier?.ToString() ?? "unknown sound";
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entityId)}. Sound: {soundInfo}. Trace: {Environment.StackTrace}");
}
#region Jobs
private record struct UpdateAudioJob : IParallelRobustJob

View File

@@ -6,6 +6,7 @@ using Robust.Shared.Audio.Midi;
using Robust.Shared.Audio.Sources;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Utility;
namespace Robust.Client.Audio.Midi;
@@ -156,8 +157,13 @@ public interface IMidiRenderer : IDisposable
/// <summary>
/// Loads a new soundfont into the renderer.
/// </summary>
[Obsolete("Use LoadSoundfontResource or LoadSoundfontUser instead")]
void LoadSoundfont(string filename, bool resetPresets = false);
void LoadSoundfontResource(ResPath path, bool resetPresets = false);
void LoadSoundfontUser(ResPath path, bool resetPresets = false);
/// <summary>
/// Invoked whenever a new midi event is registered.
/// </summary>

View File

@@ -0,0 +1,262 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using NFluidsynth;
using Robust.Shared.ContentPack;
using Robust.Shared.Utility;
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiManager
{
// For loading sound fonts, we have to use a callback model where we can only parse a string.
// This API, frankly, fucking sucks.
//
// These prefixes are used to separate the various places a file *can* be loaded from.
//
// We cannot prevent Fluidsynth from trying to load prefixed paths itself if they are invalid
// So if content specifies "/foobar.sf2" to be loaded and it doesn't exist,
// Fluidsynth *will* try to fopen("RES:/foobar.sf2"). For this reason I'm putting in some nonsense characters
// that will pass through Fluidsynth fine, but make sure the filename is *never* a practically valid OS path.
//
// NOTE: Raw disk paths *cannot* be prefixed as Fluidsynth needs to load those itself.
// Specifically, their .dls loader doesn't respect file callbacks.
// If you're curious why this is: it's two-fold:
// * The Fluidsynth C code for the .dls loader just doesn't use the file callbacks, period.
// * Even if it did, we're not specifying those file callbacks, as they're per loader,
// and we're only adding a *new* sound font loader with file callbacks, not modifying the existing ones.
// The loader for .sfX format and .dls format are different loader objects in Fluidsynth.
internal const string PrefixCommon = "!/ -?\x0001";
internal const string PrefixLegacy = PrefixCommon + "LEGACY";
internal const string PrefixUser = PrefixCommon + "USER";
internal const string PrefixResources = PrefixCommon + "RES";
private void LoadSoundFontSetup(MidiRenderer renderer)
{
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfontResource(FallbackSoundfont);
// Load system-specific soundfonts.
if (OperatingSystem.IsLinux())
{
foreach (var filepath in LinuxSoundfonts)
{
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath))
continue;
try
{
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
renderer.LoadSoundfontDisk(filepath);
}
catch (Exception)
{
continue;
}
break;
}
}
else if (OperatingSystem.IsMacOS())
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
renderer.LoadSoundfontDisk(OsxSoundfont);
}
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
renderer.LoadSoundfontDisk(WindowsSoundfont);
}
}
// Maybe load soundfont specified in environment variable.
// Load it here so it can override system soundfonts but not content or user data soundfonts.
if (Environment.GetEnvironmentVariable(SoundfontEnvironmentVariable) is { } soundfontOverride)
{
// Just to avoid funny shit: avoid people smuggling a prefix in here.
// I wish I could separate this properly...
var (prefix, _) = SplitPrefix(soundfontOverride);
if (IsValidPrefix(prefix))
{
_midiSawmill.Error($"Not respecting {SoundfontEnvironmentVariable} env variable: invalid file path");
}
else if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
{
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
renderer.LoadSoundfontDisk(soundfontOverride);
}
}
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading content soundfont {file}");
renderer.LoadSoundfontResource(file);
}
// Load every soundfont from the user data directory last, since those may override any other soundfont.
_midiSawmill.Debug($"Loading soundfonts from user data directory {CustomSoundfontDirectory}");
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
foreach (var file in enumerator)
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading user soundfont {file}");
renderer.LoadSoundfontUser(file);
}
}
internal static string PrefixPath(string prefix, string value)
{
return $"{prefix}:{value}";
}
internal static (string prefix, string? value) SplitPrefix(string filename)
{
var filenameSplit = filename.Split(':', 2);
if (filenameSplit.Length == 1)
return (filenameSplit[0], null);
return (filenameSplit[0], filenameSplit[1]);
}
internal static bool IsValidPrefix(string prefix)
{
return prefix is PrefixLegacy or PrefixUser or PrefixResources;
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
{
private readonly MidiManager _parent;
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public ResourceLoaderCallbacks(MidiManager parent)
{
_parent = parent;
}
public override IntPtr Open(string filename)
{
if (string.IsNullOrEmpty(filename))
{
return IntPtr.Zero;
}
Stream stream;
try
{
stream = OpenCore(filename);
}
catch (Exception e)
{
_parent._midiSawmill.Error($"Error while opening sound font: {e}");
return IntPtr.Zero;
}
var id = _nextStreamId++;
_openStreams.Add(id, stream);
return (IntPtr) id;
}
private Stream OpenCore(string filename)
{
var (prefix, value) = SplitPrefix(filename);
if (!IsValidPrefix(prefix) || value == null)
return File.OpenRead(filename);
var resourceCache = _parent._resourceManager;
var resourcePath = new ResPath(value);
switch (prefix)
{
case PrefixUser:
return resourceCache.UserData.OpenRead(resourcePath);
case PrefixResources:
return resourceCache.ContentFileRead(resourcePath);
case PrefixLegacy:
// Try resources first, then try user data.
if (resourceCache.TryContentFileRead(resourcePath, out var stream))
return stream;
return resourceCache.UserData.OpenRead(resourcePath);
default:
throw new UnreachableException("Invalid prefix specified!");
}
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
stream.ReadExact(buffer);
buffer.CopyTo(span);
}
else
{
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
}
catch (EndOfStreamException)
{
return -1;
}
return 0;
}
public override int Seek(IntPtr sfHandle, long offset, SeekOrigin origin)
{
var stream = _openStreams[(int) sfHandle];
stream.Seek(offset, origin);
return 0;
}
public override long Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
return (long) stream.Position;
}
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
return 0;
}
}
}

View File

@@ -119,7 +119,7 @@ internal sealed partial class MidiManager : IMidiManager
private const string OsxSoundfont =
"/System/Library/Components/CoreAudio.component/Contents/Resources/gs_instruments.dls";
private const string FallbackSoundfont = "/Midi/fallback.sf2";
private static readonly ResPath FallbackSoundfont = new ResPath("/Midi/fallback.sf2");
private const string ContentCustomSoundfontDirectory = "/Audio/MidiCustom/";
@@ -265,81 +265,7 @@ internal sealed partial class MidiManager : IMidiManager
var renderer = new MidiRenderer(_settings!, soundfontLoader, mono, this, _audio, _taskManager, _midiSawmill);
_midiSawmill.Debug($"Loading fallback soundfont {FallbackSoundfont}");
// Since the last loaded soundfont takes priority, we load the fallback soundfont before the soundfont.
renderer.LoadSoundfont(FallbackSoundfont);
// Load system-specific soundfonts.
if (OperatingSystem.IsLinux())
{
foreach (var filepath in LinuxSoundfonts)
{
if (!File.Exists(filepath) || !SoundFont.IsSoundFont(filepath))
continue;
try
{
_midiSawmill.Debug($"Loading OS soundfont {filepath}");
renderer.LoadSoundfont(filepath);
}
catch (Exception)
{
continue;
}
break;
}
}
else if (OperatingSystem.IsMacOS())
{
if (File.Exists(OsxSoundfont) && SoundFont.IsSoundFont(OsxSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {OsxSoundfont}");
renderer.LoadSoundfont(OsxSoundfont);
}
}
else if (OperatingSystem.IsWindows())
{
if (File.Exists(WindowsSoundfont) && SoundFont.IsSoundFont(WindowsSoundfont))
{
_midiSawmill.Debug($"Loading OS soundfont {WindowsSoundfont}");
renderer.LoadSoundfont(WindowsSoundfont);
}
}
// Maybe load soundfont specified in environment variable.
// Load it here so it can override system soundfonts but not content or user data soundfonts.
if (Environment.GetEnvironmentVariable(SoundfontEnvironmentVariable) is {} soundfontOverride)
{
if (File.Exists(soundfontOverride) && SoundFont.IsSoundFont(soundfontOverride))
{
_midiSawmill.Debug($"Loading environment variable soundfont {soundfontOverride}");
renderer.LoadSoundfont(soundfontOverride);
}
}
// Load content-specific custom soundfonts, which should override the system/fallback soundfont.
_midiSawmill.Debug($"Loading soundfonts from content directory {ContentCustomSoundfontDirectory}");
foreach (var file in _resourceManager.ContentFindFiles(ContentCustomSoundfontDirectory))
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading content soundfont {file}");
renderer.LoadSoundfont(file.ToString());
}
var userDataPath = _resourceManager.UserData.RootDir == null
? CustomSoundfontDirectory
: new ResPath(_resourceManager.UserData.RootDir) / CustomSoundfontDirectory.ToRelativePath();
// Load every soundfont from the user data directory last, since those may override any other soundfont.
_midiSawmill.Debug($"Loading soundfonts from user data directory {userDataPath}");
var enumerator = _resourceManager.UserData.Find($"{CustomSoundfontDirectory.ToRelativePath()}*").Item1;
foreach (var file in enumerator)
{
if (file.Extension != "sf2" && file.Extension != "dls" && file.Extension != "sf3") continue;
_midiSawmill.Debug($"Loading user soundfont {file}");
renderer.LoadSoundfont(file.ToString());
}
LoadSoundFontSetup(renderer);
renderer.Source.Gain = _gain;
@@ -572,130 +498,6 @@ internal sealed partial class MidiManager : IMidiManager
midiEvent.Velocity);
}
/// <summary>
/// This class is used to load soundfonts.
/// </summary>
private sealed class ResourceLoaderCallbacks : SoundFontLoaderCallbacks
{
private readonly MidiManager _parent;
private readonly Dictionary<int, Stream> _openStreams = new();
private int _nextStreamId = 1;
public ResourceLoaderCallbacks(MidiManager parent)
{
_parent = parent;
}
public override IntPtr Open(string filename)
{
if (string.IsNullOrEmpty(filename))
{
return IntPtr.Zero;
}
Stream? stream;
var resourceCache = _parent._resourceManager;
var resourcePath = new ResPath(filename);
if (resourcePath.IsRooted)
{
// is it in content?
if (resourceCache.ContentFileExists(filename))
{
if (!resourceCache.TryContentFileRead(filename, out stream))
return IntPtr.Zero;
}
// is it in userdata?
else if (resourceCache.UserData.Exists(resourcePath))
{
stream = resourceCache.UserData.OpenRead(resourcePath);
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
}
else
{
return IntPtr.Zero;
}
}
else if (File.Exists(filename))
{
stream = File.OpenRead(filename);
}
else
{
return IntPtr.Zero;
}
var id = _nextStreamId++;
_openStreams.Add(id, stream);
return (IntPtr) id;
}
public override unsafe int Read(IntPtr buf, long count, IntPtr sfHandle)
{
var length = (int) count;
var span = new Span<byte>(buf.ToPointer(), length);
var stream = _openStreams[(int) sfHandle];
// Fluidsynth's docs state that this method should leave the buffer unmodified if it fails. (returns -1)
try
{
// Fluidsynth does a LOT of tiny allocations (frankly, way too much).
if (count < 1024)
{
// ReSharper disable once SuggestVarOrType_Elsewhere
Span<byte> buffer = stackalloc byte[(int)count];
stream.ReadExact(buffer);
buffer.CopyTo(span);
}
else
{
var buffer = stream.ReadExact(length);
buffer.CopyTo(span);
}
}
catch (EndOfStreamException)
{
return -1;
}
return 0;
}
public override int Seek(IntPtr sfHandle, long offset, SeekOrigin origin)
{
var stream = _openStreams[(int) sfHandle];
stream.Seek(offset, origin);
return 0;
}
public override long Tell(IntPtr sfHandle)
{
var stream = _openStreams[(int) sfHandle];
return (long) stream.Position;
}
public override int Close(IntPtr sfHandle)
{
if (!_openStreams.Remove((int) sfHandle, out var stream))
return -1;
stream.Dispose();
return 0;
}
}
#region Jobs
private record struct MidiUpdateJob : IParallelRobustJob

View File

@@ -0,0 +1,45 @@
using System;
using Robust.Shared.Utility;
namespace Robust.Client.Audio.Midi;
internal sealed partial class MidiRenderer
{
[Obsolete("Use LoadSoundfontResource or LoadSoundfontUser instead")]
public void LoadSoundfont(string filename, bool resetPresets = true)
{
LoadSoundfontCore(
MidiManager.PrefixPath(MidiManager.PrefixLegacy, filename),
resetPresets);
}
public void LoadSoundfontResource(ResPath path, bool resetPresets = false)
{
LoadSoundfontCore(
MidiManager.PrefixPath(MidiManager.PrefixResources, path.ToString()),
resetPresets);
}
public void LoadSoundfontUser(ResPath path, bool resetPresets = false)
{
LoadSoundfontCore(
MidiManager.PrefixPath(MidiManager.PrefixUser, path.ToString()),
resetPresets);
}
internal void LoadSoundfontDisk(string path, bool resetPresets = false)
{
LoadSoundfontCore(
path,
resetPresets);
}
private void LoadSoundfontCore(string filenameString, bool resetPresets)
{
lock (_playerStateLock)
{
_synth.LoadSoundFont(filenameString, resetPresets);
MidiSoundfont = 1;
}
}
}

View File

@@ -16,7 +16,7 @@ using Robust.Shared.ViewVariables;
namespace Robust.Client.Audio.Midi;
internal sealed class MidiRenderer : IMidiRenderer
internal sealed partial class MidiRenderer : IMidiRenderer
{
private readonly IMidiManager _midiManager;
private readonly ITaskManager _taskManager;
@@ -435,15 +435,6 @@ internal sealed class MidiRenderer : IMidiRenderer
_sequencer.RemoveEvents(SequencerClientId.Wildcard, SequencerClientId.Wildcard, -1);
}
public void LoadSoundfont(string filename, bool resetPresets = true)
{
lock (_playerStateLock)
{
_synth.LoadSoundFont(filename, resetPresets);
MidiSoundfont = 1;
}
}
void IMidiRenderer.Render()
{
Render();

View File

@@ -21,7 +21,7 @@ public sealed class SpriteTreeSystem : ComponentTreeSystem<SpriteTreeComponent,
protected override Box2 ExtractAabb(in ComponentTreeEntry<SpriteComponent> entry, Vector2 pos, Angle rot)
{
// TODO SPRITE optimize this
// Because the just take the BB of the rotated BB, I'mt pretty sure we do a lot of unnecessary maths.
// Because the just take the BB of the rotated BB, I'm pretty sure we do a lot of unnecessary maths.
return _sprite.CalculateBounds((entry.Uid, entry.Component), pos, rot, default).CalcBoundingBox();
}

View File

@@ -191,8 +191,16 @@ namespace Robust.Client.Console
var shell = new ConsoleShell(this, session ?? _player.LocalSession, session == null);
var cmdArgs = args.ToArray();
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
cmd.Execute(shell, command, cmdArgs);
try
{
AnyCommandExecuted?.Invoke(shell, commandName, command, cmdArgs);
cmd.Execute(shell, command, cmdArgs);
}
catch (Exception e)
{
_conLogger.Error($"ExecuteError - {command}:\n{e}");
shell.WriteError($"There was an error while executing the command: {e}");
}
}
private bool CanExecute(string cmdName)

View File

@@ -82,7 +82,7 @@ namespace Robust.Client.Debugging
foreach (var ent in _mapSystem.GetAnchoredEntities(gridUid, grid, spot))
{
if (EntityManager.TryGetComponent<MetaDataComponent>(ent, out var meta))
if (TryComp<MetaDataComponent>(ent, out var meta))
{
text.AppendLine($"uid: {ent}, {meta.EntityName}");
}

View File

@@ -160,6 +160,7 @@ namespace Robust.Client
}
_serializationManager.Initialize();
_loc.Initialize();
// Call Init in game assemblies.
_modLoader.BroadcastRunLevel(ModRunLevel.PreInit);
@@ -182,7 +183,6 @@ namespace Robust.Client
_serializer.Initialize();
_inputManager.Initialize();
_console.Initialize();
_loc.Initialize();
// Make sure this is done before we try to load prototypes,
// avoid any possibility of race conditions causing the check to not finish
@@ -387,7 +387,7 @@ namespace Robust.Client
_prof.Initialize();
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null);
_resManager.Initialize(Options.LoadConfigAndUserData ? userDataDir : null, hideUserDataDir: true);
var mountOptions = _commandLineArgs != null
? MountOptions.Merge(_commandLineArgs.MountOptions, Options.MountOptions)

View File

@@ -296,7 +296,7 @@ namespace Robust.Client.GameObjects
public override void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
{
if (!MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityDeleted
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;
@@ -322,7 +322,7 @@ namespace Robust.Client.GameObjects
{
if (IsQueuedForDeletion(ent.Owner)
|| !MetaQuery.Resolve(ent.Owner, ref ent.Comp1)
|| ent.Comp1.EntityDeleted
|| ent.Comp1.EntityLifeStage >= EntityLifeStage.Terminating
|| !TransformQuery.Resolve(ent.Owner, ref ent.Comp2))
{
return;

View File

@@ -24,9 +24,7 @@ namespace Robust.Client.GameObjects
public sealed class SpriteBoundsSystem : EntitySystem
{
[Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly SpriteTreeSystem _spriteTree = default!;
private SpriteBoundsOverlay? _overlay;
@@ -42,7 +40,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
DebugTools.AssertNull(_overlay);
_overlay = new SpriteBoundsOverlay(_spriteTree, _xformSystem);
_overlay = new SpriteBoundsOverlay(EntityManager);
_overlayManager.AddOverlay(_overlay);
}
else
@@ -57,18 +55,13 @@ namespace Robust.Client.GameObjects
private bool _enabled;
}
public sealed class SpriteBoundsOverlay : Overlay
public sealed class SpriteBoundsOverlay(IEntityManager entMan) : Overlay
{
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly SharedTransformSystem _xformSystem;
private SpriteTreeSystem _renderTree;
public SpriteBoundsOverlay(SpriteTreeSystem renderTree, SharedTransformSystem xformSystem)
{
_renderTree = renderTree;
_xformSystem = xformSystem;
}
private readonly SharedTransformSystem _xformSystem = entMan.System<SharedTransformSystem>();
private readonly SpriteSystem _spriteSystem = entMan.System<SpriteSystem>();
private readonly SpriteTreeSystem _renderTree = entMan.System<SpriteTreeSystem>();
protected internal override void Draw(in OverlayDrawArgs args)
{
@@ -76,10 +69,11 @@ namespace Robust.Client.GameObjects
var currentMap = args.MapId;
var viewport = args.WorldBounds;
foreach (var (sprite, xform) in _renderTree.QueryAabb(currentMap, viewport))
foreach (var entry in _renderTree.QueryAabb(currentMap, viewport))
{
var (sprite, xform) = entry;
var (worldPos, worldRot) = _xformSystem.GetWorldPositionRotation(xform);
var bounds = sprite.CalculateRotatedBoundingBox(worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
var bounds = _spriteSystem.CalculateBounds((entry.Uid, sprite), worldPos, worldRot, args.Viewport.Eye?.Rotation ?? default);
// Get scaled down bounds used to indicate the "south" of a sprite.
var localBound = bounds.Box;

View File

@@ -1224,6 +1224,8 @@ namespace Robust.Client.GameObjects
return;
_visible = value;
Owner.Comp.BoundsDirty = true;
// ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract
if (_parent.Owner != EntityUid.Invalid)
Owner.Comp.Sys?.QueueUpdateIsInert(Owner);
@@ -1791,76 +1793,15 @@ namespace Robust.Client.GameObjects
[Obsolete("Use SpriteSystem.GetPrototypeTextures() instead")]
public static IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype prototype, IResourceCache resourceCache, out bool noRot)
{
var results = new List<IDirectionalTextureProvider>();
noRot = false;
// TODO when moving to a non-static method in a system, pass in IComponentFactory
if (prototype.TryGetComponent(out IconComponent? icon))
{
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
results.Add(sys.GetIcon(icon));
return results;
}
if (!prototype.Components.TryGetValue("Sprite", out _))
{
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
var entityManager = IoCManager.Resolve<IEntityManager>();
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
EntitySystem.Get<AppearanceSystem>().OnChangeData(dummy, spriteComponent);
foreach (var layer in spriteComponent.AllLayers)
{
if (!layer.Visible) continue;
if (layer.Texture != null)
{
results.Add(layer.Texture);
continue;
}
if (!layer.RsiState.IsValid) continue;
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
if (rsi == null ||
!rsi.TryGetState(layer.RsiState, out var state))
continue;
results.Add(state);
}
noRot = spriteComponent.NoRotation;
entityManager.DeleteEntity(dummy);
if (results.Count == 0)
results.Add(resourceCache.GetFallback<TextureResource>().Texture);
return results;
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
return sys.GetPrototypeTextures(prototype, out noRot);
}
[Obsolete("Use SpriteSystem.GetPrototypeIcon() instead")]
public static IRsiStateLike GetPrototypeIcon(EntityPrototype prototype, IResourceCache resourceCache)
{
var sys = IoCManager.Resolve<IEntitySystemManager>().GetEntitySystem<SpriteSystem>();
// TODO when moving to a non-static method in a system, pass in IComponentFactory
if (prototype.TryGetComponent(out IconComponent? icon))
return sys.GetIcon(icon);
if (!prototype.Components.ContainsKey("Sprite"))
return sys.GetFallbackState();
var entityManager = IoCManager.Resolve<IEntityManager>();
var dummy = entityManager.SpawnEntity(prototype.ID, MapCoordinates.Nullspace);
var spriteComponent = entityManager.EnsureComponent<SpriteComponent>(dummy);
var result = spriteComponent.Icon ?? sys.GetFallbackState();
entityManager.DeleteEntity(dummy);
return result;
return sys.GetPrototypeIcon(prototype);
}
}
}

View File

@@ -97,7 +97,7 @@ namespace Robust.Client.GameObjects
[Obsolete("Use Play(EntityUid<AnimationPlayerComponent> ent, Animation animation, string key) instead")]
public void Play(EntityUid uid, AnimationPlayerComponent? component, Animation animation, string key)
{
component ??= EntityManager.EnsureComponent<AnimationPlayerComponent>(uid);
component ??= EnsureComp<AnimationPlayerComponent>(uid);
Play(new Entity<AnimationPlayerComponent>(uid, component), animation, key);
}
@@ -158,7 +158,7 @@ namespace Robust.Client.GameObjects
public bool HasRunningAnimation(EntityUid uid, string key)
{
return EntityManager.TryGetComponent(uid, out AnimationPlayerComponent? component) &&
return TryComp(uid, out AnimationPlayerComponent? component) &&
component.PlayingAnimations.ContainsKey(key);
}

View File

@@ -223,12 +223,12 @@ namespace Robust.Client.GameObjects
private void SetEntityContextActive(IInputManager inputMan, EntityUid entity)
{
if(entity == default || !EntityManager.EntityExists(entity))
if(entity == default || !Exists(entity))
throw new ArgumentNullException(nameof(entity));
if (!EntityManager.TryGetComponent(entity, out InputComponent? inputComp))
if (!TryComp(entity, out InputComponent? inputComp))
{
_sawmillInputContext.Debug($"AttachedEnt has no InputComponent: entId={entity}, entProto={EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
_sawmillInputContext.Debug($"AttachedEnt has no InputComponent: entId={entity}, entProto={Comp<MetaDataComponent>(entity).EntityPrototype}. Setting default \"{InputContextContainer.DefaultContextName}\" context...");
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
return;
}
@@ -239,7 +239,7 @@ namespace Robust.Client.GameObjects
}
else
{
_sawmillInputContext.Error($"Unknown context: entId={entity}, entProto={EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
_sawmillInputContext.Error($"Unknown context: entId={entity}, entProto={Comp<MetaDataComponent>(entity).EntityPrototype}, context={inputComp.ContextName}. . Setting default \"{InputContextContainer.DefaultContextName}\" context...");
inputMan.Contexts.SetActiveContext(InputContextContainer.DefaultContextName);
}
}

View File

@@ -51,7 +51,7 @@ public sealed class ShowPlayerVelocityDebugSystem : EntitySystem
var player = _playerManager.LocalEntity;
if (player == null || !EntityManager.TryGetComponent(player.Value, out PhysicsComponent? body))
if (player == null || !TryComp(player.Value, out PhysicsComponent? body))
{
_label.Visible = false;
return;

View File

@@ -56,10 +56,6 @@ public sealed partial class SpriteSystem
/// </summary>
public IRsiStateLike GetPrototypeIcon(string prototype)
{
// Check if this prototype has been cached before, and if so return the result.
if (_cachedPrototypeIcons.TryGetValue(prototype, out var cachedResult))
return cachedResult;
if (!_proto.TryIndex<EntityPrototype>(prototype, out var entityPrototype))
{
// The specified prototype doesn't exist, return the fallback "error" sprite.
@@ -67,11 +63,7 @@ public sealed partial class SpriteSystem
return GetFallbackState();
}
// Generate the icon and cache it in case it's ever needed again.
var result = GetPrototypeIcon(entityPrototype);
_cachedPrototypeIcons[prototype] = result;
return result;
return GetPrototypeIcon(entityPrototype);
}
/// <summary>
@@ -79,13 +71,19 @@ public sealed partial class SpriteSystem
/// This method does NOT cache the result.
/// </summary>
public IRsiStateLike GetPrototypeIcon(EntityPrototype prototype)
{
// This method may spawn & delete an entity to get an accruate RSI state, hence we cache the results
if (_cachedPrototypeIcons.TryGetValue(prototype.ID, out var cachedResult))
return cachedResult;
return _cachedPrototypeIcons[prototype.ID] = GetPrototypeIconInternal(prototype);
}
private IRsiStateLike GetPrototypeIconInternal(EntityPrototype prototype)
{
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
if (prototype.Components.TryGetValue("Icon", out var compData)
&& compData.Component is IconComponent icon)
{
if (prototype.TryGetComponent(out IconComponent? icon, _factory))
return GetIcon(icon);
}
// If the prototype doesn't have a SpriteComponent, then there's nothing we can do but return the fallback.
if (!prototype.Components.ContainsKey("Sprite"))
@@ -102,6 +100,63 @@ public sealed partial class SpriteSystem
return result;
}
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto) =>
GetPrototypeTextures(proto, out _);
public IEnumerable<IDirectionalTextureProvider> GetPrototypeTextures(EntityPrototype proto, out bool noRot)
{
var results = new List<IDirectionalTextureProvider>();
noRot = false;
if (proto.TryGetComponent(out IconComponent? icon, _factory))
{
results.Add(GetIcon(icon));
return results;
}
if (!proto.Components.ContainsKey("Sprite"))
{
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
var dummy = Spawn(proto.ID, MapCoordinates.Nullspace);
var spriteComponent = EnsureComp<SpriteComponent>(dummy);
// TODO SPRITE is this needed?
// And if it is, shouldn't GetPrototypeIconInternal also use this?
_appearance.OnChangeData(dummy, spriteComponent);
foreach (var layer in spriteComponent.AllLayers)
{
if (!layer.Visible)
continue;
if (layer.Texture != null)
{
results.Add(layer.Texture);
continue;
}
if (!layer.RsiState.IsValid)
continue;
var rsi = layer.Rsi ?? spriteComponent.BaseRSI;
if (rsi == null || !rsi.TryGetState(layer.RsiState, out var state))
continue;
results.Add(state);
}
noRot = spriteComponent.NoRotation;
Del(dummy);
if (results.Count == 0)
results.Add(_resourceCache.GetFallback<TextureResource>().Texture);
return results;
}
[Pure]
public RSI.State GetFallbackState()
{

View File

@@ -34,8 +34,12 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IResourceCache _resourceCache = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IComponentFactory _factory = default!;
// Note that any new system dependencies have to be added to RobustUnitTest.BaseSetup()
[Dependency] private readonly SharedTransformSystem _xforms = default!;
[Dependency] private readonly SpriteTreeSystem _tree = default!;
[Dependency] private readonly AppearanceSystem _appearance = default!;
public static readonly ProtoId<ShaderPrototype> UnshadedId = "unshaded";
private readonly Queue<SpriteComponent> _inertUpdateQueue = new();

View File

@@ -631,7 +631,7 @@ namespace Robust.Client.GameStates
if (_sawmill.Level <= LogLevel.Debug)
_sawmill.Debug($" A component was dirtied: {comp.GetType()}");
if (compState != null)
if ((meta.Flags & MetaDataFlags.Detached) == 0 && compState != null)
{
var handleState = new ComponentHandleState(compState, null);
_entities.EventBus.RaiseComponentEvent(entity, comp, ref handleState);

View File

@@ -254,9 +254,9 @@ namespace Robust.Client.Graphics.Clyde
region = regionMaybe[tile.Variant];
}
var rotationMirroring = _tileDefinitionManager[tile.TypeId].AllowRotationMirror
? tile.RotationMirroring
: 0;
var rotationMirroring = (_tileDefinitionManager.TryGetDefinition(tile.TypeId, out var tileDef) && tileDef.AllowRotationMirror) ?
tile.RotationMirroring
: 0;
WriteTileToBuffers(i, gridX, gridY, vertexBuffer, indexBuffer, region, rotationMirroring);
i += 1;

View File

@@ -467,7 +467,7 @@ namespace Robust.Client.Graphics.Clyde
_windowing!.RunOnWindowThread(a);
}
public IFileDialogManager? FileDialogImpl => _windowing as IFileDialogManager;
public IFileDialogManagerImplementation? FileDialogImpl => _windowing as IFileDialogManagerImplementation;
private abstract class WindowReg
{

View File

@@ -307,7 +307,7 @@ namespace Robust.Client.Graphics.Clyde
action();
}
public IFileDialogManager? FileDialogImpl => null;
public IFileDialogManagerImplementation? FileDialogImpl => null;
private sealed class DummyCursor : ICursor
{

View File

@@ -662,6 +662,10 @@ namespace Robust.Client.Graphics.Clyde
{
var icons = _clyde.LoadWindowIcons().ToArray();
// Done if no icon (e.g., macOS)
if (icons.Length == 0)
return;
// Turn each image into a byte[] so we can actually pin their contents.
// Wish I knew a clean way to do this without allocations.
var images = icons

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -12,31 +11,16 @@ namespace Robust.Client.Graphics.Clyde;
internal partial class Clyde
{
private sealed partial class Sdl3WindowingImpl : IFileDialogManager
private sealed partial class Sdl3WindowingImpl : IFileDialogManagerImplementation
{
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
public async Task<string?> OpenFile(FileDialogFilters? filters)
{
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
if (fileName == null)
return null;
return File.OpenRead(fileName);
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
}
public async Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
public async Task<string?> SaveFile(FileDialogFilters? filters)
{
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
if (fileName == null)
return null;
try
{
return (File.Open(fileName, truncate ? FileMode.Truncate : FileMode.Open), true);
}
catch (FileNotFoundException)
{
return (File.Open(fileName, FileMode.Create), false);
}
return await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
}
private unsafe Task<string?> ShowFileDialogOfType(int type, FileDialogFilters? filters)
@@ -74,6 +58,8 @@ internal partial class Clyde
NativeMemory.Free(filter.name);
NativeMemory.Free(filter.pattern);
}
NativeMemory.Free(filtersAlloc);
}
return task;

View File

@@ -71,6 +71,6 @@ namespace Robust.Client.Graphics
void RunOnWindowThread(Action action);
IFileDialogManager? FileDialogImpl { get; }
IFileDialogManagerImplementation? FileDialogImpl { get; }
}
}

View File

@@ -47,7 +47,7 @@ namespace Robust.Client.Physics
* This will draw above every body involved in a particular island solve.
*/
public readonly Queue<(TimeSpan Time, List<PhysicsComponent> Bodies)> IslandSolve = new();
public readonly Queue<(TimeSpan Time, List<Entity<PhysicsComponent, TransformComponent>> Bodies)> IslandSolve = new();
public const float SolveDuration = 0.1f;
public override void Initialize()

View File

@@ -67,7 +67,7 @@ namespace Robust.Client.Physics
// Add new joint (if possible).
// Need to wait for BOTH joint components to come in first before we can add it. Yay dependencies!
if (!EntityManager.HasComponent<JointComponent>(other))
if (!HasComp<JointComponent>(other))
continue;
// TODO: if (other entity is outside of PVS range) continue;

View File

@@ -90,9 +90,10 @@ public sealed partial class PhysicsSystem
// existing contacts for predicted entities before performing any actual prediction.
var contacts = new List<Contact>();
var maps = new HashSet<EntityUid>();
var enumerator = AllEntityQuery<PredictedPhysicsComponent, PhysicsComponent, TransformComponent>();
_broadphase.FindNewContacts();
while (enumerator.MoveNext(out _, out var physics, out var xform))
{
DebugTools.Assert(physics.Predict);
@@ -100,10 +101,6 @@ public sealed partial class PhysicsSystem
if (xform.MapUid is not { } map)
continue;
if (maps.Add(map) && PhysMapQuery.TryGetComponent(map, out var physMap) &&
MapQuery.TryGetComponent(map, out var mapComp))
_broadphase.FindNewContacts(physMap, mapComp.MapId);
contacts.AddRange(physics.Contacts);
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics;
@@ -23,21 +24,23 @@ namespace Robust.Client.Physics
SimulateWorld(frameTime, _gameTiming.InPrediction);
}
protected override void Cleanup(PhysicsMapComponent component, float frameTime)
protected override void Cleanup(float frameTime)
{
var toRemove = new List<Entity<PhysicsComponent>>();
var toRemove = new ValueList<Entity<PhysicsComponent, TransformComponent>>();
// Because we're not predicting 99% of bodies its sleep timer never gets incremented so we'll just do it ourselves.
// (and serializing it over the network isn't necessary?)
// This is a client-only problem.
// Also need to suss out having the client build the island anyway and just... not solving it?
foreach (var body in component.AwakeBodies)
foreach (var ent in AwakeBodies)
{
var body = ent.Comp1;
if (!body.SleepingAllowed || body.LinearVelocity.Length() > LinearToleranceSqr / 2f || body.AngularVelocity * body.AngularVelocity > AngularToleranceSqr / 2f) continue;
body.SleepTime += frameTime;
if (body.SleepTime > TimeToSleep)
{
toRemove.Add(new Entity<PhysicsComponent>(body.Owner, body));
toRemove.Add(ent);
}
}
@@ -46,37 +49,38 @@ namespace Robust.Client.Physics
SetAwake(body, false);
}
base.Cleanup(component, frameTime);
base.Cleanup(frameTime);
}
protected override void UpdateLerpData(PhysicsMapComponent component, List<PhysicsComponent> bodies, EntityQuery<TransformComponent> xformQuery)
protected override void UpdateLerpData(List<Entity<PhysicsComponent, TransformComponent>> bodies)
{
foreach (var body in bodies)
foreach (var bodyEnt in bodies)
{
var body = bodyEnt.Comp1;
var xform = bodyEnt.Comp2;
if (body.BodyType == BodyType.Static ||
component.LerpData.TryGetValue(body.Owner, out var lerpData) ||
!xformQuery.TryGetComponent(body.Owner, out var xform) ||
lerpData.ParentUid == xform.ParentUid)
LerpData.TryGetValue(bodyEnt, out var lerpData) ||
lerpData == xform.ParentUid)
{
continue;
}
component.LerpData[xform.Owner] = (xform.ParentUid, xform.LocalPosition, xform.LocalRotation);
LerpData[bodyEnt.Owner] = xform.ParentUid;
}
}
/// <summary>
/// Flush all of our lerping data.
/// </summary>
protected override void FinalStep(PhysicsMapComponent component)
protected override void FinalStep()
{
base.FinalStep(component);
var xformQuery = GetEntityQuery<TransformComponent>();
base.FinalStep();
foreach (var (uid, (parentUid, position, rotation)) in component.LerpData)
foreach (var (uid, parentUid) in LerpData)
{
if (!xformQuery.TryGetComponent(uid, out var xform) ||
!parentUid.IsValid())
// Can't just re-use xform from before as movement events may cause event subs to fire.
if (!XformQuery.TryGetComponent(uid, out var xform) || !parentUid.IsValid())
{
continue;
}
@@ -85,7 +89,7 @@ namespace Robust.Client.Physics
_transform.SetLocalPositionRotation(uid, xform.LocalPosition, xform.LocalRotation, xform);
}
component.LerpData.Clear();
LerpData.Clear();
}
}
}

View File

@@ -22,7 +22,7 @@ public sealed class LoadPrototypeCommand : IConsoleCommand
var dialogManager = IoCManager.Resolve<IFileDialogManager>();
var loadManager = IoCManager.Resolve<IGamePrototypeLoadManager>();
var stream = await dialogManager.OpenFile();
var stream = await dialogManager.OpenFile(access: FileAccess.Read);
if (stream is null)
return;

View File

@@ -1,3 +1,4 @@
using System.IO;
using Robust.Client.UserInterface;
using Robust.Shared;
using Robust.Shared.Configuration;
@@ -36,7 +37,7 @@ public sealed class UploadFileCommand : IConsoleCommand
var path = new ResPath(args[0]).ToRelativePath();
var filters = new FileDialogFilters(new FileDialogFilters.Group(path.Extension));
await using var file = await _dialog.OpenFile(filters);
await using var file = await _dialog.OpenFile(filters, FileAccess.Read);
if (file == null)
{

View File

@@ -994,6 +994,9 @@ namespace Robust.Client.UserInterface
internal int DoFrameUpdateRecursive(FrameEventArgs args)
{
if (!Visible)
return 0;
var total = 1;
FrameUpdate(args);

View File

@@ -1,6 +1,9 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.UserInterface.Controllers;
@@ -11,11 +14,14 @@ namespace Robust.Client.UserInterface.Controllers;
/// and <see cref="UISystemDependencyAttribute"/> to depend on <see cref="EntitySystem"/>s, which will be automatically
/// injected once they are created.
/// </summary>
public abstract partial class UIController
public abstract partial class UIController : IPostInjectInit
{
[Dependency] protected readonly IUserInterfaceManager UIManager = default!;
[Dependency] protected readonly IEntitySystemManager EntitySystemManager = default!;
[Dependency] protected readonly IEntityManager EntityManager = default!;
[Dependency] protected readonly ILogManager LogManager = default!;
public ISawmill Log { get; protected set; } = default!;
public virtual void Initialize()
{
@@ -24,4 +30,39 @@ public abstract partial class UIController
public virtual void FrameUpdate(FrameEventArgs args)
{
}
protected virtual string SawmillName
{
get
{
var name = GetType().Name;
// Strip trailing "UIController"
if (name.EndsWith("UIController"))
name = name.Substring(0, name.Length - "UIController".Length);
// Convert CamelCase to snake_case
// Ignore if all uppercase, assume acronym (e.g. NPC or HTN)
if (name.All(char.IsUpper))
{
name = name.ToLower(CultureInfo.InvariantCulture);
}
else
{
name = string.Concat(name.Select(x => char.IsUpper(x) ? $"_{char.ToLower(x)}" : x.ToString()));
name = name.Trim('_');
}
return $"ui.{name}";
}
}
public void PostInject()
{
Log = LogManager.GetSawmill(SawmillName);
#if !DEBUG
Log.Level = LogLevel.Info;
#endif
}
}

View File

@@ -42,6 +42,7 @@ public sealed class ColorSelectorSliders : Control
break;
}
_currentType = value;
_typeSelector.Select(_types.IndexOf(value));
UpdateType();
Update();
}

View File

@@ -107,6 +107,11 @@ namespace Robust.Client.UserInterface.Controls
_scrollBar.Value = 0;
}
public FormattedMessage GetMessage(Index index)
{
return new FormattedMessage(_entries[index].Message);
}
public void RemoveEntry(Index index)
{
var entry = _entries[index];
@@ -138,6 +143,31 @@ namespace Robust.Client.UserInterface.Controls
_entries.Add(entry);
var font = _getFont();
AddNewItemHeight(font, entry);
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
if (_isAtBottom && ScrollFollowing)
{
_scrollBar.MoveToEnd();
}
}
public void SetMessage(Index index, FormattedMessage message, Type[]? tagsAllowed = null, Color? defaultColor = null)
{
var oldEntry = _entries[index];
var font = _getFont();
_totalContentHeight -= oldEntry.Height + font.GetLineSeparation(UIScale);
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
var entry = new RichTextEntry(message, this, _tagManager, tagsAllowed, defaultColor);
entry.Update(_tagManager, _getFont(), _getContentBox().Width, UIScale);
_entries[index] = entry;
AddNewItemHeight(font, in entry);
}
private void AddNewItemHeight(Font font, in RichTextEntry entry)
{
_totalContentHeight += entry.Height;
if (_firstLine)
{
@@ -147,12 +177,6 @@ namespace Robust.Client.UserInterface.Controls
{
_totalContentHeight += font.GetLineSeparation(UIScale);
}
_scrollBar.MaxValue = Math.Max(_scrollBar.Page, _totalContentHeight);
if (_isAtBottom && ScrollFollowing)
{
_scrollBar.MoveToEnd();
}
}
public void ScrollToBottom()

View File

@@ -30,12 +30,12 @@ namespace Robust.Client.UserInterface.Controls
get => _currentTab;
set
{
if (_currentTab < 0)
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), value, "Current tab must be positive.");
}
if (_currentTab >= ChildCount)
if (value >= ChildCount)
{
throw new ArgumentOutOfRangeException(nameof(value), value,
"Current tab must less than the amount of tabs.");

View File

@@ -145,6 +145,11 @@ namespace Robust.Client.UserInterface.CustomControls
protected override void FrameUpdate(FrameEventArgs args)
{
// This is to avoid unnecessarily setting a position where our size isn't yet fully updated.
// This most commonly happens with saved window positions if your window position is <= 0.
if (!IsMeasureValid)
return;
var (spaceX, spaceY) = Parent!.Size;
var maxX = spaceX - ((AllowOffScreen & DirectionFlag.West) == 0 ? Size.X : WindowEdgeSeparation);

View File

@@ -8,12 +8,19 @@ namespace Robust.Client.UserInterface
/// </summary>
internal sealed class DummyFileDialogManager : IFileDialogManager
{
public Task<Stream?> OpenFile(FileDialogFilters? filters = null)
public Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null)
{
return Task.FromResult<Stream?>(null);
}
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
public Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
FileDialogFilters? filters = null,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
{
return Task.FromResult<(Stream fileStream, bool alreadyExisted)?>(null);
}

View File

@@ -7,9 +7,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Robust.Client.Graphics;
using Robust.Shared;
using Robust.Shared.Console;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Utility;
@@ -20,28 +18,41 @@ namespace Robust.Client.UserInterface
[SuppressMessage("ReSharper", "IdentifierTypo")]
[SuppressMessage("ReSharper", "CommentTypo")]
[SuppressMessage("ReSharper", "StringLiteralTypo")]
internal sealed class FileDialogManager : IFileDialogManager
internal sealed class FileDialogManager : IFileDialogManager, IPostInjectInit
{
// Uses nativefiledialog to open the file dialogs cross platform.
// On Linux, if the kdialog command is found, it will be used instead.
// TODO: Should we maybe try to avoid running kdialog if the DE isn't KDE?
[Dependency] private readonly IClydeInternal _clyde = default!;
[Dependency] private readonly ILogManager _log = default!;
private ISawmill _sawmill = default!;
private bool _kDialogAvailable;
private bool _checkedKDialogAvailable;
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
public async Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null)
{
if ((access & FileAccess.ReadWrite) != access)
throw new ArgumentException("Invalid file access specified");
var realShare = share ?? (access == FileAccess.Read ? FileShare.Read : FileShare.None);
if ((realShare & (FileShare.ReadWrite | FileShare.Delete)) != realShare)
throw new ArgumentException("Invalid file share specified");
string? name;
if (_clyde.FileDialogImpl is { } clydeImpl)
return await clydeImpl.OpenFile(filters);
name = await clydeImpl.OpenFile(filters);
else
name = await GetOpenFileName(filters);
var name = await GetOpenFileName(filters);
if (name == null)
{
return null;
}
return File.Open(name, FileMode.Open);
return File.Open(name, FileMode.Open, access, realShare);
}
private async Task<string?> GetOpenFileName(FileDialogFilters? filters)
@@ -54,24 +65,34 @@ namespace Robust.Client.UserInterface
return await OpenFileNfd(filters);
}
public async Task<(Stream, bool)?> SaveFile(FileDialogFilters? filters, bool truncate = true)
public async Task<(Stream, bool)?> SaveFile(
FileDialogFilters? filters,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None)
{
if (_clyde.FileDialogImpl is { } clydeImpl)
return await clydeImpl.SaveFile(filters);
if ((access & FileAccess.ReadWrite) != access)
throw new ArgumentException("Invalid file access specified");
if ((share & (FileShare.ReadWrite | FileShare.Delete)) != share)
throw new ArgumentException("Invalid file share specified");
string? name;
if (_clyde.FileDialogImpl is { } clydeImpl)
name = await clydeImpl.SaveFile(filters);
else
name = await GetSaveFileName(filters);
var name = await GetSaveFileName(filters);
if (name == null)
{
return null;
}
try
{
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open), true);
return (File.Open(name, truncate ? FileMode.Truncate : FileMode.Open, access, share), true);
}
catch (FileNotFoundException)
{
return (File.Open(name, FileMode.Create), false);
return (File.Open(name, FileMode.Create, access, share), false);
}
}
@@ -249,7 +270,7 @@ namespace Robust.Client.UserInterface
if (_kDialogAvailable)
{
Logger.DebugS("filedialog", "kdialog available.");
_sawmill.Debug("kdialog available.");
}
}
catch
@@ -386,6 +407,11 @@ namespace Robust.Client.UserInterface
[DllImport("swnfd.dll")]
private static extern unsafe void sw_NFD_Free(void* ptr);
public void PostInject()
{
_sawmill = _log.GetSawmill("filedialog");
}
private enum sw_nfdresult
{
SW_NFD_ERROR,

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Threading.Tasks;
using Robust.Client.Graphics;
namespace Robust.Client.UserInterface
{
@@ -19,7 +20,17 @@ namespace Robust.Client.UserInterface
/// The file stream for the file the user opened.
/// <see langword="null" /> if the user cancelled the action.
/// </returns>
Task<Stream?> OpenFile(FileDialogFilters? filters = null);
/// <param name="filters">Filters for file types that the user can select.</param>
/// <param name="access">What access is desired from the file operation.</param>
/// <param name="share">
/// What sharing mode is desired from the file operation.
/// If null is provided and <paramref name="access"/> is <see cref="FileAccess.Read"/>,
/// <see cref="FileShare.Read"/> is selected, otherwise <see cref="FileShare.None"/>.
/// </param>
Task<Stream?> OpenFile(
FileDialogFilters? filters = null,
FileAccess access = FileAccess.ReadWrite,
FileShare? share = null);
/// <summary>
/// Open a file dialog used for saving a single file.
@@ -29,6 +40,21 @@ namespace Robust.Client.UserInterface
/// Null if the user cancelled the action.
/// </returns>
/// <param name="truncate">Should we truncate an existing file to 0-size then write or append.</param>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true);
/// <param name="access">What access is desired from the file operation.</param>
/// <param name="share">Sharing mode for the opened file.</param>
Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(
FileDialogFilters? filters = null,
bool truncate = true,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.None);
}
/// <summary>
/// Internal implementation interface used to connect <see cref="FileDialogManager"/> and <see cref="IClydeInternal"/>.
/// </summary>
internal interface IFileDialogManagerImplementation
{
Task<string?> OpenFile(FileDialogFilters? filters);
Task<string?> SaveFile(FileDialogFilters? filters);
}
}

View File

@@ -141,7 +141,7 @@ namespace Robust.Client.UserInterface
if (Controls == null || !Controls.TryGetValue(nodeIndex, out var control))
continue;
control.Measure(new Vector2(Width, Height));
control.Measure(new Vector2(maxSizeX, Height));
var desiredSize = control.DesiredPixelSize;
var controlMetrics = new CharMetrics(

View File

@@ -137,7 +137,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
LogAudioPlaybackOnInvalidEntity(specifier, coordinates.EntityId);
return null;
}
@@ -160,7 +160,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
LogAudioPlaybackOnInvalidEntity(specifier, coordinates.EntityId);
return null;
}
@@ -281,4 +281,10 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
// TODO: Yeah remove this...
}
private void LogAudioPlaybackOnInvalidEntity(ResolvedSoundSpecifier? specifier, EntityUid entityId)
{
var soundInfo = specifier?.ToString() ?? "unknown sound";
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(entityId)}. Sound: {soundInfo}. Trace: {Environment.StackTrace}");
}
}

View File

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

View File

@@ -60,7 +60,7 @@ namespace Robust.Server.GameObjects
foreach (var uid in toDelete)
{
EntityManager.DeleteEntity(uid);
Del(uid);
}
}
}
@@ -75,7 +75,7 @@ namespace Robust.Server.GameObjects
if (!_deleteEmptyGrids || TerminatingOrDeleted(uid) || HasComp<MapComponent>(uid))
return;
EntityManager.DeleteEntity(args.GridId);
Del(args.GridId);
}
}
}

View File

@@ -20,7 +20,7 @@ public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem
public override void AddViewSubscriber(EntityUid uid, ICommonSession session)
{
// If the entity doesn't have the component, it will be added.
var viewSubscriber = EntityManager.EnsureComponent<Shared.GameObjects.ViewSubscriberComponent>(uid);
var viewSubscriber = EnsureComp<Shared.GameObjects.ViewSubscriberComponent>(uid);
if (viewSubscriber.SubscribedSessions.Contains(session))
return; // Already subscribed, do nothing else.
@@ -36,7 +36,7 @@ public sealed class ViewSubscriberSystem : SharedViewSubscriberSystem
/// </summary>
public override void RemoveViewSubscriber(EntityUid uid, ICommonSession session)
{
if(!EntityManager.TryGetComponent(uid, out Shared.GameObjects.ViewSubscriberComponent? viewSubscriber))
if(!TryComp(uid, out Shared.GameObjects.ViewSubscriberComponent? viewSubscriber))
return; // Entity didn't have any subscriptions, do nothing.
if (!viewSubscriber.SubscribedSessions.Remove(session))

View File

@@ -132,7 +132,7 @@ internal sealed partial class PvsSystem
if (enumerateAll)
{
var query = EntityManager.AllEntityQueryEnumerator<MetaDataComponent>();
var query = AllEntityQuery<MetaDataComponent>();
while (query.MoveNext(out var uid, out var md))
{
DebugTools.Assert(md.EntityLifeStage >= EntityLifeStage.Initialized, $"Entity {ToPrettyString(uid)} has not been initialized");

View File

@@ -199,7 +199,7 @@ internal sealed partial class PvsSystem : EntitySystem
foreach (var uid in _toDelete)
{
EntityManager.QueueDeleteEntity(uid);
QueueDel(uid);
}
_toDelete.Clear();

View File

@@ -0,0 +1,82 @@
using System.Collections.Generic;
using System.Numerics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
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.Systems;
namespace Robust.Server.Physics.Commands;
public sealed class BoxStackCommand : IConsoleCommand
{
[Dependency] private readonly IEntityManager _entManager = default!;
public string Command => "boxstack";
public string Description => string.Empty;
public string Help => "boxstack [mapid] [columns] [rows]";
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
if (args.Length != 3 ||
!int.TryParse(args[0], out var mapInt) ||
!int.TryParse(args[1], out var columns) ||
!int.TryParse(args[2], out var rows))
{
return;
}
var mapId = new MapId(mapInt);
var fixtureSystem = _entManager.System<FixtureSystem>();
var physSystem = _entManager.System<SharedPhysicsSystem>();
physSystem.SetGravity(new Vector2(0f, -9.8f));
var groundUid = _entManager.SpawnEntity(null, new MapCoordinates(0, 0, mapId));
var ground = _entManager.AddComponent<PhysicsComponent>(groundUid);
var groundManager = _entManager.EnsureComponent<FixturesComponent>(groundUid);
var horizontal = new EdgeShape(new Vector2(-40, 0), new Vector2(40, 0));
fixtureSystem.CreateFixture(groundUid, "fix1", new Fixture(horizontal, 1, 1, true), manager: groundManager, body: ground);
var vertical = new EdgeShape(new Vector2(10, 0), new Vector2(10, 10));
fixtureSystem.CreateFixture(groundUid, "fix2", new Fixture(vertical, 1, 1, true), manager: groundManager, body: ground);
physSystem.WakeBody(groundUid, manager: groundManager, body: ground);
var xs = new[]
{
0.0f, -10.0f, -5.0f, 5.0f, 10.0f
};
for (var j = 0; j < columns; j++)
{
for (var i = 0; i < rows; 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);
var manager = _entManager.EnsureComponent<FixturesComponent>(boxUid);
physSystem.SetBodyType(boxUid, BodyType.Dynamic, manager: manager, body: box);
var poly = new PolygonShape(0.001f);
poly.Set(new List<Vector2>()
{
new(0.5f, -0.5f),
new(0.5f, 0.5f),
new(-0.5f, 0.5f),
new(-0.5f, -0.5f),
});
fixtureSystem.CreateFixture(boxUid, "fix1", new Fixture(poly, 1, 1, true), manager: manager, body: box);
physSystem.WakeBody(boxUid, manager: manager, body: box);
}
}
}
}

View File

@@ -140,7 +140,7 @@ namespace Robust.Server.Placement
{
// Replace existing entities if relevant.
if (msg.Replacement && _prototype.Index<EntityPrototype>(entityTemplateName).Components.TryGetValue(
_factory.GetComponentName(typeof(PlacementReplacementComponent)), out var compRegistry))
_factory.GetComponentName<PlacementReplacementComponent>(), out var compRegistry))
{
var key = ((PlacementReplacementComponent)compRegistry.Component).Key;
var gridUid = _xformSystem.GetGrid(coordinates);

View File

@@ -10,7 +10,7 @@ namespace Robust.Server.Player
{
foreach (var uid in entities)
{
if (EntityManager.TryGetComponent(uid, out ActorComponent? actor))
if (TryComp(uid, out ActorComponent? actor))
filter.AddPlayer(actor.PlayerSession);
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
@@ -188,13 +189,20 @@ namespace Robust.Server.Scripting
}
// Compile ahead of time so that we can do syntax highlighting correctly for the echo.
newScript.Compile();
await Task.Run(() =>
{
newScript.Compile();
// Echo entered script.
var echoMessage = new FormattedMessage();
ScriptInstanceShared.AddWithSyntaxHighlighting(newScript, echoMessage, code, instance.HighlightWorkspace);
// Echo entered script.
var echoMessage = new FormattedMessage();
ScriptInstanceShared.AddWithSyntaxHighlighting(
newScript,
echoMessage,
code,
instance.HighlightWorkspace.Value);
replyMessage.Echo = echoMessage;
replyMessage.Echo = echoMessage;
});
var msg = new FormattedMessage();
@@ -332,7 +340,7 @@ namespace Robust.Server.Scripting
private sealed class ScriptInstance
{
public Workspace HighlightWorkspace { get; } = new AdhocWorkspace();
public Lazy<Workspace> HighlightWorkspace { get; } = new(() => new AdhocWorkspace());
public StringBuilder InputBuffer { get; } = new();
public FormattedMessage OutputBuffer { get; } = new();
public bool RunningScript { get; set; }
@@ -373,7 +381,7 @@ namespace Robust.Server.Scripting
script.Compile();
var syntax = new FormattedMessage();
ScriptInstanceShared.AddWithSyntaxHighlighting(script, syntax, code, _scriptInstance.HighlightWorkspace);
ScriptInstanceShared.AddWithSyntaxHighlighting(script, syntax, code, _scriptInstance.HighlightWorkspace.Value);
_scriptInstance.OutputBuffer.AddMessage(syntax);
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -101,6 +102,7 @@ namespace Robust.Shared.Maths
/// <summary>
/// Returns the smallest rectangle that contains both of the rectangles.
/// </summary>
[Pure]
public readonly Box2i Union(in Box2i other)
{
var botLeft = Vector2i.ComponentMin(BottomLeft, other.BottomLeft);
@@ -207,6 +209,7 @@ namespace Robust.Shared.Maths
/// <summary>
/// Multiplies each side of the box by the scalar.
/// </summary>
[Pure]
public Box2i Scale(int scalar)
{
return new Box2i(
@@ -215,6 +218,21 @@ namespace Robust.Shared.Maths
Right * scalar,
Top * scalar);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public bool Intersects(in Box2i other)
{
return other.Bottom <= this.Top && other.Top >= this.Bottom && other.Right >= this.Left &&
other.Left <= this.Right;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public readonly Box2i Enlarged(int size)
{
return new(Left - size, Bottom - size, Right + size, Top + size);
}
}
/// <summary>

View File

@@ -13,7 +13,8 @@ namespace Robust.Shared.Audio;
/// <seealso cref="ResolvedPathSpecifier"/>
/// <seealso cref="ResolvedCollectionSpecifier"/>
[Serializable, NetSerializable]
public abstract partial class ResolvedSoundSpecifier {
public abstract partial class ResolvedSoundSpecifier
{
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
public static implicit operator ResolvedSoundSpecifier(string s) => new ResolvedPathSpecifier(s);
[Obsolete("String literals for sounds are deprecated, use a SoundSpecifier or ResolvedSoundSpecifier as appropriate instead")]
@@ -22,8 +23,10 @@ public abstract partial class ResolvedSoundSpecifier {
/// <summary>
/// Returns whether <c>s</c> is null, or if it contains an empty path/collection ID.
/// </summary>
public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s) {
return s switch {
public static bool IsNullOrEmpty(ResolvedSoundSpecifier? s)
{
return s switch
{
null => true,
ResolvedPathSpecifier path => path.Path.ToString() == "",
ResolvedCollectionSpecifier collection => string.IsNullOrEmpty(collection.Collection),
@@ -37,7 +40,8 @@ public abstract partial class ResolvedSoundSpecifier {
/// </summary>
/// <seealso cref="ResolvedCollectionSpecifier"/>
[Serializable, NetSerializable]
public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier, IEquatable<ResolvedPathSpecifier>
{
/// <summary>
/// The resource path of the sound.
/// </summary>
@@ -57,6 +61,21 @@ public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
public ResolvedPathSpecifier(string path) : this(new ResPath(path))
{
}
public bool Equals(ResolvedPathSpecifier? other)
{
return Path.Equals(other?.Path);
}
public override bool Equals(object? obj)
{
return Equals(obj as ResolvedPathSpecifier);
}
public override int GetHashCode()
{
return Path.GetHashCode();
}
}
/// <summary>
@@ -64,7 +83,9 @@ public sealed partial class ResolvedPathSpecifier : ResolvedSoundSpecifier {
/// </summary>
/// <seealso cref="ResolvedPathSpecifier"/>
[Serializable, NetSerializable]
public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier {
public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier, IEquatable<ResolvedCollectionSpecifier>
{
/// <summary>
/// The ID of the <see cref="SoundCollectionPrototype">sound collection</see> to look up.
/// </summary>
@@ -87,4 +108,19 @@ public sealed partial class ResolvedCollectionSpecifier : ResolvedSoundSpecifier
Collection = collection;
Index = index;
}
public bool Equals(ResolvedCollectionSpecifier? other)
{
return Collection.Equals(other?.Collection) && Index.Equals(other?.Index);
}
public override bool Equals(object? obj)
{
return Equals(obj as ResolvedCollectionSpecifier);
}
public override int GetHashCode()
{
return HashCode.Combine(Collection, Index);
}
}

View File

@@ -665,7 +665,7 @@ public abstract partial class SharedAudioSystem : EntitySystem
/// <param name="coordinates">The coordinates at which to play the audio.</param>
public (EntityUid Entity, Components.AudioComponent Component)? PlayStatic(SoundSpecifier? sound, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
return sound == null ? null : PlayStatic(ResolveSound(sound), playerFilter, coordinates, recordReplay, audioParams);
return sound == null ? null : PlayStatic(ResolveSound(sound), playerFilter, coordinates, recordReplay, audioParams ?? sound.Params);
}
/// <summary>

View File

@@ -127,19 +127,20 @@ public sealed class TeleportToCommand : LocalizedEntityCommands
{
foreach (var victim in args)
{
if (victim == target)
if (!TryGetTransformFromUidOrUsername(victim, shell, out var uid, out var victimTransform))
continue;
if (!TryGetTransformFromUidOrUsername(victim, shell, out var uid, out var victimTransform))
if (uid == targetUid)
continue;
victims.Add((uid.Value, victimTransform));
}
}
var targetMapCoords = _transform.ToMapCoordinates(targetCoords);
foreach (var victim in victims)
{
_transform.SetCoordinates(victim.Entity, targetCoords);
_transform.SetMapCoordinates(victim.Entity, targetMapCoords);
_transform.AttachToGridOrMap(victim.Entity, victim.Transform);
}
}

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -155,7 +156,7 @@ public static class CompletionHelper
/// Returns a completion list for all prototype IDs of the given type.
/// </summary>
/// <remarks>
/// Don't use this for prototypes types that likely have a large number of entries, like <see cref="EntityPrototype"/>.
/// Don't use this for prototypes types that likely have a large number of entries, like <see cref="EntityPrototype"/>, use <see cref="PrototypeIdsLimited{T}"/> instead.
/// </remarks>
public static IEnumerable<CompletionOption> PrototypeIDs<T>(bool sorted = true, IPrototypeManager? proto = null)
where T: class, IPrototype
@@ -166,6 +167,36 @@ public static class CompletionHelper
return sorted ? protoOptions.OrderBy(o => o.Value) : protoOptions;
}
/// <summary>
/// Returns a completion list for all prototype IDs of the given type, limited to avoid performance problems.
/// </summary>
/// <remarks>
/// This is a limited alternative to <see cref="PrototypeIDs{T}"/>.
/// The limit is applied before sorting of results, so the unfiltered results are somewhat arbitrary.
/// </remarks>
/// <param name="currentArgument">The argument being currently typed for the completion.</param>
/// <param name="proto">The <see cref="IPrototypeManager"/>.</param>
/// <param name="sorted">Whether to sort the results or not.</param>
/// <param name="maxCount">The maximum amount of results to return at once.</param>
/// <typeparam name="T">The type of prototype to search through.</typeparam>
/// <returns></returns>
public static IEnumerable<CompletionOption> PrototypeIdsLimited<T>(
string currentArgument,
IPrototypeManager proto,
bool sorted = true,
int maxCount = 30) where T : class, IPrototype
{
var protoOptions = proto.EnumeratePrototypes<T>()
.Where(p => p.ID.StartsWith(currentArgument, StringComparison.OrdinalIgnoreCase))
.Take(maxCount)
.Select(p => new CompletionOption(p.ID));
if (sorted)
protoOptions = protoOptions.OrderBy(o => o.Value);
return protoOptions;
}
/// <summary>
/// Returns a list of connected session names.
/// </summary>

View File

@@ -279,16 +279,6 @@ namespace Robust.Shared.Containers
#region Container Helpers
[Obsolete("Use Entity<T> variant")]
public bool TryGetContainingContainer(
EntityUid uid,
[NotNullWhen(true)] out BaseContainer? container,
MetaDataComponent? meta = null,
TransformComponent? transform = null)
{
return TryGetContainingContainer((uid, transform, meta), out container);
}
public bool TryGetContainingContainer(
Entity<TransformComponent?, MetaDataComponent?> ent,
[NotNullWhen(true)] out BaseContainer? container)
@@ -401,13 +391,6 @@ namespace Robust.Shared.Containers
return TryFindComponentsOnEntityContainerOrParent(xform.ParentUid, entityQuery, foundComponents);
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrNoContainer(EntityUid user, EntityUid other)
{
return IsInSameOrNoContainer((user, null, null), (other, null, null));
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container.
/// </summary>
@@ -428,13 +411,6 @@ namespace Robust.Shared.Containers
return userContainer == otherContainer;
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrParentContainer(EntityUid user, EntityUid other)
{
return IsInSameOrParentContainer((user, null), other);
}
/// <summary>
/// Returns true if the two entities are not contained, or are contained in the same container, or if one
/// entity contains the other (i.e., is the parent).
@@ -469,21 +445,6 @@ namespace Robust.Shared.Containers
return userContainer == otherContainer;
}
[Obsolete("Use Entity<T> variant")]
public bool IsInSameOrTransparentContainer(
EntityUid user,
EntityUid other,
BaseContainer? userContainer = null,
BaseContainer? otherContainer = null,
bool userSeeInsideSelf = false)
{
return IsInSameOrTransparentContainer((user, null),
other,
userContainer,
otherContainer,
userSeeInsideSelf);
}
/// <summary>
/// Check whether a given entity can see another entity despite whatever containers they may be in.
/// </summary>
@@ -606,7 +567,7 @@ namespace Robust.Shared.Containers
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <param name="wasInContainer">Whether the entity was actually inside a container or not.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
public bool TryRemoveFromContainer(EntityUid entity, bool force, out bool wasInContainer)
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force, out bool wasInContainer)
{
DebugTools.Assert(Exists(entity));
@@ -631,7 +592,7 @@ namespace Robust.Shared.Containers
/// <param name="entity">Entity that might be inside a container.</param>
/// <param name="force">Whether to forcibly remove the entity from the container.</param>
/// <returns>If the entity could be removed. Also returns false if it wasn't inside a container.</returns>
public bool TryRemoveFromContainer(EntityUid entity, bool force = false)
public bool TryRemoveFromContainer(Entity<TransformComponent?, MetaDataComponent?> entity, bool force = false)
{
return TryRemoveFromContainer(entity, force, out _);
}
@@ -678,9 +639,8 @@ namespace Robust.Shared.Containers
{
// TODO make this check upwards for any container, and parent to that.
// Currently this just checks the direct parent, so entities will still teleport through containers.
if (!transform.Comp.ParentUid.IsValid()
|| !TryGetContainingContainer(transform.Comp.ParentUid, out var container)
|| !TryGetContainingContainer((transform.Comp.ParentUid, Transform(transform.Comp.ParentUid)), out var container)
|| !TryInsertIntoContainer(transform, container))
{
_transform.AttachToGridOrMap(transform, transform.Comp);
@@ -692,8 +652,9 @@ namespace Robust.Shared.Containers
if (Insert((transform.Owner, transform.Comp, null, null), container))
return true;
if (Transform(container.Owner).ParentUid.IsValid()
&& TryGetContainingContainer(container.Owner, out var newContainer))
var ownerXform = Transform(container.Owner);
if (ownerXform.ParentUid.IsValid()
&& TryGetContainingContainer((container.Owner, ownerXform), out var newContainer))
return TryInsertIntoContainer(transform, newContainer);
return false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -547,19 +547,20 @@ public sealed class EntityDeserializer :
_stopwatch.Restart();
foreach (var (entity, data) in Entities)
{
#if EXCEPTION_TOLERANCE
try
{
#endif
CurrentReadingEntity = data;
LoadEntity(entity, _metaQuery.Comp(entity), data.Components, data.MissingComponents);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
#if !EXCEPTION_TOLERANCE
throw;
#endif
ToDelete.Add(entity);
_log.Error($"Encountered error while loading entity. Yaml uid: {data.YamlId}. Loaded loaded entity: {EntMan.ToPrettyString(entity)}. Error:\n{e}.");
}
#endif
}
CurrentReadingEntity = null;

View File

@@ -162,8 +162,8 @@ public sealed class EntitySerializer : ISerializationContext,
_log = _logMan.GetSawmill("entity_serializer");
SerializerProvider.RegisterSerializer(this);
_metaName = _factory.GetComponentName(typeof(MetaDataComponent));
_xformName = _factory.GetComponentName(typeof(TransformComponent));
_metaName = _factory.GetComponentName<MetaDataComponent>();
_xformName = _factory.GetComponentName<TransformComponent>();
_emptyMetaNode = _serialization.WriteValueAs<MappingDataNode>(typeof(MetaDataComponent), new MetaDataComponent(), alwaysWrite: true, context: this);
CurrentComponent = _xformName;

View File

@@ -41,6 +41,31 @@ public sealed partial class MapLoaderSystem
return true;
}
/// <summary>
/// Tries to load entities from a YAML file, taking in a raw byte stream.
/// </summary>
/// <param name="file">The file contents to load from.</param>
/// <param name="fileName">
/// The name of the file being loaded. This is used purely for logging/informational purposes.
/// </param>
/// <param name="result">The result of the load operation.</param>
/// <param name="options">Options for the load operation.</param>
/// <returns>True if the load succeeded, false otherwise.</returns>
/// <seealso cref="M:Robust.Shared.EntitySerialization.Systems.MapLoaderSystem.TryLoadGeneric(Robust.Shared.Utility.ResPath,Robust.Shared.EntitySerialization.LoadResult@,System.Nullable{Robust.Shared.EntitySerialization.MapLoadOptions})"/>
public bool TryLoadGeneric(
Stream file,
string fileName,
[NotNullWhen(true)] out LoadResult? result,
MapLoadOptions? options = null)
{
result = null;
if (!TryReadFile(new StreamReader(file), out var data))
return false;
return TryLoadGeneric(data, fileName, out result, options);
}
/// <summary>
/// Tries to load entities from a yaml file. Whenever possible, you should try to use <see cref="TryLoadMap"/>,
/// <see cref="TryLoadGrid"/>, or <see cref="TryLoadEntity"/> instead.
@@ -55,6 +80,17 @@ public sealed partial class MapLoaderSystem
if (!TryReadFile(file, out var data))
return false;
return TryLoadGeneric(data, file.ToString(), out result, options);
}
private bool TryLoadGeneric(
MappingDataNode data,
string fileName,
[NotNullWhen(true)] out LoadResult? result,
MapLoadOptions? options = null)
{
result = null;
_stopwatch.Restart();
var ev = new BeforeEntityReadEvent();
RaiseLocalEvent(ev);
@@ -85,7 +121,7 @@ public sealed partial class MapLoaderSystem
if (!deserializer.TryProcessData())
{
Log.Debug($"Failed to process entity data in {file}");
Log.Debug($"Failed to process entity data in {fileName}");
return false;
}
@@ -95,7 +131,7 @@ public sealed partial class MapLoaderSystem
}
catch (Exception e)
{
Log.Error($"Caught exception while creating entities for map {file}: {e}");
Log.Error($"Caught exception while creating entities for map {fileName}: {e}");
Delete(deserializer.Result);
throw;
}
@@ -103,7 +139,7 @@ public sealed partial class MapLoaderSystem
if (opts.ExpectedCategory is { } exp && exp != deserializer.Result.Category)
{
// Did someone try to load a map file as a grid or vice versa?
Log.Error($"Map {file} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
Log.Error($"Map {fileName} does not contain the expected data. Expected {exp} but got {deserializer.Result.Category}");
Delete(deserializer.Result);
return false;
}

View File

@@ -64,6 +64,13 @@ public sealed partial class MapLoaderSystem : EntitySystem
return false;
Log.Info($"Loading file: {resPath}");
return TryReadFile(reader, out data);
}
private bool TryReadFile(TextReader reader, [NotNullWhen(true)] out MappingDataNode? data)
{
data = null;
_stopwatch.Restart();
using var textReader = reader;

View File

@@ -633,7 +633,7 @@ namespace Robust.Shared.GameObjects
/// An invalid entity UID indicates that this entity has intentionally been removed from broadphases and should
/// not automatically be re-added by movement events.
/// </remarks>
internal record struct BroadphaseData(EntityUid Uid, EntityUid PhysicsMap, bool CanCollide, bool Static)
internal record struct BroadphaseData(EntityUid Uid, bool CanCollide, bool Static)
{
public bool IsValid() => Uid.IsValid();
public bool Valid => IsValid();

View File

@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Maths;
using Robust.Shared.Serialization;
@@ -84,12 +85,17 @@ public partial class EntityManager
return ents;
}
public void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
public EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames)
{
var ents = new ValueList<EntityUid>();
foreach (var protoName in protoNames)
{
SpawnAttachedTo(protoName, coordinates);
var uid = SpawnAttachedTo(protoName, coordinates);
ents.Add(uid);
}
return ents.ToArray();
}
public virtual EntityUid SpawnAttachedTo(string? protoName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)

View File

@@ -161,7 +161,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Returns true if the entity's data (apart from transform) is default.
/// </summary>
public bool IsDefault(EntityUid uid)
public bool IsDefault(EntityUid uid, ICollection<string>? ignoredComps = null)
{
if (!MetaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null)
return false;
@@ -195,6 +195,9 @@ namespace Robust.Shared.GameObjects
var compName = _componentFactory.GetComponentName(compType);
if (ignoredComps?.Contains(compName) == true)
continue;
// If the component isn't on the prototype then it's custom.
if (!protoData.TryGetValue(compName, out var protoMapping))
return false;
@@ -498,7 +501,7 @@ namespace Robust.Shared.GameObjects
if (Deleted(uid.Value))
return false;
if (!QueuedDeletionsSet.Add(uid.Value))
if (QueuedDeletionsSet.Contains(uid.Value))
return false;
QueueDeleteEntity(uid);

View File

@@ -665,7 +665,7 @@ public partial class EntitySystem
/// <inheritdoc cref="IEntityManager.AddComponent&lt;T&gt;(EntityUid)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected T AddComp<T>(EntityUid uid) where T : Component, new()
protected T AddComp<T>(EntityUid uid) where T : IComponent, new()
{
return EntityManager.AddComponent<T>(uid);
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Collections;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -22,7 +23,7 @@ public partial interface IEntityManager
EntityUid[] SpawnEntities(MapCoordinates coordinates, params string?[] protoNames);
EntityUid[] SpawnEntities(MapCoordinates coordinates, string? prototype, int count);
EntityUid[] SpawnEntities(MapCoordinates coordinates, List<string?> protoNames);
void SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, IEnumerable<EntProtoId> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params EntProtoId[] protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, List<string?> protoNames);
EntityUid[] SpawnEntitiesAttachedTo(EntityCoordinates coordinates, params string?[] protoNames);

View File

@@ -18,7 +18,7 @@ namespace Robust.Shared.GameObjects
private void OnStartup(EntityUid uid, CollideOnAnchorComponent component, ComponentStartup args)
{
if (!EntityManager.TryGetComponent(uid, out TransformComponent? transformComponent)) return;
if (!TryComp(uid, out TransformComponent? transformComponent)) return;
SetCollide(uid, component, transformComponent.Anchored);
}
@@ -31,7 +31,7 @@ namespace Robust.Shared.GameObjects
private void SetCollide(EntityUid uid, CollideOnAnchorComponent component, bool anchored)
{
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body)) return;
if (!TryComp(uid, out PhysicsComponent? body)) return;
var enabled = component.Enable;

View File

@@ -89,7 +89,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
private EntityQuery<MapGridComponent> _gridQuery;
private EntityQuery<MetaDataComponent> _metaQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
private EntityQuery<PhysicsMapComponent> _physMapQuery;
private EntityQuery<TransformComponent> _xformQuery;
/// <summary>
@@ -119,7 +118,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
_gridQuery = GetEntityQuery<MapGridComponent>();
_metaQuery = GetEntityQuery<MetaDataComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
_physMapQuery = GetEntityQuery<PhysicsMapComponent>();
_xformQuery = GetEntityQuery<TransformComponent>();
SubscribeLocalEvent<BroadphaseComponent, EntityTerminatingEvent>(OnBroadphaseTerminating);
@@ -153,15 +151,12 @@ public sealed partial class EntityLookupSystem : EntitySystem
private void OnBroadphaseTerminating(EntityUid uid, BroadphaseComponent component, ref EntityTerminatingEvent args)
{
var xform = _xformQuery.GetComponent(uid);
var map = xform.MapUid;
_physMapQuery.TryGetComponent(map, out var physMap);
RemoveChildrenFromTerminatingBroadphase(xform, component, physMap);
RemoveChildrenFromTerminatingBroadphase(xform, component);
RemComp(uid, component);
}
private void RemoveChildrenFromTerminatingBroadphase(TransformComponent xform,
BroadphaseComponent component,
PhysicsMapComponent? map)
BroadphaseComponent component)
{
foreach (var child in xform._children)
{
@@ -179,19 +174,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (childXform.Broadphase.Value.CanCollide && _fixturesQuery.TryGetComponent(child, out var fixtures))
{
if (map == null)
_physMapQuery.TryGetComponent(childXform.Broadphase.Value.PhysicsMap, out map);
DebugTools.Assert(map == null || childXform.Broadphase.Value.PhysicsMap == map.Owner);
var tree = childXform.Broadphase.Value.Static ? component.StaticTree : component.DynamicTree;
foreach (var fixture in fixtures.Fixtures.Values)
{
DestroyProxies(fixture, tree, map);
DestroyProxies(fixture, tree);
}
}
childXform.Broadphase = null;
RemoveChildrenFromTerminatingBroadphase(childXform, component, map);
RemoveChildrenFromTerminatingBroadphase(childXform, component);
}
}
@@ -227,26 +218,18 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (xform.MapUid == null)
return;
if (!_physMapQuery.TryGetComponent(xform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
}
var ent = new Entity<TransformComponent, BroadphaseComponent>(broadphase, xform, broadphase);
var map = new Entity<PhysicsMapComponent>(xform.MapUid.Value, physMap);
var enumerator = xform.ChildEnumerator;
while (enumerator.MoveNext(out var child))
{
if (!_broadQuery.HasComp(child))
InitializeChild(child, ent, map);
InitializeChild(child, ent);
}
}
private void InitializeChild(
EntityUid child,
Entity<TransformComponent, BroadphaseComponent> broadphase,
Entity<PhysicsMapComponent> map)
Entity<TransformComponent, BroadphaseComponent> broadphase)
{
if (LifeStage(child) <= EntityLifeStage.PreInit)
return;
@@ -258,7 +241,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!xform.Broadphase.Value.IsValid())
return; // Entity is intentionally not on a broadphase (deferred updating?).
_physMapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out var oldPhysMap);
if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out var oldBroadphase))
{
DebugTools.Assert("Encountered deleted broadphase.");
@@ -275,16 +257,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
else if (oldBroadphase != broadphase.Comp2)
{
RemoveFromEntityTree(xform.Broadphase.Value.Uid, oldBroadphase, ref oldPhysMap, child, xform);
RemoveFromEntityTree(xform.Broadphase.Value.Uid, oldBroadphase,child, xform);
}
}
DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && (!x.CanCollide || x.PhysicsMap == map.Owner));
DebugTools.Assert(xform.Broadphase is not {} x || x.Uid == broadphase.Owner && !x.CanCollide);
AddOrUpdateEntityTree(
broadphase.Owner,
broadphase.Comp2,
broadphase.Comp1,
map.Comp,
child,
xform);
}
@@ -315,9 +296,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!TryGetCurrentBroadphase(xform, out var broadphase))
return;
if (!_physMapQuery.TryGetComponent(xform.MapUid, out var physMap))
throw new InvalidOperationException();
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var mapTransform = new Transform(worldPos, worldRot);
@@ -326,10 +304,10 @@ public sealed partial class EntityLookupSystem : EntitySystem
var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree;
DebugTools.Assert(fixture.ProxyCount == 0);
AddOrMoveProxies(uid, fixtureId, fixture, body, tree, broadphaseTransform, mapTransform, physMap.MoveBuffer);
AddOrMoveProxies((uid, body, xform), fixtureId, fixture, tree, broadphaseTransform);
}
internal void DestroyProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase, PhysicsMapComponent? physicsMap)
internal void DestroyProxies(EntityUid uid, string fixtureId, Fixture fixture, TransformComponent xform, BroadphaseComponent broadphase)
{
DebugTools.AssertNotNull(xform.Broadphase);
DebugTools.Assert(xform.Broadphase!.Value.Uid == broadphase.Owner);
@@ -344,7 +322,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
var tree = xform.Broadphase.Value.Static ? broadphase.StaticTree : broadphase.DynamicTree;
DestroyProxies(fixture, tree, physicsMap);
DestroyProxies(fixture, tree);
}
#endregion
@@ -395,8 +373,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
var fixtures = Comp<FixturesComponent>(uid);
if (old.CanCollide)
{
_physMapQuery.TryGetComponent(old.PhysicsMap, out var physicsMap);
RemoveBroadTree(broadphase, fixtures, old.Static, physicsMap);
RemoveBroadTree(broadphase, fixtures, old.Static);
}
else
(old.Static ? broadphase.StaticSundriesTree : broadphase.SundriesTree).Remove(uid);
@@ -408,23 +385,23 @@ public sealed partial class EntityLookupSystem : EntitySystem
AddOrUpdateSundriesTree(old.Uid, broadphase, uid, xform, body.BodyType == BodyType.Static);
}
private void RemoveBroadTree(BroadphaseComponent lookup, FixturesComponent manager, bool staticBody, PhysicsMapComponent? map)
private void RemoveBroadTree(BroadphaseComponent lookup, FixturesComponent manager, bool staticBody)
{
var tree = staticBody ? lookup.StaticTree : lookup.DynamicTree;
foreach (var fixture in manager.Fixtures.Values)
{
DestroyProxies(fixture, tree, map);
DestroyProxies(fixture, tree);
}
}
internal void DestroyProxies(Fixture fixture, IBroadPhase tree, PhysicsMapComponent? map)
internal void DestroyProxies(Fixture fixture, IBroadPhase tree)
{
var buffer = map?.MoveBuffer;
var buffer = _physics.MoveBuffer;
for (var i = 0; i < fixture.ProxyCount; i++)
{
var proxy = fixture.Proxies[i];
tree.RemoveProxy(proxy.ProxyId);
buffer?.Remove(proxy);
buffer.Remove(proxy);
}
fixture.ProxyCount = 0;
@@ -438,10 +415,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (broadphaseXform.MapID == MapId.Nullspace)
return;
if (!_physMapQuery.TryGetComponent(broadphaseXform.MapUid, out var physMap))
throw new InvalidOperationException($"Physics Broadphase is missing physics map. {ToPrettyString(broadUid)}");
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, physMap, xform, body, fixtures);
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, xform, body, fixtures);
}
private void AddOrUpdatePhysicsTree(
@@ -449,16 +423,15 @@ public sealed partial class EntityLookupSystem : EntitySystem
EntityUid broadUid,
BroadphaseComponent broadphase,
TransformComponent broadphaseXform,
PhysicsMapComponent physicsMap,
TransformComponent xform,
PhysicsComponent body,
FixturesComponent manager)
{
DebugTools.Assert(!_container.IsEntityOrParentInContainer(body.Owner, null, xform));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadphase.Owner, body.CanCollide, body.BodyType == BodyType.Static));
DebugTools.Assert(broadphase.Owner == broadUid);
xform.Broadphase ??= new(broadUid, physicsMap.Owner, body.CanCollide, body.BodyType == BodyType.Static);
xform.Broadphase ??= new(broadUid, body.CanCollide, body.BodyType == BodyType.Static);
var tree = body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree;
// TOOD optimize this. This function iterates UP through parents, while we are currently iterating down.
@@ -470,20 +443,19 @@ public sealed partial class EntityLookupSystem : EntitySystem
foreach (var (id, fixture) in manager.Fixtures)
{
AddOrMoveProxies(uid, id, fixture, body, tree, broadphaseTransform, mapTransform, physicsMap.MoveBuffer);
AddOrMoveProxies((uid, body, xform), id, fixture, tree, broadphaseTransform);
}
}
private void AddOrMoveProxies(
EntityUid uid,
Entity<PhysicsComponent, TransformComponent> ent,
string fixtureId,
Fixture fixture,
PhysicsComponent body,
IBroadPhase tree,
Transform broadphaseTransform,
Transform mapTransform,
Dictionary<FixtureProxy, Box2> moveBuffer)
Transform broadphaseTransform)
{
var moveBuffer = _physics.MoveBuffer;
// Moving
if (fixture.ProxyCount > 0)
{
@@ -493,7 +465,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
var proxy = fixture.Proxies[i];
tree.MoveProxy(proxy.ProxyId, bounds);
proxy.AABB = bounds;
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
moveBuffer.Add(proxy);
}
return;
@@ -505,11 +477,11 @@ public sealed partial class EntityLookupSystem : EntitySystem
for (var i = 0; i < count; i++)
{
var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i);
var proxy = new FixtureProxy(uid, body, bounds, fixtureId, fixture, i);
var proxy = new FixtureProxy(ent.Owner, ent.Comp1, ent.Comp2, bounds, fixtureId, fixture, i);
proxy.ProxyId = tree.AddProxy(ref proxy);
proxy.AABB = bounds;
proxies[i] = proxy;
moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
moveBuffer.Add(proxy);
}
fixture.Proxies = proxies;
@@ -519,8 +491,8 @@ public sealed partial class EntityLookupSystem : EntitySystem
private void AddOrUpdateSundriesTree(EntityUid broadUid, BroadphaseComponent broadphase, EntityUid uid, TransformComponent xform, bool staticBody, Box2? aabb = null)
{
DebugTools.Assert(!_container.IsEntityOrParentInContainer(uid));
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, default, false, staticBody));
xform.Broadphase ??= new(broadUid, default, false, staticBody);
DebugTools.Assert(xform.Broadphase == null || xform.Broadphase == new BroadphaseData(broadUid, false, staticBody));
xform.Broadphase ??= new(broadUid, false, staticBody);
(staticBody ? broadphase.StaticSundriesTree : broadphase.SundriesTree).AddOrUpdate(uid, aabb);
}
@@ -538,8 +510,12 @@ public sealed partial class EntityLookupSystem : EntitySystem
{
if (args.Component.GridUid == args.Sender)
{
if (args.ParentChanged) // grid changed maps, need to update children and clear the move buffer.
// If grid changes map MoveBuffer will have incorrect worldpositions for all children.
if (args.ParentChanged)
{
OnGridChangedMap(args);
}
return;
}
DebugTools.Assert(!_gridQuery.HasComp(args.Sender));
@@ -563,84 +539,21 @@ public sealed partial class EntityLookupSystem : EntitySystem
return;
// We need to recursively update the cached data and remove children from the move buffer
DebugTools.Assert(_gridQuery.HasComp(args.Sender));
DebugTools.Assert(!newMap.IsValid() || _mapQuery.HasComp(newMap));
DebugTools.Assert(!oldMap.IsValid() || _mapQuery.HasComp(oldMap));
var oldBuffer = _physMapQuery.CompOrNull(oldMap)?.MoveBuffer;
var newBuffer = _physMapQuery.CompOrNull(newMap)?.MoveBuffer;
foreach (var child in args.Component._children)
{
RecursiveOnGridChangedMap(child, oldMap, newMap, oldBuffer, newBuffer);
}
}
private void RecursiveOnGridChangedMap(
EntityUid uid,
EntityUid oldMap,
EntityUid newMap,
Dictionary<FixtureProxy, Box2>? oldBuffer,
Dictionary<FixtureProxy, Box2>? newBuffer)
{
if (!_xformQuery.TryGetComponent(uid, out var xform))
return;
foreach (var child in xform._children)
{
RecursiveOnGridChangedMap(child, oldMap, newMap, oldBuffer, newBuffer);
}
if (xform.Broadphase == null || !xform.Broadphase.Value.CanCollide)
return;
DebugTools.Assert(_netMan.IsClient || !xform.Broadphase.Value.PhysicsMap.IsValid() || xform.Broadphase.Value.PhysicsMap == oldMap);
xform.Broadphase = xform.Broadphase.Value with { PhysicsMap = newMap };
if (!_fixturesQuery.TryGetComponent(uid, out var fixtures))
return;
if (oldBuffer != null)
{
foreach (var fix in fixtures.Fixtures.Values)
foreach (var prox in fix.Proxies)
{
oldBuffer.Remove(prox);
}
}
if (newBuffer == null)
return;
// TODO PERFORMANCE
// track world position while recursively iterating down through children.
var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform);
var mapTransform = new Transform(worldPos, worldRot);
foreach (var fixture in fixtures.Fixtures.Values)
{
for (var i = 0; i < fixture.ProxyCount; i++)
{
var proxy = fixture.Proxies[i];
newBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i);
}
}
DebugTools.Assert(HasComp<MapGridComponent>(args.Sender));
DebugTools.Assert(!newMap.IsValid() || HasComp<MapComponent>(newMap));
DebugTools.Assert(!oldMap.IsValid() || HasComp<MapComponent>(oldMap));
}
private void UpdateParent(EntityUid uid, TransformComponent xform)
{
BroadphaseComponent? oldBroadphase = null;
PhysicsMapComponent? oldPhysMap = null;
if (xform.Broadphase != null)
{
if (!xform.Broadphase.Value.IsValid())
return; // Entity is intentionally not on a broadphase (deferred updating?).
_physMapQuery.TryGetComponent(xform.Broadphase.Value.PhysicsMap, out oldPhysMap);
if (!_broadQuery.TryGetComponent(xform.Broadphase.Value.Uid, out oldBroadphase))
{
DebugTools.Assert("Encountered deleted broadphase.");
// broadphase was probably deleted.
@@ -661,24 +574,18 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (oldBroadphase != null && oldBroadphase != newBroadphase)
{
RemoveFromEntityTree(oldBroadphase.Owner, oldBroadphase, ref oldPhysMap, uid, xform);
RemoveFromEntityTree(oldBroadphase.Owner, oldBroadphase, uid, xform);
}
if (newBroadphase == null)
return;
var newBroadphaseXform = _xformQuery.GetComponent(newBroadphase.Owner);
if (!_physMapQuery.TryGetComponent(newBroadphaseXform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(newBroadphase.Owner)}");
}
AddOrUpdateEntityTree(
newBroadphase.Owner,
newBroadphase,
newBroadphaseXform,
physMap,
uid,
xform);
}
@@ -713,17 +620,11 @@ public sealed partial class EntityLookupSystem : EntitySystem
bool recursive = true)
{
var broadphaseXform = _xformQuery.GetComponent(broadphase.Owner);
if (!_physMapQuery.TryGetComponent(broadphaseXform.MapUid, out var physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
}
AddOrUpdateEntityTree(
broadUid,
broadphase,
broadphaseXform,
physMap,
uid,
xform,
recursive);
@@ -733,7 +634,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
EntityUid broadUid,
BroadphaseComponent broadphase,
TransformComponent broadphaseXform,
PhysicsMapComponent physicsMap,
EntityUid uid,
TransformComponent xform,
bool recursive = true)
@@ -758,7 +658,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
}
else
{
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, physicsMap, xform, body, _fixturesQuery.GetComponent(uid));
AddOrUpdatePhysicsTree(uid, broadUid, broadphase, broadphaseXform, xform, body, _fixturesQuery.GetComponent(uid));
}
if (xform.ChildCount == 0 || !recursive)
@@ -771,7 +671,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
foreach (var child in xform._children)
{
var childXform = _xformQuery.GetComponent(child);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child, childXform, recursive);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, child, childXform, recursive);
}
return;
}
@@ -782,7 +682,7 @@ public sealed partial class EntityLookupSystem : EntitySystem
continue;
var childXform = _xformQuery.GetComponent(child);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, physicsMap, child, childXform, recursive);
AddOrUpdateEntityTree(broadUid, broadphase, broadphaseXform, child, childXform, recursive);
}
}
@@ -794,16 +694,10 @@ public sealed partial class EntityLookupSystem : EntitySystem
if (!TryGetCurrentBroadphase(xform, out var broadphase))
return;
DebugTools.Assert(!_gridQuery.HasComp(uid));
DebugTools.Assert(!_mapQuery.HasComp(uid));
PhysicsMapComponent? physMap = null;
if (xform.Broadphase!.Value.PhysicsMap is { Valid: true } map && !_physMapQuery.TryGetComponent(map, out physMap))
{
throw new InvalidOperationException(
$"Broadphase's map is missing a physics map comp. Broadphase: {ToPrettyString(broadphase.Owner)}");
}
DebugTools.Assert(!HasComp<MapGridComponent>(uid));
DebugTools.Assert(!HasComp<MapComponent>(uid));
RemoveFromEntityTree(broadphase.Owner, broadphase, ref physMap, uid, xform);
RemoveFromEntityTree(broadphase.Owner, broadphase, uid, xform);
}
/// <summary>
@@ -812,7 +706,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
private void RemoveFromEntityTree(
EntityUid broadUid,
BroadphaseComponent broadphase,
ref PhysicsMapComponent? physicsMap,
EntityUid uid,
TransformComponent xform,
bool recursive = true)
@@ -833,16 +726,9 @@ public sealed partial class EntityLookupSystem : EntitySystem
broadUid = old.Uid;
}
if (old.PhysicsMap.IsValid() && physicsMap?.Owner != old.PhysicsMap)
{
if (!_physMapQuery.TryGetComponent(old.PhysicsMap, out physicsMap))
Log.Error($"Entity {ToPrettyString(uid)} has missing physics map?");
}
if (old.CanCollide)
{
DebugTools.Assert(old.PhysicsMap == (physicsMap?.Owner ?? default));
RemoveBroadTree(broadphase, _fixturesQuery.GetComponent(uid), old.Static, physicsMap);
RemoveBroadTree(broadphase, _fixturesQuery.GetComponent(uid), old.Static);
}
else if (old.Static)
broadphase.StaticSundriesTree.Remove(uid);
@@ -858,7 +744,6 @@ public sealed partial class EntityLookupSystem : EntitySystem
RemoveFromEntityTree(
broadUid,
broadphase,
ref physicsMap,
child,
_xformQuery.GetComponent(child));
}

View File

@@ -73,8 +73,8 @@ internal sealed class PrototypeReloadSystem : EntitySystem
var data = newPrototype.Components[name];
var component = _componentFactory.GetComponent(name);
if (!EntityManager.HasComponent(entity, component.GetType()))
EntityManager.AddComponent(entity, component);
if (!HasComp(entity, component.GetType()))
AddComp(entity, component);
}
// Update entity metadata

View File

@@ -69,19 +69,19 @@ namespace Robust.Shared.GameObjects
if (!_enabled)
return;
if (!EntityManager.TryGetComponent(uid, out PhysicsComponent? body))
if (!TryComp(uid, out PhysicsComponent? body))
{
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(body)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
if (!TryComp(uid, out FixturesComponent? manager))
{
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(manager)}");
return;
}
if (!EntityManager.TryGetComponent(uid, out TransformComponent? xform))
if (!TryComp(uid, out TransformComponent? xform))
{
Log.Error($"Trying to regenerate collision for {uid} that doesn't have {nameof(TransformComponent)}");
return;

View File

@@ -158,7 +158,7 @@ public abstract partial class SharedMapSystem
// Just MapLoader things.
if (component.MapProxy == DynamicTree.Proxy.Free) return;
var xform = EntityManager.GetComponent<TransformComponent>(uid);
var xform = Comp<TransformComponent>(uid);
var aabb = GetWorldAABB(uid, component, xform);
if (TryComp<GridTreeComponent>(xform.MapUid, out var gridTree))
@@ -166,10 +166,7 @@ public abstract partial class SharedMapSystem
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
{
movedGrids.MovedGrids.Add(uid);
}
_physics.MovedGrids.Add(uid);
}
private void OnGridMove(EntityUid uid, MapGridComponent component, ref MoveEvent args)
@@ -191,10 +188,7 @@ public abstract partial class SharedMapSystem
gridTree.Tree.MoveProxy(component.MapProxy, in aabb);
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
{
movedGrids.MovedGrids.Add(uid);
}
_physics.MovedGrids.Add(uid);
}
private void OnParentChange(EntityUid uid, MapGridComponent component, ref MoveEvent args)
@@ -219,23 +213,23 @@ public abstract partial class SharedMapSystem
if (xform.ParentUid != xform.MapUid && meta.EntityLifeStage < EntityLifeStage.Terminating && _netManager.IsServer)
{
Log.Error($"Grid {ToPrettyString(uid, meta)} is not parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
Log.Error($"Grid {ToPrettyString(uid, meta)} is parented to {ToPrettyString(xform._parent)} which is not a map. y'all need jesus. {Environment.StackTrace}");
return;
}
// Make sure we cleanup old map for moved grid stuff.
var oldMap = _transform.ToMapCoordinates(args.OldPosition);
var oldMapUid = GetMapOrInvalid(oldMap.MapId);
if (component.MapProxy != DynamicTree.Proxy.Free && TryComp<MovedGridsComponent>(oldMapUid, out var oldMovedGrids))
if (component.MapProxy != DynamicTree.Proxy.Free)
{
oldMovedGrids.MovedGrids.Remove(uid);
_physics.MovedGrids.Remove(uid);
RemoveGrid(uid, component, oldMapUid);
}
DebugTools.Assert(component.MapProxy == DynamicTree.Proxy.Free);
if (TryComp<MovedGridsComponent>(xform.MapUid, out var newMovedGrids))
if (xform.MapUid != null)
{
newMovedGrids.MovedGrids.Add(uid);
_physics.MovedGrids.Add(uid);
AddGrid(uid, component);
}
}
@@ -344,6 +338,8 @@ public abstract partial class SharedMapSystem
chunk.SuppressCollisionRegeneration = true;
DebugTools.Assert(data.TileData.Any(x => !x.IsEmpty));
DebugTools.Assert(data.TileData.Length == component.ChunkSize * component.ChunkSize);
var changedEntry = new ValueList<TileChangedEntry>();
for (ushort x = 0; x < component.ChunkSize; x++)
{
for (ushort y = 0; y < component.ChunkSize; y++)
@@ -352,12 +348,15 @@ public abstract partial class SharedMapSystem
if (!chunk.TrySetTile(x, y, tile, out var oldTile, out _))
continue;
var gridIndices = chunk.ChunkTileToGridTile((x, y));
var newTileRef = new TileRef(uid, gridIndices, tile);
_mapInternal.RaiseOnTileChanged(gridEnt, newTileRef, oldTile, index);
var chunkIndex = new Vector2i(x, y);
var gridIndices = chunk.ChunkTileToGridTile(chunkIndex);
changedEntry.Add(new TileChangedEntry(tile, oldTile, chunk.Indices, gridIndices));
}
}
var ev = new TileChangedEvent(gridEnt, changedEntry.ToArray());
EntityManager.EventBus.RaiseLocalEvent(gridEnt.Owner, ref ev, true);
DebugTools.Assert(chunk.Fixtures.SetEquals(data.Fixtures));
// These should never refer to the same object
@@ -519,9 +518,9 @@ public abstract partial class SharedMapSystem
component.MapProxy = proxy;
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
if (xform.MapUid != null)
{
movedGrids.MovedGrids.Add(uid);
_physics.MovedGrids.Add(uid);
}
}
@@ -573,9 +572,9 @@ public abstract partial class SharedMapSystem
grid.MapProxy = proxy;
}
if (TryComp<MovedGridsComponent>(xform.MapUid, out var movedGrids))
if (xform.MapUid != null)
{
movedGrids.MovedGrids.Add(uid);
_physics.MovedGrids.Add(uid);
}
}
@@ -588,9 +587,9 @@ public abstract partial class SharedMapSystem
grid.MapProxy = DynamicTree.Proxy.Free;
if (TryComp<MovedGridsComponent>(mapUid, out var movedGrids))
if (mapUid.IsValid())
{
movedGrids.MovedGrids.Remove(uid);
_physics.MovedGrids.Remove(uid);
}
}

View File

@@ -116,9 +116,7 @@ public abstract partial class SharedMapSystem
private void OnComponentAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
// ordered startups when
EnsureComp<PhysicsMapComponent>(uid);
EnsureComp<GridTreeComponent>(uid);
EnsureComp<MovedGridsComponent>(uid);
}
internal void AssignMapId(Entity<MapComponent> map, MapId? id = null)

View File

@@ -1,16 +1,16 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.Shared.Map.Components;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Shared.Containers;
namespace Robust.Shared.GameObjects;
@@ -217,9 +217,10 @@ public abstract partial class SharedTransformSystem
{
#if !EXCEPTION_TOLERANCE
throw new Exception("Transform is initialising before map ids have been assigned?");
#endif
#else
Log.Error($"Transform is initialising before map ids have been assigned?");
_map.AssignMapId((uid, mapComp));
#endif
}
xform.MapUid = uid;

View File

@@ -53,8 +53,14 @@ public abstract partial class SharedTransformSystem
return MapCoordinates.Nullspace;
}
var worldPos = Vector2.Transform(coordinates.Position, GetWorldMatrix(xform));
return new MapCoordinates(worldPos, xform.MapID);
Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition;
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
{
xform = XformQuery.GetComponent(xform.ParentUid);
pos = xform._localRotation.RotateVec(pos) + xform._localPosition;
}
return new MapCoordinates(pos, xform.MapID);
}
/// <summary>
@@ -67,6 +73,41 @@ public abstract partial class SharedTransformSystem
return ToMapCoordinates(eCoords);
}
/// <summary>
/// Converts entity-local coordinates into map terms.
/// The same as ToMapCoordinates(coordinates, logError).Position, but doesn't have to construct the MapCoordinates first.
/// </summary>
public Vector2 ToWorldPosition(EntityCoordinates coordinates, bool logError = true)
{
if (!TryComp(coordinates.EntityId, out TransformComponent? xform))
{
if (logError)
Log.Error($"Attempted to convert coordinates with invalid entity: {coordinates}. Trace: {Environment.StackTrace}");
return Vector2.Zero;
}
Vector2 pos = xform._localRotation.RotateVec(coordinates.Position) + xform._localPosition;
while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid())
{
xform = XformQuery.GetComponent(xform.ParentUid);
pos = xform._localRotation.RotateVec(pos) + xform._localPosition;
}
return pos;
}
/// <summary>
/// Converts entity-local coordinates into map terms.
/// The same as ToMapCoordinates(coordinates).Position, but doesn't have to construct the MapCoordinates first.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector2 ToWorldPosition(NetCoordinates coordinates)
{
var eCoords = GetCoordinates(coordinates);
return ToWorldPosition(eCoords);
}
/// <summary>
/// Creates EntityCoordinates given an entity and some MapCoordinates.
/// </summary>

View File

@@ -263,7 +263,7 @@ namespace Robust.Shared.GameObjects
return true;
}
public void RaiseMoveEvent(
internal void RaiseMoveEvent(
Entity<TransformComponent, MetaDataComponent> ent,
EntityUid oldParent,
Vector2 oldPosition,

View File

@@ -636,7 +636,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
if (!_timing.IsFirstTimePredicted)
return;
EntityManager.RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new CloseBoundInterfaceMessage(), key));
RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new CloseBoundInterfaceMessage(), key));
}
/// <summary>
@@ -685,7 +685,7 @@ public abstract class SharedUserInterfaceSystem : EntitySystem
{
// Not guaranteed to open so rely upon the event handling it.
// Also lets client request it to be opened remotely too.
EntityManager.RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new OpenBoundInterfaceMessage(), key));
RaisePredictiveEvent(new BoundUIWrapMessage(GetNetEntity(entity.Owner), new OpenBoundInterfaceMessage(), key));
}
}
else

View File

@@ -12,7 +12,7 @@ namespace Robust.Shared.GameObjects
base.Update(frameTime);
// Avoid a collection was modified while enumerating.
var timers = EntityManager.EntityQueryEnumerator<TimerComponent>();
var timers = EntityQueryEnumerator<TimerComponent>();
var timersList = new ValueList<(EntityUid, TimerComponent)>();
while (timers.MoveNext(out var uid, out var timer))
{
@@ -28,7 +28,7 @@ namespace Robust.Shared.GameObjects
{
if (!timer.Deleted && !EntityManager.Deleted(uid) && timer.RemoveOnEmpty && timer.TimerCount == 0)
{
EntityManager.RemoveComponent<TimerComponent>(uid);
RemComp<TimerComponent>(uid);
}
}
}

View File

@@ -170,10 +170,8 @@ namespace Robust.Shared.Localization
if (args.Args.Count < 1) return new LocValueString(nameof(Gender.Neuter));
ILocValue entity0 = args.Args[0];
if (entity0.Value != null)
if (entity0.Value is EntityUid entity)
{
EntityUid entity = (EntityUid)entity0.Value;
if (_entMan.TryGetComponent(entity, out GrammarComponent? grammar) && grammar.Gender.HasValue)
{
return new LocValueString(grammar.Gender.Value.ToString().ToLowerInvariant());
@@ -246,10 +244,8 @@ namespace Robust.Shared.Localization
if (args.Args.Count < 1) return new LocValueString(GetString("zzzz-counter-default"));
ILocValue entity0 = args.Args[0];
if (entity0.Value != null)
if (entity0.Value is EntityUid entity)
{
EntityUid entity = (EntityUid)entity0.Value;
if (TryGetEntityLocAttrib(entity, "counter", out var counter))
{
return new LocValueString(counter);
@@ -292,9 +288,8 @@ namespace Robust.Shared.Localization
if (args.Args.Count < 2) return new LocValueString("other");
ILocValue entity0 = args.Args[0];
if (entity0.Value != null)
if (entity0.Value is EntityUid entity)
{
EntityUid entity = (EntityUid)entity0.Value;
ILocValue attrib0 = args.Args[1];
if (TryGetEntityLocAttrib(entity, attrib0.Format(new LocContext(bundle)), out var attrib))
{
@@ -313,10 +308,8 @@ namespace Robust.Shared.Localization
if (args.Args.Count < 1) return new LocValueString("false");
ILocValue entity0 = args.Args[0];
if (entity0.Value != null)
if (entity0.Value is EntityUid entity)
{
EntityUid entity = (EntityUid)entity0.Value;
if (_entMan.TryGetComponent(entity, out GrammarComponent? grammar) && grammar.ProperNoun.HasValue)
{
return new LocValueString(grammar.ProperNoun.Value.ToString().ToLowerInvariant());

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Map.Components;
/// <summary>
/// Stores what grids moved in a tick.
/// </summary>
[RegisterComponent, NetworkedComponent]
public sealed partial class MovedGridsComponent : Component
{
[ViewVariables]
public HashSet<EntityUid> MovedGrids = new();
}

View File

@@ -0,0 +1,64 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Robust.Shared.Collections;
using Robust.Shared.Maths;
namespace Robust.Shared.Map.Enumerators;
/// <summary>
/// Iterates chunk indices but prefers ones closer towards the center first.
/// </summary>
public record struct NearestChunkEnumerator
{
private readonly Vector2i _chunkLB;
private readonly Vector2i _chunkRT;
private ValueList<Vector2i> _chunks = new();
private int _n;
public NearestChunkEnumerator(Box2 localAABB, int chunkSize)
{
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
InitializeChunks(new Vector2i(chunkSize, chunkSize));
}
public NearestChunkEnumerator(Box2 localAABB, Vector2i chunkSize)
{
_chunkLB = (localAABB.BottomLeft / chunkSize).Floored();
_chunkRT = (localAABB.TopRight / chunkSize).Floored();
InitializeChunks(chunkSize);
}
private void InitializeChunks(Vector2i chunkSize)
{
var bl = (Vector2)_chunkLB * chunkSize;
var tr = (Vector2)_chunkRT * chunkSize;
var halfChunk = new Vector2(chunkSize.X / 2f, chunkSize.Y / 2f);
var center = (tr - bl) / 2 + bl;
for (var x = _chunkLB.X; x < _chunkRT.X; x++)
{
for (var y = _chunkLB.Y; y < _chunkRT.Y; y++)
{
_chunks.Add(new Vector2i(x, y) * chunkSize);
}
}
_chunks.Sort((c1, c2) => ((c1 + halfChunk) - center).LengthSquared().CompareTo(((c2 + halfChunk) - center).LengthSquared()));
}
public bool MoveNext([NotNullWhen(true)] out Vector2i? indices)
{
if (_n >= _chunks.Count)
{
indices = null;
return false;
}
indices = _chunks[_n++] ;
return true;
}
}

View File

@@ -20,11 +20,6 @@ namespace Robust.Shared.Map
/// </summary>
string Name { get; }
/// <summary>
/// Internal name of the definition.
/// </summary>
string ID { get; }
/// <summary>
/// The path of the sprite to draw.
/// </summary>

View File

@@ -37,7 +37,7 @@ using FNLfloat = System.Single;
//using FNLfloat = System.Double;
[DataDefinition, Serializable, NetSerializable]
public sealed partial class FastNoiseLite
public sealed partial class FastNoiseLite : ISerializationHooks
{
private const short INLINE = 256; // MethodImplOptions.AggressiveInlining;
private const short OPTIMISE = 512; // MethodImplOptions.AggressiveOptimization;
@@ -136,7 +136,6 @@ public sealed partial class FastNoiseLite
[DataField("pingPongStrength")]
private float mPingPongStrength = 2.0f;
[DataField("fractalBounding")]
private float mFractalBounding = 1 / 1.75f;
[DataField("cellularDistanceFunction")]
@@ -170,6 +169,11 @@ public sealed partial class FastNoiseLite
SetSeed(1337);
}
void ISerializationHooks.AfterDeserialization()
{
CalculateFractalBounding();
}
public int GetSeed() => mSeed;
/// <summary>

View File

@@ -1,18 +0,0 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Maths;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Robust.Shared.Physics.Components;
[RegisterComponent, NetworkedComponent]
public sealed partial class Gravity2DComponent : Component
{
/// <summary>
/// Applies side-view gravity to the map.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField("gravity")]
public Vector2 Gravity;
}

View File

@@ -154,11 +154,6 @@ public sealed partial class PhysicsComponent : Component, IComponentDelta
// ReSharper disable once InconsistentNaming
internal float _inertia;
/// <summary>
/// Indicates whether this body ignores gravity
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public bool IgnoreGravity;
/// <summary>
/// Inverse moment of inertia (1 / I).
/// </summary>
@@ -264,6 +259,9 @@ public sealed partial class PhysicsComponent : Component, IComponentDelta
[DataField, Access(typeof(SharedPhysicsSystem), Friend = AccessPermissions.ReadWriteExecute, Other = AccessPermissions.Read)]
public BodyStatus BodyStatus { get; set; }
[DataField, Access(typeof(SharedPhysicsSystem))]
public bool IgnoreGravity;
[ViewVariables, Access(typeof(SharedPhysicsSystem))]
public bool Predict;
}

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