Compare commits

...

144 Commits

Author SHA1 Message Date
metalgearsloth
7cb3aeccc2 Version: 221.2.0 2024-05-02 12:20:54 +10:00
metalgearsloth
ae83e606d6 Add SetWorldRotNoLerp method (#5091)
* Add SetWorldRotNoLerp method

I neeeeed it.

* Also this one

* dum
2024-05-02 12:14:27 +10:00
metalgearsloth
d9d5ef7471 Add audio helpers for map-based audio (#5086)
Doesn't need to be a flag because we just set it as global, whereas gridaudio cares about stuff every frame.
2024-05-02 09:51:14 +10:00
Pieter-Jan Briers
0f97f366a6 Copy CopyToShaderParameters in SpriteComponent.CopyFrom.
Fixes dragging displacement-mapped mobs in SS14 making the displacement map visible.
2024-05-01 23:45:58 +02:00
Jezithyr
35ab0b8cc8 Version: 221.1.0 2024-04-30 12:51:22 -07:00
metalgearsloth
5a14e939bf TileChangedEvent bool (#5089)
Shows whether IsEmpty is different, useful in circumstances.

Also NotNullWhen null handling consistency.
2024-04-30 07:28:29 -07:00
T-Stalker
ccba6b5d1c Reduce default sound range to 15 (#5085) 2024-04-30 15:41:00 +10:00
DrSmugleaf
254a5987c7 Fix Array.Resize sandbox signature (#5084) 2024-04-30 02:14:56 +02:00
metalgearsloth
8550056e68 Version: 221.0.0 2024-04-29 18:46:57 +10:00
Leon Friedrich
25211e3781 Improve transform & state handling exception tolerance (#5081)
* Improve transform & state exception tolerance

* release notes

* Fix pvs assert

* Fix velocity conservation
2024-04-29 18:42:05 +10:00
Leon Friedrich
3500abfd47 Add IUserInterfaceManager.UpdateHovered() (#5083)
* Add `IUserInterfaceManager.UpdateHovered()`

* Try fix tests
2024-04-29 18:37:52 +10:00
Leon Friedrich
7d1915096a Use more entity queries in physics systems & entity manager (#5082) 2024-04-29 13:46:10 +10:00
Nemanja
4504731588 Add a method in SharedTransformSystem for swapping the position of two entities (#4988)
* swap pos method

* no forcing

* Sluth review

* weh
2024-04-29 13:45:12 +10:00
Leon Friedrich
701fa95a82 Temporarily disable macos tests (#5079) 2024-04-29 02:35:04 +10:00
ShadowCommander
40a9048704 Add margin input value order as a comment (#5067)
* Add margin input value order as a comment

* Make a better comment and move value to remark
2024-04-27 20:17:11 +02:00
Leon Friedrich
cee8d42776 Improve MergeImplicitData exception tolerance (#5075) 2024-04-28 02:23:56 +10:00
metalgearsloth
3330d96177 Version: 220.2.0 2024-04-27 16:05:51 +10:00
Pieter-Jan Briers
4033d96327 Engine changes for displacement maps. (#5023)
* Add load parameter support to RSIs.

Currently only supports turning sRGB off. RSIs with custom load parameters are not thrown into the meta-atlas.

As part of this, TextureLoadParameters and TextureSampleParameters has been made to support equality.

* Add UV2 channel to vertices.

This is a bad hack to make displacement maps work in Robust. The UV2 channel goes from 0 -> 1 across the draw and can therefore be used by displacement maps to map a separate displacement map layer on top of the regular meta-atlas RSI texture.

This creates float inaccuracy issues but they weren't bad enough to completely void the feature. I'm thinking I learn from this experience and completely re-do how UVs work with the renderer rewrite, so that hopefully won't happen anymore.

This required dumping the optimized PadVerticesV2 because the changed struct size made it impractical. RIP.

I don't like this approach at all but the renderer is slated for a rewrite anyways, and all shaders will need to be rewritten regardless.

* Add CopyToShaderParameters for sprite layers.

This effectively allows copying the parameters of a sprite layer into another layer's shader parameters. The use case is to copy texture coordinates for displacement maps, as the exact map used changes depending on orientation. It also enables animations to be used though I didn't use that personally.
2024-04-27 16:03:35 +10:00
metalgearsloth
6e0205d1a8 Version: 220.1.0 2024-04-27 12:33:45 +10:00
ShadowCommander
7cd95351c3 Remove IP address and HWId from ViewVariables (#5062)
* Remove IP address from ViewVariables

* Remove HWId from ViewVariables
2024-04-27 12:30:34 +10:00
Leon Friedrich
2a102f048f Fix client-side replay exception (#5068)
Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-04-27 12:30:00 +10:00
metalgearsloth
16bab1bc03 Close UIs on disconnect (#5071)
Engine handles it fine but content does not as the state gets handled before all comps are initialized.
2024-04-27 12:21:46 +10:00
metalgearsloth
123d0ae6ac Version: 220.0.0 2024-04-26 18:15:47 +10:00
metalgearsloth
d72de032fa Predicted BUIs (#5059)
* Add TryGetOpenBUI

Avoids having to get the component and openinterfaces separately.

* Couple more helpers

* entityquery

* reviews

* Shared BUIs

* zawehdo

* More boilerplate

* Bunch more work

* Building

* Stuff

* More state handling

* API cleanup

* Slight tweak

* Tweaks

* gabriel

* Disposies

* Active UI support

* Lots of fixes

- Fix states not applying properly, fix predicted messages, remove redundant message type, add RaiseUiMessage for an easy way to do it from shared, add the old BUI state change events back.

* Fix test failures

* weh

* Remove unncessary closes.

* release note
2024-04-26 18:12:55 +10:00
Tayrtahn
0fdba836ee Remove debug assert for Fixture.Owner equality (#5066)
* Removed debug assert for Fixture owner equality

* Blah
2024-04-26 14:47:09 +10:00
metalgearsloth
eb63809999 Version: 219.2.0 2024-04-25 00:31:04 +10:00
Leon Friedrich
4c3c74865c Fix yaml linter & improve validation of static fields (#5056) 2024-04-25 00:27:02 +10:00
Nemanja
b624f5b70f Add SetMapCoordinates (#5065)
* add set map coordinates function

* mmmmmmm yes

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

---------

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

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

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

* fixes inheritance

* proxy

* Fixes maprot

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

* Update Tests

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

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

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

Lets server pause or stop audio or whatever.

* Better API and state fix

* Bunch of fixes

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

* Clamps and despawn fixes

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

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

This reverts commit af36d24892.

* Fix tests

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

* weh

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

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

* Actually fix the Roslyn tests.

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

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

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

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

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

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

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

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

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

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

* A

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

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

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

Also made Robust.Shared.Configuration.EnvironmentVariables internal because wtf that should not be public no.
2024-04-04 02:24:57 +02:00
metalgearsloth
6764ed56b0 Version: 217.2.1 2024-03-31 17:02:37 +11:00
Leon Friedrich
2b55d39e51 Make various ValueList enumerators use spans (#5019)
* Make various ValueList enumerators use spans

* Remove reference to EntityEventBus.OrderedRegistration

---------

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

epic

* Fix IGameTiming dependency

* remove iocmanager.resolves

* test fix

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

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

* review

---------

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

* Metadata here too

* Remove object cast

* knock

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

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

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

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

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

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

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

* refactor: updated relese-notes

* refactor: changed release-notes PROPERLY

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

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

* reduce code duplication

* Cleanup code a bit

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

* Remove obsolete extension

* Remove incorrect O(n) comments.

---------

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

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

* Fixes

* rn

* review

* pitch

* VV sound params + dump unneeded params

BusName never used and per-source Attenuation got dropped.

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

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

* Fixes

* Fix grid pos

* Also fix the joint setter

* Add both

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

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

---------

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

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

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

* Add ITileDefinitionManager.TryGetDefinition

Try-pattern version of the existing indexers.

* Preserve tile maps when saving maps

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

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

Fixes #5000

* Changelog

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

This changes the behavior so that the filter log level is the FIRST set log level encountered on the hierarchy. This means the log level is compared only once, and a sawmill down the hierarchy that has a level like Verbose set can cause verbose messages to come up even if the root is still set to Debug. This matches the logging behavior in libraries such as ASP.NET Core.
2024-03-27 13:27:54 +11:00
Pieter-Jan Briers
536fca4115 Version: 215.3.1 2024-03-26 18:47:35 +01:00
Pieter-Jan Briers
7a0d02463c Revert "Zstd Update (#5002)"
This reverts commit eee771c5f1.
2024-03-26 18:46:34 +01:00
Pieter-Jan Briers
6df53d60ed Changelog 2024-03-26 15:06:38 +01:00
Pieter-Jan Briers
ff38e9f12a Version: 215.3.0 2024-03-26 02:52:36 +01:00
Pieter-Jan Briers
00c58c76a8 Disable MTU Expansion again
Reports of problems, double checked with Grafana. Great.

I am going to do manual test runs of this system against Miros again if I end up looking to improve the situation.
2024-03-26 02:49:47 +01:00
Pieter-Jan Briers
0bf99e173c Texture GetPixel() fixes
Wow this API is bad. Well the API is just terrible for perf for multiple reasons but not fixing that...

1. Was using DSA GetTextureImage() instead of GetnTexImage() so only worked if DSA was available.
2. Was using stackalloc with a potentially huge amount of memory (original bug).
3. Had an off-by-one for the vertical coordinate.

All fixed now.

Fixes #5001
2024-03-26 01:11:30 +01:00
Wrexbe (Josh)
eee771c5f1 Zstd Update (#5002) 2024-03-25 22:20:39 +01:00
Tayrtahn
2946cd866c Added length comparison helpers for Vector2 (#4999) 2024-03-25 16:08:43 +01:00
chromiumboy
9a2a3d658d Provide option to stop PlaceManager from changing the player control scheme (#4977) 2024-03-25 17:44:11 +11:00
Leon Friedrich
d933f03a54 Add TryComp and HasComp to EntityQuery<T> (#4996) 2024-03-24 21:40:28 +01:00
metalgearsloth
25bbb21dc8 Version: 215.2.0 2024-03-25 00:57:09 +11:00
metalgearsloth
4460454563 Implement sound VV (#4966)
* Implement sound VV

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

* Fixes

* rn

* review
2024-03-24 16:59:40 +11:00
KISS
a2d8fa7a9b Making possible to QueueDeleteEntity on EndCollideEvent (#4883)
* made possible to destroy entity on EndCollideEvent

* figured queue delete issue

* review

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
2024-03-24 16:32:57 +11:00
metalgearsloth
71f0491f10 Version: 215.1.0 2024-03-24 14:21:33 +11:00
Leon Friedrich
b4c1618338 Misc Toolshed tweaks (#4990)
* Toolshed tweaks

* oops

* Apply suggestions from code review

Co-authored-by: Moony <moony@hellomouse.net>

* Re-add NotImplementedException

* Move error message

---------

Co-authored-by: Moony <moony@hellomouse.net>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
2024-03-24 14:18:49 +11:00
Wrexbe (Josh)
df0945f3cd VV editor for EntProtoId? (#4986)
Co-authored-by: wrexbe <wrexbe@protonmail.com>
2024-03-24 14:13:07 +11:00
metalgearsloth
8d477716b0 Add audio filepath completion helper (#4968)
* Add audio filepath completion helper

Due to how audio is packaged server doesn't have most audio files.

* RN

* weh
2024-03-24 14:12:31 +11:00
metalgearsloth
5c1a5e9826 Add 2 random methods (#4980)
* Add 2 random methods

One for valuelist shuffle and one for NextAngle range.

* rn

* Fix RN
2024-03-24 14:00:21 +11:00
metalgearsloth
6daa3ad2fc Version: 215.0.0 2024-03-24 13:24:11 +11:00
Vasilis
033a617102 Requirement for https://github.com/space-wizards/space-station-14/pull/25569 (#4992) 2024-03-23 22:23:39 +01:00
Tayrtahn
b9b565d53e Fix uncaught overflow exception when parsing NetEntities from strings. (#4989)
* Fixed uncaught overflow exception when parsing NetEntities from strings.

* or
2024-03-23 20:55:45 +01:00
Pieter-Jan Briers
b7ea4d0cca Add release notes for #4987 2024-03-23 16:38:19 +01:00
Pieter-Jan Briers
919ec01477 Enable MTU expansion by default.
Due to yet another need to lower the MTU we should enable this by default. Improves network efficiency.
2024-03-23 16:27:33 +01:00
Pieter-Jan Briers
e484eac29c Changelog for 3097784cd7 2024-03-23 16:25:49 +01:00
Pieter-Jan Briers
eedadb250f Add net.mtu_ipv6 CVar.
Wires up to the new MaximumTransmissionUnitV6 configuration in Lidgren. Default is that of Lidgren.
2024-03-23 16:25:25 +01:00
Kevin Zheng
3097784cd7 Lower default MTU to 900 (#4985)
Some players continue to have "stuck at connected" issues connecting to
most servers, but apparently this issue has become more prominent in the
last two weeks or so?

It is almost certainly an MTU problem because there are at least two
servers on the hub that run a lower default MTU, and these players had
no problem connecting to them. For one of these reporters, I actually
increased the MTU to 1000 and they could no longer connect, and could
connect again once it was lowered to 900.

It's not clear what recent changes, either to the codebase or to the
public Internet that have been exercising this MTU issue more. For those
experiencing MTU issues, it seems that connecting to a less full server
results in higher probability of success.

Nevertheless, bringing down the default MTU and then possibly enabling
MTU expansion in the future would make this game playable for a small
but not insignificant bit of players.
2024-03-23 16:22:06 +01:00
Pieter-Jan Briers
0fb41e06c8 Upgrade to Lidgren v0.3.0 2024-03-23 16:21:34 +01:00
nikthechampiongr
0a79382a62 Add helper command for Player toolshed commands (#4987)
Allows you to invoke players:entity with a username to immediate get that player's attached entity(if any)
2024-03-23 15:27:54 +01:00
Tayrtahn
1f2b38a6d1 Code cleanup: Purge calls to obsolete EntityCoordinates methods (#4983) 2024-03-23 13:07:33 +11:00
Pieter-Jan Briers
f760929527 Add IMeterFactory implementation to IoC
This will be useful as we start using more System.Diagnostics.Metrics.
2024-03-22 22:19:18 +01:00
Pieter-Jan Briers
f5ade69f6d Show MTU in network debug panel. 2024-03-22 22:19:17 +01:00
Tayrtahn
9bfe889c86 Code cleanup: Purge obsolete MapManager methods (#4981)
* GetGrid

* GridExists

* TryGetGrid
2024-03-21 16:31:33 +01:00
Tayrtahn
e3954494e7 Just two (#4982) 2024-03-20 15:48:19 +01:00
Pieter-Jan Briers
6697e36e84 Fix ResizableMemoryRegion metrics
This was broken for more reason than one.

So the obvious reason they weren't counting right is because I forgot to add a call to update the metric in Shrink(). Whoops.

The second issue is that .NET's new metric types are unintuitive as shit and Microsoft would've done better to just link to the OpenTelemetry specification instead of whatever garbage they documented instead.

It turns out UpDownCounter just wasn't the correct choice at all! Because it only ever gets DELTA VALUES sent to collection tools, it can never be used for absolute measurements (such as memory usage) **despite Microsoft literally using examples of absolute values in their docs** ("queue size"). The OpenTelemetry spec is clear on this.

This means writing more code, because the API is shit. Great.
2024-03-20 11:58:11 +01:00
Pieter-Jan Briers
dae4041e61 Make CheckBox texture vertically centered.
This makes it look better in circumstances where the checkbox is vertically big, for example due to surrounding inline buttons.
2024-03-20 11:12:06 +01:00
Pieter-Jan Briers
390f399750 Add IMetricsManager.UpdateMetrics system
This callback enables code to update its metrics only when required. Needed this for SS14 since online admin count stats are not something I want to update on an "arbitrary" basis.

Tons of consideration and commenting for how this plays in with stuff like dotnet-counters. Added the metrics.update_interval CVar to act as a fallback for this event when dotnet-counters and such is in use.
2024-03-20 11:11:32 +01:00
Pieter-Jan Briers
28cf7442ce Fix naming of ResizableMemoryRegion metrics 2024-03-20 11:09:28 +01:00
Leon Friedrich
d8b03be651 Add debug assert to Dirty(uid, comp) (#4978) 2024-03-18 21:37:25 +01:00
Tayrtahn
16c7c71ca6 Code cleanup: Dirty(Comp) (#4979)
* The easy ones

* SharedPhysicsSystem
2024-03-18 21:36:52 +01:00
Leon Friedrich
0245c371ae Make the replay has error use the ReplayIgnoreErrors cvar (#4974) 2024-03-17 22:50:00 +01:00
Leon Friedrich
c8cb13f832 Fix serialization error logging (#4973) 2024-03-17 18:01:00 +01:00
Pieter-Jan Briers
86ecfaa56b Allow replays with mismatching type hashes.
Things like .NET bumps and engine minor version updates break the hashes. Just allow mismatching hashes, printing a warning to the log at least.

If the hash mismatches it'll explode with some weird error like invalidcast/index so whatever.

It'd be better if there was a "hey this might not work, you sure you want to load this?" screen, but I don't feel like rewriting the entire replay loading system right now.

Fixes https://github.com/space-wizards/SS14.Launcher/issues/142
2024-03-17 16:43:01 +01:00
Pieter-Jan Briers
43a32e7015 Fix dump_netserializer_type_map to show full data.
The output it gave didn't show the full info to generate the manifest (e.g. assembly version numbers)
2024-03-17 16:40:06 +01:00
Leon Friedrich
83371885fa Support transform states with unknown parents (#4972) 2024-03-17 11:28:50 +11:00
ShadowCommander
e686e1b4cc Add collection parsing to the dev window for UI (#4959)
* Debug window collection popup

* Try fixing?

* DevUI shows children and string collections

* XAMLify the popup

* Rename popup

* Deduplicate code

* Simplify popup creation

* MouseFilter.Ignore is default so it can be removed
2024-03-16 23:35:04 +01:00
312 changed files with 7427 additions and 3253 deletions

View File

@@ -10,7 +10,7 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [ubuntu-latest, windows-latest ] # , macos-latest] - temporarily disabled due to libfreetype.dll errors.
runs-on: ${{ matrix.os }}
@@ -27,7 +27,8 @@ jobs:
run: dotnet restore
- name: Build
run: dotnet build --no-restore /p:WarningsAsErrors=nullable
- name: Test Engine
- name: Robust.UnitTesting
run: dotnet test --no-build Robust.UnitTesting/Robust.UnitTesting.csproj -- NUnit.ConsoleOut=0
- name: Robust.Analyzers.Tests
run: dotnet test --no-build Robust.Analyzers.Tests/Robust.Analyzers.Tests.csproj -- NUnit.ConsoleOut=0

View File

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

View File

@@ -1,4 +1,4 @@
# Release notes for RobustToolbox.
# Release notes for RobustToolbox.
<!--
NOTE: automatically updated sometimes by version.py.
@@ -54,6 +54,359 @@ END TEMPLATE-->
*None yet*
## 221.2.0
### New features
* Add SetMapAudio helper to SharedAudioSystem to setup map-wide audio entities.
* Add SetWorldRotNoLerp method to SharedTransformSystem to avoid client lerping.
### Bugfixes
* `SpriteComponent.CopyFrom` now copies `CopyToShaderParameters` configuration.
## 221.1.0
## 221.0.0
### Breaking changes
* `EntParentChangedMessage.OldMapId` is now an `EntityUid` instead of `MapId`
* `TransformSystem.DetachParentToNull()` is being renamed to `DetachEntity`
* The order in which `MoveEvent` handlers are invoked has been changed to prioritise engine subscriptions
### New features
* Added `UpdateHovered()` and `SetHovered()` to `IUserInterfaceManager`, for updating or modifying the currently hovered control.
* Add SwapPositions to TransformSystem to swap two entity's transforms.
### Bugfixes
* Improve client gamestate exception tolerance.
### Other
* If the currently hovered control is disposed, `UserInterfaceManager` will now look for a new control, rather than just setting the hovered control to null.
### Internal
* Use more `EntityQuery<T>` internally in EntityManager and PhysicsSystem.
## 220.2.0
### New features
* RSIs can now specify load parameters, mimicking the ones from `.png.yml`. Currently only disabling sRGB is supported.
* Added a second UV channel to Clyde's vertex format. On regular batched sprite draws, this goes 0 -> 1 across the sprite quad.
* Added a new `CopyToShaderParameters` system for `SpriteComponent` layers.
## 220.1.0
### Bugfixes
* Fix client-side replay exceptions due to dropped states when recording.
### Other
* Remove IP + HWId from ViewVariables.
* Close BUIs upon disconnect.
## 220.0.0
### Breaking changes
* Refactor UserInterfaceSystem.
- The API has been significantly cleaned up and standardised, most noticeably callers don't need to worry about TryGetUi and can rely on either HasUi, SetUiState, CloseUi, or OpenUi to handle their code as appropriate.
- Interface data is now stored via key rather than as a flat list which is a breaking change for YAML.
- BoundUserInterfaces can now be completely handled via Shared code. Existing Server-side callers will behave similarly to before.
- BoundUserInterfaces now properly close in many more situations, additionally they are now attached to the entity so reconnecting can re-open them and they can be serialized properly.
## 219.2.0
### New features
* Add SetMapCoordinates to TransformSystem.
* Improve YAML Linter and validation of static fields.
### Bugfixes
* Fix DebugCoordsPanel freezing when hovering a control.
### Other
* Optimise physics networking to not dirty every tick of movement.
## 219.1.3
### Bugfixes
* Fix map-loader not pausing pre-init maps when not actively overwriting an existing map.
## 219.1.2
### Bugfixes
* Fix map-loader not map-initialising grids when loading into a post-init map.
## 219.1.1
### Bugfixes
* Fix map-loader not map-initialising maps when overwriting a post-init map.
## 219.1.0
### New features
* Added a new optional arguments to various entity spawning methods, including a new argument to set the entity's rotation.
### Bugfixes
* Fixes map initialisation not always initialising all entities on a map.
### Other
* The default value of the `auth.mode` cvar has changed
## 219.0.0
### Breaking changes
* Move most IMapManager functionality to SharedMapSystem.
## 218.2.0
### New features
* Control layout properties such as `Margin` can now be set via style sheets.
* Expose worldposition in SpriteComponent.Render
* Network audio entity Play/Pause/Stop states and playback position.
* Add `Disabled` functionality to `Slider` control.
## 218.1.0
### New features
* Add IEquatable.Equals to the sandbox.
* Enable roslyn extensions tests in CI.
* Add a VerticalTabContainer control to match the horizontal one.
### Bugfixes
* Fix divison remainder issue for Colors, fixing purples.
### Other
* Default hub address (`hub.hub_urls`) has been changed to `https://hub.spacestation14.com/`.
## 218.0.0
### Breaking changes
* `Robust.Shared.Configuration.EnvironmentVariables` is now internal and no longer usable by content.
### New features
* Add TryGetRandom to EntityManager to get a random entity with the specified component and TryGetRandom to IPrototypeManager to return a random prototype of the specified type.
* Add CopyData to AppearanceSystem.
* Update UI themes on prototype reloads.
* Allow scaling the line height of a RichTextLabel.
* You can now specify CVar overrides via environment variable with the `ROBUST_CVAR_*` prefix. For example `ROBUST_CVAR_game__hostname=foobar` would set the appropriate CVar. Double underscores in the environment variable name are replaced with ".".
* Added non-generic variant of `GetCVar` to `IConfigurationManager`.
* Add type tracking to FieldNotFoundErrorNode for serialization.
* Distance between lines of a `RichTextLabel` can now be modified with `LineHeightScale`.
* UI theme prototypes are now updated when reloaded.
* New `RA0025` analyzer diagnostic warns for manual assignment to `[Dependency]` fields.
### Bugfixes
* Request headers in `IStatusHandlerContext` are now case-insensitive.
* SetWorldPosition rotation now more closely aligns with prior behavior.
* Fix exception when inspecting elements in some cases.
* Fix HTTP errors on watchdog ping not being reported.
### Other
* Add an analyzer for redundantly assigning to dependency fields.
### Internal
* Remove redundant Exists checks in ContainerSystem.
* Improve logging on watchdog pings.
## 217.2.1
### Bugfixes
* Fix LineEdit tests on engine.
### Internal
* Make various ValueList enumerators access the span directly for performance.
## 217.2.0
### New features
* Added `AddComponents` and `RemoveComponents` methods to EntityManager that handle EntityPrototype / ComponentRegistry bulk component changes.
* Add double-clicking to LineEdit.
### Bugfixes
* Properly ignore non-hard fixtures for IntersectRayWithPredicate.
* Fix nullable TimeSpan addition on some platforms.
## 217.1.0
### New features
* Added `IRobustRandom.GetItems` extension methods for randomly picking multiple items from a collections.
* Added `SharedPhysicsSystem.EffectiveCurTime`. This is effectively a variation of `IGameTiming.CurTime` that takes into account the current physics sub-step.
### Bugfixes
* Fix `MapComponent.LightingEnabled` not leaving FOV rendering in a broken state.
### Internal
* `Shuffle<T>(Span<T>, System.Random)` has been removed, just use the builtin method.
## 217.0.0
### Breaking changes
* TransformSystem.SetWorldPosition and SetWorldPositionRotation will now also perform parent updates as necessary. Previously it would just set the entity's LocalPosition which may break if they were inside of a container. Now they will be removed from their container and TryFindGridAt will run to correctly parent them to the new position. If the old functionality is desired then you can use GetInvWorldMatrix to update the LocalPosition (bearing in mind containers may prevent this).
### New features
* Implement VV for AudioParams on SoundSpecifiers.
* Add AddUi to the shared UI system.
### Bugfixes
* Fix the first measure of ScrollContainer bars.
## 216.0.0
### Breaking changes
* The `net.low_lod_distance` cvar has been replaced with a new `net.pvs_priority_range`. Instead of limiting the range at which all entities are sent to a player, it now extends the range at which high priorities can be sent. The default value of this new cvar is 32.5, which is larger than the default `net.pvs_range` value of 25.
### New features
* You can now specify a component to not be saved to map files with `[UnsavedComponent]`.
* Added `ITileDefinitionManager.TryGetDefinition`.
* The map loader now tries to preserve the `tilemap` contents of map files, which should reduce diffs when re-saving a map after the game's internal tile IDs have changed.
### Bugfixes
* Fix buffered audio sources not being disposed.
## 215.3.1
### Bugfixes
* Revert zstd update.
## 215.3.0
### New features
* `EntityQuery<T>` now has `HasComp` and `TryComp` methods that are shorter than its existing ones.
* Added `PlacementInformation.UseEditorContext`.
* Added `Vector2Helpers` functions for comparing ranges between vectors.
### Bugfixes
* `Texture.GetPixel()`: fixed off-by-one with Y coordinate.
* `Texture.GetPixel()`: fix stack overflow when reading large images.
* `Texture.GetPixel()`: use more widely compatible OpenGL calls.
### Other
* Disabled `net.mtu_expand` again by default, as it was causing issues.
* Updated `SharpZstd` dependency.
## 215.2.0
### New features
* Implement basic VV for SoundSpecifiers.
### Bugfixes
* Fix QueueDel during EndCollideEvents from throwing while removing contacts.
## 215.1.0
### New features
* Add a CompletionHelper for audio filepaths that handles server packaging.
* Add Random.NextAngle(min, max) method and Pick for `ValueList<T>`.
* Added an `ICommonSession` parser for toolshed commands.
### Bugfixes
## 215.0.0
### Breaking changes
* Update Lidgren to 0.3.0
### New features
* Made a new `IMetricsManager` interface with an `UpdateMetrics` event that can be used to update Prometheus metrics whenever they are scraped.
* Also added a `metrics.update_interval` CVar to go along with this, when metrics are scraped without usage of Prometheus directly.
* IoC now contains an `IMeterFactory` implementation that you can use to instantiate metric meters.
* `net.mtu_ipv6` CVar allows specifying a different MTU value for IPv6.
* Allows `player:entity` to take a parameter representing the player name.
* Add collection parsing to the dev window for UI.
* Add a debug assert to Dirty(uid, comp) to catch mismatches being passed in.
### Bugfixes
* Support transform states with unknown parents.
* Fix serialization error logging.
* Fix naming of ResizableMemoryRegion metrics.
* Fix uncaught overflow exception when parsing NetEntities.
### Other
* The replay system now allows loading a replay with a mismatching serializer type hash. This means replays should be more robust against future version updates (engine security patches or .NET updates).
* `CheckBox`'s interior texture is now vertically centered.
* Lidgren.Network has been updated to [`v0.3.0`](https://github.com/space-wizards/SpaceWizards.Lidgren.Network/blob/v0.3.0/RELEASE-NOTES.md).
* Lowered default IPv4 MTU to 900 (from 1000).
* Automatic MTU expansion (`net.mtu_expand`) is now enabled by default.
### Internal
* Cleanup some Dirty component calls internally.
## 214.2.0
### New features

View File

@@ -11,6 +11,7 @@ cmd-parse-failure-uid = {$arg} is not a valid entity UID.
cmd-parse-failure-mapid = {$arg} is not a valid MapId.
cmd-parse-failure-grid = {$arg} is not a valid grid.
cmd-parse-failure-entity-exist = UID {$arg} does not correspond to an existing entity.
cmd-parse-failure-session = There is no session with username: {$username}
cmd-error-file-not-found = Could not find file: {$file}.
cmd-error-dir-not-found = Could not find directory: {$dir}.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -126,6 +126,33 @@ public sealed partial class AudioSystem : SharedAudioSystem
{
component.Source.SetAuxiliary(null);
}
switch (component.State)
{
case AudioState.Playing:
component.StartPlaying();
break;
case AudioState.Paused:
component.Pause();
break;
case AudioState.Stopped:
component.StopPlaying();
component.PlaybackPosition = 0f;
break;
}
// If playback position changed then update it.
if (!string.IsNullOrEmpty(component.FileName))
{
var position = (float) ((component.PauseTime ?? Timing.CurTime) - component.AudioStart).TotalSeconds;
var currentPosition = component.Source.PlaybackPosition;
var diff = Math.Abs(position - currentPosition);
if (diff > 0.1f)
{
component.PlaybackPosition = position;
}
}
}
/// <summary>
@@ -173,7 +200,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
private void SetupSource(Entity<AudioComponent> entity, AudioResource audioResource, TimeSpan? length = null)
{
var component = entity.Comp;
if (TryAudioLimit(component.FileName))
{
var newSource = _audio.CreateAudioSource(audioResource);
@@ -361,7 +388,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
var distance = delta.Length();
// Out of range so just clip it for us.
if (distance > component.MaxDistance)
if (GetAudioDistance(distance) > component.MaxDistance)
{
// Still keeps the source playing, just with no volume.
component.Gain = 0f;
@@ -427,13 +454,13 @@ public sealed partial class AudioSystem : SharedAudioSystem
return false;
}
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
return PlayStatic(filename, Filter.Local(), coordinates, true, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, Filter.Local(), uid, true, audioParams);
}
@@ -460,8 +487,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, AudioParams? audioParams = null, bool recordReplay = true)
private (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioGlobalMessage
@@ -493,8 +523,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// </summary>
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="entity">The entity "emitting" the audio.</param>
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
private (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid entity, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioEntityMessage
@@ -534,8 +567,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
/// <param name="filename">The resource path to the OGG Vorbis file to play.</param>
/// <param name="coordinates">The coordinates at which to play the audio.</param>
/// <param name="audioParams"></param>
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
private (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityCoordinates coordinates, AudioParams? audioParams = null, bool recordReplay = true)
{
if (string.IsNullOrEmpty(filename))
return null;
if (recordReplay && _replayRecording.IsRecording)
{
_replayRecording.RecordReplayMessage(new PlayAudioPositionalMessage
@@ -569,25 +605,25 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid entity, bool recordReplay, AudioParams? audioParams = null)
{
return PlayEntity(filename, entity, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
@@ -603,31 +639,31 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, uid, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, uid, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, coordinates, audioParams);
}

View File

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

View File

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

View File

@@ -294,7 +294,6 @@ namespace Robust.Client.Console.Commands
internal sealed class SnapGridGetCell : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "sggcell";
@@ -320,7 +319,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_map.TryGetGrid(_entManager.GetEntity(gridNet), out var grid))
if (_entManager.TryGetComponent<MapGridComponent>(_entManager.GetEntity(gridNet), out var grid))
{
foreach (var entity in grid.GetAnchoredEntities(new Vector2i(
int.Parse(indices.Split(',')[0], CultureInfo.InvariantCulture),
@@ -429,7 +428,6 @@ namespace Robust.Client.Console.Commands
internal sealed class GridTileCount : LocalizedCommands
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IMapManager _map = default!;
public override string Command => "gridtc";
@@ -448,7 +446,7 @@ namespace Robust.Client.Console.Commands
return;
}
if (_map.TryGetGrid(gridUid, out var grid))
if (_entManager.TryGetComponent<MapGridComponent>(gridUid, out var grid))
{
shell.WriteLine(grid.GetAllTiles().Count().ToString());
}
@@ -555,7 +553,7 @@ namespace Robust.Client.Console.Commands
if (type != typeof(Control))
cname = $"Control > {cname}";
returnVal.GetOrNew(cname).Add((member.Name, member.GetValue(control)?.ToString() ?? "null"));
returnVal.GetOrNew(cname).Add((member.Name, GetMemberValue(member, control, ", ")));
}
foreach (var (attachedProperty, value) in control.AllAttachedProperties)
@@ -570,6 +568,28 @@ namespace Robust.Client.Console.Commands
}
return returnVal;
}
internal static string PropertyValuesString(Control control, string key)
{
var member = GetAllMembers(control).Find(m => m.Name == key);
return GetMemberValue(member, control, "\n", "\"{0}\"");
}
private static string GetMemberValue(MemberInfo? member, Control control, string separator, string
wrap = "{0}")
{
var value = member?.GetValue(control);
var o = value switch
{
ICollection<Control> controls => string.Join(separator,
controls.Select(ctrl => $"{ctrl.Name}({ctrl.GetType()})")),
ICollection<string> list => string.Join(separator, list),
null => null,
_ => value.ToString()
};
// Convert to quote surrounded string or null with no quotes
return o is not null ? string.Format(wrap, o) : "null";
}
}
internal sealed class SetClipboardCommand : LocalizedCommands

View File

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

View File

@@ -6,13 +6,12 @@ using System.Linq;
using System.Numerics;
using System.Text;
using Robust.Client.Graphics;
using Robust.Client.Graphics.Clyde;
using Robust.Client.ResourceManagement;
using Robust.Client.Utility;
using Robust.Shared;
using Robust.Shared.Animations;
using Robust.Shared.ComponentTrees;
using Robust.Shared.GameObjects;
using Robust.Shared.Graphics;
using Robust.Shared.Graphics.RSI;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -30,6 +29,7 @@ using static Robust.Client.ComponentTrees.SpriteTreeSystem;
using DrawDepthTag = Robust.Shared.GameObjects.DrawDepth;
using static Robust.Shared.Serialization.TypeSerializers.Implementations.SpriteSpecifierSerializer;
using Direction = Robust.Shared.Maths.Direction;
using Vector4 = Robust.Shared.Maths.Vector4;
namespace Robust.Client.GameObjects
{
@@ -772,15 +772,7 @@ namespace Robust.Client.GameObjects
{
foreach (var keyString in layerDatum.MapKeys)
{
object key;
if (reflection.TryParseEnumReference(keyString, out var @enum))
{
key = @enum;
}
else
{
key = keyString;
}
var key = ParseKey(keyString);
if (LayerMap.TryGetValue(key, out var mappedIndex))
{
@@ -806,9 +798,30 @@ namespace Robust.Client.GameObjects
// If neither state: nor texture: were provided we assume that they want a blank invisible layer.
layer.Visible = layerDatum.Visible ?? layer.Visible;
if (layerDatum.CopyToShaderParameters is { } copyParameters)
{
layer.CopyToShaderParameters = new CopyToShaderParameters(ParseKey(copyParameters.LayerKey))
{
ParameterTexture = copyParameters.ParameterTexture,
ParameterUV = copyParameters.ParameterUV
};
}
else
{
layer.CopyToShaderParameters = null;
}
RebuildBounds();
}
private object ParseKey(string keyString)
{
if (reflection.TryParseEnumReference(keyString, out var @enum))
return @enum;
return keyString;
}
public void LayerSetData(object layerKey, PrototypeLayerData data)
{
if (!LayerMapTryGet(layerKey, out var layer, true))
@@ -1237,9 +1250,9 @@ namespace Robust.Client.GameObjects
public IEnumerable<ISpriteLayer> AllLayers => Layers;
// Lobby SpriteView rendering path
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null)
public void Render(DrawingHandleWorld drawingHandle, Angle eyeRotation, Angle worldRotation, Direction? overrideDirection = null, Vector2 position = default)
{
RenderInternal(drawingHandle, eyeRotation, worldRotation, Vector2.Zero, overrideDirection);
RenderInternal(drawingHandle, eyeRotation, worldRotation, position, overrideDirection);
}
[DataField("noRot")] private bool _screenLock = false;
@@ -1637,6 +1650,9 @@ namespace Robust.Client.GameObjects
[ViewVariables]
public LayerRenderingStrategy RenderingStrategy = LayerRenderingStrategy.UseSpriteStrategy;
[ViewVariables(VVAccess.ReadWrite)]
public CopyToShaderParameters? CopyToShaderParameters;
public Layer(SpriteComponent parent)
{
_parent = parent;
@@ -1665,6 +1681,8 @@ namespace Robust.Client.GameObjects
DirOffset = toClone.DirOffset;
_autoAnimated = toClone._autoAnimated;
RenderingStrategy = toClone.RenderingStrategy;
if (toClone.CopyToShaderParameters is { } copyToShaderParameters)
CopyToShaderParameters = new CopyToShaderParameters(copyToShaderParameters);
}
void ISerializationHooks.AfterDeserialization()
@@ -2009,8 +2027,6 @@ namespace Robust.Client.GameObjects
// Set the drawing transform for this layer
GetLayerDrawMatrix(dir, out var layerMatrix);
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
// The direction used to draw the sprite can differ from the one that the angle would naively suggest,
// due to direction overrides or offsets.
@@ -2020,7 +2036,41 @@ namespace Robust.Client.GameObjects
// Get the correct directional texture from the state, and draw it!
var texture = GetRenderTexture(_actualState, dir);
RenderTexture(drawingHandle, texture);
if (CopyToShaderParameters == null)
{
// Set the drawing transform for this layer
Matrix3.Multiply(in layerMatrix, in spriteMatrix, out var transformMatrix);
drawingHandle.SetTransform(in transformMatrix);
RenderTexture(drawingHandle, texture);
}
else
{
// Multiple atrocities to god being committed right here.
var otherLayerIdx = _parent.LayerMap[CopyToShaderParameters.LayerKey!];
var otherLayer = _parent.Layers[otherLayerIdx];
if (otherLayer.Shader is not { } shader)
{
// No shader set apparently..?
return;
}
if (!shader.Mutable)
otherLayer.Shader = shader = shader.Duplicate();
var clydeTexture = Clyde.RenderHandle.ExtractTexture(texture, null, out var csr);
var sr = Clyde.RenderHandle.WorldTextureBoundsToUV(clydeTexture, csr);
if (CopyToShaderParameters.ParameterTexture is { } paramTexture)
shader.SetParameter(paramTexture, clydeTexture);
if (CopyToShaderParameters.ParameterUV is { } paramUV)
{
var uv = new Vector4(sr.Left, sr.Bottom, sr.Right, sr.Top);
shader.SetParameter(paramUV, uv);
}
}
}
private void RenderTexture(DrawingHandleWorld drawingHandle, Texture texture)
@@ -2098,6 +2148,23 @@ namespace Robust.Client.GameObjects
}
}
/// <summary>
/// Instantiated version of <see cref="PrototypeCopyToShaderParameters"/>.
/// Has <see cref="LayerKey"/> actually resolved to a a real key.
/// </summary>
public sealed class CopyToShaderParameters(object layerKey)
{
public object LayerKey = layerKey;
public string? ParameterTexture;
public string? ParameterUV;
public CopyToShaderParameters(CopyToShaderParameters toClone) : this(toClone.LayerKey)
{
ParameterTexture = toClone.ParameterTexture;
ParameterUV = toClone.ParameterUV;
}
}
void IAnimationProperties.SetAnimatableProperty(string name, object value)
{
if (!name.StartsWith("layer/"))

View File

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

View File

@@ -26,6 +26,7 @@ namespace Robust.Client.GameObjects
[Dependency] private readonly IConsoleHost _conHost = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private ISawmill _sawmillInputContext = default!;
@@ -151,7 +152,7 @@ namespace Robust.Client.GameObjects
var pxform = Transform(pent);
var wPos = pxform.WorldPosition + new Vector2(float.Parse(args[2]), float.Parse(args[3]));
var coords = EntityCoordinates.FromMap(EntityManager, pent, new MapCoordinates(wPos, pxform.MapID));
var coords = EntityCoordinates.FromMap(pent, new MapCoordinates(wPos, pxform.MapID), _transform, EntityManager);
var funcId = _inputManager.NetworkBindMap.KeyFunctionID(keyFunction);

View File

@@ -1,12 +1,9 @@
using Robust.Client.Graphics;
using Robust.Client.Map;
using Robust.Client.Physics;
using Robust.Client.ResourceManagement;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics.Dynamics;
namespace Robust.Client.GameObjects;
@@ -16,6 +13,17 @@ public sealed class MapSystem : SharedMapSystem
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
protected override MapId GetNextMapId()
{
// Client-side map entities use negative map Ids to avoid conflict with server-side maps.
var id = new MapId(--LastMapId);
while (MapManager.MapExists(id))
{
id = new MapId(--LastMapId);
}
return id;
}
public override void Initialize()
{
base.Initialize();
@@ -27,9 +35,4 @@ public sealed class MapSystem : SharedMapSystem
base.Shutdown();
_overlayManager.RemoveOverlay<TileEdgeOverlay>();
}
protected override void OnMapAdd(EntityUid uid, MapComponent component, ComponentAdd args)
{
EnsureComp<PhysicsMapComponent>(uid);
}
}

View File

@@ -1,84 +1,8 @@
using Robust.Client.Player;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Reflection;
using System;
using UserInterfaceComponent = Robust.Shared.GameObjects.UserInterfaceComponent;
namespace Robust.Client.GameObjects
namespace Robust.Client.GameObjects;
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IDynamicTypeFactory _dynamicTypeFactory = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(MessageReceived);
}
private void MessageReceived(BoundUIWrapMessage ev)
{
var uid = GetEntity(ev.Entity);
if (!TryComp<UserInterfaceComponent>(uid, out var cmp))
return;
var uiKey = ev.UiKey;
var message = ev.Message;
message.Session = _playerManager.LocalSession!;
message.Entity = GetNetEntity(uid);
message.UiKey = uiKey;
// Raise as object so the correct type is used.
RaiseLocalEvent(uid, (object)message, true);
switch (message)
{
case OpenBoundInterfaceMessage _:
TryOpenUi(uid, uiKey, cmp);
break;
case CloseBoundInterfaceMessage _:
TryCloseUi(message.Session, uid, uiKey, remoteCall: true, uiComp: cmp);
break;
default:
if (cmp.OpenInterfaces.TryGetValue(uiKey, out var bui))
bui.InternalReceiveMessage(message);
break;
}
}
private bool TryOpenUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? uiComp = null)
{
if (!Resolve(uid, ref uiComp))
return false;
if (uiComp.OpenInterfaces.ContainsKey(uiKey))
return false;
var data = uiComp.MappedInterfaceData[uiKey];
// TODO: This type should be cached, but I'm too lazy.
var type = _reflectionManager.LooseGetType(data.ClientType);
var boundInterface =
(BoundUserInterface) _dynamicTypeFactory.CreateInstance(type, new object[] {uid, uiKey});
boundInterface.Open();
uiComp.OpenInterfaces[uiKey] = boundInterface;
if (_playerManager.LocalSession is { } playerSession)
{
uiComp.Interfaces[uiKey]._subscribedSessions.Add(playerSession);
RaiseLocalEvent(uid, new BoundUIOpenedEvent(uiKey, uid, playerSession), true);
}
return true;
}
}
}

View File

@@ -125,6 +125,8 @@ namespace Robust.Client.GameStates
#endif
private bool _resettingPredictedEntities;
private readonly List<EntityUid> _brokenEnts = new();
private readonly List<(EntityUid, NetEntity)> _toStart = new();
/// <inheritdoc />
public void Initialize()
@@ -667,7 +669,16 @@ namespace Robust.Client.GameStates
foreach (var netEntity in createdEntities)
{
#if EXCEPTION_TOLERANCE
if (!_entityManager.TryGetEntityData(netEntity, out _, out var meta))
{
_sawmill.Error($"Encountered deleted entity while merging implicit data! NetEntity: {netEntity}");
continue;
}
#else
var (_, meta) = _entityManager.GetEntityData(netEntity);
#endif
var compData = _compDataPool.Get();
_outputData.Add(netEntity, compData);
@@ -700,7 +711,7 @@ namespace Robust.Client.GameStates
{
using var _ = _timing.StartStateApplicationArea();
// TODO repays optimize this.
// TODO replays optimize this.
// This currently just saves game states as they are applied.
// However this is inefficient and may have redundant data.
// E.g., we may record states: [10 to 15] [11 to 16] *error* [0 to 18] [18 to 19] [18 to 20] ...
@@ -876,9 +887,22 @@ namespace Robust.Client.GameStates
{
foreach (var (entity, data) in _toApply)
{
#if EXCEPTION_TOLERANCE
try
{
#endif
HandleEntityState(entity, data.NetEntity, data.Meta, _entities.EventBus, data.curState,
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
data.nextState, data.LastApplied, curState.ToSequence, data.EnteringPvs);
#if EXCEPTION_TOLERANCE
}
catch (Exception e)
{
_sawmill.Error($"Caught exception while applying entity state. Entity: {_entities.ToPrettyString(entity)}. Exception: {e}");
_entityManager.DeleteEntity(entity);
RequestFullState();
continue;
}
#endif
if (!data.EnteringPvs)
continue;
@@ -917,7 +941,7 @@ namespace Robust.Client.GameStates
{
try
{
ProcessDeletions(delSpan, xforms, xformSys);
ProcessDeletions(delSpan, xforms, metas, xformSys);
}
catch (Exception e)
{
@@ -962,6 +986,7 @@ namespace Robust.Client.GameStates
}
var xforms = _entities.GetEntityQuery<TransformComponent>();
var metas = _entities.GetEntityQuery<MetaDataComponent>();
var xformSys = _entitySystemManager.GetEntitySystem<SharedTransformSystem>();
_toDelete.Clear();
@@ -990,12 +1015,12 @@ namespace Robust.Client.GameStates
// This entity is going to get deleted, but maybe some if its children won't be, so lets detach them to
// null. First we will detach the parent in order to reduce the number of broadphase/lookup updates.
xformSys.DetachParentToNull(ent, xform);
xformSys.DetachEntity(ent, xform);
// Then detach all children.
foreach (var child in xform._children)
{
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
if (deleteClientChildren
&& !deleteClientEntities // don't add duplicates
@@ -1014,9 +1039,9 @@ namespace Robust.Client.GameStates
}
}
private void ProcessDeletions(
ReadOnlySpan<NetEntity> delSpan,
private void ProcessDeletions(ReadOnlySpan<NetEntity> delSpan,
EntityQuery<TransformComponent> xforms,
EntityQuery<MetaDataComponent> metas,
SharedTransformSystem xformSys)
{
// Processing deletions is non-trivial, because by default deletions will also delete all child entities.
@@ -1043,13 +1068,13 @@ namespace Robust.Client.GameStates
continue; // Already deleted? or never sent to us?
// First, a single recursive map change
xformSys.DetachParentToNull(id.Value, xform);
xformSys.DetachEntity(id.Value, xform);
// Then detach all children.
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
xformSys.DetachParentToNull(child, xforms.GetComponent(child), xform);
xformSys.DetachEntity(child, xforms.Get(child), metas.Get(child), xform);
}
// Finally, delete the entity.
@@ -1138,13 +1163,13 @@ namespace Robust.Client.GameStates
if ((meta.Flags & MetaDataFlags.InContainer) != 0 &&
metas.TryGetComponent(xform.ParentUid, out var containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0 &&
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container, null, true))
containerSys.TryGetContainingContainer(xform.ParentUid, ent.Value, out container))
{
containerSys.Remove((ent.Value, xform, meta), container, false, true);
}
meta._flags |= MetaDataFlags.Detached;
xformSys.DetachParentToNull(ent.Value, xform);
xformSys.DetachEntity(ent.Value, xform);
DebugTools.Assert((meta.Flags & MetaDataFlags.InContainer) == 0);
if (container != null)
@@ -1157,63 +1182,58 @@ namespace Robust.Client.GameStates
private void InitializeAndStart(Dictionary<NetEntity, EntityState> toCreate)
{
var metaQuery = _entityManager.GetEntityQuery<MetaDataComponent>();
_toStart.Clear();
#if EXCEPTION_TOLERANCE
var brokenEnts = new List<EntityUid>();
#endif
using (_prof.Group("Initialize Entity"))
{
EntityUid entity = default;
foreach (var netEntity in toCreate.Keys)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.InitializeEntity(entity, metaQuery.GetComponent(entity));
#if EXCEPTION_TOLERANCE
(entity, var meta) = _entityManager.GetEntityData(netEntity);
_entities.InitializeEntity(entity, meta);
_toStart.Add((entity, netEntity));
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Init: ent={_entities.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Init: nent={netEntity}, ent={_entities.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
}
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
}
}
using (_prof.Group("Start Entity"))
{
foreach (var netEntity in toCreate.Keys)
foreach (var (entity, netEntity) in _toStart)
{
var entity = _entityManager.GetEntity(netEntity);
#if EXCEPTION_TOLERANCE
try
{
#endif
_entities.StartEntity(entity);
#if EXCEPTION_TOLERANCE
_entities.StartEntity(entity);
}
catch (Exception e)
{
_sawmill.Error($"Server entity threw in Start: ent={_entityManager.ToPrettyString(entity)}");
_sawmill.Error($"Server entity threw in Start: nent={netEntity}, ent={_entityManager.ToPrettyString(entity)}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(InitializeAndStart)}");
brokenEnts.Add(entity);
toCreate.Remove(netEntity);
}
_toCreate.Remove(netEntity);
_brokenEnts.Add(entity);
#if !EXCEPTION_TOLERANCE
throw;
#endif
}
}
}
#if EXCEPTION_TOLERANCE
foreach (var entity in brokenEnts)
foreach (var entity in _brokenEnts)
{
_entityManager.DeleteEntity(entity);
}
#endif
_brokenEnts.Clear();
}
private void HandleEntityState(EntityUid uid, NetEntity netEntity, MetaDataComponent meta, IEventBus bus, EntityState? curState,
@@ -1329,23 +1349,8 @@ namespace Robust.Client.GameStates
foreach (var (comp, cur, next) in _compStateWork.Values)
{
try
{
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
}
#pragma warning disable CS0168 // Variable is declared but never used
catch (Exception e)
#pragma warning restore CS0168 // Variable is declared but never used
{
#if EXCEPTION_TOLERANCE
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
_runtimeLog.LogException(e, $"{nameof(ClientGameStateManager)}.{nameof(HandleEntityState)}");
#else
_sawmill.Error($"Failed to apply comp state: entity={_entities.ToPrettyString(uid)}, comp={comp.GetType()}");
throw;
#endif
}
var handleState = new ComponentHandleState(cur, next);
bus.RaiseComponentEvent(comp, ref handleState);
}
}
@@ -1414,10 +1419,10 @@ namespace Robust.Client.GameStates
_entities.TryGetComponent(xform.ParentUid, out MetaDataComponent? containerMeta) &&
(containerMeta.Flags & MetaDataFlags.Detached) == 0)
{
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container, null, true);
containerSys.TryGetContainingContainer(xform.ParentUid, uid, out container);
}
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachParentToNull(uid, xform);
_entities.EntitySysManager.GetEntitySystem<TransformSystem>().DetachEntity(uid, xform);
if (container != null)
containerSys.AddExpectedEntity(_entities.GetNetEntity(uid), container);

View File

@@ -6,7 +6,8 @@ namespace Robust.Client.Graphics.Clyde
{
("aPos", 0),
("tCoord", 1),
("modulate", 2)
("tCoord2", 2),
("modulate", 3)
};
private const int UniIModUV = 0;

View File

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

View File

@@ -11,6 +11,7 @@ using Robust.Shared;
using Robust.Shared.Enums;
using Robust.Shared.Graphics;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Profiling;
using Robust.Shared.Utility;
@@ -250,10 +251,8 @@ namespace Robust.Client.Graphics.Clyde
private void DrawEntities(Viewport viewport, Box2Rotated worldBounds, Box2 worldAABB, IEye eye)
{
var mapId = eye.Position.MapId;
if (mapId == MapId.Nullspace || !_mapManager.HasMapEntity(mapId))
{
if (mapId == MapId.Nullspace)
return;
}
RenderOverlays(viewport, OverlaySpace.WorldSpaceBelowEntities, worldAABB, worldBounds);
var worldOverlays = GetOverlaysForSpace(OverlaySpace.WorldSpaceEntities);
@@ -514,7 +513,9 @@ namespace Robust.Client.Graphics.Clyde
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawLight && eye.DrawFov)
{
ApplyFovToBuffer(viewport, eye);
var mapUid = _mapManager.GetMapEntityId(eye.Position.MapId);
if (_entityManager.GetComponent<MapComponent>(mapUid).LightingEnabled)
ApplyFovToBuffer(viewport, eye);
}
}

View File

@@ -23,9 +23,12 @@ namespace Robust.Client.Graphics.Clyde
// Texture Coords.
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 2 * sizeof(float));
GL.EnableVertexAttribArray(1);
// Colour Modulation.
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
// Texture Coords (2).
GL.VertexAttribPointer(2, 2, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 4 * sizeof(float));
GL.EnableVertexAttribArray(2);
// Colour Modulation.
GL.VertexAttribPointer(3, 4, VertexAttribPointerType.Float, false, sizeof(Vertex2D), 6 * sizeof(float));
GL.EnableVertexAttribArray(3);
}
// NOTE: This is:
@@ -37,6 +40,7 @@ namespace Robust.Client.Graphics.Clyde
{
public readonly Vector2 Position;
public readonly Vector2 TextureCoordinates;
public readonly Vector2 TextureCoordinates2;
// Note that this color is in linear space.
public readonly Color Modulate;
@@ -48,6 +52,15 @@ namespace Robust.Client.Graphics.Clyde
Modulate = modulate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vertex2D(Vector2 position, Vector2 textureCoordinates, Vector2 textureCoordinates2, Color modulate)
{
Position = position;
TextureCoordinates = textureCoordinates;
TextureCoordinates2 = textureCoordinates2;
Modulate = modulate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vertex2D(float x, float y, float u, float v, float r, float g, float b, float a)
: this(new Vector2(x, y), new Vector2(u, v), new Color(r, g, b, a))

View File

@@ -15,7 +15,7 @@ namespace Robust.Client.Graphics.Clyde
{
private RenderHandle _renderHandle = default!;
private sealed class RenderHandle : IRenderHandle
internal sealed class RenderHandle : IRenderHandle
{
private readonly Clyde _clyde;
private readonly IEntityManager _entities;
@@ -88,16 +88,21 @@ namespace Robust.Client.Graphics.Clyde
{
var clydeTexture = ExtractTexture(texture, in subRegion, out var csr);
var (w, h) = clydeTexture.Size;
var sr = new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
var sr = WorldTextureBoundsToUV(clydeTexture, csr);
_clyde.DrawTexture(clydeTexture.TextureId, bl, br, tl, tr, in modulate, in sr);
}
internal static Box2 WorldTextureBoundsToUV(ClydeTexture texture, UIBox2 csr)
{
var (w, h) = texture.Size;
return new Box2(csr.Left / w, (h - csr.Bottom) / h, csr.Right / w, (h - csr.Top) / h);
}
/// <summary>
/// Converts a subRegion (px) into texture coords (0-1) of a given texture (cells of the textureAtlas).
/// </summary>
private static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
internal static ClydeTexture ExtractTexture(Texture texture, in UIBox2? subRegion, out UIBox2 sr)
{
if (texture is AtlasTexture atlas)
{

View File

@@ -578,10 +578,10 @@ namespace Robust.Client.Graphics.Clyde
// TODO: split batch if necessary.
var vIdx = BatchVertexIndex;
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, modulate);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, modulate);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, modulate);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, modulate);
BatchVertexData[vIdx + 0] = new Vertex2D(bl, texCoords.BottomLeft, new Vector2(0, 0), modulate);
BatchVertexData[vIdx + 1] = new Vertex2D(br, texCoords.BottomRight, new Vector2(1, 0), modulate);
BatchVertexData[vIdx + 2] = new Vertex2D(tr, texCoords.TopRight, new Vector2(1, 1), modulate);
BatchVertexData[vIdx + 3] = new Vertex2D(tl, texCoords.TopLeft, new Vector2(0, 1), modulate);
BatchVertexIndex += 4;
QuadBatchIndexWrite(BatchIndexData, ref BatchIndexIndex, (ushort) vIdx);

View File

@@ -601,7 +601,7 @@ namespace Robust.Client.Graphics.Clyde
}
}
private sealed class ClydeTexture : OwnedTexture
internal sealed class ClydeTexture : OwnedTexture
{
private readonly Clyde _clyde;
public readonly bool IsSrgb;
@@ -649,24 +649,30 @@ namespace Robust.Client.Graphics.Clyde
return $"ClydeTexture: ({TextureId})";
}
public override Color GetPixel(int x, int y)
public override unsafe Color GetPixel(int x, int y)
{
if (!_clyde._loadedTextures.TryGetValue(TextureId, out var loaded))
{
throw new DataException("Texture not found");
}
Span<byte> rgba = stackalloc byte[4*this.Size.X*this.Size.Y];
unsafe
{
fixed (byte* p = rgba)
{
var curTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
var bufSize = 4 * loaded.Size.X * loaded.Size.Y;
var buffer = ArrayPool<byte>.Shared.Rent(bufSize);
GL.GetTextureImage(loaded.OpenGLObject.Handle, 0, PF.Rgba, PT.UnsignedByte, 4*this.Size.X*this.Size.Y, (IntPtr) p);
}
GL.BindTexture(TextureTarget.Texture2D, loaded.OpenGLObject.Handle);
fixed (byte* p = buffer)
{
GL.GetnTexImage(TextureTarget.Texture2D, 0, PF.Rgba, PT.UnsignedByte, bufSize, (IntPtr) p);
}
int pixelPos = (this.Size.X*(this.Size.Y-y) + x)*4;
return new Color(rgba[pixelPos+0], rgba[pixelPos+1], rgba[pixelPos+2], rgba[pixelPos+3]);
GL.BindTexture(TextureTarget.Texture2D, curTexture2D);
var pixelPos = (loaded.Size.X * (loaded.Size.Y - y - 1) + x) * 4;
var color = new Color(buffer[pixelPos+0], buffer[pixelPos+1], buffer[pixelPos+2], buffer[pixelPos+3]);
ArrayPool<byte>.Shared.Return(buffer);
return color;
}
}

View File

@@ -1,4 +1,5 @@
varying highp vec2 UV;
varying highp vec2 UV2;
varying highp vec2 Pos;
varying highp vec4 VtxModulate;

View File

@@ -2,10 +2,12 @@
/*layout (location = 0)*/ attribute vec2 aPos;
// Texture coordinates.
/*layout (location = 1)*/ attribute vec2 tCoord;
/*layout (location = 2)*/ attribute vec2 tCoord2;
// Colour modulation.
/*layout (location = 2)*/ attribute vec4 modulate;
/*layout (location = 3)*/ attribute vec4 modulate;
varying vec2 UV;
varying vec2 UV2;
varying vec2 Pos;
varying vec4 VtxModulate;
@@ -36,5 +38,6 @@ void main()
gl_Position = vec4(VERTEX, 0.0, 1.0);
Pos = (VERTEX + 1.0) / 2.0;
UV = mix(modifyUV.xy, modifyUV.zw, tCoord);
UV2 = tCoord2;
VtxModulate = zFromSrgb(modulate);
}

View File

@@ -1,4 +1,5 @@
varying highp vec2 UV;
varying highp vec2 UV2;
uniform sampler2D lightMap;

View File

@@ -2,10 +2,12 @@
/*layout (location = 0)*/ attribute vec2 aPos;
// Texture coordinates.
/*layout (location = 1)*/ attribute vec2 tCoord;
/*layout (location = 2)*/ attribute vec2 tCoord2;
// Colour modulation.
/*layout (location = 2)*/ attribute vec4 modulate;
/*layout (location = 3)*/ attribute vec4 modulate;
varying vec2 UV;
varying vec2 UV2;
// Maybe we should merge these CPU side.
// idk yet.
@@ -40,6 +42,7 @@ void main()
vec2 VERTEX = aPos;
UV = tCoord;
UV2 = tCoord2;
// [SHADER_CODE]

View File

@@ -114,43 +114,12 @@ namespace Robust.Client.Graphics
DrawPrimitives(primitiveTopology, White, indices, drawVertices);
}
private static void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
private void PadVerticesV2(ReadOnlySpan<Vector2> input, Span<DrawVertexUV2DColor> output, Color color)
{
if (input.Length == 0)
return;
if (input.Length != output.Length)
Color colorLinear = Color.FromSrgb(color);
for (var i = 0; i < output.Length; i++)
{
throw new InvalidOperationException("Invalid lengths!");
}
var colorLinear = Color.FromSrgb(color);
var colorVec = Unsafe.As<Color, Vector128<float>>(ref colorLinear);
var uvVec = Vector128.Create(0, 0, 0.5f, 0.5f);
var maskVec = Vector128.Create(0xFFFFFFFF, 0xFFFFFFFF, 0, 0).AsSingle();
var simdVectors = (nuint)(input.Length / 2);
ref readonly var srcBase = ref Unsafe.As<Vector2, float>(ref Unsafe.AsRef(in input[0]));
ref var dstBase = ref Unsafe.As<DrawVertexUV2DColor, float>(ref output[0]);
for (nuint i = 0; i < simdVectors; i++)
{
var positions = Vector128.LoadUnsafe(in srcBase, i * 4);
var posColorLower = (positions & maskVec) | uvVec;
var posColorUpper = (Vector128.Shuffle(positions, Vector128.Create(2, 3, 0, 0)) & maskVec) | uvVec;
posColorLower.StoreUnsafe(ref dstBase, i * 16);
colorVec.StoreUnsafe(ref dstBase, i * 16 + 4);
posColorUpper.StoreUnsafe(ref dstBase, i * 16 + 8);
colorVec.StoreUnsafe(ref dstBase, i * 16 + 12);
}
var lastPos = (int)simdVectors * 2;
if (lastPos != output.Length)
{
// Odd number of vertices. Handle the last manually.
output[lastPos] = new DrawVertexUV2DColor(input[lastPos], new Vector2(0.5f, 0.5f), colorLinear);
output[i] = new DrawVertexUV2DColor(input[i], new Vector2(0.5f, 0.5f), colorLinear);
}
}
@@ -268,6 +237,8 @@ namespace Robust.Client.Graphics
{
public Vector2 Position;
public Vector2 UV;
public Vector2 UV2;
/// <summary>
/// Modulation colour for this vertex.
/// Note that this color is in linear space.

View File

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

View File

@@ -34,7 +34,7 @@ namespace Robust.Client.Placement.Modes
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared())
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager, pManager.EntityManager.System<SharedTransformSystem>())).LengthSquared())
.ToList();
if (snapToEntities.Count == 0)

View File

@@ -2,6 +2,7 @@ using System;
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Client.Placement.Modes
@@ -24,7 +25,7 @@ namespace Robust.Client.Placement.Modes
SnapSize = 1f;
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
Grid = pManager.MapManager.GetGrid(gridId);
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
SnapSize = Grid.TileSize; //Find snap size for the grid.
}
else

View File

@@ -56,7 +56,7 @@ namespace Robust.Client.Placement.Modes
SnapSize = 1f;
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
Grid = pManager.MapManager.GetGrid(gridId);
Grid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
SnapSize = Grid.TileSize; //Find snap size for the grid.
}
else

View File

@@ -1,5 +1,6 @@
using System.Numerics;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement.Modes
{
@@ -19,7 +20,7 @@ namespace Robust.Client.Placement.Modes
var gridId = MouseCoords.GetGridUid(pManager.EntityManager);
if (!pManager.MapManager.TryGetGrid(gridId, out var mapGrid))
if (!pManager.EntityManager.TryGetComponent<MapGridComponent>(gridId, out var mapGrid))
return;
CurrentTile = mapGrid.GetTileRef(MouseCoords);

View File

@@ -1,6 +1,7 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement.Modes
{
@@ -20,7 +21,7 @@ namespace Robust.Client.Placement.Modes
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.MapManager.GetGrid(gridId);
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
tileSize = mapGrid.TileSize; //convert from ushort to float
}

View File

@@ -2,6 +2,7 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
namespace Robust.Client.Placement.Modes
@@ -22,7 +23,7 @@ namespace Robust.Client.Placement.Modes
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.MapManager.GetGrid(gridId);
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
CurrentTile = mapGrid.GetTileRef(MouseCoords);
tileSize = mapGrid.TileSize; //convert from ushort to float
}

View File

@@ -1,6 +1,7 @@
using System.Numerics;
using Robust.Shared.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement.Modes
{
@@ -20,7 +21,7 @@ namespace Robust.Client.Placement.Modes
var gridIdOpt = MouseCoords.GetGridUid(pManager.EntityManager);
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var mapGrid = pManager.MapManager.GetGrid(gridId);
var mapGrid = pManager.EntityManager.GetComponent<MapGridComponent>(gridId);
tileSize = mapGrid.TileSize; //convert from ushort to float
}

View File

@@ -21,6 +21,7 @@ using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Log;
using Direction = Robust.Shared.Maths.Direction;
using Robust.Shared.Map.Components;
namespace Robust.Client.Placement
{
@@ -79,6 +80,10 @@ namespace Robust.Client.Placement
private set
{
_isActive = value;
if (CurrentPermission?.UseEditorContext is false)
return;
SwitchEditorContext(value);
}
}
@@ -332,7 +337,7 @@ namespace Robust.Client.Placement
private void HandleTileChanged(ref TileChangedEvent args)
{
var coords = MapManager.GetGrid(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
var coords = EntityManager.GetComponent<MapGridComponent>(args.NewTile.GridUid).GridTileToLocal(args.NewTile.GridIndices);
_pendingTileChanges.RemoveAll(c => c.Item1 == coords);
}
@@ -753,7 +758,7 @@ namespace Robust.Client.Placement
// If we have actually placed something on a valid grid...
if (gridIdOpt is EntityUid gridId && gridId.IsValid())
{
var grid = MapManager.GetGrid(gridId);
var grid = EntityManager.GetComponent<MapGridComponent>(gridId);
// no point changing the tile to the same thing.
if (grid.GetTileRef(coordinates).Tile.TypeId == CurrentPermission.TileType)

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;
@@ -115,11 +116,12 @@ namespace Robust.Client.Placement
var dirAng = pManager.Direction.ToAngle();
var spriteSys = pManager.EntityManager.System<SpriteSystem>();
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
foreach (var coordinate in locationcollection)
{
if (!coordinate.IsValid(pManager.EntityManager))
return; // Just some paranoia just in case
var worldPos = coordinate.ToMapPos(pManager.EntityManager);
var worldPos = coordinate.ToMapPos(pManager.EntityManager, transformSys);
var worldRot = pManager.EntityManager.GetComponent<TransformComponent>(coordinate.EntityId).WorldRotation + dirAng;
sprite.Color = IsValidPosition(coordinate) ? ValidPlaceColor : InvalidPlaceColor;
@@ -136,11 +138,12 @@ namespace Robust.Client.Placement
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (mousePos.MapId == MapId.Nullspace)
yield break;
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
var (_, (x, y)) = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
float iterations;
Vector2 distance;
if (Math.Abs(x) > Math.Abs(y))
@@ -167,11 +170,12 @@ namespace Robust.Client.Placement
{
var mouseScreen = pManager.InputManager.MouseScreenPosition;
var mousePos = pManager.EyeManager.PixelToMap(mouseScreen);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (mousePos.MapId == MapId.Nullspace)
yield break;
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, pManager.EntityManager) - pManager.StartPoint;
var placementdiff = EntityCoordinates.FromMap(pManager.StartPoint.EntityId, mousePos, transformSys, pManager.EntityManager) - pManager.StartPoint;
var xSign = Math.Sign(placementdiff.X);
var ySign = Math.Sign(placementdiff.Y);
@@ -193,9 +197,9 @@ namespace Robust.Client.Placement
public TileRef GetTileRef(EntityCoordinates coordinates)
{
var gridUidOpt = coordinates.GetGridUid(pManager.EntityManager);
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.MapManager.GetGrid(gridUid).GetTileRef(MouseCoords)
return gridUidOpt is EntityUid gridUid && gridUid.IsValid() ? pManager.EntityManager.GetComponent<MapGridComponent>(gridUid).GetTileRef(MouseCoords)
: new TileRef(gridUidOpt ?? EntityUid.Invalid,
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager), Tile.Empty);
MouseCoords.ToVector2i(pManager.EntityManager, pManager.MapManager, pManager.EntityManager.System<SharedTransformSystem>()), Tile.Empty);
}
public TextureResource GetSprite(string key)
@@ -223,7 +227,8 @@ namespace Robust.Client.Placement
}
var range = pManager.CurrentPermission!.Range;
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, coordinates, range))
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
if (range > 0 && !pManager.EntityManager.GetComponent<TransformComponent>(controlled).Coordinates.InRange(pManager.EntityManager, transformSys, coordinates, range))
return false;
return true;
}
@@ -231,7 +236,8 @@ namespace Robust.Client.Placement
public bool IsColliding(EntityCoordinates coordinates)
{
var bounds = pManager.ColliderAABB;
var mapCoords = coordinates.ToMap(pManager.EntityManager);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
var mapCoords = coordinates.ToMap(pManager.EntityManager, transformSys);
var (x, y) = mapCoords.Position;
var collisionBox = Box2.FromDimensions(
@@ -261,7 +267,8 @@ namespace Robust.Client.Placement
return EntityCoordinates.FromMap(pManager.MapManager, mapCoords);
}
return EntityCoordinates.FromMap(pManager.EntityManager, gridUid, mapCoords);
var transformSys = pManager.EntityManager.System<SharedTransformSystem>();
return EntityCoordinates.FromMap(gridUid, mapCoords, transformSys, pManager.EntityManager);
}
}
}

View File

@@ -88,7 +88,6 @@ public sealed partial class ReplayLoadManager
if (initMessages != null)
UpdateMessages(initMessages, uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
UpdateMessages(messages[0], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase, true);
ProcessQueue(GameTick.MaxValue, detachQueue, detached);
var entSpan = state0.EntityStates.Value;
Dictionary<NetEntity, EntityState> entStates = new(entSpan.Count);
@@ -98,6 +97,8 @@ public sealed partial class ReplayLoadManager
entStates.Add(entState.NetEntity, modifiedState);
}
ProcessQueue(GameTick.MaxValue, detachQueue, detached, entStates);
await callback(0, states.Count, LoadingState.ProcessingFiles, true);
var playerSpan = state0.PlayerStates.Value;
Dictionary<NetUserId, SessionState> playerStates = new(playerSpan.Count);
@@ -144,7 +145,7 @@ public sealed partial class ReplayLoadManager
UpdatePlayerStates(curState.PlayerStates.Span, playerStates);
UpdateEntityStates(curState.EntityStates.Span, entStates, ref spawnedTracker, ref stateTracker, detached);
UpdateMessages(messages[i], uploadedFiles, prototypes, cvars, detachQueue, ref timeBase);
ProcessQueue(curState.ToSequence, detachQueue, detached);
ProcessQueue(curState.ToSequence, detachQueue, detached, entStates);
UpdateDeletions(curState.EntityDeletions, entStates, detached);
serverTime[i] = GetTime(curState.ToSequence) - initialTime;
ticksSinceLastCheckpoint++;
@@ -176,14 +177,28 @@ public sealed partial class ReplayLoadManager
private void ProcessQueue(
GameTick curTick,
Dictionary<GameTick, List<NetEntity>> detachQueue,
HashSet<NetEntity> detached)
HashSet<NetEntity> detached,
Dictionary<NetEntity, EntityState> entStates)
{
foreach (var (tick, ents) in detachQueue)
{
if (tick > curTick)
continue;
detachQueue.Remove(tick);
detached.UnionWith(ents);
foreach (var e in ents)
{
if (entStates.ContainsKey(e))
detached.Add(e);
else
{
// AFAIK this should only happen if the client skipped over some ticks, probably due to packet loss
// I.e., entity was created on tick n, then leaves PVS range on the tick n+1
// If the n-th tick gets dropped, the client only ever receives the pvs-leave message.
// In that case we should just ignore it.
_sawmill.Debug($"Received a PVS detach msg for entity {e} before it was received?");
}
}
}
}

View File

@@ -129,7 +129,7 @@ public sealed partial class ReplayLoadManager
return parsed.FirstOrDefault()?.Root as MappingDataNode;
}
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan Duration, TimeSpan StartTime, bool ClientSide)
private (MappingDataNode YamlData, HashSet<string> CVars, TimeSpan? Duration, TimeSpan StartTime, bool ClientSide)
LoadMetadata(IReplayFileReader fileReader)
{
_sawmill.Info($"Reading replay metadata");
@@ -137,23 +137,16 @@ public sealed partial class ReplayLoadManager
if (data == null)
throw new Exception("Failed to load yaml metadata");
TimeSpan duration;
var finalData = LoadYamlFinalMetadata(fileReader);
TimeSpan? duration = finalData == null
? null
: TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
if (finalData == null)
{
var msg = "Failed to load final yaml metadata";
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new Exception(msg);
_sawmill.Warning("Failed to load final yaml metadata. Partial/incomplete replay?");
_sawmill.Error(msg);
duration = TimeSpan.FromDays(1);
}
else
{
duration = TimeSpan.Parse(((ValueDataNode) finalData[MetaFinalKeyDuration]).Value);
}
var typeHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyTypeHash]).Value);
var typeHashString = ((ValueDataNode) data[MetaKeyTypeHash]).Value;
var typeHash = Convert.FromHexString(typeHashString);
var stringHash = Convert.FromHexString(((ValueDataNode) data[MetaKeyStringHash]).Value);
var startTick = ((ValueDataNode) data[MetaKeyStartTick]).Value;
var timeBaseTick = ((ValueDataNode) data[MetaKeyBaseTick]).Value;
@@ -161,7 +154,12 @@ public sealed partial class ReplayLoadManager
var clientSide = bool.Parse(((ValueDataNode) data[MetaKeyIsClientRecording]).Value);
if (!typeHash.SequenceEqual(_serializer.GetSerializableTypesHash()))
throw new Exception($"{nameof(IRobustSerializer)} hashes do not match. Loading replays using a bad replay-client version?");
{
if (!_confMan.GetCVar(CVars.ReplayIgnoreErrors))
throw new Exception($"RobustSerializer hash mismatch. do not match. Client hash: {_serializer.GetSerializableTypesHashString()}, replay hash: {typeHashString}.");
_sawmill.Warning($"RobustSerializer hash mismatch. Replay may fail to load!");
}
using var stringFile = fileReader.Open(FileStrings);
var stringData = new byte[stringFile.Length];

View File

@@ -79,13 +79,14 @@ internal sealed partial class ReplayPlaybackManager
if (checkpoint.DetachedStates == null)
return;
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length); ;
var metas = _entMan.GetEntityQuery<MetaDataComponent>();
DebugTools.Assert(checkpoint.Detached.Count == checkpoint.DetachedStates.Length);
foreach (var es in checkpoint.DetachedStates)
{
var uid = _entMan.GetEntity(es.NetEntity);
if (metas.TryGetComponent(uid, out var meta) && !meta.EntityDeleted)
if (_entMan.TryGetEntityData(es.NetEntity, out var uid, out var meta))
{
DebugTools.Assert(!meta.EntityDeleted);
continue;
}
var metaState = (MetaDataComponentState?)es.ComponentChanges.Value?
.FirstOrDefault(c => c.NetID == _metaId).State;
@@ -93,18 +94,16 @@ internal sealed partial class ReplayPlaybackManager
if (metaState == null)
throw new MissingMetadataException(es.NetEntity);
_entMan.CreateEntityUninitialized(metaState.PrototypeId, uid);
meta = metas.GetComponent(uid);
uid = _entMan.CreateEntity(metaState.PrototypeId, out meta);
// Client creates a client-side net entity for the newly created entity.
// We need to clear this mapping before assigning the real net id.
// TODO NetEntity Jank: prevent the client from creating this in the first place.
_entMan.ClearNetEntity(meta.NetEntity);
_entMan.SetNetEntity(uid.Value, es.NetEntity, meta);
_entMan.SetNetEntity(uid, es.NetEntity, meta);
_entMan.InitializeEntity(uid, meta);
_entMan.StartEntity(uid);
_entMan.InitializeEntity(uid.Value, meta);
_entMan.StartEntity(uid.Value);
meta.LastStateApplied = checkpoint.Tick;
}
}

View File

@@ -90,6 +90,7 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
var maxIndex = Math.Max(1, replay.States.Count - 1);
var state = replay.States[index];
var replayTime = TimeSpan.FromSeconds(TickSlider.Value);
var end = replay.Duration == null ? "N/A" : replay.Duration.Value.ToString(TimeFormat);
IndexLabel.Text = Loc.GetString("replay-time-box-index-label",
("current", index), ("total", maxIndex), ("percentage", percentage));
@@ -98,10 +99,10 @@ public sealed partial class ReplayControlWidget : UIWidget // AKA Tardis - The f
("current", state.ToSequence), ("total", replay.States[^1].ToSequence), ("percentage", percentage));
TimeLabel.Text = Loc.GetString("replay-time-box-replay-time-label",
("current", replayTime.ToString(TimeFormat)), ("end", replay.Duration.ToString(TimeFormat)), ("percentage", percentage));
("current", replayTime.ToString(TimeFormat)), ("end", end), ("percentage", percentage));
var serverTime = (replayTime + replay.StartTime).ToString(TimeFormat);
var duration = (replay.Duration + replay.StartTime).ToString(TimeFormat);
string duration = replay.Duration == null ? "N/A" : (replay.Duration + replay.StartTime).Value.ToString(TimeFormat);
ServerTimeLabel.Text = Loc.GetString("replay-time-box-server-time-label",
("current", serverTime), ("end", duration), ("percentage", percentage));

View File

@@ -10,6 +10,7 @@ using Robust.Shared;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.ContentPack;
using Robust.Shared.Graphics;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths;
@@ -142,6 +143,26 @@ namespace Robust.Client.ResourceManagement
}
});
// Do not meta-atlas RSIs with custom load parameters.
var atlasList = rsiList.Where(x => x.LoadParameters == TextureLoadParameters.Default).ToArray();
var nonAtlasList = rsiList.Where(x => x.LoadParameters != TextureLoadParameters.Default).ToArray();
foreach (var data in nonAtlasList)
{
if (data.Bad)
continue;
try
{
RSIResource.LoadTexture(Clyde, data);
}
catch (Exception e)
{
sawmill.Error($"Exception while loading RSI {data.Path}:\n{e}");
data.Bad = true;
}
}
// This combines individual RSI atlases into larger atlases to reduce draw batches. currently this is a VERY
// lazy bundling and is not at all compact, its basically an atlas of RSI atlases. Really what this should
// try to do is to have each RSI write directly to the atlas, rather than having each RSI write to its own
@@ -155,7 +176,7 @@ namespace Robust.Client.ResourceManagement
// TODO allow RSIs to opt out (useful for very big & rare RSIs)
// TODO combine with (non-rsi) texture atlas?
Array.Sort(rsiList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
Array.Sort(atlasList, (b, a) => (b.AtlasSheet?.Height ?? 0).CompareTo(a.AtlasSheet?.Height ?? 0));
// Each RSI sub atlas has a different size.
// Even if we iterate through them once to estimate total area, I have NFI how to sanely estimate an optimal square-texture size.
@@ -167,9 +188,9 @@ namespace Robust.Client.ResourceManagement
Vector2i offset = default;
int finalized = -1;
int atlasCount = 0;
for (int i = 0; i < rsiList.Length; i++)
for (int i = 0; i < atlasList.Length; i++)
{
var rsi = rsiList[i];
var rsi = atlasList[i];
if (rsi.Bad)
continue;
@@ -200,14 +221,14 @@ namespace Robust.Client.ResourceManagement
var height = offset.Y + deltaY;
var croppedSheet = new Image<Rgba32>(maxSize, height);
sheet.Blit(new UIBox2i(0, 0, maxSize, height), croppedSheet, default);
FinalizeMetaAtlas(rsiList.Length - 1, croppedSheet);
FinalizeMetaAtlas(atlasList.Length - 1, croppedSheet);
void FinalizeMetaAtlas(int toIndex, Image<Rgba32> sheet)
{
var atlas = Clyde.LoadTextureFromImage(sheet);
for (int i = finalized + 1; i <= toIndex; i++)
{
var rsi = rsiList[i];
var rsi = atlasList[i];
rsi.AtlasTexture = atlas;
}
@@ -255,9 +276,10 @@ namespace Robust.Client.ResourceManagement
}
sawmill.Debug(
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountErrored} errored) in {LoadTime}",
"Preloaded {CountLoaded} RSIs into {CountAtlas} Atlas(es?) ({CountNotAtlas} not atlassed, {CountErrored} errored) in {LoadTime}",
rsiList.Length,
atlasCount,
nonAtlasList.Length,
errors,
sw.Elapsed);

View File

@@ -40,17 +40,21 @@ namespace Robust.Client.ResourceManagement
var loadStepData = new LoadStepData {Path = path};
var manager = dependencies.Resolve<IResourceManager>();
LoadPreTexture(manager, loadStepData);
loadStepData.AtlasTexture = dependencies.Resolve<IClyde>().LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString());
LoadTexture(dependencies.Resolve<IClyde>(), loadStepData);
LoadPostTexture(loadStepData);
LoadFinish(dependencies.Resolve<IResourceCacheInternal>(), loadStepData);
loadStepData.AtlasSheet.Dispose();
}
internal static void LoadTexture(IClyde clyde, LoadStepData loadStepData)
{
loadStepData.AtlasTexture = clyde.LoadTextureFromImage(
loadStepData.AtlasSheet,
loadStepData.Path.ToString(),
loadStepData.LoadParameters);
}
internal static void LoadPreTexture(IResourceManager manager, LoadStepData data)
{
var manifestPath = data.Path / "meta.json";
@@ -178,6 +182,7 @@ namespace Robust.Client.ResourceManagement
data.FrameSize = frameSize;
data.DimX = dimensionX;
data.CallbackOffsets = callbackOffsets;
data.LoadParameters = metadata.LoadParameters;
}
internal static void LoadPostTexture(LoadStepData data)
@@ -380,6 +385,7 @@ namespace Robust.Client.ResourceManagement
public Texture AtlasTexture = default!;
public Vector2i AtlasOffset;
public RSI Rsi = default!;
public TextureLoadParameters LoadParameters;
}
internal struct StateReg

View File

@@ -23,7 +23,7 @@
<PackageReference Include="Robust.Natives" />
<PackageReference Include="System.Numerics.Vectors" />
<PackageReference Include="TerraFX.Interop.Windows" PrivateAssets="compile" />
<PackageReference Condition="'$(FullRelease)' != 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
<PackageReference Condition="'$(RobustToolsBuild)' == 'True'" Include="JetBrains.Profiler.Api" PrivateAssets="compile" />
<PackageReference Include="SpaceWizards.Sodium" PrivateAssets="compile" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" />
<PackageReference Include="TerraFX.Interop.Xlib" />

View File

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

View File

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

View File

@@ -12,24 +12,30 @@ namespace Robust.Client.UserInterface
public partial class Control
{
private const float DefaultStretchRatio = 1;
private const float DefaultSetSize = float.NaN;
private const float DefaultMaxSize = float.PositiveInfinity;
private const HAlignment DefaultHAlignment = HAlignment.Stretch;
private const VAlignment DefaultVAlignment = VAlignment.Stretch;
private Vector2 _size;
[ViewVariables] internal Vector2? PreviousMeasure;
[ViewVariables] internal UIBox2? PreviousArrange;
private float _sizeFlagsStretchRatio = 1;
private float _sizeFlagsStretchRatio = DefaultStretchRatio;
private float _minWidth;
private float _minHeight;
private float _setWidth = float.NaN;
private float _setHeight = float.NaN;
private float _maxWidth = float.PositiveInfinity;
private float _maxHeight = float.PositiveInfinity;
private float _setWidth = DefaultSetSize;
private float _setHeight = DefaultSetSize;
private float _maxWidth = DefaultMaxSize;
private float _maxHeight = DefaultMaxSize;
private bool _horizontalExpand;
private bool _verticalExpand;
private HAlignment _horizontalAlignment = HAlignment.Stretch;
private VAlignment _verticalAlignment = VAlignment.Stretch;
private HAlignment _horizontalAlignment = DefaultHAlignment;
private VAlignment _verticalAlignment = DefaultVAlignment;
private Thickness _margin;
private bool _measuring;
private bool _arranging;
@@ -46,6 +52,10 @@ namespace Robust.Client.UserInterface
[ViewVariables] public bool IsMeasureValid { get; private set; }
[ViewVariables] public bool IsArrangeValid { get; private set; }
/// <summary>
/// Controls the amount of empty space in virtual pixels around the control.
/// </summary>
/// <remarks>Values can be provided as "All" or "Horizontal, Vertical" or "Left, Top, Right, Bottom"</remarks>
[ViewVariables]
public Thickness Margin
{
@@ -53,6 +63,7 @@ namespace Robust.Client.UserInterface
set
{
_margin = value;
SetLayoutStyleProp(LayoutStyleProperties.Margin);
InvalidateMeasure();
}
}
@@ -242,6 +253,7 @@ namespace Robust.Client.UserInterface
set
{
_horizontalAlignment = value;
SetLayoutStyleProp(LayoutStyleProperties.HorizontalAlignment);
InvalidateArrange();
}
}
@@ -258,6 +270,7 @@ namespace Robust.Client.UserInterface
set
{
_verticalAlignment = value;
SetLayoutStyleProp(LayoutStyleProperties.VerticalAlignment);
InvalidateArrange();
}
}
@@ -276,6 +289,7 @@ namespace Robust.Client.UserInterface
set
{
_horizontalExpand = value;
SetLayoutStyleProp(LayoutStyleProperties.HorizontalExpand);
Parent?.InvalidateMeasure();
}
}
@@ -294,6 +308,7 @@ namespace Robust.Client.UserInterface
set
{
_verticalExpand = value;
SetLayoutStyleProp(LayoutStyleProperties.VerticalExpand);
Parent?.InvalidateArrange();
}
}
@@ -318,6 +333,7 @@ namespace Robust.Client.UserInterface
_sizeFlagsStretchRatio = value;
SetLayoutStyleProp(LayoutStyleProperties.StretchRatio);
Parent?.InvalidateArrange();
}
}
@@ -394,6 +410,7 @@ namespace Robust.Client.UserInterface
set
{
_minWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.MinWidth);
InvalidateMeasure();
}
}
@@ -408,6 +425,7 @@ namespace Robust.Client.UserInterface
set
{
_minHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.MinHeight);
InvalidateMeasure();
}
}
@@ -422,6 +440,7 @@ namespace Robust.Client.UserInterface
set
{
_setWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.SetWidth);
InvalidateMeasure();
}
}
@@ -436,6 +455,7 @@ namespace Robust.Client.UserInterface
set
{
_setHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.SetHeight);
InvalidateMeasure();
}
}
@@ -450,6 +470,7 @@ namespace Robust.Client.UserInterface
set
{
_maxWidth = value;
SetLayoutStyleProp(LayoutStyleProperties.MaxWidth);
InvalidateMeasure();
}
}
@@ -464,6 +485,7 @@ namespace Robust.Client.UserInterface
set
{
_maxHeight = value;
SetLayoutStyleProp(LayoutStyleProperties.MaxHeight);
InvalidateMeasure();
}
}

View File

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

View File

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

View File

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

View File

@@ -29,6 +29,7 @@ namespace Robust.Client.UserInterface.Controls
TextureRect = new TextureRect
{
StyleClasses = { StyleClassCheckBox },
VerticalAlignment = VAlignment.Center,
};
hBox.AddChild(TextureRect);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -90,7 +90,7 @@ namespace Robust.Client.UserInterface.CustomControls.DebugMonitorControls
contents.TextMemory = FormatHelpers.FormatIntoMem(_textBuffer,
$@"UP: {sentBytes / ONE_KIBIBYTE:N} KiB/s, {sentPackets} pckt/s, {LastSentBytes / ONE_KIBIBYTE:N} KiB, {LastSentPackets} pckt
DOWN: {receivedBytes / ONE_KIBIBYTE:N} KiB/s, {receivedPackets} pckt/s, {LastReceivedBytes / ONE_KIBIBYTE:N} KiB, {LastReceivedPackets} pckt
PING: {NetManager.ServerChannel?.Ping ?? -1} ms");
PING: {NetManager.ServerChannel?.Ping ?? -1} ms, MTU: {NetManager.ServerChannel?.CurrentMtu} B");
}
}
}

View File

@@ -20,10 +20,14 @@
<BoxContainer Name="ControlTreeRoot" Orientation="Vertical" MouseFilter="Stop" />
</PanelContainer>
</ScrollContainer>
<ScrollContainer HScrollEnabled="False">
<ScrollContainer>
<BoxContainer Name="ControlProperties" Orientation="Vertical" MouseFilter="Stop" />
</ScrollContainer>
</SplitContainer>
</BoxContainer>
</PanelContainer>
<!-- TODO Remove and replace with a popup container on WindowRoot -->
<PopupContainer Name="PopupContainer" Access="Public">
<DevWindowTabUIPopup Name="UIPopup" />
</PopupContainer>
</Control>

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Numerics;
using Robust.Client.AutoGenerated;
using Robust.Client.Console.Commands;
using Robust.Client.Graphics;
@@ -208,25 +209,40 @@ public sealed partial class DevWindowTabUI : Control
});
foreach (var (prop, value) in values)
{
ControlProperties.AddChild(new BoxContainer
var button = new ContainerButton
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 3,
Margin = new Thickness(3, 1),
Children =
{
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 3,
Margin = new Thickness(3, 1),
Children =
{
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
new Label { Text = ":" }, // this is for the non colored ":", intentional
new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label { Text = $"{prop}", FontColorOverride = Color.GreenYellow },
new Label { Text = ":" }, // this is for the non colored ":", intentional
}
},
new Label { Text = $"{value}" },
}
},
new Label { Text = $"{value}" },
}
}
});
};
button.OnPressed += _ =>
{
// TODO replace with parenting to popup container on WindowRoot
UIPopup.Text = GuiDumpCommand.PropertyValuesString(SelectedControl, prop);
var box = UIBox2.FromDimensions(UserInterfaceManager.MousePositionScaled.Position
- GlobalPosition, Vector2.One);
UIPopup.Open(box);
};
ControlProperties.AddChild(button);
}
}
}

View File

@@ -0,0 +1,11 @@
<DevWindowTabUIPopup xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Robust.Client.UserInterface.DevWindowTabUIPopup"
xmlns:gfx="clr-namespace:Robust.Client.Graphics">
<PanelContainer>
<PanelContainer.PanelOverride>
<gfx:StyleBoxFlat BackgroundColor="#40404C" />
</PanelContainer.PanelOverride>
</PanelContainer>
<Label Name="TextLabel" />
</DevWindowTabUIPopup>

View File

@@ -0,0 +1,20 @@
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Robust.Client.UserInterface;
[GenerateTypedNameReferences]
internal sealed partial class DevWindowTabUIPopup : Popup
{
public string? Text
{
get => TextLabel.Text;
set => TextLabel.Text = value;
}
public DevWindowTabUIPopup()
{
RobustXamlLoader.Load(this);
}
}

View File

@@ -135,6 +135,17 @@ namespace Robust.Client.UserInterface
/// Plays the UI hover sound if relevant.
/// </summary>
void HoverSound();
/// <summary>
/// Sets <see cref="CurrentlyHovered"/> to the given control.
/// </summary>
void SetHovered(Control? control);
/// <summary>
/// Forces <see cref="CurrentlyHovered"/> to get updated. This is done automatically when the mouse is moved,
/// but not necessarily a new or existing control is rearranged.
/// </summary>
void UpdateHovered();
}
public readonly struct PostDrawUIRootEventArgs

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,7 @@ using Robust.Client.UserInterface.CustomControls;
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.UserInterface;
@@ -20,9 +21,10 @@ internal partial class UserInterfaceManager
private bool _needUpdateActiveCursor;
[ViewVariables] public Control? KeyboardFocused { get; private set; }
[ViewVariables] public Control? CurrentlyHovered { get; private set; } = default!;
[ViewVariables] public Control? CurrentlyHovered { get; private set; }
private Control? _controlFocused;
[ViewVariables]
public Control? ControlFocused
{
@@ -100,6 +102,7 @@ internal partial class UserInterfaceManager
return;
}
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
args.PointerLocation.Position - control.GlobalPixelPosition);
@@ -111,16 +114,18 @@ internal partial class UserInterfaceManager
args.Handle();
}
// Attempt to ensure that keybind-up events only get raised after a single keybind-down.
DebugTools.Assert(!_focusedControls.ContainsKey(args.Function));
_focusedControls[args.Function] = control;
OnKeyBindDown?.Invoke(control);
}
public void KeyBindUp(BoundKeyEventArgs args)
{
if (!_focusedControls.TryGetValue(args.Function, out var control))
{
// Only raise keybind-up for the control on which we previously raised keybind-down
if (!_focusedControls.Remove(args.Function, out var control) || control.Disposed)
return;
}
var guiArgs = new GUIBoundKeyEventArgs(args.Function, args.State, args.PointerLocation, args.CanFocus,
args.PointerLocation.Position / control.UIScale - control.GlobalPosition,
@@ -131,7 +136,6 @@ internal partial class UserInterfaceManager
// Always mark this as handled.
// The only case it should not be is if we do not have a control to click on,
// in which case we never reach this.
_focusedControls.Remove(args.Function);
args.Handle();
}
@@ -140,23 +144,7 @@ internal partial class UserInterfaceManager
_resetTooltipTimer();
// Update which control is considered hovered.
var newHovered = MouseGetControl(mouseMoveEventArgs.Position);
if (newHovered != CurrentlyHovered)
{
_clearTooltip();
CurrentlyHovered?.MouseExited();
CurrentlyHovered = newHovered;
CurrentlyHovered?.MouseEntered();
if (CurrentlyHovered != null)
{
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
}
else
{
_tooltipDelay = null;
}
_needUpdateActiveCursor = true;
}
SetHovered(newHovered);
var target = ControlFocused ?? newHovered;
if (target != null)
@@ -172,6 +160,33 @@ internal partial class UserInterfaceManager
}
}
public void UpdateHovered()
{
var ctrl = MouseGetControl(_inputManager.MouseScreenPosition);
SetHovered(ctrl);
}
public void SetHovered(Control? control)
{
if (control == CurrentlyHovered)
return;
_clearTooltip();
CurrentlyHovered?.MouseExited();
CurrentlyHovered = control;
CurrentlyHovered?.MouseEntered();
if (CurrentlyHovered != null)
{
_tooltipDelay = CurrentlyHovered.TooltipDelay ?? TooltipDelay;
}
else
{
_tooltipDelay = null;
}
_needUpdateActiveCursor = true;
}
private void UpdateActiveCursor()
{
// Consider mouse input focus first so that dragging windows don't act up etc.

View File

@@ -77,15 +77,12 @@ internal sealed partial class UserInterfaceManager
ReleaseKeyboardFocus(control);
RemoveModal(control);
if (control == CurrentlyHovered)
{
control.MouseExited();
CurrentlyHovered = null;
_clearTooltip();
}
if (control != ControlFocused) return;
ControlFocused = null;
if (control == ControlFocused)
ControlFocused = null;
if (control == CurrentlyHovered)
UpdateHovered();
}
public void PushModal(Control modal)

View File

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

View File

@@ -8,6 +8,8 @@ using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.ViewVariables.Editors;
using Robust.Client.ViewVariables.Instances;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
@@ -29,6 +31,8 @@ namespace Robust.Client.ViewVariables
[Dependency] private readonly IClientNetManager _netManager = default!;
[Dependency] private readonly IRobustSerializer _robustSerializer = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly IResourceManager _resManager = default!;
private uint _nextReqId = 1;
private readonly Vector2i _defaultWindowSize = (640, 420);
@@ -126,8 +130,12 @@ namespace Robust.Client.ViewVariables
return new VVPropEditorString();
}
if (type == typeof(EntProtoId) ||
type == typeof(EntProtoId?))
if (type == typeof(EntProtoId?))
{
return new VVPropEditorNullableEntProtoId();
}
if (type == typeof(EntProtoId))
{
return new VVPropEditorEntProtoId();
}
@@ -222,6 +230,12 @@ namespace Robust.Client.ViewVariables
return new VVPropEditorTimeSpan();
}
if (typeof(SoundSpecifier).IsAssignableFrom(type))
{
var control = new VVPropEditorSoundSpecifier(_protoManager, _resManager);
return control;
}
if (type == typeof(ViewVariablesBlobMembers.ServerKeyValuePairToken) ||
type.IsGenericType && type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
{

View File

@@ -0,0 +1,35 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
namespace Robust.Client.ViewVariables.Editors;
internal sealed class VVPropEditorNullableEntProtoId : VVPropEditor
{
protected override Control MakeUI(object? value)
{
var lineEdit = new LineEdit
{
Text = value is EntProtoId protoId ? protoId.Id : "",
Editable = !ReadOnly,
HorizontalExpand = true,
};
if (!ReadOnly)
{
lineEdit.OnTextEntered += e =>
{
if (string.IsNullOrWhiteSpace(e.Text))
{
ValueChanged(null);
}
else
{
ValueChanged((EntProtoId) e.Text);
}
};
}
return lineEdit;
}
}

View File

@@ -0,0 +1,393 @@
using System.Globalization;
using System.Numerics;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Audio;
using Robust.Shared.ContentPack;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables;
namespace Robust.Client.ViewVariables.Editors;
public sealed class VVPropEditorSoundSpecifier : VVPropEditor
{
private readonly IPrototypeManager _protoManager;
private readonly IResourceManager _resManager;
// Need to cache to some level just to make sure each edit doesn't reset the specifier to the default.
private SoundSpecifier? _specifier;
public VVPropEditorSoundSpecifier(IPrototypeManager protoManager, IResourceManager resManager)
{
_protoManager = protoManager;
_resManager = resManager;
}
protected override Control MakeUI(object? value)
{
var typeButton = new OptionButton()
{
Disabled = ReadOnly,
};
typeButton.AddItem(Loc.GetString("vv-sound-none"));
typeButton.AddItem(Loc.GetString("vv-sound-collection"), 1);
typeButton.AddItem(Loc.GetString("vv-sound-path"), 2);
var editBox = new LineEdit()
{
HorizontalExpand = true,
Editable = !ReadOnly,
};
var pathControls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
typeButton,
editBox
},
SetSize = new Vector2(384f, 32f)
};
if (value != null)
{
switch (value)
{
case SoundCollectionSpecifier collection:
typeButton.SelectId(1);
editBox.Text = collection.Collection ?? string.Empty;
_specifier = collection;
break;
case SoundPathSpecifier path:
typeButton.SelectId(2);
editBox.Text = path.Path.ToString();
_specifier = path;
break;
default:
_specifier = null;
break;
}
}
typeButton.OnItemSelected += args =>
{
typeButton.SelectId(args.Id);
editBox.Text = string.Empty;
editBox.Editable = !ReadOnly && typeButton.SelectedId > 0;
if (typeButton.SelectedId == 0)
{
// Dummy value
ValueChanged(new SoundPathSpecifier(""));
}
};
editBox.OnTextEntered += args =>
{
if (string.IsNullOrEmpty(args.Text))
return;
switch (typeButton.SelectedId)
{
case 1:
if (!_protoManager.HasIndex<SoundCollectionPrototype>(args.Text))
return;
_specifier = new SoundCollectionSpecifier(args.Text)
{
Params = _specifier?.Params ?? AudioParams.Default,
};
ValueChanged(_specifier);
break;
case 2:
var path = new ResPath(args.Text);
if (!_resManager.ContentFileExists(path))
return;
_specifier = new SoundPathSpecifier(args.Text)
{
Params = _specifier?.Params ?? AudioParams.Default,
};
ValueChanged(_specifier);
break;
default:
return;
}
};
// Audio params
/* Volume */
var volumeEdit = new LineEdit()
{
Text = _specifier?.Params.Volume.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
volumeEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithVolume(floatValue);
ValueChanged(_specifier);
};
var volumeContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-volume"),
},
volumeEdit,
}
};
/* Pitch */
var pitchEdit = new LineEdit()
{
Text = _specifier?.Params.Pitch.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
pitchEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithPitchScale(floatValue);
ValueChanged(_specifier);
};
var pitchContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-pitch"),
},
pitchEdit,
}
};
/* MaxDistance */
var maxDistanceEdit = new LineEdit()
{
Text = _specifier?.Params.MaxDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
maxDistanceEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithMaxDistance(floatValue);
ValueChanged(_specifier);
};
var maxDistanceContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-max-distance"),
},
maxDistanceEdit,
}
};
/* RolloffFactor */
var rolloffFactorEdit = new LineEdit()
{
Text = _specifier?.Params.RolloffFactor.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
rolloffFactorEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithRolloffFactor(floatValue);
ValueChanged(_specifier);
};
var rolloffFactorContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-rolloff-factor"),
},
rolloffFactorEdit,
}
};
/* ReferenceDistance */
var referenceDistanceEdit = new LineEdit()
{
Text = _specifier?.Params.ReferenceDistance.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
referenceDistanceEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithReferenceDistance(floatValue);
ValueChanged(_specifier);
};
var referenceDistanceContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-reference-distance"),
},
referenceDistanceEdit,
}
};
/* Loop */
var loopButton = new Button()
{
Text = Loc.GetString("vv-sound-loop"),
Pressed = _specifier?.Params.Loop ?? false,
ToggleMode = true,
Disabled = ReadOnly || _specifier == null,
};
loopButton.OnPressed += args =>
{
if (_specifier == null)
return;
_specifier.Params = _specifier.Params.WithLoop(args.Button.Pressed);
ValueChanged(_specifier);
};
/* PlayOffsetSeconds */
var playOffsetEdit = new LineEdit()
{
Text = _specifier?.Params.PlayOffsetSeconds.ToString(CultureInfo.InvariantCulture) ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
playOffsetEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithPlayOffset(floatValue);
ValueChanged(_specifier);
};
var playOffsetContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-play-offset"),
},
playOffsetEdit,
}
};
/* Variation */
var variationEdit = new LineEdit()
{
Text = _specifier?.Params.Variation.ToString() ?? string.Empty,
HorizontalExpand = true,
Editable = !ReadOnly && _specifier != null,
};
variationEdit.OnTextEntered += args =>
{
if (!float.TryParse(args.Text, out var floatValue) || _specifier == null)
return;
_specifier.Params = _specifier.Params.WithVariation(floatValue);
ValueChanged(_specifier);
};
var variationContainer = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Children =
{
new Label()
{
Text = Loc.GetString("vv-sound-variation"),
},
variationEdit,
}
};
var audioParamsControls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
volumeContainer,
pitchContainer,
maxDistanceContainer,
rolloffFactorContainer,
referenceDistanceContainer,
loopButton,
playOffsetContainer,
variationContainer,
}
};
var controls = new BoxContainer()
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Children =
{
pathControls,
audioParamsControls,
}
};
return controls;
}
}

View File

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

View File

@@ -42,6 +42,17 @@ public sealed partial class AudioSystem : SharedAudioSystem
component.Source = new DummyAudioSource();
}
public override void SetMapAudio(Entity<AudioComponent>? audio)
{
if (audio == null)
return;
base.SetMapAudio(audio);
// Also need a global override because clients not near 0,0 won't get the audio.
_pvs.AddGlobalOverride(audio.Value);
}
private void AddAudioFilter(EntityUid uid, AudioComponent component, Filter filter)
{
var count = filter.Count;
@@ -68,7 +79,7 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, Filter playerFilter, bool recordReplay, AudioParams? audioParams = null)
{
var entity = Spawn("Audio", MapCoordinates.Nullspace);
var audio = SetupAudio(entity, filename, audioParams);
@@ -78,8 +89,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, Filter playerFilter, EntityUid uid, bool recordReplay, AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(uid))
{
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
@@ -94,8 +108,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityUid uid, AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(uid))
{
Log.Error($"Tried to play audio on a terminating / deleted entity {ToPrettyString(uid)}. Trace: {Environment.StackTrace}");
@@ -109,8 +126,11 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, Filter playerFilter, EntityCoordinates coordinates, bool recordReplay, AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
@@ -128,9 +148,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
}
/// <inheritdoc />
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string filename, EntityCoordinates coordinates,
public override (EntityUid Entity, AudioComponent Component)? PlayPvs(string? filename, EntityCoordinates coordinates,
AudioParams? audioParams = null)
{
if (string.IsNullOrEmpty(filename))
return null;
if (TerminatingOrDeleted(coordinates.EntityId))
{
Log.Error($"Tried to play coordinates audio on a terminating / deleted entity {ToPrettyString(coordinates.EntityId)}. Trace: {Environment.StackTrace}");
@@ -176,12 +199,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return audio;
}
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, ICommonSession recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, ICommonSession recipient, AudioParams? audioParams = null)
{
return PlayGlobal(filename, Filter.SinglePlayer(recipient), false, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string filename, EntityUid recipient, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayGlobal(string? filename, EntityUid recipient, AudioParams? audioParams = null)
{
if (TryComp(recipient, out ActorComponent? actor))
return PlayGlobal(filename, actor.PlayerSession, audioParams);
@@ -189,12 +212,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null;
}
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, ICommonSession recipient, EntityUid uid, AudioParams? audioParams = null)
{
return PlayEntity(filename, Filter.SinglePlayer(recipient), uid, false, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayEntity(string? filename, EntityUid recipient, EntityUid uid, AudioParams? audioParams = null)
{
if (TryComp(recipient, out ActorComponent? actor))
return PlayEntity(filename, actor.PlayerSession, uid, audioParams);
@@ -202,12 +225,12 @@ public sealed partial class AudioSystem : SharedAudioSystem
return null;
}
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, ICommonSession recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
return PlayStatic(filename, Filter.SinglePlayer(recipient), coordinates, false, audioParams);
}
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
public override (EntityUid Entity, AudioComponent Component)? PlayStatic(string? filename, EntityUid recipient, EntityCoordinates coordinates, AudioParams? audioParams = null)
{
if (TryComp(recipient, out ActorComponent? actor))
return PlayStatic(filename, actor.PlayerSession, coordinates, audioParams);

View File

@@ -89,7 +89,7 @@ namespace Robust.Server
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
[Dependency] private readonly HubManager _hubManager = default!;
[Dependency] private readonly IScriptHost _scriptHost = default!;
[Dependency] private readonly IMetricsManager _metricsManager = default!;
[Dependency] private readonly IMetricsManagerInternal _metricsManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
[Dependency] private readonly ILocalizationManagerInternal _loc = default!;
@@ -749,6 +749,8 @@ namespace Robust.Server
_hubManager.Heartbeat();
_modLoader.BroadcastUpdate(ModUpdateLevel.FramePostEngine, frameEventArgs);
_metricsManager.FrameUpdate();
}
void IPostInjectInit.PostInject()

View File

@@ -66,7 +66,7 @@ public sealed class SpinCommand : LocalizedCommands
}
var physicsSystem = _entities.System<SharedPhysicsSystem>();
physicsSystem.SetAngularDamping(physics, drag);
physicsSystem.SetAngularDamping(target.Value, physics, drag);
physicsSystem.SetAngularVelocity(target.Value, speed, body: physics);
}
}

View File

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

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.Metrics;
using System.Linq;
using Robust.Shared.Utility;
namespace Robust.Server.DataMetrics;
internal sealed partial class MetricsManager : IMeterFactory
{
private readonly Dictionary<string, List<CachedMeter>> _meterCache = new();
private readonly object _meterCacheLock = new();
Meter IMeterFactory.Create(MeterOptions options)
{
if (options.Scope != null && options.Scope != this)
throw new InvalidOperationException("Cannot specify a custom scope when creating a meter");
lock (_meterCacheLock)
{
if (LockedFindCachedMeter(options) is { } cached)
return cached.Meter;
var meter = new Meter(options.Name, options.Version, options.Tags, this);
var meterList = _meterCache.GetOrNew(options.Name);
meterList.Add(new CachedMeter(options.Version, TagsToDict(options.Tags), meter));
return meter;
}
}
private CachedMeter? LockedFindCachedMeter(MeterOptions options)
{
if (!_meterCache.TryGetValue(options.Name, out var metersList))
return null;
var tagsDict = TagsToDict(options.Tags);
foreach (var cachedMeter in metersList)
{
if (cachedMeter.Version == options.Version && TagsMatch(tagsDict, cachedMeter.Tags))
return cachedMeter;
}
return null;
}
private static bool TagsMatch(Dictionary<string, object?> a, Dictionary<string, object?> b)
{
if (a.Count != b.Count)
return false;
foreach (var (key, valueA) in a)
{
if (!b.TryGetValue(key, out var valueB))
return false;
if (!Equals(valueA, valueB))
return false;
}
return true;
}
private static Dictionary<string, object?> TagsToDict(IEnumerable<KeyValuePair<string, object?>>? tags)
{
return tags?.ToDictionary() ?? [];
}
private void DisposeMeters()
{
lock (_meterCacheLock)
{
foreach (var meters in _meterCache.Values)
{
foreach (var meter in meters)
{
meter.Meter.Dispose();
}
}
}
}
private sealed class CachedMeter(string? version, Dictionary<string, object?> tags, Meter meter)
{
public readonly string? Version = version;
public readonly Dictionary<string, object?> Tags = tags;
public readonly Meter Meter = meter;
}
}

View File

@@ -18,13 +18,20 @@ internal sealed partial class MetricsManager
private sealed class ManagedHttpListenerMetricsServer : MetricHandler
{
private readonly ISawmill _sawmill;
private readonly Func<CancellationToken, Task>? _beforeCollect;
private readonly HttpListener _listener;
private readonly CollectorRegistry _registry;
public ManagedHttpListenerMetricsServer(ISawmill sawmill, string host, int port, string url = "metrics/",
CollectorRegistry? registry = null)
public ManagedHttpListenerMetricsServer(
ISawmill sawmill,
string host,
int port,
string url = "metrics/",
CollectorRegistry? registry = null,
Func<CancellationToken, Task>? beforeCollect = null)
{
_sawmill = sawmill;
_beforeCollect = beforeCollect;
_listener = new HttpListener();
_listener.Prefixes.Add($"http://{host}:{port}/{url}");
_registry = registry ?? Metrics.DefaultRegistry;
@@ -57,6 +64,12 @@ internal sealed partial class MetricsManager
{
MetricsEvents.Log.ScrapeStart();
// prometheus-net does have a "before collect" callback of its own.
// But it doesn't get ran before stuff like their System.Diagnostics.Metrics integration,
// So I'm just gonna make my own here.
if (_beforeCollect != null)
await _beforeCollect(cancel);
var stream = resp.OutputStream;
// prometheus-net is a terrible library and have to do all this insanity,
// just to handle the ScrapeFailedException correctly.

View File

@@ -0,0 +1,62 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.IoC;
using Robust.Shared.Timing;
namespace Robust.Server.DataMetrics;
internal sealed partial class MetricsManager
{
//
// Handles the implementation of the "UpdateMetrics" callback.
//
public event Action? UpdateMetrics;
private TimeSpan _fixedUpdateInterval;
private TimeSpan _nextFixedUpdate;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private void InitializeUpdateMetrics()
{
_cfg.OnValueChanged(
CVars.MetricsUpdateInterval,
seconds =>
{
_fixedUpdateInterval = TimeSpan.FromSeconds(seconds);
_nextFixedUpdate = _gameTiming.RealTime + _fixedUpdateInterval;
},
true);
}
public void FrameUpdate()
{
if (_fixedUpdateInterval == TimeSpan.Zero)
return;
var time = _gameTiming.RealTime;
if (_nextFixedUpdate > time)
return;
_nextFixedUpdate = time + _fixedUpdateInterval;
_sawmill.Verbose("Running fixed metrics update");
UpdateMetrics?.Invoke();
}
private async Task BeforeCollectCallback(CancellationToken cancel)
{
if (UpdateMetrics == null)
return;
await _taskManager.TaskOnMainThread(() =>
{
UpdateMetrics?.Invoke();
});
}
}

View File

@@ -1,26 +1,54 @@
using System;
using System.Diagnostics.Metrics;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Prometheus;
using Prometheus.DotNetRuntime;
using Prometheus.DotNetRuntime.Metrics.Producers;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using EventSource = System.Diagnostics.Tracing.EventSource;
#nullable enable
namespace Robust.Server.DataMetrics;
internal sealed partial class MetricsManager : IMetricsManager, IDisposable
/// <summary>
/// Manages OpenTelemetry metrics exposure.
/// </summary>
/// <remarks>
/// <para>
/// If enabled via <see cref="CVars.MetricsEnabled"/>, metrics about the game server are exposed via a HTTP server
/// in an OpenTelemetry-compatible format (Prometheus).
/// </para>
/// <para>
/// Metrics can be added through the types in <c>System.Diagnostics.Metrics</c> or <c>Prometheus</c>.
/// IoC contains an implementation of <see cref="IMeterFactory"/> that can be used to instantiate meters.
/// </para>
/// </remarks>
public interface IMetricsManager
{
/// <summary>
/// An event that gets raised on the main thread when complex metrics should be updated.
/// </summary>
/// <remarks>
/// This event is raised on the main thread before a Prometheus collection happens,
/// and also with a fixed interval if <see cref="CVars.MetricsUpdateInterval"/> is set.
/// You can use it to update complex metrics that can't "just" be stuffed into a counter.
/// </remarks>
event Action UpdateMetrics;
}
internal sealed partial class MetricsManager : IMetricsManagerInternal, IDisposable
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
private bool _initialized;
@@ -55,6 +83,8 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
{
_cfg.OnValueChanged(cVar, _ => Reload());
}
InitializeUpdateMetrics();
}
private async Task Stop()
@@ -73,6 +103,8 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
async void IDisposable.Dispose()
{
DisposeMeters();
await Stop();
_initialized = false;
@@ -100,7 +132,12 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
_sawmill.Info("Prometheus metrics enabled, host: {1} port: {0}", port, host);
var sawmill = Logger.GetSawmill("metrics.server");
_metricServer = new ManagedHttpListenerMetricsServer(sawmill, host, port);
_metricServer = new ManagedHttpListenerMetricsServer(
sawmill,
host,
port,
registry: Metrics.DefaultRegistry,
beforeCollect: BeforeCollectCallback);
_metricServer.Start();
if (_cfg.GetCVar(CVars.MetricsRuntime))
@@ -190,7 +227,8 @@ internal sealed partial class MetricsManager : IMetricsManager, IDisposable
}
}
internal interface IMetricsManager
internal interface IMetricsManagerInternal : IMetricsManager
{
void Initialize();
void FrameUpdate();
}

View File

@@ -49,7 +49,6 @@ public sealed class MapLoaderSystem : EntitySystem
private ISawmill _logLoader = default!;
private ISawmill _logWriter = default!;
private static readonly MapLoadOptions DefaultLoadOptions = new();
private const int MapFormatVersion = 6;
private const int BackwardsVersion = 2;
@@ -132,7 +131,7 @@ public sealed class MapLoaderSystem : EntitySystem
public bool TryLoad(MapId mapId, string path, [NotNullWhen(true)] out IReadOnlyList<EntityUid>? rootUids,
MapLoadOptions? options = null)
{
options ??= DefaultLoadOptions;
options ??= new();
var resPath = new ResPath(path).ToRootedPath();
@@ -280,6 +279,9 @@ public sealed class MapLoaderSystem : EntitySystem
// Load the prototype data onto entities, e.g. transform parents, etc.
LoadEntities(data);
// Assign MapSaveTileMapComponent to all read grids.
SaveGridTileMap(data);
// Build the scene graph / transform hierarchy to know the order to startup entities.
// This also allows us to swap out the root node up front if necessary.
BuildEntityHierarchy(data);
@@ -576,6 +578,19 @@ public sealed class MapLoaderSystem : EntitySystem
meta.LastComponentRemoved = _timing.CurTick;
}
private void SaveGridTileMap(MapData mapData)
{
DebugTools.Assert(_context.TileMap != null);
foreach (var entity in mapData.EntitiesToDeserialize.Keys)
{
if (HasComp<MapGridComponent>(entity))
{
EnsureComp<MapSaveTileMapComponent>(entity).TileMap = _context.TileMap;
}
}
}
private void BuildEntityHierarchy(MapData mapData)
{
_stopwatch.Restart();
@@ -642,11 +657,13 @@ public sealed class MapLoaderSystem : EntitySystem
var xformQuery = GetEntityQuery<TransformComponent>();
// We just need to cache the old mapuid and point to the new mapuid.
if (HasComp<MapComponent>(rootNode))
if (TryComp(rootNode, out MapComponent? mapComp))
{
// If map exists swap out
if (_mapManager.MapExists(data.TargetMap))
if (_mapSystem.TryGetMap(data.TargetMap, out var existing))
{
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
data.MapIsPaused = _mapSystem.IsPaused(existing.Value);
// Map exists but we also have a map file with stuff on it soooo swap out the old map.
if (data.Options.LoadMap)
{
@@ -659,26 +676,28 @@ public sealed class MapLoaderSystem : EntitySystem
data.Options.Rotation = Angle.Zero;
}
_mapManager.SetMapEntity(data.TargetMap, rootNode);
Del(existing);
EnsureComp<LoadedMapComponent>(rootNode);
mapComp.MapId = data.TargetMap;
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
}
// Otherwise just ignore the map in the file.
else
{
var oldRootUid = data.Entities[0];
var newRootUid = _mapManager.GetMapEntityId(data.TargetMap);
data.Entities[0] = newRootUid;
data.Entities[0] = existing.Value;
foreach (var ent in data.Entities)
{
if (ent == newRootUid)
if (ent == existing)
continue;
var xform = xformQuery.GetComponent(ent);
if (!xform.ParentUid.IsValid() || xform.ParentUid.Equals(oldRootUid))
{
_transform.SetParent(ent, xform, newRootUid);
_transform.SetParent(ent, xform, existing.Value);
}
}
@@ -687,16 +706,9 @@ public sealed class MapLoaderSystem : EntitySystem
}
else
{
// If we're loading a file with a map then swap out the entityuid
// TODO: Mapmanager nonsense
var AAAAA = _mapManager.CreateMap(data.TargetMap);
if (!data.MapIsPostInit)
{
_mapManager.AddUninitializedMap(data.TargetMap);
}
_mapManager.SetMapEntity(data.TargetMap, rootNode);
data.MapIsPaused = !data.MapIsPostInit;
mapComp.MapId = data.TargetMap;
DebugTools.Assert(mapComp.LifeStage < ComponentLifeStage.Initializing);
EnsureComp<LoadedMapComponent>(rootNode);
// Nothing should have invalid uid except for the root node.
@@ -705,17 +717,15 @@ public sealed class MapLoaderSystem : EntitySystem
else
{
// No map file root, in that case create a new map / get the one we're loading onto.
var mapNode = _mapManager.GetMapEntityId(data.TargetMap);
if (!mapNode.IsValid())
if (!_mapSystem.TryGetMap(data.TargetMap, out var mapNode))
{
// Map doesn't exist so we'll start it up now so we can re-attach the preinit entities to it for later.
_mapManager.CreateMap(data.TargetMap);
_mapManager.AddUninitializedMap(data.TargetMap);
mapNode = _mapManager.GetMapEntityId(data.TargetMap);
DebugTools.Assert(mapNode.IsValid());
mapNode = _mapSystem.CreateMap(data.TargetMap, false);
}
data.Options.DoMapInit |= _mapSystem.IsInitialized(data.TargetMap);
data.MapIsPaused = _mapSystem.IsPaused(mapNode.Value);
// If anything has an invalid parent (e.g. it's some form of root node) then parent it to the map.
foreach (var ent in data.Entities)
{
@@ -727,12 +737,11 @@ public sealed class MapLoaderSystem : EntitySystem
if (!xform.ParentUid.IsValid())
{
_transform.SetParent(ent, xform, mapNode);
_transform.SetParent(ent, xform, mapNode.Value);
}
}
}
data.MapIsPaused = _mapManager.IsMapPaused(data.TargetMap);
_logLoader.Debug($"Swapped out root node in {_stopwatch.Elapsed}");
}
@@ -880,7 +889,7 @@ public sealed class MapLoaderSystem : EntitySystem
{
EntityManager.SetLifeStage(metadata, EntityLifeStage.MapInitialized);
}
else if (_mapManager.IsMapInitialized(data.TargetMap))
else if (data.Options.DoMapInit)
{
_serverEntityManager.RunMapInit(uid, metadata);
}
@@ -948,7 +957,7 @@ public sealed class MapLoaderSystem : EntitySystem
// Yes, post-init maps do not have EntityLifeStage >= EntityLifeStage.MapInitialized
bool postInit;
if (TryComp(uid, out MapComponent? mapComp))
postInit = !mapComp.MapPreInit;
postInit = mapComp.MapInitialized;
else
postInit = metadata.EntityLifeStage >= EntityLifeStage.MapInitialized;
@@ -981,28 +990,74 @@ public sealed class MapLoaderSystem : EntitySystem
var gridQuery = GetEntityQuery<MapGridComponent>();
var tileDefs = new HashSet<int>();
Dictionary<int, string>? origTileMap = null;
foreach (var ent in entities)
{
if (!gridQuery.TryGetComponent(ent, out var grid))
continue;
var tileEnumerator = grid.GetAllTilesEnumerator(false);
var tileEnumerator = _mapSystem.GetAllTilesEnumerator(ent, grid, ignoreEmpty: false);
while (tileEnumerator.MoveNext(out var tileRef))
{
tileDefs.Add(tileRef.Value.Tile.TypeId);
}
if (TryComp(ent, out MapSaveTileMapComponent? saveTileMap))
origTileMap ??= saveTileMap.TileMap;
}
Dictionary<int, int> tileIdMap;
if (origTileMap != null)
{
tileIdMap = new Dictionary<int, int>();
// We are re-saving a map, so we have an original tile map we can preserve.
foreach (var (origId, prototypeId) in origTileMap)
{
// Skip removed tile definitions.
if (!_tileDefManager.TryGetDefinition(prototypeId, out var definition))
continue;
tileIdMap.Add(definition.TileId, origId);
}
// Assign new IDs for all new tile types.
var nextId = 0;
foreach (var tileId in tileDefs)
{
if (tileIdMap.ContainsKey(tileId))
continue;
// New tile, assign new ID that isn't taken by original tile map.
while (origTileMap.ContainsKey(nextId))
{
nextId += 1;
}
tileIdMap.Add(tileId, nextId);
nextId += 1;
}
}
else
{
// Make no-op tile ID map.
tileIdMap = tileDefs.ToDictionary(x => x, x => x);
}
DebugTools.Assert(
tileIdMap.Count == tileIdMap.Values.Distinct().Count(),
"Tile ID map has double mapped values??");
_context.TileWriteMap = tileIdMap;
var tileMap = new MappingDataNode();
rootNode.Add("tilemap", tileMap);
var ordered = new List<int>(tileDefs);
ordered.Sort();
foreach (var tyleId in ordered)
foreach (var (nativeId, mapId) in tileIdMap.OrderBy(x => x.Key))
{
var tileDef = _tileDefManager[tyleId];
tileMap.Add(tyleId.ToString(CultureInfo.InvariantCulture), tileDef.ID);
tileMap.Add(
mapId.ToString(CultureInfo.InvariantCulture),
_tileDefManager[nativeId].ID);
}
}
@@ -1036,17 +1091,17 @@ public sealed class MapLoaderSystem : EntitySystem
}
}
private bool IsSaveable(EntityUid uid, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> transformQuery)
private bool IsSaveable(EntityUid uid)
{
// Don't serialize things parented to un savable things.
// For example clothes inside a person.
while (uid.IsValid())
{
var meta = metaQuery.GetComponent(uid);
var meta = MetaData(uid);
if (meta.EntityDeleted || meta.EntityPrototype?.MapSavable == false) break;
uid = transformQuery.GetComponent(uid).ParentUid;
uid = Transform(uid).ParentUid;
}
// If we manage to get up to the map (root node) then it's saveable.
@@ -1061,7 +1116,7 @@ public sealed class MapLoaderSystem : EntitySystem
EntityQuery<TransformComponent> transformQuery,
EntityQuery<MapSaveIdComponent> saveCompQuery)
{
if (!IsSaveable(uid, metaQuery, transformQuery))
if (!IsSaveable(uid))
return;
entities.Add(uid);
@@ -1176,11 +1231,12 @@ public sealed class MapLoaderSystem : EntitySystem
foreach (var component in EntityManager.GetComponents(entityUid))
{
if (component is MapSaveIdComponent)
var compType = component.GetType();
var registration = _factory.GetRegistration(compType);
if (registration.Unsaved)
continue;
var compType = component.GetType();
var compName = _factory.GetComponentName(compType);
var compName = registration.Name;
_context.CurrentComponent = compName;
MappingDataNode? compMapping;
MappingDataNode? protMapping = null;

View File

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

View File

@@ -1,416 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Robust.Server.Player;
using Robust.Shared.Enums;
using System.Collections;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Player;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects
namespace Robust.Server.GameObjects;
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
public sealed class UserInterfaceSystem : SharedUserInterfaceSystem
{
[Dependency] private readonly IPlayerManager _playerMan = default!;
[Dependency] private readonly TransformSystem _xformSys = default!;
private EntityQuery<IgnoreUIRangeComponent> _ignoreUIRangeQuery;
private readonly List<ICommonSession> _sessionCache = new();
/// <inheritdoc />
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent<BoundUIWrapMessage>(OnMessageReceived);
_playerMan.PlayerStatusChanged += OnPlayerStatusChanged;
_ignoreUIRangeQuery = GetEntityQuery<IgnoreUIRangeComponent>();
}
public override void Shutdown()
{
base.Shutdown();
_playerMan.PlayerStatusChanged -= OnPlayerStatusChanged;
}
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs args)
{
if (args.NewStatus != SessionStatus.Disconnected)
return;
if (!OpenInterfaces.TryGetValue(args.Session, out var buis))
return;
foreach (var bui in buis.ToArray())
{
CloseShared(bui, args.Session);
}
}
/// <inheritdoc />
public override void Update(float frameTime)
{
var xformQuery = GetEntityQuery<TransformComponent>();
var query = AllEntityQuery<ActiveUserInterfaceComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var activeUis, out var xform))
{
foreach (var ui in activeUis.Interfaces)
{
CheckRange(uid, activeUis, ui, xform, xformQuery);
if (!ui.StateDirty)
continue;
ui.StateDirty = false;
foreach (var (player, state) in ui.PlayerStateOverrides)
{
RaiseNetworkEvent(state, player.Channel);
}
if (ui.LastStateMsg == null)
continue;
foreach (var session in ui.SubscribedSessions)
{
if (!ui.PlayerStateOverrides.ContainsKey(session))
RaiseNetworkEvent(ui.LastStateMsg, session.Channel);
}
}
}
}
/// <summary>
/// Verify that the subscribed clients are still in range of the interface.
/// </summary>
private void CheckRange(EntityUid uid, ActiveUserInterfaceComponent activeUis, PlayerBoundUserInterface ui, TransformComponent transform, EntityQuery<TransformComponent> query)
{
if (ui.InteractionRange <= 0)
return;
// We have to cache the set of sessions because Unsubscribe modifies the original.
_sessionCache.Clear();
_sessionCache.AddRange(ui.SubscribedSessions);
var uiPos = _xformSys.GetWorldPosition(transform, query);
var uiMap = transform.MapID;
foreach (var session in _sessionCache)
{
// The component manages the set of sessions, so this invalid session should be removed soon.
if (!query.TryGetComponent(session.AttachedEntity, out var xform))
continue;
if (_ignoreUIRangeQuery.HasComponent(session.AttachedEntity))
continue;
// Handle pluggable BoundUserInterfaceCheckRangeEvent
var checkRangeEvent = new BoundUserInterfaceCheckRangeEvent(uid, ui, session);
RaiseLocalEvent(uid, ref checkRangeEvent, broadcast: true);
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Pass)
continue;
if (checkRangeEvent.Result == BoundUserInterfaceRangeResult.Fail)
{
CloseUi(ui, session, activeUis);
continue;
}
DebugTools.Assert(checkRangeEvent.Result == BoundUserInterfaceRangeResult.Default);
if (uiMap != xform.MapID)
{
CloseUi(ui, session, activeUis);
continue;
}
var distanceSquared = (uiPos - _xformSys.GetWorldPosition(xform, query)).LengthSquared();
if (distanceSquared > ui.InteractionRangeSqrd)
CloseUi(ui, session, activeUis);
}
}
#region Get BUI
public bool HasUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!Resolve(uid, ref ui))
return false;
return ui.Interfaces.ContainsKey(uiKey);
}
public PlayerBoundUserInterface GetUi(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!Resolve(uid, ref ui))
throw new InvalidOperationException($"Cannot get {typeof(PlayerBoundUserInterface)} from an entity without {typeof(UserInterfaceComponent)}!");
return ui.Interfaces[uiKey];
}
public PlayerBoundUserInterface? GetUiOrNull(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
return TryGetUi(uid, uiKey, out var bui, ui)
? bui
: null;
}
/// <summary>
/// Return UIs a session has open.
/// Null if empty.
/// </summary>
public List<PlayerBoundUserInterface>? GetAllUIsForSession(ICommonSession session)
{
OpenInterfaces.TryGetValue(session, out var value);
return value;
}
#endregion
public bool IsUiOpen(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return bui.SubscribedSessions.Count > 0;
}
public bool SessionHasOpenUi(EntityUid uid, Enum uiKey, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return bui.SubscribedSessions.Contains(session);
}
/// <summary>
/// Sets a state. This can be used for stateful UI updating.
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
/// Pretty much how NanoUI did it back in ye olde BYOND.
/// </summary>
/// <param name="state">
/// The state object that will be sent to all current and future client.
/// This can be null.
/// </param>
/// <param name="session">
/// The player session to send this new state to.
/// Set to null for sending it to every subscribed player session.
/// </param>
public bool TrySetUiState(EntityUid uid,
Enum uiKey,
BoundUserInterfaceState state,
ICommonSession? session = null,
UserInterfaceComponent? ui = null,
bool clearOverrides = true)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
SetUiState(bui, state, session, clearOverrides);
return true;
}
/// <summary>
/// Sets a state. This can be used for stateful UI updating.
/// This state is sent to all clients, and automatically sent to all new clients when they open the UI.
/// Pretty much how NanoUI did it back in ye olde BYOND.
/// </summary>
/// <param name="state">
/// The state object that will be sent to all current and future client.
/// This can be null.
/// </param>
/// <param name="session">
/// The player session to send this new state to.
/// Set to null for sending it to every subscribed player session.
/// </param>
public void SetUiState(PlayerBoundUserInterface bui, BoundUserInterfaceState state, ICommonSession? session = null, bool clearOverrides = true)
{
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), new UpdateBoundStateMessage(state), bui.UiKey);
if (session == null)
{
bui.LastStateMsg = msg;
if (clearOverrides)
bui.PlayerStateOverrides.Clear();
}
else
{
bui.PlayerStateOverrides[session] = msg;
}
bui.StateDirty = true;
}
#region Close
protected override void CloseShared(PlayerBoundUserInterface bui, ICommonSession session, ActiveUserInterfaceComponent? activeUis = null)
{
var owner = bui.Owner;
bui._subscribedSessions.Remove(session);
bui.PlayerStateOverrides.Remove(session);
if (OpenInterfaces.TryGetValue(session, out var buis))
buis.Remove(bui);
RaiseLocalEvent(owner, new BoundUIClosedEvent(bui.UiKey, owner, session));
if (bui._subscribedSessions.Count == 0)
DeactivateInterface(bui.Owner, bui, activeUis);
}
/// <summary>
/// Closes this all interface for any clients that have any open.
/// </summary>
public bool TryCloseAll(EntityUid uid, Shared.GameObjects.ActiveUserInterfaceComponent? aui = null)
{
if (!Resolve(uid, ref aui, false))
return false;
foreach (var ui in aui.Interfaces)
{
CloseAll(ui);
}
return true;
}
/// <summary>
/// Closes this specific interface for any clients that have it open.
/// </summary>
public bool TryCloseAll(EntityUid uid, Enum uiKey, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
CloseAll(bui);
return true;
}
/// <summary>
/// Closes this interface for any clients that have it open.
/// </summary>
public void CloseAll(PlayerBoundUserInterface bui)
{
foreach (var session in bui.SubscribedSessions.ToArray())
{
CloseUi(bui, session);
}
}
#endregion
#region SendMessage
/// <summary>
/// Send a BUI message to all connected player sessions.
/// </summary>
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
SendUiMessage(bui, message);
return true;
}
/// <summary>
/// Send a BUI message to all connected player sessions.
/// </summary>
public void SendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message)
{
var msg = new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey);
foreach (var session in bui.SubscribedSessions)
{
RaiseNetworkEvent(msg, session.Channel);
}
}
/// <summary>
/// Send a BUI message to a specific player session.
/// </summary>
public bool TrySendUiMessage(EntityUid uid, Enum uiKey, BoundUserInterfaceMessage message, ICommonSession session, UserInterfaceComponent? ui = null)
{
if (!TryGetUi(uid, uiKey, out var bui, ui))
return false;
return TrySendUiMessage(bui, message, session);
}
/// <summary>
/// Send a BUI message to a specific player session.
/// </summary>
public bool TrySendUiMessage(PlayerBoundUserInterface bui, BoundUserInterfaceMessage message, ICommonSession session)
{
if (!bui.SubscribedSessions.Contains(session))
return false;
RaiseNetworkEvent(new BoundUIWrapMessage(GetNetEntity(bui.Owner), message, bui.UiKey), session.Channel);
return true;
}
#endregion
}
/// <summary>
/// Raised by <see cref="UserInterfaceSystem"/> to check whether an interface is still accessible by its user.
/// </summary>
[ByRefEvent]
[PublicAPI]
public struct BoundUserInterfaceCheckRangeEvent
{
/// <summary>
/// The entity owning the UI being checked for.
/// </summary>
public readonly EntityUid Target;
/// <summary>
/// The UI itself.
/// </summary>
/// <returns></returns>
public readonly PlayerBoundUserInterface UserInterface;
/// <summary>
/// The player for which the UI is being checked.
/// </summary>
public readonly ICommonSession Player;
/// <summary>
/// The result of the range check.
/// </summary>
public BoundUserInterfaceRangeResult Result;
public BoundUserInterfaceCheckRangeEvent(
EntityUid target,
PlayerBoundUserInterface userInterface,
ICommonSession player)
{
Target = target;
UserInterface = userInterface;
Player = player;
}
}
/// <summary>
/// Possible results for a <see cref="BoundUserInterfaceCheckRangeEvent"/>.
/// </summary>
public enum BoundUserInterfaceRangeResult : byte
{
/// <summary>
/// Run built-in range check.
/// </summary>
Default,
/// <summary>
/// Range check passed, UI is accessible.
/// </summary>
Pass,
/// <summary>
/// Range check failed, UI is inaccessible.
/// </summary>
Fail
}
}

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